On Mon, 2026-02-23 at 13:17 -0300, Wander Lairson Costa wrote: > Replace the generic except Exception block with a custom AutomataError > class that inherits from Exception. This provides more precise exception > handling for automata parsing and validation errors while avoiding > overly broad exception catches that could mask programming errors like > SyntaxError or TypeError. > > The AutomataError class is raised when DOT file processing fails due to > invalid format, I/O errors, or malformed automaton definitions. The > main entry point catches this specific exception and provides a > user-friendly error message to stderr before exiting. > > Also, replace generic exceptions raising in HA and LTL with > AutomataError. > > Co-authored-by: Gabriele Monaco <[email protected]> > Signed-off-by: Wander Lairson Costa <[email protected]>
All good, thanks! Reviewed-by: Gabriele Monaco <[email protected]> > --- > tools/verification/rvgen/__main__.py | 6 ++--- > tools/verification/rvgen/rvgen/automata.py | 17 ++++++++++---- > tools/verification/rvgen/rvgen/dot2c.py | 4 ++-- > tools/verification/rvgen/rvgen/dot2k.py | 26 ++++++++++----------- > tools/verification/rvgen/rvgen/generator.py | 7 ++---- > tools/verification/rvgen/rvgen/ltl2ba.py | 9 +++---- > tools/verification/rvgen/rvgen/ltl2k.py | 8 +++++-- > 7 files changed, 43 insertions(+), 34 deletions(-) > > diff --git a/tools/verification/rvgen/__main__.py > b/tools/verification/rvgen/__main__.py > index 9a5a9f08eae21..5a3f090ac3316 100644 > --- a/tools/verification/rvgen/__main__.py > +++ b/tools/verification/rvgen/__main__.py > @@ -13,6 +13,7 @@ if __name__ == '__main__': > from rvgen.generator import Monitor > from rvgen.container import Container > from rvgen.ltl2k import ltl2k > + from rvgen.automata import AutomataError > import argparse > import sys > > @@ -55,9 +56,8 @@ if __name__ == '__main__': > sys.exit(1) > else: > monitor = Container(vars(params)) > - except Exception as e: > - print('Error: '+ str(e)) > - print("Sorry : :-(") > + except AutomataError as e: > + print(f"There was an error processing {params.spec}: {e}", > file=sys.stderr) > sys.exit(1) > > print("Writing the monitor into the directory %s" % monitor.name) > diff --git a/tools/verification/rvgen/rvgen/automata.py > b/tools/verification/rvgen/rvgen/automata.py > index 5c1c5597d839f..9cc452305a2aa 100644 > --- a/tools/verification/rvgen/rvgen/automata.py > +++ b/tools/verification/rvgen/rvgen/automata.py > @@ -25,6 +25,13 @@ class _EventConstraintKey(_ConstraintKey, tuple): > def __new__(cls, state_id: int, event_id: int): > return super().__new__(cls, (state_id, event_id)) > > +class AutomataError(Exception): > + """Exception raised for errors in automata parsing and validation. > + > + Raised when DOT file processing fails due to invalid format, I/O errors, > + or malformed automaton definitions. > + """ > + > class Automata: > """Automata class: Reads a dot file and part it as an automata. > > @@ -72,11 +79,11 @@ class Automata: > basename = ntpath.basename(self.__dot_path) > if not basename.endswith(".dot") and not basename.endswith(".gv"): > print("not a dot file") > - raise Exception("not a dot file: %s" % self.__dot_path) > + raise AutomataError("not a dot file: %s" % self.__dot_path) > > model_name = ntpath.splitext(basename)[0] > if model_name.__len__() == 0: > - raise Exception("not a dot file: %s" % self.__dot_path) > + raise AutomataError("not a dot file: %s" % self.__dot_path) > > return model_name > > @@ -85,8 +92,8 @@ class Automata: > dot_lines = [] > try: > dot_file = open(self.__dot_path) > - except: > - raise Exception("Cannot open the file: %s" % self.__dot_path) > + except OSError as exc: > + raise AutomataError(exc.strerror) from exc > > dot_lines = dot_file.read().splitlines() > dot_file.close() > @@ -95,7 +102,7 @@ class Automata: > line = dot_lines[cursor].split() > > if (line[0] != "digraph") and (line[1] != "state_automaton"): > - raise Exception("Not a valid .dot format: %s" % self.__dot_path) > + raise AutomataError("Not a valid .dot format: %s" % > self.__dot_path) > else: > cursor += 1 > return dot_lines > diff --git a/tools/verification/rvgen/rvgen/dot2c.py > b/tools/verification/rvgen/rvgen/dot2c.py > index f779d9528af3f..6878cc79e6f70 100644 > --- a/tools/verification/rvgen/rvgen/dot2c.py > +++ b/tools/verification/rvgen/rvgen/dot2c.py > @@ -13,7 +13,7 @@ > # For further information, see: > # Documentation/trace/rv/deterministic_automata.rst > > -from .automata import Automata > +from .automata import Automata, AutomataError > > class Dot2c(Automata): > enum_suffix = "" > @@ -103,7 +103,7 @@ class Dot2c(Automata): > min_type = "unsigned int" > > if self.states.__len__() > 1000000: > - raise Exception("Too many states: %d" % self.states.__len__()) > + raise AutomataError("Too many states: %d" % > self.states.__len__()) > > return min_type > > diff --git a/tools/verification/rvgen/rvgen/dot2k.py > b/tools/verification/rvgen/rvgen/dot2k.py > index e7ba68a54c1f8..55222e38323f5 100644 > --- a/tools/verification/rvgen/rvgen/dot2k.py > +++ b/tools/verification/rvgen/rvgen/dot2k.py > @@ -11,7 +11,7 @@ > from collections import deque > from .dot2c import Dot2c > from .generator import Monitor > -from .automata import _EventConstraintKey, _StateConstraintKey > +from .automata import _EventConstraintKey, _StateConstraintKey, AutomataError > > > class dot2k(Monitor, Dot2c): > @@ -166,14 +166,14 @@ class da2k(dot2k): > def __init__(self, *args, **kwargs): > super().__init__(*args, **kwargs) > if self.is_hybrid_automata(): > - raise ValueError("Detected hybrid automata, use the 'ha' class") > + raise AutomataError("Detected hybrid automata, use the 'ha' > class") > > class ha2k(dot2k): > """Hybrid automata only""" > def __init__(self, *args, **kwargs): > super().__init__(*args, **kwargs) > if not self.is_hybrid_automata(): > - raise ValueError("Detected deterministic automata, use the 'da' > class") > + raise AutomataError("Detected deterministic automata, use the > 'da' class") > self.trace_h = self._read_template_file("trace_hybrid.h") > self.__parse_constraints() > > @@ -266,22 +266,22 @@ class ha2k(dot2k): > # state constraints are only used for expirations (e.g. clk<N) > if self.is_event_constraint(key): > if not rule and not reset: > - raise ValueError("Unrecognised event constraint " > - > f"({self.states[key[0]]}/{self.events[key[1]]}: {constr})") > + raise AutomataError("Unrecognised event constraint " > + > f"({self.states[key[0]]}/{self.events[key[1]]}: {constr})") > if rule and (rule["env"] in self.env_types and > rule["env"] not in self.env_stored): > - raise ValueError("Clocks in hybrid automata always require a > storage" > - f" ({rule["env"]})") > + raise AutomataError("Clocks in hybrid automata always require > a storage" > + f" ({rule["env"]})") > else: > if not rule: > - raise ValueError("Unrecognised state constraint " > - f"({self.states[key]}: {constr})") > + raise AutomataError("Unrecognised state constraint " > + f"({self.states[key]}: {constr})") > if rule["env"] not in self.env_stored: > - raise ValueError("State constraints always require a storage > " > - f"({rule["env"]})") > + raise AutomataError("State constraints always require a > storage " > + f"({rule["env"]})") > if rule["op"] not in ["<", "<="]: > - raise ValueError("State constraints must be clock expirations > like" > - f" clk<N ({rule.string})") > + raise AutomataError("State constraints must be clock > expirations like" > + f" clk<N ({rule.string})") > > def __parse_constraints(self) -> None: > self.guards: dict[_EventConstraintKey, str] = {} > diff --git a/tools/verification/rvgen/rvgen/generator.py > b/tools/verification/rvgen/rvgen/generator.py > index 5eac12e110dce..571093a92bdc8 100644 > --- a/tools/verification/rvgen/rvgen/generator.py > +++ b/tools/verification/rvgen/rvgen/generator.py > @@ -51,10 +51,7 @@ class RVGenerator: > raise FileNotFoundError("Could not find the rv directory, do you have > the kernel source installed?") > > def _read_file(self, path): > - try: > - fd = open(path, 'r') > - except OSError: > - raise Exception("Cannot open the file: %s" % path) > + fd = open(path, 'r') > > content = fd.read() > > @@ -65,7 +62,7 @@ class RVGenerator: > try: > path = os.path.join(self.abs_template_dir, file) > return self._read_file(path) > - except Exception: > + except OSError: > # Specific template file not found. Try the generic template file > in the template/ > # directory, which is one level up > path = os.path.join(self.abs_template_dir, "..", file) > diff --git a/tools/verification/rvgen/rvgen/ltl2ba.py > b/tools/verification/rvgen/rvgen/ltl2ba.py > index f14e6760ac3db..f9855dfa3bc1c 100644 > --- a/tools/verification/rvgen/rvgen/ltl2ba.py > +++ b/tools/verification/rvgen/rvgen/ltl2ba.py > @@ -9,6 +9,7 @@ > > from ply.lex import lex > from ply.yacc import yacc > +from .automata import AutomataError > > # Grammar: > # ltl ::= opd | ( ltl ) | ltl binop ltl | unop ltl > @@ -62,7 +63,7 @@ t_ignore_COMMENT = r'\#.*' > t_ignore = ' \t\n' > > def t_error(t): > - raise ValueError(f"Illegal character '{t.value[0]}'") > + raise AutomataError(f"Illegal character '{t.value[0]}'") > > lexer = lex() > > @@ -487,7 +488,7 @@ def p_unop(p): > elif p[1] == "not": > op = NotOp(p[2]) > else: > - raise ValueError(f"Invalid unary operator {p[1]}") > + raise AutomataError(f"Invalid unary operator {p[1]}") > > p[0] = ASTNode(op) > > @@ -507,7 +508,7 @@ def p_binop(p): > elif p[2] == "imply": > op = ImplyOp(p[1], p[3]) > else: > - raise ValueError(f"Invalid binary operator {p[2]}") > + raise AutomataError(f"Invalid binary operator {p[2]}") > > p[0] = ASTNode(op) > > @@ -526,7 +527,7 @@ def parse_ltl(s: str) -> ASTNode: > subexpr[assign[0]] = assign[1] > > if rule is None: > - raise ValueError("Please define your specification in the \"RULE = > <LTL spec>\" format") > + raise AutomataError("Please define your specification in the \"RULE = > <LTL spec>\" format") > > for node in rule: > if not isinstance(node.op, Variable): > diff --git a/tools/verification/rvgen/rvgen/ltl2k.py > b/tools/verification/rvgen/rvgen/ltl2k.py > index b075f98d50c47..08ad245462e7d 100644 > --- a/tools/verification/rvgen/rvgen/ltl2k.py > +++ b/tools/verification/rvgen/rvgen/ltl2k.py > @@ -4,6 +4,7 @@ > from pathlib import Path > from . import generator > from . import ltl2ba > +from .automata import AutomataError > > COLUMN_LIMIT = 100 > > @@ -60,8 +61,11 @@ class ltl2k(generator.Monitor): > if MonitorType != "per_task": > raise NotImplementedError("Only per_task monitor is supported for > LTL") > super().__init__(extra_params) > - with open(file_path) as f: > - self.atoms, self.ba, self.ltl = ltl2ba.create_graph(f.read()) > + try: > + with open(file_path) as f: > + self.atoms, self.ba, self.ltl = ltl2ba.create_graph(f.read()) > + except OSError as exc: > + raise AutomataError(exc.strerror) from exc > self.atoms_abbr = abbreviate_atoms(self.atoms) > self.name = extra_params.get("model_name") > if not self.name:
