• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2025 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.platform.test.ravenwood.ravenhelper.policytoannot
17 
18 import com.android.hoststubgen.LogLevel
19 import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
20 import com.android.hoststubgen.asm.toJvmClassName
21 import com.android.hoststubgen.filters.FilterPolicyWithReason
22 import com.android.hoststubgen.filters.MethodCallReplaceSpec
23 import com.android.hoststubgen.filters.PolicyFileProcessor
24 import com.android.hoststubgen.filters.SpecialClass
25 import com.android.hoststubgen.filters.TextFileFilterPolicyParser
26 import com.android.hoststubgen.log
27 import com.android.hoststubgen.utils.ClassPredicate
28 import com.android.platform.test.ravenwood.ravenhelper.SubcommandHandler
29 import com.android.platform.test.ravenwood.ravenhelper.psi.createUastEnvironment
30 import com.android.platform.test.ravenwood.ravenhelper.sourcemap.AllClassInfo
31 import com.android.platform.test.ravenwood.ravenhelper.sourcemap.ClassInfo
32 import com.android.platform.test.ravenwood.ravenhelper.sourcemap.MethodInfo
33 import com.android.platform.test.ravenwood.ravenhelper.sourcemap.SourceLoader
34 import java.io.FileReader
35 import java.util.regex.Pattern
36 
37 /**
38  * This is the main routine of the "pta" -- policy-to-annotation -- subcommands.
39  */
40 class PtaProcessor : SubcommandHandler {
41     override fun handle(args: List<String>) {
42         val options = PtaOptions().apply { parseArgs(args) }
43 
44         log.v("Options: $options")
45 
46         val converter = TextPolicyToAnnotationConverter(
47             options.policyOverrideFiles,
48             options.sourceFilesOrDirectories,
49             options.annotationAllowedClassesFile.get,
50             Annotations(),
51             options.dumpOperations.get || log.isEnabled(LogLevel.Debug),
52         )
53         converter.process()
54 
55         createShellScript(converter.resultOperations, options.outputScriptFile.get)
56     }
57 }
58 
59 /**
60  * This class implements the actual logic.
61  */
62 private class TextPolicyToAnnotationConverter(
63     val policyFiles: List<String>,
64     val sourceFilesOrDirectories: List<String>,
65     val annotationAllowedClassesFile: String?,
66     val annotations: Annotations,
67     val dumpOperations: Boolean,
68 ) {
filenull69     private val annotationAllowedClasses: ClassPredicate = annotationAllowedClassesFile.let { file ->
70         if (file == null) {
71             ClassPredicate.newConstantPredicate(true) // Allow all classes
72         } else {
73             ClassPredicate.loadFromFile(file, false)
74         }
75     }
76 
77     val resultOperations = SourceOperations()
78     private val classes = AllClassInfo()
79     private val policyParser = TextFileFilterPolicyParser()
80     private val annotationNeedingClasses = mutableSetOf<String>()
81 
82     /**
83      * Entry point.
84      */
processnull85     fun process() {
86         // First, load
87         val env = createUastEnvironment()
88         try {
89             loadSources()
90 
91             processPolicies()
92 
93             addToAnnotationsAllowedListFile()
94 
95             if (dumpOperations) {
96                 log.withIndent {
97                     resultOperations.getOperations().toSortedMap().forEach { (file, ops) ->
98                         log.i("ops: $file")
99                         ops.forEach { op ->
100                             log.i("  line: ${op.lineNumber}: ${op.type}: \"${op.text}\" " +
101                                     "(${op.description})")
102                         }
103                     }
104                 }
105             }
106         } finally {
107             env.dispose()
108         }
109     }
110 
111     /**
112      * Load all the java source files into [classes].
113      */
loadSourcesnull114     private fun loadSources() {
115         val env = createUastEnvironment()
116         try {
117             val loader = SourceLoader(env)
118             loader.load(sourceFilesOrDirectories, classes)
119         } finally {
120             env.dispose()
121         }
122     }
123 
addToAnnotationsAllowedListFilenull124     private fun addToAnnotationsAllowedListFile() {
125         log.i("Generating operations to update annotation allowlist file...")
126         log.withIndent {
127             annotationNeedingClasses.sorted().forEach { className ->
128                 if (!annotationAllowedClasses.matches(className.toJvmClassName())) {
129                     resultOperations.add(
130                         SourceOperation(
131                             annotationAllowedClassesFile!!,
132                             -1, // add to the end
133                             SourceOperationType.Insert,
134                             className,
135                             "add to annotation allowlist"
136                         ))
137                 }
138             }
139         }
140     }
141 
142     /**
143      * Process the policy files with [Processor].
144      */
processPoliciesnull145     private fun processPolicies() {
146         log.i("Loading the policy files and generating operations...")
147         log.withIndent {
148             policyFiles.forEach { policyFile ->
149                 log.i("Parsing $policyFile ...")
150                 log.withIndent {
151                     policyParser.parse(FileReader(policyFile), policyFile, Processor())
152                 }
153             }
154         }
155     }
156 
157     private inner class Processor : PolicyFileProcessor {
158 
159         var classPolicyText = ""
160         var classPolicyLine = -1
161 
162         // Whether the current class has a skip marker, in which case we ignore all members.
163         // Applicable only within a "simple class"
164         var classSkipping = false
165 
166         var classLineConverted = false
167         var classHasMember = false
168 
currentLineHasSkipMarkernull169         private fun currentLineHasSkipMarker(): Boolean {
170             val ret = policyParser.currentLineText.contains("no-pta")
171 
172             if (ret) {
173                 log.forVerbose {
174                     log.v("Current line has a skip marker: ${policyParser.currentLineText}")
175                 }
176             }
177 
178             return ret
179         }
180 
shouldSkipCurrentLinenull181         private fun shouldSkipCurrentLine(): Boolean {
182             // If a line contains a special marker "no-pta", we'll skip it.
183             return classSkipping || currentLineHasSkipMarker()
184         }
185 
186         /** Print a warning about an unsupported policy directive. */
warnOnPolicynull187         private fun warnOnPolicy(message: String, policyLine: String, lineNumber: Int) {
188             log.w("Warning: $message")
189             log.w("  policy: \"$policyLine\"")
190             log.w("  at ${policyParser.filename}:$lineNumber")
191         }
192 
193         /** Print a warning about an unsupported policy directive. */
warnOnCurrentPolicynull194         private fun warnOnCurrentPolicy(message: String) {
195             warnOnPolicy(message, policyParser.currentLineText, policyParser.lineNumber)
196         }
197 
198         /** Print a warning about an unsupported policy directive on the class line. */
warnOnClassPolicynull199         private fun warnOnClassPolicy(message: String) {
200             warnOnPolicy(message, classPolicyText, classPolicyLine)
201         }
202 
onPackagenull203         override fun onPackage(name: String, policy: FilterPolicyWithReason) {
204             warnOnCurrentPolicy("'package' directive isn't supported (yet).")
205         }
206 
onRenamenull207         override fun onRename(pattern: Pattern, prefix: String) {
208             // Rename will never be supported, so don't show a warning.
209         }
210 
addOperationnull211         private fun addOperation(op: SourceOperation) {
212             resultOperations.add(op)
213         }
214 
commentOutPolicynull215         private fun commentOutPolicy(lineNumber: Int, description: String) {
216             addOperation(
217                 SourceOperation(
218                     policyParser.filename,
219                     lineNumber,
220                     SourceOperationType.Prepend,
221                     "#[PTA]: ", // comment out.
222                     description,
223                 )
224             )
225         }
226 
onClassStartnull227         override fun onClassStart(className: String) {
228             classSkipping = currentLineHasSkipMarker()
229             classLineConverted = false
230             classHasMember = false
231             classPolicyLine = policyParser.lineNumber
232             classPolicyText = policyParser.currentLineText
233         }
234 
onClassEndnull235         override fun onClassEnd(className: String) {
236             if (classSkipping) {
237                 classSkipping = false
238                 return
239             }
240             if (!classLineConverted) {
241                 // Class line is still needed in the policy file.
242                 // (Because the source file wasn't found.)
243                 return
244             }
245             if (!classHasMember) {
246                 commentOutPolicy(classPolicyLine, "remove class policy on $className")
247             } else {
248                 warnOnClassPolicy(
249                     "Class policy on $className can't be removed because it still has members.")
250             }
251         }
252 
findClassnull253         private fun findClass(className: String): ClassInfo? {
254             val ci = classes.findClass(className)
255             if (ci == null) {
256                 warnOnCurrentPolicy("Class not found: $className")
257             }
258             return ci
259         }
260 
addClassAnnotationnull261         private fun addClassAnnotation(
262             className: String,
263             annotation: String,
264         ): Boolean {
265             val ci = findClass(className) ?: return false
266 
267             // Add the annotation to the source file.
268             addOperation(
269                 SourceOperation(
270                     ci.location.file,
271                     ci.location.line,
272                     SourceOperationType.Insert,
273                     ci.location.getIndent() + annotation,
274                     "add class annotation to $className"
275                 )
276             )
277             annotationNeedingClasses.add(className)
278             return true
279         }
280 
onSimpleClassPolicynull281         override fun onSimpleClassPolicy(className: String, policy: FilterPolicyWithReason) {
282             if (shouldSkipCurrentLine()) {
283                 return
284             }
285             log.v("Found simple class policy: $className - ${policy.policy}")
286 
287             val annot = annotations.get(policy.policy, Annotations.Target.Class)!!
288             if (addClassAnnotation(className, annot)) {
289                 classLineConverted = true
290             }
291         }
292 
onSubClassPolicynull293         override fun onSubClassPolicy(superClassName: String, policy: FilterPolicyWithReason) {
294             warnOnCurrentPolicy("Subclass policies isn't supported (yet).")
295         }
296 
onRedirectionClassnull297         override fun onRedirectionClass(fromClassName: String, toClassName: String) {
298             if (shouldSkipCurrentLine()) {
299                 return
300             }
301 
302             log.v("Found class redirection: $fromClassName - $toClassName")
303 
304             if (addClassAnnotation(
305                     fromClassName,
306                     annotations.getRedirectionClassAnnotation(toClassName),
307                 )) {
308                 commentOutPolicy(policyParser.lineNumber,
309                     "remove class redirection policy on $fromClassName")
310             }
311         }
312 
onClassLoadHooknull313         override fun onClassLoadHook(className: String, callback: String) {
314             if (shouldSkipCurrentLine()) {
315                 return
316             }
317 
318             log.v("Found class load hook: $className - $callback")
319 
320             if (addClassAnnotation(
321                     className,
322                     annotations.getClassLoadHookAnnotation(callback),
323                 )) {
324                 commentOutPolicy(policyParser.lineNumber,
325                     "remove class load hook policy on $className")
326             }
327         }
328 
onSpecialClassPolicynull329         override fun onSpecialClassPolicy(type: SpecialClass, policy: FilterPolicyWithReason) {
330             // This can't be converted to an annotation, so don't show a warning.
331         }
332 
onFieldnull333         override fun onField(className: String, fieldName: String, policy: FilterPolicyWithReason) {
334             if (shouldSkipCurrentLine()) {
335                 return
336             }
337 
338             log.v("Found field policy: $className.$fieldName - ${policy.policy}")
339 
340             val ci = findClass(className) ?: return
341 
342             ci.findField(fieldName)?.let { fi ->
343                 val annot = annotations.get(policy.policy, Annotations.Target.Field)!!
344 
345                 addOperation(
346                     SourceOperation(
347                         fi.location.file,
348                         fi.location.line,
349                         SourceOperationType.Insert,
350                         fi.location.getIndent() + annot,
351                         "add annotation to field $className.$fieldName",
352                     )
353                 )
354                 commentOutPolicy(policyParser.lineNumber,
355                     "remove field policy $className.$fieldName")
356 
357                 annotationNeedingClasses.add(className)
358             } ?: {
359                 warnOnCurrentPolicy("Field not found: $className.$fieldName")
360             }
361         }
362 
onSimpleMethodPolicynull363         override fun onSimpleMethodPolicy(
364             className: String,
365             methodName: String,
366             methodDesc: String,
367             policy: FilterPolicyWithReason
368         ) {
369             if (shouldSkipCurrentLine()) {
370                 return
371             }
372             val readableName = "$className.$methodName$methodDesc"
373             log.v("Found simple method policy: $readableName - ${policy.policy}")
374 
375 
376             // Inner method to get the matching methods for this policy.
377             //
378             // If this policy can't be converted for any reason, it'll return null.
379             // Otherwise, it'll return a pair of method list and the annotation string.
380             fun getMethods(): Pair<List<MethodInfo>, String>? {
381                 if (methodName == CLASS_INITIALIZER_NAME) {
382                     warnOnClassPolicy("Policy for class initializers not supported.")
383                     return null
384                 }
385                 val ci = findClass(className) ?: return null
386                 val methods = ci.findMethods(methodName, methodDesc)
387                 if (methods == null) {
388                     warnOnCurrentPolicy("Method not found: $readableName")
389                     return null
390                 }
391 
392                 // If the policy is "ignore", we can't convert it to an annotation, in which case
393                 // annotations.get() will return null.
394                 val annot = annotations.get(policy.policy, Annotations.Target.Method)
395                 if (annot == null) {
396                     warnOnCurrentPolicy("Annotation for policy '${policy.policy}' isn't available")
397                     return null
398                 }
399                 return Pair(methods, annot)
400             }
401 
402             val methodsAndAnnot = getMethods()
403 
404             if (methodsAndAnnot == null) {
405                 classHasMember = true
406                 return // This policy can't be converted.
407             }
408             val methods = methodsAndAnnot.first
409             val annot = methodsAndAnnot.second
410 
411             var found = false
412             methods.forEach { mi ->
413                 found = true
414                 addOperation(
415                     SourceOperation(
416                         mi.location.file,
417                         mi.location.line,
418                         SourceOperationType.Insert,
419                         mi.location.getIndent() + annot,
420                         "add annotation to method $readableName",
421                     )
422                 )
423             }
424             if (found) {
425                 commentOutPolicy(
426                     policyParser.lineNumber,
427                     "remove method policy $readableName"
428                 )
429 
430                 annotationNeedingClasses.add(className)
431             } else {
432                 warnOnCurrentPolicy("Method not found: $readableName")
433             }
434         }
435 
onMethodInClassReplacenull436         override fun onMethodInClassReplace(
437             className: String,
438             methodName: String,
439             methodDesc: String,
440             targetName: String,
441             policy: FilterPolicyWithReason
442         ) {
443             warnOnCurrentPolicy("Found method replace but it's not supported yet: "
444                 + "$className.$methodName$methodDesc - $targetName")
445         }
446 
onMethodOutClassReplacenull447         override fun onMethodOutClassReplace(
448             className: String,
449             methodName: String,
450             methodDesc: String,
451             replaceSpec: MethodCallReplaceSpec,
452         ) {
453             // This can't be converted to an annotation.
454             classHasMember = true
455         }
456     }
457 }
458