Skip to content

Commit a977a2e

Browse files
vecchiot-awstedinski
authored andcommitted
C lib flag (rust-lang#260)
* Added --c-lib flag to rmc and cargo-rmc. * Added rmc test for extern C files. * Added cargo-rmc example for extern C files. * PR fixes. * Fixups after rebase. * PR fixes.
1 parent 6b7514c commit a977a2e

File tree

14 files changed

+208
-32
lines changed

14 files changed

+208
-32
lines changed

.github/workflows/copyright.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
- name: Get paths for files added
1616
id: git-diff
1717
run: |
18-
files=$(git diff --name-only --diff-filter=A ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} | grep -v -E '(expected|gitignore)$' | xargs)
18+
files=$(git diff --name-only --diff-filter=A ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} | grep -v -E '(expected|ignore|gitignore)$' | xargs)
1919
echo "::set-output name=paths::$files"
2020
2121
- name: Execute copyright check

scripts/cargo-rmc

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import glob
77
import sys
88
import rmc
99
import os
10+
import pathlib
1011

11-
12+
MY_PATH = pathlib.Path(__file__).parent.parent.absolute()
13+
RMC_C_LIB = MY_PATH / "library" / "rmc" / "rmc_lib.c"
1214
EXIT_CODE_SUCCESS = 0
1315

1416

@@ -18,28 +20,36 @@ def main():
1820
del sys.argv[1]
1921

