1 /*
<lambda>null2  * Copyright (C) 2023 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.visitors
17 
18 import com.android.hoststubgen.HostStubGenErrors
19 import com.android.hoststubgen.HostStubGenStats
20 import com.android.hoststubgen.LogLevel
21 import com.android.hoststubgen.asm.ClassNodes
22 import com.android.hoststubgen.asm.UnifiedVisitor
23 import com.android.hoststubgen.asm.getPackageNameFromFullClassName
24 import com.android.hoststubgen.asm.resolveClassNameWithDefaultPackage
25 import com.android.hoststubgen.asm.toJvmClassName
26 import com.android.hoststubgen.filters.FilterPolicy
27 import com.android.hoststubgen.filters.FilterPolicyWithReason
28 import com.android.hoststubgen.filters.OutputFilter
29 import com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
30 import com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
31 import com.android.hoststubgen.log
32 import org.objectweb.asm.ClassVisitor
33 import org.objectweb.asm.FieldVisitor
34 import org.objectweb.asm.MethodVisitor
35 import org.objectweb.asm.Opcodes
36 import org.objectweb.asm.commons.ClassRemapper
37 import org.objectweb.asm.util.TraceClassVisitor
38 import java.io.PrintWriter
39 
40 val OPCODE_VERSION = Opcodes.ASM9
41 
42 abstract class BaseAdapter (
43         protected val classes: ClassNodes,
44         nextVisitor: ClassVisitor,
45         protected val filter: OutputFilter,
46         protected val options: Options,
47 ) : ClassVisitor(OPCODE_VERSION, nextVisitor) {
48 
49     /**
50      * Options to control the behavior.
51      */
52     data class Options (
53             val errors: HostStubGenErrors,
54             val stats: HostStubGenStats?,
55             val enablePreTrace: Boolean,
56             val enablePostTrace: Boolean,
57             val enableNonStubMethodCallDetection: Boolean,
58             )
59 
60     protected lateinit var currentPackageName: String
61     protected lateinit var currentClassName: String
62     protected var nativeSubstitutionClass: String? = null
63     protected lateinit var classPolicy: FilterPolicyWithReason
64 
65     /**
66      * Return whether an item with a given policy should be included in the output.
67      */
68     protected abstract fun shouldEmit(policy: FilterPolicy): Boolean
69 
70     /**
71      * Inject [HostStubGenKeptInStub] and [HostStubGenKeptInImpl] as needed to an item.
72      */
73     protected fun injectInStubAndKeepAnnotations(policy: FilterPolicy, v: UnifiedVisitor) {
74         if (policy.needsInStub) {
75             v.visitAnnotation(HostStubGenKeptInStub.CLASS_DESCRIPTOR, true)
76         }
77         if (policy.needsInImpl) {
78             v.visitAnnotation(HostStubGenKeptInImpl.CLASS_DESCRIPTOR, true)
79         }
80     }
81 
82     override fun visit(
83             version: Int,
84             access: Int,
85             name: String,
86             signature: String?,
87             superName: String?,
88             interfaces: Array<String>,
89     ) {
90         super.visit(version, access, name, signature, superName, interfaces)
91         currentClassName = name
92         currentPackageName = getPackageNameFromFullClassName(name)
93         classPolicy = filter.getPolicyForClass(currentClassName)
94 
95         log.d("[%s] visit: %s (package: %s)", this.javaClass.simpleName, name, currentPackageName)
96         log.indent()
97         log.v("Emitting class: %s", name)
98         log.indent()
99 
100         filter.getNativeSubstitutionClass(currentClassName)?.let { className ->
101             val fullClassName = resolveClassNameWithDefaultPackage(className, currentPackageName)
102                 .toJvmClassName()
103             log.d("  NativeSubstitutionClass: $fullClassName")
104             if (classes.findClass(fullClassName) == null) {
105                 log.w("Native substitution class $fullClassName not found. Class must be " +
106                         "available at runtime.")
107             } else {
108                 // If the class exists, it must have a KeepClass policy.
109                 if (filter.getPolicyForClass(fullClassName).policy != FilterPolicy.KeepClass) {
110                     // TODO: Use real annotation name.
111                     options.errors.onErrorFound(
112                             "Native substitution class $fullClassName should have @Keep.")
113                 }
114             }
115 
116             nativeSubstitutionClass = fullClassName
117         }
118         // Inject annotations to generated classes.
119         injectInStubAndKeepAnnotations(classPolicy.policy, UnifiedVisitor.on(this))
120     }
121 
122     override fun visitEnd() {
123         log.unindent()
124         log.unindent()
125         super.visitEnd()
126     }
127 
128     var skipMemberModificationNestCount = 0
129 
130     /**
131      * This method allows writing class members without any modifications.
132      */
133     protected inline fun writeRawMembers(callback: () -> Unit) {
134         skipMemberModificationNestCount++
135         try {
136             callback()
137         } finally {
138             skipMemberModificationNestCount--
139         }
140     }
141 
142     override fun visitField(
143             access: Int,
144             name: String,
145             descriptor: String,
146             signature: String?,
147             value: Any?,
148     ): FieldVisitor? {
149         if (skipMemberModificationNestCount > 0) {
150             return super.visitField(access, name, descriptor, signature, value)
151         }
152         val policy = filter.getPolicyForField(currentClassName, name)
153         log.d("visitField: %s %s [%x] Policy: %s", name, descriptor, access, policy)
154 
155         log.withIndent {
156             if (!shouldEmit(policy.policy)) {
157                 log.d("Removing %s %s", name, policy)
158                 return null
159             }
160 
161             log.v("Emitting field: %s %s %s", name, descriptor, policy)
162             val ret = super.visitField(access, name, descriptor, signature, value)
163 
164             injectInStubAndKeepAnnotations(policy.policy, UnifiedVisitor.on(ret))
165 
166             return ret
167         }
168     }
169 
170     override fun visitMethod(
171             access: Int,
172             name: String,
173             descriptor: String,
174             signature: String?,
175             exceptions: Array<String>?,
176     ): MethodVisitor? {
177         if (skipMemberModificationNestCount > 0) {
178             return super.visitMethod(access, name, descriptor, signature, exceptions)
179         }
180         val p = filter.getPolicyForMethod(currentClassName, name, descriptor)
181         log.d("visitMethod: %s%s [%x] [%s] Policy: %s", name, descriptor, access, signature, p)
182         options.stats?.onVisitPolicyForMethod(currentClassName, name, descriptor, p, access)
183 
184         log.withIndent {
185             // If it's a substitute-from method, then skip (== remove).
186             // Instead of this method, we rename the substitute-to method with the original
187             // name, in the "Maybe rename the method" part below.
188             val policy = filter.getPolicyForMethod(currentClassName, name, descriptor)
189             if (policy.policy.isSubstitute) {
190                 log.d("Skipping %s%s %s", name, descriptor, policy)
191                 return null
192             }
193             if (!shouldEmit(p.policy)) {
194                 log.d("Removing %s%s %s", name, descriptor, policy)
195                 return null
196             }
197 
198             var newAccess = access
199 
200             // Maybe rename the method.
201             val newName: String
202             val renameTo = filter.getRenameTo(currentClassName, name, descriptor)
203             if (renameTo != null) {
204                 newName = renameTo
205 
206                 // It's confusing, but here, `newName` is the original method name
207                 // (the one with the @substitute/replace annotation).
208                 // `name` is the name of the method we're currently visiting, so it's usually a
209                 // "...$ravewnwood" name.
210                 newAccess = checkSubstitutionMethodCompatibility(
211                         classes, currentClassName, newName, name, descriptor, options.errors)
212                 if (newAccess == NOT_COMPATIBLE) {
213                     return null
214                 }
215 
216                 log.v("Emitting %s.%s%s as %s %s", currentClassName, name, descriptor,
217                         newName, policy)
218             } else {
219                 log.v("Emitting method: %s%s %s", name, descriptor, policy)
220                 newName = name
221             }
222 
223             // Let subclass update the flag.
224             // But note, we only use it when calling the super's method,
225             // but not for visitMethodInner(), because when subclass wants to change access,
226             // it can do so inside visitMethodInner().
227             newAccess = updateAccessFlags(newAccess, name, descriptor)
228 
229             val ret = visitMethodInner(access, newName, descriptor, signature, exceptions, policy,
230                 renameTo != null,
231                 super.visitMethod(newAccess, newName, descriptor, signature, exceptions))
232 
233             ret?.let {
234                 injectInStubAndKeepAnnotations(policy.policy, UnifiedVisitor.on(ret))
235             }
236 
237             return ret
238         }
239     }
240 
241     open fun updateAccessFlags(
242             access: Int,
243             name: String,
244             descriptor: String,
245     ): Int {
246         return access
247     }
248 
249     abstract fun visitMethodInner(
250         access: Int,
251         name: String,
252         descriptor: String,
253         signature: String?,
254         exceptions: Array<String>?,
255         policy: FilterPolicyWithReason,
256         substituted: Boolean,
257         superVisitor: MethodVisitor?,
258         ): MethodVisitor?
259 
260     companion object {
261         fun getVisitor(
262                 classInternalName: String,
263                 classes: ClassNodes,
264                 nextVisitor: ClassVisitor,
265                 filter: OutputFilter,
266                 packageRedirector: PackageRedirectRemapper,
267                 forImpl: Boolean,
268                 options: Options,
269         ): ClassVisitor {
270             var next = nextVisitor
271 
272             val verbosePrinter = PrintWriter(log.getWriter(LogLevel.Verbose))
273 
274             // Inject TraceClassVisitor for debugging.
275             if (options.enablePostTrace) {
276                 next = TraceClassVisitor(next, verbosePrinter)
277             }
278 
279             // Handle --package-redirect
280             if (!packageRedirector.isEmpty) {
281                 // Don't apply the remapper on redirect-from classes.
282                 // Otherwise, if the target jar actually contains the "from" classes (which
283                 // may or may not be the case) they'd be renamed.
284                 // But we update all references in other places, so, a method call to a "from" class
285                 // would be replaced with the "to" class. All type references (e.g. variable types)
286                 // will be updated too.
287                 if (!packageRedirector.isTarget(classInternalName)) {
288                     next = ClassRemapper(next, packageRedirector)
289                 } else {
290                     log.v("Class $classInternalName is a redirect-from class, not applying" +
291                             " --package-redirect")
292                 }
293             }
294 
295             var ret: ClassVisitor
296             if (forImpl) {
297                 ret = ImplGeneratingAdapter(classes, next, filter, options)
298             } else {
299                 ret = StubGeneratingAdapter(classes, next, filter, options)
300             }
301 
302             // Inject TraceClassVisitor for debugging.
303             if (options.enablePreTrace) {
304                 ret = TraceClassVisitor(ret, verbosePrinter)
305             }
306             return ret
307         }
308     }
309 }
310