Skip to content

Commit 69e12db

Browse files
authored
Update/generalize option to include lemmas at prove time (#979)
* src/kontrol/{cli,options,__main__}: add more general option --lemmas to RpcOptions, deprecate --extra-module * src/{foundry,prove}: factor out logic for loading a lemmas modules * src/kontrol/foundry: enable extra lemmas for all commands involving RPC server * kontrol/{utils,foundry}: make sure to build modules that can be included at prove time * kontrol/cli: correct CLI message * src/kontrol/utils: correct the basic lemmas file * src/kontrol/cli: correct help message * src/kontrol/{cli,options}: remove k_gen_args and inline into build_args * src/kontrol/foundry: correct build name
1 parent a6ae726 commit 69e12db

File tree

7 files changed

+63
-69
lines changed

7 files changed

+63
-69
lines changed

src/kontrol/__main__.py

+6
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@ def exec_build(options: BuildOptions) -> None:
171171
def exec_prove(options: ProveOptions) -> None:
172172
_LOGGER.debug(options)
173173

174+
if options.extra_module is not None:
175+
_LOGGER.warning('Option --extra-module is being deprecated in favor of option --lemmas.')
176+
if options.lemmas is not None:
177+
raise ValueError('Cannot specify both --extra-module and --lemmas, prefer --lemmas.')
178+
options.lemmas = options.extra_module
179+
174180
if options.recorded_diff_state_path and options.recorded_dump_state_path:
175181
raise AssertionError('Provide only one file for recorded state updates')
176182

src/kontrol/cli.py

+22-22
Original file line numberDiff line numberDiff line change
@@ -179,23 +179,6 @@ def foundry_test_args(self) -> ArgumentParser:
179179
args.add_argument('--version', type=int, required=False, help='Version of the test to use')
180180
return args
181181

182-
@cached_property
183-
def k_gen_args(self) -> ArgumentParser:
184-
args = ArgumentParser(add_help=False)
185-
args.add_argument(
186-
'--require',
187-
dest='requires',
188-
action='append',
189-
help='Extra K requires to include in generated output.',
190-
)
191-
args.add_argument(
192-
'--module-import',
193-
dest='imports',
194-
action='append',
195-
help='Extra modules to import into generated main module.',
196-
)
197-
return args
198-
199182
@cached_property
200183
def rpc_args(self) -> ArgumentParser:
201184
args = ArgumentParser(add_help=False)
@@ -239,6 +222,15 @@ def rpc_args(self) -> ArgumentParser:
239222
type=int,
240223
help='Use existing RPC server on named port.',
241224
)
225+
args.add_argument(
226+
'--lemmas',
227+
dest='lemmas',
228+
default=None,
229+
help=(
230+
'File and extra module to include for verification (which must import the KONTROL-MAIN module).'
231+
'Format is <file>:<module name>.'
232+
),
233+
)
242234
return args
243235

244236

@@ -266,7 +258,6 @@ def parse(s: str) -> list[T]:
266258
parents=[
267259
kontrol_cli_args.logging_args,
268260
kontrol_cli_args.k_args,
269-
kontrol_cli_args.k_gen_args,
270261
kontrol_cli_args.kompile_args,
271262
kontrol_cli_args.foundry_args,
272263
config_args.config_args,
@@ -328,6 +319,18 @@ def parse(s: str) -> list[T]:
328319
action='store_true',
329320
help='Include auxiliary Kontrol lemmas.',
330321
)
322+
build.add_argument(
323+
'--require',
324+
dest='requires',
325+
action='append',
326+
help='Extra K requires to include in generated output.',
327+
)
328+
build.add_argument(
329+
'--module-import',
330+
dest='imports',
331+
action='append',
332+
help='Extra modules to import into generated main module.',
333+
)
331334

332335
state_diff_args = command_parser.add_parser(
333336
'load-state',
@@ -569,10 +572,7 @@ def parse(s: str) -> list[T]:
569572
'--extra-module',
570573
dest='extra_module',
571574
default=None,
572-
help=(
573-
'File and extra module to include for verification (which must import KONTROL-MAIN module).'
574-
'Format is <file>:<module name>.'
575-
),
575+
help='Deprecated alias for --lemmas.',
576576
)
577577
prove_args.add_argument(
578578
'--symbolic-caller',

src/kontrol/foundry.py

+22-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
set_cell,
3131
top_down,
3232
)
33-
from pyk.kast.outer import KDefinition, KFlatModule, KImport, KRequire
33+
from pyk.kast.outer import KDefinition, KFlatModule, KImport, KRequire, KRule
3434
from pyk.kcfg import KCFG
3535
from pyk.kcfg.kcfg import Step
3636
from pyk.kcfg.minimize import KCFGMinimizer
@@ -552,6 +552,22 @@ def build(self, metadata: bool) -> None:
552552
except CalledProcessError as err:
553553
raise RuntimeError(f"Couldn't forge build! {err.stderr.strip()}") from err
554554

