diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c74d33024..c15526930 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -284,3 +284,9 @@ ``` - `release 7 7152` - `release 6 6461` +- `note 6` Warning and error messages reported by DMLC now display the tag for + the warning/error kind by default. The old behavior of having the tag + be omitted now requires passing `--no-tags` to DMLC. With this change, the + `-T` flag has become a no-op and will be removed in a future version. +- `note 6` Added the `WARNING` pragma, which allows for the granular + suppression of DMLC warnings for a target line. diff --git a/doc/1.4/language.md b/doc/1.4/language.md index d8f6b573f..44847f8b5 100644 --- a/doc/1.4/language.md +++ b/doc/1.4/language.md @@ -246,7 +246,8 @@ or, if _`classification`_ is omitted: A DML line will be affected by every `COVERITY` pragma specified in preceding -lines, up until the first line not containing any `COVERITY` pragma. For +lines, up until the first line not containing any `COVERITY` pragma or other +line-based pragma (like `WARNING`). For example: ``` /*% COVERITY unreachable %*/ @@ -259,7 +260,50 @@ Any C line corresponding to the call to `some_function(...)` will receive analysis annotations for `var_deref_model`, `check_return`, and `copy_paste_error` (with `copy_paste_error` specifically being classified as a false positive), but not any analysis annotation for `unreachable`, as the empty -line breaks the consecutive specifications of COVERITY pragmas. +line breaks the consecutive specifications of pragmas. + +### WARNING pragma +The `WARNING` pragma prevents DMLC from reporting DML warnings of a specified +kind for a particular DML line. + +Note that most warnings are designed to have an idiomatic way to silence them +without needing to employ the `WARNING` pragma; check the documentation for any +particular warning in Appendix [Messages](messages.html#warning-messages) +before employing the pragma. + +The syntax for the `WARNING` pragma is as follows: +
+/*% WARNING tag %*/
+
+where _`tag`_ is the tag of the warning kind to suppress; see Appendix +[Messages](messages.html#warning-messages). + +A DML line will be affected by every `WARNING` pragma specified in preceding +lines, up until the first line not containing any `WARNING` pragma or other +line-based pragma (like `COVERITY`). For example: +``` +param special default -1; +param then_level default 1; + +method check(uint32 i) { + /*% WARNING WREDUNDANTLEVEL %*/ + /*% WARNING WNEGCONSTCOMP %*/ + log info, 1 then then_level: "%s", i == special ? "special" : "regular"; +} +``` +In this case, the two `WARNING` pragmas apply *exclusively* to the `log info` +line. If any line (even an empty line) without a pragma were to be inserted +between the two `WARNING` pragmas, then it would break up the consecutive +specifications of pragmas and only `/*% WARNING WNEGCONSTCOMP %*/` would apply +to the `log info` line. + +This example also demonstrates the most compelling kind of use-case for the +pragma. By itself, this snippet would cause DMLC to warn about the `then` being +redundant (`WREDUNDANTLEVEL`) and the `i == special` check being impossible to +satisfy (`WNEGCONSTCOMP`), and yet those pieces of the code can still be +meaningful if used in a context where the definitions of `special` and +`then_level` get overridden. So in this case, the warnings fail to identify +true problems in the code, making it desirable to suppress them. ## The Object Model diff --git a/doc/1.4/running-dmlc.md b/doc/1.4/running-dmlc.md index 182cae7ce..59a51e5db 100644 --- a/doc/1.4/running-dmlc.md +++ b/doc/1.4/running-dmlc.md @@ -95,11 +95,14 @@ constant. The parameter will appear in the top-level scope. Output makefile rules describing dependencies.
--T +--no-tags
-Show tags on warning messages. The tags can be used with -the `--nowarn` and `--warn` options. +Don't display the tag associated with each warning or error message. +The tag identifies the particular kind of warning/error of the message, +which can be used to look up its documentation in Appendix +[Messages](messages.html). The `--nowarn` and `--warn` options as well as the +[`WARNING` pragma](language.html#warning-pragma) also operate on warning tags.
-g @@ -127,15 +130,15 @@ only used when needed. -\-warn=*tag*
-Enable selected warnings. The tags can be found using -the `-T` option. +Enable selected warnings. The tag of a warning will be displayed before the +message body, unless `--no-tags` has been passed to DMLC.
-\-nowarn=*tag*
-Suppress selected warnings. The tags can be found using -the `-T` option. +Suppress selected warnings. The tag of a warning will be displayed before the +message body, unless `--no-tags` has been passed to DMLC.
-\-werror diff --git a/py/dml/dmlc.py b/py/dml/dmlc.py index 86eab30b0..673dd5c80 100644 --- a/py/dml/dmlc.py +++ b/py/dml/dmlc.py @@ -364,12 +364,16 @@ def main(argv): + ' dependency generation. Specify multiple times to have multiple' + ' targets.') - #
-T
- #
Show tags on warning messages. The tags can be used with - # the -\-nowarn and -\-warn options.
+ #
-\-no-tags
+ #
Disables showing the tag associated with each error or warning + # message.
parser.add_argument( - '-T', dest='include_tag', action='store_true', - help='show tags on warning messages') + '--no-tags', dest='include_tag', action='store_false', + help='disable tags on error and warning messages') + + # Purely for backwards compatibility + parser.add_argument('-T', action='store_true', dest=argparse.SUPPRESS, + help=argparse.SUPPRESS) # Deprecated with SIMICS_API > 4.8 parser.add_argument( @@ -385,8 +389,7 @@ def main(argv): help='generate artifacts and C code that allow for easier debugging') #
-\-warn=tag
- #
Enable selected warnings. The tags can be found using - # the -T option.
+ #
Enable selected warnings.
parser.add_argument( '--warn', dest='enabled_warnings', action='append', metavar='TAG', @@ -394,8 +397,7 @@ def main(argv): help='enable warning TAG') #
-\-nowarn=tag
- #
Suppress selected warnings. The tags can be found using - # the -T option.
+ #
Suppress selected warnings.
parser.add_argument( '--nowarn', dest='disabled_warnings', action='append', metavar='TAG', diff --git a/py/dml/dmlparse.py b/py/dml/dmlparse.py index de461896d..e143b2fa6 100644 --- a/py/dml/dmlparse.py +++ b/py/dml/dmlparse.py @@ -1547,14 +1547,19 @@ def cdecl2_ptr(t): def cdecl2_vect(t): 'cdecl2 : VECT cdecl2' if provisional.simics_util_vect not in t.parser.file_info.provisional: - if not breaking_changes.vect_needs_provisional.enabled: - vsite = site(t) - if vsite.dml_version() != (1, 2): - # defensively suppress warning in 1.2, for - # compatibility - report(WEXPERIMENTAL(site(t), 'vect types')) - else: - report(EOLDVECT(site(t))) + # defensively suppress warning in 1.2, for + # compatibility + vsite = site(t) + if vsite.dml_version() != (1, 2): + if not breaking_changes.vect_needs_provisional.enabled: + report(WEXPERIMENTAL(vsite, + "vect types\nTo suppress this " + + "warning, specify " + + "'provisional simics_util_vect;' at " + + "the top of the file, after " + + "'dml 1.4;'")) + else: + report(EOLDVECT(vsite)) t[0] = ['vect'] + t[2] @prod_dml12 diff --git a/py/dml/globals.py b/py/dml/globals.py index 65c131bd5..4c61567fe 100644 --- a/py/dml/globals.py +++ b/py/dml/globals.py @@ -66,7 +66,9 @@ def compat_dml12_int(site): coverity = False -coverity_pragmas = {} +# the specified pragmas in the model that operate on a subsequent line. +# For the moment, all pragmas are of that sort +line_pragmas = {} # all warnings are disabled by the --dep flag ignore_all_warnings = False diff --git a/py/dml/logging.py b/py/dml/logging.py index 98cc90979..c76cb0487 100644 --- a/py/dml/logging.py +++ b/py/dml/logging.py @@ -13,6 +13,7 @@ 'warning_is_ignored', 'enable_warning', 'set_include_tag', + 'site_line_pragmas', 'ErrorContext', 'dollar', @@ -66,6 +67,14 @@ def enable_warning(tag): def warning_is_ignored(tag): return dml.globals.ignore_all_warnings or ignored_warnings.get(tag, False) +def warning_occurrence_is_ignored(warning): + tag = warning.tag() + if warning_is_ignored(tag): + return True + + return any((p == "WARNING" and t == tag + for (p, t) in site_line_pragmas(warning.site))) + class ErrorContext(object): __slots__ = ('node', 'site') @@ -219,7 +228,7 @@ class DMLWarning(LogMessage): def preprocess(self): # Don't print anything if the user asked us not to - if warning_is_ignored(self.tag()): + if warning_occurrence_is_ignored(self): return False if DMLWarning.next_warning_yields_error: self.print_site_message(self.site, @@ -266,6 +275,35 @@ def log(self): os.path.normcase(arg.loc()) if isinstance(arg, Site) else arg for arg in self.args])) +def site_line_pragmas(s): + if s is None or isinstance(s, SimpleSite): + return [] + + pragmas = [] + filename = s.filename() + tgt_lineno = s.lineno + + while (filename, tgt_lineno) in dml.globals.line_pragmas: + (start_lineno, inline_pragmas) = dml.globals.line_pragmas[(filename, + tgt_lineno)] + pragmas.extend(reversed(inline_pragmas)) + + # A minor HACK to handle the case of: + # /*% PRAGMA foo %*/ /*% PRAGMA + # bar %*/ + # some_statement + # + # Otherwise 'foo' won't be captured + if (start_lineno + 1 < tgt_lineno + and ((filename, start_lineno + 1) + in dml.globals.line_pragmas)): + tgt_lineno = start_lineno + 1 + else: + tgt_lineno = start_lineno + + pragmas.reverse() + return pragmas + class Site(metaclass=abc.ABCMeta): __slots__ = () @abc.abstractmethod diff --git a/py/dml/messages.py b/py/dml/messages.py index 5a277e0c1..d6793b943 100644 --- a/py/dml/messages.py +++ b/py/dml/messages.py @@ -1991,6 +1991,13 @@ class EDISCARDREF(DMLError): fmt = ("'_' can only be used as an assignment target " + "(to discard some value)") +class EWARNING(DMLError): + """ + An invalid warning tag was specified in a WARNING pragma. + """ + version = "1.4" + fmt = "Not a valid warning tag: '%s'" + # # WARNINGS (keep these as few as possible) # diff --git a/py/dml/output.py b/py/dml/output.py index e58173700..1dd6e67ff 100644 --- a/py/dml/output.py +++ b/py/dml/output.py @@ -6,7 +6,7 @@ from pathlib import Path import dml.globals -from .logging import ICE, SimpleSite +from .logging import ICE, SimpleSite, site_line_pragmas __all__ = ( 'NoOutput', @@ -185,30 +185,8 @@ def coverity_marker(event, classification=None, site=None): def coverity_markers(markers, site=None): site_with_loc = site is not None and not isinstance(site, SimpleSite) if dml.globals.coverity and site_with_loc: - custom_markers = [] - filename = site.filename() - tgt_lineno = site.lineno - - while (filename, tgt_lineno) in dml.globals.coverity_pragmas: - (start_lineno, - inline_markers) = dml.globals.coverity_pragmas[(filename, - tgt_lineno)] - custom_markers.extend(reversed(inline_markers)) - - # A minor HACK to handle the case of: - # /*% COVERITY foo %*/ /*% COVERITY - # bar %*/ - # some_statement - # - # Otherwise 'foo' won't be captured - if (start_lineno + 1 < tgt_lineno - and ((filename, start_lineno + 1) - in dml.globals.coverity_pragmas)): - tgt_lineno = start_lineno + 1 - else: - tgt_lineno = start_lineno - - custom_markers.reverse() + custom_markers = [d for (p, d) in site_line_pragmas(site) + if p == "COVERITY"] markers = custom_markers + markers if dml.globals.coverity and markers: diff --git a/py/dml/toplevel.py b/py/dml/toplevel.py index 8e2e347ab..bf5c137f9 100644 --- a/py/dml/toplevel.py +++ b/py/dml/toplevel.py @@ -196,8 +196,10 @@ def scan_statements(filename, site, stmts): # For the moment, we ban usages of * inside pragmas to not make multiple usages # of pragmas ruin everything. In the future if we introduce pragmas that may # want to contain * we need to be smarter about this. -pragma_re = re.compile(r'/\*%\s*([^\s*]+)\s*(?:\s([^*\s][^*]*))?%\*/') +pragma_re = re.compile(r'/\*%\s*([^\s*]+)\s*(?:\s([^*\s][^*]*?))?\s*%\*/') + pragma_coverity_data_re = re.compile(r'^(\S+)\s*(?:\s(\S[\s\S]*))?$') +warning_coverity_data_re = re.compile(r'^(\S+)$') def check_bidi(filename, filestr): for m in bidi_re.finditer(filestr): @@ -226,25 +228,32 @@ def parse_pragma(filename, start_lineno, end_lineno, pragma, data): None, "COVERITY pragma must specify event to suppress, " + "and optionally classification")) - else: - return ('COVERITY', - (filename, start_lineno, end_lineno + 1, data.groups())) + return None + data = data.groups() + elif pragma == 'WARNING': + data = data and warning_coverity_data_re.match(data) + if data is None: + report(ESYNTAX(SimpleSite(f"{filename}:{start_lineno}"), + None, + "WARNING pragma must specify warning to suppress")) + return None + data = data.group(1) else: report(EPRAGMA(SimpleSite(f"{filename}:{start_lineno}"), pragma)) return None -def process_pragma(t): - (pragma, data) = t - if pragma == 'COVERITY': - (filename, start_lineno, end_lineno, data) = data - # The first COVERITY pragma we encounter for a given end_lineno is - # the only one whose starting line may differ. - (dml.globals.coverity_pragmas - .setdefault((filename, end_lineno), (start_lineno, [])) - [1].append(data)) - else: - raise ICE(f'unknown pragma: {pragma}') + return (pragma, filename, start_lineno, end_lineno + 1, data) +def process_pragma(t): + (pragma, filename, start_lineno, tgt_lineno, data) = t + if pragma == 'WARNING': + if not is_warning_tag(data): + report(EWARNING(SimpleSite(f'{filename}:{start_lineno}:1'), data)) + return None + + (dml.globals.line_pragmas + .setdefault((filename, tgt_lineno), (start_lineno, [])) + [1].append((pragma, data))) def parse_file(dml_filename): try: diff --git a/test/1.4/errors/T_EWARNING.dml b/test/1.4/errors/T_EWARNING.dml new file mode 100644 index 000000000..1e048248b --- /dev/null +++ b/test/1.4/errors/T_EWARNING.dml @@ -0,0 +1,11 @@ +/* + © 2026 Intel Corporation + SPDX-License-Identifier: MPL-2.0 +*/ +dml 1.4; + +/// ERROR EWARNING +/*% WARNING EAFTER %*/ +/// ERROR EWARNING +/*% WARNING WHATEVER %*/ +device test; diff --git a/test/1.4/pragmas/T_WARNING.dml b/test/1.4/pragmas/T_WARNING.dml new file mode 100644 index 000000000..68f81e49f --- /dev/null +++ b/test/1.4/pragmas/T_WARNING.dml @@ -0,0 +1,47 @@ +/* + © 2026 Intel Corporation + SPDX-License-Identifier: MPL-2.0 +*/ + +dml 1.4; + +device test; + +/// COMPILE-ONLY + +template t { + param special default -1; + param then_level default 1; + + method check(uint32 i) { + /// WARNING WREDUNDANTLEVEL + log info, 1 then then_level: "%s", + /// WARNING WNEGCONSTCOMP + i == special ? "special" : "regular"; + + // no warning + /*% WARNING WREDUNDANTLEVEL %*/ + /*% WARNING WNEGCONSTCOMP %*/ + log info, 1 then then_level: "%s", i == special ? "special" : "regular"; + } +} + +is t; +method init() { + local uint32 x = 0; + + // pragma must immediately precede the line causing the warning, or another + // pragma targeting that line + /*% WARNING WNEGCONSTCOMP %*/ + /// WARNING WNEGCONSTCOMP + _ = x == -1; + + /*% WARNING WNEGCONSTCOMP + %*/ + /*% COVERITY bar %*/ + /*% COVERITY baz %*/ /*% WARNING WREDUNDANTLEVEL + %*/ + log info, 1 then 1: "%d", cast(x == -1, uint1); + + dev.check(0); +} diff --git a/test/tests.py b/test/tests.py index 922ac4ab1..59f27ee06 100644 --- a/test/tests.py +++ b/test/tests.py @@ -288,7 +288,7 @@ def run_dmlc(self, filename, dmlc_extraargs): reaper = dmlc_reaper_args( exitcode_file, dmlc_timeout_multipliers.get(self.fullname, 1)) - args = ["-T"] + args = [] if not line_directives: args += ["--noline"] args += dmlc_extraargs