diff --git a/py/dml/messages.py b/py/dml/messages.py index 5a277e0c..55358f3a 100644 --- a/py/dml/messages.py +++ b/py/dml/messages.py @@ -2150,7 +2150,19 @@ def __init__(self, dmlfile): DMLWarning.__init__(self, SimpleSite(dmlfile + ":0"), dmlfile + "ast") +class WDMLAST(DMLWarning): + """Some files in the DML standard library are preparsed into files + with the `.dmlast` suffix. If such files are encountered + elsewhere, then they are ignored to avoid potential coherency + problems, and this warning is printed. + """ + fmt = "AST file for file outside the standard library: %s" + def __init__(self, dmlfile): + DMLWarning.__init__(self, SimpleSite(dmlfile + ":0"), + dmlfile + "ast") + class WWRNSTMT(DMLWarning): + """ The source code contained a statement "`warning;`", which causes a warning to be printed. diff --git a/py/dml/toplevel.py b/py/dml/toplevel.py index 8e2e347a..9dd95b2d 100644 --- a/py/dml/toplevel.py +++ b/py/dml/toplevel.py @@ -279,10 +279,22 @@ def parse_file(dml_filename): ast = parse(contents, file_info, dml_filename, version) return ast +class ASTUnpickler(pickle.Unpickler): + _safe_classes = { + ('dml.ast', 'AST'), + ('dml.logging', 'DumpableSite'), + ('dml.logging', 'FileInfo')} + + def find_class(self, module, name): + if (module, name) not in self._safe_classes: + raise pickle.UnpicklingError('broken dmlast data') + return super().find_class(module, name) + def load_dmlast(ast_filename): '''Return a previously compiled AST, or None''' try: - return pickle.loads(bz2.BZ2File(ast_filename).read()) # nosec + with bz2.BZ2File(ast_filename) as f: + return ASTUnpickler(f).load() except Exception as e: raise ICE(SimpleSite(ast_filename), "Failed to load AST from %r: %s" @@ -319,6 +331,13 @@ def parse_dmlast_or_dml(dml_filename): # error in dml-builtins.dml, one accidentally edits the # copy in [host]/bin/dml/, instead of the one in the repo. report(WOLDAST(dml_filename)) + elif (Path(__file__).parent.parent.parent.parent + not in Path(ast_filename).parents): + # The .dmlast file mechanism is meant only to speed up the + # parsing of the standard library. Using it elsewhere would + # mean that parser updates can cause confusing errors, and the + # speed gains are small, so refuse to do that. + report(WDMLAST(dml_filename)) else: file_info, pragmas, parsedata = load_dmlast(ast_filename) if file_info.name is None: @@ -427,8 +446,7 @@ def parse_main_file(inputfilename, explicit_import_path): deps.setdefault(path, set()).add(importfile) - path = str(Path(path).resolve()) - normalized = os.path.normcase(path) + normalized = os.path.normcase(str(Path(path).resolve())) if normalized in imported: # Already imported if importfile not in imported[normalized]: diff --git a/test/1.4/errors/T_WDMLAST.dml b/test/1.4/errors/T_WDMLAST.dml new file mode 100644 index 00000000..c425aff4 --- /dev/null +++ b/test/1.4/errors/T_WDMLAST.dml @@ -0,0 +1,10 @@ +/* + © 2024 Intel Corporation + SPDX-License-Identifier: MPL-2.0 +*/ +dml 1.4; + +device test; + +/// COMPILE-ONLY +/// WARNING WDMLAST T_WDMLAST.dml diff --git a/test/1.4/errors/T_WDMLAST.dmlast b/test/1.4/errors/T_WDMLAST.dmlast new file mode 100644 index 00000000..e69de29b diff --git a/test/tests.py b/test/tests.py index 922ac4ab..bc2246db 100644 --- a/test/tests.py +++ b/test/tests.py @@ -1889,6 +1889,8 @@ def test(self): # Don't bother. 'test/1.2/misc/T_dos_newline.dml', # empty + 'test/1.4/errors/T_WDMLAST.dmlast', + # empty 'test/SUITEINFO', # data files 'test/XFAIL',