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