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 }