| // 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.DexAnnotation; |
| import com.android.tools.r8.graph.DexAnnotationElement; |
| import com.android.tools.r8.graph.DexAnnotationSet; |
| 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.DexMember; |
| 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.DexProgramClass; |
| import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier; |
| 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.DexValueKind; |
| import com.android.tools.r8.graph.DexValue.DexValueNull; |
| import com.android.tools.r8.graph.EnclosingMethodAttribute; |
| import com.android.tools.r8.graph.FieldAccessFlags; |
| import com.android.tools.r8.graph.GenericSignature; |
| import com.android.tools.r8.graph.GenericSignature.ClassSignature; |
| import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature; |
| import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature; |
| 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.graph.ParameterAnnotationsList; |
| 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.InternalOptions; |
| 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.Int2ReferenceMap; |
| import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; |
| import it.unimi.dsi.fastutil.objects.Object2LongMap; |
| 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 DexParser<T extends DexClass> { |
| |
| private final int NO_INDEX = -1; |
| private final Origin origin; |
| private DexReader dexReader; |
| private final DexSection[] dexSections; |
| private int[] stringIDs; |
| private final ClassKind<T> classKind; |
| private final InternalOptions options; |
| private Object2LongMap<String> checksums; |
| |
| public static DexSection[] parseMapFrom(Path file) throws IOException { |
| return parseMapFrom(Files.newInputStream(file), new PathOrigin(file)); |
| } |
| |
| public static DexSection[] parseMapFrom(InputStream stream, Origin origin) throws IOException { |
| return parseMapFrom(new DexReader(origin, ByteStreams.toByteArray(stream))); |
| } |
| |
| private static DexSection[] parseMapFrom(DexReader dexReader) { |
| DexParser<DexProgramClass> dexParser = |
| new DexParser<>(dexReader, ClassKind.PROGRAM, new InternalOptions()); |
| return dexParser.dexSections; |
| } |
| |
| public void close() { |
| // This close behavior is needed to reduce peak memory usage of D8/R8. |
| indexedItems = null; |
| codes = null; |
| offsetMap = null; |
| dexReader = null; |
| stringIDs = null; |
| } |
| |
| // Mapping from indexes to indexable dex items. |
| private OffsetToObjectMapping indexedItems = new OffsetToObjectMapping(); |
| |
| // Mapping from offset to code item; |
| private Int2ReferenceMap<DexCode> codes = new Int2ReferenceOpenHashMap<>(); |
| |
| // Mapping from offset to dex item; |
| private Int2ReferenceMap<Object> offsetMap = new Int2ReferenceOpenHashMap<>(); |
| |
| // Factory to canonicalize certain dexitems. |
| private final DexItemFactory dexItemFactory; |
| |
| public DexParser(DexReader dexReader, ClassKind<T> classKind, InternalOptions options) { |
| assert dexReader.getOrigin() != null; |
| this.origin = dexReader.getOrigin(); |
| this.dexReader = dexReader; |
| this.dexItemFactory = options.itemFactory; |
| dexReader.setByteOrder(); |
| dexSections = parseMap(); |
| parseStringIDs(); |
| this.classKind = classKind; |
| this.options = options; |
| } |
| |
| private void ensureCodesInited(int offset) { |
| if (offset == 0) { |
| return; |
| } |
| |
| if (codes == null) { |
| codes = new Int2ReferenceOpenHashMap<>(); |
| } |
| |
| if (classKind == ClassKind.LIBRARY) { |
| // Ignore contents of library files. |
| return; |
| } |
| DexSection dexSection = lookupSection(Constants.TYPE_CODE_ITEM); |
| if (dexSection.length == 0) { |
| return; |
| } |
| |
| if (!codes.containsKey(offset)) { |
| int currentPos = dexReader.position(); |
| dexReader.position(offset); |
| dexReader.align(4); |
| DexCode code = parseCodeItem(); |
| codes.put(offset, code); // Update the file local offset to code mapping. |
| dexReader.position(currentPos); |
| } |
| } |
| |
| private DexTypeList parseTypeList() { |
| DexType[] result = new DexType[dexReader.getUint()]; |
| for (int j = 0; j < result.length; j++) { |
| result[j] = indexedItems.getType(dexReader.getUshort()); |
| } |
| return new DexTypeList(result); |
| } |
| |
| private DexTypeList typeListAt(int offset) { |
| if (offset == 0) { |
| return DexTypeList.empty(); |
| } |
| return (DexTypeList) cacheAt(offset, this::parseTypeList); |
| } |
| |
| private DexValue parseEncodedValue() { |
| int header = dexReader.get() & 0xff; |
| int valueArg = header >> 5; |
| int valueType = header & 0x1f; |
| switch (DexValueKind.fromId(valueType)) { |
| case BYTE: |
| { |
| assert valueArg == 0; |
| byte value = (byte) parseSigned(dexReader, 1); |
| return DexValue.DexValueByte.create(value); |
| } |
| case SHORT: |
| { |
| int size = valueArg + 1; |
| short value = (short) parseSigned(dexReader, size); |
| return DexValue.DexValueShort.create(value); |
| } |
| case CHAR: |
| { |
| int size = valueArg + 1; |
| char value = (char) parseUnsigned(dexReader, size); |
| return DexValue.DexValueChar.create(value); |
| } |
| case INT: |
| { |
| int size = valueArg + 1; |
| int value = (int) parseSigned(dexReader, size); |
| return DexValue.DexValueInt.create(value); |
| } |
| case LONG: |
| { |
| int size = valueArg + 1; |
| long value = parseSigned(dexReader, size); |
| return DexValue.DexValueLong.create(value); |
| } |
| case FLOAT: |
| { |
| int size = valueArg + 1; |
| return DexValue.DexValueFloat.create(parseFloat(dexReader, size)); |
| } |
| case DOUBLE: |
| { |
| int size = valueArg + 1; |
| return DexValue.DexValueDouble.create(parseDouble(dexReader, size)); |
| } |
| case STRING: |
| { |
| int size = valueArg + 1; |
| int index = (int) parseUnsigned(dexReader, size); |
| DexString value = indexedItems.getString(index); |
| return new DexValue.DexValueString(value); |
| } |
| case TYPE: |
| { |
| int size = valueArg + 1; |
| DexType value = indexedItems.getType((int) parseUnsigned(dexReader, size)); |
| return new DexValue.DexValueType(value); |
| } |
| case FIELD: |
| { |
| int size = valueArg + 1; |
| DexField value = indexedItems.getField((int) parseUnsigned(dexReader, size)); |
| checkName(value.name); |
| return new DexValue.DexValueField(value); |
| } |
| case METHOD: |
| { |
| int size = valueArg + 1; |
| DexMethod value = indexedItems.getMethod((int) parseUnsigned(dexReader, size)); |
| checkName(value.name); |
| return new DexValue.DexValueMethod(value); |
| } |
| case ENUM: |
| { |
| int size = valueArg + 1; |
| DexField value = indexedItems.getField((int) parseUnsigned(dexReader, size)); |
| return new DexValue.DexValueEnum(value); |
| } |
| case ARRAY: |
| { |
| assert valueArg == 0; |
| return new DexValue.DexValueArray(parseEncodedArrayValues()); |
| } |
| case ANNOTATION: |
| { |
| assert valueArg == 0; |
| return new DexValue.DexValueAnnotation(parseEncodedAnnotation()); |
| } |
| case NULL: |
| { |
| assert valueArg == 0; |
| return DexValueNull.NULL; |
| } |
| case BOOLEAN: |
| { |
| // 0 is false, and 1 is true. |
| return DexValue.DexValueBoolean.create(valueArg != 0); |
| } |
| case METHOD_TYPE: |
| { |
| int size = valueArg + 1; |
| DexProto value = indexedItems.getProto((int) parseUnsigned(dexReader, size)); |
| return new DexValue.DexValueMethodType(value); |
| } |
| case METHOD_HANDLE: |
| { |
| int size = valueArg + 1; |
| DexMethodHandle value = |
| indexedItems.getMethodHandle((int) parseUnsigned(dexReader, size)); |
| return new DexValue.DexValueMethodHandle(value); |
| } |
| default: |
| throw new IndexOutOfBoundsException(); |
| } |
| } |
| |
| private void checkName(DexString name) { |
| if (!options.itemFactory.getSkipNameValidationForTesting() |
| && !name.isValidSimpleName(options.minApiLevel)) { |
| throw new CompilationError("Space characters in SimpleName '" |
| + name.toASCIIString() |
| + "' are not allowed prior to DEX version 040"); |
| } |
| } |
| |
| private DexEncodedAnnotation parseEncodedAnnotation() { |
| int typeIdx = dexReader.getUleb128(); |
| int size = dexReader.getUleb128(); |
| DexAnnotationElement[] elements = new DexAnnotationElement[size]; |
| for (int i = 0; i < size; i++) { |
| int nameIdx = dexReader.getUleb128(); |
| DexValue value = parseEncodedValue(); |
| elements[i] = new DexAnnotationElement(indexedItems.getString(nameIdx), value); |
| } |
| return new DexEncodedAnnotation(indexedItems.getType(typeIdx), elements); |
| } |
| |
| private DexValue[] parseEncodedArrayValues() { |
| int size = dexReader.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] = dexReader.getUint(); |
| annotationOffsets[i] = dexReader.getUint(); |
| } |
| int saved = dexReader.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); |
| } |
| dexReader.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] = dexReader.getUint(); |
| annotationOffsets[i] = dexReader.getUint(); |
| } |
| int saved = dexReader.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); |
| } |
| dexReader.position(saved); |
| return result; |
| } |
| |
| private ParameterAnnotationsList annotationSetRefListAt(int offset) { |
| return (ParameterAnnotationsList) cacheAt(offset, this::parseAnnotationSetRefList); |
| } |
| |
| private ParameterAnnotationsList parseAnnotationSetRefList() { |
| int size = dexReader.getUint(); |
| int[] annotationOffsets = new int[size]; |
| for (int i = 0; i < size; i++) { |
| annotationOffsets[i] = dexReader.getUint(); |
| } |
| DexAnnotationSet[] values = new DexAnnotationSet[size]; |
| for (int i = 0; i < size; i++) { |
| values[i] = annotationSetAt(annotationOffsets[i]); |
| } |
| return new ParameterAnnotationsList(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] = dexReader.getUint(); |
| annotationOffsets[i] = dexReader.getUint(); |
| } |
| int saved = dexReader.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]) |
| .withParameterCount(method.proto.parameters.size())); |
| } |
| dexReader.position(saved); |
| return result; |
| } |
| |
| private <S> Object cacheAt(int offset, Supplier<S> function, Supplier<S> defaultValue) { |
| if (offset == 0) { |
| return defaultValue.get(); |
| } |
| return cacheAt(offset, function); |
| } |
| |
| private <S> Object cacheAt(int offset, Supplier<S> 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. |
| dexReader.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.", dexReader.position()); |
| } |
| int visibility = dexReader.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.", dexReader.position()); |
| } |
| int size = dexReader.getUint(); |
| int[] annotationOffsets = new int[size]; |
| for (int i = 0; i < size; i++) { |
| annotationOffsets[i] = dexReader.getUint(); |
| } |
| DexAnnotation[] result = new DexAnnotation[size]; |
| int actualSize = 0; |
| for (int i = 0; i < size; i++) { |
| DexAnnotation dexAnnotation = annotationAt(annotationOffsets[i]); |
| if (retainAnnotation(dexAnnotation)) { |
| result[actualSize++] = dexAnnotation; |
| } |
| } |
| if (actualSize < size) { |
| DexAnnotation[] temp = new DexAnnotation[actualSize]; |
| System.arraycopy(result, 0, temp, 0, actualSize); |
| result = temp; |
| } |
| DexType dupType = DexAnnotationSet.findDuplicateEntryType(result); |
| if (dupType != null) { |
| throw new CompilationError( |
| "Multiple annotations of type `" + dupType.toSourceString() + "`"); |
| } |
| return new DexAnnotationSet(result); |
| } |
| |
| private boolean retainAnnotation(DexAnnotation annotation) { |
| return annotation.visibility != DexAnnotation.VISIBILITY_BUILD |
| || DexAnnotation.retainCompileTimeAnnotation(annotation.annotation.type, options); |
| } |
| |
| 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 = dexReader.getUint(); |
| int fieldsSize = dexReader.getUint(); |
| int methodsSize = dexReader.getUint(); |
| int parametersSize = dexReader.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 = dexReader.getUleb128(); |
| int parametersSize = dexReader.getUleb128(); |
| DexString[] parameters = new DexString[parametersSize]; |
| for (int i = 0; i < parametersSize; i++) { |
| int index = dexReader.getUleb128p1(); |
| if (index != NO_INDEX) { |
| parameters[i] = indexedItems.getString(index); |
| } |
| } |
| List<DexDebugEvent> events = new ArrayList<>(); |
| for (int head = dexReader.getUbyte(); head != Constants.DBG_END_SEQUENCE; head = dexReader.getUbyte()) { |
| switch (head) { |
| case Constants.DBG_ADVANCE_PC: |
| events.add(dexItemFactory.createAdvancePC(dexReader.getUleb128())); |
| break; |
| case Constants.DBG_ADVANCE_LINE: |
| events.add(dexItemFactory.createAdvanceLine(dexReader.getSleb128())); |
| break; |
| case Constants.DBG_START_LOCAL: { |
| int registerNum = dexReader.getUleb128(); |
| int nameIdx = dexReader.getUleb128p1(); |
| int typeIdx = dexReader.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 = dexReader.getUleb128(); |
| int nameIdx = dexReader.getUleb128p1(); |
| int typeIdx = dexReader.getUleb128p1(); |
| int sigIdx = dexReader.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(dexReader.getUleb128())); |
| break; |
| } |
| case Constants.DBG_RESTART_LOCAL: { |
| events.add(dexItemFactory.createRestartLocal(dexReader.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 = dexReader.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(DexDebugEvent.EMPTY_ARRAY)); |
| } |
| |
| private static class MemberAnnotationIterator<R extends DexMember<?, R>, T extends DexItem> { |
| |
| private int index = 0; |
| private final DexMemberAnnotation<R, T>[] annotations; |
| private final Supplier<T> emptyValue; |
| |
| private MemberAnnotationIterator( |
| DexMemberAnnotation<R, 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(R 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.compareTo(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 += dexReader.getUleb128(); |
| DexField field = indexedItems.getField(fieldIndex); |
| FieldAccessFlags accessFlags = FieldAccessFlags.fromDexAccessFlags(dexReader.getUleb128()); |
| DexValue staticValue = null; |
| if (accessFlags.isStatic()) { |
| if (staticValues != null && i < staticValues.length) { |
| staticValue = staticValues[i]; |
| } |
| } |
| DexAnnotationSet fieldAnnotations = annotationIterator.getNextFor(field); |
| FieldTypeSignature fieldTypeSignature = FieldTypeSignature.noSignature(); |
| if (!options.passthroughDexCode) { |
| String fieldSignature = DexAnnotation.getSignature(fieldAnnotations, dexItemFactory); |
| if (fieldSignature != null) { |
| fieldAnnotations = fieldAnnotations.getWithout(dexItemFactory.annotationSignature); |
| fieldTypeSignature = |
| GenericSignature.parseFieldTypeSignature( |
| field.name.toString(), fieldSignature, origin, dexItemFactory, options.reporter); |
| } |
| } |
| fields[i] = |
| new DexEncodedField( |
| field, accessFlags, fieldTypeSignature, 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, ParameterAnnotationsList> parameterAnnotationsIterator = |
| new MemberAnnotationIterator<>(parameters, ParameterAnnotationsList::empty); |
| for (int i = 0; i < size; i++) { |
| methodIndex += dexReader.getUleb128(); |
| MethodAccessFlags accessFlags = MethodAccessFlags.fromDexAccessFlags(dexReader.getUleb128()); |
| int codeOff = dexReader.getUleb128(); |
| DexCode code = null; |
| if (!skipCodes) { |
| ensureCodesInited(codeOff); |
| assert codeOff == 0 || codes.get(codeOff) != null; |
| code = codes.get(codeOff); |
| } |
| DexMethod method = indexedItems.getMethod(methodIndex); |
| accessFlags.setConstructor(method, dexItemFactory); |
| DexAnnotationSet methodAnnotations = annotationIterator.getNextFor(method); |
| MethodTypeSignature methodTypeSignature = MethodTypeSignature.noSignature(); |
| if (!options.passthroughDexCode) { |
| String methodSignature = DexAnnotation.getSignature(methodAnnotations, dexItemFactory); |
| if (methodSignature != null) { |
| methodAnnotations = methodAnnotations.getWithout(dexItemFactory.annotationSignature); |
| methodTypeSignature = |
| GenericSignature.parseMethodSignature( |
| method.name.toString(), |
| methodSignature, |
| origin, |
| dexItemFactory, |
| options.reporter); |
| } |
| } |
| methods[i] = |
| new DexEncodedMethod( |
| method, |
| accessFlags, |
| methodTypeSignature, |
| methodAnnotations, |
| parameterAnnotationsIterator.getNextFor(method), |
| code); |
| } |
| return methods; |
| } |
| |
| void addClassDefsTo(Consumer<T> classCollection) { |
| final DexSection dexSection = lookupSection(Constants.TYPE_CLASS_DEF_ITEM); |
| final int length = dexSection.length; |
| indexedItems.initializeClasses(length); |
| if (length == 0) { |
| return; |
| } |
| dexReader.position(dexSection.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.", dexReader.position()); |
| } |
| classIndices[i] = dexReader.getUint(); |
| accessFlags[i] = dexReader.getUint(); |
| superclassIndices[i] = dexReader.getInt(); |
| interfacesOffsets[i] = dexReader.getUint(); |
| sourceFileIndices[i] = dexReader.getInt(); |
| annotationsOffsets[i] = dexReader.getUint(); |
| classDataOffsets[i] = dexReader.getUint(); |
| staticValuesOffsets[i] = dexReader.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, false)) { |
| 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]); |
| |
| Long checksum = null; |
| if (checksums != null && !checksums.isEmpty()) { |
| String desc = type.toDescriptorString(); |
| checksum = checksums.getOrDefault(desc, null); |
| if (!options.dexClassChecksumFilter.test(desc, checksum)) { |
| continue; |
| } |
| } |
| if (classDataOffsets[i] != 0) { |
| DexEncodedArray staticValues = encodedArrayAt(staticValuesOffsets[i]); |
| |
| dexReader.position(classDataOffsets[i]); |
| int staticFieldsSize = dexReader.getUleb128(); |
| int instanceFieldsSize = dexReader.getUleb128(); |
| int directMethodsSize = dexReader.getUleb128(); |
| int virtualMethodsSize = dexReader.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, origin, annotationsDirectory.clazz, options); |
| |
| Long finalChecksum = checksum; |
| ChecksumSupplier checksumSupplier = |
| finalChecksum == null ? DexProgramClass::invalidChecksumRequest : c -> finalChecksum; |
| |
| T clazz = |
| classKind.create( |
| type, |
| Kind.DEX, |
| origin, |
| flags, |
| superclass, |
| typeListAt(interfacesOffsets[i]), |
| source, |
| null, |
| Collections.emptyList(), |
| attrs.getEnclosingMethodAttribute(), |
| attrs.getInnerClasses(), |
| attrs.classSignature, |
| attrs.getAnnotations(), |
| staticFields, |
| instanceFields, |
| directMethods, |
| virtualMethods, |
| dexItemFactory.getSkipNameValidationForTesting(), |
| checksumSupplier); |
| classCollection.accept(clazz); // Update the application object. |
| } |
| } |
| |
| private void parseStringIDs() { |
| DexSection dexSection = lookupSection(Constants.TYPE_STRING_ID_ITEM); |
| stringIDs = new int[dexSection.length]; |
| if (dexSection.length == 0) { |
| return; |
| } |
| dexReader.position(dexSection.offset); |
| for (int i = 0; i < dexSection.length; i++) { |
| stringIDs[i] = dexReader.getUint(); |
| } |
| } |
| |
| private DexSection lookupSection(int type) { |
| for (DexSection s : dexSections) { |
| if (s.type == type) { |
| return s; |
| } |
| } |
| // If the section doesn't exist, return an empty section of this type. |
| return new DexSection(type, 0, 0, 0); |
| } |
| |
| private DexSection[] parseMap() { |
| // Read the dexSections information from the MAP. |
| int mapOffset = dexReader.getUint(Constants.MAP_OFF_OFFSET); |
| dexReader.position(mapOffset); |
| int mapSize = dexReader.getUint(); |
| final DexSection[] result = new DexSection[mapSize]; |
| for (int i = 0; i < mapSize; i++) { |
| int type = dexReader.getUshort(); |
| int unused = dexReader.getUshort(); |
| int size = dexReader.getUint(); |
| int offset = dexReader.getUint(); |
| if (offset + size > dexReader.end()) { |
| throw new CompilationError( |
| "The dex file had an offset + size that pointed past the end of the dex file." |
| + "\nSection type: " |
| + DexSection.typeName(type) |
| + "\nSection offset: " |
| + offset |
| + "\nSection size: " |
| + size |
| + "\nFile size: " |
| + dexReader.end(), |
| origin); |
| } |
| result[i] = new DexSection(type, unused, size, offset); |
| } |
| if (Log.ENABLED) { |
| for (int i = 0; i < result.length; i++) { |
| DexSection dexSection = result[i]; |
| int nextOffset = i < result.length - 1 ? result[i + 1].offset : dexSection.offset; |
| Log.debug(this.getClass(), "Read section 0x%04x @ 0x%08x #items %08d size 0x%08x.", |
| dexSection.type, dexSection.offset, dexSection.length, nextOffset - dexSection.offset); |
| } |
| } |
| for (int i = 0; i < mapSize - 1; i++) { |
| result[i].setEnd(result[i + 1].offset); |
| } |
| result[mapSize - 1].setEnd(dexReader.end()); |
| return result; |
| } |
| |
| private DexCode parseCodeItem() { |
| int registerSize = dexReader.getUshort(); |
| int insSize = dexReader.getUshort(); |
| int outsSize = dexReader.getUshort(); |
| int triesSize = dexReader.getUshort(); |
| int debugInfoOff = dexReader.getUint(); |
| int insnsSize = dexReader.getUint(); |
| short[] code = new short[insnsSize]; |
| Try[] tries = new Try[triesSize]; |
| TryHandler[] handlers = new TryHandler[0]; |
| |
| if (insnsSize != 0) { |
| for (int i = 0; i < insnsSize; i++) { |
| code[i] = dexReader.getShort(); |
| } |
| if (insnsSize % 2 != 0) { |
| dexReader.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 = dexReader.getUint(); |
| int insnCount = dexReader.getUshort(); |
| int handlerOff = dexReader.getUshort(); |
| tries[i] = new Try(startAddr, insnCount, handlerOff); |
| } |
| // handlers: encoded_catch_handler_list |
| int encodedCatchHandlerListPosition = dexReader.position(); |
| // - size: uleb128 |
| int size = dexReader.getUleb128(); |
| handlers = new TryHandler[size]; |
| // - list: encoded_catch_handler[handlers_size] |
| for (int i = 0; i < size; i++) { |
| // encoded_catch_handler |
| int encodedCatchHandlerOffset = dexReader.position() - encodedCatchHandlerListPosition; |
| handlerMap.put(encodedCatchHandlerOffset, i); |
| // - size: sleb128 |
| int hsize = dexReader.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 = dexReader.getUleb128(); |
| int addr = dexReader.getUleb128(); |
| pairs[j] = new TypeAddrPair(indexedItems.getType(typeIdx), addr); |
| } |
| int catchAllAddr = -1; |
| if (hsize <= 0) { |
| catchAllAddr = dexReader.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 = dexReader.position(); |
| DexDebugInfo debugInfo = debugInfoAt(debugInfoOff); |
| dexReader.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); |
| } |
| |
| void populateIndexTables() { |
| // Populate structures that are already sorted upon read. |
| populateStrings(); // Depends on nothing. |
| populateChecksums(); // Depends on Strings. |
| populateTypes(); // Depends on Strings. |
| populateFields(); // Depends on Types, and Strings. |
| populateProtos(); // Depends on Types and Strings. |
| populateMethods(); // Depends on Protos, Types, and Strings. |
| populateMethodHandles(); // Depends on Methods and Fields |
| populateCallSites(); // Depends on MethodHandles |
| } |
| |
| private void populateStrings() { |
| indexedItems.initializeStrings(stringIDs.length); |
| for (int i = 0; i < stringIDs.length; i++) { |
| indexedItems.setString(i, stringAt(i)); |
| } |
| } |
| |
| private void populateMethodHandles() { |
| DexSection dexSection = lookupSection(Constants.TYPE_METHOD_HANDLE_ITEM); |
| indexedItems.initializeMethodHandles(dexSection.length); |
| for (int i = 0; i < dexSection.length; i++) { |
| indexedItems.setMethodHandle(i, methodHandleAt(i)); |
| } |
| } |
| |
| private void populateCallSites() { |
| DexSection dexSection = lookupSection(Constants.TYPE_CALL_SITE_ID_ITEM); |
| indexedItems.initializeCallSites(dexSection.length); |
| for (int i = 0; i < dexSection.length; i++) { |
| indexedItems.setCallSites(i, callSiteAt(i)); |
| } |
| } |
| |
| private void populateTypes() { |
| DexSection dexSection = lookupSection(Constants.TYPE_TYPE_ID_ITEM); |
| assert verifyOrderOfTypeIds(dexSection); |
| indexedItems.initializeTypes(dexSection.length); |
| for (int i = 0; i < dexSection.length; i++) { |
| indexedItems.setType(i, typeAt(i)); |
| } |
| } |
| |
| private void populateChecksums() { |
| ClassesChecksum parsedChecksums = new ClassesChecksum(); |
| for (int i = stringIDs.length - 1; i >= 0; i--) { |
| DexString value = indexedItems.getString(i); |
| if (ClassesChecksum.definitelyPrecedesChecksumMarker(value)) { |
| break; |
| } |
| parsedChecksums.tryParseAndAppend(value); |
| } |
| this.checksums = parsedChecksums.getChecksums(); |
| } |
| |
| /** |
| * From https://source.android.com/devices/tech/dalvik/dex-format#file-layout: |
| * |
| * <p>This list must be sorted by string_id index, and it must not contain any duplicate entries. |
| */ |
| private boolean verifyOrderOfTypeIds(DexSection dexSection) { |
| if (dexSection.length >= 2) { |
| int initialOffset = dexSection.offset; |
| dexReader.position(initialOffset); |
| |
| int prevStringIndex = dexReader.getUint(); |
| |
| for (int index = 1; index < dexSection.length; index++) { |
| int offset = initialOffset + Constants.TYPE_TYPE_ID_ITEM_SIZE * index; |
| dexReader.position(offset); |
| |
| int stringIndex = dexReader.getUint(); |
| |
| boolean isValidOrder = stringIndex > prevStringIndex; |
| assert isValidOrder |
| : String.format( |
| "Out-of-order type ids (type #%s: `%s`, type #%s: `%s`)", |
| index - 1, |
| indexedItems.getString(prevStringIndex), |
| index, |
| indexedItems.getString(stringIndex)); |
| |
| prevStringIndex = stringIndex; |
| } |
| } |
| return true; |
| } |
| |
| private void populateFields() { |
| DexSection dexSection = lookupSection(Constants.TYPE_FIELD_ID_ITEM); |
| assert verifyOrderOfFieldIds(dexSection); |
| indexedItems.initializeFields(dexSection.length); |
| for (int i = 0; i < dexSection.length; i++) { |
| indexedItems.setField(i, fieldAt(i)); |
| } |
| } |
| |
| /** |
| * From https://source.android.com/devices/tech/dalvik/dex-format#file-layout: |
| * |
| * <p>This list must be sorted, where the defining type (by type_id index) is the major order, |
| * field name (by string_id index) is the intermediate order, and type (by type_id index) is the |
| * minor order. The list must not contain any duplicate entries. |
| */ |
| private boolean verifyOrderOfFieldIds(DexSection dexSection) { |
| if (dexSection.length >= 2) { |
| int initialOffset = dexSection.offset; |
| dexReader.position(initialOffset); |
| |
| int prevHolderIndex = dexReader.getUshort(); |
| int prevTypeIndex = dexReader.getUshort(); |
| int prevNameIndex = dexReader.getUint(); |
| |
| for (int index = 1; index < dexSection.length; index++) { |
| int offset = initialOffset + Constants.TYPE_FIELD_ID_ITEM_SIZE * index; |
| dexReader.position(offset); |
| |
| int holderIndex = dexReader.getUshort(); |
| int typeIndex = dexReader.getUshort(); |
| int nameIndex = dexReader.getUint(); |
| |
| boolean isValidOrder; |
| if (holderIndex == prevHolderIndex) { |
| if (nameIndex == prevNameIndex) { |
| isValidOrder = typeIndex > prevTypeIndex; |
| } else { |
| isValidOrder = nameIndex > prevNameIndex; |
| } |
| } else { |
| isValidOrder = holderIndex > prevHolderIndex; |
| } |
| |
| assert isValidOrder |
| : String.format( |
| "Out-of-order field ids (field #%s: `%s`, field #%s: `%s`)", |
| index - 1, |
| dexItemFactory |
| .createField( |
| indexedItems.getType(prevHolderIndex), |
| indexedItems.getType(prevTypeIndex), |
| indexedItems.getString(prevNameIndex)) |
| .toSourceString(), |
| index, |
| dexItemFactory |
| .createField( |
| indexedItems.getType(holderIndex), |
| indexedItems.getType(typeIndex), |
| indexedItems.getString(nameIndex)) |
| .toSourceString()); |
| |
| prevHolderIndex = holderIndex; |
| prevTypeIndex = typeIndex; |
| prevNameIndex = nameIndex; |
| } |
| } |
| return true; |
| } |
| |
| private void populateProtos() { |
| DexSection dexSection = lookupSection(Constants.TYPE_PROTO_ID_ITEM); |
| indexedItems.initializeProtos(dexSection.length); |
| for (int i = 0; i < dexSection.length; i++) { |
| indexedItems.setProto(i, protoAt(i)); |
| } |
| } |
| |
| private void populateMethods() { |
| DexSection dexSection = lookupSection(Constants.TYPE_METHOD_ID_ITEM); |
| assert verifyOrderOfMethodIds(dexSection); |
| indexedItems.initializeMethods(dexSection.length); |
| for (int i = 0; i < dexSection.length; i++) { |
| indexedItems.setMethod(i, methodAt(i)); |
| } |
| } |
| |
| /** |
| * From https://source.android.com/devices/tech/dalvik/dex-format#file-layout: |
| * |
| * <p>This list must be sorted, where the defining type (by type_id index) is the major order, |
| * method name (by string_id index) is the intermediate order, and method prototype |
| * (by proto_id index) is the minor order. The list must not contain any duplicate entries. |
| * |
| */ |
| private boolean verifyOrderOfMethodIds(DexSection dexSection) { |
| if (dexSection.length >= 2) { |
| int initialOffset = dexSection.offset; |
| dexReader.position(initialOffset); |
| |
| int prevHolderIndex = dexReader.getUshort(); |
| int prevProtoIndex = dexReader.getUshort(); |
| int prevNameIndex = dexReader.getUint(); |
| |
| for (int index = 1; index < dexSection.length; index++) { |
| int offset = initialOffset + Constants.TYPE_METHOD_ID_ITEM_SIZE * index; |
| dexReader.position(offset); |
| |
| int holderIndex = dexReader.getUshort(); |
| int protoIndex = dexReader.getUshort(); |
| int nameIndex = dexReader.getUint(); |
| |
| boolean isValidOrder; |
| if (holderIndex == prevHolderIndex) { |
| if (nameIndex == prevNameIndex) { |
| isValidOrder = protoIndex > prevProtoIndex; |
| } else { |
| isValidOrder = nameIndex > prevNameIndex; |
| } |
| } else { |
| isValidOrder = holderIndex > prevHolderIndex; |
| } |
| |
| assert isValidOrder |
| : String.format( |
| "Out-of-order method ids (method #%s: `%s`, method #%s: `%s`)", |
| index - 1, |
| dexItemFactory |
| .createMethod( |
| indexedItems.getType(prevHolderIndex), |
| indexedItems.getProto(prevProtoIndex), |
| indexedItems.getString(prevNameIndex)) |
| .toSourceString(), |
| index, |
| dexItemFactory |
| .createMethod( |
| indexedItems.getType(holderIndex), |
| indexedItems.getProto(protoIndex), |
| indexedItems.getString(nameIndex)) |
| .toSourceString()); |
| |
| prevHolderIndex = holderIndex; |
| prevProtoIndex = protoIndex; |
| prevNameIndex = nameIndex; |
| } |
| } |
| return true; |
| } |
| |
| private DexString stringAt(int index) { |
| final int offset = stringIDs[index]; |
| dexReader.position(offset); |
| int size = dexReader.getUleb128(); |
| ByteArrayOutputStream os = new ByteArrayOutputStream(); |
| byte read; |
| do { |
| read = dexReader.get(); |
| os.write(read); |
| } while (read != 0); |
| return dexItemFactory.createString(size, os.toByteArray()); |
| } |
| |
| private DexType typeAt(int index) { |
| DexSection dexSection = lookupSection(Constants.TYPE_TYPE_ID_ITEM); |
| if (index >= dexSection.length) { |
| return null; |
| } |
| int offset = dexSection.offset + (Constants.TYPE_TYPE_ID_ITEM_SIZE * index); |
| int stringIndex = dexReader.getUint(offset); |
| return dexItemFactory.createType(indexedItems.getString(stringIndex)); |
| } |
| |
| private DexField fieldAt(int index) { |
| DexSection dexSection = lookupSection(Constants.TYPE_FIELD_ID_ITEM); |
| if (index >= dexSection.length) { |
| return null; |
| } |
| int offset = dexSection.offset + (Constants.TYPE_FIELD_ID_ITEM_SIZE * index); |
| dexReader.position(offset); |
| int classIndex = dexReader.getUshort(); |
| int typeIndex = dexReader.getUshort(); |
| int nameIndex = dexReader.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) { |
| DexSection dexSection = lookupSection(Constants.TYPE_METHOD_HANDLE_ITEM); |
| if (index >= dexSection.length) { |
| return null; |
| } |
| int offset = dexSection.offset + (Constants.TYPE_METHOD_HANDLE_ITEM_SIZE * index); |
| dexReader.position(offset); |
| MethodHandleType type = MethodHandleType.getKind(dexReader.getUshort()); |
| dexReader.getUshort(); // unused |
| int indexFieldOrMethod = dexReader.getUshort(); |
| DexMember<? extends DexItem, ? extends DexMember<?, ?>> 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."); |
| } |
| dexReader.getUshort(); // unused |
| |
| return dexItemFactory.createMethodHandle( |
| type, fieldOrMethod, type == MethodHandleType.INVOKE_INTERFACE); |
| } |
| |
| private DexCallSite callSiteAt(int index) { |
| DexSection dexSection = lookupSection(Constants.TYPE_CALL_SITE_ID_ITEM); |
| if (index >= dexSection.length) { |
| return null; |
| } |
| int callSiteOffset = |
| dexReader.getUint(dexSection.offset + (Constants.TYPE_CALL_SITE_ID_ITEM_SIZE * index)); |
| DexEncodedArray callSiteEncodedArray = encodedArrayAt(callSiteOffset); |
| DexValue[] values = callSiteEncodedArray.values; |
| assert values[0].isDexValueMethodHandle(); |
| assert values[1].isDexValueString(); |
| assert values[2].isDexValueMethodType(); |
| |
| return dexItemFactory.createCallSite( |
| values[1].asDexValueString().value, |
| values[2].asDexValueMethodType().value, |
| values[0].asDexValueMethodHandle().value, |
| // 3 means first extra args |
| Arrays.asList(Arrays.copyOfRange(values, 3, values.length))); |
| } |
| |
| private DexProto protoAt(int index) { |
| DexSection dexSection = lookupSection(Constants.TYPE_PROTO_ID_ITEM); |
| if (index >= dexSection.length) { |
| return null; |
| } |
| int offset = dexSection.offset + (Constants.TYPE_PROTO_ID_ITEM_SIZE * index); |
| dexReader.position(offset); |
| int shortyIndex = dexReader.getUint(); |
| int returnTypeIndex = dexReader.getUint(); |
| int parametersOffsetIndex = dexReader.getUint(); |
| DexString shorty = indexedItems.getString(shortyIndex); |
| DexType returnType = indexedItems.getType(returnTypeIndex); |
| DexTypeList parameters = typeListAt(parametersOffsetIndex); |
| return dexItemFactory.createProto(returnType, parameters, shorty); |
| } |
| |
| private DexMethod methodAt(int index) { |
| DexSection dexSection = lookupSection(Constants.TYPE_METHOD_ID_ITEM); |
| if (index >= dexSection.length) { |
| return null; |
| } |
| int offset = dexSection.offset + (Constants.TYPE_METHOD_ID_ITEM_SIZE * index); |
| dexReader.position(offset); |
| int classIndex = dexReader.getUshort(); |
| int protoIndex = dexReader.getUshort(); |
| int nameIndex = dexReader.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; |
| private ClassSignature classSignature = ClassSignature.noSignature(); |
| |
| public DexAnnotationSet getAnnotations() { |
| if (lazyAnnotations != null) { |
| int size = lazyAnnotations.size(); |
| return size == 0 |
| ? DexAnnotationSet.empty() |
| : new DexAnnotationSet(lazyAnnotations.toArray(DexAnnotation.EMPTY_ARRAY)); |
| } |
| return originalAnnotations; |
| } |
| |
| public List<InnerClassAttribute> getInnerClasses() { |
| return innerClasses == null ? Collections.emptyList() : innerClasses; |
| } |
| |
| public EnclosingMethodAttribute getEnclosingMethodAttribute() { |
| return enclosingMethodAttribute; |
| } |
| |
| public ClassSignature getClassSignature() { |
| return classSignature; |
| } |
| |
| public AttributesAndAnnotations( |
| DexType type, Origin origin, DexAnnotationSet annotations, InternalOptions options) { |
| this.originalAnnotations = annotations; |
| DexType enclosingClass = null; |
| DexMethod enclosingMethod = null; |
| List<DexType> memberClasses = null; |
| DexItemFactory factory = options.dexItemFactory(); |
| |
| 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 if (members != null) { |
| memberClasses.addAll(members); |
| } |
| } else if (DexAnnotation.isSignatureAnnotation(annotation, factory) |
| && !options.passthroughDexCode) { |
| ensureAnnotations(i); |
| String signature = DexAnnotation.getSignature(annotation); |
| classSignature = |
| GenericSignature.parseClassSignature( |
| type.getName(), signature, origin, factory, options.reporter); |
| } 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); |
| } |
| } |
| } |
| } |