555+
def load_lemmas(self, lemmas_id: str | None) -> KFlatModule | None:
556+
if lemmas_id is None:
557+
return None
558+
lemmas_file, lemmas_name, *_ = lemmas_id.split(':')
559+
lemmas_path = Path(lemmas_file)
560+
if not lemmas_path.is_file():
561+
raise ValueError(f'Supplied lemmas path is not a file: {lemmas_path}')
562+
modules = self.kevm.parse_modules(
563+
lemmas_path, module_name=lemmas_name, include_dirs=(kdist.get('kontrol.base'),)
564+
)
565+
lemmas_module = single(module for module in modules.modules if module.name == lemmas_name)
566+
non_rule_sentences = [sent for sent in lemmas_module.sentences if not isinstance(sent, KRule)]
567+
if non_rule_sentences:
568+
raise ValueError(f'Supplied lemmas module contains non-Rule sentences: {non_rule_sentences}')
569+
return lemmas_module
570+
555571
@cached_property
556572
def all_tests(self) -> list[str]:
557573
test_dir = os.path.join(self.profile.get('test', 'test'), '')
@@ -971,6 +987,7 @@ def foundry_show(
971987
smt_retry_limit=options.smt_retry_limit,
972988
start_server=start_server,
973989
port=options.port,
990+
extra_module=foundry.load_lemmas(options.lemmas),
974991
) as kcfg_explore:
975992
res_lines += print_failure_info(proof, kcfg_explore, options.counterexample_info)
976993
res_lines += Foundry.help_info()
@@ -1234,6 +1251,7 @@ def foundry_simplify_node(
12341251
log_fail_rewrites=options.log_fail_rewrites,
12351252
start_server=start_server,
12361253
port=options.port,
1254+
extra_module=foundry.load_lemmas(options.lemmas),
12371255
) as kcfg_explore:
12381256
new_term, _ = kcfg_explore.cterm_symbolic.simplify(cterm)
12391257
if options.replace:
@@ -1322,6 +1340,7 @@ def foundry_step_node(
13221340
log_fail_rewrites=options.log_fail_rewrites,
13231341
start_server=start_server,
13241342
port=options.port,
1343+
extra_module=foundry.load_lemmas(options.lemmas),
13251344
) as kcfg_explore:
13261345
node = options.node
13271346
for _i in range(options.repeat):
@@ -1398,6 +1417,7 @@ def foundry_section_edge(
13981417
log_fail_rewrites=options.log_fail_rewrites,
13991418
start_server=start_server,
14001419
port=options.port,
1420+
extra_module=foundry.load_lemmas(options.lemmas),
14011421
) as kcfg_explore:
14021422
kcfg_explore.section_edge(
14031423
apr_proof.kcfg,
@@ -1449,6 +1469,7 @@ def foundry_get_model(
14491469
log_fail_rewrites=options.log_fail_rewrites,
14501470
start_server=start_server,
14511471
port=options.port,
1472+
extra_module=foundry.load_lemmas(options.lemmas),
14521473
) as kcfg_explore:
14531474
for node_id in nodes:
14541475
res_lines.append('')

src/kontrol/options.py

+7-29
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class RpcOptions(Options):
6363
kore_rpc_command: str | None
6464
use_booster: bool
6565
port: int | None
66+
lemmas: str | None
6667

6768
@staticmethod
6869
def default() -> dict[str, Any]:
@@ -72,6 +73,7 @@ def default() -> dict[str, Any]:
7273
'kore_rpc_command': None,
7374
'use_booster': True,
7475
'port': None,
76+
'lemmas': None,
7577
}
7678

7779
@staticmethod
@@ -206,32 +208,6 @@ def get_argument_type() -> dict[str, Callable]:
206208
}
207209

208210

209-
class KGenOptions(Options):
210-
requires: list[str]
211-
imports: list[str]
212-
213-
@staticmethod
214-
def default() -> dict[str, Any]:
215-
return {
216-
'requires': [],
217-
'imports': [],
218-
}
219-
220-
@staticmethod
221-
def from_option_string() -> dict[str, str]:
222-
return {
223-
'require': 'requires',
224-
'module-import': 'imports',
225-
}
226-
227-
@staticmethod
228-
def get_argument_type() -> dict[str, Callable]:
229-
return {
230-
'require': list_of(str),
231-
'module-import': list_of(str),
232-
}
233-
234-
235211
class KompileTargetOptions(Options):
236212
target: KompileTarget
237213

@@ -836,14 +812,16 @@ def get_argument_type() -> dict[str, Callable]:
836812
)
837813

838814

839-
class BuildOptions(LoggingOptions, KOptions, KGenOptions, KompileOptions, FoundryOptions, KompileTargetOptions):
815+
class BuildOptions(LoggingOptions, KOptions, KompileOptions, FoundryOptions, KompileTargetOptions):
840816
regen: bool
841817
rekompile: bool
842818
forge_build: bool
843819
silence_warnings: bool
844820
metadata: bool
845821
keccak_lemmas: bool
846822
auxiliary_lemmas: bool
823+
requires: list[str]
824+
imports: list[str]
847825

848826
@staticmethod
849827
def default() -> dict[str, Any]:
@@ -856,6 +834,8 @@ def default() -> dict[str, Any]:
856834
'metadata': True,
857835
'keccak_lemmas': True,
858836
'auxiliary_lemmas': False,
837+
'requires': [],
838+
'imports': [],
859839
}
860840

861841
@staticmethod
@@ -864,7 +844,6 @@ def from_option_string() -> dict[str, str]:
864844
FoundryOptions.from_option_string()
865845
| LoggingOptions.from_option_string()
866846
| KOptions.from_option_string()
867-
| KGenOptions.from_option_string()
868847
| KompileOptions.from_option_string()
869848
| KompileTargetOptions.from_option_string()
870849
)
@@ -875,7 +854,6 @@ def get_argument_type() -> dict[str, Callable]:
875854
FoundryOptions.get_argument_type()
876855
| LoggingOptions.get_argument_type()
877856
| KOptions.get_argument_type()
878-
| KGenOptions.get_argument_type()
879857
| KompileOptions.get_argument_type()
880858
| KompileTargetOptions.get_argument_type()
881859
)

src/kontrol/prove.py

+3-15
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from collections import Counter
77
from copy import copy
88
from functools import partial
9-
from pathlib import Path
109
from subprocess import CalledProcessError
1110
from typing import TYPE_CHECKING, Any, ContextManager, NamedTuple
1211

@@ -17,7 +16,6 @@
1716
from pyk.cterm import CTerm, CTermSymbolic
1817
from pyk.kast.inner import KApply, KSequence, KSort, KVariable, Subst
1918
from pyk.kast.manip import flatten_label, free_vars, set_cell
20-
from pyk.kast.outer import KFlatModule, KRule
2119
from pyk.kcfg import KCFG, KCFGExplore
2220
from pyk.kcfg.minimize import KCFGMinimizer
2321
from pyk.kore.rpc import KoreClient, kore_server
@@ -32,7 +30,7 @@
3230
from pyk.proof import ProofStatus
3331
from pyk.proof.proof import Proof
3432
from pyk.proof.reachability import APRFailureInfo, APRProof
35-
from pyk.utils import hash_str, run_process_2, single, unique
33+
from pyk.utils import hash_str, run_process_2, unique
3634
from rich.progress import Progress, SpinnerColumn, TaskID, TextColumn, TimeElapsedColumn
3735

3836
from .foundry import Foundry, KontrolSemantics, foundry_to_xml
@@ -438,17 +436,7 @@ def create_kcfg_explore() -> KCFGExplore:
438436
rule.label for rule in foundry.kevm.definition.all_modules_dict['KONTROL-ASSERTIONS'].rules
439437
)
440438

441-
extra_lemmas_module: KFlatModule | None = None
442-
if options.extra_module:
443-
extra_module_file, extra_module_name, *_ = options.extra_module.split(':')
444-
extra_module_path = Path(extra_module_file)
445-
if not extra_module_path.is_file():
446-
raise ValueError(f'Supplied --extra-module path is not a file: {extra_module_path}')
447-
modules = foundry.kevm.parse_modules(extra_module_path, module_name=extra_module_name)
448-
extra_lemmas_module = single(module for module in modules.modules if module.name == extra_module_name)
449-
non_rule_sentences = [sent for sent in extra_lemmas_module.sentences if not isinstance(sent, KRule)]
450-
if non_rule_sentences:
451-
raise ValueError(f'Supplied --extra-module contains non-Rule sentences: {non_rule_sentences}')
439+
lemmas_module = foundry.load_lemmas(options.lemmas)
452440

453441
if progress is not None and task is not None:
454442
progress.update(
@@ -471,7 +459,7 @@ def create_kcfg_explore() -> KCFGExplore:
471459
task_id=task,
472460
maintenance_rate=options.maintenance_rate,
473461
assume_defined=options.assume_defined,
474-
extra_module=extra_lemmas_module,
462+
extra_module=lemmas_module,
475463
optimize_kcfg=options.optimize_kcfg,
476464
)
477465

src/kontrol/utils.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,10 @@ def append_to_file(file_path: Path, content: str) -> None:
146146

147147

148148
def empty_lemmas_file_contents() -> str:
149-
return """requires "foundry.md"
149+
return """requires "kontrol.md"
150150
151151
module KONTROL-LEMMAS
152+
imports KONTROL-BASE
152153
153154
// Your lemmas go here
154155
// Not sure what to do next? Try checking the documentation for writing lemmas: https://docs.runtimeverification.com/kontrol/guides/advancing-proofs/kevm-lemmas

src/tests/integration/test_foundry_prove.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1006,7 +1006,7 @@ def test_foundry_extra_lemmas(
10061006
'break_on_calls': True,
10071007
'port': server.port,
10081008
'force_sequential': force_sequential,
1009-
'extra_module': f'{TEST_DATA_DIR / lemmas_file}:XOR-LEMMAS',
1009+
'lemmas': f'{TEST_DATA_DIR / lemmas_file}:XOR-LEMMAS',
10101010
}
10111011
),
10121012
)

0 commit comments

Comments
 (0)