FindUsages.java revision 1c7c32e2ff5d9f6a3c0319e9d0088261f2cccb2b
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
17package com.android.dx.command.findusages;
18
19import com.android.dx.io.ClassData;
20import com.android.dx.io.ClassDef;
21import com.android.dx.io.CodeReader;
22import com.android.dx.io.DexBuffer;
23import com.android.dx.io.FieldId;
24import com.android.dx.io.MethodId;
25import com.android.dx.io.OpcodeInfo;
26import com.android.dx.io.instructions.DecodedInstruction;
27import java.io.PrintStream;
28import java.io.PrintWriter;
29import java.util.Collections;
30import java.util.HashSet;
31import java.util.List;
32import java.util.Set;
33import java.util.regex.Pattern;
34
35public final class FindUsages {
36    private final DexBuffer dex;
37    private final Set<Integer> methodIds;
38    private final Set<Integer> fieldIds;
39    private final CodeReader codeReader = new CodeReader();
40    private final PrintWriter out;
41
42    private ClassDef currentClass;
43    private ClassData.Method currentMethod;
44
45    public FindUsages(final DexBuffer dex, String declaredBy, String memberName, final PrintWriter out) {
46        this.dex = dex;
47        this.out = out;
48
49        Set<Integer> typeStringIndexes = new HashSet<Integer>();
50        Set<Integer> memberNameIndexes = new HashSet<Integer>();
51        Pattern declaredByPattern = Pattern.compile(declaredBy);
52        Pattern memberNamePattern = Pattern.compile(memberName);
53        List<String> strings = dex.strings();
54        for (int i = 0; i < strings.size(); ++i) {
55            String string = strings.get(i);
56            if (declaredByPattern.matcher(string).matches()) {
57                typeStringIndexes.add(i);
58            }
59            if (memberNamePattern.matcher(string).matches()) {
60                memberNameIndexes.add(i);
61            }
62        }
63        if (typeStringIndexes.isEmpty() || memberNameIndexes.isEmpty()) {
64            methodIds = fieldIds = null;
65            return; // these symbols are not mentioned in this dex
66        }
67
68        methodIds = new HashSet<Integer>();
69        fieldIds = new HashSet<Integer>();
70        for (int typeStringIndex : typeStringIndexes) {
71            int typeIndex = Collections.binarySearch(dex.typeIds(), typeStringIndex);
72            if (typeIndex < 0) {
73                continue; // this type name isn't used as a type in this dex
74            }
75            methodIds.addAll(getMethodIds(dex, memberNameIndexes, typeIndex));
76            fieldIds.addAll(getFieldIds(dex, memberNameIndexes, typeIndex));
77        }
78
79        codeReader.setFieldVisitor(new CodeReader.Visitor() {
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            public void visit(DecodedInstruction[] all, DecodedInstruction one) {
92                int methodId = one.getIndex();
93                if (methodIds.contains(methodId)) {
94                    out.println(location() + ": method reference " + dex.methodIds().get(methodId)
95                            + " (" + OpcodeInfo.getName(one.getOpcode()) + ")");
96                }
97            }
98        });
99    }
100
101    private String location() {
102        String className = dex.typeNames().get(currentClass.getTypeIndex());
103        if (currentMethod != null) {
104            MethodId methodId = dex.methodIds().get(currentMethod.getMethodIndex());
105            return className + "." + dex.strings().get(methodId.getNameIndex());
106        } else {
107            return className;
108        }
109    }
110
111    /**
112     * Prints usages to out.
113     */
114    public void findUsages() {
115        if (fieldIds == null || methodIds == null) {
116            return;
117        }
118
119        for (ClassDef classDef : dex.classDefs()) {
120            currentClass = classDef;
121            currentMethod = null;
122
123            if (classDef.getClassDataOffset() == 0) {
124                continue;
125            }
126
127            ClassData classData = dex.readClassData(classDef);
128            for (ClassData.Field field : classData.allFields()) {
129                int fieldIndex = field.getFieldIndex();
130                if (fieldIds.contains(fieldIndex)) {
131                    out.println(location() + " field declared " + dex.fieldIds().get(fieldIndex));
132                }
133            }
134
135            for (ClassData.Method method : classData.allMethods()) {
136                currentMethod = method;
137                int methodIndex = method.getMethodIndex();
138                if (methodIds.contains(methodIndex)) {
139                    out.println(location() + " method declared " + dex.methodIds().get(methodIndex));
140                }
141                if (method.getCodeOffset() != 0) {
142                    codeReader.visitAll(dex.readCode(method).getInstructions());
143                }
144            }
145        }
146
147        currentClass = null;
148        currentMethod = null;
149    }
150
151    /**
152     * Returns the fields with {@code memberNameIndex} declared by {@code
153     * declaringType}.
154     */
155    private Set<Integer> getFieldIds(DexBuffer dex, Set<Integer> memberNameIndexes, int declaringType) {
156        Set<Integer> fields = new HashSet<Integer>();
157        int fieldIndex = 0;
158        for (FieldId fieldId : dex.fieldIds()) {
159            if (memberNameIndexes.contains(fieldId.getNameIndex())
160                    && declaringType == fieldId.getDeclaringClassIndex()) {
161                fields.add(fieldIndex);
162            }
163            fieldIndex++;
164        }
165        return fields;
166    }
167
168    /**
169     * Returns the methods with {@code memberNameIndex} declared by {@code
170     * declaringType} and its subtypes.
171     */
172    private Set<Integer> getMethodIds(DexBuffer dex, Set<Integer> memberNameIndexes, int declaringType) {
173        Set<Integer> subtypes = findAssignableTypes(dex, declaringType);
174
175        Set<Integer> methods = new HashSet<Integer>();
176        int methodIndex = 0;
177        for (MethodId method : dex.methodIds()) {
178            if (memberNameIndexes.contains(method.getNameIndex())
179                    && subtypes.contains(method.getDeclaringClassIndex())) {
180                methods.add(methodIndex);
181            }
182            methodIndex++;
183        }
184        return methods;
185    }
186
187    /**
188     * Returns the set of types that can be assigned to {@code typeIndex}.
189     */
190    private Set<Integer> findAssignableTypes(DexBuffer dex, int typeIndex) {
191        Set<Integer> assignableTypes = new HashSet<Integer>();
192        assignableTypes.add(typeIndex);
193
194        for (ClassDef classDef : dex.classDefs()) {
195            if (assignableTypes.contains(classDef.getSupertypeIndex())) {
196                assignableTypes.add(classDef.getTypeIndex());
197                continue;
198            }
199
200            for (int implemented : classDef.getInterfaces()) {
201                if (assignableTypes.contains(implemented)) {
202                    assignableTypes.add(classDef.getTypeIndex());
203                    break;
204                }
205            }
206        }
207
208        return assignableTypes;
209    }
210}
211