Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

rv/rvgen: refactor automata.py to use iterator-based parsing

Refactor the DOT file parsing logic in automata.py to use Python's
iterator-based patterns instead of manual cursor indexing. The previous
implementation relied on while loops with explicit cursor management,
which made the code prone to off-by-one errors and would crash on
malformed input files containing empty lines.

The new implementation uses enumerate and itertools.islice to iterate
over lines, eliminating manual cursor tracking. Functions that search
for specific markers now use for loops with early returns and explicit
AutomataError exceptions for missing markers, rather than assuming the
markers exist. Additional bounds checking ensures that split line
arrays have sufficient elements before accessing specific indices,
preventing IndexError exceptions on malformed DOT files.

The matrix creation and event variable extraction methods now use
functional patterns with map combined with itertools.islice,
making the intent clearer while maintaining the same behavior. Minor
improvements include using extend instead of append in a loop, adding
empty file validation, and replacing enumerate with range where the
enumerated value was unused.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
Reviewed-by: Gabriele Monaco <gmonaco@redhat.com>
Link: https://lore.kernel.org/r/20260223162407.147003-12-wander@redhat.com
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>

authored by

Wander Lairson Costa and committed by
Gabriele Monaco
0c25d8c8 d474fedc

+72 -46
+72 -46
tools/verification/rvgen/rvgen/automata.py
··· 11 11 import ntpath 12 12 import re 13 13 from typing import Iterator 14 + from itertools import islice 14 15 15 16 class _ConstraintKey: 16 17 """Base class for constraint keys.""" ··· 90 89 return model_name 91 90 92 91 def __open_dot(self) -> list[str]: 93 - cursor = 0 94 92 dot_lines = [] 95 93 try: 96 94 with open(self.__dot_path) as dot_file: 97 - dot_lines = dot_file.read().splitlines() 95 + dot_lines = dot_file.readlines() 98 96 except OSError as exc: 99 97 raise AutomataError(exc.strerror) from exc 100 98 101 - # checking the first line: 102 - line = dot_lines[cursor].split() 99 + if not dot_lines: 100 + raise AutomataError(f"{self.__dot_path} is empty") 103 101 104 - if (line[0] != "digraph") or (line[1] != "state_automaton"): 102 + # checking the first line: 103 + line = dot_lines[0].split() 104 + 105 + if len(line) < 2 or line[0] != "digraph" or line[1] != "state_automaton": 105 106 raise AutomataError(f"Not a valid .dot format: {self.__dot_path}") 106 - else: 107 - cursor += 1 107 + 108 108 return dot_lines 109 109 110 110 def __get_cursor_begin_states(self) -> int: 111 - cursor = 0 112 - while self.__dot_lines[cursor].split()[0] != "{node": 113 - cursor += 1 114 - return cursor 111 + for cursor, line in enumerate(self.__dot_lines): 112 + split_line = line.split() 113 + 114 + if len(split_line) and split_line[0] == "{node": 115 + return cursor 116 + 117 + raise AutomataError("Could not find a beginning state") 115 118 116 119 def __get_cursor_begin_events(self) -> int: 117 - cursor = 0 118 - while self.__dot_lines[cursor].split()[0] != "{node": 119 - cursor += 1 120 - while self.__dot_lines[cursor].split()[0] == "{node": 121 - cursor += 1 122 - # skip initial state transition 123 - cursor += 1 120 + state = 0 121 + cursor = 0 # make pyright happy 122 + 123 + for cursor, line in enumerate(self.__dot_lines): 124 + line = line.split() 125 + if not line: 126 + continue 127 + 128 + if state == 0: 129 + if line[0] == "{node": 130 + state = 1 131 + elif line[0] != "{node": 132 + break 133 + else: 134 + raise AutomataError("Could not find beginning event") 135 + 136 + cursor += 1 # skip initial state transition 137 + if cursor == len(self.__dot_lines): 138 + raise AutomataError("Dot file ended after event beginning") 139 + 124 140 return cursor 125 141 126 142 def __get_state_variables(self) -> tuple[list[str], str, list[str]]: ··· 149 131 cursor = self.__get_cursor_begin_states() 150 132 151 133 # process nodes 152 - while self.__dot_lines[cursor].split()[0] == "{node": 153 - line = self.__dot_lines[cursor].split() 154 - raw_state = line[-1] 134 + for line in islice(self.__dot_lines, cursor, None): 135 + split_line = line.split() 136 + if not split_line or split_line[0] != "{node": 137 + break 138 + 139 + raw_state = split_line[-1] 155 140 156 141 # "enabled_fired"}; -> enabled_fired 157 142 state = raw_state.replace('"', '').replace('};', '').replace(',', '_') ··· 162 141 initial_state = state[len(self.init_marker):] 163 142 else: 164 143 states.append(state) 165 - if "doublecircle" in self.__dot_lines[cursor]: 144 + if "doublecircle" in line: 166 145 final_states.append(state) 167 146 has_final_states = True 168 147 169 - if "ellipse" in self.__dot_lines[cursor]: 148 + if "ellipse" in line: 170 149 final_states.append(state) 171 150 has_final_states = True 172 - 173 - cursor += 1 174 151 175 152 states = sorted(set(states)) 176 153 states.remove(initial_state) ··· 182 163 return states, initial_state, final_states 183 164 184 165 def __get_event_variables(self) -> tuple[list[str], list[str]]: 166 + events: list[str] = [] 167 + envs: list[str] = [] 185 168 # here we are at the begin of transitions, take a note, we will return later. 186 169 cursor = self.__get_cursor_begin_events() 187 170 188 - events = [] 189 - envs = [] 190 - while self.__dot_lines[cursor].lstrip()[0] == '"': 171 + for line in map(str.lstrip, islice(self.__dot_lines, cursor, None)): 172 + if not line.startswith('"'): 173 + break 174 + 191 175 # transitions have the format: 192 176 # "all_fired" -> "both_fired" [ label = "disable_irq" ]; 193 177 # ------------ event is here ------------^^^^^ 194 - if self.__dot_lines[cursor].split()[1] == "->": 195 - line = self.__dot_lines[cursor].split() 196 - event = "".join(line[line.index("label") + 2:-1]).replace('"', '') 178 + split_line = line.split() 179 + if len(split_line) > 1 and split_line[1] == "->": 180 + event = "".join(split_line[split_line.index("label") + 2:-1]).replace('"', '') 197 181 198 182 # when a transition has more than one label, they are like this 199 183 # "local_irq_enable\nhw_local_irq_enable_n" ··· 209 187 ev, *constr = i.split(";") 210 188 if constr: 211 189 if len(constr) > 2: 212 - raise ValueError("Only 1 constraint and 1 reset are supported") 190 + raise AutomataError("Only 1 constraint and 1 reset are supported") 213 191 envs += self.__extract_env_var(constr) 214 192 events.append(ev) 215 193 else: ··· 217 195 # "enable_fired" [label = "enable_fired\ncondition"]; 218 196 # ----- label is here -----^^^^^ 219 197 # label and node name must be the same, condition is optional 220 - state = self.__dot_lines[cursor].split("label")[1].split('"')[1] 198 + state = line.split("label")[1].split('"')[1] 221 199 _, *constr = state.split("\\n") 222 200 if constr: 223 201 if len(constr) > 1: 224 - raise ValueError("Only 1 constraint is supported in the state") 202 + raise AutomataError("Only 1 constraint is supported in the state") 225 203 envs += self.__extract_env_var([constr[0].replace(" ", "")]) 226 - cursor += 1 227 204 228 205 return sorted(set(events)), sorted(set(envs)) 229 206 ··· 286 265 nr_state += 1 287 266 288 267 # declare the matrix.... 289 - matrix = [[self.invalid_state_str for x in range(nr_event)] for y in range(nr_state)] 268 + matrix = [[self.invalid_state_str for _ in range(nr_event)] for _ in range(nr_state)] 290 269 constraints: dict[_ConstraintKey, list[str]] = {} 291 270 292 271 # and we are back! Let's fill the matrix 293 272 cursor = self.__get_cursor_begin_events() 294 273 295 - while self.__dot_lines[cursor].lstrip()[0] == '"': 296 - if self.__dot_lines[cursor].split()[1] == "->": 297 - line = self.__dot_lines[cursor].split() 298 - origin_state = line[0].replace('"', '').replace(',', '_') 299 - dest_state = line[2].replace('"', '').replace(',', '_') 300 - possible_events = "".join(line[line.index("label") + 2:-1]).replace('"', '') 274 + for line in map(str.lstrip, 275 + islice(self.__dot_lines, cursor, None)): 276 + 277 + if not line or line[0] != '"': 278 + break 279 + 280 + split_line = line.split() 281 + 282 + if len(split_line) > 2 and split_line[1] == "->": 283 + origin_state = split_line[0].replace('"', '').replace(',', '_') 284 + dest_state = split_line[2].replace('"', '').replace(',', '_') 285 + possible_events = "".join(split_line[split_line.index("label") + 2:-1]).replace('"', '') 301 286 for event in possible_events.split("\\n"): 302 287 event, *constr = event.split(";") 303 288 if constr: ··· 314 287 self.self_loop_reset_events.add(event) 315 288 matrix[states_dict[origin_state]][events_dict[event]] = dest_state 316 289 else: 317 - state = self.__dot_lines[cursor].split("label")[1].split('"')[1] 290 + state = line.split("label")[1].split('"')[1] 318 291 state, *constr = state.replace(" ", "").split("\\n") 319 292 if constr: 320 293 constraints[_StateConstraintKey(states_dict[state])] = constr 321 - cursor += 1 322 294 323 295 return matrix, constraints 324 296 325 297 def __store_init_events(self) -> tuple[list[bool], list[bool]]: 326 298 events_start = [False] * len(self.events) 327 299 events_start_run = [False] * len(self.events) 328 - for i, _ in enumerate(self.events): 300 + for i in range(len(self.events)): 329 301 curr_event_will_init = 0 330 302 curr_event_from_init = False 331 303 curr_event_used = 0 332 - for j, _ in enumerate(self.states): 304 + for j in range(len(self.states)): 333 305 if self.function[j][i] != self.invalid_state_str: 334 306 curr_event_used += 1 335 307 if self.function[j][i] == self.initial_state: