diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..b547037a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "test/pycparser"] + path = test/pycparser + url = https://github.com/eliben/pycparser.git diff --git a/requirements.txt b/requirements.txt index 01b892fa..b3f4ea5d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ PyYAML nose rednose +pycparser diff --git a/test/pycparser b/test/pycparser new file mode 160000 index 00000000..e1a1d737 --- /dev/null +++ b/test/pycparser @@ -0,0 +1 @@ +Subproject commit e1a1d737be66308b633215fa26ac5ed30e890103 diff --git a/test/test_char.py b/test/test_char.py new file mode 100644 index 00000000..32ad99a0 --- /dev/null +++ b/test/test_char.py @@ -0,0 +1,62 @@ + +""" +Checks that the implementation does not make use of the `char` type. +This is ambiguous; compilers can freely choose `signed` or `unsigned` char. +""" + +import pqclean +import pycparser +import os + + +def test_char(): + for scheme in pqclean.Scheme.all_schemes(): + for implementation in scheme.implementations: + yield check_char, implementation + + +def walk_tree(ast): + if type(ast) is pycparser.c_ast.IdentifierType: + if ast.names == ['char']: + yield ast + + for (_, child) in ast.children(): + yield from walk_tree(child) # recursively yield prohibited nodes + + +def check_char(implementation): + errors = [] + for fname in os.listdir(implementation.path()): + if not fname.endswith(".c"): + continue + tdir, _ = os.path.split(os.path.realpath(__file__)) + ast = pycparser.parse_file( + os.path.join(implementation.path(), fname), + use_cpp=True, + cpp_args=[ + '-nostdinc', # pycparser cannot deal with e.g. __attribute__ + '-I{}'.format(os.path.join(tdir, "../common")), + # necessary to mock e.g. + '-I{}'.format( + os.path.join(tdir, 'pycparser/utils/fake_libc_include')), + ] + ) + for node in walk_tree(ast): + # flatten nodes to a string to easily enforce uniqueness + err = "\n at {c.file}:{c.line}:{c.column}".format(c=node.coord) + if err not in errors: + errors.append(err) + if errors: + raise AssertionError( + "Prohibited use of char without explicit signed/unsigned" + + "".join(errors) + ) + + +if __name__ == '__main__': + try: + import nose2 + nose2.main() + except ImportError: + import nose + nose.runmodule()