diff --git a/dexlib/src/main/java/org/jf/dexlib/Util/Deodexerant.java b/dexlib/src/main/java/org/jf/dexlib/Util/Deodexerant.java index ffda3b3a..10aae6fe 100644 --- a/dexlib/src/main/java/org/jf/dexlib/Util/Deodexerant.java +++ b/dexlib/src/main/java/org/jf/dexlib/Util/Deodexerant.java @@ -46,6 +46,9 @@ public class Deodexerant { private final int port; private final HashMap vtableMap = new HashMap(); + private final HashMap cachedCommonSuperclassLookup = + new HashMap(); + private InlineMethod[] inlineMethods; public final DexFile dexFile; @@ -59,67 +62,77 @@ public class Deodexerant { this.port = port; } + private void loadInlineMethods() { + List responseLines = sendMultilineCommand("I"); + + inlineMethods = new InlineMethod[responseLines.size()]; + for (int i=0; i= inlineMethods.length) { + throw new RuntimeException("Invalid inline method index " + inlineMethodIndex + ". Too big."); } - String methodDescriptor = response.substring(colon+2); - - Matcher m = fullMethodPattern.matcher(methodDescriptor); - if (!m.matches()) { - throw new RuntimeException("Invalid method descriptor: " + methodDescriptor); - } - - String classType = m.group(1); - String methodName = m.group(2); - String methodParams = m.group(3); - String methodRet = m.group(4); - - TypeIdItem classTypeItem = TypeIdItem.getInternedTypeIdItem(dexFile, classType); - if (classTypeItem == null) { - throw new RuntimeException("Could not find type " + classType + " in the dex file"); - } - - MethodIdItem method = parseAndResolveMethod(classTypeItem, methodName, methodParams, methodRet); - if (method == null) { - throw new RuntimeException("Could not find method " + methodDescriptor + " in the dex file"); - } - - return new InlineMethod(method, type); + return inlineMethods[inlineMethodIndex]; } public FieldIdItem lookupField(TypeIdItem type, int fieldOffset) { - connectIfNeeded(); + ClassData classData = getClassData(type); - String response = sendCommand("F " + type.getTypeDescriptor() + " " + fieldOffset); - - int colon = response.indexOf(':'); - if (colon == -1) { - throw new RuntimeException("Invalid response from deodexerant"); - } - - String fieldDescriptor = response.substring(colon+2); - - return parseAndLookupField(fieldDescriptor); + return classData.lookupField(fieldOffset); } private ClassData getClassData(TypeIdItem type) { @@ -150,7 +163,7 @@ public class Deodexerant { return classData.lookupMethod(methodIndex); } - + public String lookupSuperclass(String typeDescriptor) { connectIfNeeded(); @@ -168,15 +181,20 @@ public class Deodexerant { } public String lookupCommonSuperclass(String typeDescriptor1, String typeDescriptor2) { - connectIfNeeded(); + CommonSuperclassLookup lookup = new CommonSuperclassLookup(typeDescriptor1, typeDescriptor2); + String result = cachedCommonSuperclassLookup.get(lookup); + if (result == null) { + String response = sendCommand("C " + typeDescriptor1 + " " + typeDescriptor2); + int colon = response.indexOf(':'); + if (colon == -1) { + return null; + } - String response = sendCommand("C " + typeDescriptor1 + " " + typeDescriptor2); - int colon = response.indexOf(':'); - if (colon == -1) { - return null; + result = response.substring(colon+2); + + cachedCommonSuperclassLookup.put(lookup, result); } - - return response.substring(colon+2); + return result; } private String sendCommand(String cmd) { @@ -199,7 +217,7 @@ public class Deodexerant { //The command is still just a single line, but we're expecting a multi-line //response. The repsonse is considered finished when a line starting with "err" //or with "done" is encountered - private List sendMultlineCommand(String cmd) { + private List sendMultilineCommand(String cmd) { try { connectIfNeeded(); @@ -208,6 +226,9 @@ public class Deodexerant { ArrayList responseLines = new ArrayList(); String response = in.readLine(); + if (response == null) { + throw new RuntimeException("Error talking to deodexerant"); + } while (!response.startsWith("done")) { if (response.startsWith("err")) { @@ -344,23 +365,28 @@ public class Deodexerant { private static final Pattern fullMethodPattern = Pattern.compile("(\\[*(?:L[^;]+;|[ZBSCIJFD]))->([^(]+)\\(([^)]*)\\)(.+)"); private static final Pattern shortMethodPattern = Pattern.compile("([^(]+)\\(([^)]*)\\)(.+)"); + //private static final Pattern fieldPattern = Pattern.compile("(\\[*L[^;]+;)->([^:]+):(.+)"); - private static final Pattern fieldPattern = Pattern.compile("(\\[*L[^;]+;)->([^:]+):(.+)"); - private FieldIdItem parseAndLookupField(String field) { - //expecting a string like Lsome/class;->someField:Lfield/type; - Matcher m = fieldPattern.matcher(field); - if (!m.matches()) { - throw new RuntimeException("invalid field string: " + field); + private FieldIdItem parseAndResolveField(TypeIdItem classType, String field) { + //expecting a string like someField:Lfield/type; + String[] parts = field.split(":"); + if (parts.length != 2) { + throw new RuntimeException("Invalid field descriptor " + field); } - String clazz = m.group(1); - String fieldName = m.group(2); - String fieldType = m.group(3); + String fieldName = parts[0]; + String fieldType = parts[1]; - TypeIdItem classType = TypeIdItem.getInternedTypeIdItem(dexFile, clazz); StringIdItem fieldNameItem = StringIdItem.getInternedStringIdItem(dexFile, fieldName); + if (fieldNameItem == null) { + return null; + } + TypeIdItem fieldTypeItem = TypeIdItem.getInternedTypeIdItem(dexFile, fieldType); + if (fieldTypeItem == null) { + return null; + } FieldIdItem fieldIdItem; @@ -408,13 +434,17 @@ public class Deodexerant { private class ClassData { - public final TypeIdItem ClassType; + private final TypeIdItem ClassType; - public boolean vtableLoaded; - public String[] MethodNames; - public String[] MethodParams; - public String[] MethodRets; - public MethodIdItem[] resolvedMethods; + private boolean vtableLoaded = false; + private String[] methodNames; + private String[] methodParams; + private String[] methodRets; + private MethodIdItem[] resolvedMethods; + + private boolean fieldsLoaded = false; + private SparseArray instanceFields; + private SparseArray resolvedFields; public ClassData(TypeIdItem classType) { @@ -430,18 +460,38 @@ public class Deodexerant { throw new RuntimeException("Invalid vtable index " + index + ". Too large."); } if (resolvedMethods[index] == null) { - resolvedMethods[index] = parseAndResolveMethod(ClassType, MethodNames[index], MethodParams[index], - MethodRets[index]); + resolvedMethods[index] = parseAndResolveMethod(ClassType, methodNames[index], methodParams[index], + methodRets[index]); } return resolvedMethods[index]; } - private void loadvtable() { - List responseLines = sendMultlineCommand("V " + ClassType.getTypeDescriptor()); + public FieldIdItem lookupField(int fieldOffset) { + if (!fieldsLoaded) { + loadFields(); + } - MethodNames = new String[responseLines.size()]; - MethodParams = new String[responseLines.size()]; - MethodRets = new String[responseLines.size()]; + FieldIdItem fieldIdItem = resolvedFields.get(fieldOffset); + if (fieldIdItem == null) { + String field = instanceFields.get(fieldOffset); + if (field == null) { + throw new RuntimeException("Invalid field offset " + fieldOffset); + } + fieldIdItem = parseAndResolveField(ClassType, field); + if (fieldIdItem != null) { + resolvedFields.put(fieldOffset, fieldIdItem); + } + } + return fieldIdItem; + + } + + private void loadvtable() { + List responseLines = sendMultilineCommand("V " + ClassType.getTypeDescriptor()); + + methodNames = new String[responseLines.size()]; + methodParams = new String[responseLines.size()]; + methodRets = new String[responseLines.size()]; resolvedMethods = new MethodIdItem[responseLines.size()]; int index = 0; @@ -456,13 +506,62 @@ public class Deodexerant { throw new RuntimeException("invalid method string: " + method); } - MethodNames[index] = m.group(1); - MethodParams[index] = m.group(2); - MethodRets[index] = m.group(3); + methodNames[index] = m.group(1); + methodParams[index] = m.group(2); + methodRets[index] = m.group(3); index++; } vtableLoaded = true; } + + private void loadFields() { + List responseLines = sendMultilineCommand("F " + ClassType.getTypeDescriptor()); + + instanceFields = new SparseArray(responseLines.size()); + resolvedFields = new SparseArray(responseLines.size()); + + for (String fieldLine: responseLines) { + if (!fieldLine.startsWith("field: ")) { + throw new RuntimeException("Invalid response from deodexerant"); + } + + String field = fieldLine.substring(7); + String[] parts = field.split(" "); + if (parts.length != 2) { + throw new RuntimeException("Invalid response from deodexerant"); + } + + int fieldOffset = Integer.parseInt(parts[0]); + instanceFields.put(fieldOffset, parts[1]); + } + + fieldsLoaded = true; + } + } + + private static class CommonSuperclassLookup { + public final String Type1; + public final String Type2; + + public CommonSuperclassLookup(String type1, String type2) { + this.Type1 = type1; + this.Type2 = type2; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + CommonSuperclassLookup that = (CommonSuperclassLookup) o; + + return Type1.equals(that.Type1) && Type2.equals(that.Type2); + } + + @Override + public int hashCode() { + return Type1.hashCode() + 31 * Type2.hashCode(); + } } }