// Copyright 2017 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.android.dexer; import com.android.dex.Dex; import com.android.dex.FieldId; import com.android.dex.MethodId; import com.android.dex.ProtoId; import com.android.dex.TypeList; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.collect.ImmutableList; import java.util.HashSet; /** * Helper to track how many unique field and method references we've seen in a given set of .dex * files. */ class DexLimitTracker { private final HashSet fieldsSeen = new HashSet<>(); private final HashSet methodsSeen = new HashSet<>(); private final int maxNumberOfIdxPerDex; public DexLimitTracker(int maxNumberOfIdxPerDex) { this.maxNumberOfIdxPerDex = maxNumberOfIdxPerDex; } /** * Tracks the field and method references in the given file and returns whether we're within * limits. * * @return {@code true} if method or field references are outside limits, {@code false} both * are within limits. */ public boolean track(Dex dexFile) { trackFieldsAndMethods(dexFile); return fieldsSeen.size() > maxNumberOfIdxPerDex || methodsSeen.size() > maxNumberOfIdxPerDex; } public void clear() { fieldsSeen.clear(); methodsSeen.clear(); } private void trackFieldsAndMethods(Dex dexFile) { int fieldCount = dexFile.fieldIds().size(); for (int fieldIndex = 0; fieldIndex < fieldCount; ++fieldIndex) { fieldsSeen.add(FieldDescriptor.fromDex(dexFile, fieldIndex)); } int methodCount = dexFile.methodIds().size(); for (int methodIndex = 0; methodIndex < methodCount; ++methodIndex) { methodsSeen.add(MethodDescriptor.fromDex(dexFile, methodIndex)); } } private static String typeName(Dex dex, int typeIndex) { return dex.typeNames().get(typeIndex); } @AutoValue abstract static class FieldDescriptor { static FieldDescriptor fromDex(Dex dex, int fieldIndex) { FieldId field = dex.fieldIds().get(fieldIndex); String name = dex.strings().get(field.getNameIndex()); String declaringClass = typeName(dex, field.getDeclaringClassIndex()); String type = typeName(dex, field.getTypeIndex()); return new AutoValue_DexLimitTracker_FieldDescriptor(declaringClass, name, type); } abstract String declaringClass(); abstract String fieldName(); abstract String fieldType(); @Override @Memoized public abstract int hashCode(); } @AutoValue abstract static class MethodDescriptor { static MethodDescriptor fromDex(Dex dex, int methodIndex) { MethodId method = dex.methodIds().get(methodIndex); ProtoId proto = dex.protoIds().get(method.getProtoIndex()); String name = dex.strings().get(method.getNameIndex()); String declaringClass = typeName(dex, method.getDeclaringClassIndex()); String returnType = typeName(dex, proto.getReturnTypeIndex()); TypeList parameterTypeIndices = dex.readTypeList(proto.getParametersOffset()); ImmutableList.Builder parameterTypes = ImmutableList.builder(); for (short parameterTypeIndex : parameterTypeIndices.getTypes()) { parameterTypes.add(typeName(dex, parameterTypeIndex & 0xFFFF)); } return new AutoValue_DexLimitTracker_MethodDescriptor( declaringClass, name, parameterTypes.build(), returnType); } abstract String declaringClass(); abstract String methodName(); abstract ImmutableList parameterTypes(); abstract String returnType(); @Override @Memoized public abstract int hashCode(); } }