diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java index 46aa3f16..52f510ae 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java @@ -290,6 +290,15 @@ public class MethodDefinition { return false; } + private boolean needsAnalyzed() { + for (Instruction instruction: encodedMethod.codeItem.getInstructions()) { + if (instruction.opcode.odexOnly()) { + return true; + } + } + return false; + } + private List getMethodItems() { ArrayList methodItems = new ArrayList(); @@ -297,7 +306,8 @@ public class MethodDefinition { return methodItems; } - if (baksmali.registerInfo != 0 || baksmali.deodex || baksmali.verify) { + if ((baksmali.registerInfo != 0) || baksmali.verify || + (baksmali.deodex && needsAnalyzed())) { addAnalyzedInstructionMethodItems(methodItems); } else { addInstructionMethodItems(methodItems); diff --git a/baksmali/src/main/java/org/jf/baksmali/baksmali.java b/baksmali/src/main/java/org/jf/baksmali/baksmali.java index 9db30317..549d8eab 100644 --- a/baksmali/src/main/java/org/jf/baksmali/baksmali.java +++ b/baksmali/src/main/java/org/jf/baksmali/baksmali.java @@ -75,16 +75,6 @@ public class baksmali { baksmali.bootClassPath = bootClassPath; baksmali.verify = verify; - ClassPath.ClassPathErrorHandler classPathErrorHandler = null; - if (ignoreErrors) { - classPathErrorHandler = new ClassPath.ClassPathErrorHandler() { - public void ClassPathError(String className, Exception ex) { - System.err.println(String.format("Skipping %s", className)); - ex.printStackTrace(System.err); - } - }; - } - if (registerInfo != 0 || deodex || verify) { try { String[] extraBootClassPathArray = null; @@ -101,15 +91,14 @@ public class baksmali { if (extraBootClassPathArray == null && isExtJar(dexFilePath)) { extraBootClassPathArray = new String[] {"framework.jar"}; } - ClassPath.InitializeClassPathFromOdex(classPathDirs, extraBootClassPathArray, dexFilePath, dexFile, - classPathErrorHandler); + ClassPath.InitializeClassPathFromOdex(classPathDirs, extraBootClassPathArray, dexFilePath, dexFile); } else { String[] bootClassPathArray = null; if (bootClassPath != null) { bootClassPathArray = bootClassPath.split(":"); } ClassPath.InitializeClassPath(classPathDirs, bootClassPathArray, extraBootClassPathArray, - dexFilePath, dexFile, classPathErrorHandler); + dexFilePath, dexFile); } if (inlineTable != null) { @@ -156,15 +145,6 @@ public class baksmali { * package name are separated by '/' */ - if (registerInfo != 0 || deodex || verify) { - //If we are analyzing the bytecode, make sure that this class is loaded into the ClassPath. If it isn't - //then there was some error while loading it, and we should skip it - ClassPath.ClassDef classDef = ClassPath.getClassDef(classDefItem.getClassType(), false); - if (classDef == null || classDef instanceof ClassPath.UnresolvedClassDef) { - continue; - } - } - String classDescriptor = classDefItem.getClassType().getTypeDescriptor(); //validate that the descriptor is formatted like we expect @@ -206,6 +186,7 @@ public class baksmali { } catch (Exception ex) { System.err.println("\n\nError occured while disassembling class " + classDescriptor.replace('/', '.') + " - skipping class"); ex.printStackTrace(); + smaliFile.delete(); } finally { diff --git a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ClassPath.java b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ClassPath.java index 35077604..96878d96 100644 --- a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ClassPath.java +++ b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ClassPath.java @@ -33,6 +33,8 @@ import org.jf.dexlib.Util.AccessFlags; import org.jf.dexlib.Util.ExceptionWithContext; import org.jf.dexlib.Util.SparseArray; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.io.File; import java.util.*; import java.util.regex.Matcher; @@ -45,19 +47,13 @@ public class ClassPath { private static ClassPath theClassPath = null; private final HashMap classDefs; - protected ClassDef javaLangObjectClassDef; //Ljava/lang/Object; - - //This is only used while initialing the class path. It is set to null after initialization has finished. - private LinkedHashMap tempClasses; + protected ClassDef javaLangObjectClassDef; //cached ClassDef for Ljava/lang/Object; + // Contains the classes that we haven't loaded yet + private HashMap unloadedClasses; private static final Pattern dalvikCacheOdexPattern = Pattern.compile("@([^@]+)@classes.dex$"); - - public static interface ClassPathErrorHandler { - void ClassPathError(String className, Exception ex); - } - /** * Initialize the class path using the dependencies from an odex file * @param classPathDirs The directories to search for boot class path files @@ -65,12 +61,9 @@ public class ClassPath { * from the odex file * @param dexFilePath The path of the dex file (used for error reporting purposes only) * @param dexFile The DexFile to load - it must represents an odex file - * @param errorHandler a ClassPathErrorHandler object to receive and handle any errors that occur while loading - * classes */ public static void InitializeClassPathFromOdex(String[] classPathDirs, String[] extraBootClassPathEntries, - String dexFilePath, DexFile dexFile, - ClassPathErrorHandler errorHandler) { + String dexFilePath, DexFile dexFile) { if (!dexFile.isOdex()) { throw new ExceptionWithContext("Cannot use InitialiazeClassPathFromOdex with a non-odex DexFile"); } @@ -107,8 +100,7 @@ public class ClassPath { } theClassPath = new ClassPath(); - theClassPath.initClassPath(classPathDirs, bootClassPath, extraBootClassPathEntries, dexFilePath, dexFile, - errorHandler); + theClassPath.initClassPath(classPathDirs, bootClassPath, extraBootClassPathEntries, dexFilePath, dexFile); } /** @@ -121,15 +113,13 @@ public class ClassPath { * classes */ public static void InitializeClassPath(String[] classPathDirs, String[] bootClassPath, - String[] extraBootClassPathEntries, String dexFilePath, DexFile dexFile, - ClassPathErrorHandler errorHandler) { + String[] extraBootClassPathEntries, String dexFilePath, DexFile dexFile) { if (theClassPath != null) { throw new ExceptionWithContext("Cannot initialize ClassPath multiple times"); } theClassPath = new ClassPath(); - theClassPath.initClassPath(classPathDirs, bootClassPath, extraBootClassPathEntries, dexFilePath, dexFile, - errorHandler); + theClassPath.initClassPath(classPathDirs, bootClassPath, extraBootClassPathEntries, dexFilePath, dexFile); } private ClassPath() { @@ -137,8 +127,8 @@ public class ClassPath { } private void initClassPath(String[] classPathDirs, String[] bootClassPath, String[] extraBootClassPathEntries, - String dexFilePath, DexFile dexFile, ClassPathErrorHandler errorHandler) { - tempClasses = new LinkedHashMap(); + String dexFilePath, DexFile dexFile) { + unloadedClasses = new LinkedHashMap(); if (bootClassPath != null) { for (String bootClassPathEntry: bootClassPath) { @@ -156,32 +146,12 @@ public class ClassPath { loadDexFile(dexFilePath, dexFile); } - - for (String classType: tempClasses.keySet()) { - ClassDef classDef = null; - try { - classDef = ClassPath.loadClassDef(classType); - assert classDef != null; - } catch (Exception ex) { - if (errorHandler != null) { - errorHandler.ClassPathError(classType, ex); - } else { - throw ExceptionWithContext.withContext(ex, - String.format("Error while loading ClassPath class %s", classType)); - } - } - - if (classType.equals("Ljava/lang/Object;")) { - this.javaLangObjectClassDef = classDef; - } - } + javaLangObjectClassDef = getClassDef("Ljava/lang/Object;", false); for (String primitiveType: new String[]{"Z", "B", "S", "C", "I", "J", "F", "D"}) { ClassDef classDef = new PrimitiveClassDef(primitiveType); classDefs.put(primitiveType, classDef); } - - tempClasses = null; } private void loadBootClassPath(String[] classPathDirs, String bootClassPathEntry) { @@ -240,10 +210,10 @@ public class ClassPath { private void loadDexFile(String dexFilePath, DexFile dexFile) { for (ClassDefItem classDefItem: dexFile.ClassDefsSection.getItems()) { try { - TempClassInfo tempClassInfo = new TempClassInfo(dexFilePath, classDefItem); + UnresolvedClassInfo unresolvedClassInfo = new UnresolvedClassInfo(dexFilePath, classDefItem); - if (!tempClasses.containsKey(tempClassInfo.classType)) { - tempClasses.put(tempClassInfo.classType, tempClassInfo); + if (!unloadedClasses.containsKey(unresolvedClassInfo.classType)) { + unloadedClasses.put(unresolvedClassInfo.classType, unresolvedClassInfo); } } catch (Exception ex) { throw ExceptionWithContext.withContext(ex, String.format("Error while loading class %s", @@ -252,42 +222,33 @@ public class ClassPath { } } - private static class ClassNotFoundException extends ExceptionWithContext { - public ClassNotFoundException(String message) { - super(message); - } - } - - public static ClassDef getClassDef(String classType) { - return getClassDef(classType, true); - } - /** - * This method checks if the given class has been loaded yet. If it has, it returns the loaded ClassDef. If not, - * then it looks up the TempClassItem for the given class and (possibly recursively) loads the ClassDef. + * This method loads the given class (and any dependent classes, as needed), removing them from unloadedClasses * @param classType the class to load - * @return the existing or newly loaded ClassDef object for the given class, or null if the class cannot be found + * @return the newly loaded ClassDef object for the given class, or null if the class cannot be found */ + @Nullable private static ClassDef loadClassDef(String classType) { - ClassDef classDef = getClassDef(classType, false); + ClassDef classDef = null; - if (classDef == null) { - TempClassInfo classInfo = theClassPath.tempClasses.get(classType); - if (classInfo == null) { - return null; - } - - try { - classDef = new ClassDef(classInfo); - theClassPath.classDefs.put(classDef.classType, classDef); - } catch (Exception ex) { - throw ExceptionWithContext.withContext(ex, String.format("Error while loading class %s from file %s", - classInfo.classType, classInfo.dexFilePath)); - } + UnresolvedClassInfo classInfo = theClassPath.unloadedClasses.get(classType); + if (classInfo == null) { + return null; } + + try { + classDef = new ClassDef(classInfo); + theClassPath.classDefs.put(classDef.classType, classDef); + } catch (Exception ex) { + throw ExceptionWithContext.withContext(ex, String.format("Error while loading class %s from file %s", + classInfo.classType, classInfo.dexFilePath)); + } + theClassPath.unloadedClasses.remove(classType); + return classDef; } + @Nonnull public static ClassDef getClassDef(String classType, boolean createUnresolvedClassDef) { ClassDef classDef = theClassPath.classDefs.get(classType); if (classDef == null) { @@ -295,17 +256,31 @@ public class ClassPath { if (classType.charAt(0) == '[') { return theClassPath.createArrayClassDef(classType); } else { - if (createUnresolvedClassDef) { - //TODO: we should output a warning - return theClassPath.createUnresolvedClassDef(classType); - } else { - return null; + try { + classDef = loadClassDef(classType); + if (classDef == null) { + throw new ExceptionWithContext( + String.format("Could not find definition for class %s", classType)); + } + } catch (Exception ex) { + RuntimeException exWithContext = ExceptionWithContext.withContext(ex, + String.format("Error while loading ClassPath class %s", classType)); + if (createUnresolvedClassDef) { + //TODO: add warning message + return theClassPath.createUnresolvedClassDef(classType); + } else { + throw exWithContext; + } } } } return classDef; } + public static ClassDef getClassDef(String classType) { + return getClassDef(classType, true); + } + public static ClassDef getClassDef(TypeIdItem classType) { return getClassDef(classType.getTypeDescriptor()); } @@ -322,6 +297,14 @@ public class ClassPath { return getClassDef(arrayPrefix.substring(256 - arrayDimension) + classDef.classType); } + private static ClassDef unresolvedObjectClassDef = null; + public static ClassDef getUnresolvedObjectClassDef() { + if (unresolvedObjectClassDef == null) { + unresolvedObjectClassDef = new UnresolvedClassDef("Ljava/lang/Object;"); + } + return unresolvedObjectClassDef; + } + private ClassDef createUnresolvedClassDef(String classType) { assert classType.charAt(0) == 'L'; @@ -449,7 +432,7 @@ public class ClassPath { try { elementClass = ClassPath.getClassDef(arrayClassType.substring(i)); - } catch (ClassNotFoundException ex) { + } catch (Exception ex) { throw ExceptionWithContext.withContext(ex, "Error while creating array class " + arrayClassType); } arrayDimensions = i; @@ -662,7 +645,7 @@ public class ClassPath { } else /*if (classFlavor == UnresolvedClassDef)*/ { assert classType.charAt(0) == 'L'; this.classType = classType; - this.superclass = ClassPath.theClassPath.javaLangObjectClassDef; + this.superclass = ClassPath.getClassDef("Ljava/lang/Object;"); implementedInterfaces = new TreeSet(); isInterface = false; @@ -677,7 +660,7 @@ public class ClassPath { } } - protected ClassDef(TempClassInfo classInfo) { + protected ClassDef(UnresolvedClassInfo classInfo) { classType = classInfo.classType; isInterface = classInfo.isInterface; @@ -812,7 +795,7 @@ public class ClassPath { fields[position2] = tempField; } - private ClassDef loadSuperclass(TempClassInfo classInfo) { + private ClassDef loadSuperclass(UnresolvedClassInfo classInfo) { if (classInfo.classType.equals("Ljava/lang/Object;")) { if (classInfo.superclassType != null) { throw new ExceptionWithContext("Invalid superclass " + @@ -826,9 +809,12 @@ public class ClassPath { throw new ExceptionWithContext(classInfo.classType + " has no superclass"); } - ClassDef superclass = ClassPath.loadClassDef(superclassType); - if (superclass == null) { - throw new ClassNotFoundException(String.format("Could not find superclass %s", superclassType)); + ClassDef superclass; + try { + superclass = ClassPath.getClassDef(superclassType); + } catch (Exception ex) { + throw ExceptionWithContext.withContext(ex, + String.format("Could not find superclass %s", superclassType)); } if (!isInterface && superclass.isInterface) { @@ -845,7 +831,7 @@ public class ClassPath { } } - private TreeSet loadAllImplementedInterfaces(TempClassInfo classInfo) { + private TreeSet loadAllImplementedInterfaces(UnresolvedClassInfo classInfo) { assert classType != null; assert classType.equals("Ljava/lang/Object;") || superclass != null; assert classInfo != null; @@ -861,9 +847,12 @@ public class ClassPath { if (classInfo.interfaces != null) { for (String interfaceType: classInfo.interfaces) { - ClassDef interfaceDef = ClassPath.loadClassDef(interfaceType); - if (interfaceDef == null) { - throw new ClassNotFoundException(String.format("Could not find interface %s", interfaceType)); + ClassDef interfaceDef; + try { + interfaceDef = ClassPath.getClassDef(interfaceType); + } catch (Exception ex) { + throw ExceptionWithContext.withContext(ex, + String.format("Could not find interface %s", interfaceType)); } assert interfaceDef.isInterface(); implementedInterfaceSet.add(interfaceDef); @@ -880,7 +869,7 @@ public class ClassPath { return implementedInterfaceSet; } - private LinkedHashMap loadInterfaceTable(TempClassInfo classInfo) { + private LinkedHashMap loadInterfaceTable(UnresolvedClassInfo classInfo) { if (classInfo.interfaces == null) { return null; } @@ -889,9 +878,12 @@ public class ClassPath { for (String interfaceType: classInfo.interfaces) { if (!interfaceTable.containsKey(interfaceType)) { - ClassDef interfaceDef = ClassPath.loadClassDef(interfaceType); - if (interfaceDef == null) { - throw new ClassNotFoundException(String.format("Could not find interface %s", interfaceType)); + ClassDef interfaceDef; + try { + interfaceDef = ClassPath.getClassDef(interfaceType); + } catch (Exception ex) { + throw ExceptionWithContext.withContext(ex, + String.format("Could not find interface %s", interfaceType)); } interfaceTable.put(interfaceType, interfaceDef); @@ -908,7 +900,7 @@ public class ClassPath { return interfaceTable; } - private String[] loadVtable(TempClassInfo classInfo) { + private String[] loadVtable(UnresolvedClassInfo classInfo) { //TODO: it might be useful to keep track of which class's implementation is used for each virtual method. In other words, associate the implementing class type with each vtable entry List virtualMethodList = new LinkedList(); //use a temp hash table, so that we can construct the final lookup with an appropriate @@ -981,7 +973,7 @@ public class ClassPath { } } - private SparseArray loadFields(TempClassInfo classInfo) { + private SparseArray loadFields(UnresolvedClassInfo classInfo) { //This is a bit of an "involved" operation. We need to follow the same algorithm that dalvik uses to //arrange fields, so that we end up with the same field offsets (which is needed for deodexing). //See mydroid/dalvik/vm/oo/Class.c - computeFieldOffsets() @@ -1175,10 +1167,10 @@ public class ClassPath { } /** - * In some cases, classes can reference another class (i.e. a superclass or an interface) that is in a *later* - * boot class path entry. So we load all classes from all boot class path entries before starting to process them + * This aggregates the basic information about a class in an easy-to-use format, without requiring references + * to any other class. */ - private static class TempClassInfo { + private static class UnresolvedClassInfo { public final String dexFilePath; public final String classType; public final boolean isInterface; @@ -1189,7 +1181,7 @@ public class ClassPath { public final String[] virtualMethods; public final String[][] instanceFields; - public TempClassInfo(String dexFilePath, ClassDefItem classDefItem) { + public UnresolvedClassInfo(String dexFilePath, ClassDefItem classDefItem) { this.dexFilePath = dexFilePath; classType = classDefItem.getClassType().getTypeDescriptor(); diff --git a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DeodexUtil.java b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DeodexUtil.java index 4d351835..be689ccd 100644 --- a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DeodexUtil.java +++ b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DeodexUtil.java @@ -91,7 +91,11 @@ public class DeodexUtil { String methodParams = m.group(2); String methodRet = m.group(3); - if (classDef.isInterface()) { + if (classDef instanceof ClassPath.UnresolvedClassDef) { + //if this is an unresolved class, the only way getVirtualMethod could have found a method is if the virtual + //method being looked up was a method on java.lang.Object. + classDef = ClassPath.getClassDef("Ljava/lang/Object;"); + } else if (classDef.isInterface()) { classDef = classDef.getSuperclass(); assert classDef != null; } diff --git a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/RegisterType.java b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/RegisterType.java index 53543e38..ed67732a 100644 --- a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/RegisterType.java +++ b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/RegisterType.java @@ -272,9 +272,13 @@ public class RegisterType { ClassDef mergedType = null; if (mergedCategory == Category.Reference) { - mergedType = ClassPath.getCommonSuperclass(this.type, type.type); - } - if (mergedCategory == Category.UninitRef || mergedCategory == Category.UninitThis) { + if (this.type instanceof ClassPath.UnresolvedClassDef || + type.type instanceof ClassPath.UnresolvedClassDef) { + mergedType = ClassPath.getUnresolvedObjectClassDef(); + } else { + mergedType = ClassPath.getCommonSuperclass(this.type, type.type); + } + } else if (mergedCategory == Category.UninitRef || mergedCategory == Category.UninitThis) { if (this.category == Category.Unknown) { return type; }