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