• 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.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