Skip to content

Commit 9859f9f

Browse files
ehildenbrv-auditor
andauthored
Code re-organizations prepping for KCFG refactors (#486)
Part of #471, #413 As I was working towards the goal of better BMC prover visualization, it's clear we need: - Proof specific viewers, - Fixing the BMC checker off-by-one-error, and - Trimming of the structure stored in the KCFG (removing the notion of `target`, `expanded`). This makes the first least controversial steps towards those changes, including: - Method `KCFG.replace_node` now replaces the node in place, so that the node identifier doesn't change. This is possible now because the node identifier is not tied to the contents of the node, and makes tracking things like `init/target/terminal/bounded` sets externally in the `*Proof` classes more feasible even if nodes are being simplified for example. - The notion of a unique `target` and `init` is added to the `APRProof` class, in preparation for removing the `target` notion from `KCFG`. - Add fields `target` and `init` to `APRProof` state, which explicitly call out the unique init/target states. - Move `KCFGExplore.target_subsume => APRProver._check_subsumed`, which makes use of the explicitly called out `init/target` states. - Moves `KCFG.path_constraints => APRProof.path_constraints`, because it relies on a unique target node. - The notion of `stuck` nodes is moved into `APRProof` state as `terminal` nodes. - The field `_terminal_nodes` is added to `APRProof` state. - `APRProver._check_terminal` directly manipulates this set instead of `stuck/expanded` set. - The notion of `APRProof.terminal` is added, which returns the set of nodes in the `APRProof._terminal_nodes` set. - The notion of `APRProof.pending` is added, which returns the set of `KCFG.frontier` nodes that are _not_ in the proofs `terminal_nodes` set. - The `APRProof.terminal` and `APRProof.pending` sets are used for determining `APRProof.status`. - The `APRBMCProof.bounded` set is added, which similarly tracks the bounded set of nodes directly. This is used in computing `APRBMCProof.pending` and `APRBMCProof.status`. - Methods `APRProof.from_claim` and `APRBMCProof.from_claim_with_bmc_depth` are added for reading these proof types directly from claims (and tests are updated appropriately). - Method `APRBMCProof.dict` is refactored to use `APRProof.dict` directly first, then add the needed information to it. - Methods `KCFG.node_attrs => KCFGShow.node_attrs` and `KCFG.node_short_info => KCFGShow.node_short_info` are moved to localize them to use site. - Adds `Proof.json` as shortcut for `json.dumps(Proof.dict)`. - For each of the `integration/kcfg` tests that do `APRProof`s, adds logging at `info` level for the generated KCFG, to make debugging them easier by just throwing a flag. --------- Co-authored-by: devops <[email protected]>
1 parent 87e0ee0 commit 9859f9f

File tree

11 files changed

+269
-179
lines changed

11 files changed

+269
-179
lines changed

pyk/package/version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.324
1+
0.1.325

pyk/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
44

55
[tool.poetry]
66
name = "pyk"
7-
version = "0.1.324"
7+
version = "0.1.325"
88
description = ""
99
authors = [
1010
"Runtime Verification, Inc. <[email protected]>",

pyk/src/pyk/kcfg/explore.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -369,20 +369,6 @@ def section_edge(
369369
new_depth += section_depth
370370
return tuple(new_nodes)
371371

372-
def target_subsume(
373-
self,
374-
kcfg: KCFG,
375-
node: KCFG.Node,
376-
) -> bool:
377-
target_node = kcfg.get_unique_target()
378-
_LOGGER.info(f'Checking subsumption into target state {self.id}: {shorten_hashes((node.id, target_node.id))}')
379-
csubst = self.cterm_implies(node.cterm, target_node.cterm)
380-
if csubst is not None:
381-
kcfg.create_cover(node.id, target_node.id, csubst=csubst)
382-
_LOGGER.info(f'Subsumed into target node {self.id}: {shorten_hashes((node.id, target_node.id))}')
383-
return True
384-
return False
385-
386372
def extend(
387373
self,
388374
kcfg: KCFG,

pyk/src/pyk/kcfg/kcfg.py

Lines changed: 14 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@
1616
remove_source_attributes,
1717
rename_generated_vars,
1818
)
19-
from ..prelude.ml import mlAnd, mlTop
2019
from ..utils import single
2120

2221
if TYPE_CHECKING:
23-
from collections.abc import Callable, Iterable, Mapping
22+
from collections.abc import Iterable, Mapping
2423
from types import TracebackType
2524
from typing import Any
2625

@@ -253,21 +252,21 @@ def stuck(self) -> list[Node]:
253252
return [node for node in self.nodes if self.is_stuck(node.id)]
254253

255254
@staticmethod
256-
def from_claim(defn: KDefinition, claim: KClaim) -> KCFG:
255+
def from_claim(defn: KDefinition, claim: KClaim) -> tuple[KCFG, NodeIdLike, NodeIdLike]:
257256
cfg = KCFG()
258257
claim_body = claim.body
259258
claim_body = defn.instantiate_cell_vars(claim_body)
260259
claim_body = rename_generated_vars(claim_body)
261260

262261
claim_lhs = CTerm.from_kast(extract_lhs(claim_body)).add_constraint(bool_to_ml_pred(claim.requires))
263-
init_state = cfg.create_node(claim_lhs)
264-
cfg.add_init(init_state.id)
262+
init_node = cfg.create_node(claim_lhs)
263+
cfg.add_init(init_node.id)
265264

266265
claim_rhs = CTerm.from_kast(extract_rhs(claim_body)).add_constraint(bool_to_ml_pred(claim.ensures))
267-
target_state = cfg.create_node(claim_rhs)
268-
cfg.add_target(target_state.id)
266+
target_node = cfg.create_node(claim_rhs)
267+
cfg.add_target(target_node.id)
269268

270-
return cfg
269+
return cfg, init_node.id, target_node.id
271270

272271
def to_dict(self) -> dict[str, Any]:
273272
nodes = [node.to_dict() for node in self.nodes]
@@ -359,15 +358,6 @@ def to_json(self) -> str:
359358
def from_json(s: str) -> KCFG:
360359
return KCFG.from_dict(json.loads(s))
361360

362-
def node_short_info(self, node: Node, node_printer: Callable[[CTerm], Iterable[str]] | None = None) -> list[str]:
363-
attrs = self.node_attrs(node.id) + ['@' + alias for alias in sorted(self.aliases(node.id))]
364-
attr_string = ' (' + ', '.join(attrs) + ')' if attrs else ''
365-
node_header = str(node.id) + attr_string
366-
node_strs = [node_header]
367-
if node_printer:
368-
node_strs.extend(f' {nl}' for nl in node_printer(node.cterm))
369-
return node_strs
370-
371361
def get_unique_init(self) -> Node:
372362
return single(self.init)
373363

@@ -466,40 +456,13 @@ def remove_node(self, node_id: NodeIdLike) -> None:
466456
for alias in [alias for alias, id in self._aliases.items() if id == node_id]:
467457
self.remove_alias(alias)
468458

469-
def replace_node(self, node_id: NodeIdLike, new_cterm: CTerm) -> int:
470-
# Remove old node, record data
471-
node = self.node(node_id)
472-
in_edges = self.edges(target_id=node.id)
473-
out_edges = self.edges(source_id=node.id)
474-
in_covers = self.covers(target_id=node.id)
475-
out_covers = self.covers(source_id=node.id)
476-
init = self.is_init(node.id)
477-
target = self.is_target(node.id)
478-
expanded = self.is_expanded(node.id)
479-
in_expanded = {edge.source.id: self.is_expanded(edge.source.id) for edge in in_edges}
480-
self.remove_node(node.id)
481-
482-
# Add the new, update data
483-
new_node = self.create_node(new_cterm)
484-
for in_edge in in_edges:
485-
self.create_edge(in_edge.source.id, new_node.id, in_edge.depth)
486-
for out_edge in out_edges:
487-
self.create_edge(new_node.id, out_edge.target.id, out_edge.depth)
488-
for in_cover in in_covers:
489-
self.create_cover(in_cover.source.id, new_node.id, csubst=in_cover.csubst)
490-
for out_cover in out_covers:
491-
self.create_cover(new_node.id, out_cover.target.id, csubst=out_cover.csubst)
492-
if init:
493-
self.add_init(new_node.id)
494-
if target:
495-
self.add_target(new_node.id)
496-
if expanded:
497-
self.add_expanded(new_node.id)
498-
for nid in in_expanded:
499-
if in_expanded[nid]:
500-
self.add_expanded(nid)
501-
502-
return new_node.id
459+
def replace_node(self, node_id: NodeIdLike, cterm: CTerm) -> None:
460+
term = cterm.kast
461+
term = remove_source_attributes(term)
462+
cterm = CTerm.from_kast(term)
463+
node_id = self._resolve(node_id)
464+
node = KCFG.Node(node_id, cterm)
465+
self._nodes[node_id] = node
503466

504467
def successors(
505468
self,
@@ -805,24 +768,6 @@ def is_stuck(self, node_id: NodeIdLike) -> bool:
805768
node_id = self._resolve(node_id)
806769
return self.is_expanded(node_id) and self.is_leaf(node_id)
807770

808-
def node_attrs(self, node_id: NodeIdLike) -> list[str]:
809-
attrs = []
810-
if self.is_init(node_id):
811-
attrs.append('init')
812-
if self.is_target(node_id):
813-
attrs.append('target')
814-
if self.is_expanded(node_id):
815-
attrs.append('expanded')
816-
if self.is_stuck(node_id):
817-
attrs.append('stuck')
818-
if self.is_frontier(node_id):
819-
attrs.append('frontier')
820-
if self.is_leaf(node_id):
821-
attrs.append('leaf')
822-
if self.is_split(node_id):
823-
attrs.append('split')
824-
return attrs
825-
826771
def prune(self, node_id: NodeIdLike, keep_init: bool = True, keep_target: bool = True) -> list[int]:
827772
nodes = self.reachable_nodes(node_id)
828773
pruned_nodes = []
@@ -843,20 +788,6 @@ def shortest_path_between(
843788
return None
844789
return sorted(paths, key=(lambda path: path_length(path)))[0]
845790

846-
def path_constraints(self, final_node_id: NodeIdLike) -> KInner:
847-
path = self.shortest_path_between(self.get_unique_init().id, final_node_id)
848-
if path is None:
849-
raise ValueError(f'No path found to specified node: {final_node_id}')
850-
curr_constraint: KInner = mlTop()
851-
for edge in reversed(path):
852-
if type(edge) is KCFG.Split:
853-
assert len(edge.targets) == 1
854-
csubst = edge.splits[edge.targets[0].id]
855-
curr_constraint = mlAnd([csubst.subst.ml_pred, csubst.constraint, curr_constraint])
856-
if type(edge) is KCFG.Cover:
857-
curr_constraint = mlAnd([edge.csubst.constraint, edge.csubst.subst.apply(curr_constraint)])
858-
return mlAnd(flatten_label('#And', curr_constraint))
859-
860791
def paths_between(self, source_id: NodeIdLike, target_id: NodeIdLike) -> list[tuple[Successor, ...]]:
861792
source_id = self._resolve(source_id)
862793
target_id = self._resolve(target_id)

pyk/src/pyk/kcfg/show.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,37 @@ def __init__(
4444
):
4545
self.kprint = kprint
4646

47+
@staticmethod
48+
def node_attrs(kcfg: KCFG, node: KCFG.Node) -> list[str]:
49+
attrs = []
50+
if kcfg.is_init(node.id):
51+
attrs.append('init')
52+
if kcfg.is_target(node.id):
53+
attrs.append('target')
54+
if kcfg.is_expanded(node.id):
55+
attrs.append('expanded')
56+
if kcfg.is_stuck(node.id):
57+
attrs.append('stuck')
58+
if kcfg.is_frontier(node.id):
59+
attrs.append('frontier')
60+
if kcfg.is_leaf(node.id):
61+
attrs.append('leaf')
62+
if kcfg.is_split(node.id):
63+
attrs.append('split')
64+
return attrs
65+
66+
@staticmethod
67+
def node_short_info(
68+
kcfg: KCFG, node: KCFG.Node, node_printer: Callable[[CTerm], Iterable[str]] | None = None
69+
) -> list[str]:
70+
attrs = KCFGShow.node_attrs(kcfg, node) + ['@' + alias for alias in sorted(kcfg.aliases(node.id))]
71+
attr_string = ' (' + ', '.join(attrs) + ')' if attrs else ''
72+
node_header = str(node.id) + attr_string
73+
node_strs = [node_header]
74+
if node_printer:
75+
node_strs.extend(f' {nl}' for nl in node_printer(node.cterm))
76+
return node_strs
77+
4778
def pretty_segments(
4879
self,
4980
kcfg: KCFG,
@@ -67,7 +98,7 @@ def _green(text: str) -> str:
6798
return '\033[32m' + text + '\033[0m'
6899

69100
def _print_node(node: KCFG.Node) -> list[str]:
70-
short_info = kcfg.node_short_info(node, node_printer=node_printer)
101+
short_info = KCFGShow.node_short_info(kcfg, node, node_printer=node_printer)
71102
if kcfg.is_frontier(node.id):
72103
short_info[0] = _bold(short_info[0])
73104
return short_info
@@ -377,8 +408,8 @@ def _short_label(label: str) -> str:
377408
graph = Digraph()
378409

379410
for node in kcfg.nodes:
380-
label = '\n'.join(kcfg.node_short_info(node, node_printer=node_printer))
381-
class_attrs = ' '.join(kcfg.node_attrs(node.id))
411+
label = '\n'.join(KCFGShow.node_short_info(kcfg, node, node_printer=node_printer))
412+
class_attrs = ' '.join(KCFGShow.node_attrs(kcfg, node))
382413
attrs = {'class': class_attrs} if class_attrs else {}
383414
graph.node(name=node.id, label=label, **attrs)
384415

pyk/src/pyk/proof/proof.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ def read_proof(cls: type[Proof], id: str, proof_dir: Path) -> Proof:
101101

102102
raise ValueError(f'Could not load Proof from file {id}: {proof_path}')
103103

104+
@property
105+
def json(self) -> str:
106+
return json.dumps(self.dict)
107+
104108
@property
105109
def summary(self) -> Iterable[str]:
106110
return [

0 commit comments

Comments
 (0)