From 846beb7f5442041d7e47cdfeceb65d3f1fd7329c Mon Sep 17 00:00:00 2001 From: Paul Duffin Date: Tue, 15 Mar 2022 17:45:57 +0000 Subject: Make bootclasspath_fragment hidden API package checks exhaustive Previously, the bootclasspath_fragment's hidden_api.split_packages and hidden_api.package_prefixes properties did not specify an exhaustive set of packages that were provided by the fragment. They excluded packages which were either not split or which could not be used as a package prefix because it would match sub-packages provided by other bootclasspath modules. This change adds the hidden_api.single_packages list to specify those additional packages and then uses that information to verify that any bootclasspath_fragment that specifies at least one of split_packages, single_packages or package_prefixes properties only contains classes from a package that matches one of those properties. That will prevent a module from accidentally including unexpected classes, such as might happen when statically including a common utility library. It also adds coverage specific versions of the properties as additional packages are added to the art-bootclasspath-fragment when building coverage builds. Bug: 194063708 Test: atest signature_patterns_test m out/soong/hiddenapi/hiddenapi-flags.csv m EMMA_INSTRUMENT=true EMMA_INSTRUMENT_FRAMEWORK=true out/soong/hiddenapi/hiddenapi-flags.csv # Breaks without corresponding change to add android.system to # the art-bootclasspath-fragment. /usr/bin/pylint --rcfile $ANDROID_BUILD_TOP/tools/repohooks/tools/pylintrc scripts/hiddenapi/signature_patterns*.py pyformat -s 4 --force_quote_type single -i scripts/hiddenapi/signature_patterns*.py Change-Id: Iddf6c59cd4dc8c36dde7943a9840ccef5794b320 --- scripts/hiddenapi/signature_patterns.py | 122 +++++++++++++++++++++++++------- 1 file changed, 98 insertions(+), 24 deletions(-) (limited to 'scripts/hiddenapi/signature_patterns.py') diff --git a/scripts/hiddenapi/signature_patterns.py b/scripts/hiddenapi/signature_patterns.py index 1abdef114..5a82be7b2 100755 --- a/scripts/hiddenapi/signature_patterns.py +++ b/scripts/hiddenapi/signature_patterns.py @@ -60,7 +60,22 @@ def matched_by_package_prefix_pattern(package_prefixes, prefix): return False -def validate_package_prefixes(split_packages, package_prefixes): +def validate_package_is_not_matched_by_package_prefix(package_type, pkg, + package_prefixes): + package_prefix = matched_by_package_prefix_pattern(package_prefixes, pkg) + if package_prefix: + # A package prefix matches the package. + package_for_output = slash_package_to_dot_package(pkg) + package_prefix_for_output = slash_package_to_dot_package(package_prefix) + return [ + f'{package_type} {package_for_output} is matched by ' + f'package prefix {package_prefix_for_output}' + ] + return [] + + +def validate_package_prefixes(split_packages, single_packages, + package_prefixes): # If there are no package prefixes then there is no possible conflict # between them and the split packages. if len(package_prefixes) == 0: @@ -79,17 +94,16 @@ def validate_package_prefixes(split_packages, package_prefixes): f'{package_prefixes_for_output}\n' ' add split_packages:[] to fix') else: - package_prefix = matched_by_package_prefix_pattern( - package_prefixes, split_package) - if package_prefix: - # A package prefix matches a split package. - split_package_for_output = slash_package_to_dot_package( - split_package) - package_prefix_for_output = slash_package_to_dot_package( - package_prefix) - errors.append( - f'split package {split_package_for_output} is matched by ' - f'package prefix {package_prefix_for_output}') + errs = validate_package_is_not_matched_by_package_prefix( + 'split package', split_package, package_prefixes) + errors.extend(errs) + + # Check to make sure that the single packages and package prefixes do not + # overlap. + for single_package in single_packages: + errs = validate_package_is_not_matched_by_package_prefix( + 'single package', single_package, package_prefixes) + errors.extend(errs) return errors @@ -102,21 +116,40 @@ def validate_split_packages(split_packages): return errors +def validate_single_packages(split_packages, single_packages): + overlaps = [] + for single_package in single_packages: + if single_package in split_packages: + overlaps.append(single_package) + if overlaps: + indented = ''.join([f'\n {o}' for o in overlaps]) + return [ + f'single_packages and split_packages overlap, please ensure the ' + f'following packages are only present in one:{indented}' + ] + return [] + + def produce_patterns_from_file(file, split_packages=None, + single_packages=None, package_prefixes=None): with open(file, 'r', encoding='utf8') as f: - return produce_patterns_from_stream(f, split_packages, package_prefixes) + return produce_patterns_from_stream(f, split_packages, single_packages, + package_prefixes) def produce_patterns_from_stream(stream, split_packages=None, + single_packages=None, package_prefixes=None): split_packages = set(split_packages or []) + single_packages = set(single_packages or []) package_prefixes = list(package_prefixes or []) # Read in all the signatures into a list and remove any unnecessary class # and member names. patterns = set() + unmatched_packages = set() for row in dict_reader(stream): signature = row['signature'] text = signature.removeprefix('L') @@ -138,11 +171,31 @@ def produce_patterns_from_stream(stream, # Remove inner class names. pieces = qualified_class_name.split('$', maxsplit=1) pattern = pieces[0] - else: + patterns.add(pattern) + elif pkg in single_packages: # Add a * to ensure that the pattern matches the classes in that # package. pattern = pkg + '/*' - patterns.add(pattern) + patterns.add(pattern) + else: + unmatched_packages.add(pkg) + + # Remove any unmatched packages that would be matched by a package prefix + # pattern. + unmatched_packages = [ + p for p in unmatched_packages + if not matched_by_package_prefix_pattern(package_prefixes, p) + ] + errors = [] + if unmatched_packages: + unmatched_packages.sort() + indented = ''.join([ + f'\n {slash_package_to_dot_package(p)}' + for p in unmatched_packages + ]) + errors.append('The following packages were unexpected, please add them ' + 'to one of the hidden_api properties, split_packages, ' + f'single_packages or package_prefixes:{indented}') # Remove any patterns that would be matched by a package prefix pattern. patterns = [ @@ -155,7 +208,13 @@ def produce_patterns_from_stream(stream, patterns = patterns + [f'{p}/**' for p in package_prefixes] # Sort the patterns. patterns.sort() - return patterns + return patterns, errors + + +def print_and_exit(errors): + for error in errors: + print(error) + sys.exit(1) def main(args): @@ -175,26 +234,41 @@ def main(args): '--package-prefix', action='append', help='A package prefix unique to this set of flags') + args_parser.add_argument( + '--single-package', + action='append', + help='A single package unique to this set of flags') args_parser.add_argument('--output', help='Generated signature prefixes') args = args_parser.parse_args(args) split_packages = set( dot_packages_to_slash_packages(args.split_package or [])) errors = validate_split_packages(split_packages) + if errors: + print_and_exit(errors) - package_prefixes = dot_packages_to_slash_packages(args.package_prefix or []) + single_packages = list( + dot_packages_to_slash_packages(args.single_package or [])) - if not errors: - errors = validate_package_prefixes(split_packages, package_prefixes) + errors = validate_single_packages(split_packages, single_packages) + if errors: + print_and_exit(errors) + + package_prefixes = dot_packages_to_slash_packages(args.package_prefix or []) + errors = validate_package_prefixes(split_packages, single_packages, + package_prefixes) if errors: - for error in errors: - print(error) - sys.exit(1) + print_and_exit(errors) + patterns = [] # Read in all the patterns into a list. - patterns = produce_patterns_from_file(args.flags, split_packages, - package_prefixes) + patterns, errors = produce_patterns_from_file(args.flags, split_packages, + single_packages, + package_prefixes) + + if errors: + print_and_exit(errors) # Write out all the patterns. with open(args.output, 'w', encoding='utf8') as outputFile: -- cgit v1.2.3-59-g8ed1b