# HG changeset patch # User Michael Starzinger # Date 1302472467 -7200 # Node ID 8bb44682be11be41b2d0c3dfae10b0135b526117 # Parent 3ec98ede8a062bfc178b1af625d21f4579c2d8b1 Implemented lazy parsing for DEX parser. * dex/ClassData.java (getCode): Perform lazy parsing. * dex/ClassDef.java (getClassData, getStaticValues): Likewise. * dex/Code.java (getDebugInfo): Likewise. diff -r 3ec98ede8a06 -r 8bb44682be11 src/main/java/org/icedrobot/daneel/dex/ClassData.java --- a/src/main/java/org/icedrobot/daneel/dex/ClassData.java Sun Apr 10 23:52:10 2011 +0200 +++ b/src/main/java/org/icedrobot/daneel/dex/ClassData.java Sun Apr 10 23:54:27 2011 +0200 @@ -61,6 +61,8 @@ return new ClassData(buffer, dex, classDef); } + private final DexFile dex; + private final ClassDef classDef; private int staticFieldsSize; @@ -79,15 +81,14 @@ private final MethodId[] directMethodsIds; private int[] directMethodsFlags; - private final Code[] directMethodsCode; + private final int[] directMethodsCodeOff; private final MethodId[] virtualMethodsIds; private int[] virtualMethodsFlags; - private final Code[] virtualMethodsCode; - - private final Object[] staticValues; + private final int[] virtualMethodsCodeOff; private ClassData(ByteBuffer buffer, DexFile dex, ClassDef classDef) { + this.dex = dex; this.classDef = classDef; staticFieldsSize = BufferUtil.getULEB128(buffer); instanceFieldsSize = BufferUtil.getULEB128(buffer); @@ -119,84 +120,47 @@ // Parse encoded_method structures in direct_methods array. directMethodsIds = new MethodId[directMethodsSize]; directMethodsFlags = new int[directMethodsSize]; - directMethodsCode = new Code[directMethodsSize]; + directMethodsCodeOff = new int[directMethodsSize]; int directMethodsIdx = 0; for (int i = 0; i < directMethodsSize; i++) { directMethodsIdx += BufferUtil.getULEB128(buffer); - MethodId method = dex.getMethodId(directMethodsIdx); - int flags = BufferUtil.getULEB128(buffer); - int codeOff = BufferUtil.getULEB128(buffer); - directMethodsIds[i] = method; - directMethodsFlags[i] = flags; - if (codeOff != 0) - directMethodsCode[i] = Code.parse(dex.getDataBuffer(codeOff), - dex, method, flags); + directMethodsIds[i] = dex.getMethodId(directMethodsIdx); + directMethodsFlags[i] = BufferUtil.getULEB128(buffer);; + directMethodsCodeOff[i] = BufferUtil.getULEB128(buffer);; } // Parse encoded_method structures in virtual_methods array. virtualMethodsIds = new MethodId[virtualMethodsSize]; virtualMethodsFlags = new int[virtualMethodsSize]; - virtualMethodsCode = new Code[virtualMethodsSize]; + virtualMethodsCodeOff = new int[virtualMethodsSize]; int virtualMethodsIdx = 0; for (int i = 0; i < virtualMethodsSize; i++) { virtualMethodsIdx += BufferUtil.getULEB128(buffer); - MethodId method = dex.getMethodId(virtualMethodsIdx); - int flags = BufferUtil.getULEB128(buffer); - int codeOff = BufferUtil.getULEB128(buffer); - virtualMethodsIds[i] = method; - virtualMethodsFlags[i] = flags; - if (codeOff != 0) - virtualMethodsCode[i] = Code.parse(dex.getDataBuffer(codeOff), - dex, method, flags); + virtualMethodsIds[i] = dex.getMethodId(virtualMethodsIdx); + virtualMethodsFlags[i] = BufferUtil.getULEB128(buffer);; + virtualMethodsCodeOff[i] = BufferUtil.getULEB128(buffer);; } - - // Parse encoded_array_item and contained encoded_value structures. - int staticValuesOff = classDef.getStaticValuesOff(); - if (staticValuesOff != 0) { - ByteBuffer buf = dex.getDataBuffer(staticValuesOff); - staticValues = EncodedValue.parseArray(buf, dex); - } else - staticValues = new Object[0]; - } - - public int getStaticFieldsSize() { - return staticFieldsSize; - } - - public int getInstanceFieldsSize() { - return instanceFieldsSize; } - public int getDirectMethodsSize() { - return directMethodsSize; - } - - public int getVirtualMethodsSize() { - return virtualMethodsSize; - } - - public FieldId getStaticFieldsId(int idx) { - return staticFieldsIds[idx]; - } + /** + * Returns the code object for a {@code code_item} at the given data offset. + * The underlying implementation uses a lazy parsing approach. + * + * @param codeOff The given offset into the data area. + * @param method The method identifier of the method the code belongs to. + * @param flags The access flags of the method the code belongs to. + * @return The code object or {@code null} if there is none. + */ + public Code getCode(int codeOff, MethodId method, int flags) { + if (codeOff == 0) + return null; - public int getStaticFieldsFlag(int idx) { - return staticFieldsFlags[idx]; - } - - public FieldId getInstanceFieldsId(int idx) { - return instanceFieldsIds[idx]; - } + // Parse associated code_item structure. + ByteBuffer buf = dex.getDataBuffer(codeOff); + Code code = Code.parse(buf, dex, method, flags); - public int getInstanceFieldsFlag(int idx) { - return instanceFieldsFlags[idx]; - } - - public int getDirectMethodsFlag(int idx) { - return directMethodsFlags[idx]; - } - - public int getVirtualMethodsFlag(int idx) { - return virtualMethodsFlags[idx]; + // Return the non-null object. + return code; } /** @@ -208,6 +172,7 @@ public void accept(DexClassVisitor visitor, int skip) { // Visit all static fields. + Object[] staticValues = classDef.getStaticValues(); for (int i = 0; i < staticFieldsSize; i++) { int access = staticFieldsFlags[i]; FieldId field = staticFieldsIds[i]; @@ -226,8 +191,8 @@ for (int i = 0; i < directMethodsSize; i++) { int access = directMethodsFlags[i]; MethodId method = directMethodsIds[i]; - Code code = ((skip & DexReader.SKIP_CODE) == 0) ? - directMethodsCode[i] : null; + Code code = ((skip & DexReader.SKIP_CODE) == 0) ? getCode( + directMethodsCodeOff[i], method, access) : null; acceptMethod(visitor, access, method, code, skip); } @@ -235,8 +200,8 @@ for (int i = 0; i < virtualMethodsSize; i++) { int access = virtualMethodsFlags[i]; MethodId method = virtualMethodsIds[i]; - Code code = ((skip & DexReader.SKIP_CODE) == 0) ? - virtualMethodsCode[i] : null; + Code code = ((skip & DexReader.SKIP_CODE) == 0) ? getCode( + virtualMethodsCodeOff[i], method, access) : null; acceptMethod(visitor, access, method, code, skip); } } diff -r 3ec98ede8a06 -r 8bb44682be11 src/main/java/org/icedrobot/daneel/dex/ClassDef.java --- a/src/main/java/org/icedrobot/daneel/dex/ClassDef.java Sun Apr 10 23:52:10 2011 +0200 +++ b/src/main/java/org/icedrobot/daneel/dex/ClassDef.java Sun Apr 10 23:54:27 2011 +0200 @@ -57,6 +57,8 @@ return new ClassDef(buffer, dex); } + private final DexFile dex; + private String className; private int accessFlags; @@ -69,18 +71,19 @@ private final AnnotationsDirectory annotations; - private ClassData classData; + private final int classDataOff; private final int staticValuesOff; private ClassDef(ByteBuffer buffer, DexFile dex) { + this.dex = dex; int classIdx = buffer.getInt(); accessFlags = buffer.getInt(); int superclassIdx = buffer.getInt(); int interfacesOff = buffer.getInt(); int sourceFileIdx = buffer.getInt(); int annotationsOff = buffer.getInt(); - int classDataOff = buffer.getInt(); + classDataOff = buffer.getInt(); staticValuesOff = buffer.getInt(); // Resolve string and type indices. @@ -104,12 +107,6 @@ annotations = AnnotationsDirectory.parse(buf, dex); } else annotations = null; - - // Parse associated class_data_item structure. - if (classDataOff != 0) { - ByteBuffer buf = dex.getDataBuffer(classDataOff); - classData = ClassData.parse(buf, dex, this); - } } public String getClassName() { @@ -136,12 +133,65 @@ return annotations; } + /** + * Returns the class data associated with this class definition. The + * underlying implementation uses a lazy parsing approach. + * + * @return The class data object or {@code null} if there is none. + */ public ClassData getClassData() { + if (classDataOff == 0) + return null; + + /* + * XXX Should we at some point decide to use caching for data object, + * that is how it is done. Note that the method doesn't need to be + * synchronized, we are aware of the race-condition but can safely + * ignore it in this case. + + // Retrieve previously parsed and cached object. + ClassData classData = (cachedClassData == null) ? null + : cachedClassData.get(); + + // In case there is no previously cached object (or it was collected), + // we parse the data in a lazy fashion and cache it. + if (classData == null) { + ByteBuffer buf = dex.getDataBuffer(classDataOff); + classData = ClassData.parse(buf, dex, this); + cachedClassData = new SoftReference(classData); + } + + * ... but we do not use caching at the moment. + */ + + // Parse associated class_data_item structure. + ByteBuffer buf = dex.getDataBuffer(classDataOff); + ClassData classData = ClassData.parse(buf, dex, this); + + // Return the non-null object. return classData; } - public int getStaticValuesOff() { - return staticValuesOff; + /** Cache for parsed class data object, allowed to be collected. */ + //private SoftReference cachedClassData; + + /** + * Returns the list of initial values for static fields. The underlying + * implementation uses a lazy parsing approach. + * + * @return The array holding static field values or an empty array in case + * there are none, never returns {@code null}. + */ + public Object[] getStaticValues() { + if (staticValuesOff == 0) + return new Object[0]; + + // Parse encoded_array_item and contained encoded_value structures. + ByteBuffer buf = dex.getDataBuffer(staticValuesOff); + Object[] staticValues = EncodedValue.parseArray(buf, dex); + + // Return the non-null object. + return staticValues; } /** @@ -155,9 +205,9 @@ if (sourceFile != null) visitor.visitSource(sourceFile); if (annotations != null && (skip & DexReader.SKIP_ANNOTATIONS) == 0) - annotations.acceptClassAnnotations(visitor); - if (classData != null) - classData.accept(visitor, skip); + getAnnotations().acceptClassAnnotations(visitor); + if (classDataOff != 0) + getClassData().accept(visitor, skip); visitor.visitEnd(); } } diff -r 3ec98ede8a06 -r 8bb44682be11 src/main/java/org/icedrobot/daneel/dex/Code.java --- a/src/main/java/org/icedrobot/daneel/dex/Code.java Sun Apr 10 23:52:10 2011 +0200 +++ b/src/main/java/org/icedrobot/daneel/dex/Code.java Sun Apr 10 23:54:27 2011 +0200 @@ -83,12 +83,12 @@ private final int triesSize; + private final int debugInfoOff; + private final int insnsSize; private final ShortBuffer insns; - private final DebugInfo debugInfo; - private final List tryCatchInfos; private Code(ByteBuffer buffer, DexFile dex, MethodId method, int flags) { @@ -99,7 +99,7 @@ insSize = buffer.getShort(); outsSize = buffer.getShort(); triesSize = buffer.getShort(); - int debugInfoOff = buffer.getInt(); + debugInfoOff = buffer.getInt(); insnsSize = buffer.getInt(); // Keep a separate buffer for the instructions array. @@ -149,13 +149,6 @@ } } } - - // Parse any associated debug information. - if (debugInfoOff != 0) { - ByteBuffer buf = dex.getDataBuffer(debugInfoOff); - debugInfo = DebugInfo.parse(buf, dex, this); - } else - debugInfo = null; } /** @@ -187,6 +180,24 @@ } /** + * Returns the debug information associated with this code. The underlying + * implementation uses a lazy parsing approach. + * + * @return The debug info object or {@code null} if there is none. + */ + public DebugInfo getDebugInfo() { + if (debugInfoOff == 0) + return null; + + // Parse any associated debug information. + ByteBuffer buf = dex.getDataBuffer(debugInfoOff); + DebugInfo debugInfo = DebugInfo.parse(buf, dex, this); + + // Return the non-null object. + return debugInfo; + } + + /** * Allows the given visitor to visit this code object. * * @param visitor The given DEX method visitor object. @@ -204,7 +215,8 @@ acceptInsns(visitor); // Visit debug information if available and requested. - if (debugInfo != null && (skip & DexReader.SKIP_DEBUGINFO) == 0) { + if (debugInfoOff != 0 && (skip & DexReader.SKIP_DEBUGINFO) == 0) { + DebugInfo debugInfo = getDebugInfo(); for (LocalVariable local : debugInfo.getLocalVariables()) visitor.visitLocalVariable(local.name, local.type, local.startLabel, local.endLabel, local.regNum);