| // Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| package com.android.tools.r8.dex; |
| |
| import static com.android.tools.r8.utils.EncodedValueUtils.parseDouble; |
| import static com.android.tools.r8.utils.EncodedValueUtils.parseFloat; |
| import static com.android.tools.r8.utils.EncodedValueUtils.parseSigned; |
| import static com.android.tools.r8.utils.EncodedValueUtils.parseUnsigned; |
| |
| import com.android.tools.r8.ProgramResource.Kind; |
| import com.android.tools.r8.code.Instruction; |
| import com.android.tools.r8.code.InstructionFactory; |
| import com.android.tools.r8.errors.CompilationError; |
| import com.android.tools.r8.graph.ClassAccessFlags; |
| import com.android.tools.r8.graph.ClassKind; |
| import com.android.tools.r8.graph.Descriptor; |
| import com.android.tools.r8.graph.DexAnnotation; |
| import com.android.tools.r8.graph.DexAnnotationElement; |
| import com.android.tools.r8.graph.DexAnnotationSet; |
| import com.android.tools.r8.graph.DexAnnotationSetRefList; |
| import com.android.tools.r8.graph.DexCallSite; |
| import com.android.tools.r8.graph.DexClass; |
| import com.android.tools.r8.graph.DexCode; |
| import com.android.tools.r8.graph.DexCode.Try; |
| import com.android.tools.r8.graph.DexCode.TryHandler; |
| import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair; |
| import com.android.tools.r8.graph.DexDebugEvent; |
| import com.android.tools.r8.graph.DexDebugInfo; |
| import com.android.tools.r8.graph.DexEncodedAnnotation; |
| import com.android.tools.r8.graph.DexEncodedArray; |
| import com.android.tools.r8.graph.DexEncodedField; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexField; |
| import com.android.tools.r8.graph.DexItem; |
| import com.android.tools.r8.graph.DexItemFactory; |
| import com.android.tools.r8.graph.DexMemberAnnotation; |
| import com.android.tools.r8.graph.DexMemberAnnotation.DexFieldAnnotation; |
| import com.android.tools.r8.graph.DexMemberAnnotation.DexMethodAnnotation; |
| import com.android.tools.r8.graph.DexMemberAnnotation.DexParameterAnnotation; |
| import com.android.tools.r8.graph.DexMethod; |
| import com.android.tools.r8.graph.DexMethodHandle; |
| import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType; |
| import com.android.tools.r8.graph.DexProto; |
| import com.android.tools.r8.graph.DexString; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.DexTypeList; |
| import com.android.tools.r8.graph.DexValue; |
| import com.android.tools.r8.graph.DexValue.DexValueMethodHandle; |
| import com.android.tools.r8.graph.DexValue.DexValueMethodType; |
| import com.android.tools.r8.graph.DexValue.DexValueNull; |
| import com.android.tools.r8.graph.DexValue.DexValueString; |
| import com.android.tools.r8.graph.EnclosingMethodAttribute; |
| import com.android.tools.r8.graph.FieldAccessFlags; |
| import com.android.tools.r8.graph.InnerClassAttribute; |
| import com.android.tools.r8.graph.MethodAccessFlags; |
| import com.android.tools.r8.graph.OffsetToObjectMapping; |
| import com.android.tools.r8.logging.Log; |
| import com.android.tools.r8.origin.Origin; |
| import com.android.tools.r8.origin.PathOrigin; |
| import com.android.tools.r8.utils.Pair; |
| import com.google.common.io.ByteStreams; |
| import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; |
| import it.unimi.dsi.fastutil.ints.Int2ObjectMap; |
| import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.nio.ShortBuffer; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.function.Consumer; |
| import java.util.function.Supplier; |
| |
| public class DexFileReader { |
| |
| final int NO_INDEX = -1; |
| private final Origin origin; |
| private DexFile file; |
| private final Segment[] segments; |
| private int[] stringIDs; |
| private final ClassKind classKind; |
| |
| public static Segment[] parseMapFrom(Path file) throws IOException { |
| return parseMapFrom(Files.newInputStream(file), new PathOrigin(file)); |
| } |
| |
| public static Segment[] parseMapFrom(InputStream stream, Origin origin) throws IOException { |
| return parseMapFrom(new DexFile(origin, ByteStreams.toByteArray(stream))); |
| } |
| |
| private static Segment[] parseMapFrom(DexFile dex) { |
| DexFileReader reader = new DexFileReader(dex, ClassKind.PROGRAM, new DexItemFactory()); |
| return reader.segments; |
| } |
| |
| public void close() { |
| // This close behavior is needed to reduce peak memory usage of D8/R8. |
| indexedItems = null; |
| codes = null; |
| offsetMap = null; |
| file = null; |
| stringIDs = null; |
| } |
| |
| // Mapping from indexes to indexable dex items. |
| private OffsetToObjectMapping indexedItems = new OffsetToObjectMapping(); |
| |
| // Mapping from offset to code item; |
| private Int2ObjectMap<DexCode> codes = new Int2ObjectOpenHashMap<>(); |
| |
| // Mapping from offset to dex item; |
| private Int2ObjectMap<Object> offsetMap = new Int2ObjectOpenHashMap<>(); |
| |
| // Factory to canonicalize certain dexitems. |
| private final DexItemFactory dexItemFactory; |
| |
| public DexFileReader(DexFile file, ClassKind classKind, DexItemFactory dexItemFactory) { |
| assert file.getOrigin() != null; |
| this.origin = file.getOrigin(); |
| this.file = file; |
| this.dexItemFactory = dexItemFactory; |
| file.setByteOrder(); |
| segments = parseMap(); |
| parseStringIDs(); |
| this.classKind = classKind; |
| } |
| |
| public OffsetToObjectMapping getIndexedItemsMap() { |
| return indexedItems; |
| } |
| |
| void addCodeItemsTo() { |
| if (classKind == ClassKind.LIBRARY) { |
| // Ignore contents of library files. |
| return; |
| } |
| Segment segment = lookupSegment(Constants.TYPE_CODE_ITEM); |
| if (segment.length == 0) { |
| return; |
| } |
| file.position(segment.offset); |
| for (int i = 0; i < segment.length; i++) { |
| file.align(4); // code items are 4 byte aligned. |
| int offset = file.position(); |
| DexCode code = parseCodeItem(); |
| codes.put(offset, code); // Update the file local offset to code mapping. |
| } |
| } |
| |
| private DexTypeList parseTypeList() { |
| DexType[] result = new DexType[file.getUint()]; |
| for (int j = 0; j < result.length; j++) { |
| result[j] = indexedItems.getType(file.getUshort()); |
| } |
| return new DexTypeList(result); |
| } |
| |
| private DexTypeList typeListAt(int offset) { |
| if (offset == 0) { |
| return DexTypeList.empty(); |
| } |
| return (DexTypeList) cacheAt(offset, this::parseTypeList); |
| } |
| |
| public DexValue parseEncodedValue() { |
| int header = file.get() & 0xff; |
| int valueArg = header >> 5; |
| int valueType = header & 0x1f; |
| switch (valueType) { |
| case DexValue.VALUE_BYTE: { |
| assert valueArg == 0; |
| byte value = (byte) parseSigned(file, 1); |
| return DexValue.DexValueByte.create(value); |
| } |
| case DexValue.VALUE_SHORT: { |
| int size = valueArg + 1; |
| short value = (short) parseSigned(file, size); |
| return DexValue.DexValueShort.create(value); |
| } |
| case DexValue.VALUE_CHAR: { |
| int size = valueArg + 1; |
| char value = (char) parseUnsigned(file, size); |
| return DexValue.DexValueChar.create(value); |
| } |
| case DexValue.VALUE_INT: { |
| int size = valueArg + 1; |
| int value = (int) parseSigned(file, size); |
| return DexValue.DexValueInt.create(value); |
| } |
| case DexValue.VALUE_LONG: { |
| int size = valueArg + 1; |
| long value = parseSigned(file, size); |
| return DexValue.DexValueLong.create(value); |
| } |
| case DexValue.VALUE_FLOAT: { |
| int size = valueArg + 1; |
| return DexValue.DexValueFloat.create(parseFloat(file, size)); |
| } |
| case DexValue.VALUE_DOUBLE: { |
| int size = valueArg + 1; |
| return DexValue.DexValueDouble.create(parseDouble(file, size)); |
| } |
| case DexValue.VALUE_STRING: { |
| int size = valueArg + 1; |
| int index = (int) parseUnsigned(file, size); |
| DexString value = indexedItems.getString(index); |
| return new DexValue.DexValueString(value); |
| } |
| case DexValue.VALUE_TYPE: { |
| int size = valueArg + 1; |
| DexType value = indexedItems.getType((int) parseUnsigned(file, size)); |
| return new DexValue.DexValueType(value); |
| } |
| case DexValue.VALUE_FIELD: { |
| int size = valueArg + 1; |
| DexField value = indexedItems.getField((int) parseUnsigned(file, size)); |
| return new DexValue.DexValueField(value); |
| } |
| case DexValue.VALUE_METHOD: { |
| int size = valueArg + 1; |
| DexMethod value = indexedItems.getMethod((int) parseUnsigned(file, size)); |
| return new DexValue.DexValueMethod(value); |
| } |
| case DexValue.VALUE_ENUM: { |
| int size = valueArg + 1; |
| DexField value = indexedItems.getField((int) parseUnsigned(file, size)); |
| return new DexValue.DexValueEnum(value); |
| } |
| case DexValue.VALUE_ARRAY: { |
| assert valueArg == 0; |
| return new DexValue.DexValueArray(parseEncodedArrayValues()); |
| } |
| case DexValue.VALUE_ANNOTATION: { |
| assert valueArg == 0; |
| return new DexValue.DexValueAnnotation(parseEncodedAnnotation()); |
| } |
| case DexValue.VALUE_NULL: { |
| assert valueArg == 0; |
| return DexValueNull.NULL; |
| } |
| case DexValue.VALUE_BOOLEAN: { |
| // 0 is false, and 1 is true. |
| return DexValue.DexValueBoolean.create(valueArg != 0); |
| } |
| case DexValue.VALUE_METHOD_TYPE: { |
| int size = valueArg + 1; |
| DexProto value = indexedItems.getProto((int) parseUnsigned(file, size)); |
| return new DexValue.DexValueMethodType(value); |
| } |
| case DexValue.VALUE_METHOD_HANDLE: { |
| int size = valueArg + 1; |
| DexMethodHandle value = indexedItems.getMethodHandle((int) parseUnsigned(file, size)); |
| return new DexValue.DexValueMethodHandle(value); |
| } |
| default: |
| throw new IndexOutOfBoundsException(); |
| } |
| } |
| |
| private DexEncodedAnnotation parseEncodedAnnotation() { |
| int typeIdx = file.getUleb128(); |
| int size = file.getUleb128(); |
| DexAnnotationElement[] elements = new DexAnnotationElement[size]; |
| for (int i = 0; i < size; i++) { |
| int nameIdx = file.getUleb128(); |
| DexValue value = parseEncodedValue(); |
| elements[i] = new DexAnnotationElement(indexedItems.getString(nameIdx), value); |
| } |
| return new DexEncodedAnnotation(indexedItems.getType(typeIdx), elements); |
| } |
| |
| private DexValue[] parseEncodedArrayValues() { |
| int size = file.getUleb128(); |
| DexValue[] values = new DexValue[size]; |
| for (int i = 0; i < size; i++) { |
| values[i] = parseEncodedValue(); |
| } |
| return values; |
| } |
| |
| |
| private DexEncodedArray parseEncodedArray() { |
| return new DexEncodedArray(parseEncodedArrayValues()); |
| } |
| |
| private DexEncodedArray encodedArrayAt(int offset) { |
| return (DexEncodedArray) cacheAt(offset, this::parseEncodedArray); |
| } |
| |
| private DexFieldAnnotation[] parseFieldAnnotations(int size) { |
| if (size == 0) { |
| return null; |
| } |
| int[] fieldIndices = new int[size]; |
| int[] annotationOffsets = new int[size]; |
| for (int i = 0; i < size; i++) { |
| fieldIndices[i] = file.getUint(); |
| annotationOffsets[i] = file.getUint(); |
| } |
| int saved = file.position(); |
| DexFieldAnnotation[] result = new DexFieldAnnotation[size]; |
| for (int i = 0; i < size; i++) { |
| DexField field = indexedItems.getField(fieldIndices[i]); |
| DexAnnotationSet annotation = annotationSetAt(annotationOffsets[i]); |
| result[i] = new DexFieldAnnotation(field, annotation); |
| } |
| file.position(saved); |
| return result; |
| } |
| |
| private DexMethodAnnotation[] parseMethodAnnotations(int size) { |
| if (size == 0) { |
| return null; |
| } |
| int[] methodIndices = new int[size]; |
| int[] annotationOffsets = new int[size]; |
| for (int i = 0; i < size; i++) { |
| methodIndices[i] = file.getUint(); |
| annotationOffsets[i] = file.getUint(); |
| } |
| int saved = file.position(); |
| DexMethodAnnotation[] result = new DexMethodAnnotation[size]; |
| for (int i = 0; i < size; i++) { |
| DexMethod method = indexedItems.getMethod(methodIndices[i]); |
| DexAnnotationSet annotation = annotationSetAt(annotationOffsets[i]); |
| result[i] = new DexMethodAnnotation(method, annotation); |
| } |
| file.position(saved); |
| return result; |
| } |
| |
| private DexAnnotationSetRefList annotationSetRefListAt(int offset) { |
| return (DexAnnotationSetRefList) cacheAt(offset, this::parseAnnotationSetRefList); |
| } |
| |
| private DexAnnotationSetRefList parseAnnotationSetRefList() { |
| int size = file.getUint(); |
| int[] annotationOffsets = new int[size]; |
| for (int i = 0; i < size; i++) { |
| annotationOffsets[i] = file.getUint(); |
| } |
| DexAnnotationSet[] values = new DexAnnotationSet[size]; |
| for (int i = 0; i < size; i++) { |
| values[i] = annotationSetAt(annotationOffsets[i]); |
| } |
| return new DexAnnotationSetRefList(values); |
| } |
| |
| private DexParameterAnnotation[] parseParameterAnnotations(int size) { |
| if (size == 0) { |
| return null; |
| } |
| int[] methodIndices = new int[size]; |
| int[] annotationOffsets = new int[size]; |
| for (int i = 0; i < size; i++) { |
| methodIndices[i] = file.getUint(); |
| annotationOffsets[i] = file.getUint(); |
| } |
| int saved = file.position(); |
| DexParameterAnnotation[] result = new DexParameterAnnotation[size]; |
| for (int i = 0; i < size; i++) { |
| DexMethod method = indexedItems.getMethod(methodIndices[i]); |
| result[i] = new DexParameterAnnotation( |
| method, |
| annotationSetRefListAt(annotationOffsets[i])); |
| } |
| file.position(saved); |
| return result; |
| } |
| |
| private <T> Object cacheAt(int offset, Supplier<T> function, Supplier<T> defaultValue) { |
| if (offset == 0) { |
| return defaultValue.get(); |
| } |
| return cacheAt(offset, function); |
| } |
| |
| private <T> Object cacheAt(int offset, Supplier<T> function) { |
| if (offset == 0) { |
| return null; // return null for offset zero. |
| } |
| Object result = offsetMap.get(offset); |
| if (result != null) { |
| return result; // return the cached result. |
| } |
| // Cache is empty so parse the structure. |
| file.position(offset); |
| result = function.get(); |
| // Update the map. |
| offsetMap.put(offset, result); |
| assert offsetMap.get(offset) == result; |
| return result; |
| } |
| |
| private DexAnnotation parseAnnotation() { |
| if (Log.ENABLED) { |
| Log.verbose(getClass(), "Reading Annotation @ 0x%08x.", file.position()); |
| } |
| int visibility = file.get(); |
| return new DexAnnotation(visibility, parseEncodedAnnotation()); |
| } |
| |
| private DexAnnotation annotationAt(int offset) { |
| return (DexAnnotation) cacheAt(offset, this::parseAnnotation); |
| } |
| |
| private DexAnnotationSet parseAnnotationSet() { |
| if (Log.ENABLED) { |
| Log.verbose(getClass(), "Reading AnnotationSet @ 0x%08x.", file.position()); |
| } |
| int size = file.getUint(); |
| int[] annotationOffsets = new int[size]; |
| for (int i = 0; i < size; i++) { |
| annotationOffsets[i] = file.getUint(); |
| } |
| DexAnnotation[] result = new DexAnnotation[size]; |
| for (int i = 0; i < size; i++) { |
| result[i] = annotationAt(annotationOffsets[i]); |
| } |
| return new DexAnnotationSet(result); |
| } |
| |
| private DexAnnotationSet annotationSetAt(int offset) { |
| return (DexAnnotationSet) cacheAt(offset, this::parseAnnotationSet, DexAnnotationSet::empty); |
| } |
| |
| private AnnotationsDirectory annotationsDirectoryAt(int offset) { |
| return (AnnotationsDirectory) cacheAt(offset, this::parseAnnotationsDirectory, |
| AnnotationsDirectory::empty); |
| } |
| |
| private AnnotationsDirectory parseAnnotationsDirectory() { |
| int classAnnotationsOff = file.getUint(); |
| int fieldsSize = file.getUint(); |
| int methodsSize = file.getUint(); |
| int parametersSize = file.getUint(); |
| final DexFieldAnnotation[] fields = parseFieldAnnotations(fieldsSize); |
| final DexMethodAnnotation[] methods = parseMethodAnnotations(methodsSize); |
| final DexParameterAnnotation[] parameters = parseParameterAnnotations(parametersSize); |
| return new AnnotationsDirectory( |
| annotationSetAt(classAnnotationsOff), |
| fields, |
| methods, |
| parameters); |
| } |
| |
| private DexDebugInfo debugInfoAt(int offset) { |
| return (DexDebugInfo) cacheAt(offset, this::parseDebugInfo); |
| } |
| |
| private DexDebugInfo parseDebugInfo() { |
| int start = file.getUleb128(); |
| int parametersSize = file.getUleb128(); |
| DexString[] parameters = new DexString[parametersSize]; |
| for (int i = 0; i < parametersSize; i++) { |
| int index = file.getUleb128p1(); |
| if (index != NO_INDEX) { |
| parameters[i] = indexedItems.getString(index); |
| } |
| } |
| List<DexDebugEvent> events = new ArrayList<>(); |
| for (int head = file.getUbyte(); head != Constants.DBG_END_SEQUENCE; head = file.getUbyte()) { |
| switch (head) { |
| case Constants.DBG_ADVANCE_PC: |
| events.add(dexItemFactory.createAdvancePC(file.getUleb128())); |
| break; |
| case Constants.DBG_ADVANCE_LINE: |
| events.add(dexItemFactory.createAdvanceLine(file.getSleb128())); |
| break; |
| case Constants.DBG_START_LOCAL: { |
| int registerNum = file.getUleb128(); |
| int nameIdx = file.getUleb128p1(); |
| int typeIdx = file.getUleb128p1(); |
| events.add(new DexDebugEvent.StartLocal( |
| registerNum, |
| nameIdx == NO_INDEX ? null : indexedItems.getString(nameIdx), |
| typeIdx == NO_INDEX ? null : indexedItems.getType(typeIdx), |
| null)); |
| break; |
| } |
| case Constants.DBG_START_LOCAL_EXTENDED: { |
| int registerNum = file.getUleb128(); |
| int nameIdx = file.getUleb128p1(); |
| int typeIdx = file.getUleb128p1(); |
| int sigIdx = file.getUleb128p1(); |
| events.add(new DexDebugEvent.StartLocal( |
| registerNum, |
| nameIdx == NO_INDEX ? null : indexedItems.getString(nameIdx), |
| typeIdx == NO_INDEX ? null : indexedItems.getType(typeIdx), |
| sigIdx == NO_INDEX ? null : indexedItems.getString(sigIdx))); |
| break; |
| } |
| case Constants.DBG_END_LOCAL: { |
| events.add(dexItemFactory.createEndLocal(file.getUleb128())); |
| break; |
| } |
| case Constants.DBG_RESTART_LOCAL: { |
| events.add(dexItemFactory.createRestartLocal(file.getUleb128())); |
| break; |
| } |
| case Constants.DBG_SET_PROLOGUE_END: { |
| events.add(dexItemFactory.createSetPrologueEnd()); |
| break; |
| } |
| case Constants.DBG_SET_EPILOGUE_BEGIN: { |
| events.add(dexItemFactory.createSetEpilogueBegin()); |
| break; |
| } |
| case Constants.DBG_SET_FILE: { |
| int nameIdx = file.getUleb128p1(); |
| DexString sourceFile = nameIdx == NO_INDEX ? null : indexedItems.getString(nameIdx); |
| events.add(dexItemFactory.createSetFile(sourceFile)); |
| break; |
| } |
| default: { |
| assert head >= 0x0a && head <= 0xff; |
| events.add(dexItemFactory.createDefault(head)); |
| } |
| } |
| } |
| return new DexDebugInfo(start, parameters, events.toArray(new DexDebugEvent[events.size()])); |
| } |
| |
| private static class MemberAnnotationIterator<S extends Descriptor<?, S>, T extends DexItem> { |
| |
| private int index = 0; |
| private final DexMemberAnnotation<S, T>[] annotations; |
| private final Supplier<T> emptyValue; |
| |
| private MemberAnnotationIterator(DexMemberAnnotation<S, T>[] annotations, |
| Supplier<T> emptyValue) { |
| this.annotations = annotations; |
| this.emptyValue = emptyValue; |
| } |
| |
| // Get the annotation set for an item. This method assumes that it is always called with |
| // an item that is higher in the sorting order than the last item. |
| T getNextFor(S item) { |
| // TODO(ager): We could use the indices from the file to make this search faster using |
| // compareTo instead of slowCompareTo. That would require us to assign indices during |
| // reading. Those indices should be cleared after reading to make sure that we resort |
| // everything correctly at the end. |
| while (index < annotations.length && annotations[index].item.slowCompareTo(item) < 0) { |
| index++; |
| } |
| if (index >= annotations.length || !annotations[index].item.equals(item)) { |
| return emptyValue.get(); |
| } |
| return annotations[index].annotations; |
| } |
| } |
| |
| private DexEncodedField[] readFields(int size, DexFieldAnnotation[] annotations, |
| DexValue[] staticValues) { |
| DexEncodedField[] fields = new DexEncodedField[size]; |
| int fieldIndex = 0; |
| MemberAnnotationIterator<DexField, DexAnnotationSet> annotationIterator = |
| new MemberAnnotationIterator<>(annotations, DexAnnotationSet::empty); |
| for (int i = 0; i < size; i++) { |
| fieldIndex += file.getUleb128(); |
| DexField field = indexedItems.getField(fieldIndex); |
| FieldAccessFlags accessFlags = FieldAccessFlags.fromDexAccessFlags(file.getUleb128()); |
| DexAnnotationSet fieldAnnotations = annotationIterator.getNextFor(field); |
| DexValue staticValue = null; |
| if (accessFlags.isStatic()) { |
| if (staticValues != null && i < staticValues.length) { |
| staticValue = staticValues[i]; |
| } else { |
| staticValue = DexValue.defaultForType(field.type, dexItemFactory); |
| } |
| } |
| fields[i] = new DexEncodedField(field, accessFlags, fieldAnnotations, staticValue); |
| } |
| return fields; |
| } |
| |
| private DexEncodedMethod[] readMethods(int size, DexMethodAnnotation[] annotations, |
| DexParameterAnnotation[] parameters, boolean skipCodes) { |
| DexEncodedMethod[] methods = new DexEncodedMethod[size]; |
| int methodIndex = 0; |
| MemberAnnotationIterator<DexMethod, DexAnnotationSet> annotationIterator = |
| new MemberAnnotationIterator<>(annotations, DexAnnotationSet::empty); |
| MemberAnnotationIterator<DexMethod, DexAnnotationSetRefList> parameterAnnotationsIterator = |
| new MemberAnnotationIterator<>(parameters, DexAnnotationSetRefList::empty); |
| for (int i = 0; i < size; i++) { |
| methodIndex += file.getUleb128(); |
| MethodAccessFlags accessFlags = MethodAccessFlags.fromDexAccessFlags(file.getUleb128()); |
| int codeOff = file.getUleb128(); |
| DexCode code = null; |
| if (!skipCodes) { |
| assert codeOff == 0 || codes.get(codeOff) != null; |
| code = codes.get(codeOff); |
| } |
| DexMethod method = indexedItems.getMethod(methodIndex); |
| methods[i] = new DexEncodedMethod(method, accessFlags, annotationIterator.getNextFor(method), |
| parameterAnnotationsIterator.getNextFor(method), code); |
| } |
| return methods; |
| } |
| |
| void addClassDefsTo(Consumer<DexClass> classCollection) { |
| final Segment segment = lookupSegment(Constants.TYPE_CLASS_DEF_ITEM); |
| final int length = segment.length; |
| indexedItems.initializeClasses(length); |
| if (length == 0) { |
| return; |
| } |
| file.position(segment.offset); |
| |
| int[] classIndices = new int[length]; |
| int[] accessFlags = new int[length]; |
| int[] superclassIndices = new int[length]; |
| int[] interfacesOffsets = new int[length]; |
| int[] sourceFileIndices = new int[length]; |
| int[] annotationsOffsets = new int[length]; |
| int[] classDataOffsets = new int[length]; |
| int[] staticValuesOffsets = new int[length]; |
| |
| for (int i = 0; i < length; i++) { |
| if (Log.ENABLED) { |
| Log.verbose(getClass(), "Reading ClassDef @ 0x%08x.", file.position()); |
| } |
| classIndices[i] = file.getUint(); |
| accessFlags[i] = file.getUint(); |
| superclassIndices[i] = file.getInt(); |
| interfacesOffsets[i] = file.getUint(); |
| sourceFileIndices[i] = file.getInt(); |
| annotationsOffsets[i] = file.getUint(); |
| classDataOffsets[i] = file.getUint(); |
| staticValuesOffsets[i] = file.getUint(); |
| } |
| |
| for (int i = 0; i < length; i++) { |
| int superclassIdx = superclassIndices[i]; |
| DexType superclass = superclassIdx == NO_INDEX ? null : indexedItems.getType(superclassIdx); |
| int srcIdx = sourceFileIndices[i]; |
| DexString source = srcIdx == NO_INDEX ? null : indexedItems.getString(srcIdx); |
| DexType type = indexedItems.getType(classIndices[i]); |
| ClassAccessFlags flags = ClassAccessFlags.fromDexAccessFlags(accessFlags[i]); |
| // Check if constraints from |
| // https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1 are met. |
| if (!flags.areValid(Constants.CORRESPONDING_CLASS_FILE_VERSION)) { |
| throw new CompilationError("Class " + type.toSourceString() |
| + " has illegal access flags. Found: " + flags, origin); |
| } |
| DexEncodedField[] staticFields = DexEncodedField.EMPTY_ARRAY; |
| DexEncodedField[] instanceFields = DexEncodedField.EMPTY_ARRAY; |
| DexEncodedMethod[] directMethods = DexEncodedMethod.EMPTY_ARRAY; |
| DexEncodedMethod[] virtualMethods = DexEncodedMethod.EMPTY_ARRAY; |
| AnnotationsDirectory annotationsDirectory = annotationsDirectoryAt(annotationsOffsets[i]); |
| if (classDataOffsets[i] != 0) { |
| DexEncodedArray staticValues = encodedArrayAt(staticValuesOffsets[i]); |
| |
| file.position(classDataOffsets[i]); |
| int staticFieldsSize = file.getUleb128(); |
| int instanceFieldsSize = file.getUleb128(); |
| int directMethodsSize = file.getUleb128(); |
| int virtualMethodsSize = file.getUleb128(); |
| |
| staticFields = readFields(staticFieldsSize, annotationsDirectory.fields, |
| staticValues != null ? staticValues.values : null); |
| instanceFields = readFields(instanceFieldsSize, annotationsDirectory.fields, null); |
| directMethods = |
| readMethods( |
| directMethodsSize, |
| annotationsDirectory.methods, |
| annotationsDirectory.parameters, |
| classKind != ClassKind.PROGRAM); |
| virtualMethods = |
| readMethods( |
| virtualMethodsSize, |
| annotationsDirectory.methods, |
| annotationsDirectory.parameters, |
| classKind != ClassKind.PROGRAM); |
| } |
| |
| AttributesAndAnnotations attrs = |
| new AttributesAndAnnotations(type, annotationsDirectory.clazz, dexItemFactory); |
| |
| DexClass clazz = classKind.create( |
| type, |
| Kind.DEX, |
| origin, |
| flags, |
| superclass, |
| typeListAt(interfacesOffsets[i]), |
| source, |
| attrs.getEnclosingMethodAttribute(), |
| attrs.getInnerClasses(), |
| attrs.getAnnotations(), |
| staticFields, |
| instanceFields, |
| directMethods, |
| virtualMethods); |
| classCollection.accept(clazz); // Update the application object. |
| } |
| } |
| |
| private void parseStringIDs() { |
| Segment segment = lookupSegment(Constants.TYPE_STRING_ID_ITEM); |
| stringIDs = new int[segment.length]; |
| if (segment.length == 0) { |
| return; |
| } |
| file.position(segment.offset); |
| for (int i = 0; i < segment.length; i++) { |
| stringIDs[i] = file.getUint(); |
| } |
| } |
| |
| private Segment lookupSegment(int type) { |
| for (Segment s : segments) { |
| if (s.type == type) { |
| return s; |
| } |
| } |
| // If the segment doesn't exist, return an empty segment of this type. |
| return new Segment(type, 0, 0, 0); |
| } |
| |
| private Segment[] parseMap() { |
| // Read the segments information from the MAP. |
| int mapOffset = file.getUint(Constants.MAP_OFF_OFFSET); |
| file.position(mapOffset); |
| int mapSize = file.getUint(); |
| final Segment[] result = new Segment[mapSize]; |
| for (int i = 0; i < mapSize; i++) { |
| int type = file.getUshort(); |
| int unused = file.getUshort(); |
| int size = file.getUint(); |
| int offset = file.getUint(); |
| result[i] = new Segment(type, unused, size, offset); |
| } |
| if (Log.ENABLED) { |
| for (int i = 0; i < result.length; i++) { |
| Segment segment = result[i]; |
| int nextOffset = i < result.length - 1 ? result[i + 1].offset : segment.offset; |
| Log.debug(this.getClass(), "Read segment 0x%04x @ 0x%08x #items %08d size 0x%08x.", |
| segment.type, segment.offset, segment.length, nextOffset - segment.offset); |
| } |
| } |
| for (int i = 0; i < mapSize - 1; i++) { |
| result[i].setEnd(result[i + 1].offset); |
| } |
| result[mapSize - 1].setEnd(file.end()); |
| return result; |
| } |
| |
| private DexCode parseCodeItem() { |
| int registerSize = file.getUshort(); |
| int insSize = file.getUshort(); |
| int outsSize = file.getUshort(); |
| int triesSize = file.getUshort(); |
| int debugInfoOff = file.getUint(); |
| int insnsSize = file.getUint(); |
| short[] code = new short[insnsSize]; |
| Try[] tries = new Try[triesSize]; |
| DexCode.TryHandler[] handlers = null; |
| |
| if (insnsSize != 0) { |
| for (int i = 0; i < insnsSize; i++) { |
| code[i] = file.getShort(); |
| } |
| if (insnsSize % 2 != 0) { |
| file.getUshort(); // Skip padding ushort |
| } |
| if (triesSize > 0) { |
| Int2IntArrayMap handlerMap = new Int2IntArrayMap(); |
| // tries: try_item[tries_size]. |
| for (int i = 0; i < triesSize; i++) { |
| int startAddr = file.getUint(); |
| int insnCount = file.getUshort(); |
| int handlerOff = file.getUshort(); |
| tries[i] = new Try(startAddr, insnCount, handlerOff); |
| } |
| // handlers: encoded_catch_handler_list |
| int encodedCatchHandlerListPosition = file.position(); |
| // - size: uleb128 |
| int size = file.getUleb128(); |
| handlers = new TryHandler[size]; |
| // - list: encoded_catch_handler[handlers_size] |
| for (int i = 0; i < size; i++) { |
| // encoded_catch_handler |
| int encodedCatchHandlerOffset = file.position() - encodedCatchHandlerListPosition; |
| handlerMap.put(encodedCatchHandlerOffset, i); |
| // - size: sleb128 |
| int hsize = file.getSleb128(); |
| int realHsize = Math.abs(hsize); |
| // - handlers encoded_type_addr_pair[abs(size)] |
| TryHandler.TypeAddrPair pairs[] = new TryHandler.TypeAddrPair[realHsize]; |
| for (int j = 0; j < realHsize; j++) { |
| int typeIdx = file.getUleb128(); |
| int addr = file.getUleb128(); |
| pairs[j] = new TypeAddrPair(indexedItems.getType(typeIdx), addr); |
| } |
| int catchAllAddr = -1; |
| if (hsize <= 0) { |
| catchAllAddr = file.getUleb128(); |
| } |
| handlers[i] = new TryHandler(pairs, catchAllAddr); |
| } |
| // Convert the handler offsets inside the Try objects to indexes. |
| for (Try t : tries) { |
| t.setHandlerIndex(handlerMap); |
| } |
| } |
| } |
| // Store and restore offset information around reading debug info. |
| int saved = file.position(); |
| DexDebugInfo debugInfo = debugInfoAt(debugInfoOff); |
| file.position(saved); |
| InstructionFactory factory = new InstructionFactory(); |
| Instruction[] instructions = |
| factory.readSequenceFrom(ShortBuffer.wrap(code), 0, code.length, indexedItems); |
| return new DexCode( |
| registerSize, |
| insSize, |
| outsSize, |
| instructions, |
| tries, |
| handlers, |
| debugInfo, |
| factory.getHighestSortingString()); |
| } |
| |
| static void populateIndexTables(DexFileReader fileReader) { |
| // Populate structures that are already sorted upon read. |
| DexFileReader.populateStrings(fileReader); // Depends on nothing. |
| DexFileReader.populateTypes(fileReader); // Depends on Strings. |
| DexFileReader.populateFields(fileReader); // Depends on Types, and Strings. |
| DexFileReader.populateProtos(fileReader); // Depends on Types and Strings. |
| DexFileReader.populateMethods(fileReader); // Depends on Protos, Types, and Strings. |
| DexFileReader.populateMethodHandles(fileReader); // Depends on Methods and Fields |
| DexFileReader.populateCallSites(fileReader); // Depends on MethodHandles |
| } |
| |
| private static void populateStrings(DexFileReader reader) { |
| reader.indexedItems.initializeStrings(reader.stringIDs.length); |
| for (int i = 0; i < reader.stringIDs.length; i++) { |
| reader.indexedItems.setString(i, reader.stringAt(i)); |
| } |
| } |
| |
| private static void populateMethodHandles(DexFileReader reader) { |
| Segment segment = reader.lookupSegment(Constants.TYPE_METHOD_HANDLE_ITEM); |
| reader.indexedItems.initializeMethodHandles(segment.length); |
| for (int i = 0; i < segment.length; i++) { |
| reader.indexedItems.setMethodHandle(i, reader.methodHandleAt(i)); |
| } |
| } |
| |
| private static void populateCallSites(DexFileReader reader) { |
| Segment segment = reader.lookupSegment(Constants.TYPE_CALL_SITE_ID_ITEM); |
| reader.indexedItems.initializeCallSites(segment.length); |
| for (int i = 0; i < segment.length; i++) { |
| reader.indexedItems.setCallSites(i, reader.callSiteAt(i)); |
| } |
| } |
| |
| private static void populateTypes(DexFileReader reader) { |
| Segment segment = reader.lookupSegment(Constants.TYPE_TYPE_ID_ITEM); |
| reader.indexedItems.initializeTypes(segment.length); |
| for (int i = 0; i < segment.length; i++) { |
| reader.indexedItems.setType(i, reader.typeAt(i)); |
| } |
| } |
| |
| private static void populateFields(DexFileReader reader) { |
| Segment segment = reader.lookupSegment(Constants.TYPE_FIELD_ID_ITEM); |
| reader.indexedItems.initializeFields(segment.length); |
| for (int i = 0; i < segment.length; i++) { |
| reader.indexedItems.setField(i, reader.fieldAt(i)); |
| } |
| } |
| |
| private static void populateProtos(DexFileReader reader) { |
| Segment segment = reader.lookupSegment(Constants.TYPE_PROTO_ID_ITEM); |
| reader.indexedItems.initializeProtos(segment.length); |
| for (int i = 0; i < segment.length; i++) { |
| reader.indexedItems.setProto(i, reader.protoAt(i)); |
| } |
| } |
| |
| private static void populateMethods(DexFileReader reader) { |
| Segment segment = reader.lookupSegment(Constants.TYPE_METHOD_ID_ITEM); |
| reader.indexedItems.initializeMethods(segment.length); |
| for (int i = 0; i < segment.length; i++) { |
| reader.indexedItems.setMethod(i, reader.methodAt(i)); |
| } |
| } |
| |
| private DexString stringAt(int index) { |
| final int offset = stringIDs[index]; |
| file.position(offset); |
| int size = file.getUleb128(); |
| ByteArrayOutputStream os = new ByteArrayOutputStream(); |
| byte read; |
| do { |
| read = file.get(); |
| os.write(read); |
| } while (read != 0); |
| return dexItemFactory.createString(size, os.toByteArray()); |
| } |
| |
| private DexType typeAt(int index) { |
| Segment segment = lookupSegment(Constants.TYPE_TYPE_ID_ITEM); |
| if (index >= segment.length) { |
| return null; |
| } |
| int offset = segment.offset + (Constants.TYPE_TYPE_ID_ITEM_SIZE * index); |
| int stringIndex = file.getUint(offset); |
| return dexItemFactory.createType(indexedItems.getString(stringIndex)); |
| } |
| |
| private DexField fieldAt(int index) { |
| Segment segment = lookupSegment(Constants.TYPE_FIELD_ID_ITEM); |
| if (index >= segment.length) { |
| return null; |
| } |
| int offset = segment.offset + (Constants.TYPE_FIELD_ID_ITEM_SIZE * index); |
| file.position(offset); |
| int classIndex = file.getUshort(); |
| int typeIndex = file.getUshort(); |
| int nameIndex = file.getUint(); |
| DexType clazz = indexedItems.getType(classIndex); |
| DexType type = indexedItems.getType(typeIndex); |
| DexString name = indexedItems.getString(nameIndex); |
| return dexItemFactory.createField(clazz, type, name); |
| } |
| |
| private DexMethodHandle methodHandleAt(int index) { |
| Segment segment = lookupSegment(Constants.TYPE_METHOD_HANDLE_ITEM); |
| if (index >= segment.length) { |
| return null; |
| } |
| int offset = segment.offset + (Constants.TYPE_METHOD_HANDLE_ITEM_SIZE * index); |
| file.position(offset); |
| MethodHandleType type = MethodHandleType.getKind(file.getUshort()); |
| file.getUshort(); // unused |
| int indexFieldOrMethod = file.getUshort(); |
| Descriptor<? extends DexItem, ? extends Descriptor<?, ?>> fieldOrMethod; |
| switch (type) { |
| case INSTANCE_GET: |
| case INSTANCE_PUT: |
| case STATIC_GET: |
| case STATIC_PUT: { |
| fieldOrMethod = indexedItems.getField(indexFieldOrMethod); |
| break; |
| } |
| case INVOKE_CONSTRUCTOR: |
| case INVOKE_DIRECT: |
| case INVOKE_INTERFACE: |
| case INVOKE_INSTANCE: |
| case INVOKE_STATIC: { |
| fieldOrMethod = indexedItems.getMethod(indexFieldOrMethod); |
| break; |
| } |
| default: |
| throw new AssertionError("Method handle type unsupported in a dex file."); |
| } |
| file.getUshort(); // unused |
| |
| return dexItemFactory.createMethodHandle(type, fieldOrMethod); |
| } |
| |
| private DexCallSite callSiteAt(int index) { |
| Segment segment = lookupSegment(Constants.TYPE_CALL_SITE_ID_ITEM); |
| if (index >= segment.length) { |
| return null; |
| } |
| int callSiteOffset = |
| file.getUint(segment.offset + (Constants.TYPE_CALL_SITE_ID_ITEM_SIZE * index)); |
| DexEncodedArray callSiteEncodedArray = encodedArrayAt(callSiteOffset); |
| DexValue[] values = callSiteEncodedArray.values; |
| assert values[0] instanceof DexValueMethodHandle; |
| assert values[1] instanceof DexValueString; |
| assert values[2] instanceof DexValueMethodType; |
| |
| return dexItemFactory.createCallSite( |
| ((DexValueString) values[1]).value, |
| ((DexValueMethodType) values[2]).value, |
| ((DexValueMethodHandle) values[0]).value, |
| // 3 means first extra args |
| Arrays.asList(Arrays.copyOfRange(values, 3, values.length))); |
| } |
| |
| private DexProto protoAt(int index) { |
| Segment segment = lookupSegment(Constants.TYPE_PROTO_ID_ITEM); |
| if (index >= segment.length) { |
| return null; |
| } |
| int offset = segment.offset + (Constants.TYPE_PROTO_ID_ITEM_SIZE * index); |
| file.position(offset); |
| int shortyIndex = file.getUint(); |
| int returnTypeIndex = file.getUint(); |
| int parametersOffsetIndex = file.getUint(); |
| DexString shorty = indexedItems.getString(shortyIndex); |
| DexType returnType = indexedItems.getType(returnTypeIndex); |
| DexTypeList parameters = typeListAt(parametersOffsetIndex); |
| return dexItemFactory.createProto(returnType, shorty, parameters); |
| } |
| |
| private DexMethod methodAt(int index) { |
| Segment segment = lookupSegment(Constants.TYPE_METHOD_ID_ITEM); |
| if (index >= segment.length) { |
| return null; |
| } |
| int offset = segment.offset + (Constants.TYPE_METHOD_ID_ITEM_SIZE * index); |
| file.position(offset); |
| int classIndex = file.getUshort(); |
| int protoIndex = file.getUshort(); |
| int nameIndex = file.getUint(); |
| return dexItemFactory.createMethod( |
| indexedItems.getType(classIndex), |
| indexedItems.getProto(protoIndex), |
| indexedItems.getString(nameIndex)); |
| } |
| |
| private static class AnnotationsDirectory { |
| |
| private static final DexParameterAnnotation[] NO_PARAMETER_ANNOTATIONS = |
| new DexParameterAnnotation[0]; |
| |
| private static final DexFieldAnnotation[] NO_FIELD_ANNOTATIONS = |
| new DexFieldAnnotation[0]; |
| |
| private static final DexMethodAnnotation[] NO_METHOD_ANNOTATIONS = |
| new DexMethodAnnotation[0]; |
| |
| private static final AnnotationsDirectory THE_EMPTY_ANNOTATIONS_DIRECTORY = |
| new AnnotationsDirectory(DexAnnotationSet.empty(), |
| NO_FIELD_ANNOTATIONS, new DexMethodAnnotation[0], |
| NO_PARAMETER_ANNOTATIONS); |
| |
| public final DexAnnotationSet clazz; |
| public final DexFieldAnnotation[] fields; |
| public final DexMethodAnnotation[] methods; |
| public final DexParameterAnnotation[] parameters; |
| |
| AnnotationsDirectory(DexAnnotationSet clazz, |
| DexFieldAnnotation[] fields, |
| DexMethodAnnotation[] methods, |
| DexParameterAnnotation[] parameters) { |
| this.clazz = clazz == null ? DexAnnotationSet.empty() : clazz; |
| this.fields = fields == null ? NO_FIELD_ANNOTATIONS : fields; |
| this.methods = methods == null ? NO_METHOD_ANNOTATIONS : methods; |
| this.parameters = parameters == null ? NO_PARAMETER_ANNOTATIONS : parameters; |
| } |
| |
| public static AnnotationsDirectory empty() { |
| return THE_EMPTY_ANNOTATIONS_DIRECTORY; |
| } |
| } |
| |
| private static class AttributesAndAnnotations { |
| |
| private final DexAnnotationSet originalAnnotations; |
| private EnclosingMethodAttribute enclosingMethodAttribute = null; |
| private List<InnerClassAttribute> innerClasses = null; |
| private List<DexAnnotation> lazyAnnotations = null; |
| |
| public DexAnnotationSet getAnnotations() { |
| if (lazyAnnotations != null) { |
| int size = lazyAnnotations.size(); |
| return size == 0 |
| ? DexAnnotationSet.empty() |
| : new DexAnnotationSet(lazyAnnotations.toArray(new DexAnnotation[size])); |
| } |
| return originalAnnotations; |
| } |
| |
| public List<InnerClassAttribute> getInnerClasses() { |
| return innerClasses == null ? Collections.emptyList() : innerClasses; |
| } |
| |
| public EnclosingMethodAttribute getEnclosingMethodAttribute() { |
| return enclosingMethodAttribute; |
| } |
| |
| public AttributesAndAnnotations( |
| DexType type, DexAnnotationSet annotations, DexItemFactory factory) { |
| this.originalAnnotations = annotations; |
| DexType enclosingClass = null; |
| DexMethod enclosingMethod = null; |
| List<DexType> memberClasses = null; |
| |
| for (int i = 0; i < annotations.annotations.length; i++) { |
| DexAnnotation annotation = annotations.annotations[i]; |
| if (DexAnnotation.isEnclosingClassAnnotation(annotation, factory)) { |
| ensureAnnotations(i); |
| enclosingClass = DexAnnotation.getEnclosingClassFromAnnotation(annotation, factory); |
| } else if (DexAnnotation.isEnclosingMethodAnnotation(annotation, factory)) { |
| ensureAnnotations(i); |
| enclosingMethod = DexAnnotation.getEnclosingMethodFromAnnotation(annotation, factory); |
| } else if (DexAnnotation.isInnerClassAnnotation(annotation, factory)) { |
| ensureAnnotations(i); |
| if (innerClasses == null) { |
| innerClasses = new ArrayList<>(annotations.annotations.length - i); |
| } |
| Pair<DexString, Integer> entry = |
| DexAnnotation.getInnerClassFromAnnotation(annotation, factory); |
| innerClasses.add( |
| new InnerClassAttribute(entry.getSecond(), type, null, entry.getFirst())); |
| } else if (DexAnnotation.isMemberClassesAnnotation(annotation, factory)) { |
| ensureAnnotations(i); |
| List<DexType> members = DexAnnotation.getMemberClassesFromAnnotation(annotation, factory); |
| if (memberClasses == null) { |
| memberClasses = members; |
| } else { |
| memberClasses.addAll(members); |
| } |
| } else { |
| copyAnnotation(annotation); |
| } |
| } |
| |
| if (enclosingClass != null || enclosingMethod != null) { |
| assert enclosingClass == null || enclosingMethod == null; |
| if (enclosingMethod != null) { |
| enclosingMethodAttribute = new EnclosingMethodAttribute(enclosingMethod); |
| } else { |
| InnerClassAttribute namedEnclosing = null; |
| if (innerClasses != null) { |
| for (InnerClassAttribute innerClass : innerClasses) { |
| if (type == innerClass.getInner()) { |
| // If inner-class is anonymous then we create an enclosing-method attribute. |
| // Unfortunately we can't distinguish member classes from local classes, and thus |
| // can't at this point conform to the spec which requires a enclosing-method |
| // attribute iff the inner-class is anonymous or local. A local inner class will |
| // thus be represented as an ordinary member class and given an inner-classes |
| // entry below. |
| namedEnclosing = innerClass.isNamed() ? innerClass : null; |
| break; |
| } |
| } |
| } |
| if (namedEnclosing == null) { |
| enclosingMethodAttribute = new EnclosingMethodAttribute(enclosingClass); |
| } else { |
| assert innerClasses != null; |
| innerClasses.remove(namedEnclosing); |
| innerClasses.add( |
| new InnerClassAttribute( |
| namedEnclosing.getAccess(), |
| type, |
| enclosingClass, |
| namedEnclosing.getInnerName())); |
| } |
| } |
| } |
| |
| if (memberClasses != null) { |
| if (innerClasses == null) { |
| innerClasses = new ArrayList<>(memberClasses.size()); |
| } |
| for (DexType memberClass : memberClasses) { |
| innerClasses.add(InnerClassAttribute.createUnknownNamedInnerClass(memberClass, type)); |
| } |
| } |
| } |
| |
| private void ensureAnnotations(int index) { |
| if (lazyAnnotations == null) { |
| lazyAnnotations = new ArrayList<>(originalAnnotations.annotations.length); |
| lazyAnnotations.addAll(Arrays.asList(originalAnnotations.annotations).subList(0, index)); |
| } |
| } |
| |
| private void copyAnnotation(DexAnnotation annotation) { |
| if (lazyAnnotations != null) { |
| lazyAnnotations.add(annotation); |
| } |
| } |
| } |
| } |