diff --git a/baksmali/src/main/java/org/jf/baksmali/Analysis/Analysis.java b/baksmali/src/main/java/org/jf/baksmali/Analysis/Analysis.java deleted file mode 100644 index 9f80e15c..00000000 --- a/baksmali/src/main/java/org/jf/baksmali/Analysis/Analysis.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.jf.baksmali.Analysis; - -public class Analysis { -} diff --git a/baksmali/src/main/java/org/jf/baksmali/Analysis/AnalysisInstruction.java b/baksmali/src/main/java/org/jf/baksmali/Analysis/AnalysisInstruction.java deleted file mode 100644 index d36be25e..00000000 --- a/baksmali/src/main/java/org/jf/baksmali/Analysis/AnalysisInstruction.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.jf.baksmali.Analysis; - -public interface AnalysisInstruction { - -} diff --git a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/AnalyzedInstruction.java b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/AnalyzedInstruction.java index 69617898..a12cc9e6 100644 --- a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/AnalyzedInstruction.java +++ b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/AnalyzedInstruction.java @@ -1,6 +1,7 @@ package org.jf.dexlib.Code.Analysis; -import org.jf.dexlib.Code.Instruction; +import org.jf.dexlib.Code.*; +import org.jf.dexlib.Util.ExceptionWithContext; import java.util.LinkedList; @@ -11,19 +12,24 @@ public class AnalyzedInstruction { protected final Instruction instruction; /** - * The address of the instruction, in 2-byte code blocks + * The index of the instruction, where the first instruction in the method is at index 0, and so on */ - protected final int codeAddress; + protected final int instructionIndex; /** * Instructions that can pass on execution to this one during normal execution */ - protected LinkedList predecessors = new LinkedList(); + protected final LinkedList predecessors = new LinkedList(); /** * Instructions that can execution could pass on to next during normal execution */ - protected LinkedList successors = new LinkedList(); + protected final LinkedList successors = new LinkedList(); + + /** + * This contains the register types *after* the instruction has executed + */ + protected final RegisterType[] postRegisterMap; /** * This is set to true when this instruction follows an odexed instruction that couldn't be deodexed. In this case @@ -36,13 +42,14 @@ public class AnalyzedInstruction { */ protected boolean dead = false; - public AnalyzedInstruction(Instruction instruction, int codeAddress) { + public AnalyzedInstruction(Instruction instruction, int instructionIndex, int registerCount) { this.instruction = instruction; - this.codeAddress = codeAddress; + this.instructionIndex = instructionIndex; + this.postRegisterMap = new RegisterType[registerCount]; } - public int getCodeAddress() { - return codeAddress; + public int getInstructionIndex() { + return instructionIndex; } protected void addPredecessor(AnalyzedInstruction predecessor) { @@ -61,5 +68,78 @@ public class AnalyzedInstruction { successors.add(successor); return true; } + + /* + * Sets the "post-instruction" register type as indicated. This should only be used to set + * the method parameter types for the "start of method" instruction, or to set the register + * type of the destination register during verification. The change to the register type + * will + * @param registerNumber Which register to set + * @param registerType The "post-instruction" register type + */ + protected boolean setPostRegisterType(int registerNumber, RegisterType registerType) { + assert registerNumber >= 0 && registerNumber < postRegisterMap.length; + assert registerType != null; + + RegisterType oldRegisterType = postRegisterMap[registerNumber]; + if (oldRegisterType == registerType) { + return false; + } + + postRegisterMap[registerNumber] = registerType; + return true; + } + + protected RegisterType getMergedRegisterTypeFromPredecessors(int registerNumber) { + RegisterType mergedRegisterType = null; + for (AnalyzedInstruction predecessor: predecessors) { + RegisterType predecessorRegisterType = predecessor.postRegisterMap[registerNumber]; + assert predecessorRegisterType != null; + mergedRegisterType = predecessorRegisterType.merge(mergedRegisterType); + } + return mergedRegisterType; + } + + public boolean setsRegister() { + return instruction.opcode.setsRegister(); + } + + public boolean setsWideRegister() { + return instruction.opcode.setsWideRegister(); + } + + public boolean setsRegister(int registerNumber) { + if (!setsRegister()) { + return false; + } + int destinationRegister = getDestinationRegister(); + if (registerNumber == destinationRegister) { + return true; + } + if (setsWideRegister() && registerNumber == (destinationRegister + 1)) { + return true; + } + return false; + } + + public int getDestinationRegister() { + if (!this.instruction.opcode.setsRegister()) { + throw new ExceptionWithContext("Cannot call getDestinationRegister() for an instruction that doesn't " + + "store a value"); + } + return ((SingleRegisterInstruction)instruction).getRegisterA(); + } + + public RegisterType getPreInstructionRegisterType(int registerNumber) { + //if the specific register is not a destination register, then the stored post-instruction register type will + //be the same as the pre-instruction regsiter type, so we can use that. + //otherwise, we need to merge the predecessor's post-instruction register types + + if (this.setsRegister(registerNumber)) { + return getMergedRegisterTypeFromPredecessors(registerNumber); + } else { + return postRegisterMap[registerNumber]; + } + } } 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 new file mode 100644 index 00000000..8b40df6d --- /dev/null +++ b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ClassPath.java @@ -0,0 +1,704 @@ +package org.jf.dexlib.Code.Analysis; + +import org.jf.dexlib.*; +import static org.jf.dexlib.ClassDataItem.EncodedMethod; +import static org.jf.dexlib.ClassDataItem.EncodedField; + +import org.jf.dexlib.Util.AccessFlags; +import org.jf.dexlib.Util.ExceptionWithContext; +import org.jf.dexlib.Util.SparseArray; + +import java.io.File; +import java.util.*; + +public class ClassPath { + private static ClassPath theClassPath = null; + + private final HashMap classDefs; + protected final ClassDef javaLangObjectClassDef; //Ljava/lang/Object; + + public static void InitializeClassPath(String[] bootClassPath, DexFile dexFile) { + if (theClassPath != null) { + throw new ExceptionWithContext("Cannot initialize ClassPath multiple times"); + } + + theClassPath = new ClassPath(bootClassPath, dexFile); + } + + private ClassPath(String[] bootClassPath, DexFile dexFile) { + if (bootClassPath == null || bootClassPath.length == 0) { + throw new ExceptionWithContext("No BOOTCLASSPATH entries were given"); + } + + classDefs = new HashMap(); + + for (String bootClassPathEntry: bootClassPath) { + loadBootClassPath(bootClassPathEntry); + } + + loadDexFile(dexFile); + + try { + javaLangObjectClassDef = getClassDef("Ljava/lang/Object;"); + } catch (ClassNotFoundException ex) { + throw ExceptionWithContext.withContext(ex, "Ljava/lang/Object; must be present in the classpath"); + } + + for (String primitiveType: new String[]{"Z", "B", "S", "C", "I", "J", "F", "D"}) { + ClassDef classDef = new PrimitiveClassDef(primitiveType); + classDefs.put(primitiveType, classDef); + } + } + + private void loadBootClassPath(String bootClassPathEntry) { + File file = new File(bootClassPathEntry); + + if (!file.exists()) { + throw new ExceptionWithContext("ClassPath entry \"" + bootClassPathEntry + "\" does not exist."); + } + + if (!file.canRead()) { + throw new ExceptionWithContext("Cannot read ClassPath entry \"" + bootClassPathEntry + "\"."); + } + + DexFile dexFile; + try { + dexFile = new DexFile(file); + } catch (Exception ex) { + throw ExceptionWithContext.withContext(ex, "Error while reading ClassPath entry \"" + + bootClassPathEntry + "\"."); + } + + loadDexFile(dexFile); + } + + private void loadDexFile(DexFile dexFile) { + for (ClassDefItem classDefItem: dexFile.ClassDefsSection.getItems()) { + //TODO: need to check if the class already exists. (and if so, what to do about it?) + ClassDef classDef = new ClassDef(classDefItem); + classDefs.put(classDef.getClassType(), classDef); + classDef.dumpVtable(); + classDef.dumpFields(); + } + } + + private static class ClassNotFoundException extends ExceptionWithContext { + public ClassNotFoundException(String message) { + super(message); + } + } + + public static ClassDef getClassDef(String classType) { + ClassDef classDef = theClassPath.classDefs.get(classType); + if (classDef == null) { + //if it's an array class, try to create it + if (classType.charAt(0) == '[') { + return theClassPath.createArrayClassDef(classType); + } else { + throw new ClassNotFoundException("Class " + classType + " cannot be found"); + } + } + return classDef; + } + + public static ClassDef getClassDef(TypeIdItem classType) { + return getClassDef(classType.getTypeDescriptor()); + } + + //256 [ characters + private static final String arrayPrefix = "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" + + "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" + + "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[["; + private static ClassDef getArrayClassDefByElementClassAndDimension(ClassDef classDef, int arrayDimension) { + return getClassDef(arrayPrefix.substring(256 - arrayDimension) + classDef.classType); + } + + private static ClassDef createArrayClassDef(String arrayClassName) { + assert arrayClassName != null; + assert arrayClassName.charAt(0) == '['; + + ArrayClassDef arrayClassDef = new ArrayClassDef(arrayClassName); + if (arrayClassDef.elementClass == null) { + return null; + } + + theClassPath.classDefs.put(arrayClassName, arrayClassDef); + return arrayClassDef; + } + + public static ClassDef getCommonSuperclass(ClassDef class1, ClassDef class2) { + if (class1 == class2) { + return class1; + } + + if (class1 == null) { + return class2; + } + + if (class2 == null) { + return class1; + } + + //TODO: do we want to handle primitive types here? I don't think so.. (if not, add assert) + + if (!class1.isInterface && class2.isInterface) { + if (class1.implementsInterface(class2)) { + return class2; + } + return theClassPath.javaLangObjectClassDef; + } + + if (!class2.isInterface && class1.isInterface) { + if (class2.implementsInterface(class1)) { + return class1; + } + return theClassPath.javaLangObjectClassDef; + } + + if (class1 instanceof ArrayClassDef && class2 instanceof ArrayClassDef) { + return getCommonArraySuperclass((ArrayClassDef)class1, (ArrayClassDef)class2); + } + + //we've got two non-array reference types. Find the class depth of each, and then move up the longer one + //so that both classes are at the same class depth, and then move each class up until they match + + //we don't strictly need to keep track of the class depth separately, but it's probably slightly faster + //to do so, rather than calling getClassDepth() many times + int class1Depth = class1.getClassDepth(); + int class2Depth = class2.getClassDepth(); + + while (class1Depth > class2Depth) { + class1 = class1.superclass; + class1Depth--; + } + + while (class2Depth > class1Depth) { + class2 = class2.superclass; + class2Depth--; + } + + while (class1Depth > 0) { + if (class1 == class2) { + return class1; + } + class1 = class1.superclass; + class1Depth--; + class2 = class2.superclass; + class2Depth--; + } + + return class1; + } + + private static ClassDef getCommonArraySuperclass(ArrayClassDef class1, ArrayClassDef class2) { + assert class1 != class2; + + //If one of the arrays is a primitive array, then the only option is to return java.lang.Object + //TODO: might it be possible to merge something like int[] and short[] into int[]? (I don't think so..) + if (class1.elementClass instanceof PrimitiveClassDef || class2.elementClass instanceof PrimitiveClassDef) { + return theClassPath.javaLangObjectClassDef; + } + + //if the two arrays have the same number of dimensions, then we should return an array class with the + //same number of dimensions, for the common superclass of the 2 element classes + if (class1.arrayDimensions == class2.arrayDimensions) { + ClassDef commonElementClass = getCommonSuperclass(class1.elementClass, class2.elementClass); + return getArrayClassDefByElementClassAndDimension(commonElementClass, class1.arrayDimensions); + } + + //something like String[][][] and String[][] should be merged to Object[][] + //this also holds when the element classes aren't the same (but are both reference types) + int dimensions = Math.min(class1.arrayDimensions, class2.arrayDimensions); + return getArrayClassDefByElementClassAndDimension(theClassPath.javaLangObjectClassDef, dimensions); + } + + public static class ArrayClassDef extends ClassDef { + private final ClassDef elementClass; + private final int arrayDimensions; + + protected ArrayClassDef(String arrayClassType) { + super(arrayClassType, true); + assert arrayClassType.charAt(0) == '['; + + int i=0; + while (arrayClassType.charAt(i) == '[') i++; + + String elementClassType = arrayClassType.substring(i); + + if (i>256) { + throw new ExceptionWithContext("Error while creating array class for element type " + elementClassType + + " with " + i + " dimensions. The maximum number of dimensions is 256"); + } + + try { + elementClass = ClassPath.getClassDef(arrayClassType.substring(i)); + } catch (ClassNotFoundException ex) { + throw ExceptionWithContext.withContext(ex, "Error while creating array class " + arrayClassType); + } + arrayDimensions = i; + } + + public ClassDef getElementClass() { + return elementClass; + } + + public int getArrayDimensions() { + return arrayDimensions; + } + } + + public static class PrimitiveClassDef extends ClassDef { + protected PrimitiveClassDef(String primitiveClassType) { + super(primitiveClassType, false); + } + } + + public static class ClassDef { + private final String classType; + private final ClassDef superclass; + /** + * This is a list of all of the interfaces that a class implements, either directly or indirectly. It includes + * all interfaces implemented by the superclass, and all super-interfaces of any implemented interface. The + * intention is to make it easier to determine whether the class implements a given interface or not. + */ + private final TreeSet implementedInterfaces; + + private final boolean isInterface; + + private final int classDepth; + + private final String[] vtable; + private final HashMap virtualMethodLookup; + + private final SparseArray instanceFields; + private final HashMap instanceFieldLookup; + + /** + * This constructor is used for the ArrayClassDef and PrimitiveClassDef subclasses + * @param classType the class type + * @param isArrayType whether this is an array ClassDef or a primitive ClassDef + */ + protected ClassDef(String classType, boolean isArrayType) { + if (isArrayType) { + assert (classType.charAt(0) == '['); + this.classType = classType; + this.superclass = ClassPath.theClassPath.javaLangObjectClassDef; + implementedInterfaces = new TreeSet(); + implementedInterfaces.add(ClassPath.getClassDef("Ljava/lang/Cloneable;")); + implementedInterfaces.add(ClassPath.getClassDef("Ljava/io/Serializable;")); + isInterface = false; + + vtable = superclass.vtable; + virtualMethodLookup = superclass.virtualMethodLookup; + + instanceFields = superclass.instanceFields; + instanceFieldLookup = superclass.instanceFieldLookup; + classDepth = 1; //1 off from java.lang.Object + } else { + //primitive type + this.classType = classType; + this.superclass = null; + implementedInterfaces = null; + isInterface = false; + vtable = null; + virtualMethodLookup = null; + instanceFields = null; + instanceFieldLookup = null; + classDepth = 0; //TODO: maybe use -1 to indicate not applicable? + } + } + + protected ClassDef(ClassDefItem classDefItem) { + classType = classDefItem.getClassType().getTypeDescriptor(); + + isInterface = (classDefItem.getAccessFlags() & AccessFlags.INTERFACE.getValue()) != 0; + + superclass = loadSuperclass(classDefItem); + if (superclass == null) { + classDepth = 0; + } else { + classDepth = superclass.classDepth + 1; + } + + implementedInterfaces = loadAllImplementedInterfaces(classDefItem); + + vtable = loadVtable(classDefItem); + virtualMethodLookup = new HashMap((int)Math.ceil(vtable.length / .7f), .75f); + for (int i=0; i((int)Math.ceil(instanceFields.size() / .7f), .75f); + for (int i=0; i superclassDepth) { + ancestor = ancestor.getSuperclass(); + } + + return ancestor == superclassDef; + } + + /** + * Returns true if this class implements the given interface. This searches the interfaces that this class + * directly implements, any interface implemented by this class's superclasses, and any super-interface of + * any of these interfaces. + * @param interfaceDef the interface + * @return true if this class implements the given interface + */ + public boolean implementsInterface(ClassDef interfaceDef) { + return implementedInterfaces.contains(interfaceDef); + } + + //TODO: GROT + public void dumpVtable() { + System.out.println(classType + " methods:"); + int i=0; + for (String method: vtable) { + System.out.println(i + ":\t" + method); + i++; + } + } + + //TODO: GROT + public void dumpFields() { + System.out.println(classType + " fields:"); + for (int i=0; i loadAllImplementedInterfaces(ClassDefItem classDefItem) { + assert classType != null; + assert classType.equals("Ljava/lang/Object;") || superclass != null; + assert classDefItem != null; + + TreeSet implementedInterfaceSet = new TreeSet(); + + if (superclass != null) { + for (ClassDef interfaceDef: superclass.implementedInterfaces) { + implementedInterfaceSet.add(interfaceDef); + } + } + + TypeListItem interfaces = classDefItem.getInterfaces(); + if (interfaces != null) { + for (TypeIdItem interfaceType: interfaces.getTypes()) { + ClassDef interfaceDef = ClassPath.getClassDef(interfaceType.getTypeDescriptor()); + assert interfaceDef.isInterface; + implementedInterfaceSet.add(interfaceDef); + + interfaceDef = interfaceDef.getSuperclass(); + while (!interfaceDef.getClassType().equals("Ljava/lang/Object;")) { + assert interfaceDef.isInterface; + implementedInterfaceSet.add(interfaceDef); + interfaceDef = interfaceDef.getSuperclass(); + } + } + } + + return implementedInterfaceSet; + } + + private String[] loadVtable(ClassDefItem classDefItem) { + //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 + //capacity, based on the number of virtual methods + HashMap tempVirtualMethodLookup = new HashMap(); + + //copy the virtual methods from the superclass + int methodIndex = 0; + if (superclass != null) { + for (String method: superclass.vtable) { + virtualMethodList.add(method); + tempVirtualMethodLookup.put(method, methodIndex++); + } + + assert superclass.instanceFields != null; + } + + + //iterate over the virtual methods in the current class, and only add them when we don't already have the + //method (i.e. if it was implemented by the superclass) + ClassDataItem classDataItem = classDefItem.getClassData(); + if (classDataItem != null) { + EncodedMethod[] virtualMethods = classDataItem.getVirtualMethods(); + if (virtualMethods != null) { + for (EncodedMethod virtualMethod: virtualMethods) { + String methodString = virtualMethod.method.getMethodString(); + if (tempVirtualMethodLookup.get(methodString) == null) { + virtualMethodList.add(methodString); + } + } + } + } + + String[] vtable = new String[virtualMethodList.size()]; + for (int i=0; i loadFields(ClassDefItem classDefItem) { + //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). + + final byte REFERENCE = 0; + final byte WIDE = 1; + final byte OTHER = 2; + + ClassDataItem classDataItem = classDefItem.getClassData(); + + String[] fields = null; + //the "type" for each field in fields. 0=reference,1=wide,2=other + byte[] fieldTypes = null; + + if (classDataItem != null) { + EncodedField[] encodedFields = classDataItem.getInstanceFields(); + if (encodedFields != null) { + fields = new String[encodedFields.length]; + fieldTypes = new byte[encodedFields.length]; + + for (int i=0; i front) { + if (fieldTypes[back] == REFERENCE) { + swap(fieldTypes, fields, front, back--); + break; + } + back--; + } + } + + if (fieldTypes[front] != REFERENCE) { + break; + } + } + + //next, we need to group all the wide fields after the reference fields. But the wide fields have to be + //8-byte aligned. If we're on an odd field index, we need to insert a 32-bit field. If the next field + //is already a 32-bit field, use that. Otherwise, find the first 32-bit field from the end and swap it in. + //If there are no 32-bit fields, do nothing for now. We'll add padding when calculating the field offsets + if (front < fields.length && (front % 2) != 0) { + if (fieldTypes[front] == WIDE) { + //we need to swap in a 32-bit field, so the wide fields will be correctly aligned + back = fields.length - 1; + while (back > front) { + if (fieldTypes[back] == OTHER) { + swap(fieldTypes, fields, front++, back); + break; + } + back--; + } + } else { + //there's already a 32-bit field here that we can use + front++; + } + } + + //do the swap thing for wide fields + back = fields.length - 1; + for (; front front) { + if (fieldTypes[back] == WIDE) { + swap(fieldTypes, fields, front, back--); + break; + } + back--; + } + } + + if (fieldTypes[front] != WIDE) { + break; + } + } + + int superFieldCount = 0; + if (superclass != null) { + superclass.instanceFields.size(); + } + + //now the fields are in the correct order. Add them to the SparseArray and lookup, and calculate the offsets + int totalFieldCount = superFieldCount + fields.length; + SparseArray instanceFields = new SparseArray(totalFieldCount); + + int fieldOffset; + + if (superclass != null && superFieldCount > 0) { + for (int i=0; i= 0; + assert lastSuperField.indexOf(':') < superFieldCount-1; //the ':' shouldn't be the last char + char fieldType = lastSuperField.charAt(lastSuperField.indexOf(':') + 1); + if (fieldType == 'J' || fieldType == 'D') { + fieldOffset += 8; + } else { + fieldOffset += 4; + } + } else { + //the field values start at 8 bytes into the DataObject dalvik structure + fieldOffset = 8; + } + + boolean gotDouble = false; + for (int i=0; i= field.length()-1 instead, but that's too easy to mistake for an off-by-one error + if (sepIndex < 0 || sepIndex == field.length()-1 || sepIndex >= field.length()) { + assert false; + throw new ExceptionWithContext("Invalid field format: " + field); + } + switch (field.charAt(sepIndex+1)) { + case '[': + case 'L': + return 0; //REFERENCE + case 'J': + case 'D': + return 1; //WIDE + default: + return 2; //OTHER + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ClassDef)) return false; + + ClassDef classDef = (ClassDef) o; + + return classType.equals(classDef.classType); + } + + @Override + public int hashCode() { + return classType.hashCode(); + } + } +} diff --git a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/MethodAnalyzer.java b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/MethodAnalyzer.java index 4624adb4..893f805e 100644 --- a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/MethodAnalyzer.java +++ b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/MethodAnalyzer.java @@ -1,20 +1,17 @@ package org.jf.dexlib.Code.Analysis; -import org.jf.dexlib.ClassDataItem; -import org.jf.dexlib.Code.Instruction; -import org.jf.dexlib.Code.MultiOffsetInstruction; -import org.jf.dexlib.Code.OffsetInstruction; -import org.jf.dexlib.Code.Opcode; -import org.jf.dexlib.CodeItem; +import org.jf.dexlib.*; +import org.jf.dexlib.Code.*; +import org.jf.dexlib.Util.*; -import java.util.Arrays; -import java.util.HashSet; +import java.util.*; public class MethodAnalyzer { - private final HashSet registerInfoCache = new HashSet(); private final ClassDataItem.EncodedMethod encodedMethod; - private AnalyzedInstruction[] instructions; + private SparseArray instructions; + + private boolean analyzed = false; //This is a dummy instruction that occurs immediately before the first real instruction. We can initialize the //register types for this instruction to the parameter types, in order to have them propagate to all of its @@ -26,31 +23,238 @@ public class MethodAnalyzer { if (encodedMethod == null) { throw new IllegalArgumentException("encodedMethod cannot be null"); } - if (encodedMethod.codeItem == null) { + if (encodedMethod.codeItem == null || encodedMethod.codeItem.getInstructions().length == 0) { throw new IllegalArgumentException("The method has no code"); } this.encodedMethod = encodedMethod; buildInstructionList(); + + //override AnalyzedInstruction and provide custom implementations of some of the methods, so that we don't + //have to handle the case this special case of instruction being null, in the main class + startOfMethod = new AnalyzedInstruction(null, -1, encodedMethod.codeItem.getRegisterCount()) { + public boolean setsRegister() { + return false; + } + + @Override + public boolean setsWideRegister() { + return false; + } + + @Override + public boolean setsRegister(int registerNumber) { + return false; + } + + @Override + public int getDestinationRegister() { + assert false; + return -1; + }; + }; } public AnalyzedInstruction[] analyze() { - return null; + assert encodedMethod != null; + assert encodedMethod.codeItem != null; + + if (analyzed) { + return makeInstructionArray(); + } + + CodeItem codeItem = encodedMethod.codeItem; + MethodIdItem methodIdItem = encodedMethod.method; + + int totalRegisters = codeItem.getRegisterCount(); + int parameterRegisters = methodIdItem.getPrototype().getParameterRegisterCount(); + + //if this isn't a static method, determine which register is the "this" register and set the type to the + //current class + if ((encodedMethod.accessFlags & AccessFlags.STATIC.getValue()) == 0) { + int thisRegister = totalRegisters - parameterRegisters - 1; + + //if this is a constructor, then set the "this" register to an uninitialized reference of the current class + if ((encodedMethod.accessFlags & AccessFlags.CONSTRUCTOR.getValue()) != 0) { + //TODO: it would probably make more sense to validate this somewhere else, and just put an assert here. Also, need to do a similar check for static constructor + if (!encodedMethod.method.getMethodName().equals("")) { + throw new ValidationException("The constructor flag can only be used with an method."); + } + + setRegisterTypeAndPropagateChanges(startOfMethod, thisRegister, + RegisterType.getRegisterType(RegisterType.Category.UninitRef, + ClassPath.getClassDef(methodIdItem.getContainingClass()))); + } else { + if (encodedMethod.method.getMethodName().equals("")) { + throw new ValidationException("An method must have the \"constructor\" access flag"); + } + + setRegisterTypeAndPropagateChanges(startOfMethod, thisRegister, + RegisterType.getRegisterType(RegisterType.Category.Reference, + ClassPath.getClassDef(methodIdItem.getContainingClass()))); + } + } + + TypeListItem parameters = methodIdItem.getPrototype().getParameters(); + if (parameters != null) { + RegisterType[] parameterTypes = getParameterTypes(parameters, parameterRegisters); + for (int i=0; i=0; + instructionIndex=changedInstructions.nextSetBit(instructionIndex)) { + + changedInstructions.clear(instructionIndex); + + propagateRegisterToSuccessors(instructions.valueAt(instructionIndex), registerNumber, + changedInstructions); + } + } + } + + private void propagateRegisterToSuccessors(AnalyzedInstruction instruction, int registerNumber, + BitSet changedInstructions) { + for (AnalyzedInstruction successor: instruction.successors) { + if (!successor.setsRegister(registerNumber)) { + RegisterType registerType = successor.getMergedRegisterTypeFromPredecessors(registerNumber); + + if (successor.setPostRegisterType(registerNumber, registerType)) { + changedInstructions.set(successor.instructionIndex); + } + } + } + } + + + private void buildInstructionList() { assert encodedMethod != null; assert encodedMethod.codeItem != null; + int registerCount = encodedMethod.codeItem.getRegisterCount(); - startOfMethod = new AnalyzedInstruction(null, -1); + startOfMethod = new AnalyzedInstruction(null, -1, registerCount); Instruction[] insns = encodedMethod.codeItem.getInstructions(); - instructions = new AnalyzedInstruction[insns.length]; + instructions = new SparseArray(insns.length); //first, create all the instructions and populate the instructionAddresses array int currentCodeAddress = 0; for (int i=0; i 0; + addPredecessorSuccessor(startOfMethod, instructions.valueAt(0), exceptionHandlers); + startOfMethod.addSuccessor(instructions.valueAt(0)); + + for (int i=0; i (start + 1)) { - int index = (start + end) / 2; + switch (instruction.opcode) { + case NOP: + return true; + case MOVE: + case MOVE_FROM16: + case MOVE_16: + return handleMove(analyzedInstruction, Primitive32BitCategories); + case MOVE_WIDE: + case MOVE_WIDE_FROM16: + case MOVE_WIDE_16: + return handleMoveWide(analyzedInstruction); + case MOVE_OBJECT: + case MOVE_OBJECT_FROM16: + case MOVE_OBJECT_16: + return handleMove(analyzedInstruction, ReferenceCategories); + case MOVE_RESULT: + return handleMoveResult(analyzedInstruction, Primitive32BitCategories); + case MOVE_RESULT_WIDE: + return handleMoveResult(analyzedInstruction, WideLowCategories); + case MOVE_RESULT_OBJECT: + return handleMoveResult(analyzedInstruction, ReferenceCategories); + case MOVE_EXCEPTION: + return handleMoveException(analyzedInstruction); + case RETURN_VOID: + return handleReturnVoid(analyzedInstruction); + case RETURN: + return handleReturn(analyzedInstruction); + case RETURN_WIDE: + return handleReturnWide(analyzedInstruction); + case RETURN_OBJECT: + return handleReturnObject(analyzedInstruction); + case CONST_4: + case CONST_16: + case CONST: + return handleConst(analyzedInstruction); + case CONST_HIGH16: + return handleConstHigh16(analyzedInstruction); + case CONST_WIDE_16: + case CONST_WIDE_32: + case CONST_WIDE: + case CONST_WIDE_HIGH16: + return handleWideConst(analyzedInstruction); + case CONST_STRING: + case CONST_STRING_JUMBO: + return handleConstString(analyzedInstruction); + case CONST_CLASS: + return handleConstClass(analyzedInstruction); + case MONITOR_ENTER: + case MONITOR_EXIT: + return handleMonitor(analyzedInstruction); + case CHECK_CAST: + return handleCheckCast(analyzedInstruction); + case INSTANCE_OF: + return handleInstanceOf(analyzedInstruction); + case ARRAY_LENGTH: + return handleArrayLength(analyzedInstruction); + case NEW_INSTANCE: + return handleNewInstance(analyzedInstruction); + case NEW_ARRAY: + return handleNewArray(analyzedInstruction); + } + assert false; + return false; + } - if (instructions[index].codeAddress < address) { - start = index; - } else { - end = index; + private static final EnumSet Primitive32BitCategories = EnumSet.of( + RegisterType.Category.Null, + RegisterType.Category.Boolean, + RegisterType.Category.Byte, + RegisterType.Category.Short, + RegisterType.Category.Char, + RegisterType.Category.Integer, + RegisterType.Category.Float); + + private static final EnumSet WideLowCategories = EnumSet.of( + RegisterType.Category.LongLo, + RegisterType.Category.DoubleLo); + + private static final EnumSet WideHighCategories = EnumSet.of( + RegisterType.Category.LongHi, + RegisterType.Category.DoubleHi); + + private static final EnumSet ReferenceCategories = EnumSet.of( + RegisterType.Category.Null, + RegisterType.Category.Reference); + + private boolean handleMove(AnalyzedInstruction analyzedInstruction, + EnumSet allowedCategories) { + TwoRegisterInstruction instruction = (TwoRegisterInstruction)analyzedInstruction.instruction; + + //get the "pre-instruction" register type for the source register + RegisterType sourceRegisterType = analyzedInstruction.getPreInstructionRegisterType(instruction.getRegisterB()); + assert sourceRegisterType != null; + + if (sourceRegisterType.category == RegisterType.Category.Unknown) { + //we don't know the source register type yet, so we can't verify it. Return false, and we'll come back later + return false; + } + + checkRegister(sourceRegisterType, allowedCategories); + + setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, sourceRegisterType); + return true; + } + + private boolean handleMoveWide(AnalyzedInstruction analyzedInstruction) { + TwoRegisterInstruction instruction = (TwoRegisterInstruction)analyzedInstruction.instruction; + + RegisterType sourceRegisterType = getAndCheckWideSourcePair(analyzedInstruction, + instruction.getRegisterB()); + assert sourceRegisterType != null; + + if (sourceRegisterType.category == RegisterType.Category.Unknown) { + //we don't know the source register type yet, so we can't verify it. Return false, and we'll come back later + return false; + } + + checkWideDestinationPair(analyzedInstruction); + + setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, sourceRegisterType); + return true; + } + + private boolean handleMoveResult(AnalyzedInstruction analyzedInstruction, + EnumSet allowedCategories) { + + //TODO: handle the case when the previous instruction is an odexed instruction + + if (analyzedInstruction.instructionIndex == 0) { + throw new ValidationException(analyzedInstruction.instruction.opcode.name + " cannot be the first " + + "instruction in a method. It must occur after an invoke-*/fill-new-array instruction"); + } + + AnalyzedInstruction previousInstruction = instructions.valueAt(analyzedInstruction.instructionIndex-1); + + if (!previousInstruction.instruction.opcode.setsResult()) { + throw new ValidationException(analyzedInstruction.instruction.opcode.name + " must occur after an " + + "invoke-*/fill-new-array instruction"); + } + + if (analyzedInstruction.instruction.opcode.setsWideRegister()) { + checkWideDestinationPair(analyzedInstruction); + } + + //TODO: does dalvik allow a move-result after an invoke with a void return type? + RegisterType destinationRegisterType; + + InstructionWithReference invokeInstruction = (InstructionWithReference)previousInstruction.instruction; + Item item = invokeInstruction.getReferencedItem(); + + if (item instanceof MethodIdItem) { + destinationRegisterType = RegisterType.getRegisterTypeForTypeIdItem( + ((MethodIdItem)item).getPrototype().getReturnType()); + } else { + assert item instanceof TypeIdItem; + destinationRegisterType = RegisterType.getRegisterTypeForTypeIdItem((TypeIdItem)item); + } + + checkRegister(destinationRegisterType, allowedCategories); + setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, destinationRegisterType); + return true; + } + + private boolean handleMoveException(AnalyzedInstruction analyzedInstruction) { + CodeItem.TryItem[] tries = encodedMethod.codeItem.getTries(); + int instructionAddress = getInstructionAddress(analyzedInstruction); + + if (tries == null) { + throw new ValidationException("move-exception must be the first instruction in an exception handler block"); + } + + RegisterType exceptionType = null; + + for (CodeItem.TryItem tryItem: encodedMethod.codeItem.getTries()) { + if (tryItem.encodedCatchHandler.getCatchAllHandlerAddress() == instructionAddress) { + exceptionType = RegisterType.getRegisterType(RegisterType.Category.Reference, + ClassPath.getClassDef("Ljava/lang/Throwable;")); + break; + } + for (CodeItem.EncodedTypeAddrPair handler: tryItem.encodedCatchHandler.handlers) { + if (handler.getHandlerAddress() == instructionAddress) { + exceptionType = RegisterType.getRegisterTypeForTypeIdItem(handler.exceptionType) + .merge(exceptionType); + } } } - if (end < instructions.length && instructions[end].codeAddress == address) { - return end; + //TODO: check if the type is a throwable. Should we throw a ValidationException or print a warning? (does dalvik validate that it's a throwable? It doesn't in CodeVerify.c, but it might check in DexSwapVerify.c) + checkRegister(exceptionType, ReferenceCategories); + setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, exceptionType); + return true; + } + + private boolean checkConstructorReturn(AnalyzedInstruction analyzedInstruction) { + assert this.isInstanceConstructor(); + + //if we're in an instance constructor (an method), then the superclass must have been called. + //When execution enters the method, the "this" register is set as an uninitialized reference to the containing + //class. Once the superclass' is called, the "this" register is upgraded to a full-blown reference type, + //so we need to ensure that the "this" register isn't an uninitialized reference + + int thisRegister = getThisRegister(); + RegisterType thisRegisterType = analyzedInstruction.postRegisterMap[thisRegister]; + + if (thisRegisterType.category == RegisterType.Category.Unknown) { + //we don't have enough information yet, so return false. We'll come back later + return false; + } + if (thisRegisterType.category == RegisterType.Category.UninitRef) { + throw new ValidationException("Returning from constructor without calling the superclass' "); + } + assert thisRegisterType.category == RegisterType.Category.Reference; + assert thisRegisterType.type == ClassPath.getClassDef(encodedMethod.method.getContainingClass()); + return true; + } + + private boolean handleReturnVoid(AnalyzedInstruction analyzedInstruction) { + if (this.isInstanceConstructor()) { + if (!checkConstructorReturn(analyzedInstruction)) { + return false; + } } - throw new RuntimeException("No instruction at address " + address); - } + TypeIdItem returnType = encodedMethod.method.getPrototype().getReturnType(); + if (returnType.getTypeDescriptor().charAt(0) != 'V') { + //TODO: could add which return-* variation should be used instead + throw new ValidationException("Cannot use return-void with a non-void return type (" + + returnType.getTypeDescriptor() + ")"); + } + return true; + } - private AnalyzedInstruction getInstructionByAddress(int address) { - int index = getInstructionIndexByAddress(address); + private boolean handleReturn(AnalyzedInstruction analyzedInstruction) { + if (this.isInstanceConstructor()) { + if (!checkConstructorReturn(analyzedInstruction)) { + return false; + } + } - assert index < instructions.length; - assert instructions[index] != null; - return instructions[index]; + SingleRegisterInstruction instruction = (SingleRegisterInstruction)analyzedInstruction.instruction; + RegisterType returnRegisterType = analyzedInstruction.postRegisterMap[instruction.getRegisterA()]; + + if (returnRegisterType.category == RegisterType.Category.Unknown) { + return false; + } + + checkRegister(returnRegisterType, Primitive32BitCategories); + + TypeIdItem returnType = encodedMethod.method.getPrototype().getReturnType(); + if (returnType.getTypeDescriptor().charAt(0) == 'V') { + throw new ValidationException("Cannot use return with a void return type. Use return-void instead"); + } + + RegisterType registerType = RegisterType.getRegisterTypeForTypeIdItem(returnType); + + if (!Primitive32BitCategories.contains(registerType.category)) { + //TODO: could add which return-* variation should be used instead + throw new ValidationException("Cannot use return with return type " + returnType.getTypeDescriptor()); + } + + + return true; + } + + private boolean handleReturnWide(AnalyzedInstruction analyzedInstruction) { + if (this.isInstanceConstructor()) { + if (!checkConstructorReturn(analyzedInstruction)) { + return false; + } + } + + SingleRegisterInstruction instruction = (SingleRegisterInstruction)analyzedInstruction.instruction; + RegisterType returnType = getAndCheckWideSourcePair(analyzedInstruction, instruction.getRegisterA()); + + if (returnType.category == RegisterType.Category.Unknown) { + return false; + } + + + TypeIdItem returnTypeIdItem = encodedMethod.method.getPrototype().getReturnType(); + if (returnTypeIdItem.getTypeDescriptor().charAt(0) == 'V') { + throw new ValidationException("Cannot use return-wide with a void return type. Use return-void instead"); + } + + returnType = RegisterType.getRegisterTypeForTypeIdItem(returnTypeIdItem); + if (!WideLowCategories.contains(returnType.category)) { + //TODO: could add which return-* variation should be used instead + throw new ValidationException("Cannot use return-wide with return type " + + returnTypeIdItem.getTypeDescriptor()); + } + + return true; + } + + private boolean handleReturnObject(AnalyzedInstruction analyzedInstruction) { + if (this.isInstanceConstructor()) { + if (!checkConstructorReturn(analyzedInstruction)) { + return false; + } + } + + SingleRegisterInstruction instruction = (SingleRegisterInstruction)analyzedInstruction.instruction; + int returnRegister = instruction.getRegisterA(); + RegisterType returnRegisterType = analyzedInstruction.postRegisterMap[returnRegister]; + + if (returnRegisterType.category == RegisterType.Category.Unknown) { + return false; + } + + checkRegister(returnRegisterType, ReferenceCategories); + + + TypeIdItem returnTypeIdItem = encodedMethod.method.getPrototype().getReturnType(); + if (returnTypeIdItem.getTypeDescriptor().charAt(0) == 'V') { + throw new ValidationException("Cannot use return with a void return type. Use return-void instead"); + } + + RegisterType returnType = RegisterType.getRegisterTypeForTypeIdItem(returnTypeIdItem); + + if (!ReferenceCategories.contains(returnType.category)) { + //TODO: could add which return-* variation should be used instead + throw new ValidationException("Cannot use " + analyzedInstruction + " with return type " + + returnTypeIdItem.getTypeDescriptor()); + } + + if (returnType.type.isInterface()) { + if (!returnRegisterType.type.implementsInterface(returnType.type)) { + //TODO: how to handle warnings? + } + } else { + if (!returnRegisterType.type.extendsClass(returnType.type)) { + throw new ValidationException("The return value in register v" + Integer.toString(returnRegister) + + "(" + returnRegisterType.type.getClassType() + ") is not compatible with the method's return " + + "type (" + returnType.type.getClassType() + ")"); + } + } + + return true; + } + + private boolean handleConst(AnalyzedInstruction analyzedInstruction) { + LiteralInstruction instruction = (LiteralInstruction)analyzedInstruction.instruction; + + RegisterType newDestinationRegisterType = RegisterType.getRegisterTypeForLiteral(instruction.getLiteral()); + + //we assume that the literal value is a valid value for the given instruction type, because it's impossible + //to store an invalid literal with the instruction. so we don't need to check the type of the literal + setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, newDestinationRegisterType); + return true; + } + + private boolean handleConstHigh16(AnalyzedInstruction analyzedInstruction) { + LiteralInstruction instruction = (LiteralInstruction)analyzedInstruction.instruction; + + //TODO: test this + long literalValue = instruction.getLiteral() << 16; + RegisterType newDestinationRegisterType = RegisterType.getRegisterTypeForLiteral(literalValue); + + //we assume that the literal value is a valid value for the given instruction type, because it's impossible + //to store an invalid literal with the instruction. so we don't need to check the type of the literal + setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, newDestinationRegisterType); + return true; + } + + private boolean handleWideConst(AnalyzedInstruction analyzedInstruction) { + setWideDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, + RegisterType.getRegisterType(RegisterType.Category.LongLo, null)); + return true; + } + + private boolean handleConstString(AnalyzedInstruction analyzedInstruction) { + ClassPath.ClassDef stringClassDef = ClassPath.getClassDef("Ljava/lang/String;"); + RegisterType stringType = RegisterType.getRegisterType(RegisterType.Category.Reference, stringClassDef); + setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, stringType); + return true; + } + + private boolean handleConstClass(AnalyzedInstruction analyzedInstruction) { + ClassPath.ClassDef classClassDef = ClassPath.getClassDef("Ljava/lang/Class;"); + RegisterType classType = RegisterType.getRegisterType(RegisterType.Category.Reference, classClassDef); + + InstructionWithReference instruction = (InstructionWithReference)analyzedInstruction.instruction; + Item item = instruction.getReferencedItem(); + assert item.getItemType() == ItemType.TYPE_TYPE_ID_ITEM; + + //make sure the referenced class is resolvable + //TODO: need to check class access + ClassPath.ClassDef classDef = ClassPath.getClassDef((TypeIdItem)item); + return false; + } + + private boolean handleMonitor(AnalyzedInstruction analyzedInstruction) { + SingleRegisterInstruction instruction = (SingleRegisterInstruction)analyzedInstruction; + + RegisterType registerType = analyzedInstruction.postRegisterMap[instruction.getRegisterA()]; + assert registerType != null; + if (registerType.category == RegisterType.Category.Unknown) { + return false; + } + + checkRegister(registerType, ReferenceCategories); + return true; + } + + private boolean handleCheckCast(AnalyzedInstruction analyzedInstruction) { + { + //ensure the "source" register is a reference type + SingleRegisterInstruction instruction = (SingleRegisterInstruction)analyzedInstruction.instruction; + + RegisterType registerType = analyzedInstruction.getPreInstructionRegisterType(instruction.getRegisterA()); + assert registerType != null; + if (registerType.category == RegisterType.Category.Unknown) { + return false; + } + + checkRegister(registerType, ReferenceCategories); + } + + { + //resolve and verify the class that we're casting to + InstructionWithReference instruction = (InstructionWithReference)analyzedInstruction.instruction; + + Item item = instruction.getReferencedItem(); + assert item.getItemType() == ItemType.TYPE_TYPE_ID_ITEM; + + //TODO: need to check class access + RegisterType newDestinationRegisterType = RegisterType.getRegisterTypeForTypeIdItem((TypeIdItem)item); + try { + checkRegister(newDestinationRegisterType, ReferenceCategories); + } catch (ValidationException ex) { + //TODO: verify that dalvik allows a non-reference type.. + //TODO: print a warning, but don't re-throw the exception. dalvik allows a non-reference type during validation (but throws an exception at runtime) + } + + setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, newDestinationRegisterType); + return true; + } + } + + private boolean handleInstanceOf(AnalyzedInstruction analyzedInstruction) { + { + //ensure the register that is being checks is a reference type + TwoRegisterInstruction instruction = (TwoRegisterInstruction)analyzedInstruction; + + RegisterType registerType = analyzedInstruction.getPreInstructionRegisterType(instruction.getRegisterB()); + assert registerType != null; + if (registerType.category == RegisterType.Category.Unknown) { + return false; + } + + checkRegister(registerType, ReferenceCategories); + } + + { + //resolve and verify the class that we're checking against + InstructionWithReference instruction = (InstructionWithReference)analyzedInstruction.instruction; + + Item item = instruction.getReferencedItem(); + assert item.getItemType() == ItemType.TYPE_TYPE_ID_ITEM; + RegisterType registerType = RegisterType.getRegisterTypeForTypeIdItem((TypeIdItem)item); + checkRegister(registerType, ReferenceCategories); + + //TODO: is it valid to use an array type? + + //TODO: could probably do an even more sophisticated check, where we check the possible register types against the specified type. In some cases, we could determine that it always fails, and print a warning to that effect. + setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, + RegisterType.getRegisterType(RegisterType.Category.Boolean, null)); + return true; + } + } + + private boolean handleArrayLength(AnalyzedInstruction analyzedInstruction) { + TwoRegisterInstruction instruction = (TwoRegisterInstruction)analyzedInstruction; + + int arrayRegisterNumber = instruction.getRegisterB(); + RegisterType arrayRegisterType = analyzedInstruction.getPreInstructionRegisterType(arrayRegisterNumber); + assert arrayRegisterType != null; + if (arrayRegisterType.category == RegisterType.Category.Unknown) { + return false; + } + + assert arrayRegisterType.type instanceof ClassPath.ArrayClassDef; + + checkRegister(arrayRegisterType, ReferenceCategories); + if (arrayRegisterType.type != null) { + if (arrayRegisterType.type.getClassType().charAt(0) != '[') { + throw new ValidationException("Cannot use array-length with non-array type " + + arrayRegisterType.type.getClassType()); + } + } + + setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, + RegisterType.getRegisterType(RegisterType.Category.Integer, null)); + return true; + } + + private boolean handleNewInstance(AnalyzedInstruction analyzedInstruction) { + InstructionWithReference instruction = (InstructionWithReference)analyzedInstruction.instruction; + + Item item = instruction.getReferencedItem(); + assert item.getItemType() == ItemType.TYPE_TYPE_ID_ITEM; + + //TODO: need to check class access + RegisterType classType = RegisterType.getRegisterTypeForTypeIdItem((TypeIdItem)item); + checkRegister(classType, ReferenceCategories); + if (((TypeIdItem)item).getTypeDescriptor().charAt(0) == '[') { + throw new ValidationException("Cannot use array type \"" + ((TypeIdItem)item).getTypeDescriptor() + + "\" with new-instance. Use new-array instead."); + } + + setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, + RegisterType.getRegisterType(RegisterType.Category.UninitRef, classType.type)); + return true; + } + + private boolean handleNewArray(AnalyzedInstruction analyzedInstruction) { + { + TwoRegisterInstruction instruction = (TwoRegisterInstruction)analyzedInstruction.instruction; + + int sizeRegister = instruction.getRegisterB(); + RegisterType registerType = analyzedInstruction.getPreInstructionRegisterType(sizeRegister); + assert registerType != null; + + if (registerType.category == RegisterType.Category.Unknown) { + return false; + } + + checkRegister(registerType, Primitive32BitCategories); + } + + InstructionWithReference instruction = (InstructionWithReference)analyzedInstruction.instruction; + + Item item = instruction.getReferencedItem(); + assert item.getItemType() == ItemType.TYPE_TYPE_ID_ITEM; + + RegisterType arrayType = RegisterType.getRegisterTypeForTypeIdItem((TypeIdItem)item); + assert arrayType.type instanceof ClassPath.ArrayClassDef; + + checkRegister(arrayType, ReferenceCategories); + if (arrayType.type.getClassType().charAt(0) != '[') { + throw new ValidationException("Cannot use non-array type \"" + arrayType.type.getClassType() + + "\" with new-array. Use new-instance instead."); + } + + setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, arrayType); + return true; + } + + private static void checkRegister(RegisterType registerType, EnumSet validCategories) { + if (!validCategories.contains(registerType.category)) { + //TODO: add expected categories to error message + throw new ValidationException("Invalid register type. Expecting one of: " + " but got \"" + + registerType.category + "\""); + } + } + + private static void checkWideDestinationPair(AnalyzedInstruction analyzedInstruction) { + int register = analyzedInstruction.getDestinationRegister(); + + if (register == (analyzedInstruction.postRegisterMap.length - 1)) { + throw new ValidationException("v" + register + " is the last register and not a valid wide register " + + "pair."); + } + } + + private static RegisterType getAndCheckWideSourcePair(AnalyzedInstruction analyzedInstruction, int firstRegister) { + assert firstRegister >= 0 && firstRegister < analyzedInstruction.postRegisterMap.length; + + if (firstRegister == analyzedInstruction.postRegisterMap.length - 1) { + throw new ValidationException("v" + firstRegister + " is the last register and not a valid wide register " + + "pair."); + } + + RegisterType registerType = analyzedInstruction.getPreInstructionRegisterType(firstRegister); + assert registerType != null; + if (registerType.category == RegisterType.Category.Unknown) { + return registerType; + } + checkRegister(registerType, WideLowCategories); + + RegisterType secondRegisterType = analyzedInstruction.getPreInstructionRegisterType(firstRegister + 1); + assert secondRegisterType != null; + checkRegister(secondRegisterType, WideHighCategories); + + if (( registerType.category == RegisterType.Category.LongLo && + secondRegisterType.category == RegisterType.Category.DoubleHi) + || ( registerType.category == RegisterType.Category.DoubleLo && + secondRegisterType.category == RegisterType.Category.LongHi)) { + assert false; + throw new ValidationException("The first register in the wide register pair isn't the same type (long " + + "vs. double) as the second register in the pair"); + } + + return registerType; } } diff --git a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/RegisterInfo.java b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/RegisterInfo.java deleted file mode 100644 index 36c83adf..00000000 --- a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/RegisterInfo.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.jf.dexlib.Code.Analysis; - -import java.util.LinkedList; - -public class RegisterInfo { - private final LinkedList possibleRegisterTypes = new LinkedList(); - - protected RegisterInfo() { - } -} 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 6af7a5ee..c46e6bdb 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 @@ -1,16 +1,32 @@ package org.jf.dexlib.Code.Analysis; +import org.jf.dexlib.ClassDefItem; import org.jf.dexlib.TypeIdItem; +import static org.jf.dexlib.Code.Analysis.ClassPath.ClassDef; +import static org.jf.dexlib.Code.Analysis.ClassPath.PrimitiveClassDef; +import static org.jf.dexlib.Code.Analysis.ClassPath.ArrayClassDef; +import java.util.HashMap; public class RegisterType { - public final Category category; - public final TypeIdItem type; + private final static HashMap internedRegisterTypes = + new HashMap(); + + public final Category category; + public final ClassDef type; + + private RegisterType(Category category, ClassDef type) { + assert ((category == Category.Reference || category == Category.UninitRef) && type != null) || + ((category != Category.Reference && category != Category.UninitRef) && type == null); - protected RegisterType(Category category, TypeIdItem type) { this.category = category; this.type = type; } + @Override + public String toString() { + return "(" + category.name() + (type==null?"":("," + type.getClassType())) + ")"; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -31,26 +47,158 @@ public class RegisterType { return result; } - - /*private static RegisterType[][] mergeTable = - { - //Unknown Null Nonreference Reference Conflicted - {Unknown, Null, NonReference, Reference, Conflicted}, //Unknown - {Null, Null, NonReference, Reference, Conflicted}, //Null - {NonReference, NonReference, NonReference, Conflicted, Conflicted}, //NonReference - {Reference, Reference, Conflicted, Reference, Conflicted}, //Referenced - {Conflicted, Conflicted, Conflicted, Conflicted, Conflicted}, //Conflicted - };*/ - - /*public static RegisterType mergeRegisterTypes(RegisterType type1, RegisterType type2) { - return mergeTable[type1.ordinal()][type2.ordinal()]; - }*/ - public static enum Category { Unknown, Null, - NonReference, + One, + Boolean, + Byte, + PosByte, + Short, + PosShort, + Char, + Integer, + Float, + LongLo, + LongHi, + DoubleLo, + DoubleHi, + UninitRef, Reference, - Conflicted + Conflicted; + + protected static Category[][] mergeTable = + { + /* Unknown Null One, Boolean Byte PosByte Short PosShort Char Integer, Float, LongLo LongHi DoubleLo DoubleHi UninitRef Reference Conflicted*/ + /*Unknown*/ {Unknown, Unknown, Unknown, Unknown, Unknown, Unknown, Unknown, Unknown, Unknown, Unknown, Unknown, Unknown, Unknown, Unknown, Unknown, Unknown, Unknown, Unknown}, + /*Null*/ {Unknown, Null, Conflicted, Boolean, Byte, PosByte, Short, PosShort, Char, Integer, Float, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Reference, Conflicted}, + /*One*/ {Unknown, Conflicted, One, Boolean, Byte, PosByte, Short, PosShort, Char, Integer, Float, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted}, + /*Boolean*/ {Unknown, Boolean, Boolean, Boolean, Byte, PosByte, Short, PosShort, Char, Integer, Float, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted}, + /*Byte*/ {Unknown, Byte, Byte, Byte, Byte, Byte, Short, Short, Integer, Integer, Float, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted}, + /*PosByte*/ {Unknown, PosByte, PosByte, PosByte, Byte, PosByte, Short, PosShort, Char, Integer, Float, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted}, + /*Short*/ {Unknown, Short, Short, Short, Short, Short, Short, Short, Integer, Integer, Float, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted}, + /*PosShort*/ {Unknown, PosShort, PosShort, PosShort, Short, PosShort, Short, PosShort, Char, Integer, Float, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted}, + /*Char*/ {Unknown, Char, Char, Char, Integer, Char, Integer, Char, Char, Integer, Float, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted}, + /*Integer*/ {Unknown, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted}, + /*Float*/ {Unknown, Float, Float, Float, Float, Float, Float, Float, Float, Integer, Float, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted}, + /*LongLo*/ {Unknown, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, LongLo, Conflicted, LongLo, Conflicted, Conflicted, Conflicted, Conflicted}, + /*LongHi*/ {Unknown, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, LongHi, Conflicted, LongHi, Conflicted, Conflicted, Conflicted}, + /*DoubleLo*/ {Unknown, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, LongLo, Conflicted, DoubleLo, Conflicted, Conflicted, Conflicted, Conflicted}, + /*DoubleHi*/ {Unknown, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, LongHi, Conflicted, DoubleHi, Conflicted, Conflicted, Conflicted}, + /*UninitRef*/ {Unknown, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, UninitRef, Conflicted, Conflicted}, + /*Reference*/ {Unknown, Reference, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Reference, Conflicted}, + /*Conflicted*/ {Unknown, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted} + }; + } + + public static RegisterType getRegisterTypeForTypeIdItem(TypeIdItem typeIdItem) { + switch (typeIdItem.getTypeDescriptor().charAt(0)) { + case 'V': + throw new ValidationException("The V type can only be used as a method return type"); + case 'Z': + return getRegisterType(Category.Boolean, null); + case 'B': + return getRegisterType(Category.Byte, null); + case 'S': + return getRegisterType(Category.Short, null); + case 'C': + return getRegisterType(Category.Char, null); + case 'I': + return getRegisterType(Category.Integer, null); + case 'F': + return getRegisterType(Category.Float, null); + case 'J': + return getRegisterType(Category.LongLo, null); + case 'D': + return getRegisterType(Category.DoubleLo, null); + case 'L': + case '[': + return getRegisterType(Category.Reference, ClassPath.getClassDef(typeIdItem)); + default: + throw new RuntimeException("Invalid type: " + typeIdItem.getTypeDescriptor()); + } + } + + public static RegisterType getWideRegisterTypeForTypeIdItem(TypeIdItem typeIdItem, boolean firstRegister) { + if (typeIdItem.getRegisterCount() == 1) { + throw new RuntimeException("Cannot use this method for non-wide register type: " + + typeIdItem.getTypeDescriptor()); + } + + switch (typeIdItem.getTypeDescriptor().charAt(0)) { + case 'J': + if (firstRegister) { + return getRegisterType(Category.LongLo, null); + } else { + return getRegisterType(Category.LongHi, null); + } + case 'D': + if (firstRegister) { + return getRegisterType(Category.DoubleLo, null); + } else { + return getRegisterType(Category.DoubleHi, null); + } + default: + throw new RuntimeException("Invalid type: " + typeIdItem.getTypeDescriptor()); + } + } + + public static RegisterType getRegisterTypeForLiteral(long literalValue) { + if (literalValue < -32768) { + return getRegisterType(Category.Integer, null); + } + if (literalValue < -128) { + return getRegisterType(Category.Short, null); + } + if (literalValue < 0) { + return getRegisterType(Category.Byte, null); + } + if (literalValue == 0) { + return getRegisterType(Category.Null, null); + } + if (literalValue == 1) { + return getRegisterType(Category.One, null); + } + if (literalValue < 128) { + return getRegisterType(Category.PosByte, null); + } + if (literalValue < 32768) { + return getRegisterType(Category.PosShort, null); + } + if (literalValue < 65536) { + return getRegisterType(Category.Char, null); + } + return getRegisterType(Category.Integer, null); + } + + public RegisterType merge(RegisterType type) { + if (type == null || type == this) { + return this; + } + + Category mergedCategory = Category.mergeTable[this.category.ordinal()][type.category.ordinal()]; + if (mergedCategory == Category.Conflicted) { + throw new ValidationException("Incompatible register types." + + " Category1: " + this.category.name() + + (this.type==null?"":" Type1: " + this.type.getClassType()) + + " Category2: " + type.category.name() + + (type.type==null?"":" Type2: " + type.type.getClassType())); + } + + ClassDef mergedType = null; + if (mergedCategory == Category.Reference) { + mergedType = ClassPath.getCommonSuperclass(this.type, type.type); + } + return RegisterType.getRegisterType(mergedCategory, mergedType); + } + + public static RegisterType getRegisterType(Category category, ClassDef classType) { + RegisterType newRegisterType = new RegisterType(category, classType); + RegisterType internedRegisterType = internedRegisterTypes.get(newRegisterType); + if (internedRegisterType == null) { + internedRegisterTypes.put(newRegisterType, newRegisterType); + return newRegisterType; + } + return internedRegisterType; } } diff --git a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ValidationException.java b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ValidationException.java index e427c242..b602f7ae 100644 --- a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ValidationException.java +++ b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ValidationException.java @@ -1,6 +1,8 @@ package org.jf.dexlib.Code.Analysis; -public class ValidationException extends RuntimeException { +import org.jf.dexlib.Util.ExceptionWithContext; + +public class ValidationException extends ExceptionWithContext { public ValidationException(String errorMessage) { super(errorMessage); }