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