/* * Copyright (C) 2011 The Android Open Source Project * * 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.taobao.android.dx.command.findusages; import com.taobao.android.dex.ClassData; import com.taobao.android.dex.ClassDef; import com.taobao.android.dex.Dex; import com.taobao.android.dex.FieldId; import com.taobao.android.dex.MethodId; import com.taobao.android.dx.io.CodeReader; import com.taobao.android.dx.io.OpcodeInfo; import com.taobao.android.dx.io.instructions.DecodedInstruction; import java.io.PrintWriter; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; public final class FindUsages { private final Dex dex; private final Set<Integer> methodIds; private final Set<Integer> fieldIds; private final CodeReader codeReader = new CodeReader(); private final PrintWriter out; private ClassDef currentClass; private ClassData.Method currentMethod; public FindUsages(final Dex dex, String declaredBy, String memberName, final PrintWriter out) { this.dex = dex; this.out = out; Set<Integer> typeStringIndexes = new HashSet<Integer>(); Set<Integer> memberNameIndexes = new HashSet<Integer>(); Pattern declaredByPattern = Pattern.compile(declaredBy); Pattern memberNamePattern = Pattern.compile(memberName); List<String> strings = dex.strings(); for (int i = 0; i < strings.size(); ++i) { String string = strings.get(i); if (declaredByPattern.matcher(string).matches()) { typeStringIndexes.add(i); } if (memberNamePattern.matcher(string).matches()) { memberNameIndexes.add(i); } } if (typeStringIndexes.isEmpty() || memberNameIndexes.isEmpty()) { methodIds = fieldIds = null; return; // these symbols are not mentioned in this dex } methodIds = new HashSet<Integer>(); fieldIds = new HashSet<Integer>(); for (int typeStringIndex : typeStringIndexes) { int typeIndex = Collections.binarySearch(dex.typeIds(), typeStringIndex); if (typeIndex < 0) { continue; // this type name isn't used as a type in this dex } methodIds.addAll(getMethodIds(dex, memberNameIndexes, typeIndex)); fieldIds.addAll(getFieldIds(dex, memberNameIndexes, typeIndex)); } codeReader.setFieldVisitor(new CodeReader.Visitor() { public void visit(DecodedInstruction[] all, DecodedInstruction one) { int fieldId = one.getIndex(); if (fieldIds.contains(fieldId)) { out.println(location() + ": field reference " + dex.fieldIds().get(fieldId) + " (" + OpcodeInfo.getName(one.getOpcode()) + ")"); } } }); codeReader.setMethodVisitor(new CodeReader.Visitor() { public void visit(DecodedInstruction[] all, DecodedInstruction one) { int methodId = one.getIndex(); if (methodIds.contains(methodId)) { out.println(location() + ": method reference " + dex.methodIds().get(methodId) + " (" + OpcodeInfo.getName(one.getOpcode()) + ")"); } } }); } private String location() { String className = dex.typeNames().get(currentClass.getTypeIndex()); if (currentMethod != null) { MethodId methodId = dex.methodIds().get(currentMethod.getMethodIndex()); return className + "." + dex.strings().get(methodId.getNameIndex()); } else { return className; } } /** * Prints usages to out. */ public void findUsages() { if (fieldIds == null || methodIds == null) { return; } for (ClassDef classDef : dex.classDefs()) { currentClass = classDef; currentMethod = null; if (classDef.getClassDataOffset() == 0) { continue; } ClassData classData = dex.readClassData(classDef); for (ClassData.Field field : classData.allFields()) { int fieldIndex = field.getFieldIndex(); if (fieldIds.contains(fieldIndex)) { out.println(location() + " field declared " + dex.fieldIds().get(fieldIndex)); } } for (ClassData.Method method : classData.allMethods()) { currentMethod = method; int methodIndex = method.getMethodIndex(); if (methodIds.contains(methodIndex)) { out.println(location() + " method declared " + dex.methodIds().get(methodIndex)); } if (method.getCodeOffset() != 0) { codeReader.visitAll(dex.readCode(method).getInstructions()); } } } currentClass = null; currentMethod = null; } /** * Returns the fields with {@code memberNameIndex} declared by {@code * declaringType}. */ private Set<Integer> getFieldIds(Dex dex, Set<Integer> memberNameIndexes, int declaringType) { Set<Integer> fields = new HashSet<Integer>(); int fieldIndex = 0; for (FieldId fieldId : dex.fieldIds()) { if (memberNameIndexes.contains(fieldId.getNameIndex()) && declaringType == fieldId.getDeclaringClassIndex()) { fields.add(fieldIndex); } fieldIndex++; } return fields; } /** * Returns the methods with {@code memberNameIndex} declared by {@code * declaringType} and its subtypes. */ private Set<Integer> getMethodIds(Dex dex, Set<Integer> memberNameIndexes, int declaringType) { Set<Integer> subtypes = findAssignableTypes(dex, declaringType); Set<Integer> methods = new HashSet<Integer>(); int methodIndex = 0; for (MethodId method : dex.methodIds()) { if (memberNameIndexes.contains(method.getNameIndex()) && subtypes.contains(method.getDeclaringClassIndex())) { methods.add(methodIndex); } methodIndex++; } return methods; } /** * Returns the set of types that can be assigned to {@code typeIndex}. */ private Set<Integer> findAssignableTypes(Dex dex, int typeIndex) { Set<Integer> assignableTypes = new HashSet<Integer>(); assignableTypes.add(typeIndex); for (ClassDef classDef : dex.classDefs()) { if (assignableTypes.contains(classDef.getSupertypeIndex())) { assignableTypes.add(classDef.getTypeIndex()); continue; } for (int implemented : classDef.getInterfaces()) { if (assignableTypes.contains(implemented)) { assignableTypes.add(classDef.getTypeIndex()); break; } } } return assignableTypes; } }