• 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.utils
17 
18 import com.android.hoststubgen.ParseException
19 import com.android.hoststubgen.asm.toHumanReadableClassName
20 import com.android.hoststubgen.asm.toJvmClassName
21 import com.android.hoststubgen.normalizeTextLine
22 import java.io.File
23 
24 /**
25  * General purpose class "selector", which returns a boolean for a given class name.
26  *
27  * (It's used to check if a class is in the "annotations allowed classes" allowlist.)
28  */
29 class ClassPredicate private constructor(
30     private val defaultResult: Boolean,
31 ) {
32     private enum class MatchType {
33         Full,
34         Prefix,
35         Suffix,
36     }
37 
38     private class FilterElement(
39         val allowed: Boolean,
40         val internalName: String,
41         val matchType: MatchType,
42     ) {
43         fun matches(classInternalName: String): Boolean {
44             return when (matchType) {
45                 MatchType.Full -> classInternalName == internalName
46                 MatchType.Prefix -> classInternalName.startsWith(internalName)
47                 MatchType.Suffix -> classInternalName.endsWith(internalName)
48             }
49         }
50     }
51 
52     private val elements: MutableList<FilterElement> = mutableListOf()
53 
54     private val cache: MutableMap<String, Boolean> = mutableMapOf()
55 
56     /**
57      * Takes an internal class name (e.g. "com/android/hoststubgen/ClassName") and returns if
58      * matches the filter or not.
59      */
60     fun matches(classInternalName: String): Boolean {
61         cache[classInternalName]?.let {
62             return it
63         }
64 
65         val testClasses = sequence {
66             // Yield itself and its outer class(es) one by one
67             var idx = classInternalName.length
68             while (idx > 0) {
69                 yield(classInternalName.substring(0, idx))
70                 idx = classInternalName.lastIndexOf('$', idx - 1)
71             }
72         }
73 
74         val result = elements.find { testClasses.any(it::matches) }?.allowed ?: defaultResult
75         cache[classInternalName] = result
76 
77         return result
78     }
79 
80     fun getCacheSizeForTest(): Int {
81         return cache.size
82     }
83 
84     companion object {
85         /**
86          * Return a filter that always returns true or false.
87          */
88         fun newConstantPredicate(defaultResult: Boolean): ClassPredicate {
89             return ClassPredicate(defaultResult)
90         }
91 
92         /** Build a filter from a file. */
93         fun loadFromFile(filename: String, defaultResult: Boolean): ClassPredicate {
94             return buildFromString(File(filename).readText(), defaultResult, filename)
95         }
96 
97         /** Build a filter from a string (for unit tests). */
98         fun buildFromString(
99             filterString: String,
100             defaultResult: Boolean,
101             filenameForErrorMessage: String
102         ): ClassPredicate {
103             val ret = ClassPredicate(defaultResult)
104 
105             var lineNo = 0
106             filterString.split('\n').forEach { s ->
107                 lineNo++
108 
109                 var line = normalizeTextLine(s)
110 
111                 if (line.isEmpty()) {
112                     return@forEach // skip empty lines.
113                 }
114 
115                 line = line.toHumanReadableClassName() // Convert all the slashes to periods.
116 
117                 var allow = true
118                 if (line.startsWith("!")) {
119                     allow = false
120                     line = line.substring(1).trimStart()
121                 }
122 
123                 // Special case -- matches any class names.
124                 if (line == "*") {
125                     ret.elements.add(FilterElement(allow, "", MatchType.Prefix))
126                     return@forEach
127                 }
128 
129                 // Handle prefix match -- e.g. "package.name.*"
130                 if (line.endsWith(".*")) {
131                     ret.elements.add(
132                         FilterElement(
133                             allow,
134                             line.substring(0, line.length - 2).toJvmClassName() + "/",
135                             MatchType.Prefix
136                         )
137                     )
138                     return@forEach
139                 }
140 
141                 // Handle suffix match -- e.g. "*.Flags"
142                 if (line.startsWith("*.")) {
143                     ret.elements.add(
144                         FilterElement(
145                             allow,
146                             "/" + line.substring(2, line.length).toJvmClassName(),
147                             MatchType.Suffix
148                         )
149                     )
150                     return@forEach
151                 }
152 
153                 // Any other uses of "*" would be an error.
154                 if (line.contains('*')) {
155                     throw ParseException(
156                         "Wildcard (*) can only show up as the last element",
157                         filenameForErrorMessage,
158                         lineNo
159                     )
160                 }
161                 ret.elements.add(FilterElement(allow, line.toJvmClassName(), MatchType.Suffix))
162             }
163 
164             return ret
165         }
166     }
167 }
168