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