Skip to content

Commit a115ee5

Browse files
walacglemco
andcommitted
rv/rvgen: introduce AutomataError exception class
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 <gmonaco@redhat.com> Signed-off-by: Wander Lairson Costa <wander@redhat.com> Reviewed-by: Gabriele Monaco <gmonaco@redhat.com> Link: https://lore.kernel.org/r/20260223162407.147003-2-wander@redhat.com Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
1 parent b133207 commit a115ee5

7 files changed

Lines changed: 43 additions & 34 deletions

File tree

tools/verification/rvgen/__main__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from rvgen.generator import Monitor
1414
from rvgen.container import Container
1515
from rvgen.ltl2k import ltl2k
16+
from rvgen.automata import AutomataError
1617
import argparse
1718
import sys
1819

@@ -53,9 +54,8 @@
5354
sys.exit(1)
5455
else:
5556
monitor = Container(vars(params))
56-
except Exception as e:
57-
print('Error: '+ str(e))
58-
print("Sorry : :-(")
57+
except AutomataError as e:
58+
print(f"There was an error processing {params.spec}: {e}", file=sys.stderr)
5959
sys.exit(1)
6060

6161
print("Writing the monitor into the directory %s" % monitor.name)

tools/verification/rvgen/rvgen/automata.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ class _EventConstraintKey(_ConstraintKey, tuple):
2525
def __new__(cls, state_id: int, event_id: int):
2626
return super().__new__(cls, (state_id, event_id))
2727

