Skip to content
This repository was archived by the owner on Apr 25, 2024. It is now read-only.

Commit 5ef6cda

Browse files
ehildenbrv-auditor
andauthored
Steps towards RPC prover (#112)
These changes are what I needed to have a first working version of KProve implemented fully here: runtimeverification/evm-semantics#1443. In particular: - Some KAst <-> Kore problems are addressed: - If the same variable shows up multiple times in `KDefinition.sort_vars`, but with different sorts, instead of just giving up we'll check if one sort is a subsort of the other. If so, we'll return the smaller sort. Test added to `test_kast_to_kore`. - The conversions for `Exists` are added. Test added to `test_bidirectional`. - `KProve.execute` method now returns `CTerm` instead of `KInner`, as it shouldn't be able to return `mlTop()` or `mlBottom()` anyway. - `KCFG.create_cover` now allows you to specify a witness manually instead of trying to compute it for you. (De)Serialization requires that we can do `Subst.{from_dict,to_dict}`, so that functionality is added too. - `KProve.implies` method is added as a wrapper around the `implies` RPC endpoint. Tests are added that make sure it's giving back sane results. By default, we much existentially bind variables in the consequent that are not present in the antecedent to get anything useful out of the endpoint. Tests added to `test_implies` in `test_kprove`. Co-authored-by: devops <[email protected]>
1 parent 562f452 commit 5ef6cda

File tree

11 files changed

+199
-35
lines changed

11 files changed

+199
-35
lines changed

k-files/simple-proofs.k

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ module SIMPLE-PROOFS
1818
syntax Baz ::= "foo-bar" | "baz" [klabel(baz), symbol]
1919

2020
syntax BarHolder ::= hold ( Foo , Bar ) [klabel(barholder), symbol]
21+
| hold2 ( Bar , Baz ) [klabel(barholder2), symbol]
2122

2223
rule <k> foo => bar ... </k>
2324
<state> ... 3 |-> N ... </state>

package/version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.68
1+
0.1.69

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.68"
7+
version = "0.1.69"
88
description = ""
99
authors = [
1010
"Runtime Verification, Inc. <[email protected]>",

src/integration_tests/test_kore_to_kast.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from pyk.kast.inner import KApply, KLabel, KSequence, KSort, KToken, KVariable
2-
from pyk.kore.syntax import DV, And, App, Ceil, Equals, EVar, LeftAssoc, Not, RightAssoc, SortApp, String
2+
from pyk.kore.syntax import DV, And, App, Ceil, Equals, EVar, Exists, LeftAssoc, Not, RightAssoc, SortApp, String
33
from pyk.ktool import KompileBackend
44
from pyk.ktool.kprint import SymbolTable
55
from pyk.prelude.kbool import TRUE
@@ -108,6 +108,22 @@ def test_bidirectional(self) -> None:
108108
[KVariable('X', sort=KSort('Bool'))],
109109
),
110110
),
111+
(
112+
'ml-exists',
113+
KSort('Bool'),
114+
Exists(
115+
SortApp('SortBool'),
116+
EVar('VarX', SortApp('SortBool')),
117+
EVar('VarX', SortApp('SortBool')),
118+
),
119+
KApply(
120+
KLabel('#Exists', [KSort('Bool')]),
121+
[
122+
KVariable('X', sort=KSort('Bool')),
123+
KVariable('X', sort=KSort('Bool')),
124+
],
125+
),
126+
),
111127
(
112128
'ml-not',
113129
KSort('Bool'),
@@ -222,6 +238,19 @@ def test_kast_to_kore(self) -> None:
222238
),
223239
KApply('barholder', [KApply('foo', [KVariable('B', sort=KSort('Baz'))]), KVariable('B')]),
224240
),
241+
(
242+
'variable-with-multi-sort',
243+
KSort('BarHolder'),
244+
App(
245+
'Lblbarholder2',
246+
[],
247+
[
248+
App('inj', [SortApp('SortBaz'), SortApp('SortBar')], [EVar('VarX', SortApp('SortBaz'))]),
249+
EVar('VarX', SortApp('SortBaz')),
250+
],
251+
),
252+
KApply('barholder2', [KVariable('X', sort=KSort('Baz')), KVariable('X', sort=KSort('Bar'))]),
253+
),
225254
)
226255
for name, sort, kore, kast in kore_kast_pairs:
227256
with self.subTest(name):

src/integration_tests/test_kprove.py

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
from typing import List, Tuple
22