2022
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")
2125
parser.add_argument("--verbose", "-v", action="store_true")
2226
parser.add_argument("--quiet", "-q", action="store_true")
2327
parser.add_argument("--debug", action="store_true")
2428
parser.add_argument("--keep-temps", action="store_true")
2529
parser.add_argument("--gen-c", action="store_true")
2630
parser.add_argument("--mangler", default="v0")
2731
parser.add_argument("--visualize", action="store_true")
32+
parser.add_argument("--c-lib", nargs="*", default=[], action="extend")
2833
parser.add_argument("--srcdir", default=".")
2934
parser.add_argument("--target-dir", default="target",
3035
help="Directory for all generated artifacts")
3136
parser.add_argument("--wkdir", default=".")
3237
parser.add_argument("--function", default="main")
33-
parser.add_argument("crate", help="crate to verify")
3438
parser.add_argument("--no-default-checks", action="store_true", help="Disable all default checks")
3539
parser.add_argument("--no-memory-safety-checks", action="store_true", help="Disable default memory safety checks")
3640
parser.add_argument("--no-overflow-checks", action="store_true", help="Disable default overflow checks")
3741
parser.add_argument("--no-unwinding-checks", action="store_true", help="Disable default unwinding checks")
3842
parser.add_argument("--dry-run", action="store_true", help="Print commands instead of running them")
39-
parser.add_argument("cbmc_args", nargs=argparse.REMAINDER,
43+
parser.add_argument("--cbmc-args", nargs=argparse.REMAINDER,
4044
default=[], help="Pass through directly to CBMC")
4145
args = parser.parse_args()
4246

47+
if args.crate:
48+
assert args.crate_flag is None, "Please provide a single crate to verify."
49+
else:
50+
assert args.crate_flag is not None, "Please provide a crate to verify."
51+
args.crate = args.crate_flag
52+
4353
if args.quiet:
4454
args.verbose = False
4555

@@ -63,6 +73,11 @@ def main():
6373
if EXIT_CODE_SUCCESS != rmc.symbol_table_to_gotoc(jsons[0], cbmc_filename, args.verbose, args.keep_temps, args.dry_run):
6474
return 1
6575

76+
args.c_lib.append(str(RMC_C_LIB))
77+
78+
if EXIT_CODE_SUCCESS != rmc.link_c_lib(cbmc_filename, cbmc_filename, args.c_lib, args.verbose, args.quiet, args.function, args.dry_run):
79+
return 1
80+
6681
if args.gen_c:
6782
if EXIT_CODE_SUCCESS != rmc.goto_to_c(cbmc_filename, c_filename, args.verbose, args.dry_run):
6883
return 1
@@ -71,7 +86,9 @@ def main():
7186
args.cbmc_args.extend(["--function", args.function])
7287

7388
if args.visualize:
74-
return rmc.run_visualize(cbmc_filename, args.cbmc_args, \
89+
# Use a separate set of flags for coverage checking (empty for now)
90+
cover_args = []
91+
return rmc.run_visualize(cbmc_filename, args.cbmc_args, cover_args, \
7592
args.verbose, args.quiet, args.keep_temps, \
7693
args.function, args.srcdir, args.wkdir, args.target_dir, args.dry_run)
7794
else:

scripts/rmc

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import os
77
import sys
88
import rmc
99
import pathlib
10+
1011
MY_PATH = pathlib.Path(__file__).parent.parent.absolute()
1112
RMC_C_LIB = MY_PATH / "library" / "rmc" / "rmc_lib.c"
1213
EXIT_CODE_SUCCESS = 0
@@ -15,7 +16,8 @@ CBMC_VERIFICATION_FAILURE_EXIT_CODE = 10
1516

1617
def main():
1718
parser = argparse.ArgumentParser(description="Verify a single Rust file")
18-
parser.add_argument("input", help="Rust file to verify")
19+
parser.add_argument("input", help="Rust file to verify", nargs="?")
20+
parser.add_argument("--input", help="Rust file to verify", dest="input_flag")
1921
parser.add_argument("--verbose", "-v", action="store_true")
2022
parser.add_argument("--quiet", "-q", action="store_true")
2123
parser.add_argument("--debug", action="store_true")
@@ -25,21 +27,30 @@ def main():
2527
parser.add_argument("--allow-cbmc-verification-failure", action="store_true")
2628
parser.add_argument("--mangler", default="v0")
2729
parser.add_argument("--visualize", action="store_true")
30+
parser.add_argument("--c-lib", nargs="*", default=[], action="extend")
2831
parser.add_argument("--srcdir", default=".")
32+
parser.add_argument("--target-dir", default=".",
33+
help="Directory for all generated artifacts")
2934
parser.add_argument("--wkdir", default=".")
3035
parser.add_argument("--no-default-checks", action="store_true", help="Disable all default checks")
3136
parser.add_argument("--no-memory-safety-checks", action="store_true", help="Disable default memory safety checks")
3237
parser.add_argument("--no-overflow-checks", action="store_true", help="Disable default overflow checks")
3338
parser.add_argument("--no-unwinding-checks", action="store_true", help="Disable default unwinding checks")
3439
parser.add_argument("--dry-run", action="store_true", help="Print commands instead of running them")
35-
parser.add_argument("cbmc_args", nargs=argparse.REMAINDER,
40+
parser.add_argument("--cbmc-args", nargs=argparse.REMAINDER,
3641
default=[], help="Pass through directly to CBMC")
3742
args = parser.parse_args()
3843

3944
# In the future we hope to make this configurable in the command line.
4045
# For now, however, changing this from "main" breaks rmc.
4146
# Issue: https://github.com/model-checking/rmc/issues/169
4247
args.function = "main"
48+
49+
if args.input:
50+
assert args.input_flag is None, "Please provide a single file to verify."
51+
else:
52+
assert args.input_flag is not None, "Please provide a file to verify."
53+
args.input = args.input_flag
4354

4455
if args.quiet:
4556
args.verbose = False
@@ -66,6 +77,11 @@ def main():
6677
if EXIT_CODE_SUCCESS != rmc.symbol_table_to_gotoc(json_filename, goto_filename, args.verbose, args.keep_temps, args.dry_run):
6778
return 1
6879

80+
args.c_lib.append(str(RMC_C_LIB))
81+
82+
if EXIT_CODE_SUCCESS != rmc.link_c_lib(goto_filename, goto_filename, args.c_lib, args.verbose, args.quiet, args.function, args.dry_run):
83+
return 1
84+
6985
if args.gen_c:
7086
if EXIT_CODE_SUCCESS != rmc.goto_to_c(goto_filename, c_filename, args.verbose, args.dry_run):
7187
return 1
@@ -77,13 +93,12 @@ def main():
7793
if "--function" not in args.cbmc_args:
7894
args.cbmc_args.extend(["--function", args.function])
7995

80-
args.cbmc_args.append(str(RMC_C_LIB))
8196
if args.visualize:
8297
# Use a separate set of flags for coverage checking (empty for now)
8398
cover_args = []
8499
retcode = rmc.run_visualize(goto_filename, args.cbmc_args, cover_args, \
85100
args.verbose, args.quiet, args.keep_temps, \
86-
args.function, args.srcdir, args.wkdir, args.dry_run)
101+
args.function, args.srcdir, args.wkdir, args.target_dir, args.dry_run)
87102
else:
88103
retcode = rmc.run_cbmc(goto_filename, args.cbmc_args, args.verbose, args.quiet, args.dry_run)
89104
if retcode == CBMC_VERIFICATION_FAILURE_EXIT_CODE and args.allow_cbmc_verification_failure:

scripts/rmc.py

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,11 @@ def add_selected_default_cbmc_flags(args):
7979
if not args.no_unwinding_checks:
8080
add_set_cbmc_flags(args, UNWINDING_CHECKS)
8181

82+
# Updates environment to use gotoc backend debugging
8283
def add_rmc_rustc_debug_to_env(env):
8384
env["RUSTC_LOG"] = env.get("RUSTC_LOG", "rustc_codegen_llvm::gotoc")
8485

86+
# Prints info about the RMC process
8587
def print_rmc_step_status(step_name, completed_process, verbose=False):
8688
status = "PASS"
8789
if completed_process.returncode != EXIT_CODE_SUCCESS:
@@ -90,6 +92,7 @@ def print_rmc_step_status(step_name, completed_process, verbose=False):
9092
print(f"[RMC] stage: {step_name} ({status})")
9193
print(f"[RMC] cmd: {' '.join(completed_process.args)}")
9294

95+
# Handler for running arbitrary command-line commands
9396
def run_cmd(cmd, label=None, cwd=None, env=None, output_to=None, quiet=False, verbose=False, debug=False, scanners=[], dry_run=False):
9497
# If this a dry run, we emulate running a successful process whose output is the command itself
9598
# We set `output_to` to `stdout` so that the output is not omitted below
@@ -123,6 +126,7 @@ def run_cmd(cmd, label=None, cwd=None, env=None, output_to=None, quiet=False, ve
123126

124127
return process.returncode
125128

129+
# Generates a symbol table from a rust file
126130
def compile_single_rust_file(input_filename, output_filename, verbose=False, debug=False, keep_temps=False, mangler="v0", dry_run=False):
127131
if not keep_temps:
128132
atexit.register(delete_file, output_filename)
@@ -134,7 +138,7 @@ def compile_single_rust_file(input_filename, output_filename, verbose=False, deb
134138

135139
return run_cmd(build_cmd, env=build_env, label="compile", verbose=verbose, debug=debug, dry_run=dry_run)
136140

137-
141+
# Generates a symbol table (and some other artifacts) from a rust crate
138142
def cargo_build(crate, target_dir="target", verbose=False, debug=False, mangler="v0", dry_run=False):
139143
rustflags = ["-Z", "codegen-backend=gotoc", "-Z", f"symbol-mangling-version={mangler}", f"--cfg={RMC_CFG}"]
140144
rustflags = " ".join(rustflags)
@@ -150,17 +154,25 @@ def cargo_build(crate, target_dir="target", verbose=False, debug=False, mangler=
150154
add_rmc_rustc_debug_to_env(build_env)
151155
return run_cmd(build_cmd, env=build_env, cwd=crate, label="build", verbose=verbose, debug=debug, dry_run=dry_run)
152156

157+
# Adds information about unwinding to the RMC output
153158
def append_unwind_tip(text):
154159
unwind_tip = ("[RMC] info: Verification output shows one or more unwinding failures.\n"
155160
"[RMC] tip: Consider increasing the unwinding value or disabling `--unwinding-assertions`.\n")
156161
return text + unwind_tip
157162

163+
# Generates a goto program from a symbol table
158164
def symbol_table_to_gotoc(json_filename, cbmc_filename, verbose=False, keep_temps=False, dry_run=False):
159165
if not keep_temps:
160166
atexit.register(delete_file, cbmc_filename)
161167
cmd = ["symtab2gb", json_filename, "--out", cbmc_filename]
162168
return run_cmd(cmd, label="to-gotoc", verbose=verbose, dry_run=dry_run)
163169

170+
# Links in external C programs into a goto program
171+
def link_c_lib(src, dst, c_lib, verbose=False, quiet=False, function="main", dry_run=False):
172+
cmd = ["goto-cc"] + ["--function", function] + [src] + c_lib + ["-o", dst]
173+
return run_cmd(cmd, label="goto-cc", verbose=verbose, quiet=quiet, dry_run=dry_run)
174+
175+
# Runs CBMC on a goto program
164176
def run_cbmc(cbmc_filename, cbmc_args, verbose=False, quiet=False, dry_run=False):
165177
cbmc_cmd = ["cbmc"] + cbmc_args + [cbmc_filename]
166178
scanners = []
@@ -171,25 +183,22 @@ def run_cbmc(cbmc_filename, cbmc_args, verbose=False, quiet=False, dry_run=False
171183
scanners.append(unwind_asserts_scanner)
172184
return run_cmd(cbmc_cmd, label="cbmc", output_to="stdout", verbose=verbose, quiet=quiet, scanners=scanners, dry_run=dry_run)
173185

186+
# Generates a viewer report from a goto program
174187
def run_visualize(cbmc_filename, prop_args, cover_args, verbose=False, quiet=False, keep_temps=False, function="main", srcdir=".", wkdir=".", outdir=".", dry_run=False):
175188
results_filename = os.path.join(outdir, "results.xml")
176189
coverage_filename = os.path.join(outdir, "coverage.xml")
177190
property_filename = os.path.join(outdir, "property.xml")
178-
temp_goto_filename = os.path.join(outdir, "temp.goto")
179191
if not keep_temps:
180-
for filename in [results_filename, coverage_filename, property_filename, temp_goto_filename]:
192+
for filename in [results_filename, coverage_filename, property_filename]:
181193
atexit.register(delete_file, filename)
182194

183-
# 1) goto-cc --function main <cbmc_filename> -o temp.goto
184-
# 2) cbmc --xml-ui --trace ~/rmc/library/rmc/rmc_lib.c temp.goto > results.xml
185-
# 3) cbmc --xml-ui --cover location ~/rmc/library/rmc/rmc_lib.c temp.goto > coverage.xml
186-
# 4) cbmc --xml-ui --show-properties ~/rmc/library/rmc/rmc_lib.c temp.goto > property.xml
187-
# 5) cbmc-viewer --result results.xml --coverage coverage.xml --property property.xml --srcdir . --goto temp.goto --reportdir report
188-
189-
run_goto_cc(cbmc_filename, temp_goto_filename, verbose, quiet, function=function, dry_run=dry_run)
195+
# 1) cbmc --xml-ui --trace ~/rmc/library/rmc/rmc_lib.c <cbmc_filename> > results.xml
196+
# 2) cbmc --xml-ui --cover location ~/rmc/library/rmc/rmc_lib.c <cbmc_filename> > coverage.xml
197+
# 3) cbmc --xml-ui --show-properties ~/rmc/library/rmc/rmc_lib.c <cbmc_filename> > property.xml
198+
# 4) cbmc-viewer --result results.xml --coverage coverage.xml --property property.xml --srcdir . --goto <cbmc_filename> --reportdir report
190199

191200
def run_cbmc_local(cbmc_args, output_to, dry_run=False):
192-
cbmc_cmd = ["cbmc"] + cbmc_args + [temp_goto_filename]
201+
cbmc_cmd = ["cbmc"] + cbmc_args + [cbmc_filename]
193202
return run_cmd(cbmc_cmd, label="cbmc", output_to=output_to, verbose=verbose, quiet=quiet, dry_run=dry_run)
194203

195204
cbmc_prop_args = prop_args + ["--xml-ui"]
@@ -199,15 +208,12 @@ def run_cbmc_local(cbmc_args, output_to, dry_run=False):
199208
run_cbmc_local(cbmc_cover_args + ["--cover", "location"], coverage_filename, dry_run=dry_run)
200209
run_cbmc_local(cbmc_prop_args + ["--show-properties"], property_filename, dry_run=dry_run)
201210

202-
run_cbmc_viewer(temp_goto_filename, results_filename, coverage_filename,
211+
run_cbmc_viewer(cbmc_filename, results_filename, coverage_filename,
203212
property_filename, verbose, quiet, srcdir, wkdir, os.path.join(outdir, "report"), dry_run=dry_run)
204213

205214
return retcode
206215

207-
def run_goto_cc(src, dst, verbose=False, quiet=False, function="main", dry_run=False):
208-
cmd = ["goto-cc"] + ["--function", function] + [src] + ["-o", dst]
209-
return run_cmd(cmd, label="goto-cc", verbose=verbose, quiet=quiet, dry_run=dry_run)
210-
216+
# Handler for calling cbmc-viewer
211217
def run_cbmc_viewer(goto_filename, results_filename, coverage_filename, property_filename, verbose=False, quiet=False, srcdir=".", wkdir=".", reportdir="report", dry_run=False):
212218
cmd = ["cbmc-viewer"] + \
213219
["--result", results_filename] + \
@@ -219,15 +225,15 @@ def run_cbmc_viewer(goto_filename, results_filename, coverage_filename, property
219225
["--reportdir", reportdir]
220226
return run_cmd(cmd, label="cbmc-viewer", verbose=verbose, quiet=quiet, dry_run=dry_run)
221227

222-
228+
# Handler for calling goto-instrument
223229
def run_goto_instrument(input_filename, output_filename, args, verbose=False, dry_run=False):
224230
cmd = ["goto-instrument"] + args + [input_filename]
225231
return run_cmd(cmd, label="goto-instrument", verbose=verbose, output_to=output_filename, dry_run=dry_run)
226232

227-
233+
# Generates a C program from a goto program
228234
def goto_to_c(goto_filename, c_filename, verbose=False, dry_run=False):
229235
return run_goto_instrument(goto_filename, c_filename, ["--dump-c"], verbose, dry_run=dry_run)
230236

231-
237+
# Generates the CMBC symbol table from a goto program
232238
def goto_to_symbols(goto_filename, symbols_filename, verbose=False, dry_run=False):
233239
return run_goto_instrument(goto_filename, symbols_filename, ["--show-symbol-table"], verbose, dry_run=dry_run)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0 OR MIT
3+
[package]
4+
name = "simple-lib"
5+
version = "0.1.0"
6+
edition = "2018"
7+
8+
[dependencies]
9+
10+
[workspace]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
extern "C" {
5+
pub fn external_c_assertion(i: u32) -> u32;
6+
}
7+
8+
#[no_mangle]
9+
pub extern "C" fn rust_add1(i: u32) -> u32 {
10+
i + 1
11+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
#include <stdint.h>
5+
#include <assert.h>
6+
#include <string.h>
7+
#include <stdio.h>
8+
#include <stdarg.h>
9+
10+
uint32_t rust_add1(uint32_t i);
11+
12+
uint32_t external_c_assertion(uint32_t x) {
13+
assert(rust_add1(x) == x + 1);
14+
return 0;
15+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
pub mod externs;
4+
pub use externs::external_c_assertion;
5+
6+
#[cfg(test)]
7+
mod tests {
8+
#[test]
9+
fn it_works() {
10+
unsafe {
11+
external_c_assertion(12);
12+
}
13+
}
14+
}
15+
16+
#[cfg(rmc)]
17+
mod rmc_tests {
18+
use super::*;
19+
20+
fn __nondet<T>() -> T {
21+
unimplemented!()
22+
}
23+
#[allow(dead_code)]
24+
#[no_mangle]
25+
fn test_sum() {
26+
let a: u32 = __nondet();
27+
28+
if a < 100 {
29+
unsafe {
30+
external_c_assertion(a);
31+
}
32+
}
33+
}
34+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[call_into_rust.assertion.1] line 13 assertion takes_int(x) == x + 1: SUCCESS

src/test/cbmc/ForeignItems/fixme_main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
//! To run this test, do
44
//! rmc fixme_main.rs -- lib.c
55
6+
// rmc-flags: --c-lib lib.c
7+
68
// TODO, we should also test packed and transparent representations
79
// https://doc.rust-lang.org/reference/type-layout.html
810
#[repr(C)]

src/test/cbmc/ForeignItems/lib.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ uint32_t takes_struct_ptr(struct Foo* f) {
8484

8585
uint32_t takes_struct2(struct Foo2 f) {
8686
assert(sizeof(unsigned int) == sizeof(uint32_t));
87-
return f.i + f.c;
87+
return f.i + f.i2;
8888
}
8989

9090
uint32_t takes_struct_ptr2(struct Foo2* f) {

0 commit comments

Comments
 (0)