28+
class AutomataError(Exception):
29+
"""Exception raised for errors in automata parsing and validation.
30+
31+
Raised when DOT file processing fails due to invalid format, I/O errors,
32+
or malformed automaton definitions.
33+
"""
34+
2835
class Automata:
2936
"""Automata class: Reads a dot file and part it as an automata.
3037
@@ -72,11 +79,11 @@ def __get_model_name(self) -> str:
7279
basename = ntpath.basename(self.__dot_path)
7380
if not basename.endswith(".dot") and not basename.endswith(".gv"):
7481
print("not a dot file")
75-
raise Exception("not a dot file: %s" % self.__dot_path)
82+
raise AutomataError("not a dot file: %s" % self.__dot_path)
7683

7784
model_name = ntpath.splitext(basename)[0]
7885
if model_name.__len__() == 0:
79-
raise Exception("not a dot file: %s" % self.__dot_path)
86+
raise AutomataError("not a dot file: %s" % self.__dot_path)
8087

8188
return model_name
8289

@@ -85,8 +92,8 @@ def __open_dot(self) -> list[str]:
8592
dot_lines = []
8693
try:
8794
dot_file = open(self.__dot_path)
88-
except:
89-
raise Exception("Cannot open the file: %s" % self.__dot_path)
95+
except OSError as exc:
96+
raise AutomataError(exc.strerror) from exc
9097

9198
dot_lines = dot_file.read().splitlines()
9299
dot_file.close()
@@ -95,7 +102,7 @@ def __open_dot(self) -> list[str]:
95102
line = dot_lines[cursor].split()
96103

97104
if (line[0] != "digraph") and (line[1] != "state_automaton"):
98-
raise Exception("Not a valid .dot format: %s" % self.__dot_path)
105+
raise AutomataError("Not a valid .dot format: %s" % self.__dot_path)
99106
else:
100107
cursor += 1
101108
return dot_lines

tools/verification/rvgen/rvgen/dot2c.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# For further information, see:
1414
# Documentation/trace/rv/deterministic_automata.rst
1515

16-
from .automata import Automata
16+
from .automata import Automata, AutomataError
1717

1818
class Dot2c(Automata):
1919
enum_suffix = ""
@@ -103,7 +103,7 @@ def get_minimun_type(self) -> str:
103103
min_type = "unsigned int"
104104

105105
if self.states.__len__() > 1000000:
106-
raise Exception("Too many states: %d" % self.states.__len__())
106+
raise AutomataError("Too many states: %d" % self.states.__len__())
107107

108108
return min_type
109109

tools/verification/rvgen/rvgen/dot2k.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from collections import deque
1212
from .dot2c import Dot2c
1313
from .generator import Monitor
14-
from .automata import _EventConstraintKey, _StateConstraintKey
14+
from .automata import _EventConstraintKey, _StateConstraintKey, AutomataError
1515

1616

1717
class dot2k(Monitor, Dot2c):
@@ -166,14 +166,14 @@ class da2k(dot2k):
166166
def __init__(self, *args, **kwargs):
167167
super().__init__(*args, **kwargs)
168168
if self.is_hybrid_automata():
169-
raise ValueError("Detected hybrid automata, use the 'ha' class")
169+
raise AutomataError("Detected hybrid automata, use the 'ha' class")
170170

171171
class ha2k(dot2k):
172172
"""Hybrid automata only"""
173173
def __init__(self, *args, **kwargs):
174174
super().__init__(*args, **kwargs)
175175
if not self.is_hybrid_automata():
176-
raise ValueError("Detected deterministic automata, use the 'da' class")
176+
raise AutomataError("Detected deterministic automata, use the 'da' class")
177177
self.trace_h = self._read_template_file("trace_hybrid.h")
178178
self.__parse_constraints()
179179

@@ -266,22 +266,22 @@ def __validate_constraint(self, key: tuple[int, int] | int, constr: str,
266266
# state constraints are only used for expirations (e.g. clk<N)
267267
if self.is_event_constraint(key):
268268
if not rule and not reset:
269-
raise ValueError("Unrecognised event constraint "
270-
f"({self.states[key[0]]}/{self.events[key[1]]}: {constr})")
269+
raise AutomataError("Unrecognised event constraint "
270+
f"({self.states[key[0]]}/{self.events[key[1]]}: {constr})")
271271
if rule and (rule["env"] in self.env_types and
272272
rule["env"] not in self.env_stored):
273-
raise ValueError("Clocks in hybrid automata always require a storage"
274-
f" ({rule["env"]})")
273+
raise AutomataError("Clocks in hybrid automata always require a storage"
274+
f" ({rule["env"]})")
275275
else:
276276
if not rule:
277-
raise ValueError("Unrecognised state constraint "
278-
f"({self.states[key]}: {constr})")
277+
raise AutomataError("Unrecognised state constraint "
278+
f"({self.states[key]}: {constr})")
279279
if rule["env"] not in self.env_stored:
280-
raise ValueError("State constraints always require a storage "
281-
f"({rule["env"]})")
280+
raise AutomataError("State constraints always require a storage "
281+
f"({rule["env"]})")
282282
if rule["op"] not in ["<", "<="]:
283-
raise ValueError("State constraints must be clock expirations like"
284-
f" clk<N ({rule.string})")
283+
raise AutomataError("State constraints must be clock expirations like"
284+
f" clk<N ({rule.string})")
285285

286286
def __parse_constraints(self) -> None:
287287
self.guards: dict[_EventConstraintKey, str] = {}

tools/verification/rvgen/rvgen/generator.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,7 @@ def __fill_rv_kernel_dir(self):
5151
raise FileNotFoundError("Could not find the rv directory, do you have the kernel source installed?")
5252

5353
def _read_file(self, path):
54-
try:
55-
fd = open(path, 'r')
56-
except OSError:
57-
raise Exception("Cannot open the file: %s" % path)
54+
fd = open(path, 'r')
5855

5956
content = fd.read()
6057

@@ -65,7 +62,7 @@ def _read_template_file(self, file):
6562
try:
6663
path = os.path.join(self.abs_template_dir, file)
6764
return self._read_file(path)
68-
except Exception:
65+
except OSError:
6966
# Specific template file not found. Try the generic template file in the template/
7067
# directory, which is one level up
7168
path = os.path.join(self.abs_template_dir, "..", file)

tools/verification/rvgen/rvgen/ltl2ba.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from ply.lex import lex
1111
from ply.yacc import yacc
12+
from .automata import AutomataError
1213

1314
# Grammar:
1415
# ltl ::= opd | ( ltl ) | ltl binop ltl | unop ltl
@@ -62,7 +63,7 @@
6263
t_ignore = ' \t\n'
6364

6465
def t_error(t):
65-
raise ValueError(f"Illegal character '{t.value[0]}'")
66+
raise AutomataError(f"Illegal character '{t.value[0]}'")
6667

6768
lexer = lex()
6869

@@ -487,7 +488,7 @@ def p_unop(p):
487488
elif p[1] == "not":
488489
op = NotOp(p[2])
489490
else:
490-
raise ValueError(f"Invalid unary operator {p[1]}")
491+
raise AutomataError(f"Invalid unary operator {p[1]}")
491492

492493
p[0] = ASTNode(op)
493494

@@ -507,7 +508,7 @@ def p_binop(p):
507508
elif p[2] == "imply":
508509
op = ImplyOp(p[1], p[3])
509510
else:
510-
raise ValueError(f"Invalid binary operator {p[2]}")
511+
raise AutomataError(f"Invalid binary operator {p[2]}")
511512

512513
p[0] = ASTNode(op)
513514

@@ -526,7 +527,7 @@ def parse_ltl(s: str) -> ASTNode:
526527
subexpr[assign[0]] = assign[1]
527528

528529
if rule is None:
529-
raise ValueError("Please define your specification in the \"RULE = <LTL spec>\" format")
530+
raise AutomataError("Please define your specification in the \"RULE = <LTL spec>\" format")
530531

531532
for node in rule:
532533
if not isinstance(node.op, Variable):

tools/verification/rvgen/rvgen/ltl2k.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from pathlib import Path
55
from . import generator
66
from . import ltl2ba
7+
from .automata import AutomataError
78

89
COLUMN_LIMIT = 100
910

@@ -60,8 +61,11 @@ def __init__(self, file_path, MonitorType, extra_params={}):
6061
if MonitorType != "per_task":
6162
raise NotImplementedError("Only per_task monitor is supported for LTL")
6263
super().__init__(extra_params)
63-
with open(file_path) as f:
64-
self.atoms, self.ba, self.ltl = ltl2ba.create_graph(f.read())
64+
try:
65+
with open(file_path) as f:
66+
self.atoms, self.ba, self.ltl = ltl2ba.create_graph(f.read())
67+
except OSError as exc:
68+
raise AutomataError(exc.strerror) from exc
6569
self.atoms_abbr = abbreviate_atoms(self.atoms)
6670
self.name = extra_params.get("model_name")
6771
if not self.name:

0 commit comments

Comments
 (0)