• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.InvalidAnnotationException
21 import com.android.hoststubgen.addLists
22 import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC
23 import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
24 import com.android.hoststubgen.asm.ClassNodes
25 import com.android.hoststubgen.asm.findAllAnnotations
26 import com.android.hoststubgen.asm.findAnnotationValueAsString
27 import com.android.hoststubgen.asm.findAnyAnnotation
28 import com.android.hoststubgen.asm.getPackageNameFromFullClassName
29 import com.android.hoststubgen.asm.resolveClassNameWithDefaultPackage
30 import com.android.hoststubgen.asm.toHumanReadableClassName
31 import com.android.hoststubgen.asm.toHumanReadableMethodName
32 import com.android.hoststubgen.asm.toJvmClassName
33 import com.android.hoststubgen.log
34 import com.android.hoststubgen.utils.ClassPredicate
35 import org.objectweb.asm.tree.AnnotationNode
36 import org.objectweb.asm.tree.ClassNode
37 
38 // TODO: Detect invalid cases, such as...
39 // - Class's visibility is lower than the members'.
40 
41 /**
42  * [OutputFilter] using Java annotations.
43  */
44 class AnnotationBasedFilter(
45     private val errors: HostStubGenErrors,
46     private val classes: ClassNodes,
47     keepAnnotations_: Set<String>,
48     keepClassAnnotations_: Set<String>,
49     throwAnnotations_: Set<String>,
50     removeAnnotations_: Set<String>,
51     ignoreAnnotations_: Set<String>,
52     substituteAnnotations_: Set<String>,
53     redirectAnnotations_: Set<String>,
54     redirectionClassAnnotations_: Set<String>,
55     classLoadHookAnnotations_: Set<String>,
56     partiallyAllowlistedClassAnnotations_: Set<String>,
57     keepStaticInitializerAnnotations_: Set<String>,
58     private val annotationAllowedClassesFilter: ClassPredicate,
59     fallback: OutputFilter,
60 ) : DelegatingFilter(fallback) {
61 
62     /**
63      * This is a filter chain to check if an entity (class/member) has a "allow-annotation"
64      * policy.
65      */
66     var annotationAllowedMembers: OutputFilter =
67         ConstantFilter(FilterPolicy.Remove, "default disallowed")
68 
69     private val keepAnnotations = convertToInternalNames(keepAnnotations_)
70     private val keepClassAnnotations = convertToInternalNames(keepClassAnnotations_)
71     private val throwAnnotations = convertToInternalNames(throwAnnotations_)
72     private val removeAnnotations = convertToInternalNames(removeAnnotations_)
73     private val ignoreAnnotations = convertToInternalNames(ignoreAnnotations_)
74     private val redirectAnnotations = convertToInternalNames(redirectAnnotations_)
75     private val substituteAnnotations = convertToInternalNames(substituteAnnotations_)
76     private val redirectionClassAnnotations =
77         convertToInternalNames(redirectionClassAnnotations_)
78     private val classLoadHookAnnotations = convertToInternalNames(classLoadHookAnnotations_)
79     private val partiallyAllowlistedClassAnnotations =
80         convertToInternalNames(partiallyAllowlistedClassAnnotations_)
81 
82     private val keepStaticInitializerAnnotations =
83         convertToInternalNames(keepStaticInitializerAnnotations_)
84 
85     /** Annotations that control API visibility. */
86     private val visibilityAnnotations = keepAnnotations +
87             keepClassAnnotations +
88             throwAnnotations +
89             removeAnnotations +
90             ignoreAnnotations +
91             redirectAnnotations +
92             substituteAnnotations
93 
94     /**
95      * Annotations that require "fully" allowlisting.
96      */
97     private val allowlistRequiringAnnotations = visibilityAnnotations +
98             redirectionClassAnnotations +
99             classLoadHookAnnotations +
100             keepStaticInitializerAnnotations
101             // partiallyAllowlistedClassAnnotations // This is excluded.
102 
103     /**
104      * We always keep these types.
105      *
106      * Note, this one is in a [convertToJvmNames] format unlike other ones, because of how it's
107      * used.
108      */
109     private val alwaysKeepClasses: Set<String> = convertToJvmNames(
110         keepAnnotations_ +
111                 keepClassAnnotations_ +
112                 throwAnnotations_ +
113                 removeAnnotations_ +
114                 redirectAnnotations_ +
115                 substituteAnnotations_ +
116                 redirectionClassAnnotations_ +
117                 classLoadHookAnnotations_ +
118                 partiallyAllowlistedClassAnnotations_ +
119                 keepStaticInitializerAnnotations_
120     )
121 
122     private val policyCache = mutableMapOf<String, ClassAnnotations>()
123 
124     private val AnnotationNode.policy: FilterPolicyWithReason? get() {
125         return when (desc) {
126             in keepAnnotations -> FilterPolicy.Keep.withReason(REASON_ANNOTATION)
127             in keepClassAnnotations -> FilterPolicy.KeepClass.withReason(REASON_CLASS_ANNOTATION)
128             in substituteAnnotations -> FilterPolicy.Substitute.withReason(REASON_ANNOTATION)
129             in throwAnnotations -> FilterPolicy.Throw.withReason(REASON_ANNOTATION)
130             in removeAnnotations -> FilterPolicy.Remove.withReason(REASON_ANNOTATION)
131             in ignoreAnnotations -> FilterPolicy.Ignore.withReason(REASON_ANNOTATION)
132             in redirectAnnotations -> FilterPolicy.Redirect.withReason(REASON_ANNOTATION)
133             else -> null
134         }
135     }
136 
137     private fun getAnnotationPolicy(cn: ClassNode): ClassAnnotations {
138         return policyCache.getOrPut(cn.name) { ClassAnnotations(cn) }
139     }
140 
141     override fun getPolicyForClass(className: String): FilterPolicyWithReason {
142         // If it's any of the annotations, then always keep it.
143         if (alwaysKeepClasses.contains(className)) {
144             return FilterPolicy.KeepClass.withReason("HostStubGen Annotation")
145         }
146 
147         val cn = classes.getClass(className)
148         return getAnnotationPolicy(cn).classPolicy ?: super.getPolicyForClass(className)
149     }
150 
151     override fun getPolicyForField(className: String, fieldName: String): FilterPolicyWithReason {
152         val cn = classes.getClass(className)
153         return getAnnotationPolicy(cn).fieldPolicies[fieldName]
154             ?: super.getPolicyForField(className, fieldName)
155     }
156 
157     override fun getPolicyForMethod(
158         className: String,
159         methodName: String,
160         descriptor: String
161     ): FilterPolicyWithReason {
162         val cn = classes.getClass(className)
163         return getAnnotationPolicy(cn).methodPolicies[MethodKey(methodName, descriptor)]
164             ?: super.getPolicyForMethod(className, methodName, descriptor)
165     }
166 
167     override fun getRenameTo(
168         className: String,
169         methodName: String,
170         descriptor: String
171     ): String? {
172         val cn = classes.getClass(className)
173         return getAnnotationPolicy(cn).renamedMethods[MethodKey(methodName, descriptor)]
174             ?: super.getRenameTo(className, methodName, descriptor)
175     }
176 
177     override fun getRedirectionClass(className: String): String? {
178         val cn = classes.getClass(className)
179         return getAnnotationPolicy(cn).redirectionClass
180     }
181 
182     override fun getClassLoadHooks(className: String): List<String> {
183         val cn = classes.getClass(className)
184         return addLists(super.getClassLoadHooks(className), getAnnotationPolicy(cn).classLoadHooks)
185     }
186 
187     private data class MethodKey(val name: String, val desc: String)
188 
189     /**
190      * Every time we see a class, we scan all its methods for substitution attributes,
191      * and compute (implicit) policies caused by them.
192      *
193      * For example, for the following methods:
194      *
195      *   @Substitute(suffix = "_host")
196      *   private void foo() {
197      *      // This isn't supported on the host side.
198      *   }
199      *   private void foo_host() {
200      *      // Host side implementation
201      *   }
202      *
203      * We internally handle them as:
204      *
205      *   foo() -> Substitute
206      *   foo_host() -> Stub, and then rename it to foo().
207      */
208     private inner class ClassAnnotations(cn: ClassNode) {
209 
210         val classPolicy: FilterPolicyWithReason?
211         val fieldPolicies = mutableMapOf<String, FilterPolicyWithReason>()
212         val methodPolicies = mutableMapOf<MethodKey, FilterPolicyWithReason>()
213         val renamedMethods = mutableMapOf<MethodKey, String>()
214         val redirectionClass: String?
215         val classLoadHooks: List<String>
216 
217         init {
218             // First, check if the class has "partially-allowed" policy.
219             // This filter chain contains
220             val annotationPartiallyAllowedClass =
221                 annotationAllowedMembers.getPolicyForClass(cn.name).policy ==
222                         FilterPolicy.AnnotationAllowed
223 
224             // If a class is partially-allowlisted, then it's not fully-allowlisted.
225             // Otherwise, just use annotationAllowedClassesFilter.
226             val fullyAllowAnnotation = !annotationPartiallyAllowedClass &&
227                 annotationAllowedClassesFilter.matches(cn.name)
228             detectInvalidAnnotations(isClass = true,
229                 cn.name, fullyAllowAnnotation, annotationPartiallyAllowedClass,
230                 annotationPartiallyAllowedClass,
231                 cn.visibleAnnotations, cn.invisibleAnnotations,
232                 "class", cn.name
233             )
234 
235             val classAnnot = cn.findAnyAnnotation(visibilityAnnotations)
236             classPolicy = classAnnot?.policy
237 
238             classPolicy?.let { policy ->
239                 if (policy.policy.isClassWide && annotationPartiallyAllowedClass) {
240                     errors.onErrorFound("Class ${cn.name.toHumanReadableClassName()}" +
241                             " has class wide annotation" +
242                             " ${classAnnot?.desc?.toHumanReadableClassName()}" +
243                             ", which can't be used in a partially-allowlisted class")
244                 }
245             }
246             redirectionClass = cn.findAnyAnnotation(redirectionClassAnnotations)?.let { an ->
247                 getAnnotationField(an, "value")?.let { resolveRelativeClass(cn, it) }
248             }
249             classLoadHooks = cn.findAllAnnotations(classLoadHookAnnotations).mapNotNull { an ->
250                 getAnnotationField(an, "value")?.toHumanReadableMethodName()
251             }
252             if (cn.findAnyAnnotation(keepStaticInitializerAnnotations) != null) {
253                 methodPolicies[MethodKey(CLASS_INITIALIZER_NAME, CLASS_INITIALIZER_DESC)] =
254                     FilterPolicy.Keep.withReason(REASON_ANNOTATION)
255             }
256 
257             for (fn in cn.fields ?: emptyList()) {
258                 val partiallyAllowAnnotation = false // No partial allowlisting on fields (yet)
259                 detectInvalidAnnotations(isClass = false,
260                     cn.name, fullyAllowAnnotation, partiallyAllowAnnotation,
261                     annotationPartiallyAllowedClass,
262                     fn.visibleAnnotations, fn.invisibleAnnotations,
263                     "field", cn.name, fn.name
264                 )
265                 fn.findAnyAnnotation(visibilityAnnotations)?.policy?.let {
266                     fieldPolicies[fn.name] = it
267                 }
268             }
269 
270             for (mn in cn.methods ?: emptyList()) {
271                 val partiallyAllowAnnotation =
272                     annotationAllowedMembers.getPolicyForMethod(cn.name, mn.name, mn.desc).policy ==
273                             FilterPolicy.AnnotationAllowed
274                 detectInvalidAnnotations(isClass = false,
275                     cn.name, fullyAllowAnnotation, partiallyAllowAnnotation,
276                     annotationPartiallyAllowedClass,
277                     mn.visibleAnnotations, mn.invisibleAnnotations,
278                     "method", cn.name, mn.name, mn.desc
279                 )
280 
281                 val an = mn.findAnyAnnotation(visibilityAnnotations) ?: continue
282                 val policy = an.policy ?: continue
283                 methodPolicies[MethodKey(mn.name, mn.desc)] = policy
284 
285                 if (policy.policy != FilterPolicy.Substitute) continue
286 
287                 // Handle substitution
288                 val suffix = getAnnotationField(an, "suffix", false) ?: "\$ravenwood"
289                 val replacement = mn.name + suffix
290 
291                 if (replacement == mn.name) {
292                     errors.onErrorFound("@SubstituteWith require a different name")
293                 } else {
294                     // The replacement method has to be renamed
295                     methodPolicies[MethodKey(replacement, mn.desc)] =
296                         FilterPolicy.Keep.withReason(REASON_ANNOTATION)
297                     renamedMethods[MethodKey(replacement, mn.desc)] = mn.name
298 
299                     log.v("Substitution found: %s%s -> %s", replacement, mn.desc, mn.name)
300                 }
301             }
302         }
303 
304         /**
305          * Throw if an item has more than one visibility annotations, or the class is not allowed
306          *
307          * name1 - 4 are only used in exception messages. We take them as separate strings
308          * to avoid unnecessary string concatenations.
309          */
310         private fun detectInvalidAnnotations(
311             isClass: Boolean,
312             className: String,
313             fullyAllowAnnotation: Boolean,
314             partiallyAllowAnnotation: Boolean,
315             classPartiallyAllowAnnotation: Boolean,
316             visibles: List<AnnotationNode>?,
317             invisibles: List<AnnotationNode>?,
318             type: String,
319             name1: String,
320             name2: String = "",
321             name3: String = "",
322         ) {
323             // Lazily create the description.
324             val desc = { getItemDescription(type, name1, name2, name3) }
325 
326             val partiallyAllowlistAnnotation =
327                 findAnyAnnotation(partiallyAllowlistedClassAnnotations, visibles, invisibles)
328             partiallyAllowlistAnnotation?.let { anot ->
329                 if (!partiallyAllowAnnotation) {
330                     errors.onErrorFound(desc() +
331                             " has annotation ${anot.desc?.toHumanReadableClassName()}, but" +
332                             " doesn't have" +
333                             " '${FilterPolicy.AnnotationAllowed.policyStringOrPrefix}' policy.'")
334                 }
335             }
336             var count = 0
337             var visibleCount = 0
338             for (an in visibles ?: emptyList()) {
339                 if (visibilityAnnotations.contains(an.desc)) {
340                     visibleCount++
341                 }
342                 if (allowlistRequiringAnnotations.contains(an.desc)) {
343                     count++
344                 }
345             }
346             for (an in invisibles ?: emptyList()) {
347                 if (visibilityAnnotations.contains(an.desc)) {
348                     visibleCount++
349                 }
350                 if (allowlistRequiringAnnotations.contains(an.desc)) {
351                     count++
352                 }
353             }
354             // Special case -- if it's a class, and has an "allow-annotation" policy
355             // *and* if it actually has an annotation, then it must have the
356             // "PartiallyAllowlisted" annotation.
357             // Conversely, even if it has an "allow-annotation" policy, it's okay
358             // if it doesn't have the annotation, as long as it doesn't have any
359             // annotations.
360             if (isClass && count > 0 && partiallyAllowAnnotation) {
361                 if (partiallyAllowlistAnnotation == null) {
362                     val requiredAnnot = partiallyAllowlistedClassAnnotations.firstOrNull()
363                     throw InvalidAnnotationException(
364                         "${desc()} must have ${requiredAnnot?.toHumanReadableClassName()} to use" +
365                                 " annotations")
366                 }
367             }
368 
369             if (count > 0 && !(fullyAllowAnnotation || partiallyAllowAnnotation)) {
370                 val extInfo = if (classPartiallyAllowAnnotation) {
371                     " (Class is partially allowlisted.)"
372                 } else {""}
373                 throw InvalidAnnotationException(
374                     "${desc()} is not allowed to have " +
375                             "Ravenwood annotations.$extInfo Contact g/ravenwood for more details."
376                 )
377             }
378             if (visibleCount > 1) {
379                 throw InvalidAnnotationException(
380                     "Found more than one visibility annotations on ${desc()}"
381                 )
382             }
383         }
384 
385         private fun getItemDescription(
386             type: String,
387             name1: String,
388             name2: String,
389             name3: String,
390         ): String {
391             return if (name2 == "" && name3 == "") {
392                 "$type $name1"
393             } else {
394                 "$type $name1.$name2$name3"
395             }
396         }
397 
398         /**
399          * Return the (String) value of 'value' parameter from an annotation.
400          */
401         private fun getAnnotationField(
402             an: AnnotationNode,
403             name: String,
404             required: Boolean = true
405         ): String? {
406             try {
407                 val suffix = findAnnotationValueAsString(an, name)
408                 if (suffix == null && required) {
409                     errors.onErrorFound("Annotation \"${an.desc}\" must have field $name")
410                 }
411                 return suffix
412             } catch (e: ClassParseException) {
413                 errors.onErrorFound(e.message!!)
414                 return null
415             }
416         }
417 
418         /**
419          * Resolve the full class name if the class is relative
420          */
421         private fun resolveRelativeClass(
422             cn: ClassNode,
423             name: String
424         ): String {
425             val packageName = getPackageNameFromFullClassName(cn.name)
426             return resolveClassNameWithDefaultPackage(name, packageName).toJvmClassName()
427         }
428     }
429 
430     companion object {
431         private const val REASON_ANNOTATION = "annotation"
432         private const val REASON_CLASS_ANNOTATION = "class-annotation"
433 
434         /**
435          * Convert from human-readable type names (e.g. "com.android.TypeName") to the internal type
436          * names (e.g. "Lcom/android/TypeName).
437          */
438         private fun convertToInternalNames(input: Set<String>): Set<String> {
439             val ret = mutableSetOf<String>()
440             input.forEach { ret.add("L" + it.toJvmClassName() + ";") }
441             return ret
442         }
443 
444         /**
445          * Convert from human-readable type names to JVM type names.
446          */
447         private fun convertToJvmNames(input: Set<String>): Set<String> {
448             val ret = mutableSetOf<String>()
449             input.forEach { ret.add(it.toJvmClassName()) }
450             return ret
451         }
452     }
453 }
454