Implement ClassPath class, refactor analysis code to use new ClassPath functionality, start adding verification for individual opcodes

git-svn-id: https://smali.googlecode.com/svn/trunk@574 55b6fa8a-2a1e-11de-a435-ffa8d773f76a
This commit is contained in:
JesusFreke@JesusFreke.com 2010-01-24 00:55:17 +00:00
parent 2d5d83efca
commit fffb29fd9d
8 changed files with 1812 additions and 113 deletions

View File

@ -1,4 +0,0 @@
package org.jf.baksmali.Analysis;
public class Analysis {
}

View File

@ -1,5 +0,0 @@
package org.jf.baksmali.Analysis;
public interface AnalysisInstruction {
}

View File

@ -1,6 +1,7 @@
package org.jf.dexlib.Code.Analysis; 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; import java.util.LinkedList;
@ -11,19 +12,24 @@ public class AnalyzedInstruction {
protected final Instruction instruction; 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 * Instructions that can pass on execution to this one during normal execution
*/ */
protected LinkedList<AnalyzedInstruction> predecessors = new LinkedList<AnalyzedInstruction>(); protected final LinkedList<AnalyzedInstruction> predecessors = new LinkedList<AnalyzedInstruction>();
/** /**
* Instructions that can execution could pass on to next during normal execution * Instructions that can execution could pass on to next during normal execution
*/ */
protected LinkedList<AnalyzedInstruction> successors = new LinkedList<AnalyzedInstruction>(); protected final LinkedList<AnalyzedInstruction> successors = new LinkedList<AnalyzedInstruction>();
/**
* 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 * 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; protected boolean dead = false;
public AnalyzedInstruction(Instruction instruction, int codeAddress) { public AnalyzedInstruction(Instruction instruction, int instructionIndex, int registerCount) {
this.instruction = instruction; this.instruction = instruction;
this.codeAddress = codeAddress; this.instructionIndex = instructionIndex;
this.postRegisterMap = new RegisterType[registerCount];
} }
public int getCodeAddress() { public int getInstructionIndex() {
return codeAddress; return instructionIndex;
} }
protected void addPredecessor(AnalyzedInstruction predecessor) { protected void addPredecessor(AnalyzedInstruction predecessor) {
@ -61,5 +68,78 @@ public class AnalyzedInstruction {
successors.add(successor); successors.add(successor);
return true; 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];
}
}
} }

View File

@ -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<String, ClassDef> 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<String, ClassDef>();
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<ClassDef> implementedInterfaces;
private final boolean isInterface;
private final int classDepth;
private final String[] vtable;
private final HashMap<String, Integer> virtualMethodLookup;
private final SparseArray<String> instanceFields;
private final HashMap<String, Integer> 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<ClassDef>();
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<String, Integer>((int)Math.ceil(vtable.length / .7f), .75f);
for (int i=0; i<vtable.length; i++) {
virtualMethodLookup.put(vtable[i], i);
}
instanceFields = loadFields(classDefItem);
instanceFieldLookup = new HashMap<String, Integer>((int)Math.ceil(instanceFields.size() / .7f), .75f);
for (int i=0; i<instanceFields.size(); i++) {
instanceFieldLookup.put(instanceFields.get(i), i);
}
}
public String getClassType() {
return classType;
}
public ClassDef getSuperclass() {
return superclass;
}
public int getClassDepth() {
return classDepth;
}
public boolean isInterface() {
return this.isInterface;
}
public boolean extendsClass(ClassDef superclassDef) {
if (superclassDef == null) {
return false;
}
if (this == superclassDef) {
return true;
}
int superclassDepth = superclassDef.classDepth;
ClassDef ancestor = this;
while (ancestor.classDepth > 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<instanceFields.size(); i++) {
int fieldOffset = instanceFields.keyAt(i);
System.out.println(fieldOffset + ":\t" + instanceFields.valueAt(i));
}
}
private void swap(byte[] fieldTypes, String[] fields, int position1, int position2) {
byte tempType = fieldTypes[position1];
fieldTypes[position1] = fieldTypes[position2];
fieldTypes[position2] = tempType;
String tempField = fields[position1];
fields[position1] = fields[position2];
fields[position2] = tempField;
}
private ClassDef loadSuperclass(ClassDefItem classDefItem) {
if (classDefItem.getClassType().getTypeDescriptor().equals("Ljava/lang/Object;")) {
if (classDefItem.getSuperclass() != null) {
throw new ExceptionWithContext("Invalid superclass " +
classDefItem.getSuperclass().getTypeDescriptor() + " for Ljava/lang/Object;. " +
"The Object class cannot have a superclass");
}
return null;
} else {
TypeIdItem superClass = classDefItem.getSuperclass();
if (superClass == null) {
throw new ExceptionWithContext(classDefItem.getClassType().getTypeDescriptor() +
" has no superclass");
}
ClassDef superclass = ClassPath.getClassDef(superClass.getTypeDescriptor());
if (!isInterface && superclass.isInterface) {
throw new ValidationException("Class " + classType + " has the interface " + superclass.classType +
" as its superclass");
}
if (isInterface && !superclass.isInterface && superclass !=
ClassPath.theClassPath.javaLangObjectClassDef) {
throw new ValidationException("Interface " + classType + " has the non-interface class " +
superclass.classType + " as its superclass");
}
return superclass;
}
}
private TreeSet<ClassDef> loadAllImplementedInterfaces(ClassDefItem classDefItem) {
assert classType != null;
assert classType.equals("Ljava/lang/Object;") || superclass != null;
assert classDefItem != null;
TreeSet<ClassDef> implementedInterfaceSet = new TreeSet<ClassDef>();
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<String> virtualMethodList = new LinkedList<String>();
//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<String, Integer> tempVirtualMethodLookup = new HashMap<String, Integer>();
//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<virtualMethodList.size(); i++) {
vtable[i] = virtualMethodList.get(i);
}
return vtable;
}
private SparseArray<String> 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<encodedFields.length; i++) {
EncodedField encodedField = encodedFields[i];
String fieldType = encodedField.field.getFieldType().getTypeDescriptor();
String field = String.format("%s:%s", encodedField.field.getFieldName().getStringValue(),
fieldType);
fieldTypes[i] = getFieldType(field);
fields[i] = field;
}
}
}
if (fields == null) {
fields = new String[0];
fieldTypes = new byte[0];
}
//The first operation is to move all of the reference fields to the front. To do this, find the first
//non-reference field, then find the last reference field, swap them and repeat
int back = fields.length - 1;
int front;
for (front = 0; front<fields.length; front++) {
if (fieldTypes[front] != REFERENCE) {
while (back > 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<fields.length; front++) {
if (fieldTypes[front] != WIDE) {
while (back > 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<String> instanceFields = new SparseArray<String>(totalFieldCount);
int fieldOffset;
if (superclass != null && superFieldCount > 0) {
for (int i=0; i<superFieldCount; i++) {
instanceFields.append(superclass.instanceFields.keyAt(i), superclass.instanceFields.valueAt(i));
}
fieldOffset = instanceFields.keyAt(superFieldCount-1);
String lastSuperField = superclass.instanceFields.valueAt(superFieldCount-1);
assert lastSuperField.indexOf(':') >= 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<fields.length; i++) {
String field = fields[i];
//add padding to align the wide fields, if needed
if (fieldTypes[i] == WIDE && !gotDouble) {
if (!gotDouble) {
if (fieldOffset % 8 != 0) {
assert fieldOffset % 8 == 4;
fieldOffset += 4;
}
gotDouble = true;
}
}
instanceFields.append(fieldOffset, field);
if (fieldTypes[i] == WIDE) {
fieldOffset += 8;
} else {
fieldOffset += 4;
}
}
return instanceFields;
}
private byte getFieldType(String field) {
int sepIndex = field.indexOf(':');
//we could use sepIndex >= 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();
}
}
}

View File

@ -1,10 +0,0 @@
package org.jf.dexlib.Code.Analysis;
import java.util.LinkedList;
public class RegisterInfo {
private final LinkedList<RegisterType> possibleRegisterTypes = new LinkedList<RegisterType>();
protected RegisterInfo() {
}
}

View File

@ -1,16 +1,32 @@
package org.jf.dexlib.Code.Analysis; package org.jf.dexlib.Code.Analysis;
import org.jf.dexlib.ClassDefItem;
import org.jf.dexlib.TypeIdItem; 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 class RegisterType {
public final Category category; private final static HashMap<RegisterType, RegisterType> internedRegisterTypes =
public final TypeIdItem type; new HashMap<RegisterType, RegisterType>();
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.category = category;
this.type = type; this.type = type;
} }
@Override
public String toString() {
return "(" + category.name() + (type==null?"":("," + type.getClassType())) + ")";
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
@ -31,26 +47,158 @@ public class RegisterType {
return result; 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 { public static enum Category {
Unknown, Unknown,
Null, Null,
NonReference, One,
Boolean,
Byte,
PosByte,
Short,
PosShort,
Char,
Integer,
Float,
LongLo,
LongHi,
DoubleLo,
DoubleHi,
UninitRef,
Reference, 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;
} }
} }

View File

@ -1,6 +1,8 @@
package org.jf.dexlib.Code.Analysis; 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) { public ValidationException(String errorMessage) {
super(errorMessage); super(errorMessage);
} }