diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java index 2233f599..83104b2f 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java @@ -31,6 +31,8 @@ package org.jf.dexlib2.analysis; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; @@ -205,7 +207,7 @@ public class ClassPath { dexFiles.addAll(classPathDexFiles); } dexFiles.add(dexFile); - return new ClassPath(dexFiles, checkPackagePrivateAccess); + return new ClassPath(dexFiles, checkPackagePrivateAccess, isArt); } @Nonnull @@ -215,9 +217,7 @@ public class ClassPath { ArrayList dexFiles = Lists.newArrayList(); for (String classPathEntry: classPath) { - try { - dexFiles.addAll(loadClassPathEntry(classPathDirs, classPathEntry, api, experimental)); - } catch (ExceptionWithContext e){} + dexFiles.addAll(loadClassPathEntry(classPathDirs, classPathEntry, api, experimental)); } dexFiles.add(dexFile); return new ClassPath(dexFiles, checkPackagePrivateAccess, isArt); @@ -286,4 +286,16 @@ public class ClassPath { } throw new ExceptionWithContext("Cannot locate boot class path file %s", bootClassPathEntry); } + + private final Supplier fieldInstructionMapperSupplier = Suppliers.memoize( + new Supplier() { + @Override public OdexedFieldInstructionMapper get() { + return new OdexedFieldInstructionMapper(isArt); + } + }); + + @Nonnull + public OdexedFieldInstructionMapper getFieldInstructionMapper() { + return fieldInstructionMapperSupplier.get(); + } } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java index e513f23d..d011c1e7 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java @@ -34,10 +34,8 @@ package org.jf.dexlib2.analysis; import com.google.common.base.Predicates; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; +import com.google.common.collect.*; +import com.google.common.primitives.Ints; import org.jf.dexlib2.AccessFlags; import org.jf.dexlib2.analysis.util.TypeProtoUtils; import org.jf.dexlib2.iface.ClassDef; @@ -46,21 +44,23 @@ import org.jf.dexlib2.iface.Method; import org.jf.dexlib2.iface.reference.FieldReference; import org.jf.dexlib2.iface.reference.MethodReference; import org.jf.dexlib2.immutable.ImmutableMethod; +import org.jf.util.AlignmentUtils; import org.jf.util.ExceptionWithContext; import org.jf.util.SparseArray; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; +import java.util.*; /** * A class "prototype". This contains things like the interfaces, the superclass, the vtable and the instance fields * and their offsets. */ public class ClassProto implements TypeProto { + private static final byte REFERENCE = 0; + private static final byte WIDE = 1; + private static final byte OTHER = 2; + @Nonnull protected final ClassPath classPath; @Nonnull protected final String type; @@ -356,20 +356,20 @@ public class ClassProto implements TypeProto { } @Nonnull SparseArray getInstanceFields() { - return instanceFieldsSupplier.get(); + if (classPath.isArt) { + return artInstanceFieldsSupplier.get(); + } else { + return dalvikInstanceFieldsSupplier.get(); + } } - @Nonnull private final Supplier> instanceFieldsSupplier = + @Nonnull private final Supplier> dalvikInstanceFieldsSupplier = Suppliers.memoize(new Supplier>() { @Override public SparseArray get() { //This is a bit of an "involved" operation. We need to follow the same algorithm that dalvik uses to //arrange fields, so that we end up with the same field offsets (which is needed for deodexing). //See mydroid/dalvik/vm/oo/Class.c - computeFieldOffsets() - final byte REFERENCE = 0; - final byte WIDE = 1; - final byte OTHER = 2; - ArrayList fields = getSortedInstanceFields(getClassDef()); final int fieldCount = fields.size(); //the "type" for each field in fields. 0=reference,1=wide,2=other @@ -520,19 +520,6 @@ public class ClassProto implements TypeProto { return fields; } - private byte getFieldType(@Nonnull FieldReference field) { - switch (field.getType().charAt(0)) { - case '[': - case 'L': - return 0; //REFERENCE - case 'J': - case 'D': - return 1; //WIDE - default: - return 2; //OTHER - } - } - private void swap(byte[] fieldTypes, List fields, int position1, int position2) { byte tempType = fieldTypes[position1]; fieldTypes[position1] = fieldTypes[position2]; @@ -543,23 +530,199 @@ public class ClassProto implements TypeProto { } }); + private static class FieldGap implements Comparable { + public final int offset; + public final int size; + + public FieldGap(int offset, int size) { + this.offset = offset; + this.size = size; + } + + @Override public int compareTo(@Nonnull FieldGap o) { + int result = Ints.compare(o.size, size); + if (result != 0) { + return result; + } + return Ints.compare(o.offset, offset); + } + } + + @Nonnull private final Supplier> artInstanceFieldsSupplier = + Suppliers.memoize(new Supplier>() { + + @Override public SparseArray get() { + // We need to follow the same algorithm that art uses to arrange fields, so that we end up with the + // same field offsets, which is needed for deodexing. + // See LinkFields() in art/runtime/class_linker.cc + + PriorityQueue gaps = new PriorityQueue(); + + SparseArray linkedFields = new SparseArray(); + ArrayList fields = getSortedInstanceFields(getClassDef()); + + int fieldOffset = 0; + String superclassType = getSuperclass(); + if (superclassType != null) { + // TODO: what to do if superclass doesn't exist? + ClassProto superclass = (ClassProto) classPath.getClass(superclassType); + SparseArray superFields = superclass.getInstanceFields(); + FieldReference field = null; + int lastOffset = 0; + for (int i=0; i= fieldSize) { + gaps.poll(); + linkedFields.put(gap.offset, field); + if (gap.size > fieldSize) { + addFieldGap(gap.offset + fieldSize, gap.offset + gap.size, gaps); + } + } else { + linkedFields.append(fieldOffset, field); + fieldOffset += fieldSize; + } + } + + return linkedFields; + } + + private void addFieldGap(int gapStart, int gapEnd, @Nonnull PriorityQueue gaps) { + int offset = gapStart; + + while (offset < gapEnd) { + int remaining = gapEnd - offset; + + if ((remaining >= 4) && (offset % 4 == 0)) { + gaps.add(new FieldGap(offset, 4)); + offset += 4; + } else if (remaining >= 2 && (offset % 2 == 0)) { + gaps.add(new FieldGap(offset, 2)); + offset += 2; + } else { + gaps.add(new FieldGap(offset, 1)); + offset += 1; + } + } + } + + @Nonnull + private ArrayList getSortedInstanceFields(@Nonnull ClassDef classDef) { + ArrayList fields = Lists.newArrayList(classDef.getInstanceFields()); + Collections.sort(fields, new Comparator() { + @Override public int compare(Field field1, Field field2) { + int result = Ints.compare(getFieldSortOrder(field1), getFieldSortOrder(field2)); + if (result != 0) { + return result; + } + + result = field1.getName().compareTo(field2.getName()); + if (result != 0) { + return result; + } + return field1.getType().compareTo(field2.getType()); + } + }); + return fields; + } + + private int getFieldSortOrder(@Nonnull FieldReference field) { + // The sort order is based on type size (except references are first), and then based on the + // enum value of the primitive type for types of equal size. See: Primitive::Type enum + // in art/runtime/primitive.h + switch (field.getType().charAt(0)) { + /* reference */ + case '[': + case 'L': + return 0; + /* 64 bit */ + case 'J': + return 1; + case 'D': + return 2; + /* 32 bit */ + case 'I': + return 3; + case 'F': + return 4; + /* 16 bit */ + case 'C': + return 5; + case 'S': + return 6; + /* 8 bit */ + case 'Z': + return 7; + case 'B': + return 8; + } + throw new ExceptionWithContext("Invalid field type: %s", field.getType()); + } + + private int getFieldSize(@Nonnull FieldReference field) { + return getTypeSize(field.getType().charAt(0)); + } + }); + private int getNextFieldOffset() { SparseArray instanceFields = getInstanceFields(); if (instanceFields.size() == 0) { - return 8; + return classPath.isArt ? 0 : 8; } int lastItemIndex = instanceFields.size()-1; int fieldOffset = instanceFields.keyAt(lastItemIndex); FieldReference lastField = instanceFields.valueAt(lastItemIndex); - switch (lastField.getType().charAt(0)) { + if (classPath.isArt) { + return fieldOffset + getTypeSize(lastField.getType().charAt(0)); + } else { + switch (lastField.getType().charAt(0)) { + case 'J': + case 'D': + return fieldOffset + 8; + default: + return fieldOffset + 4; + } + } + } + + private static int getTypeSize(char type) { + switch (type) { case 'J': case 'D': - return fieldOffset + 8; - default: - return fieldOffset + 4; + return 8; + case '[': + case 'L': + case 'I': + case 'F': + return 4; + case 'C': + case 'S': + return 2; + case 'B': + case 'Z': + return 1; } + throw new ExceptionWithContext("Invalid type: %s", type); } @Nonnull List getVtable() { @@ -672,4 +835,17 @@ public class ClassProto implements TypeProto { AccessFlags.PUBLIC.getValue())) == 0; } }); + + private static byte getFieldType(@Nonnull FieldReference field) { + switch (field.getType().charAt(0)) { + case '[': + case 'L': + return 0; //REFERENCE + case 'J': + case 'D': + return 1; //WIDE + default: + return 2; //OTHER + } + } } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java index d5d9d722..401c0ec0 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java @@ -1573,8 +1573,8 @@ public class MethodAnalyzer { String fieldType = resolvedField.getType(); - Opcode opcode = OdexedFieldInstructionMapper.getAndCheckDeodexedOpcodeForOdexedOpcode(fieldType, - instruction.getOpcode()); + Opcode opcode = classPath.getFieldInstructionMapper().getAndCheckDeodexedOpcode( + fieldType, instruction.getOpcode()); Instruction22c deodexedInstruction = new ImmutableInstruction22c(opcode, (byte)instruction.getRegisterA(), (byte)instruction.getRegisterB(), resolvedField); @@ -1726,8 +1726,8 @@ public class MethodAnalyzer { Opcode originalOpcode = analyzedInstruction.instruction.getOpcode(); - Opcode opcode = OdexedFieldInstructionMapper.getAndCheckDeodexedOpcodeForOdexedOpcode(fieldType, - originalOpcode); + Opcode opcode = classPath.getFieldInstructionMapper().getAndCheckDeodexedOpcode( + fieldType, originalOpcode); Instruction deodexedInstruction; diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/OdexedFieldInstructionMapper.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/OdexedFieldInstructionMapper.java index 49136c35..6352f08c 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/OdexedFieldInstructionMapper.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/OdexedFieldInstructionMapper.java @@ -34,155 +34,111 @@ package org.jf.dexlib2.analysis; import org.jf.dexlib2.Opcode; import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; public class OdexedFieldInstructionMapper { - private static Opcode[][][][] opcodeMap = new Opcode[][][][] { - //get opcodes - new Opcode[][][] { - //iget quick - new Opcode[][] { - //odexed - new Opcode[] { - /*Z*/ Opcode.IGET_QUICK, - /*B*/ Opcode.IGET_QUICK, - /*S*/ Opcode.IGET_QUICK, - /*C*/ Opcode.IGET_QUICK, - /*I,F*/ Opcode.IGET_QUICK, - /*J,D*/ Opcode.IGET_WIDE_QUICK, - /*L,[*/ Opcode.IGET_OBJECT_QUICK - }, - //deodexed - new Opcode[] { - /*Z*/ Opcode.IGET_BOOLEAN, - /*B*/ Opcode.IGET_BYTE, - /*S*/ Opcode.IGET_SHORT, - /*C*/ Opcode.IGET_CHAR, - /*I,F*/ Opcode.IGET, - /*J,D*/ Opcode.IGET_WIDE, - /*L,[*/ Opcode.IGET_OBJECT - } - }, - //iget volatile - new Opcode[][] { - //odexed - new Opcode[] { - /*Z*/ Opcode.IGET_VOLATILE, - /*B*/ Opcode.IGET_VOLATILE, - /*S*/ Opcode.IGET_VOLATILE, - /*C*/ Opcode.IGET_VOLATILE, - /*I,F*/ Opcode.IGET_VOLATILE, - /*J,D*/ Opcode.IGET_WIDE_VOLATILE, - /*L,[*/ Opcode.IGET_OBJECT_VOLATILE - }, - //deodexed - new Opcode[] { - /*Z*/ Opcode.IGET_BOOLEAN, - /*B*/ Opcode.IGET_BYTE, - /*S*/ Opcode.IGET_SHORT, - /*C*/ Opcode.IGET_CHAR, - /*I,F*/ Opcode.IGET, - /*J,D*/ Opcode.IGET_WIDE, - /*L,[*/ Opcode.IGET_OBJECT - } - }, - //sget volatile - new Opcode[][] { - //odexed - new Opcode[] { - /*Z*/ Opcode.SGET_VOLATILE, - /*B*/ Opcode.SGET_VOLATILE, - /*S*/ Opcode.SGET_VOLATILE, - /*C*/ Opcode.SGET_VOLATILE, - /*I,F*/ Opcode.SGET_VOLATILE, - /*J,D*/ Opcode.SGET_WIDE_VOLATILE, - /*L,[*/ Opcode.SGET_OBJECT_VOLATILE - }, - //deodexed - new Opcode[] { - /*Z*/ Opcode.SGET_BOOLEAN, - /*B*/ Opcode.SGET_BYTE, - /*S*/ Opcode.SGET_SHORT, - /*C*/ Opcode.SGET_CHAR, - /*I,F*/ Opcode.SGET, - /*J,D*/ Opcode.SGET_WIDE, - /*L,[*/ Opcode.SGET_OBJECT - } - } - }, - //put opcodes - new Opcode[][][] { - //iput quick - new Opcode[][] { - //odexed - new Opcode[] { - /*Z*/ Opcode.IPUT_QUICK, - /*B*/ Opcode.IPUT_QUICK, - /*S*/ Opcode.IPUT_QUICK, - /*C*/ Opcode.IPUT_QUICK, - /*I,F*/ Opcode.IPUT_QUICK, - /*J,D*/ Opcode.IPUT_WIDE_QUICK, - /*L,[*/ Opcode.IPUT_OBJECT_QUICK - }, - //deodexed - new Opcode[] { - /*Z*/ Opcode.IPUT_BOOLEAN, - /*B*/ Opcode.IPUT_BYTE, - /*S*/ Opcode.IPUT_SHORT, - /*C*/ Opcode.IPUT_CHAR, - /*I,F*/ Opcode.IPUT, - /*J,D*/ Opcode.IPUT_WIDE, - /*L,[*/ Opcode.IPUT_OBJECT - } - }, - //iput volatile - new Opcode[][] { - //odexed - new Opcode[] { - /*Z*/ Opcode.IPUT_VOLATILE, - /*B*/ Opcode.IPUT_VOLATILE, - /*S*/ Opcode.IPUT_VOLATILE, - /*C*/ Opcode.IPUT_VOLATILE, - /*I,F*/ Opcode.IPUT_VOLATILE, - /*J,D*/ Opcode.IPUT_WIDE_VOLATILE, - /*L,[*/ Opcode.IPUT_OBJECT_VOLATILE - }, - //deodexed - new Opcode[] { - /*Z*/ Opcode.IPUT_BOOLEAN, - /*B*/ Opcode.IPUT_BYTE, - /*S*/ Opcode.IPUT_SHORT, - /*C*/ Opcode.IPUT_CHAR, - /*I,F*/ Opcode.IPUT, - /*J,D*/ Opcode.IPUT_WIDE, - /*L,[*/ Opcode.IPUT_OBJECT - } - }, - //sput volatile - new Opcode[][] { - //odexed - new Opcode[] { - /*Z*/ Opcode.SPUT_VOLATILE, - /*B*/ Opcode.SPUT_VOLATILE, - /*S*/ Opcode.SPUT_VOLATILE, - /*C*/ Opcode.SPUT_VOLATILE, - /*I,F*/ Opcode.SPUT_VOLATILE, - /*J,D*/ Opcode.SPUT_WIDE_VOLATILE, - /*L,[*/ Opcode.SPUT_OBJECT_VOLATILE - }, - //deodexed - new Opcode[] { - /*Z*/ Opcode.SPUT_BOOLEAN, - /*B*/ Opcode.SPUT_BYTE, - /*S*/ Opcode.SPUT_SHORT, - /*C*/ Opcode.SPUT_CHAR, - /*I,F*/ Opcode.SPUT, - /*J,D*/ Opcode.SPUT_WIDE, - /*L,[*/ Opcode.SPUT_OBJECT - } - } - } + + private static final int GET = 0; + private static final int PUT = 1; + + private static final int PRIMITIVE = 0; + private static final int WIDE = 1; + private static final int REFERENCE = 2; + + private static class FieldOpcode { + public final char type; + @Nonnull public final Opcode normalOpcode; + @Nonnull public final Opcode quickOpcode; + @Nullable public final Opcode volatileOpcode; + + public FieldOpcode(char type, @Nonnull Opcode normalOpcode, @Nonnull Opcode quickOpcode, + @Nullable Opcode volatileOpcode) { + this.type = type; + this.normalOpcode = normalOpcode; + this.quickOpcode = quickOpcode; + this.volatileOpcode = volatileOpcode; + } + + public FieldOpcode(char type, @Nonnull Opcode normalOpcode, @Nonnull Opcode quickOpcode) { + this.type = type; + this.normalOpcode = normalOpcode; + this.quickOpcode = quickOpcode; + this.volatileOpcode = null; + } + } + + private static final FieldOpcode[] dalvikFieldOpcodes = new FieldOpcode[] { + new FieldOpcode('Z', Opcode.IGET_BOOLEAN, Opcode.IGET_QUICK, Opcode.IGET_VOLATILE), + new FieldOpcode('B', Opcode.IGET_BYTE, Opcode.IGET_QUICK, Opcode.IGET_VOLATILE), + new FieldOpcode('S', Opcode.IGET_SHORT, Opcode.IGET_QUICK, Opcode.IGET_VOLATILE), + new FieldOpcode('C', Opcode.IGET_CHAR, Opcode.IGET_QUICK, Opcode.IGET_VOLATILE), + new FieldOpcode('I', Opcode.IGET, Opcode.IGET_QUICK, Opcode.IGET_VOLATILE), + new FieldOpcode('F', Opcode.IGET, Opcode.IGET_QUICK, Opcode.IGET_VOLATILE), + new FieldOpcode('J', Opcode.IGET_WIDE, Opcode.IGET_WIDE_QUICK, Opcode.IGET_WIDE_VOLATILE), + new FieldOpcode('D', Opcode.IGET_WIDE, Opcode.IGET_WIDE_QUICK, Opcode.IGET_WIDE_VOLATILE), + new FieldOpcode('L', Opcode.IGET_OBJECT, Opcode.IGET_OBJECT_QUICK, Opcode.IGET_OBJECT_VOLATILE), + new FieldOpcode('[', Opcode.IGET_OBJECT, Opcode.IGET_OBJECT_QUICK, Opcode.IGET_OBJECT_VOLATILE), + + new FieldOpcode('Z', Opcode.IPUT_BOOLEAN, Opcode.IPUT_QUICK, Opcode.IPUT_VOLATILE), + new FieldOpcode('B', Opcode.IPUT_BYTE, Opcode.IPUT_QUICK, Opcode.IPUT_VOLATILE), + new FieldOpcode('S', Opcode.IPUT_SHORT, Opcode.IPUT_QUICK, Opcode.IPUT_VOLATILE), + new FieldOpcode('C', Opcode.IPUT_CHAR, Opcode.IPUT_QUICK, Opcode.IPUT_VOLATILE), + new FieldOpcode('I', Opcode.IPUT, Opcode.IPUT_QUICK, Opcode.IPUT_VOLATILE), + new FieldOpcode('F', Opcode.IPUT, Opcode.IPUT_QUICK, Opcode.IPUT_VOLATILE), + new FieldOpcode('J', Opcode.IPUT_WIDE, Opcode.IPUT_WIDE_QUICK, Opcode.IPUT_WIDE_VOLATILE), + new FieldOpcode('D', Opcode.IPUT_WIDE, Opcode.IPUT_WIDE_QUICK, Opcode.IPUT_WIDE_VOLATILE), + new FieldOpcode('L', Opcode.IPUT_OBJECT, Opcode.IPUT_OBJECT_QUICK, Opcode.IPUT_OBJECT_VOLATILE), + new FieldOpcode('[', Opcode.IPUT_OBJECT, Opcode.IPUT_OBJECT_QUICK, Opcode.IPUT_OBJECT_VOLATILE), }; + private static final FieldOpcode[] artFieldOpcodes = new FieldOpcode[] { + new FieldOpcode('Z', Opcode.IGET_BOOLEAN, Opcode.IGET_BOOLEAN_QUICK), + new FieldOpcode('B', Opcode.IGET_BYTE, Opcode.IGET_BYTE_QUICK), + new FieldOpcode('S', Opcode.IGET_SHORT, Opcode.IGET_SHORT_QUICK), + new FieldOpcode('C', Opcode.IGET_CHAR, Opcode.IGET_CHAR_QUICK), + new FieldOpcode('I', Opcode.IGET, Opcode.IGET_QUICK), + new FieldOpcode('F', Opcode.IGET, Opcode.IGET_QUICK), + new FieldOpcode('J', Opcode.IGET_WIDE, Opcode.IGET_WIDE_QUICK), + new FieldOpcode('D', Opcode.IGET_WIDE, Opcode.IGET_WIDE_QUICK), + new FieldOpcode('L', Opcode.IGET_OBJECT, Opcode.IGET_OBJECT_QUICK), + new FieldOpcode('[', Opcode.IGET_OBJECT, Opcode.IGET_OBJECT_QUICK), + + new FieldOpcode('Z', Opcode.IPUT_BOOLEAN, Opcode.IPUT_BOOLEAN_QUICK), + new FieldOpcode('B', Opcode.IPUT_BYTE, Opcode.IPUT_BYTE_QUICK), + new FieldOpcode('S', Opcode.IPUT_SHORT, Opcode.IPUT_SHORT_QUICK), + new FieldOpcode('C', Opcode.IPUT_CHAR, Opcode.IPUT_CHAR_QUICK), + new FieldOpcode('I', Opcode.IPUT, Opcode.IPUT_QUICK), + new FieldOpcode('F', Opcode.IPUT, Opcode.IPUT_QUICK), + new FieldOpcode('J', Opcode.IPUT_WIDE, Opcode.IPUT_WIDE_QUICK), + new FieldOpcode('D', Opcode.IPUT_WIDE, Opcode.IPUT_WIDE_QUICK), + new FieldOpcode('L', Opcode.IPUT_OBJECT, Opcode.IPUT_OBJECT_QUICK), + new FieldOpcode('[', Opcode.IPUT_OBJECT, Opcode.IPUT_OBJECT_QUICK) + }; + + private final FieldOpcode[][] opcodeMap = new FieldOpcode[2][10]; + private final Map opcodeValueTypeMap = new HashMap(30); + + private static int getValueType(char type) { + switch (type) { + case 'Z': + case 'B': + case 'S': + case 'C': + case 'I': + case 'F': + return PRIMITIVE; + case 'J': + case 'D': + return WIDE; + case 'L': + case '[': + return REFERENCE; + } + throw new RuntimeException(String.format("Unknown type %s: ", type)); + } + private static int getTypeIndex(char type) { switch (type) { case 'Z': @@ -194,47 +150,61 @@ public class OdexedFieldInstructionMapper { case 'C': return 3; case 'I': - case 'F': return 4; - case 'J': - case 'D': + case 'F': return 5; - case 'L': - case '[': + case 'J': return 6; - default: + case 'D': + return 7; + case 'L': + return 8; + case '[': + return 9; } throw new RuntimeException(String.format("Unknown type %s: ", type)); } - private static int getOpcodeSubtype(@Nonnull Opcode opcode) { - if (opcode.isOdexedInstanceQuick()) { - return 0; - } else if (opcode.isOdexedInstanceVolatile()) { - return 1; - } else if (opcode.isOdexedStaticVolatile()) { - return 2; + private static boolean isGet(@Nonnull Opcode opcode) { + return (opcode.flags & Opcode.SETS_REGISTER) != 0; + } + + public OdexedFieldInstructionMapper(boolean isArt) { + FieldOpcode[] opcodes; + if (isArt) { + opcodes = artFieldOpcodes; + } else { + opcodes = dalvikFieldOpcodes; + } + + for (FieldOpcode fieldOpcode: opcodes) { + opcodeMap[isGet(fieldOpcode.normalOpcode)?GET:PUT][getTypeIndex(fieldOpcode.type)] = fieldOpcode; + + opcodeValueTypeMap.put(fieldOpcode.quickOpcode, getValueType(fieldOpcode.type)); + if (fieldOpcode.volatileOpcode != null) { + opcodeValueTypeMap.put(fieldOpcode.volatileOpcode, getValueType(fieldOpcode.type)); + } } - throw new RuntimeException(String.format("Not an odexed field access opcode: %s", opcode.name)); } @Nonnull - static Opcode getAndCheckDeodexedOpcodeForOdexedOpcode(@Nonnull String fieldType, @Nonnull Opcode odexedOpcode) { - int opcodeType = odexedOpcode.setsRegister()?0:1; - int opcodeSubType = getOpcodeSubtype(odexedOpcode); - int typeIndex = getTypeIndex(fieldType.charAt(0)); + public Opcode getAndCheckDeodexedOpcode(@Nonnull String fieldType, @Nonnull Opcode odexedOpcode) { + FieldOpcode fieldOpcode = opcodeMap[isGet(odexedOpcode)?GET:PUT][getTypeIndex(fieldType.charAt(0))]; - Opcode correctOdexedOpcode, deodexedOpcode; - - correctOdexedOpcode = opcodeMap[opcodeType][opcodeSubType][0][typeIndex]; - deodexedOpcode = opcodeMap[opcodeType][opcodeSubType][1][typeIndex]; - - if (correctOdexedOpcode != odexedOpcode) { + if (!isCompatible(odexedOpcode, fieldOpcode.type)) { throw new AnalysisException(String.format("Incorrect field type \"%s\" for %s", fieldType, odexedOpcode.name)); } - return deodexedOpcode; + return fieldOpcode.normalOpcode; + } + + private boolean isCompatible(Opcode opcode, char type) { + Integer valueType = opcodeValueTypeMap.get(opcode); + if (valueType == null) { + throw new RuntimeException("Unexpected opcode: " + opcode.name); + } + return valueType == getValueType(type); } }