package com.rc.retroweaver;
import com.rc.retroweaver.runtime.*;
import java.io.*;
import java.util.*;
import jace.parser.*;
import jace.parser.method.*;
import jace.parser.field.*;
import jace.parser.constant.*;
import jace.metaclass.*;
import jace.autoproxy.*;
/**
* 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.
*
* @author Toby Reyelts
*/
public class RefVerifier {
List<String> classpath;
String classfile;
ClassFile file;
ConstantPool pool;
ClassSource classSource;
Set<String> failedClasses;
Listener listener;
public static interface Listener {
public void verifyStarted( String msg );
public void acceptWarning( String msg );
}
private static final String nl = System.getProperty( "line.separator" );
private static Set<String> primitiveTypes = new HashSet<String>();
static {
primitiveTypes.add( "java/lang/Boolean" );
primitiveTypes.add( "java/lang/Byte" );
primitiveTypes.add( "java/lang/Character" );
primitiveTypes.add( "java/lang/Short" );
primitiveTypes.add( "java/lang/Integer" );
primitiveTypes.add( "java/lang/Float" );
primitiveTypes.add( "java/lang/Long" );
primitiveTypes.add( "java/lang/Double" );
}
public RefVerifier( List<String> classpath, Listener listener ) {
this.classpath = classpath;
classSource = new ClassSource( classpath );
this.listener = listener;
}
public void verify( String classfile ) throws IOException {
failedClasses = new HashSet<String>();
this.classfile = classfile;
file = new ClassFile( classfile );
pool = file.getConstantPool();
listener.verifyStarted( "Verifying " + classfile );
MetaClassFactory mcf = new MetaClassFactory();
// Search the constant pool looking for Class, MethodRef, InterfaceMethodRef, and FieldRef constants
// This handles any class (including superclasses and interfaces), method, or field that has been referred to.
nextConstant: for ( int i = 0; i < pool.getSize(); ++i ) {
Constant c = pool.getConstant( i );
if ( c instanceof ClassConstant ) {
String className = c.toString();
// System.out.println( "class name: " + className );
// Don't bother with primitives or arrays of primitives
MetaClass metaClass = mcf.getMetaClass( className, false, false );
// System.out.println( "MetaClass: " + metaClass );
if ( metaClass instanceof ArrayMetaClass ) {
metaClass = ( ( ArrayMetaClass ) metaClass ).getBaseClass();
// System.out.println( "Base MetaClass: " + metaClass );
className = metaClass.getFullyQualifiedName( "/" );
if ( metaClass.isPrimitive() ) {
continue;
}
metaClass = mcf.getMetaClass( className, true, false );
// System.out.println( "Base MetaClass 2: " + metaClass );
className = metaClass.getFullyQualifiedName( "/" );
}
boolean couldBeFound = true;
InputStream input = null;
try {
classSource.openClass( className );
}
catch ( NoClassDefFoundError e ) {
couldBeFound = false;
}
finally {
try {
if ( input != null ) {
input.close();
}
}
catch ( IOException e ) {
}
}
if ( ! couldBeFound ) {
failedClasses.add( className );
report( c );
continue;
}
}
else if ( c instanceof MethodRefConstant || c instanceof InterfaceMethodRefConstant ) {
TypeInfo t = getTypeInfo( ( TypedConstant ) c );
InputStream input = null;
// Don't report a method error, about a class for which we've already shown an error
if ( failedClasses.contains( t.ownerClass ) ) {
continue;
}
String className = t.ownerClass;
if ( mcf.getMetaClass( className, false, false ) instanceof ArrayMetaClass ) {
// We just ignore methods called on arrays, because we know they must exist
continue;
}
try {
input = classSource.openClass( className );
ClassFile cf = new ClassFile( input );
Collection<ClassMethod> methods = cf.getMethods();
for ( ClassMethod m : methods ) {
if ( m.getName().equals( t.name ) && m.getDescriptor().equals( t.type ) ) {
continue nextConstant;
}
}
}
catch ( NoClassDefFoundError e ) {
report( c, "The parent class, " + className + ", could not be located." );
continue;
}
finally {
try {
if ( input != null ) {
input.close();
}
}
catch ( IOException e ) {
}
}
// For some reason, the compiler is specifying the method call to name() directly on the enum
// subclass instead of on Enum. Frankly, I don't know whether this illegal or not. It's, at the least,
// out of line with all of the rest of the compiler calls I've seen which all go directly to the subclass that
// actually defines the method. For now, we're just going to glaze over this, but once we get an official
// answer about what is allowed, I may have to add a lot more code to search through subclasses
// as well as the current class.
//
if ( file.getSuperClassName().equals( Enum_.class.getName().replace( '.', '/' ) ) && t.name.equals( "name" ) ) {
continue;
}
// When Retroweaver fixes up autoboxing calls, it doesn't remove the MethodRef constants that
// point to the 1.5 autoboxing from the constant pool. It doesn't hurt anything, but it does trigger
// a false alarm here. It'd probably be nice to fix Retroweaver to actually remove/change those MethodRef
// constants, but it's easier to just ignore the autoboxing methods here, for now.
//
if ( t.name.equals( "valueOf" ) && primitiveTypes.contains( t.ownerClass ) ) {
continue;
}
report( c, "Method not found in " + className );
}
else if ( c instanceof FieldRefConstant ) {
TypeInfo t = getTypeInfo( ( TypedConstant ) c );
InputStream input = null;
// Don't report a field error, about a class for which we've already shown an error
if ( failedClasses.contains( t.ownerClass ) ) {
continue;
}
try {
input = classSource.openClass( t.ownerClass );
ClassFile cf = new ClassFile( input );
Collection<ClassField> fields = cf.getFields();
for ( ClassField f : fields ) {
if ( f.getName().equals( t.name ) && f.getDescriptor().equals( t.type ) ) {
continue nextConstant;
}
}
}
catch ( NoClassDefFoundError e ) {
report( c, "The parent class, " + t.ownerClass + ", could not be located." );
continue;
}
finally {
try {
if ( input != null ) {
input.close();
}
}
catch ( IOException e ) {
}
}
report( c, "Field not found in " + t.ownerClass );
}
}
}
private static class TypeInfo {
String ownerClass;
String name;
String type;
}
public TypeInfo getTypeInfo( TypedConstant c ) {
TypeInfo t = new TypeInfo();
t.ownerClass = pool.getConstantAt( c.getClassIndex() ).toString();
int index = c.getNameAndTypeIndex();
NameAndTypeConstant ntc = ( NameAndTypeConstant ) pool.getConstantAt( index );
t.name = pool.getConstantAt( ntc.getNameIndex() ).toString();
t.type = pool.getConstantAt( ntc.getDescriptorIndex() ).toString();
return t;
}
private void report( Constant c ) {
report( c, null );
}
private void report( Constant c, String msg ) {
if ( c instanceof ClassConstant ) {
report( ( ClassConstant ) c, msg );
}
else if ( c instanceof TypedConstant ) {
report( ( TypedConstant ) c, msg );
}
else {
throw new RuntimeException( "Unexpected constant type" );
}
}
private void report( ClassConstant c, String msg ) {
String report = "While processing, " + classfile + ". Failed to find the class, " + c + ", on the class path";
if ( msg != null ) {
report += ": " + msg;
}
listener.acceptWarning( report );
}
private void report( TypedConstant c, String msg ) {
TypeInfo t = getTypeInfo( c );
String type = c instanceof FieldRefConstant ? "field" : "method";
String report = "While processing, " + classfile + ". Failed to find the " + type + ", " + t.name + "/" + t.type;
if ( msg != null ) {
report += ", " + msg;
}
listener.acceptWarning( report );
}
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 Exception {
List<String> classpath = new ArrayList<String>();
String classfile = null;
for ( int i = 0; i < args.length; ++i ) {
String command = args[ i ];
++i;
if ( command.equals( "-class" ) ) {
classfile = args[ i ];
}
else if ( command.equals( "-cp" ) ) {
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 );
System.out.println();
System.out.println( getUsage() );
return;
}
}
if ( classfile == null ) {
System.out.println( "Option \"-class\" is required." );
System.out.println();
System.out.println( getUsage() );
return;
}
RefVerifier vr = new RefVerifier( classpath, new Listener() {
public void verifyStarted( String msg ) {
System.out.println( "[RefVerifier] " + msg );
}
public void acceptWarning( String msg ) {
System.out.println( "[RefVerifier] " + msg );
}
} );
vr.verify( classfile );
}
}