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