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.ParseException 19 import com.android.hoststubgen.asm.ClassNodes 20 import com.android.hoststubgen.asm.splitWithLastPeriod 21 import com.android.hoststubgen.asm.toHumanReadableClassName 22 import com.android.hoststubgen.asm.toJvmClassName 23 import com.android.hoststubgen.log 24 import com.android.hoststubgen.normalizeTextLine 25 import com.android.hoststubgen.utils.FileOrResource 26 import com.android.hoststubgen.whitespaceRegex 27 import java.io.BufferedReader 28 import java.io.PrintWriter 29 import java.io.Reader 30 import java.util.regex.Pattern 31 import org.objectweb.asm.tree.ClassNode 32 33 /** 34 * Print a class node as a "keep" policy. 35 */ 36 fun printAsTextPolicy(pw: PrintWriter, cn: ClassNode) { 37 pw.printf("class %s %s\n", cn.name.toHumanReadableClassName(), "keep") 38 39 cn.fields?.let { 40 for (f in it.sortedWith(compareBy({ it.name }))) { 41 pw.printf(" field %s %s\n", f.name, "keep") 42 } 43 } 44 cn.methods?.let { 45 for (m in it.sortedWith(compareBy({ it.name }, { it.desc }))) { 46 pw.printf(" method %s %s %s\n", m.name, m.desc, "keep") 47 } 48 } 49 } 50 51 private const val FILTER_REASON = "file-override" 52 53 enum class SpecialClass { 54 NotSpecial, 55 Aidl, 56 FeatureFlags, 57 Sysprops, 58 RFile, 59 } 60 61 data class MethodCallReplaceSpec( 62 val fromClass: String, 63 val fromMethod: String, 64 val fromDescriptor: String, 65 val toClass: String, 66 val toMethod: String, 67 ) 68 69 /** 70 * When a package name matches [typeInternalNamePattern], we prepend [typeInternalNamePrefix] 71 * to it. 72 */ 73 data class TypeRenameSpec( 74 val typeInternalNamePattern: Pattern, 75 val typeInternalNamePrefix: String, 76 ) 77 78 /** 79 * This receives [TextFileFilterPolicyBuilder] parsing result. 80 */ 81 interface PolicyFileProcessor { 82 /** "package" directive. */ onPackagenull83 fun onPackage(name: String, policy: FilterPolicyWithReason) 84 85 /** "rename" directive. */ 86 fun onRename(pattern: Pattern, prefix: String) 87 88 /** "class" directive. */ 89 fun onClassStart(className: String) 90 fun onSimpleClassPolicy(className: String, policy: FilterPolicyWithReason) 91 fun onClassEnd(className: String) 92 93 fun onSubClassPolicy(superClassName: String, policy: FilterPolicyWithReason) 94 fun onRedirectionClass(fromClassName: String, toClassName: String) 95 fun onClassLoadHook(className: String, callback: String) 96 fun onSpecialClassPolicy(type: SpecialClass, policy: FilterPolicyWithReason) 97 98 /** "field" directive. */ 99 fun onField(className: String, fieldName: String, policy: FilterPolicyWithReason) 100 101 /** "method" directive. */ 102 fun onSimpleMethodPolicy( 103 className: String, 104 methodName: String, 105 methodDesc: String, 106 policy: FilterPolicyWithReason, 107 ) 108 fun onMethodInClassReplace( 109 className: String, 110 methodName: String, 111 methodDesc: String, 112 targetName: String, 113 policy: FilterPolicyWithReason, 114 ) 115 fun onMethodOutClassReplace( 116 className: String, 117 methodName: String, 118 methodDesc: String, 119 replaceSpec: MethodCallReplaceSpec, 120 ) 121 } 122 123 class TextFileFilterPolicyBuilder( 124 private val classes: ClassNodes, 125 fallback: OutputFilter 126 ) { 127 private val parser = TextFileFilterPolicyParser() 128 129 private val subclassFilter = SubclassFilter(classes, fallback) 130 private val packageFilter = PackageFilter(subclassFilter) 131 private val imf = InMemoryOutputFilter(classes, packageFilter) 132 private var aidlPolicy: FilterPolicyWithReason? = null 133 private var featureFlagsPolicy: FilterPolicyWithReason? = null 134 private var syspropsPolicy: FilterPolicyWithReason? = null 135 private var rFilePolicy: FilterPolicyWithReason? = null 136 137 /** 138 * Fields for a filter chain used for "partial allowlisting", which are used by 139 * [AnnotationBasedFilter]. 140 */ 141 private val annotationAllowedInMemoryFilter: InMemoryOutputFilter 142 val annotationAllowedMembersFilter: OutputFilter 143 get() = annotationAllowedInMemoryFilter 144 145 private val annotationAllowedPolicy = FilterPolicy.AnnotationAllowed.withReason(FILTER_REASON) 146 147 init { 148 // Create a filter that checks "partial allowlisting". 149 val filter = ConstantFilter(FilterPolicy.Remove, "default disallowed") 150 annotationAllowedInMemoryFilter = InMemoryOutputFilter(classes, filter) 151 } 152 153 /** 154 * Parse a given policy file. This method can be called multiple times to read from 155 * multiple files. To get the resulting filter, use [createOutputFilter] 156 */ 157 fun parse(file: FileOrResource) { 158 // We may parse multiple files, but we reuse the same parser, because the parser 159 // will make sure there'll be no dupplicating "special class" policies. 160 parser.parse(file.open(), file.path, Processor()) 161 } 162 163 /** 164 * Generate the resulting [OutputFilter]. 165 */ 166 fun createOutputFilter(): OutputFilter { 167 // Wrap the in-memory-filter with AHF. 168 return AndroidHeuristicsFilter( 169 classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, rFilePolicy, imf 170 ) 171 } 172 173 private inner class Processor : PolicyFileProcessor { 174 override fun onPackage(name: String, policy: FilterPolicyWithReason) { 175 if (policy.policy == FilterPolicy.AnnotationAllowed) { 176 throw ParseException("${FilterPolicy.AnnotationAllowed.policyStringOrPrefix}" + 177 " on `package` isn't supported yet.") 178 return 179 } 180 packageFilter.addPolicy(name, policy) 181 } 182 183 override fun onRename(pattern: Pattern, prefix: String) { 184 imf.setRemapTypeSpec(TypeRenameSpec(pattern, prefix)) 185 } 186 187 override fun onClassStart(className: String) { 188 } 189 190 override fun onClassEnd(className: String) { 191 } 192 193 override fun onSimpleClassPolicy(className: String, policy: FilterPolicyWithReason) { 194 if (policy.policy == FilterPolicy.AnnotationAllowed) { 195 annotationAllowedInMemoryFilter.setPolicyForClass( 196 className, annotationAllowedPolicy) 197 return 198 } 199 imf.setPolicyForClass(className, policy) 200 } 201 202 override fun onSubClassPolicy( 203 superClassName: String, 204 policy: FilterPolicyWithReason, 205 ) { 206 log.i("class extends $superClassName") 207 subclassFilter.addPolicy( superClassName, policy) 208 } 209 210 override fun onRedirectionClass(fromClassName: String, toClassName: String) { 211 imf.setRedirectionClass(fromClassName, toClassName) 212 } 213 214 override fun onClassLoadHook(className: String, callback: String) { 215 imf.setClassLoadHook(className, callback) 216 } 217 218 override fun onSpecialClassPolicy( 219 type: SpecialClass, 220 policy: FilterPolicyWithReason, 221 ) { 222 log.i("class special $type $policy") 223 when (type) { 224 SpecialClass.NotSpecial -> {} // Shouldn't happen 225 226 SpecialClass.Aidl -> { 227 aidlPolicy = policy 228 } 229 230 SpecialClass.FeatureFlags -> { 231 featureFlagsPolicy = policy 232 } 233 234 SpecialClass.Sysprops -> { 235 syspropsPolicy = policy 236 } 237 238 SpecialClass.RFile -> { 239 rFilePolicy = policy 240 } 241 } 242 } 243 244 override fun onField(className: String, fieldName: String, policy: FilterPolicyWithReason) { 245 imf.setPolicyForField(className, fieldName, policy) 246 } 247 248 override fun onSimpleMethodPolicy( 249 className: String, 250 methodName: String, 251 methodDesc: String, 252 policy: FilterPolicyWithReason, 253 ) { 254 if (policy.policy == FilterPolicy.AnnotationAllowed) { 255 annotationAllowedInMemoryFilter.setPolicyForMethod( 256 className, methodName, methodDesc, annotationAllowedPolicy) 257 return 258 } 259 imf.setPolicyForMethod(className, methodName, methodDesc, policy) 260 } 261 262 override fun onMethodInClassReplace( 263 className: String, 264 methodName: String, 265 methodDesc: String, 266 targetName: String, 267 policy: FilterPolicyWithReason, 268 ) { 269 imf.setPolicyForMethod(className, methodName, methodDesc, policy) 270 271 // Make sure to keep the target method. 272 imf.setPolicyForMethod( 273 className, 274 targetName, 275 methodDesc, 276 FilterPolicy.Keep.withReason(FILTER_REASON) 277 ) 278 // Set up the rename. 279 imf.setRenameTo(className, targetName, methodDesc, methodName) 280 } 281 282 override fun onMethodOutClassReplace( 283 className: String, 284 methodName: String, 285 methodDesc: String, 286 replaceSpec: MethodCallReplaceSpec, 287 ) { 288 // Keep the source method, because the target method may call it. 289 imf.setPolicyForMethod(className, methodName, methodDesc, 290 FilterPolicy.Keep.withReason(FILTER_REASON)) 291 imf.setMethodCallReplaceSpec(replaceSpec) 292 } 293 } 294 } 295 296 /** 297 * Parses a filer policy text file. 298 */ 299 class TextFileFilterPolicyParser { 300 private lateinit var processor: PolicyFileProcessor 301 private var currentClassName: String? = null 302 303 private var aidlPolicy: FilterPolicyWithReason? = null 304 private var featureFlagsPolicy: FilterPolicyWithReason? = null 305 private var syspropsPolicy: FilterPolicyWithReason? = null 306 private var rFilePolicy: FilterPolicyWithReason? = null 307 308 /** Name of the file that's currently being processed. */ 309 var filename: String = "" 310 private set 311 312 /** 1-based line number in the current file */ 313 var lineNumber = -1 314 private set 315 316 /** Current line */ 317 var currentLineText = "" 318 private set 319 320 /** 321 * Parse a given "policy" file. 322 */ parsenull323 fun parse(reader: Reader, inputName: String, processor: PolicyFileProcessor) { 324 filename = inputName 325 326 this.processor = processor 327 BufferedReader(reader).use { rd -> 328 lineNumber = 0 329 try { 330 while (true) { 331 var line = rd.readLine() 332 if (line == null) { 333 break 334 } 335 lineNumber++ 336 currentLineText = line 337 line = normalizeTextLine(line) // Remove comment and trim. 338 if (line.isEmpty()) { 339 continue 340 } 341 parseLine(line) 342 } 343 finishLastClass() 344 } catch (e: ParseException) { 345 throw e.withSourceInfo(inputName, lineNumber) 346 } 347 } 348 } 349 finishLastClassnull350 private fun finishLastClass() { 351 currentClassName?.let { className -> 352 processor.onClassEnd(className) 353 currentClassName = null 354 } 355 } 356 ensureInClassnull357 private fun ensureInClass(directive: String): String { 358 return currentClassName ?: 359 throw ParseException("Directive '$directive' must follow a 'class' directive") 360 } 361 parseLinenull362 private fun parseLine(line: String) { 363 val fields = line.split(whitespaceRegex).toTypedArray() 364 when (fields[0].lowercase()) { 365 "p", "package" -> { 366 finishLastClass() 367 parsePackage(fields) 368 } 369 "c", "class" -> { 370 finishLastClass() 371 parseClass(fields) 372 } 373 "f", "field" -> { 374 ensureInClass("field") 375 parseField(fields) 376 } 377 "m", "method" -> { 378 ensureInClass("method") 379 parseMethod(fields) 380 } 381 "r", "rename" -> { 382 finishLastClass() 383 parseRename(fields) 384 } 385 else -> throw ParseException("Unknown directive \"${fields[0]}\"") 386 } 387 } 388 resolveSpecialClassnull389 private fun resolveSpecialClass(className: String): SpecialClass { 390 if (!className.startsWith(":")) { 391 return SpecialClass.NotSpecial 392 } 393 when (className.lowercase()) { 394 ":aidl" -> return SpecialClass.Aidl 395 ":feature_flags" -> return SpecialClass.FeatureFlags 396 ":sysprops" -> return SpecialClass.Sysprops 397 ":r" -> return SpecialClass.RFile 398 } 399 throw ParseException("Invalid special class name \"$className\"") 400 } 401 resolveExtendingClassnull402 private fun resolveExtendingClass(className: String): String? { 403 if (!className.startsWith("*")) { 404 return null 405 } 406 return className.substring(1) 407 } 408 parsePolicynull409 private fun parsePolicy(s: String): FilterPolicy { 410 return when (s.lowercase()) { 411 "k", FilterPolicy.Keep.policyStringOrPrefix -> FilterPolicy.Keep 412 "t", FilterPolicy.Throw.policyStringOrPrefix -> FilterPolicy.Throw 413 "r", FilterPolicy.Remove.policyStringOrPrefix -> FilterPolicy.Remove 414 "kc", FilterPolicy.KeepClass.policyStringOrPrefix -> FilterPolicy.KeepClass 415 "i", FilterPolicy.Ignore.policyStringOrPrefix -> FilterPolicy.Ignore 416 "rdr", FilterPolicy.Redirect.policyStringOrPrefix -> FilterPolicy.Redirect 417 FilterPolicy.AnnotationAllowed.policyStringOrPrefix -> FilterPolicy.AnnotationAllowed 418 else -> { 419 if (s.startsWith(FilterPolicy.Substitute.policyStringOrPrefix)) { 420 FilterPolicy.Substitute 421 } else { 422 throw ParseException("Invalid policy \"$s\"") 423 } 424 } 425 } 426 } 427 parsePackagenull428 private fun parsePackage(fields: Array<String>) { 429 if (fields.size < 3) { 430 throw ParseException("Package ('p') expects 2 fields.") 431 } 432 val name = fields[1] 433 val rawPolicy = fields[2] 434 if (resolveExtendingClass(name) != null) { 435 throw ParseException("Package can't be a super class type") 436 } 437 if (resolveSpecialClass(name) != SpecialClass.NotSpecial) { 438 throw ParseException("Package can't be a special class type") 439 } 440 if (rawPolicy.startsWith("!")) { 441 throw ParseException("Package can't have a substitution") 442 } 443 if (rawPolicy.startsWith("~")) { 444 throw ParseException("Package can't have a class load hook") 445 } 446 val policy = parsePolicy(rawPolicy) 447 if (!policy.isUsableWithClasses) { 448 throw ParseException("Package can't have policy '$policy'") 449 } 450 processor.onPackage(name, policy.withReason(FILTER_REASON)) 451 } 452 parseClassnull453 private fun parseClass(fields: Array<String>) { 454 if (fields.size <= 1) { 455 throw ParseException("Class ('c') expects 1 or 2 fields.") 456 } 457 val className = fields[1].toHumanReadableClassName() 458 459 // superClass is set when the class name starts with a "*". 460 val superClass = resolveExtendingClass(className) 461 462 // :aidl, etc? 463 val classType = resolveSpecialClass(className) 464 465 val policyStr = if (fields.size > 2) { fields[2] } else { "" } 466 467 if (policyStr.startsWith("!")) { 468 if (classType != SpecialClass.NotSpecial) { 469 // We could support it, but not needed at least for now. 470 throw ParseException( 471 "Special class can't have a substitution" 472 ) 473 } 474 // It's a redirection class. 475 val toClass = policyStr.substring(1) 476 477 currentClassName = className 478 processor.onClassStart(className) 479 processor.onRedirectionClass(className, toClass) 480 } else if (policyStr.startsWith("~")) { 481 if (classType != SpecialClass.NotSpecial) { 482 // We could support it, but not needed at least for now. 483 throw ParseException( 484 "Special class can't have a class load hook" 485 ) 486 } 487 // It's a class-load hook 488 val callback = policyStr.substring(1) 489 490 currentClassName = className 491 processor.onClassStart(className) 492 processor.onClassLoadHook(className, callback) 493 } else { 494 // Special case: if it's a class directive with no policy, then it encloses 495 // members, but we don't apply any policy to the class itself. 496 // This is only allowed in a not-special case. 497 if (policyStr == "") { 498 if (classType == SpecialClass.NotSpecial && superClass == null) { 499 currentClassName = className 500 return 501 } 502 throw ParseException("Special class or subclass directive must have a policy") 503 } 504 505 val policy = parsePolicy(policyStr) 506 if (!policy.isUsableWithClasses) { 507 throw ParseException("Class can't have policy '$policy'") 508 } 509 510 when (classType) { 511 SpecialClass.NotSpecial -> { 512 // TODO: Duplicate check, etc 513 if (superClass == null) { 514 currentClassName = className 515 processor.onClassStart(className) 516 processor.onSimpleClassPolicy(className, policy.withReason(FILTER_REASON)) 517 } else { 518 processor.onSubClassPolicy( 519 superClass, 520 policy.withReason("extends $superClass"), 521 ) 522 } 523 } 524 SpecialClass.Aidl -> { 525 if (aidlPolicy != null) { 526 throw ParseException( 527 "Policy for AIDL classes already defined" 528 ) 529 } 530 val p = policy.withReason( 531 "$FILTER_REASON (special-class AIDL)", 532 StatsLabel.SupportedButBoring, 533 ) 534 processor.onSpecialClassPolicy(classType, p) 535 aidlPolicy = p 536 } 537 538 SpecialClass.FeatureFlags -> { 539 if (featureFlagsPolicy != null) { 540 throw ParseException( 541 "Policy for feature flags already defined" 542 ) 543 } 544 val p = policy.withReason( 545 "$FILTER_REASON (special-class feature flags)", 546 StatsLabel.SupportedButBoring, 547 ) 548 processor.onSpecialClassPolicy(classType, p) 549 featureFlagsPolicy = p 550 } 551 552 SpecialClass.Sysprops -> { 553 if (syspropsPolicy != null) { 554 throw ParseException( 555 "Policy for sysprops already defined" 556 ) 557 } 558 val p = policy.withReason( 559 "$FILTER_REASON (special-class sysprops)", 560 StatsLabel.SupportedButBoring, 561 ) 562 processor.onSpecialClassPolicy(classType, p) 563 syspropsPolicy = p 564 } 565 566 SpecialClass.RFile -> { 567 if (rFilePolicy != null) { 568 throw ParseException( 569 "Policy for R file already defined" 570 ) 571 } 572 val p = policy.withReason( 573 "$FILTER_REASON (special-class R file)", 574 StatsLabel.SupportedButBoring, 575 ) 576 processor.onSpecialClassPolicy(classType, p) 577 rFilePolicy = p 578 } 579 } 580 } 581 } 582 parseFieldnull583 private fun parseField(fields: Array<String>) { 584 if (fields.size < 3) { 585 throw ParseException("Field ('f') expects 2 fields.") 586 } 587 val name = fields[1] 588 val policy = parsePolicy(fields[2]) 589 if (!policy.isUsableWithFields) { 590 throw ParseException("Field can't have policy '$policy'") 591 } 592 593 // TODO: Duplicate check, etc 594 processor.onField(currentClassName!!, name, policy.withReason(FILTER_REASON)) 595 } 596 parseMethodnull597 private fun parseMethod(fields: Array<String>) { 598 if (fields.size < 3 || fields.size > 4) { 599 throw ParseException("Method ('m') expects 3 or 4 fields.") 600 } 601 val methodName = fields[1] 602 val signature: String 603 val policyStr: String 604 if (fields.size <= 3) { 605 signature = "*" 606 policyStr = fields[2] 607 } else { 608 signature = fields[2] 609 policyStr = fields[3] 610 } 611 612 val policy = parsePolicy(policyStr) 613 614 if (!policy.isUsableWithMethods) { 615 throw ParseException("Method can't have policy '$policy'") 616 } 617 618 val className = currentClassName!! 619 620 val policyWithReason = policy.withReason(FILTER_REASON) 621 if (policy != FilterPolicy.Substitute) { 622 processor.onSimpleMethodPolicy(className, methodName, signature, policyWithReason) 623 } else { 624 val targetName = policyStr.substring(1) 625 626 if (targetName == methodName) { 627 throw ParseException( 628 "Substitution must have a different name" 629 ) 630 } 631 632 val classAndMethod = splitWithLastPeriod(targetName) 633 if (classAndMethod != null) { 634 // If the substitution target contains a ".", then 635 // it's a method call redirect. 636 val spec = MethodCallReplaceSpec( 637 className.toJvmClassName(), 638 methodName, 639 signature, 640 classAndMethod.first.toJvmClassName(), 641 classAndMethod.second, 642 ) 643 processor.onMethodOutClassReplace( 644 className, 645 methodName, 646 signature, 647 spec, 648 ) 649 } else { 650 // It's an in-class replace. 651 // ("@RavenwoodReplace" equivalent) 652 processor.onMethodInClassReplace( 653 className, 654 methodName, 655 signature, 656 targetName, 657 policyWithReason, 658 ) 659 } 660 } 661 } 662 parseRenamenull663 private fun parseRename(fields: Array<String>) { 664 if (fields.size < 3) { 665 throw ParseException("Rename ('r') expects 2 fields.") 666 } 667 // Add ".*" to make it a prefix match. 668 val pattern = Pattern.compile(fields[1] + ".*") 669 670 // Removing the leading /'s from the prefix. This allows 671 // using a single '/' as an empty suffix, which is useful to have a 672 // "negative" rename rule to avoid subsequent raname's from getting 673 // applied. (Which is needed for services.jar) 674 val prefix = fields[2].trimStart('/') 675 676 processor.onRename( 677 pattern, prefix 678 ) 679 } 680 } 681