33
from pyk.cterm import CTerm
4-
from pyk.kast.inner import KApply, KAtt, KSequence, KToken, KVariable
4+
from pyk.kast.inner import KApply, KAtt, KSequence, KSort, KToken, KVariable, Subst
55
from pyk.kast.manip import get_cell
66
from pyk.kast.outer import KClaim, KRule
77
from pyk.ktool import KompileBackend
88
from pyk.ktool.kprint import SymbolTable
99
from pyk.prelude.kbool import BOOL
10+
from pyk.prelude.kint import intToken
1011

1112
from .kprove_test import KProveTest
1213

@@ -72,13 +73,13 @@ def _config(k: str, state: str) -> CTerm:
7273
actual_depth, _actual_state, _actual_next_states = self.kprove.execute(
7374
_config(pre_k, pre_state), depth=depth
7475
)
75-
actual_k = self.kprove.pretty_print(get_cell(_actual_state, 'K_CELL'))
76-
actual_state = self.kprove.pretty_print(get_cell(_actual_state, 'STATE_CELL'))
76+
actual_k = self.kprove.pretty_print(get_cell(_actual_state.kast, 'K_CELL'))
77+
actual_state = self.kprove.pretty_print(get_cell(_actual_state.kast, 'STATE_CELL'))
7778

7879
actual_next_states = [
7980
(
80-
self.kprove.pretty_print(get_cell(s, 'K_CELL')),
81-
self.kprove.pretty_print(get_cell(s, 'STATE_CELL')),
81+
self.kprove.pretty_print(get_cell(s.kast, 'K_CELL')),
82+
self.kprove.pretty_print(get_cell(s.kast, 'STATE_CELL')),
8283
)
8384
for s in _actual_next_states
8485
]
@@ -233,13 +234,13 @@ def _config(k: str, state: str) -> CTerm:
233234
actual_depth, _actual_state, _actual_next_states = self.kprove.execute(
234235
_config(pre_k, pre_state), depth=depth
235236
)
236-
actual_k = self.kprove.pretty_print(get_cell(_actual_state, 'K_CELL'))
237-
actual_state = self.kprove.pretty_print(get_cell(_actual_state, 'STATE_CELL'))
237+
actual_k = self.kprove.pretty_print(get_cell(_actual_state.kast, 'K_CELL'))
238+
actual_state = self.kprove.pretty_print(get_cell(_actual_state.kast, 'STATE_CELL'))
238239

239240
actual_next_states = [
240241
(
241-
self.kprove.pretty_print(get_cell(s, 'K_CELL')),
242-
self.kprove.pretty_print(get_cell(s, 'STATE_CELL')),
242+
self.kprove.pretty_print(get_cell(s.kast, 'K_CELL')),
243+
self.kprove.pretty_print(get_cell(s.kast, 'STATE_CELL')),
243244
)
244245
for s in _actual_next_states
245246
]
@@ -249,3 +250,54 @@ def _config(k: str, state: str) -> CTerm:
249250
self.assertEqual(actual_state, expected_state)
250251
self.assertEqual(actual_depth, expected_depth)
251252
self.assertCountEqual(actual_next_states, expected_next_states)
253+
254+
def test_implies(self) -> None:
255+
def _config(k: str, state: str) -> CTerm:
256+
_k_parsed = self.kprove.parse_token(KToken(k, 'Pgm'), as_rule=True)
257+
_state_parsed = self.kprove.parse_token(KToken(state, 'Map'), as_rule=True)
258+
return CTerm(
259+
KApply(
260+
'<generatedTop>',
261+
[
262+
KApply(
263+
'<T>',
264+
(
265+
KApply('<k>', [KSequence([_k_parsed])]),
266+
KApply('<state>', [_state_parsed]),
267+
),
268+
),
269+
KVariable('GENERATED_COUNTER_CELL'),
270+
],
271+
)
272+
)
273+
274+
# Given
275+
test_data = (
276+
(
277+
'constant-subst',
278+
('int $n , $s ; $n = X ;', '.Map'),
279+
('int $n , $s ; $n = 3 ;', '.Map'),
280+
Subst({'X': intToken(3)}),
281+
),
282+
(
283+
'variable-subst',
284+
('int $n , $s ; $n = X ;', '.Map'),
285+
('int $n , $s ; $n = Y ;', '.Map'),
286+
Subst({'X': KVariable('Y', sort=KSort('AExp'))}),
287+
),
288+
)
289+
290+
for (
291+
name,
292+
(antecedent_k, antecedent_map),
293+
(consequent_k, consequent_map),
294+
expected_subst,
295+
) in test_data:
296+
with self.subTest(name):
297+
antecedent = _config(antecedent_k, antecedent_map)
298+
consequent = _config(consequent_k, consequent_map)
299+
# When
300+
actual_subst = self.kprove.implies(antecedent, consequent)
301+
302+
# Then
303+
self.assertEqual(actual_subst, expected_subst)

