1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.dx.command.findusages; 18 19 import com.android.dex.ClassData; 20 import com.android.dex.ClassDef; 21 import com.android.dex.Dex; 22 import com.android.dex.FieldId; 23 import com.android.dex.MethodId; 24 import com.android.dx.io.CodeReader; 25 import com.android.dx.io.OpcodeInfo; 26 import com.android.dx.io.instructions.DecodedInstruction; 27 import java.io.PrintWriter; 28 import java.util.Collections; 29 import java.util.HashSet; 30 import java.util.List; 31 import java.util.Set; 32 import java.util.regex.Pattern; 33 34 public final class FindUsages { 35 private final Dex dex; 36 private final Set<Integer> methodIds; 37 private final Set<Integer> fieldIds; 38 private final CodeReader codeReader = new CodeReader(); 39 private final PrintWriter out; 40 41 private ClassDef currentClass; 42 private ClassData.Method currentMethod; 43 FindUsages(final Dex dex, String declaredBy, String memberName, final PrintWriter out)44 public FindUsages(final Dex dex, String declaredBy, String memberName, final PrintWriter out) { 45 this.dex = dex; 46 this.out = out; 47 48 Set<Integer> typeStringIndexes = new HashSet<Integer>(); 49 Set<Integer> memberNameIndexes = new HashSet<Integer>(); 50 Pattern declaredByPattern = Pattern.compile(declaredBy); 51 Pattern memberNamePattern = Pattern.compile(memberName); 52 List<String> strings = dex.strings(); 53 for (int i = 0; i < strings.size(); ++i) { 54 String string = strings.get(i); 55 if (declaredByPattern.matcher(string).matches()) { 56 typeStringIndexes.add(i); 57 } 58 if (memberNamePattern.matcher(string).matches()) { 59 memberNameIndexes.add(i); 60 } 61 } 62 if (typeStringIndexes.isEmpty() || memberNameIndexes.isEmpty()) { 63 methodIds = fieldIds = null; 64 return; // these symbols are not mentioned in this dex 65 } 66 67 methodIds = new HashSet<Integer>(); 68 fieldIds = new HashSet<Integer>(); 69 for (int typeStringIndex : typeStringIndexes) { 70 int typeIndex = Collections.binarySearch(dex.typeIds(), typeStringIndex); 71 if (typeIndex < 0) { 72 continue; // this type name isn't used as a type in this dex 73 } 74 methodIds.addAll(getMethodIds(dex, memberNameIndexes, typeIndex)); 75 fieldIds.addAll(getFieldIds(dex, memberNameIndexes, typeIndex)); 76 } 77 78 codeReader.setFieldVisitor(new CodeReader.Visitor() { 79 @Override 80 public void visit(DecodedInstruction[] all, 81 DecodedInstruction one) { 82 int fieldId = one.getIndex(); 83 if (fieldIds.contains(fieldId)) { 84 out.println(location() + ": field reference " + dex.fieldIds().get(fieldId) 85 + " (" + OpcodeInfo.getName(one.getOpcode()) + ")"); 86 } 87 } 88 }); 89 90 codeReader.setMethodVisitor(new CodeReader.Visitor() { 91 @Override 92 public void visit(DecodedInstruction[] all, DecodedInstruction one) { 93 int methodId = one.getIndex(); 94 if (methodIds.contains(methodId)) { 95 out.println(location() + ": method reference " + dex.methodIds().get(methodId) 96 + " (" + OpcodeInfo.getName(one.getOpcode()) + ")"); 97 } 98 } 99 }); 100 } 101 location()102 private String location() { 103 String className = dex.typeNames().get(currentClass.getTypeIndex()); 104 if (currentMethod != null) { 105 MethodId methodId = dex.methodIds().get(currentMethod.getMethodIndex()); 106 return className + "." + dex.strings().get(methodId.getNameIndex()); 107 } else { 108 return className; 109 } 110 } 111 112 /** 113 * Prints usages to out. 114 */ findUsages()115 public void findUsages() { 116 if (fieldIds == null || methodIds == null) { 117 return; 118 } 119 120 for (ClassDef classDef : dex.classDefs()) { 121 currentClass = classDef; 122 currentMethod = null; 123 124 if (classDef.getClassDataOffset() == 0) { 125 continue; 126 } 127 128 ClassData classData = dex.readClassData(classDef); 129 for (ClassData.Field field : classData.allFields()) { 130 int fieldIndex = field.getFieldIndex(); 131 if (fieldIds.contains(fieldIndex)) { 132 out.println(location() + " field declared " + dex.fieldIds().get(fieldIndex)); 133 } 134 } 135 136 for (ClassData.Method method : classData.allMethods()) { 137 currentMethod = method; 138 int methodIndex = method.getMethodIndex(); 139 if (methodIds.contains(methodIndex)) { 140 out.println(location() + " method declared " + dex.methodIds().get(methodIndex)); 141 } 142 if (method.getCodeOffset() != 0) { 143 codeReader.visitAll(dex.readCode(method).getInstructions()); 144 } 145 } 146 } 147 148 currentClass = null; 149 currentMethod = null; 150 } 151 152 /** 153 * Returns the fields with {@code memberNameIndex} declared by {@code 154 * declaringType}. 155 */ getFieldIds(Dex dex, Set<Integer> memberNameIndexes, int declaringType)156 private Set<Integer> getFieldIds(Dex dex, Set<Integer> memberNameIndexes, int declaringType) { 157 Set<Integer> fields = new HashSet<Integer>(); 158 int fieldIndex = 0; 159 for (FieldId fieldId : dex.fieldIds()) { 160 if (memberNameIndexes.contains(fieldId.getNameIndex()) 161 && declaringType == fieldId.getDeclaringClassIndex()) { 162 fields.add(fieldIndex); 163 } 164 fieldIndex++; 165 } 166 return fields; 167 } 168 169 /** 170 * Returns the methods with {@code memberNameIndex} declared by {@code 171 * declaringType} and its subtypes. 172 */ getMethodIds(Dex dex, Set<Integer> memberNameIndexes, int declaringType)173 private Set<Integer> getMethodIds(Dex dex, Set<Integer> memberNameIndexes, int declaringType) { 174 Set<Integer> subtypes = findAssignableTypes(dex, declaringType); 175 176 Set<Integer> methods = new HashSet<Integer>(); 177 int methodIndex = 0; 178 for (MethodId method : dex.methodIds()) { 179 if (memberNameIndexes.contains(method.getNameIndex()) 180 && subtypes.contains(method.getDeclaringClassIndex())) { 181 methods.add(methodIndex); 182 } 183 methodIndex++; 184 } 185 return methods; 186 } 187 188 /** 189 * Returns the set of types that can be assigned to {@code typeIndex}. 190 */ findAssignableTypes(Dex dex, int typeIndex)191 private Set<Integer> findAssignableTypes(Dex dex, int typeIndex) { 192 Set<Integer> assignableTypes = new HashSet<Integer>(); 193 assignableTypes.add(typeIndex); 194 195 for (ClassDef classDef : dex.classDefs()) { 196 if (assignableTypes.contains(classDef.getSupertypeIndex())) { 197 assignableTypes.add(classDef.getTypeIndex()); 198 continue; 199 } 200 201 for (int implemented : classDef.getInterfaces()) { 202 if (assignableTypes.contains(implemented)) { 203 assignableTypes.add(classDef.getTypeIndex()); 204 break; 205 } 206 } 207 } 208 209 return assignableTypes; 210 } 211 } 212