1 /* <lambda>null2 * Copyright (C) 2024 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.hoststubgen.dumper 17 18 import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME 19 import com.android.hoststubgen.asm.CTOR_NAME 20 import com.android.hoststubgen.asm.ClassNodes 21 import com.android.hoststubgen.asm.getClassNameFromFullClassName 22 import com.android.hoststubgen.asm.getPackageNameFromFullClassName 23 import com.android.hoststubgen.asm.toHumanReadableClassName 24 import com.android.hoststubgen.csvEscape 25 import com.android.hoststubgen.filters.FilterPolicy 26 import com.android.hoststubgen.filters.FilterPolicyWithReason 27 import com.android.hoststubgen.filters.OutputFilter 28 import com.android.hoststubgen.log 29 import org.objectweb.asm.Type 30 import org.objectweb.asm.tree.ClassNode 31 import java.io.PrintWriter 32 33 /** 34 * Dump all the API methods in [classes], with inherited methods, with their policies. 35 */ 36 class ApiDumper( 37 val pw: PrintWriter, 38 val classes: ClassNodes, 39 val frameworkClasses: ClassNodes?, 40 val filter: OutputFilter, 41 ) { 42 private data class MethodKey( 43 val name: String, 44 val descriptor: String, 45 ) 46 47 val javaStandardApiPolicy = FilterPolicy.Stub.withReason("Java standard API") 48 49 private val shownMethods = mutableSetOf<MethodKey>() 50 51 /** 52 * Do the dump. 53 */ 54 fun dump() { 55 pw.printf("PackageName,ClassName,FromSubclass,DeclareClass,MethodName,MethodDesc" + 56 ",Supported,Policy,Reason\n") 57 58 classes.forEach { classNode -> 59 shownMethods.clear() 60 dump(classNode, classNode) 61 } 62 } 63 64 private fun dumpMethod( 65 classPackage: String, 66 className: String, 67 isSuperClass: Boolean, 68 methodClassName: String, 69 methodName: String, 70 methodDesc: String, 71 policy: FilterPolicyWithReason, 72 ) { 73 pw.printf( 74 "%s,%s,%d,%s,%s,%s,%d,%s,%s\n", 75 csvEscape(classPackage), 76 csvEscape(className), 77 if (isSuperClass) { 1 } else { 0 }, 78 csvEscape(methodClassName), 79 csvEscape(methodName), 80 csvEscape(methodDesc), 81 if (policy.policy.isSupported) { 1 } else { 0 }, 82 policy.policy, 83 csvEscape(policy.reason), 84 ) 85 } 86 87 private fun isDuplicate(methodName: String, methodDesc: String): Boolean { 88 val methodKey = MethodKey(methodName, methodDesc) 89 90 if (shownMethods.contains(methodKey)) { 91 return true 92 } 93 shownMethods.add(methodKey) 94 return false 95 } 96 97 private fun dump( 98 dumpClass: ClassNode, 99 methodClass: ClassNode, 100 ) { 101 val pkg = getPackageNameFromFullClassName(dumpClass.name).toHumanReadableClassName() 102 val cls = getClassNameFromFullClassName(dumpClass.name).toHumanReadableClassName() 103 104 val isSuperClass = dumpClass != methodClass 105 106 methodClass.methods?.sortedWith(compareBy({ it.name }, { it.desc }))?.forEach { method -> 107 108 // Don't print ctor's from super classes. 109 if (isSuperClass) { 110 if (CTOR_NAME == method.name || CLASS_INITIALIZER_NAME == method.name) { 111 return@forEach 112 } 113 } 114 // If we already printed the method from a subclass, don't print it. 115 if (isDuplicate(method.name, method.desc)) { 116 return@forEach 117 } 118 119 val policy = filter.getPolicyForMethod(methodClass.name, method.name, method.desc) 120 121 // Let's skip "Remove" APIs. Ideally we want to print it, just to make the CSV 122 // complete, we still need to hide methods substituted (== @RavenwoodReplace) methods 123 // and for now we don't have an easy way to detect it. 124 if (policy.policy == FilterPolicy.Remove) { 125 return@forEach 126 } 127 128 val renameTo = filter.getRenameTo(methodClass.name, method.name, method.desc) 129 130 dumpMethod(pkg, cls, isSuperClass, methodClass.name.toHumanReadableClassName(), 131 renameTo ?: method.name, method.desc, policy) 132 } 133 134 // Dump super class methods. 135 dumpSuper(dumpClass, methodClass.superName) 136 137 // Dump interface methods (which may have default methods). 138 methodClass.interfaces?.sorted()?.forEach { interfaceName -> 139 dumpSuper(dumpClass, interfaceName) 140 } 141 } 142 143 /** 144 * Dump a given super class / interface. 145 */ 146 private fun dumpSuper( 147 dumpClass: ClassNode, 148 methodClassName: String, 149 ) { 150 classes.findClass(methodClassName)?.let { methodClass -> 151 dump(dumpClass, methodClass) 152 return 153 } 154 frameworkClasses?.findClass(methodClassName)?.let { methodClass -> 155 dump(dumpClass, methodClass) 156 return 157 } 158 if (methodClassName.startsWith("java/") || 159 methodClassName.startsWith("javax/") 160 ) { 161 dumpStandardClass(dumpClass, methodClassName) 162 return 163 } 164 log.w("Super class or interface $methodClassName (used by ${dumpClass.name}) not found.") 165 } 166 167 /** 168 * Dump methods from Java standard classes. 169 */ 170 private fun dumpStandardClass( 171 dumpClass: ClassNode, 172 methodClassName: String, 173 ) { 174 val pkg = getPackageNameFromFullClassName(dumpClass.name).toHumanReadableClassName() 175 val cls = getClassNameFromFullClassName(dumpClass.name).toHumanReadableClassName() 176 177 val methodClassName = methodClassName.toHumanReadableClassName() 178 179 try { 180 val clazz = Class.forName(methodClassName) 181 182 // Method.getMethods() returns only public methods, but with inherited ones. 183 // Method.getDeclaredMethods() returns private methods too, but no inherited methods. 184 // 185 // Since we're only interested in public ones, just use getMethods(). 186 clazz.methods.forEach { method -> 187 val methodName = method.name 188 val methodDesc = Type.getMethodDescriptor(method) 189 190 // If we already printed the method from a subclass, don't print it. 191 if (isDuplicate(methodName, methodDesc)) { 192 return@forEach 193 } 194 195 dumpMethod(pkg, cls, true, methodClassName, 196 methodName, methodDesc, javaStandardApiPolicy) 197 } 198 } catch (e: ClassNotFoundException) { 199 log.w("JVM type $methodClassName (used by ${dumpClass.name}) not found.") 200 } 201 } 202 } 203