Skip to content

Commit 3a1fe68

Browse files
vecchiot-awstedinski
authored andcommitted
Cleanup rmc flags (rust-lang#365)
* Rearranges common flags between `rmc` and `cargo rmc` into groups in a shared file to allow better organization and easier changes in the future. * Adds help strings to all of the flags to explain their purpose.
1 parent deb6928 commit 3a1fe68

File tree

4 files changed

+182
-72
lines changed

4 files changed

+182
-72
lines changed

scripts/cargo-rmc

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import argparse
66
import glob
77
import sys
88
import rmc
9+
import rmc_flags
910
import os
1011
import pathlib
1112

@@ -19,42 +20,31 @@ def main():
1920
if len(sys.argv) >= 2 and sys.argv[1] == "rmc":
2021
del sys.argv[1]
2122

22-
parser = argparse.ArgumentParser(description="Verify a Rust crate")
23-
parser.add_argument("crate", help="crate to verify", nargs="?")
24-
parser.add_argument("--crate", help="crate to verify", dest="crate_flag")
25-
parser.add_argument("--verbose", "-v", action="store_true")
26-
parser.add_argument("--quiet", "-q", action="store_true")
27-
parser.add_argument("--debug", action="store_true")
28-
parser.add_argument("--keep-temps", action="store_true")
29-
parser.add_argument("--gen-c", action="store_true")
30-
parser.add_argument("--mangler", default="v0")
31-
parser.add_argument("--visualize", action="store_true")
32-
parser.add_argument("--c-lib", nargs="*", default=[], action="extend")
33-
parser.add_argument("--srcdir", default=".")
34-
parser.add_argument("--target-dir", default="target",
35-
help="Directory for all generated artifacts")
36-
parser.add_argument("--wkdir", default=".")
37-
parser.add_argument("--function", default="main")
38-
parser.add_argument("--no-default-checks", action="store_true", help="Disable all default checks")
39-
parser.add_argument("--no-memory-safety-checks", action="store_true", help="Disable default memory safety checks")
40-
parser.add_argument("--no-overflow-checks", action="store_true", help="Disable default overflow checks")
41-
parser.add_argument("--no-unwinding-checks", action="store_true", help="Disable default unwinding checks")
42-
parser.add_argument("--dry-run", action="store_true", help="Print commands instead of running them")
43-
parser.add_argument("--cbmc-args", nargs=argparse.REMAINDER,
44-
default=[], help="Pass through directly to CBMC")
23+
parser = argparse.ArgumentParser(prog="cargo rmc", description="Verify a Rust crate. For more information, see https://github.com/model-checking/rmc.")
24+
25+
crate_group = parser.add_argument_group("Crate", "You can pass in the rust crate positionally or with the --crate flag.")
26+
crate_group.add_argument("crate", help="crate to verify", nargs="?")
27+
crate_group.add_argument("--crate", help="crate to verify", dest="crate_flag", metavar="CRATE")
28+
29+
exclude_flags = [
30+
# This should be able to be supported; https://github.com/model-checking/rmc/issues/359
31+
"--gen-symbols",
32+
# This should be able to be supported; https://github.com/model-checking/rmc/issues/360
33+
"--allow-cbmc-verification-failure",
34+
]
35+
rmc_flags.add_flags(parser, {"default-target": "target"}, exclude_flags=exclude_flags)
4536
args = parser.parse_args()
4637

4738
if args.crate:
48-
assert args.crate_flag is None, "Please provide a single crate to verify."
39+
rmc.ensure(args.crate_flag is None, "Please provide a single crate to verify.")
4940
else:
50-
assert args.crate_flag is not None, "Please provide a crate to verify."
41+
rmc.ensure(args.crate_flag is not None, "Please provide a crate to verify.")
5142
args.crate = args.crate_flag
5243

5344
if args.quiet:
5445
args.verbose = False
5546

56-
if not rmc.dependencies_in_path():
57-
return 1
47+
rmc.ensure_dependencies_in_path()
5848

5949
# Add some CBMC flags by default unless `--no-default-checks` is being used
6050
if not args.no_default_checks:
@@ -65,9 +55,8 @@ def main():
6555

6656
pattern = os.path.join(args.target_dir, "debug", "deps", "*.json")
6757
jsons = glob.glob(pattern)
68-
if len(jsons) != 1:
69-
print("ERROR: unexpected number of json outputs")
70-
return 1
58+
rmc.ensure(len(jsons) == 1, "Unexpected number of json outputs.")
59+
7160
cbmc_filename = os.path.join(args.target_dir, "cbmc.out")
7261
c_filename = os.path.join(args.target_dir, "cbmc.c")
7362
if EXIT_CODE_SUCCESS != rmc.symbol_table_to_gotoc(jsons[0], cbmc_filename, args.verbose, args.keep_temps, args.dry_run):

scripts/rmc

Lines changed: 19 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import argparse
66
import os
77
import sys
88
import rmc
9+
import rmc_flags
910
import pathlib
1011

1112
MY_PATH = pathlib.Path(__file__).parent.parent.absolute()
@@ -15,57 +16,40 @@ CBMC_VERIFICATION_FAILURE_EXIT_CODE = 10
1516

1617

1718
def main():
18-
parser = argparse.ArgumentParser(description="Verify a single Rust file")
19-
parser.add_argument("input", help="Rust file to verify", nargs="?")
20-
parser.add_argument("--input", help="Rust file to verify", dest="input_flag")
21-
parser.add_argument("--verbose", "-v", action="store_true")
22-
parser.add_argument("--quiet", "-q", action="store_true")
23-
parser.add_argument("--debug", action="store_true")
24-
parser.add_argument("--keep-temps", action="store_true")
25-
parser.add_argument("--gen-c", action="store_true")
26-
parser.add_argument("--gen-symbols", action="store_true")
27-
parser.add_argument("--allow-cbmc-verification-failure", action="store_true")
28-
parser.add_argument("--mangler", default="v0")
29-
parser.add_argument("--visualize", action="store_true")
30-
parser.add_argument("--c-lib", nargs="*", default=[], action="extend")
31-
parser.add_argument("--srcdir", default=".")
32-
parser.add_argument("--target-dir", default=".",
33-
help="Directory for all generated artifacts")
34-
parser.add_argument("--wkdir", default=".")
35-
parser.add_argument("--no-default-checks", action="store_true", help="Disable all default checks")
36-
parser.add_argument("--no-memory-safety-checks", action="store_true", help="Disable default memory safety checks")
37-
parser.add_argument("--no-overflow-checks", action="store_true", help="Disable default overflow checks")
38-
parser.add_argument("--no-unwinding-checks", action="store_true", help="Disable default unwinding checks")
39-
parser.add_argument("--dry-run", action="store_true", help="Print commands instead of running them")
40-
parser.add_argument("--cbmc-args", nargs=argparse.REMAINDER,
41-
default=[], help="Pass through directly to CBMC")
19+
parser = argparse.ArgumentParser(description="Verify a single Rust file. For more information, see https://github.com/model-checking/rmc.")
20+
21+
input_group = parser.add_argument_group("Input", "You can pass in the rust file positionally or with the --input flag.")
22+
input_group.add_argument("input", help="Rust file to verify", nargs="?")
23+
input_group.add_argument("--input", help="Rust file to verify", dest="input_flag", metavar="INPUT")
24+
25+
exclude_flags = [
26+
# In the future we hope to make this configurable in the command line.
27+
# For now, however, changing this from "main" breaks rmc.
28+
# Issue: https://github.com/model-checking/rmc/issues/169
29+
"--function"
30+
]
31+
rmc_flags.add_flags(parser, {"default-target": "."}, exclude_flags=exclude_flags)
4232
args = parser.parse_args()
43-
44-
# In the future we hope to make this configurable in the command line.
45-
# For now, however, changing this from "main" breaks rmc.
46-
# Issue: https://github.com/model-checking/rmc/issues/169
4733
args.function = "main"
4834

4935
if args.input:
50-
assert args.input_flag is None, "Please provide a single file to verify."
36+
rmc.ensure(args.input_flag is None, "Please provide a single file to verify.")
5137
else:
52-
assert args.input_flag is not None, "Please provide a file to verify."
38+
rmc.ensure(args.input_flag is not None, "Please provide a file to verify.")
5339
args.input = args.input_flag
5440

5541
if args.quiet:
5642
args.verbose = False
5743

58-
if not rmc.dependencies_in_path():
59-
return 1
44+
rmc.ensure_dependencies_in_path()
6045

6146
# Add some CBMC flags by default unless `--no-default-checks` is being used
6247
if not args.no_default_checks:
6348
rmc.add_selected_default_cbmc_flags(args)
6449

6550
base, ext = os.path.splitext(args.input)
66-
if ext != ".rs":
67-
print("ERROR: Expecting .rs input file")
68-
return 1
51+
rmc.ensure(ext == ".rs", "Expecting .rs input file.")
52+
6953
json_filename = base + ".json"
7054
goto_filename = base + ".goto"
7155
c_filename = base + ".c"

scripts/rmc.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import atexit
66
import os
77
import os.path
8+
import sys
89
import re
910

1011

@@ -43,16 +44,18 @@ def edit_output(self, text):
4344
def is_exe(name):
4445
from shutil import which
4546
return which(name) is not None
46-
47-
48-
def dependencies_in_path():
47+
48+
def ensure_dependencies_in_path():
4949
for program in [RMC_RUSTC_EXE, "symtab2gb", "cbmc", "cbmc-viewer", "goto-instrument", "goto-cc"]:
50-
if not is_exe(program):
51-
print("ERROR: Could not find {} in PATH".format(program))
52-
return False
53-
return True
50+
ensure(is_exe(program), f"Could not find {program} in PATH")
5451

52+
# Assert a condition holds, or produce a user error message.
53+
def ensure(condition, message, retcode=1):
54+
if not condition:
55+
print(f"ERROR: {message}")
56+
sys.exit(retcode)
5557

58+
# Deletes a file; used by atexit.register to remove temporary files on exit
5659
def delete_file(filename):
5760
try:
5861
os.remove(filename)

scripts/rmc_flags.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#!/usr/bin/env python3
2+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
# SPDX-License-Identifier: Apache-2.0 OR MIT
4+
5+
import argparse
6+
7+
# Add flags related to debugging output.
8+
def add_loudness_flags(make_group, add_flag, config):
9+
group = make_group(
10+
"Loudness flags", "Determine how much textual output to produce.")
11+
add_flag(group, "--debug", action="store_true",
12+
help="Produce full debug information")
13+
add_flag(group, "--quiet", "-q", action="store_true",
14+
help="Produces no output, just an exit code and requested artifacts; overrides --verbose")
15+
add_flag(group, "--verbose", "-v", action="store_true",
16+
help="Output processing stages and commands, along with minor debug information")
17+
18+
# Add flags which specify configurations for the proof.
19+
def add_linking_flags(make_group, add_flag, config):
20+
group = make_group("Linking flags",
21+
"Provide information about how to link the prover for RMC.")
22+
add_flag(group, "--c-lib", nargs="*", default=[], action="extend",
23+
help="Link external C files referenced by Rust code")
24+
add_flag(group, "--function", default="main",
25+
help="Entry point for verification")
26+
27+
# Add flags that produce extra artifacts.
28+
def add_artifact_flags(make_group, add_flag, config):
29+
default_target = config["default-target"]
30+
assert default_target is not None, \
31+
f"Missing item in parser config: \"default-target\".\n" \
32+
"This is a bug; please report this to https://github.com/model-checking/rmc/issues."
33+
34+
group = make_group(
35+
"Artifact flags", "Produce artifacts in addition to a basic RMC report.")
36+
add_flag(group, "--gen-c", action="store_true",
37+
help="Generate C file equivalent to inputted program")
38+
add_flag(group, "--gen-symbols", action="store_true",
39+
help="Generate a goto symbol table")
40+
add_flag(group, "--keep-temps", action="store_true",
41+
help="Keep temporary files generated throughout RMC process")
42+
add_flag(group, "--target-dir", default=default_target, metavar="DIR",
43+
help=f"Directory for all generated artifacts; defaults to \"{default_target}\"")
44+
45+
# Add flags to turn off default checks.
46+
def add_check_flags(make_group, add_flag, config):
47+
group = make_group("Check flags", "Disable some or all default checks.")
48+
add_flag(group, "--no-default-checks", action="store_true",
49+
help="Disable all default checks")
50+
add_flag(group, "--no-memory-safety-checks", action="store_true",
51+
help="Disable default memory safety checks")
52+
add_flag(group, "--no-overflow-checks", action="store_true",
53+
help="Disable default overflow checks")
54+
add_flag(group, "--no-unwinding-checks", action="store_true",
55+
help="Disable default unwinding checks")
56+
57+
# Add flags needed only for visualizer.
58+
def add_visualizer_flags(make_group, add_flag, config):
59+
group = make_group(
60+
"Visualizer flags", "Generate an HTML-based UI for the generated RMC report.\nSee https://github.com/awslabs/aws-viewer-for-cbmc.")
61+
add_flag(group, "--srcdir", default=".",
62+
help="The source directory: the root of the source tree")
63+
add_flag(group, "--visualize", action="store_true",
64+
help="Generate visualizer report to <target-dir>/report/html/index.html")
65+
add_flag(group, "--wkdir", default=".",
66+
help="""
67+
The working directory: used to determine source locations in output;
68+
this is generally the location from which rmc is currently being invoked
69+
""")
70+
71+
# Add flags for ad-hoc features.
72+
def add_other_flags(make_group, add_flag, config):
73+
group = make_group("Other flags")
74+
add_flag(group, "--allow-cbmc-verification-failure", action="store_true",
75+
help="Do not produce error return code on CBMC verification failure")
76+
add_flag(group, "--dry-run", action="store_true",
77+
help="Print commands instead of running them")
78+
79+
# Add flags we don't expect end-users to use.
80+
def add_developer_flags(make_group, add_flag, config):
81+
group = make_group(
82+
"Developer flags", "These are generally meant for use by RMC developers, and are not stable.")
83+
add_flag(group, "--cbmc-args", nargs=argparse.REMAINDER, default=[],
84+
help="Pass through directly to CBMC; must be the last flag")
85+
add_flag(group, "--mangler", default="v0", choices=["v0", "legacy"],
86+
help="Change what mangler is used by the Rust compiler")
87+
88+
# Adds the flags common to both rmc and cargo-rmc.
89+
# Allows you to specify flags/groups of flags to not add.
90+
# This does not return the parser, but mutates the one provided.
91+
def add_flags(parser, config, exclude_flags=[], exclude_groups=[]):
92+
# Keep track of what excluded flags and groups we've seen
93+
# so we can warn for possibly incorrect names passed in.
94+
excluded_flags = set()
95+
excluded_groups = set()
96+
97+
# Add a group to the parser with title/description, and get a handler for it.
98+
def make_group(title=None, description=None):
99+
if title in exclude_groups:
100+
excluded_groups.add(group.title)
101+
return None
102+
103+
return parser.add_argument_group(title, description)
104+
105+
# Add the flag to the group,
106+
def add_flag(group, flag, *args, **kwargs):
107+
if group == None:
108+
return
109+
110+
if flag in exclude_flags:
111+
excluded_flags.add(flag)
112+
return
113+
114+
group.add_argument(flag, *args, **kwargs)
115+
116+
add_groups = [
117+
add_loudness_flags,
118+
add_linking_flags,
119+
add_artifact_flags,
120+
add_check_flags,
121+
add_visualizer_flags,
122+
add_other_flags,
123+
add_developer_flags
124+
]
125+
126+
for add_group in add_groups:
127+
add_group(make_group, add_flag, config)
128+
129+
# Error if any excluded flags/groups don't exist.
130+
extra_flags = set(exclude_flags) - excluded_flags
131+
extra_groups = set(exclude_groups) - excluded_groups
132+
assert len(extra_flags.union(extra_groups)) == 0, \
133+
f"Attempt to exclude parser options which don't exist: {extra_groups.union(extra_flags)}\n" \
134+
"This is a bug; please report this to https://github.com/model-checking/rmc/issues."

0 commit comments

Comments
 (0)