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