src/pyk/kast/inner.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ def __mul__(self, other: 'Subst') -> 'Subst':
9292
def __call__(self, term: KInner) -> KInner:
9393
return self.apply(term)
9494

95+
@staticmethod
96+
def from_dict(d: Dict[str, Any]) -> 'Subst':
97+
return Subst({k: KInner.from_dict(v) for k, v in d.items()})
98+
99+
def to_dict(self) -> Dict[str, Any]:
100+
return {k: v.to_dict() for k, v in self.items()}
101+
95102
def minimize(self) -> 'Subst':
96103
return Subst({k: v for k, v in self.items() if v != KVariable(k)})
97104

src/pyk/kast/outer.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,10 +1007,14 @@ def sort_vars_subst(self, kast: KInner) -> Subst:
10071007
for v in voccurrences[1:]:
10081008
if vsort is None and v.sort is not None:
10091009
vsort = v.sort
1010-
elif vsort is not None and v.sort is not None and vsort != v.sort:
1011-
raise ValueError(
1012-
f'Could not find common subsort among variable occurrences: {voccurrences}'
1013-
)
1010+
elif vsort is not None and v.sort is not None:
1011+
if vsort != v.sort:
1012+
if v.sort in self.subsorts(vsort):
1013+
vsort = v.sort
1014+
elif vsort not in self.subsorts(v.sort):
1015+
raise ValueError(
1016+
f'Could not find common subsort among variable occurrences: {voccurrences}'
1017+
)
10141018
subst[vname] = KVariable(vname, sort=vsort)
10151019
return Subst(subst)
10161020

src/pyk/kcfg.py

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from .kast.manip import ml_pred_to_bool, mlAnd, remove_source_attributes, simplify_bool
1515
from .kast.outer import KClaim, KRule
1616
from .ktool import KPrint
17+
from .prelude.ml import mlTop
1718
from .utils import add_indent, compare_short_hashes, shorten_hash
1819

1920

@@ -95,20 +96,35 @@ class Cover(EdgeLike):
9596
subst: Subst
9697
constraint: KInner
9798

98-
def __init__(self, source: 'KCFG.Node', target: 'KCFG.Node'):
99+
def __init__(
100+
self,
101+
source: 'KCFG.Node',
102+
target: 'KCFG.Node',
103+
subst: Optional[Subst] = None,
104+
constraint: Optional[KInner] = None,
105+
):
99106
object.__setattr__(self, 'source', source)
100107
object.__setattr__(self, 'target', target)
101108

102-
match_res = target.cterm.match_with_constraint(source.cterm)
103-
if not match_res:
104-
raise ValueError(f'No matching between: {source.id} and {target.id}')
109+
if subst is None and constraint is not None:
110+
subst = Subst({})
111+
elif subst is not None and constraint is None:
112+
constraint = mlTop()
113+
elif subst is None and constraint is None:
114+
match_res = target.cterm.match_with_constraint(source.cterm)
115+
if not match_res:
116+
raise ValueError(f'No matching between: {source.id} and {target.id}')
117+
subst, constraint = match_res
105118

106-
subst, constraint = match_res
107119
object.__setattr__(self, 'subst', subst)
108120
object.__setattr__(self, 'constraint', constraint)
109121

110122
def to_dict(self) -> Dict[str, Any]:
111-
return {'source': self.source.id, 'target': self.target.id}
123+
return {
124+
'source': self.source.id,
125+
'target': self.target.id,
126+
'witness': {'subst': self.subst.to_dict(), 'constraint': self.constraint.to_dict()},
127+
}
112128

