blob: 3650fa159e72ccd46cb9955dab5c9189c7294ebc [file] [log] [blame]
Paul Duffinb5cd5222022-02-28 19:06:49 +00001#!/usr/bin/env python
2#
3# Copyright (C) 2022 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16"""Verify that one set of hidden API flags is a subset of another."""
Paul Duffin1f8a6b22022-03-08 16:31:55 +000017import dataclasses
18import typing
Paul Duffinb5cd5222022-02-28 19:06:49 +000019
20from itertools import chain
21
22
Paul Duffin1f8a6b22022-03-08 16:31:55 +000023@dataclasses.dataclass()
24class Node:
Paul Duffin92532e72022-03-09 14:28:34 +000025 """A node in the signature trie."""
26
27 # The type of the node.
28 #
29 # Leaf nodes are of type "member".
30 # Interior nodes can be either "package", or "class".
31 type: str
32
33 # The selector of the node.
34 #
35 # That is a string that can be used to select the node, e.g. in a pattern
36 # that is passed to InteriorNode.get_matching_rows().
37 selector: str
Paul Duffin1f8a6b22022-03-08 16:31:55 +000038
39 def values(self, selector):
40 """Get the values from a set of selected nodes.
41
42 :param selector: a function that can be applied to a key in the nodes
43 attribute to determine whether to return its values.
44
45 :return: A list of iterables of all the values associated with
46 this node and its children.
47 """
Paul Duffindbbb8372022-04-01 15:04:23 +010048 values = []
49 self.append_values(values, selector)
50 return values
Paul Duffin1f8a6b22022-03-08 16:31:55 +000051
52 def append_values(self, values, selector):
53 """Append the values associated with this node and its children.
54
55 For each item (key, child) in nodes the child node's values are returned
56 if and only if the selector returns True when called on its key. A child
57 node's values are all the values associated with it and all its
58 descendant nodes.
59
60 :param selector: a function that can be applied to a key in the nodes
61 attribute to determine whether to return its values.
62 :param values: a list of a iterables of values.
63 """
64 raise NotImplementedError("Please Implement this method")
65
Paul Duffin92532e72022-03-09 14:28:34 +000066 def child_nodes(self):
67 """Get an iterable of the child nodes of this node."""
68 raise NotImplementedError("Please Implement this method")
69
Paul Duffin1f8a6b22022-03-08 16:31:55 +000070
Paul Duffinb5cd5222022-02-28 19:06:49 +000071# pylint: disable=line-too-long
Paul Duffin1f8a6b22022-03-08 16:31:55 +000072@dataclasses.dataclass()
73class InteriorNode(Node):
Paul Duffinb5cd5222022-02-28 19:06:49 +000074 """An interior node in a trie.
75
76 Each interior node has a dict that maps from an element of a signature to
77 either another interior node or a leaf. Each interior node represents either
78 a package, class or nested class. Class members are represented by a Leaf.
79
80 Associating the set of flags [public-api] with the signature
81 "Ljava/lang/Object;->String()Ljava/lang/String;" will cause the following
82 nodes to be created:
83 Node()
84 ^- package:java -> Node()
85 ^- package:lang -> Node()
86 ^- class:Object -> Node()
87 ^- member:String()Ljava/lang/String; -> Leaf([public-api])
88
89 Associating the set of flags [blocked,core-platform-api] with the signature
90 "Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;"
91 will cause the following nodes to be created:
92 Node()
93 ^- package:java -> Node()
94 ^- package:lang -> Node()
95 ^- class:Character -> Node()
96 ^- class:UnicodeScript -> Node()
97 ^- member:of(I)Ljava/lang/Character$UnicodeScript;
98 -> Leaf([blocked,core-platform-api])
Paul Duffinb5cd5222022-02-28 19:06:49 +000099 """
100
101 # pylint: enable=line-too-long
102
Paul Duffin1f8a6b22022-03-08 16:31:55 +0000103 # A dict from an element of the signature to the Node/Leaf containing the
104 # next element/value.
105 nodes: typing.Dict[str, Node] = dataclasses.field(default_factory=dict)
Paul Duffinb5cd5222022-02-28 19:06:49 +0000106
107 # pylint: disable=line-too-long
108 @staticmethod
109 def signature_to_elements(signature):
110 """Split a signature or a prefix into a number of elements:
111
112 1. The packages (excluding the leading L preceding the first package).
113 2. The class names, from outermost to innermost.
114 3. The member signature.
115 e.g.
116 Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;
117 will be broken down into these elements:
118 1. package:java
119 2. package:lang
120 3. class:Character
121 4. class:UnicodeScript
122 5. member:of(I)Ljava/lang/Character$UnicodeScript;
123 """
124 # Remove the leading L.
125 # - java/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;
126 text = signature.removeprefix("L")
127 # Split the signature between qualified class name and the class member
128 # signature.
129 # 0 - java/lang/Character$UnicodeScript
130 # 1 - of(I)Ljava/lang/Character$UnicodeScript;
131 parts = text.split(";->")
Paul Duffin19255f12022-03-08 16:35:52 +0000132 # If there is no member then this will be an empty list.
Paul Duffinb5cd5222022-02-28 19:06:49 +0000133 member = parts[1:]
134 # Split the qualified class name into packages, and class name.
135 # 0 - java
136 # 1 - lang
137 # 2 - Character$UnicodeScript
138 elements = parts[0].split("/")
Paul Duffin19255f12022-03-08 16:35:52 +0000139 last_element = elements[-1]
140 wildcard = []
141 classes = []
142 if "*" in last_element:
143 if last_element not in ("*", "**"):
144 raise Exception(f"Invalid signature '{signature}': invalid "
145 f"wildcard '{last_element}'")
146 packages = elements[0:-1]
Paul Duffinb5cd5222022-02-28 19:06:49 +0000147 # Cannot specify a wildcard and target a specific member
Paul Duffin19255f12022-03-08 16:35:52 +0000148 if member:
149 raise Exception(f"Invalid signature '{signature}': contains "
150 f"wildcard '{last_element}' and "
151 f"member signature '{member[0]}'")
152 wildcard = [last_element]
153 elif last_element.islower():
154 raise Exception(f"Invalid signature '{signature}': last element "
155 f"'{last_element}' is lower case but should be an "
156 f"upper case class name or wildcard")
Paul Duffinb5cd5222022-02-28 19:06:49 +0000157 else:
Paul Duffin19255f12022-03-08 16:35:52 +0000158 packages = elements[0:-1]
Paul Duffinb5cd5222022-02-28 19:06:49 +0000159 # Split the class name into outer / inner classes
160 # 0 - Character
161 # 1 - UnicodeScript
Paul Duffin19255f12022-03-08 16:35:52 +0000162 classes = last_element.removesuffix(";").split("$")
163
164 # Assemble the parts into a single list, adding prefixes to identify
165 # the different parts. If a wildcard is provided then it looks something
166 # like this:
167 # 0 - package:java
168 # 1 - package:lang
169 # 2 - *
170 #
171 # Otherwise, it looks something like this:
172 # 0 - package:java
173 # 1 - package:lang
174 # 2 - class:Character
175 # 3 - class:UnicodeScript
176 # 4 - member:of(I)Ljava/lang/Character$UnicodeScript;
177 return list(
Paul Duffinea935422022-03-09 14:51:17 +0000178 chain([("package", x) for x in packages],
179 [("class", x) for x in classes],
180 [("member", x) for x in member],
181 [("wildcard", x) for x in wildcard]))
Paul Duffinb5cd5222022-02-28 19:06:49 +0000182
183 # pylint: enable=line-too-long
184
Paul Duffin19255f12022-03-08 16:35:52 +0000185 @staticmethod
186 def split_element(element):
Paul Duffinea935422022-03-09 14:51:17 +0000187 element_type, element_value = element
Paul Duffin19255f12022-03-08 16:35:52 +0000188 return element_type, element_value
189
190 @staticmethod
191 def element_type(element):
192 element_type, _ = InteriorNode.split_element(element)
193 return element_type
194
Paul Duffin92532e72022-03-09 14:28:34 +0000195 @staticmethod
196 def elements_to_selector(elements):
197 """Compute a selector for a set of elements.
198
199 A selector uniquely identifies a specific Node in the trie. It is
200 essentially a prefix of a signature (without the leading L).
201
202 e.g. a trie containing "Ljava/lang/Object;->String()Ljava/lang/String;"
203 would contain nodes with the following selectors:
204 * "java"
205 * "java/lang"
206 * "java/lang/Object"
207 * "java/lang/Object;->String()Ljava/lang/String;"
208 """
209 signature = ""
210 preceding_type = ""
211 for element in elements:
212 element_type, element_value = InteriorNode.split_element(element)
213 separator = ""
214 if element_type == "package":
215 separator = "/"
216 elif element_type == "class":
217 if preceding_type == "class":
218 separator = "$"
219 else:
220 separator = "/"
221 elif element_type == "wildcard":
222 separator = "/"
223 elif element_type == "member":
224 separator += ";->"
225
226 if signature:
227 signature += separator
228
229 signature += element_value
230
231 preceding_type = element_type
232
233 return signature
234
235 def add(self, signature, value, only_if_matches=False):
Paul Duffinb5cd5222022-02-28 19:06:49 +0000236 """Associate the value with the specific signature.
237
238 :param signature: the member signature
239 :param value: the value to associated with the signature
Paul Duffin92532e72022-03-09 14:28:34 +0000240 :param only_if_matches: True if the value is added only if the signature
241 matches at least one of the existing top level packages.
Paul Duffinb5cd5222022-02-28 19:06:49 +0000242 :return: n/a
243 """
244 # Split the signature into elements.
245 elements = self.signature_to_elements(signature)
246 # Find the Node associated with the deepest class.
247 node = self
Paul Duffin92532e72022-03-09 14:28:34 +0000248 for index, element in enumerate(elements[:-1]):
Paul Duffinb5cd5222022-02-28 19:06:49 +0000249 if element in node.nodes:
250 node = node.nodes[element]
Paul Duffin92532e72022-03-09 14:28:34 +0000251 elif only_if_matches and index == 0:
252 return
Paul Duffinb5cd5222022-02-28 19:06:49 +0000253 else:
Paul Duffin92532e72022-03-09 14:28:34 +0000254 selector = self.elements_to_selector(elements[0:index + 1])
255 next_node = InteriorNode(
256 type=InteriorNode.element_type(element), selector=selector)
Paul Duffinb5cd5222022-02-28 19:06:49 +0000257 node.nodes[element] = next_node
258 node = next_node
259 # Add a Leaf containing the value and associate it with the member
260 # signature within the class.
261 last_element = elements[-1]
Paul Duffin19255f12022-03-08 16:35:52 +0000262 last_element_type = self.element_type(last_element)
263 if last_element_type != "member":
Paul Duffinb5cd5222022-02-28 19:06:49 +0000264 raise Exception(
265 f"Invalid signature: {signature}, does not identify a "
266 "specific member")
267 if last_element in node.nodes:
268 raise Exception(f"Duplicate signature: {signature}")
Paul Duffin92532e72022-03-09 14:28:34 +0000269 leaf = Leaf(
270 type=last_element_type,
271 selector=signature,
272 value=value,
273 )
274 node.nodes[last_element] = leaf
Paul Duffinb5cd5222022-02-28 19:06:49 +0000275
276 def get_matching_rows(self, pattern):
277 """Get the values (plural) associated with the pattern.
278
279 e.g. If the pattern is a full signature then this will return a list
280 containing the value associated with that signature.
281
282 If the pattern is a class then this will return a list containing the
283 values associated with all members of that class.
284
Paul Duffinb5cd5222022-02-28 19:06:49 +0000285 If the pattern ends with "*" then the preceding part is treated as a
286 package and this will return a list containing the values associated
287 with all the members of all the classes in that package.
288
289 If the pattern ends with "**" then the preceding part is treated
290 as a package and this will return a list containing the values
291 associated with all the members of all the classes in that package and
292 all sub-packages.
293
294 :param pattern: the pattern which could be a complete signature or a
295 class, or package wildcard.
296 :return: an iterable containing all the values associated with the
297 pattern.
298 """
299 elements = self.signature_to_elements(pattern)
300 node = self
301
302 # Include all values from this node and all its children.
303 selector = lambda x: True
304
305 last_element = elements[-1]
Paul Duffin19255f12022-03-08 16:35:52 +0000306 last_element_type, last_element_value = self.split_element(last_element)
307 if last_element_type == "wildcard":
Paul Duffinb5cd5222022-02-28 19:06:49 +0000308 elements = elements[:-1]
Paul Duffin19255f12022-03-08 16:35:52 +0000309 if last_element_value == "*":
Paul Duffinb5cd5222022-02-28 19:06:49 +0000310 # Do not include values from sub-packages.
Paul Duffin19255f12022-03-08 16:35:52 +0000311 selector = lambda x: InteriorNode.element_type(x) != "package"
Paul Duffinb5cd5222022-02-28 19:06:49 +0000312
313 for element in elements:
314 if element in node.nodes:
315 node = node.nodes[element]
316 else:
317 return []
Paul Duffinb5cd5222022-02-28 19:06:49 +0000318
Paul Duffindbbb8372022-04-01 15:04:23 +0100319 return node.values(selector)
Paul Duffinb5cd5222022-02-28 19:06:49 +0000320
321 def append_values(self, values, selector):
Paul Duffinb5cd5222022-02-28 19:06:49 +0000322 for key, node in self.nodes.items():
323 if selector(key):
324 node.append_values(values, lambda x: True)
325
Paul Duffin92532e72022-03-09 14:28:34 +0000326 def child_nodes(self):
327 return self.nodes.values()
328
Paul Duffinb5cd5222022-02-28 19:06:49 +0000329
Paul Duffin1f8a6b22022-03-08 16:31:55 +0000330@dataclasses.dataclass()
331class Leaf(Node):
332 """A leaf of the trie"""
Paul Duffinb5cd5222022-02-28 19:06:49 +0000333
Paul Duffin1f8a6b22022-03-08 16:31:55 +0000334 # The value associated with this leaf.
335 value: typing.Any
Paul Duffinb5cd5222022-02-28 19:06:49 +0000336
Paul Duffin1f8a6b22022-03-08 16:31:55 +0000337 def append_values(self, values, selector):
Paul Duffindbbb8372022-04-01 15:04:23 +0100338 values.append(self.value)
Paul Duffinb5cd5222022-02-28 19:06:49 +0000339
Paul Duffin92532e72022-03-09 14:28:34 +0000340 def child_nodes(self):
341 return []
342
Paul Duffinb5cd5222022-02-28 19:06:49 +0000343
344def signature_trie():
Paul Duffin92532e72022-03-09 14:28:34 +0000345 return InteriorNode(type="root", selector="")