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