113129
def pretty(self, kprint: KPrint) -> Iterable[str]:
114130
return [
@@ -255,7 +271,13 @@ def resolve(node_id: str) -> str:
255271
for cover_dict in dct.get('covers') or []:
256272
source_id = resolve(cover_dict['source'])
257273
target_id = resolve(cover_dict['target'])
258-
cfg.create_cover(source_id, target_id)
274+
subst = None
275+
constraint = None
276+
if 'subst' in cover_dict:
277+
subst = Subst.from_dict(cover_dict['subst'])
278+
if 'constraint' in cover_dict:
279+
constraint = KInner.from_dict(cover_dict['constraint'])
280+
cfg.create_cover(source_id, target_id, subst=subst, constraint=constraint)
259281

260282
for init_id in dct.get('init') or []:
261283
cfg.add_init(resolve(init_id))
@@ -600,7 +622,9 @@ def contains_cover(self, cover: Cover) -> bool:
600622
return cover == other
601623
return False
602624

603-
def create_cover(self, source_id: str, target_id: str) -> Cover:
625+
def create_cover(
626+
self, source_id: str, target_id: str, subst: Optional[Subst] = None, constraint: Optional[KInner] = None
627+
) -> Cover:
604628
source = self.node(source_id)
605629
target = self.node(target_id)
606630

@@ -610,7 +634,7 @@ def create_cover(self, source_id: str, target_id: str) -> Cover:
610634
if source.id not in self._covers:
611635
self._covers[source.id] = {}
612636

613-
cover = KCFG.Cover(source, target)
637+
cover = KCFG.Cover(source, target, subst=subst, constraint=constraint)
614638
self._covers[source.id][target.id] = cover
615639
return cover
616640

src/pyk/kore/rpc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def request(self, method: str, **params: Any) -> Dict[str, Any]:
110110
_LOGGER.info(f'Sending request to {server_addr}: {req}')
111111
self._sock.sendall(req.encode())
112112
_LOGGER.info(f'Waiting for response from {server_addr}...')
113-
resp = self._file.readline()
113+
resp = self._file.readline().rstrip()
114114
_LOGGER.info(f'Received response from {server_addr}: {resp}')
115115

116116
data = json.loads(resp)

src/pyk/ktool/kprint.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
read_kast_definition,
3131
)
3232
from ..kore.parser import KoreParser
33-
from ..kore.syntax import DV, And, App, Assoc, Ceil, Equals, EVar, Not, Pattern, SortApp, String
33+
from ..kore.syntax import DV, And, App, Assoc, Ceil, Equals, EVar, Exists, Not, Pattern, SortApp, String
3434
from ..prelude.bytes import BYTES, bytesToken
3535
from ..prelude.k import DOTS, EMPTY_K
3636
from ..prelude.kbool import TRUE
@@ -324,6 +324,13 @@ def _kore_to_kast(self, kore: Pattern) -> Optional[KInner]:
324324
if arg is not None:
325325
return KApply(KLabel('#Not', [psort]), [arg])
326326

327+
elif type(kore) is Exists:
328+
psort = KSort(kore.sort.name[4:])
329+
var = self._kore_to_kast(kore.var)
330+
body = self._kore_to_kast(kore.pattern)
331+
if var is not None and type(var) is KVariable and body is not None:
332+
return KApply(KLabel('#Exists', [psort]), [var, body])
333+
327334
elif type(kore) is Equals:
328335
osort = KSort(kore.op_sort.name[4:])
329336
psort = KSort(kore.sort.name[4:])
@@ -418,6 +425,7 @@ def _get_sort(_ki: KInner) -> Optional[KSort]:
418425

419426
elif len(kast.label.params) == 1:
420427
psort = kast.label.params[0]
428+
421429
if kast.label.name == '#And' and kast.arity == 2:
422430
larg = self._kast_to_kore(kast.args[0], sort=psort)
423431
rarg = self._kast_to_kore(kast.args[1], sort=psort)
@@ -426,6 +434,7 @@ def _get_sort(_ki: KInner) -> Optional[KSort]:
426434
if sort is not None:
427435
_and = self._add_sort_injection(_and, psort, sort)
428436
return _and
437+
429438
elif kast.label.name == '#Not' and kast.arity == 1:
430439
arg = self._kast_to_kore(kast.args[0], sort=psort)
431440
if arg is not None:
@@ -434,6 +443,15 @@ def _get_sort(_ki: KInner) -> Optional[KSort]:
434443
_not = self._add_sort_injection(_not, psort, sort)
435444
return _not
436445

446+
elif kast.label.name == '#Exists' and kast.arity == 2 and type(kast.args[0]) is KVariable:
447+
var = self._kast_to_kore(kast.args[0])
448+
body = self._kast_to_kore(kast.args[1], sort=psort)
449+
if var is not None and type(var) is EVar and body is not None:
450+
_exists: Pattern = Exists(SortApp('Sort' + psort.name), var, body)
451+
if sort is not None:
452+
_exists = self._add_sort_injection(_exists, psort, sort)
453+
return _exists
454+
437455
elif len(kast.label.params) == 2:
438456
osort = kast.label.params[0]
439457
psort = kast.label.params[1]
@@ -474,7 +492,9 @@ def _add_sort_injection(self, pat: Pattern, isort: KSort, osort: KSort) -> Patte
474492
if isort == osort:
475493
return pat
476494
if isort not in self.definition.subsorts(osort):
477-
raise ValueError(f'Could not find injection from subsort to supersort: {isort} -> {osort}')
495+
raise ValueError(
496+
f'Could not find injection from subsort to supersort {isort} -> {osort} for pattern: {pat}'
497+
)
478498
return App('inj', [SortApp('Sort' + isort.name), SortApp('Sort' + osort.name)], [pat])
479499

480500
def pretty_print(self, kast: KAst) -> str:

0 commit comments

Comments
 (0)