|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
| 3 | +import heapq |
3 | 4 | import re |
4 | 5 | from pathlib import Path |
5 | 6 | from typing import TYPE_CHECKING, Sequence |
@@ -177,53 +178,62 @@ def classify(node_id: int) -> str: |
177 | 178 | reachable_leaf_count = 0 |
178 | 179 | leaf_lines: list[str] = [] |
179 | 180 |
|
180 | | - def _path_nodes(source_id: int, path: Sequence[KCFG.Successor]) -> list[int]: |
| 181 | + def _successor_edges(source_id: int) -> list[tuple[int, int]]: |
181 | 182 | from pyk.kcfg.kcfg import KCFG as _KCFG |
182 | 183 |
|
183 | | - node_ids = [source_id] |
184 | | - current = source_id |
185 | | - for succ in path: |
186 | | - target_id: int | None = None |
187 | | - if isinstance(succ, _KCFG.EdgeLike): |
188 | | - target_id = succ.target.id |
189 | | - elif isinstance(succ, _KCFG.MultiEdge): |
190 | | - targets = list(succ.targets) |
191 | | - if len(targets) == 1: |
192 | | - target_id = targets[0].id |
193 | | - if target_id is not None and target_id != current: |
194 | | - node_ids.append(target_id) |
195 | | - current = target_id |
196 | | - return node_ids |
197 | | - |
198 | | - for leaf in sorted(leaves, key=lambda n: n.id): |
199 | | - paths = kcfg.paths_between(proof.init, leaf.id) |
200 | | - if not paths: |
201 | | - leaf_lines.append(f' leaf {leaf.id}: unreachable from init') |
202 | | - continue |
| 184 | + edges: list[tuple[int, int]] = [] |
| 185 | + for succ in kcfg.successors(source_id): |
| 186 | + match succ: |
| 187 | + case _KCFG.Edge(target=target, depth=depth): |
| 188 | + edges.append((target.id, depth)) |
| 189 | + case _KCFG.MergedEdge(target=target, edges=merged_edges): |
| 190 | + edges.append((target.id, min(edge.depth for edge in merged_edges))) |
| 191 | + case _KCFG.Cover(target=target): |
| 192 | + edges.append((target.id, 0)) |
| 193 | + case _KCFG.Split(targets=targets): |
| 194 | + edges.extend((target.id, 0) for target in targets) |
| 195 | + case _KCFG.NDBranch(targets=targets): |
| 196 | + edges.extend((target.id, 1) for target in targets) |
| 197 | + case _: |
| 198 | + raise ValueError(f'Cannot handle Successor type: {type(succ)}') |
| 199 | + return edges |
203 | 200 |
|
204 | | - path_infos: list[tuple[int, tuple[int, ...]]] = [] |
205 | | - seen_sequences: set[tuple[int, ...]] = set() |
| 201 | + shortest_steps: dict[int, int] = {proof.init: 0} |
| 202 | + shortest_prev: dict[int, int] = {} |
| 203 | + worklist: list[tuple[int, int]] = [(0, proof.init)] |
206 | 204 |
|
207 | | - for path in paths: |
208 | | - steps = kcfg.path_length(path) |
209 | | - node_seq = tuple(_path_nodes(proof.init, path)) |
210 | | - if node_seq in seen_sequences: |
211 | | - continue |
212 | | - seen_sequences.add(node_seq) |
213 | | - path_infos.append((steps, node_seq)) |
| 205 | + while worklist: |
| 206 | + curr_steps, node_id = heapq.heappop(worklist) |
| 207 | + if curr_steps != shortest_steps.get(node_id): |
| 208 | + continue |
| 209 | + for target_id, weight in sorted(_successor_edges(node_id)): |
| 210 | + next_steps = curr_steps + weight |
| 211 | + prev_steps = shortest_steps.get(target_id) |
| 212 | + # Keep the first equal-cost predecessor. Rewriting predecessors on |
| 213 | + # ties can create zero-cost cycles through Cover/Split edges and |
| 214 | + # make path reconstruction loop forever. |
| 215 | + if prev_steps is None or next_steps < prev_steps: |
| 216 | + shortest_steps[target_id] = next_steps |
| 217 | + shortest_prev[target_id] = node_id |
| 218 | + heapq.heappush(worklist, (next_steps, target_id)) |
| 219 | + |
| 220 | + def _shortest_path_nodes(target_id: int) -> list[int]: |
| 221 | + node_ids = [target_id] |
| 222 | + while node_ids[-1] != proof.init: |
| 223 | + node_ids.append(shortest_prev[node_ids[-1]]) |
| 224 | + node_ids.reverse() |
| 225 | + return node_ids |
214 | 226 |
|
215 | | - if not path_infos: |
| 227 | + for leaf in sorted(leaves, key=lambda n: n.id): |
| 228 | + min_steps = shortest_steps.get(leaf.id) |
| 229 | + if min_steps is None: |
216 | 230 | leaf_lines.append(f' leaf {leaf.id}: unreachable from init') |
217 | 231 | continue |
218 | 232 |
|
219 | | - total_steps += min(steps for steps, _ in path_infos) |
| 233 | + total_steps += min_steps |
220 | 234 | reachable_leaf_count += 1 |
221 | | - path_infos.sort(key=lambda info: (info[0], info[1])) |
222 | | - |
223 | | - for idx, (steps, node_seq) in enumerate(path_infos, start=1): |
224 | | - suffix = '' if len(path_infos) == 1 else f' (path {idx}/{len(path_infos)})' |
225 | | - seq_str = ' -> '.join(str(nid) for nid in node_seq) |
226 | | - leaf_lines.append(f' leaf {leaf.id}{suffix}: steps {steps}, path {seq_str}') |
| 235 | + seq_str = ' -> '.join(str(nid) for nid in _shortest_path_nodes(leaf.id)) |
| 236 | + leaf_lines.append(f' leaf {leaf.id}: shortest steps {min_steps}, path {seq_str}') |
227 | 237 |
|
228 | 238 | lines.append(f' total leaves (non-root): {len(leaves)}') |
229 | 239 | lines.append(f' reachable leaves : {reachable_leaf_count}') |
|
0 commit comments