• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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 package com.android.dependencymapper;
17 
18 import org.objectweb.asm.signature.SignatureReader;
19 import org.objectweb.asm.signature.SignatureVisitor;
20 import org.objectweb.asm.ClassReader;
21 import org.objectweb.asm.ClassVisitor;
22 import org.objectweb.asm.Label;
23 import org.objectweb.asm.Opcodes;
24 import org.objectweb.asm.Type;
25 import org.objectweb.asm.TypePath;
26 
27 import java.lang.annotation.RetentionPolicy;
28 import java.util.HashSet;
29 import java.util.Set;
30 
31 /**
32  * An ASM based class visitor to analyze and club all dependencies of a java file.
33  * Most of the logic of this class is inspired from
34  * <a href="https://github.com/gradle/gradle/blob/master/platforms/jvm/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/asm/ClassDependenciesVisitor.java">gradle incremental compilation</a>
35  */
36 public class ClassDependenciesVisitor extends ClassVisitor {
37 
38     private final static int API = Opcodes.ASM9;
39 
40     private final Set<String> mClassTypes;
41     private final Set<Object> mConstantsDefined;
42     private final Set<Object> mInlinedUsages;
43     private String mSource;
44     private boolean isAnnotationType;
45     private boolean mIsDependencyToAll;
46     private final RetentionPolicyVisitor retentionPolicyVisitor;
47 
48     private final ClassRelevancyFilter mClassFilter;
49 
ClassDependenciesVisitor(ClassReader reader, ClassRelevancyFilter filter)50     private ClassDependenciesVisitor(ClassReader reader, ClassRelevancyFilter filter) {
51         super(API);
52         this.mClassTypes = new HashSet<>();
53         this.mConstantsDefined = new HashSet<>();
54         this.mInlinedUsages =  new HashSet<>();
55         this.retentionPolicyVisitor = new RetentionPolicyVisitor();
56         this.mClassFilter = filter;
57         collectRemainingClassDependencies(reader);
58     }
59 
analyze( String className, ClassReader reader, ClassRelevancyFilter filter)60     public static ClassDependencyData analyze(
61             String className, ClassReader reader, ClassRelevancyFilter filter) {
62         ClassDependenciesVisitor visitor = new ClassDependenciesVisitor(reader, filter);
63         reader.accept(visitor, ClassReader.SKIP_FRAMES);
64         // Sometimes a class may contain references to the same class, we remove such cases to
65         // prevent circular dependency.
66         visitor.getClassTypes().remove(className);
67         return new ClassDependencyData(Utils.buildPackagePrependedClassSource(
68                 className, visitor.getSource()), className, visitor.getClassTypes(),
69                 visitor.isDependencyToAll(), visitor.getConstantsDefined(),
70                 visitor.getInlinedUsages());
71     }
72 
73     @Override
visitSource(String source, String debug)74     public void visitSource(String source, String debug) {
75         mSource = source;
76     }
77 
78     @Override
visit(int version, int access, String name, String signature, String superName, String[] interfaces)79     public void visit(int version, int access, String name, String signature, String superName,
80             String[] interfaces) {
81         isAnnotationType = isAnnotationType(interfaces);
82         maybeAddClassTypesFromSignature(signature, mClassTypes);
83         if (superName != null) {
84             // superName can be null if what we are analyzing is `java.lang.Object`
85             // which can happen when a custom Java SDK is on classpath (typically, android.jar)
86             Type type = Type.getObjectType(superName);
87             maybeAddClassType(mClassTypes, type);
88         }
89         for (String s : interfaces) {
90             Type interfaceType = Type.getObjectType(s);
91             maybeAddClassType(mClassTypes, interfaceType);
92         }
93     }
94 
95     // performs a fast analysis of classes referenced in bytecode (method bodies)
96     // avoiding us to implement a costly visitor and potentially missing edge cases
collectRemainingClassDependencies(ClassReader reader)97     private void collectRemainingClassDependencies(ClassReader reader) {
98         char[] charBuffer = new char[reader.getMaxStringLength()];
99         for (int i = 1; i < reader.getItemCount(); i++) {
100             int itemOffset = reader.getItem(i);
101             // see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4
102             if (itemOffset > 0 && reader.readByte(itemOffset - 1) == 7) {
103                 // A CONSTANT_Class entry, read the class descriptor
104                 String classDescriptor = reader.readUTF8(itemOffset, charBuffer);
105                 Type type = Type.getObjectType(classDescriptor);
106                 maybeAddClassType(mClassTypes, type);
107             }
108         }
109     }
110 
maybeAddClassTypesFromSignature(String signature, Set<String> types)111     private void maybeAddClassTypesFromSignature(String signature, Set<String> types) {
112         if (signature != null) {
113             SignatureReader signatureReader = new SignatureReader(signature);
114             signatureReader.accept(new SignatureVisitor(API) {
115                 @Override
116                 public void visitClassType(String className) {
117                     Type type = Type.getObjectType(className);
118                     maybeAddClassType(types, type);
119                 }
120             });
121         }
122     }
123 
maybeAddClassType(Set<String> types, Type type)124     protected void maybeAddClassType(Set<String> types, Type type) {
125         while (type.getSort() == Type.ARRAY) {
126             type = type.getElementType();
127         }
128         if (type.getSort() != Type.OBJECT) {
129             return;
130         }
131         //String name = Utils.classPackageToFilePath(type.getClassName());
132         String name = type.getClassName();
133         if (mClassFilter.test(name)) {
134             types.add(name);
135         }
136     }
137 
getSource()138     public String getSource() {
139         return mSource;
140     }
141 
getClassTypes()142     public Set<String> getClassTypes() {
143         return mClassTypes;
144     }
145 
getConstantsDefined()146     public Set<Object> getConstantsDefined() {
147         return mConstantsDefined;
148     }
149 
getInlinedUsages()150     public Set<Object> getInlinedUsages() {
151         return mInlinedUsages;
152     }
153 
isAnnotationType(String[] interfaces)154     private boolean isAnnotationType(String[] interfaces) {
155         return interfaces.length == 1 && interfaces[0].equals("java/lang/annotation/Annotation");
156     }
157 
158     @Override
visitField( int access, String name, String desc, String signature, Object value)159     public FieldVisitor visitField(
160             int access, String name, String desc, String signature, Object value) {
161         maybeAddClassTypesFromSignature(signature, mClassTypes);
162         maybeAddClassType(mClassTypes, Type.getType(desc));
163         if (isAccessibleConstant(access, value)) {
164             mConstantsDefined.add(value);
165         }
166         return new FieldVisitor(mClassTypes);
167     }
168 
169     @Override
visitMethod( int access, String name, String desc, String signature, String[] exceptions)170     public MethodVisitor visitMethod(
171             int access, String name, String desc, String signature, String[] exceptions) {
172         maybeAddClassTypesFromSignature(signature, mClassTypes);
173         Type methodType = Type.getMethodType(desc);
174         maybeAddClassType(mClassTypes, methodType.getReturnType());
175         for (Type argType : methodType.getArgumentTypes()) {
176             maybeAddClassType(mClassTypes, argType);
177         }
178         return new MethodVisitor(mClassTypes);
179     }
180 
181     @Override
visitAnnotation(String desc, boolean visible)182     public org.objectweb.asm.AnnotationVisitor visitAnnotation(String desc, boolean visible) {
183         if (isAnnotationType && "Ljava/lang/annotation/Retention;".equals(desc)) {
184             return retentionPolicyVisitor;
185         } else {
186             maybeAddClassType(mClassTypes, Type.getType(desc));
187             return new AnnotationVisitor(mClassTypes);
188         }
189     }
190 
isAccessible(int access)191     private static boolean isAccessible(int access) {
192         return (access & Opcodes.ACC_PRIVATE) == 0;
193     }
194 
isAccessibleConstant(int access, Object value)195     private static boolean isAccessibleConstant(int access, Object value) {
196         return isConstant(access) && isAccessible(access) && value != null;
197     }
198 
isConstant(int access)199     private static boolean isConstant(int access) {
200         return (access & Opcodes.ACC_FINAL) != 0 && (access & Opcodes.ACC_STATIC) != 0;
201     }
202 
isDependencyToAll()203     public boolean isDependencyToAll() {
204         return mIsDependencyToAll;
205     }
206 
207     private class FieldVisitor extends org.objectweb.asm.FieldVisitor {
208         private final Set<String> types;
209 
FieldVisitor(Set<String> types)210         public FieldVisitor(Set<String> types) {
211             super(API);
212             this.types = types;
213         }
214 
215         @Override
visitAnnotation( String descriptor, boolean visible)216         public org.objectweb.asm.AnnotationVisitor visitAnnotation(
217                 String descriptor, boolean visible) {
218             maybeAddClassType(types, Type.getType(descriptor));
219             return new AnnotationVisitor(types);
220         }
221 
222         @Override
visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible)223         public org.objectweb.asm.AnnotationVisitor visitTypeAnnotation(int typeRef,
224                 TypePath typePath, String descriptor, boolean visible) {
225             maybeAddClassType(types, Type.getType(descriptor));
226             return new AnnotationVisitor(types);
227         }
228     }
229 
230     private class MethodVisitor extends org.objectweb.asm.MethodVisitor {
231         private final Set<String> types;
232 
MethodVisitor(Set<String> types)233         protected MethodVisitor(Set<String> types) {
234             super(API);
235             this.types = types;
236         }
237 
238         @Override
visitLdcInsn(Object value)239         public void visitLdcInsn(Object value) {
240             mInlinedUsages.add(value);
241             super.visitLdcInsn(value);
242         }
243 
244         @Override
visitLocalVariable( String name, String desc, String signature, Label start, Label end, int index)245         public void visitLocalVariable(
246                 String name, String desc, String signature, Label start, Label end, int index) {
247             maybeAddClassTypesFromSignature(signature, mClassTypes);
248             maybeAddClassType(mClassTypes, Type.getType(desc));
249             super.visitLocalVariable(name, desc, signature, start, end, index);
250         }
251 
252         @Override
visitAnnotation( String descriptor, boolean visible)253         public org.objectweb.asm.AnnotationVisitor visitAnnotation(
254                 String descriptor, boolean visible) {
255             maybeAddClassType(types, Type.getType(descriptor));
256             return new AnnotationVisitor(types);
257         }
258 
259         @Override
visitParameterAnnotation( int parameter, String descriptor, boolean visible)260         public org.objectweb.asm.AnnotationVisitor visitParameterAnnotation(
261                 int parameter, String descriptor, boolean visible) {
262             maybeAddClassType(types, Type.getType(descriptor));
263             return new AnnotationVisitor(types);
264         }
265 
266         @Override
visitTypeAnnotation( int typeRef, TypePath typePath, String descriptor, boolean visible)267         public org.objectweb.asm.AnnotationVisitor visitTypeAnnotation(
268                 int typeRef, TypePath typePath, String descriptor, boolean visible) {
269             maybeAddClassType(types, Type.getType(descriptor));
270             return new AnnotationVisitor(types);
271         }
272     }
273 
274     private class RetentionPolicyVisitor extends org.objectweb.asm.AnnotationVisitor {
RetentionPolicyVisitor()275         public RetentionPolicyVisitor() {
276             super(ClassDependenciesVisitor.API);
277         }
278 
279         @Override
visitEnum(String name, String desc, String value)280         public void visitEnum(String name, String desc, String value) {
281             if ("Ljava/lang/annotation/RetentionPolicy;".equals(desc)) {
282                 RetentionPolicy policy = RetentionPolicy.valueOf(value);
283                 if (policy == RetentionPolicy.SOURCE) {
284                     mIsDependencyToAll = true;
285                 }
286             }
287         }
288     }
289 
290     private class AnnotationVisitor extends org.objectweb.asm.AnnotationVisitor {
291         private final Set<String> types;
292 
AnnotationVisitor(Set<String> types)293         public AnnotationVisitor(Set<String> types) {
294             super(ClassDependenciesVisitor.API);
295             this.types = types;
296         }
297 
298         @Override
visit(String name, Object value)299         public void visit(String name, Object value) {
300             if (value instanceof Type) {
301                 maybeAddClassType(types, (Type) value);
302             }
303         }
304 
305         @Override
visitArray(String name)306         public org.objectweb.asm.AnnotationVisitor visitArray(String name) {
307             return this;
308         }
309 
310         @Override
visitAnnotation(String name, String descriptor)311         public org.objectweb.asm.AnnotationVisitor visitAnnotation(String name, String descriptor) {
312             maybeAddClassType(types, Type.getType(descriptor));
313             return this;
314         }
315     }
316 }