Make signature_to_elements stricter and more consistent

Previously, signature_to_elements would return a string array where
non-wildcard strings were of the form <type>:<value> but wildcard
strings were just * or **. This change makes it handle wildcards
consistently with the other element types and adds some extra
checking for edge cases.

Bug: 202154151
Test: m out/soong/hiddenapi/hiddenapi-flags.csv
      atest --host signature_trie_test verify_overlaps_test
      pyformat -s 4 --force_quote_type double -i scripts/hiddenapi/signature_trie*
      /usr/bin/pylint --rcfile $ANDROID_BUILD_TOP/tools/repohooks/tools/pylintrc scripts/hiddenapi/signature_trie*
Change-Id: I5bfaf5e75c7da54b6241f68e03231939c9d65501
This commit is contained in:
Paul Duffin
2022-03-08 16:35:52 +00:00
parent b6a55c53e7
commit 19255f1d91
2 changed files with 76 additions and 37 deletions

View File

@@ -110,46 +110,69 @@ class InteriorNode(Node):
# 0 - java/lang/Character$UnicodeScript
# 1 - of(I)Ljava/lang/Character$UnicodeScript;
parts = text.split(";->")
# If there is no member then this will be an empty list.
member = parts[1:]
# Split the qualified class name into packages, and class name.
# 0 - java
# 1 - lang
# 2 - Character$UnicodeScript
elements = parts[0].split("/")
packages = elements[0:-1]
class_name = elements[-1]
if class_name in ("*", "**"): # pylint: disable=no-else-return
last_element = elements[-1]
wildcard = []
classes = []
if "*" in last_element:
if last_element not in ("*", "**"):
raise Exception(f"Invalid signature '{signature}': invalid "
f"wildcard '{last_element}'")
packages = elements[0:-1]
# Cannot specify a wildcard and target a specific member
if len(member) != 0:
raise Exception(f"Invalid signature {signature}: contains "
f"wildcard {class_name} and "
f"member signature {member[0]}")
wildcard = [class_name]
# Assemble the parts into a single list, adding prefixes to identify
# the different parts.
# 0 - package:java
# 1 - package:lang
# 2 - *
return list(chain(["package:" + x for x in packages], wildcard))
if member:
raise Exception(f"Invalid signature '{signature}': contains "
f"wildcard '{last_element}' and "
f"member signature '{member[0]}'")
wildcard = [last_element]
elif last_element.islower():
raise Exception(f"Invalid signature '{signature}': last element "
f"'{last_element}' is lower case but should be an "
f"upper case class name or wildcard")
else:
packages = elements[0:-1]
# Split the class name into outer / inner classes
# 0 - Character
# 1 - UnicodeScript
classes = class_name.split("$")
# Assemble the parts into a single list, adding prefixes to identify
# the different parts.
# 0 - package:java
# 1 - package:lang
# 2 - class:Character
# 3 - class:UnicodeScript
# 4 - member:of(I)Ljava/lang/Character$UnicodeScript;
return list(
chain(["package:" + x for x in packages],
["class:" + x for x in classes],
["member:" + x for x in member]))
classes = last_element.removesuffix(";").split("$")
# Assemble the parts into a single list, adding prefixes to identify
# the different parts. If a wildcard is provided then it looks something
# like this:
# 0 - package:java
# 1 - package:lang
# 2 - *
#
# Otherwise, it looks something like this:
# 0 - package:java
# 1 - package:lang
# 2 - class:Character
# 3 - class:UnicodeScript
# 4 - member:of(I)Ljava/lang/Character$UnicodeScript;
return list(
chain([f"package:{x}" for x in packages],
[f"class:{x}" for x in classes],
[f"member:{x}" for x in member],
[f"wildcard:{x}" for x in wildcard]))
# pylint: enable=line-too-long
@staticmethod
def split_element(element):
element_type, element_value = element.split(":", 1)
return element_type, element_value
@staticmethod
def element_type(element):
element_type, _ = InteriorNode.split_element(element)
return element_type
def add(self, signature, value):
"""Associate the value with the specific signature.
@@ -171,7 +194,8 @@ class InteriorNode(Node):
# Add a Leaf containing the value and associate it with the member
# signature within the class.
last_element = elements[-1]
if not last_element.startswith("member:"):
last_element_type = self.element_type(last_element)
if last_element_type != "member":
raise Exception(
f"Invalid signature: {signature}, does not identify a "
"specific member")
@@ -213,11 +237,12 @@ class InteriorNode(Node):
selector = lambda x: True
last_element = elements[-1]
if last_element in ("*", "**"):
last_element_type, last_element_value = self.split_element(last_element)
if last_element_type == "wildcard":
elements = elements[:-1]
if last_element == "*":
if last_element_value == "*":
# Do not include values from sub-packages.
selector = lambda x: not x.startswith("package:")
selector = lambda x: InteriorNode.element_type(x) != "package"
for element in elements:
if element in node.nodes:
@@ -237,7 +262,6 @@ class InteriorNode(Node):
node.append_values(values, lambda x: True)
@dataclasses.dataclass()
class Leaf(Node):
"""A leaf of the trie"""

View File

@@ -77,7 +77,7 @@ class TestSignatureToElements(unittest.TestCase):
elements = [
"package:java",
"package:lang",
"*",
"wildcard:*",
]
signature = "java/lang/*"
self.assertEqual(elements, self.signature_to_elements(signature))
@@ -86,25 +86,33 @@ class TestSignatureToElements(unittest.TestCase):
elements = [
"package:java",
"package:lang",
"**",
"wildcard:**",
]
signature = "java/lang/**"
self.assertEqual(elements, self.signature_to_elements(signature))
def test_no_packages_wildcard(self):
elements = [
"*",
"wildcard:*",
]
signature = "*"
self.assertEqual(elements, self.signature_to_elements(signature))
def test_no_packages_recursive_wildcard(self):
elements = [
"**",
"wildcard:**",
]
signature = "**"
self.assertEqual(elements, self.signature_to_elements(signature))
def test_invalid_no_class_or_wildcard(self):
signature = "java/lang"
with self.assertRaises(Exception) as context:
self.signature_to_elements(signature)
self.assertIn(
"last element 'lang' is lower case but should be an "
"upper case class name or wildcard", str(context.exception))
def test_non_standard_class_name(self):
elements = [
"package:javax",
@@ -114,12 +122,19 @@ class TestSignatureToElements(unittest.TestCase):
signature = "Ljavax/crypto/extObjectInputStream"
self.assertEqual(elements, self.signature_to_elements(signature))
def test_invalid_pattern_wildcard(self):
pattern = "Ljava/lang/Class*"
with self.assertRaises(Exception) as context:
self.signature_to_elements(pattern)
self.assertIn("invalid wildcard 'Class*'", str(context.exception))
def test_invalid_pattern_wildcard_and_member(self):
pattern = "Ljava/lang/*;->hashCode()I"
with self.assertRaises(Exception) as context:
self.signature_to_elements(pattern)
self.assertIn("contains wildcard * and member signature hashCode()I",
str(context.exception))
self.assertIn(
"contains wildcard '*' and member signature 'hashCode()I'",
str(context.exception))
class TestGetMatchingRows(unittest.TestCase):