package net.sourceforge.retroweaver; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.jar.JarEntry; import java.util.jar.JarFile; import net.sourceforge.retroweaver.event.VerifierListener; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Attribute; import org.objectweb.asm.ClassAdapter; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.MethodAdapter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.EmptyVisitor; /** * Reads through a class file searching for references to classes, methods, or * fields, which don't exist on the specified classpath. This is primarily * useful when trying to target one JDK while using the compiler for another. */ public class RefVerifier extends ClassAdapter { private final int target; private String currentclassName; private final RetroWeaverClassLoader classLoader; private final List<String> classPathArray; private Set<String> failedClasses; private final VerifierListener listener; private int warningCount; private final List<String> classes; private final Map<String, SoftReference<ClassReader>> classReaderCache = new HashMap<String, SoftReference<ClassReader>>(); private static final String nl = System.getProperty("line.separator"); public RefVerifier(int target, ClassVisitor cv, List<String> classPathArray, VerifierListener listener) { super(cv); classLoader = new RetroWeaverClassLoader(); this.classPathArray = classPathArray; this.listener = listener; this.target = target; classes = new LinkedList<String>(); } public void addClass(String className) { classes.add(className); } public void verifyJarFile(String jarFileName) throws IOException { JarFile jarFile = new JarFile(jarFileName); int count = classes.size(); if (count > 0) { listener.verifyPathStarted("Verifying " + count + (count == 1?" class":" classes")); } classLoader.setClassPath(classPathArray); for (String name : classes) { JarEntry entry = jarFile.getJarEntry(name); InputStream is = jarFile.getInputStream(entry); verifyClass(is); } } public void verifyFiles() throws IOException { int count = classes.size(); if (count > 0) { listener.verifyPathStarted("Verifying " + count + (count == 1?" class":" classes")); } classLoader.setClassPath(classPathArray); for (String sourcePath : classes) { verifyClass(new FileInputStream(sourcePath)); } } private void verifySingleClass(String classFileName) throws IOException { classLoader.setClassPath(classPathArray); verifyClass(new FileInputStream(classFileName)); } private void verifyClass(InputStream sourceStream) throws IOException { failedClasses = new HashSet<String>(); ClassReader cr = new ClassReader(sourceStream); cr.accept(this, 0); } private void unknowClassWarning(String className, String msg) { StringBuffer report = new StringBuffer().append(currentclassName) .append(": unknown class ").append(className); if (msg != null) { report.append(": ").append(msg); } warning(report); } private void unknownFieldWarning(String name, String desc, String msg) { StringBuffer report = new StringBuffer().append(currentclassName) .append(": unknown field ").append(name).append('/').append(desc.replace('/', '.')); if (msg != null) { report.append(", ").append(msg); } warning(report); } private void unknownMethodWarning(String name, String desc, String msg) { StringBuffer report = new StringBuffer().append(currentclassName) .append(": unknown method ").append(name).append('/').append(desc.replace('/', '.')); if (msg != null) { report.append(", ").append(msg); } warning(report); } private void invalidClassVersion(String className, int target, int version) { StringBuffer report = new StringBuffer().append(className) .append(": invalid class version ").append(version).append(", target is ").append(target); warning(report); } private void warning(StringBuffer report) { warningCount++; listener.acceptWarning(report.toString()); } public void displaySummary() { if (warningCount != 0) { listener.displaySummary(warningCount); } } private ClassReader getClassReader(String className) throws ClassNotFoundException { ClassReader reader = null; SoftReference<ClassReader> ref = classReaderCache.get(className); if (ref != null) { reader = ref.get(); } if (reader == null) { byte b[] = classLoader.getClassData(className); reader = new ClassReader(b); classReaderCache.put(className, new SoftReference<ClassReader>(reader)); // class file version should not be higher than target int version = reader.readShort(6); // get major number only if (version > target) { invalidClassVersion(className.replace('/', '.'), target, version); } } return reader; } public static String getUsage() { return "Usage: RefVerifier <options>" + nl + " Options: " + nl + " -class <path to class to verify> (required) " + nl + " -cp <classpath containing valid classes> (required)"; } public static void main(String[] args) throws IOException { List<String> classpath = new ArrayList<String>(); String classfile = null; for (int i = 0; i < args.length; ++i) { String command = args[i]; ++i; if ("-class".equals(command)) { classfile = args[i]; } else if ("-cp".equals(command)) { String path = args[i]; StringTokenizer st = new StringTokenizer(path, File.pathSeparator); while (st.hasMoreTokens()) { classpath.add(st.nextToken()); } } else { System.out.println("I don't understand the command: " + command); // NOPMD by xlv System.out.println(); // NOPMD by xlv System.out.println(getUsage()); // NOPMD by xlv return; } } if (classfile == null) { System.out.println("Option \"-class\" is required."); // NOPMD by xlv System.out.println(); // NOPMD by xlv System.out.println(getUsage()); // NOPMD by xlv return; } RefVerifier vr = new RefVerifier(Weaver.VERSION_1_4, EMPTY_VISITOR, classpath, new DefaultListener(true)); vr.verifySingleClass(classfile); vr.displaySummary(); } private void checkClassName(String className) { Type t = Type.getType(className); String name; switch (t.getSort()) { case Type.ARRAY: t = t.getElementType(); if (t.getSort() != Type.OBJECT) { return; } // fall through to object processing case Type.OBJECT: name = t.getClassName(); break; default: return; } checkSimpleClassName(name); } private void checkClassNameInType(String className) { switch (className.charAt(0)) { case 'L': if (className.endsWith(";")) { checkClassName(className); } else { checkSimpleClassName(className); } break; case '[': checkClassName(className); break; default: checkSimpleClassName(className); } } private void checkSimpleClassName(String className) { String name = className.replace('.', '/'); try { getClassReader(name); } catch (ClassNotFoundException e) { failedClasses.add(name); unknowClassWarning(name.replace('/', '.'), null); } } // visitor methods public void visit( final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { listener.verifyClassStarted("Verifying " + name); currentclassName = name.replace('/', '.'); if (superName != null) { checkSimpleClassName(superName); } if (interfaces != null) { for (int i = 0; i < interfaces.length; ++i) { checkSimpleClassName(interfaces[i]); } } cv.visit(version, access, name, signature, superName, interfaces); } public void visitOuterClass( final String owner, final String name, final String desc) { checkSimpleClassName(owner); cv.visitOuterClass(owner, name, desc); } public void visitInnerClass( final String name, final String outerName, final String innerName, final int access) { if (name != null) { checkSimpleClassName(name); } if (outerName != null) { checkSimpleClassName(outerName); } cv.visitInnerClass(name, outerName, innerName, access); } public MethodVisitor visitMethod( final int access, final String name, final String desc, final String signature, final String[] exceptions) { if (exceptions != null) { for (String s: exceptions) { checkSimpleClassName(s); } } return new MethodVerifier(cv.visitMethod(access, name, desc, signature, exceptions)); } public AnnotationVisitor visitAnnotation( final String desc, final boolean visible) { checkClassNameInType(desc); return new AnnotationVerifier(cv.visitAnnotation(desc, visible)); } private class AnnotationVerifier implements AnnotationVisitor { private final AnnotationVisitor av; AnnotationVerifier(final AnnotationVisitor av) { this.av = av; } public void visit(final String name, final Object value) { av.visit(name, value); } public void visitEnum(final String name, final String desc, final String value) { checkClassNameInType(desc); av.visitEnum(name, desc, value); } public AnnotationVisitor visitAnnotation(final String name, final String desc) { checkClassNameInType(desc); return av.visitAnnotation(name, desc); } public AnnotationVisitor visitArray(final String name) { return av.visitArray(name); } public void visitEnd() { av.visitEnd(); } } private class MethodVerifier extends MethodAdapter { MethodVerifier(MethodVisitor mv) { super(mv); } public AnnotationVisitor visitAnnotationDefault() { return new AnnotationVerifier(mv.visitAnnotationDefault()); } public AnnotationVisitor visitAnnotation( final String desc, final boolean visible) { checkClassNameInType(desc); return new AnnotationVerifier(mv.visitAnnotation(desc, visible)); } public void visitTypeInsn(int opcode, String desc) { checkClassNameInType(desc); mv.visitTypeInsn(opcode, desc); } public void visitFieldInsn(int opcode, String owner, String name, String desc) { // Don't report a field error, about a class for which we've // already shown an error if (!failedClasses.contains(owner)) { try { if (!findField(owner, name, desc)) { unknownFieldWarning(name,desc, "Field not found in " + owner.replace('/', '.')); } } catch (ClassNotFoundException e) { unknownFieldWarning(name,desc, "The class, " + owner.replace('/', '.') + ", could not be located: " + e.getMessage()); } } mv.visitFieldInsn(opcode, owner, name, desc); } public void visitMethodInsn(int opcode, String owner, String name, String desc) { if (!failedClasses.contains(owner) && owner.charAt(0) != '[') { // Don't report a method error, about a class for which we've // already shown an error. // We just ignore methods called on arrays, because we know // they must exist try { if (!findMethod(owner, name, desc)) { unknownMethodWarning(name, desc, "Method not found in " + owner.replace('/', '.')); } } catch (ClassNotFoundException e) { unknownMethodWarning(name, desc, "The class, " + owner.replace('/', '.') + ", could not be located: " + e.getMessage()); } } mv.visitMethodInsn(opcode, owner, name, desc); } public void visitMultiANewArrayInsn(String desc, int dims) { checkClassName(desc); mv.visitMultiANewArrayInsn(desc, dims); } public void visitLocalVariable( String name, String desc, String signature, Label start, Label end, int index) { checkClassName(desc); mv.visitLocalVariable(name, desc, signature, start, end, index); } } private boolean findField(String owner, final String name, final String c) throws ClassNotFoundException { String javaClassName = owner; while (true) { ClassReader reader = getClassReader(javaClassName); FindFieldOrMethodClassVisitor visitor = new FindFieldOrMethodClassVisitor(false, name, c); try { reader.accept(visitor, 0); } catch (Success s) { return true; } String[] is = visitor.classInterfaces; for (String i : is) { if (findField(i, name, c)) { return true; } } if ("java/lang/Object".equals(javaClassName)) { return false; } javaClassName = visitor.superClassName; } } private boolean findMethod(final String owner, final String name, final String desc) throws ClassNotFoundException { String javaClassName = owner; while (true) { ClassReader reader = getClassReader(javaClassName); FindFieldOrMethodClassVisitor visitor = new FindFieldOrMethodClassVisitor(true, name, desc); try { reader.accept(visitor, 0); } catch (Success s) { return true; } if (visitor.isInterface || visitor.isAbstract) { String[] is = visitor.classInterfaces; for (String i : is) { if (findMethod(i, name, desc)) { return true; } } if (visitor.isInterface) { return false; } } if ("java/lang/Object".equals(javaClassName)) { return false; } javaClassName = visitor.superClassName; } } private static final EmptyVisitor EMPTY_VISITOR = new EmptyVisitor(); private static class Success extends RuntimeException {}; // Visitor to search for fields or methods in supplier classes private static class FindFieldOrMethodClassVisitor implements ClassVisitor { FindFieldOrMethodClassVisitor(boolean methdodMatcher, final String name, final String desc) { this.searchedName = name; this.searchedDesc = desc; this.methdodMatcher = methdodMatcher; } private final boolean methdodMatcher; private final String searchedName; private final String searchedDesc; protected String classInterfaces[]; protected String superClassName; protected boolean isInterface; protected boolean isAbstract; public void visit( final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { classInterfaces = interfaces; superClassName = superName; isInterface = (access & Opcodes.ACC_INTERFACE) != 0; isAbstract = (access & Opcodes.ACC_ABSTRACT) != 0; } public void visitSource(final String source, final String debug) { } public void visitOuterClass( final String owner, final String name, final String desc) { } public AnnotationVisitor visitAnnotation( final String desc, final boolean visible) { return EMPTY_VISITOR; } public void visitAttribute(final Attribute attr) { } public void visitInnerClass( final String name, final String outerName, final String innerName, final int access) { } public FieldVisitor visitField( final int access, final String name, final String desc, final String signature, final Object value) { if (!methdodMatcher && name.equals(searchedName) && desc.equals(searchedDesc)) { throw new Success(); } return null; } public MethodVisitor visitMethod( int access, String name, String desc, String signature, String[] exceptions) { if (methdodMatcher && name.equals(searchedName) && desc.equals(searchedDesc)) { throw new Success(); } return null; } public void visitEnd() { } } public static class DefaultListener implements VerifierListener { private final boolean verbose; DefaultListener(boolean verbose) { this.verbose = verbose; } public void verifyPathStarted(String msg) { System.out.println("[RefVerifier] " + msg); // NOPMD by xlv } public void verifyClassStarted(String msg) { if (verbose) { System.out.println("[RefVerifier] " + msg); // NOPMD by xlv } } public void acceptWarning(String msg) { System.out.println("[RefVerifier] " + msg); // NOPMD by xlv } public void displaySummary(int warningCount) { System.out.println("[RefVerifier] Verification complete, " + warningCount + " warning(s)."); // NOPMD by xlv } } }