blob: e813a97816d0e21301e9baf06e770b37c63b06a7 [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 """
48 raise NotImplementedError("Please Implement this method")
49
50 def append_values(self, values, selector):
51 """Append the values associated with this node and its children.
52
53 For each item (key, child) in nodes the child node's values are returned
54 if and only if the selector returns True when called on its key. A child
55 node's values are all the values associated with it and all its
56 descendant nodes.
57
58 :param selector: a function that can be applied to a key in the nodes
59 attribute to determine whether to return its values.
60 :param values: a list of a iterables of values.
61 """
62 raise NotImplementedError("Please Implement this method")
63
Paul Duffin92532e72022-03-09 14:28:34 +000064 def child_nodes(self):
65 """Get an iterable of the child nodes of this node."""
66 raise NotImplementedError("Please Implement this method")
67
Paul Duffin1f8a6b22022-03-08 16:31:55 +000068
Paul Duffinb5cd5222022-02-28 19:06:49 +000069# pylint: disable=line-too-long
Paul Duffin1f8a6b22022-03-08 16:31:55 +000070@dataclasses.dataclass()
71class InteriorNode(Node):
Paul Duffinb5cd5222022-02-28 19:06:49 +000072 """An interior node in a trie.
73
74 Each interior node has a dict that maps from an element of a signature to
75 either another interior node or a leaf. Each interior node represents either
76 a package, class or nested class. Class members are represented by a Leaf.
77
78 Associating the set of flags [public-api] with the signature
79 "Ljava/lang/Object;->String()Ljava/lang/String;" will cause the following
80 nodes to be created:
81 Node()
82 ^- package:java -> Node()
83 ^- package:lang -> Node()
84 ^- class:Object -> Node()
85 ^- member:String()Ljava/lang/String; -> Leaf([public-api])
86
87 Associating the set of flags [blocked,core-platform-api] with the signature
88 "Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;"
89 will cause the following nodes to be created:
90 Node()
91 ^- package:java -> Node()
92 ^- package:lang -> Node()
93 ^- class:Character -> Node()
94 ^- class:UnicodeScript -> Node()
95 ^- member:of(I)Ljava/lang/Character$UnicodeScript;
96 -> Leaf([blocked,core-platform-api])
Paul Duffinb5cd5222022-02-28 19:06:49 +000097 """
98
99 # pylint: enable=line-too-long
100
Paul Duffin1f8a6b22022-03-08 16:31:55 +0000101 # A dict from an element of the signature to the Node/Leaf containing the
102 # next element/value.
103 nodes: typing.Dict[str, Node] = dataclasses.field(default_factory=dict)
Paul Duffinb5cd5222022-02-28 19:06:49 +0000104
105 # pylint: disable=line-too-long
106 @staticmethod
107 def signature_to_elements(signature):
108 """Split a signature or a prefix into a number of elements:
109
110 1. The packages (excluding the leading L preceding the first package).
111 2. The class names, from outermost to innermost.
112 3. The member signature.
113 e.g.
114 Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;
115 will be broken down into these elements:
116 1. package:java
117 2. package:lang
118 3. class:Character
119 4. class:UnicodeScript
120 5. member:of(I)Ljava/lang/Character$UnicodeScript;
121 """
122 # Remove the leading L.
123 # - java/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;
124 text = signature.removeprefix("L")
125 # Split the signature between qualified class name and the class member
126 # signature.
127 # 0 - java/lang/Character$UnicodeScript
128 # 1 - of(I)Ljava/lang/Character$UnicodeScript;
129 parts = text.split(";->")
Paul Duffin19255f12022-03-08 16:35:52 +0000130 # If there is no member then this will be an empty list.
Paul Duffinb5cd5222022-02-28 19:06:49 +0000131 member = parts[1:]
132 # Split the qualified class name into packages, and class name.
133 # 0 - java
134 # 1 - lang
135 # 2 - Character$UnicodeScript
136 elements = parts[0].split("/")
Paul Duffin19255f12022-03-08 16:35:52 +0000137 last_element = elements[-1]
138 wildcard = []
139 classes = []
140 if "*" in last_element:
141 if last_element not in ("*", "**"):
142 raise Exception(f"Invalid signature '{signature}': invalid "
143 f"wildcard '{last_element}'")
144 packages = elements[0:-1]
Paul Duffinb5cd5222022-02-28 19:06:49 +0000145 # Cannot specify a wildcard and target a specific member
Paul Duffin19255f12022-03-08 16:35:52 +0000146 if member:
147 raise Exception(f"Invalid signature '{signature}': contains "
148 f"wildcard '{last_element}' and "
149 f"member signature '{member[0]}'")
150 wildcard = [last_element]
151 elif last_element.islower():
152 raise Exception(f"Invalid signature '{signature}': last element "
153 f"'{last_element}' is lower case but should be an "
154 f"upper case class name or wildcard")
Paul Duffinb5cd5222022-02-28 19:06:49 +0000155 else:
Paul Duffin19255f12022-03-08 16:35:52 +0000156 packages = elements[0:-1]
Paul Duffinb5cd5222022-02-28 19:06:49 +0000157 # Split the class name into outer / inner classes
158 # 0 - Character
159 # 1 - UnicodeScript
Paul Duffin19255f12022-03-08 16:35:52 +0000160 classes = last_element.removesuffix(";").split("$")
161
162 # Assemble the parts into a single list, adding prefixes to identify
163 # the different parts. If a wildcard is provided then it looks something
164 # like this:
165 # 0 - package:java
166 # 1 - package:lang
167 # 2 - *
168 #
169 # Otherwise, it looks something like this:
170 # 0 - package:java
171 # 1 - package:lang
172 # 2 - class:Character
173 # 3 - class:UnicodeScript
174 # 4 - member:of(I)Ljava/lang/Character$UnicodeScript;
175 return list(
Paul Duffinea935422022-03-09 14:51:17 +0000176 chain([("package", x) for x in packages],
177 [("class", x) for x in classes],
178 [("member", x) for x in member],
179 [("wildcard", x) for x in wildcard]))
Paul Duffinb5cd5222022-02-28 19:06:49 +0000180
181 # pylint: enable=line-too-long
182
Paul Duffin19255f12022-03-08 16:35:52 +0000183 @staticmethod
184 def split_element(element):
Paul Duffinea935422022-03-09 14:51:17 +0000185 element_type, element_value = element
Paul Duffin19255f12022-03-08 16:35:52 +0000186 return element_type, element_value
187
188 @staticmethod
189 def element_type(element):
190 element_type, _ = InteriorNode.split_element(element)
191 return element_type
192
Paul Duffin92532e72022-03-09 14:28:34 +0000193 @staticmethod
194 def elements_to_selector(elements):
195 """Compute a selector for a set of elements.
196
197 A selector uniquely identifies a specific Node in the trie. It is
198 essentially a prefix of a signature (without the leading L).
199
200 e.g. a trie containing "Ljava/lang/Object;->String()Ljava/lang/String;"
201 would contain nodes with the following selectors:
202 * "java"
203 * "java/lang"
204 * "java/lang/Object"
205 * "java/lang/Object;->String()Ljava/lang/String;"
206 """
207 signature = ""
208 preceding_type = ""
209 for element in elements:
210 element_type, element_value = InteriorNode.split_element(element)
211 separator = ""
212 if element_type == "package":
213 separator = "/"
214 elif element_type == "class":
215 if preceding_type == "class":
216 separator = "$"
217 else:
218 separator = "/"
219 elif element_type == "wildcard":
220 separator = "/"
221 elif element_type == "member":
222 separator += ";->"
223
224 if signature:
225 signature += separator
226
227 signature += element_value
228
229 preceding_type = element_type
230
231 return signature
232
233 def add(self, signature, value, only_if_matches=False):
Paul Duffinb5cd5222022-02-28 19:06:49 +0000234 """Associate the value with the specific signature.
235
236 :param signature: the member signature
237 :param value: the value to associated with the signature
Paul Duffin92532e72022-03-09 14:28:34 +0000238 :param only_if_matches: True if the value is added only if the signature
239 matches at least one of the existing top level packages.
Paul Duffinb5cd5222022-02-28 19:06:49 +0000240 :return: n/a
241 """
242 # Split the signature into elements.
243 elements = self.signature_to_elements(signature)
244 # Find the Node associated with the deepest class.
245 node = self
Paul Duffin92532e72022-03-09 14:28:34 +0000246 for index, element in enumerate(elements[:-1]):
Paul Duffinb5cd5222022-02-28 19:06:49 +0000247 if element in node.nodes:
248 node = node.nodes[element]
Paul Duffin92532e72022-03-09 14:28:34 +0000249 elif only_if_matches and index == 0:
250 return
Paul Duffinb5cd5222022-02-28 19:06:49 +0000251 else:
Paul Duffin92532e72022-03-09 14:28:34 +0000252 selector = self.elements_to_selector(elements[0:index + 1])
253 next_node = InteriorNode(
254 type=InteriorNode.element_type(element), selector=selector)
Paul Duffinb5cd5222022-02-28 19:06:49 +0000255 node.nodes[element] = next_node
256 node = next_node
257 # Add a Leaf containing the value and associate it with the member
258 # signature within the class.
259 last_element = elements[-1]
Paul Duffin19255f12022-03-08 16:35:52 +0000260 last_element_type = self.element_type(last_element)
261 if last_element_type != "member":
Paul Duffinb5cd5222022-02-28 19:06:49 +0000262 raise Exception(
263 f"Invalid signature: {signature}, does not identify a "
264 "specific member")
265 if last_element in node.nodes:
266 raise Exception(f"Duplicate signature: {signature}")
Paul Duffin92532e72022-03-09 14:28:34 +0000267 leaf = Leaf(
268 type=last_element_type,
269 selector=signature,
270 value=value,
271 )
272 node.nodes[last_element] = leaf
Paul Duffinb5cd5222022-02-28 19:06:49 +0000273
274 def get_matching_rows(self, pattern):
275 """Get the values (plural) associated with the pattern.
276
277 e.g. If the pattern is a full signature then this will return a list
278 containing the value associated with that signature.
279
280 If the pattern is a class then this will return a list containing the
281 values associated with all members of that class.
282
Paul Duffinb5cd5222022-02-28 19:06:49 +0000283 If the pattern ends with "*" then the preceding part is treated as a
284 package and this will return a list containing the values associated
285 with all the members of all the classes in that package.
286
287 If the pattern ends with "**" then the preceding part is treated
288 as a package and this will return a list containing the values
289 associated with all the members of all the classes in that package and
290 all sub-packages.
291
292 :param pattern: the pattern which could be a complete signature or a
293 class, or package wildcard.
294 :return: an iterable containing all the values associated with the
295 pattern.
296 """
297 elements = self.signature_to_elements(pattern)
298 node = self
299
300 # Include all values from this node and all its children.
301 selector = lambda x: True
302
303 last_element = elements[-1]
Paul Duffin19255f12022-03-08 16:35:52 +0000304 last_element_type, last_element_value = self.split_element(last_element)
305 if last_element_type == "wildcard":
Paul Duffinb5cd5222022-02-28 19:06:49 +0000306 elements = elements[:-1]
Paul Duffin19255f12022-03-08 16:35:52 +0000307 if last_element_value == "*":
Paul Duffinb5cd5222022-02-28 19:06:49 +0000308 # Do not include values from sub-packages.
Paul Duffin19255f12022-03-08 16:35:52 +0000309 selector = lambda x: InteriorNode.element_type(x) != "package"
Paul Duffinb5cd5222022-02-28 19:06:49 +0000310
311 for element in elements:
312 if element in node.nodes:
313 node = node.nodes[element]
314 else:
315 return []
316 return chain.from_iterable(node.values(selector))
317
318 def values(self, selector):
Paul Duffinb5cd5222022-02-28 19:06:49 +0000319 values = []
320 self.append_values(values, selector)
321 return values
322
323 def append_values(self, values, selector):
Paul Duffinb5cd5222022-02-28 19:06:49 +0000324 for key, node in self.nodes.items():
325 if selector(key):
326 node.append_values(values, lambda x: True)
327
Paul Duffin92532e72022-03-09 14:28:34 +0000328 def child_nodes(self):
329 return self.nodes.values()
330
Paul Duffinb5cd5222022-02-28 19:06:49 +0000331
Paul Duffin1f8a6b22022-03-08 16:31:55 +0000332@dataclasses.dataclass()
333class Leaf(Node):
334 """A leaf of the trie"""
Paul Duffinb5cd5222022-02-28 19:06:49 +0000335
Paul Duffin1f8a6b22022-03-08 16:31:55 +0000336 # The value associated with this leaf.
337 value: typing.Any
Paul Duffinb5cd5222022-02-28 19:06:49 +0000338
Paul Duffin1f8a6b22022-03-08 16:31:55 +0000339 def values(self, selector):
Paul Duffinb5cd5222022-02-28 19:06:49 +0000340 return [[self.value]]
341
Paul Duffin1f8a6b22022-03-08 16:31:55 +0000342 def append_values(self, values, selector):
Paul Duffinb5cd5222022-02-28 19:06:49 +0000343 values.append([self.value])
344
Paul Duffin92532e72022-03-09 14:28:34 +0000345 def child_nodes(self):
346 return []
347
Paul Duffinb5cd5222022-02-28 19:06:49 +0000348
349def signature_trie():
Paul Duffin92532e72022-03-09 14:28:34 +0000350 return InteriorNode(type="root", selector="")