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.filters
17 
18 import com.android.hoststubgen.ClassParseException
19 import com.android.hoststubgen.HostStubGenErrors
20 import com.android.hoststubgen.HostStubGenInternalException
21 import com.android.hoststubgen.InvalidAnnotationException
22 import com.android.hoststubgen.addNonNullElement
23 import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC
24 import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
25 import com.android.hoststubgen.asm.ClassNodes
26 import com.android.hoststubgen.asm.findAnnotationValueAsString
27 import com.android.hoststubgen.asm.findAnyAnnotation
28 import com.android.hoststubgen.asm.toHumanReadableClassName
29 import com.android.hoststubgen.asm.toHumanReadableMethodName
30 import com.android.hoststubgen.asm.toJvmClassName
31 import com.android.hoststubgen.log
32 import com.android.hoststubgen.utils.ClassFilter
33 import org.objectweb.asm.tree.AnnotationNode
34 import org.objectweb.asm.tree.ClassNode
35 
36 // TODO: Detect invalid cases, such as...
37 // - Class's visibility is lower than the members'.
38 // - HostSideTestSubstituteWith is set, but it doesn't have @Stub or @Keep
39 
40 /**
41  * [OutputFilter] using Java annotations.
42  */
43 class AnnotationBasedFilter(
44         private val errors: HostStubGenErrors,
45         private val classes: ClassNodes,
46         stubAnnotations_: Set<String>,
47         keepAnnotations_: Set<String>,
48         stubClassAnnotations_: Set<String>,
49         keepClassAnnotations_: Set<String>,
50         throwAnnotations_: Set<String>,
51         removeAnnotations_: Set<String>,
52         substituteAnnotations_: Set<String>,
53         nativeSubstituteAnnotations_: Set<String>,
54         classLoadHookAnnotations_: Set<String>,
55         keepStaticInitializerAnnotations_: Set<String>,
56         private val annotationAllowedClassesFilter: ClassFilter,
57         fallback: OutputFilter,
58 ) : DelegatingFilter(fallback) {
59     private var stubAnnotations = convertToInternalNames(stubAnnotations_)
60     private var keepAnnotations = convertToInternalNames(keepAnnotations_)
61     private var stubClassAnnotations = convertToInternalNames(stubClassAnnotations_)
62     private var keepClassAnnotations = convertToInternalNames(keepClassAnnotations_)
63     private var throwAnnotations = convertToInternalNames(throwAnnotations_)
64     private var removeAnnotations = convertToInternalNames(removeAnnotations_)
65     private var substituteAnnotations = convertToInternalNames(substituteAnnotations_)
66     private var nativeSubstituteAnnotations = convertToInternalNames(nativeSubstituteAnnotations_)
67     private var classLoadHookAnnotations = convertToInternalNames(classLoadHookAnnotations_)
68     private var keepStaticInitializerAnnotations =
69             convertToInternalNames(keepStaticInitializerAnnotations_)
70 
71     /** Annotations that control API visibility. */
72     private var visibilityAnnotations: Set<String> = convertToInternalNames(
73         stubAnnotations_ +
74         keepAnnotations_ +
75         stubClassAnnotations_ +
76         keepClassAnnotations_ +
77         throwAnnotations_ +
78         removeAnnotations_)
79 
80     /**
81      * All the annotations we use. Note, this one is in a [convertToJvmNames] format unlike
82      * other ones, because of how it's used.
83      */
84     private var allAnnotations: Set<String> = convertToJvmNames(
85         stubAnnotations_ +
86                 keepAnnotations_ +
87                 stubClassAnnotations_ +
88                 keepClassAnnotations_ +
89                 throwAnnotations_ +
90                 removeAnnotations_ +
91                 substituteAnnotations_ +
92                 nativeSubstituteAnnotations_ +
93                 classLoadHookAnnotations_)
94 
95     private val substitutionHelper = SubstitutionHelper()
96 
97     private val reasonAnnotation = "annotation"
98     private val reasonClassAnnotation = "class-annotation"
99 
100     /**
101      * Throw if an item has more than one visibility annotations.
102      *
103      * name1 - 4 are only used in exception messages. We take them as separate strings
104      * to avoid unnecessary string concatenations.
105      */
106     private fun detectInvalidAnnotations(
107         visibles: List<AnnotationNode>?,
108         invisibles: List<AnnotationNode>?,
109         type: String,
110         name1: String,
111         name2: String,
112         name3: String,
113     ) {
114         var count = 0
115         for (an in visibles ?: emptyList()) {
116             if (visibilityAnnotations.contains(an.desc)) {
117                 count++
118             }
119         }
120         for (an in invisibles ?: emptyList()) {
121             if (visibilityAnnotations.contains(an.desc)) {
122                 count++
123             }
124         }
125         if (count > 1) {
126             val description = if (name2 == "" && name3 == "") {
127                 "$type $name1"
128             } else {
129                 "$type $name1.$name2$name3"
130             }
131             throw InvalidAnnotationException(
132                 "Found more than one visibility annotations on $description")
133         }
134     }
135 
136     fun findAnyAnnotation(
137             className: String,
138             anyAnnotations: Set<String>,
139             visibleAnnotations: List<AnnotationNode>?,
140             invisibleAnnotations: List<AnnotationNode>?,
141     ): AnnotationNode? {
142         val ret = findAnyAnnotation(anyAnnotations, visibleAnnotations, invisibleAnnotations)
143 
144         if (ret != null) {
145             if (!annotationAllowedClassesFilter.matches(className)) {
146                 throw InvalidAnnotationException(
147                         "Class ${className.toHumanReadableClassName()} is not allowed to have " +
148                                 "Ravenwood annotations. Contact g/ravenwood for more details.")
149             }
150         }
151 
152         return ret
153     }
154 
155     /**
156      * Find a visibility annotation.
157      *
158      * name1 - 4 are only used in exception messages.
159      */
160     private fun findAnnotation(
161             className: String,
162             visibles: List<AnnotationNode>?,
163             invisibles: List<AnnotationNode>?,
164             type: String,
165             name1: String,
166             name2: String = "",
167             name3: String = "",
168     ): FilterPolicyWithReason? {
169         detectInvalidAnnotations(visibles, invisibles, type, name1, name2, name3)
170 
171         findAnyAnnotation(className, stubAnnotations, visibles, invisibles)?.let {
172             return FilterPolicy.Stub.withReason(reasonAnnotation)
173         }
174         findAnyAnnotation(className, stubClassAnnotations, visibles, invisibles)?.let {
175             return FilterPolicy.StubClass.withReason(reasonClassAnnotation)
176         }
177         findAnyAnnotation(className, keepAnnotations, visibles, invisibles)?.let {
178             return FilterPolicy.Keep.withReason(reasonAnnotation)
179         }
180         findAnyAnnotation(className, keepClassAnnotations, visibles, invisibles)?.let {
181             return FilterPolicy.KeepClass.withReason(reasonClassAnnotation)
182         }
183         findAnyAnnotation(className, throwAnnotations, visibles, invisibles)?.let {
184             return FilterPolicy.Throw.withReason(reasonAnnotation)
185         }
186         findAnyAnnotation(className, removeAnnotations, visibles, invisibles)?.let {
187             return FilterPolicy.Remove.withReason(reasonAnnotation)
188         }
189 
190         return null
191     }
192 
193     override fun getPolicyForClass(className: String): FilterPolicyWithReason {
194         val cn = classes.getClass(className)
195 
196         findAnnotation(
197             cn.name,
198             cn.visibleAnnotations,
199             cn.invisibleAnnotations,
200             "class",
201             className)?.let {
202             return it
203         }
204 
205         // If it's any of the annotations, then always keep it.
206         if (allAnnotations.contains(className)) {
207             return FilterPolicy.KeepClass.withReason("HostStubGen Annotation")
208         }
209 
210         return super.getPolicyForClass(className)
211     }
212 
213     override fun getPolicyForField(
214             className: String,
215             fieldName: String
216     ): FilterPolicyWithReason {
217         val cn = classes.getClass(className)
218 
219         cn.fields?.firstOrNull { it.name == fieldName }?.let {fn ->
220             findAnnotation(
221                 cn.name,
222                 fn.visibleAnnotations,
223                 fn.invisibleAnnotations,
224                 "field",
225                 className,
226                 fieldName
227                 )?.let { policy ->
228                 // If the item has an annotation, then use it.
229                 return policy
230             }
231         }
232         return super.getPolicyForField(className, fieldName)
233     }
234 
235     override fun getPolicyForMethod(
236             className: String,
237             methodName: String,
238             descriptor: String
239     ): FilterPolicyWithReason {
240         val cn = classes.getClass(className)
241 
242         if (methodName == CLASS_INITIALIZER_NAME && descriptor == CLASS_INITIALIZER_DESC) {
243             findAnyAnnotation(cn.name, keepStaticInitializerAnnotations,
244                     cn.visibleAnnotations, cn.invisibleAnnotations)?.let {
245                 return FilterPolicy.Keep.withReason(reasonAnnotation)
246             }
247         }
248 
249         cn.methods?.firstOrNull { it.name == methodName && it.desc == descriptor }?.let { mn ->
250             // @SubstituteWith is going to complicate the policy here, so we ask helper
251             // what to do.
252             substitutionHelper.getPolicyFromSubstitution(cn, mn.name, mn.desc)?.let {
253                 return it
254             }
255 
256             // If there's no substitution, then we check the annotation.
257             findAnnotation(
258                 cn.name,
259                 mn.visibleAnnotations,
260                 mn.invisibleAnnotations,
261                 "method",
262                 className,
263                 methodName,
264                 descriptor
265             )?.let { policy ->
266                 return policy
267             }
268         }
269         return super.getPolicyForMethod(className, methodName, descriptor)
270     }
271 
272     override fun getRenameTo(
273             className: String,
274             methodName: String,
275             descriptor: String
276     ): String? {
277         val cn = classes.getClass(className)
278 
279         // If the method has a "substitute with" annotation, then return its "value" parameter.
280         cn.methods?.firstOrNull { it.name == methodName && it.desc == descriptor }?.let { mn ->
281             return substitutionHelper.getRenameTo(cn, mn.name, mn.desc)
282         }
283         return null
284     }
285 
286     override fun getNativeSubstitutionClass(className: String): String? {
287         classes.getClass(className).let { cn ->
288             findAnyAnnotation(nativeSubstituteAnnotations,
289                     cn.visibleAnnotations, cn.invisibleAnnotations)?.let { an ->
290                 return getAnnotationField(an, "value")?.toJvmClassName()
291             }
292         }
293         return null
294     }
295 
296     override fun getClassLoadHooks(className: String): List<String> {
297         val e = classes.getClass(className).let { cn ->
298             findAnyAnnotation(classLoadHookAnnotations,
299                 cn.visibleAnnotations, cn.invisibleAnnotations)?.let { an ->
300                 getAnnotationField(an, "value")?.toHumanReadableMethodName()
301             }
302         }
303         return addNonNullElement(super.getClassLoadHooks(className), e)
304     }
305 
306     private data class MethodKey(val name: String, val desc: String)
307 
308     /**
309      * In order to handle substitution, we need to build a reverse mapping of substitution
310      * methods.
311      *
312      * This class automatically builds such a map internally that the above methods can
313      * take advantage of.
314      */
315     private inner class SubstitutionHelper {
316         private var currentClass: ClassNode? = null
317 
318         private var policiesFromSubstitution = mutableMapOf<MethodKey, FilterPolicyWithReason>()
319         private var substituteToMethods = mutableMapOf<MethodKey, String>()
320 
321         fun getPolicyFromSubstitution(cn: ClassNode, methodName: String, descriptor: String):
322                 FilterPolicyWithReason? {
323             setClass(cn)
324             return policiesFromSubstitution[MethodKey(methodName, descriptor)]
325         }
326 
327         fun getRenameTo(cn: ClassNode, methodName: String, descriptor: String): String? {
328             setClass(cn)
329             return substituteToMethods[MethodKey(methodName, descriptor)]
330         }
331 
332         /**
333          * Every time we see a different class, we scan all its methods for substitution attributes,
334          * and compute (implicit) policies caused by them.
335          *
336          * For example, for the following methods:
337          *
338          *   @Stub
339          *   @Substitute(suffix = "_host")
340          *   private void foo() {
341          *      // This isn't supported on the host side.
342          *   }
343          *   private void foo_host() {
344          *      // Host side implementation
345          *   }
346          *
347          * We internally handle them as:
348          *
349          *   foo() -> Remove
350          *   foo_host() -> Stub, and then rename it to foo().
351          */
352         private fun setClass(cn: ClassNode) {
353             if (currentClass == cn) {
354                 return
355             }
356             // If the class is changing, we'll rebuild the internal structure.
357             currentClass = cn
358 
359             policiesFromSubstitution.clear()
360             substituteToMethods.clear()
361 
362             for (mn in cn.methods ?: emptyList()) {
363                 findAnyAnnotation(substituteAnnotations,
364                         mn.visibleAnnotations,
365                         mn.invisibleAnnotations)?.let { an ->
366 
367                     // Find the policy for this method.
368                     val policy = outermostFilter.getPolicyForMethod(cn.name, mn.name, mn.desc)
369                             .policy.resolveClassWidePolicy()
370                     // Make sure it's either Stub or Keep.
371                     if (!(policy.needsInStub || policy.needsInImpl)) {
372                         // TODO: Use the real annotation names in the message
373                         errors.onErrorFound("@SubstituteWith must have either @Stub or @Keep")
374                         return@let
375                     }
376                     if (!policy.isUsableWithMethods) {
377                         throw HostStubGenInternalException("Policy $policy shouldn't show up here")
378                     }
379 
380                     val suffix = getAnnotationField(an, "suffix", false) ?: "\$ravenwood"
381                     val renameFrom = mn.name + suffix
382                     val renameTo = mn.name
383 
384                     if (renameFrom == renameTo) {
385                         errors.onErrorFound("@SubstituteWith have a different name")
386                         return@let
387                     }
388 
389                     // This mn has "SubstituteWith". This means,
390                     // 1. Re move the "rename-to" method, so add it to substitutedMethods.
391                     policiesFromSubstitution[MethodKey(renameTo, mn.desc)] =
392                             FilterPolicy.Remove.withReason("substitute-to")
393 
394                     // If the policy is "stub", use "stub".
395                     // Otherwise, it must be "keep" or "throw", but there's no point in using
396                     // "throw", so let's use "keep".
397                     val newPolicy = if (policy.needsInStub) policy else FilterPolicy.Keep
398                     // 2. We also keep the from-to in the map.
399                     policiesFromSubstitution[MethodKey(renameFrom, mn.desc)] =
400                             newPolicy.withReason("substitute-from")
401                     substituteToMethods[MethodKey(renameFrom, mn.desc)] = renameTo
402 
403                     log.v("Substitution found: %s%s -> %s", renameFrom, mn.desc, renameTo)
404                 }
405             }
406         }
407     }
408 
409     /**
410      * Return the (String) value of 'value' parameter from an annotation.
411      */
412     private fun getAnnotationField(an: AnnotationNode, name: String,
413                                    required: Boolean = true): String? {
414         try {
415             val suffix = findAnnotationValueAsString(an, name)
416             if (suffix == null && required) {
417                 errors.onErrorFound("Annotation \"${an.desc}\" must have field $name")
418             }
419             return suffix
420         } catch (e: ClassParseException) {
421             errors.onErrorFound(e.message!!)
422             return null
423         }
424     }
425 
426     companion object {
427         /**
428          * Convert from human-readable type names (e.g. "com.android.TypeName") to the internal type
429          * names (e.g. "Lcom/android/TypeName).
430          */
431         private fun convertToInternalNames(input: Set<String>): Set<String> {
432             val ret = mutableSetOf<String>()
433             input.forEach { ret.add("L" + it.toJvmClassName() + ";") }
434             return ret
435         }
436 
437         /**
438          * Convert from human-readable type names to JVM type names.
439          */
440         private fun convertToJvmNames(input: Set<String>): Set<String> {
441             val ret = mutableSetOf<String>()
442             input.forEach { ret.add(it.toJvmClassName()) }
443             return ret
444         }
445     }
446 }
447