From 519762b7e650547c3e4a9bcd0332abd15fbd5a9a Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Wed, 10 Apr 2024 20:41:51 +0200 Subject: [PATCH] Clean up not-implemented detection Move hack_dependencies_not_implemented into a class to make the file structure easier to understand and reduce the visibility of the _implemented_dependencies cache. Rename it because it's no longer a temporary hack (originally intended to work around the fact that not all PSA_WANT symbols were implemented), it's now a way to detect test cases for cryptographic mechanisms that are declared but not implemented. Internal refactoring only. No behavior change. Signed-off-by: Gilles Peskine --- scripts/mbedtls_dev/psa_information.py | 71 ++++++++++++++++---------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/scripts/mbedtls_dev/psa_information.py b/scripts/mbedtls_dev/psa_information.py index 0e68df0a95..6f9c738c3a 100644 --- a/scripts/mbedtls_dev/psa_information.py +++ b/scripts/mbedtls_dev/psa_information.py @@ -6,7 +6,7 @@ import re -from typing import Dict, FrozenSet, List, Optional, Set +from typing import Dict, FrozenSet, Iterator, List, Optional, Set from . import macro_collector from . import test_case @@ -54,30 +54,6 @@ def automatic_dependencies(*expressions: str) -> List[str]: used.difference_update(SYMBOLS_WITHOUT_DEPENDENCY) return sorted(psa_want_symbol(name) for name in used) -# Skip test cases for which the dependency symbols are not defined. -# We assume that this means that a required mechanism is not implemented. -# Note that if we erroneously skip generating test cases for -# mechanisms that are not implemented, this should be caught -# by the NOT_SUPPORTED test cases generated by generate_psa_tests.py -# in test_suite_psa_crypto_not_supported and test_suite_psa_crypto_op_fail: -# those emit negative tests, which will not be skipped here. -def read_implemented_dependencies(filename: str) -> FrozenSet[str]: - return frozenset(symbol - for line in open(filename) - for symbol in re.findall(r'\bPSA_WANT_\w+\b', line)) -_implemented_dependencies = None #type: Optional[FrozenSet[str]] #pylint: disable=invalid-name - -def hack_dependencies_not_implemented(dependencies: List[str]) -> None: - global _implemented_dependencies #pylint: disable=global-statement,invalid-name - if _implemented_dependencies is None: - _implemented_dependencies = \ - read_implemented_dependencies('include/psa/crypto_config.h') - _implemented_dependencies = _implemented_dependencies.union( - read_implemented_dependencies('include/mbedtls/config_psa.h')) - for dep in dependencies: - if dep.startswith('PSA_WANT') and dep not in _implemented_dependencies: - dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET_' + dep) - dependencies.sort() class Information: """Gather information about PSA constructors.""" @@ -119,6 +95,47 @@ class TestCase(test_case.TestCase): involved in a given test case. """ + # Use a class variable to cache the set of implemented dependencies. + # Call read_implemented_dependencies() to fill the cache. + _implemented_dependencies = None #type: Optional[FrozenSet[str]] + + DEPENDENCY_SYMBOL_RE = re.compile(r'\bPSA_WANT_\w+\b') + @classmethod + def _yield_implemented_dependencies(cls) -> Iterator[str]: + for filename in ['include/psa/crypto_config.h', + 'include/mbedtls/config_psa.h']: + with open(filename) as inp: + content = inp.read() + yield from cls.DEPENDENCY_SYMBOL_RE.findall(content) + + @classmethod + def read_implemented_dependencies(cls) -> FrozenSet[str]: + if cls._implemented_dependencies is None: + cls._implemented_dependencies = \ + frozenset(cls._yield_implemented_dependencies()) + # Redundant return to reassure pylint (mypy is fine without it). + # Known issue: https://github.com/pylint-dev/pylint/issues/3045 + return cls._implemented_dependencies + return cls._implemented_dependencies + + # We skip test cases for which the dependency symbols are not defined. + # We assume that this means that a required mechanism is not implemented. + # Note that if we erroneously skip generating test cases for + # mechanisms that are not implemented, this should be caught + # by the NOT_SUPPORTED test cases generated by generate_psa_tests.py + # in test_suite_psa_crypto_not_supported and test_suite_psa_crypto_op_fail: + # those emit negative tests, which will not be skipped here. + def detect_not_implemented_dependencies(self) -> None: + """Detect dependencies that are not implemented.""" + all_implemented_dependencies = self.read_implemented_dependencies() + not_implemented = set() + for dep in self.dependencies: + if (dep.startswith('PSA_WANT') and + dep not in all_implemented_dependencies): + not_implemented.add('DEPENDENCY_NOT_IMPLEMENTED_YET_' + dep) + self.dependencies = sorted(not_implemented) + self.dependencies + self.dependencies.sort() + def __init__(self) -> None: super().__init__() self.key_bits = None #type: Optional[int] @@ -157,5 +174,5 @@ class TestCase(test_case.TestCase): dependencies[i] = '!' + dependencies[i] if self.key_bits is not None: dependencies = finish_family_dependencies(dependencies, self.key_bits) - hack_dependencies_not_implemented(dependencies) - self.dependencies += dependencies + self.dependencies += sorted(dependencies) + self.detect_not_implemented_dependencies()