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.toHumanReadableClassName
21 import com.android.hoststubgen.log
22 import com.android.hoststubgen.normalizeTextLine
23 import com.android.hoststubgen.whitespaceRegex
24 import org.objectweb.asm.Opcodes
25 import org.objectweb.asm.tree.ClassNode
26 import java.io.BufferedReader
27 import java.io.FileReader
28 import java.io.PrintWriter
29 import java.util.Objects
30 
31 /**
32  * Print a class node as a "keep" policy.
33  */
34 fun printAsTextPolicy(pw: PrintWriter, cn: ClassNode) {
35     pw.printf("class %s %s\n", cn.name.toHumanReadableClassName(), "keep")
36 
37     cn.fields?.let {
38         for (f in it.sortedWith(compareBy({ it.name }))) {
39             pw.printf("    field %s %s\n", f.name, "keep")
40         }
41     }
42     cn.methods?.let {
43         for (m in it.sortedWith(compareBy({ it.name }, { it.desc }))) {
44             pw.printf("    method %s %s %s\n", m.name, m.desc, "keep")
45         }
46     }
47 }
48 
49 /** Return true if [access] is either public or protected. */
isVisiblenull50 private fun isVisible(access: Int): Boolean {
51     return (access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED)) != 0
52 }
53 
54 private const val FILTER_REASON = "file-override"
55 
56 /**
57  * Read a given "policy" file and return as an [OutputFilter]
58  */
createFilterFromTextPolicyFilenull59 fun createFilterFromTextPolicyFile(
60         filename: String,
61         classes: ClassNodes,
62         fallback: OutputFilter,
63         ): OutputFilter {
64     log.i("Loading offloaded annotations from $filename ...")
65     log.withIndent {
66         val subclassFilter = SubclassFilter(classes, fallback)
67         val imf = InMemoryOutputFilter(classes, subclassFilter)
68 
69         var lineNo = 0
70 
71         var aidlPolicy: FilterPolicyWithReason? = null
72         var featureFlagsPolicy: FilterPolicyWithReason? = null
73         var syspropsPolicy: FilterPolicyWithReason? = null
74         var rFilePolicy: FilterPolicyWithReason? = null
75 
76         try {
77             BufferedReader(FileReader(filename)).use { reader ->
78                 var className = ""
79 
80                 while (true) {
81                     var line = reader.readLine()
82                     if (line == null) {
83                         break
84                     }
85                     lineNo++
86 
87                     line = normalizeTextLine(line)
88 
89                     if (line.isEmpty()) {
90                         continue // skip empty lines.
91                     }
92 
93 
94                     // TODO: Method too long, break it up.
95 
96                     val fields = line.split(whitespaceRegex).toTypedArray()
97                     when (fields[0].lowercase()) {
98                         "c", "class" -> {
99                             if (fields.size < 3) {
100                                 throw ParseException("Class ('c') expects 2 fields.")
101                             }
102                             className = fields[1]
103 
104                             // superClass is set when the class name starts with a "*".
105                             val superClass = resolveExtendingClass(className)
106 
107                             // :aidl, etc?
108                             val classType = resolveSpecialClass(className)
109 
110                             if (fields[2].startsWith("!")) {
111                                 if (classType != SpecialClass.NotSpecial) {
112                                     // We could support it, but not needed at least for now.
113                                     throw ParseException(
114                                             "Special class can't have a substitution")
115                                 }
116                                 // It's a native-substitution.
117                                 val toClass = fields[2].substring(1)
118                                 imf.setNativeSubstitutionClass(className, toClass)
119                             } else if (fields[2].startsWith("~")) {
120                                 if (classType != SpecialClass.NotSpecial) {
121                                     // We could support it, but not needed at least for now.
122                                     throw ParseException(
123                                             "Special class can't have a class load hook")
124                                 }
125                                 // It's a class-load hook
126                                 val callback = fields[2].substring(1)
127                                 imf.setClassLoadHook(className, callback)
128                             } else {
129                                 val policy = parsePolicy(fields[2])
130                                 if (!policy.isUsableWithClasses) {
131                                     throw ParseException("Class can't have policy '$policy'")
132                                 }
133                                 Objects.requireNonNull(className)
134 
135                                 when (classType) {
136                                     SpecialClass.NotSpecial -> {
137                                         // TODO: Duplicate check, etc
138                                         if (superClass == null) {
139                                             imf.setPolicyForClass(
140                                                 className, policy.withReason(FILTER_REASON)
141                                             )
142                                         } else {
143                                             subclassFilter.addPolicy(superClass,
144                                                 policy.withReason("extends $superClass"))
145                                         }
146                                     }
147                                     SpecialClass.Aidl -> {
148                                         if (aidlPolicy != null) {
149                                             throw ParseException(
150                                                     "Policy for AIDL classes already defined")
151                                         }
152                                         aidlPolicy = policy.withReason(
153                                                 "$FILTER_REASON (special-class AIDL)")
154                                     }
155                                     SpecialClass.FeatureFlags -> {
156                                         if (featureFlagsPolicy != null) {
157                                             throw ParseException(
158                                                     "Policy for feature flags already defined")
159                                         }
160                                         featureFlagsPolicy = policy.withReason(
161                                                 "$FILTER_REASON (special-class feature flags)")
162                                     }
163                                     SpecialClass.Sysprops -> {
164                                         if (syspropsPolicy != null) {
165                                             throw ParseException(
166                                                     "Policy for sysprops already defined")
167                                         }
168                                         syspropsPolicy = policy.withReason(
169                                                 "$FILTER_REASON (special-class sysprops)")
170                                     }
171                                     SpecialClass.RFile -> {
172                                         if (rFilePolicy != null) {
173                                             throw ParseException(
174                                                 "Policy for R file already defined")
175                                         }
176                                         rFilePolicy = policy.withReason(
177                                             "$FILTER_REASON (special-class R file)")
178                                     }
179                                 }
180                             }
181                         }
182 
183                         "f", "field" -> {
184                             if (fields.size < 3) {
185                                 throw ParseException("Field ('f') expects 2 fields.")
186                             }
187                             val name = fields[1]
188                             val policy = parsePolicy(fields[2])
189                             if (!policy.isUsableWithFields) {
190                                 throw ParseException("Field can't have policy '$policy'")
191                             }
192                             Objects.requireNonNull(className)
193 
194                             // TODO: Duplicate check, etc
195                             imf.setPolicyForField(className, name, policy.withReason(FILTER_REASON))
196                         }
197 
198                         "m", "method" -> {
199                             if (fields.size < 4) {
200                                 throw ParseException("Method ('m') expects 3 fields.")
201                             }
202                             val name = fields[1]
203                             val signature = fields[2]
204                             val policy = parsePolicy(fields[3])
205 
206                             if (!policy.isUsableWithMethods) {
207                                 throw ParseException("Method can't have policy '$policy'")
208                             }
209 
210                             Objects.requireNonNull(className)
211 
212                             imf.setPolicyForMethod(className, name, signature,
213                                     policy.withReason(FILTER_REASON))
214                             if (policy.isSubstitute) {
215                                 val fromName = fields[3].substring(1)
216 
217                                 if (fromName == name) {
218                                     throw ParseException(
219                                             "Substitution must have a different name")
220                                 }
221 
222                                 // Set the policy  for the "from" method.
223                                 imf.setPolicyForMethod(className, fromName, signature,
224                                         policy.getSubstitutionBasePolicy()
225                                                 .withReason(FILTER_REASON))
226 
227                                 // Keep "from" -> "to" mapping.
228                                 imf.setRenameTo(className, fromName, signature, name)
229                             }
230                         }
231 
232                         else -> {
233                             throw ParseException("Unknown directive \"${fields[0]}\"")
234                         }
235                     }
236                 }
237             }
238         } catch (e: ParseException) {
239             throw e.withSourceInfo(filename, lineNo)
240         }
241 
242         // Wrap the in-memory-filter with AHF.
243         return AndroidHeuristicsFilter(
244                 classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, rFilePolicy, imf)
245     }
246 }
247 
248 private enum class SpecialClass {
249     NotSpecial,
250     Aidl,
251     FeatureFlags,
252     Sysprops,
253     RFile,
254 }
255 
resolveSpecialClassnull256 private fun resolveSpecialClass(className: String): SpecialClass {
257     if (!className.startsWith(":")) {
258         return SpecialClass.NotSpecial
259     }
260     when (className.lowercase()) {
261         ":aidl" -> return SpecialClass.Aidl
262         ":feature_flags" -> return SpecialClass.FeatureFlags
263         ":sysprops" -> return SpecialClass.Sysprops
264         ":r" -> return SpecialClass.RFile
265     }
266     throw ParseException("Invalid special class name \"$className\"")
267 }
268 
resolveExtendingClassnull269 private fun resolveExtendingClass(className: String): String? {
270     if (!className.startsWith("*")) {
271         return null
272     }
273     return className.substring(1)
274 }
275 
parsePolicynull276 private fun parsePolicy(s: String): FilterPolicy {
277     return when (s.lowercase()) {
278         "s", "stub" -> FilterPolicy.Stub
279         "k", "keep" -> FilterPolicy.Keep
280         "t", "throw" -> FilterPolicy.Throw
281         "r", "remove" -> FilterPolicy.Remove
282         "sc", "stubclass" -> FilterPolicy.StubClass
283         "kc", "keepclass" -> FilterPolicy.KeepClass
284         else -> {
285             if (s.startsWith("@")) {
286                 FilterPolicy.SubstituteAndStub
287             } else if (s.startsWith("%")) {
288                 FilterPolicy.SubstituteAndKeep
289             } else {
290                 throw ParseException("Invalid policy \"$s\"")
291             }
292         }
293     }
294 }
295