Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 19255f1d authored by Paul Duffin's avatar Paul Duffin
Browse files

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
parent b6a55c53
Loading
Loading
Loading
Loading
+55 −31
Original line number Diff line number Diff line
@@ -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("/")
        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]
        class_name = elements[-1]
        if class_name in ("*", "**"):  # pylint: disable=no-else-return
            # 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("$")
            classes = last_element.removesuffix(";").split("$")

        # Assemble the parts into a single list, adding prefixes to identify
            # the different parts.
        # 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(["package:" + x for x in packages],
                      ["class:" + x for x in classes],
                      ["member:" + x for x in member]))
            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"""
+21 −6
Original line number Diff line number Diff line
@@ -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,11 +122,18 @@ 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",
        self.assertIn(
            "contains wildcard '*' and member signature 'hashCode()I'",
            str(context.exception))