Skip to content

Commit 88aace3

Browse files
Executable trace feature (#1409)
* feat: prototype E2E mechanism for gen det vals and running exec trace This was formed by squashing the following commits: feat: exec-trace POC that gets kani::any assignments feat: init work making exec_trace a Kani command line flag feat: make trace parsing more precise feat: allow for multiple extracted values feat: Move parsing code into the driver refactor: move functions outside KaniSession test: add basic UI test for det val feat: change any_raw to make it easier to parse trace feat: update any_raw to use byte array feat: update parser to take in byte array chore: run rustfmt test: add constant generic bound to func abstraction tests test: fix remaining test issues to pass regression feat: update any_raw_inner to return bytes chore: remove spurious results file feat: update any_raw_inner to read from det_vals file feat: add exec_trace flag to kani-compiler refactor(kani-compiler): rename vars to increase readability feat(kani-compiler): specify different Kani lib paths feat: prototype E2E mechanism for gen det vals & running exec trace fix: add generic const trait bounds to any_vec feat: add print out to get debug command chore: remove det vals from tmp file list for demo * feat: towards printing unit test case * feat: add source modification feature * refactor: lots of it * feat: call cbmc only once * add back exe trace and fix unit test line numbers * refactor: create exe trace main * chore: revert kani-compiler changes * fix: update parser for new cbmc trace format * refactor: change contains to starts_with for perf * chore: revert check-output change * feat: put Kani lib exe trace code behind a feature * tests: fix one-assert test * feat: add more code under exe_trace feat * fix: reverse det vals list since we're popping off vals * feat: add exe_trace impls for other kani lib funcs * fix: assertion failed may not always be failure * fix: strict string equality actually works * feat: add fields to get the end of a func * feat: insert exe trace after harness * refactor: rename everything to exe_trace instead of exec_trace * refactor: move funcs out of kani session and remove pub visibility * refactor: move some code into parser module * refactor: remove generic const any_raw_inner since it will only be called on ints after Celina's PR * fix: remove 1 TODO and make 1 func priv * fix: add newline to cargo toml * feat: switch to using Vec<Vec<u8>> * feat: add comment in exe trace with interp det val * refactor: extend iterator * feat: don't insert same exe trace twice * feat: add kani lib func to init det vals * tests: forgot to add generic const stuff back into tests * tests: forgot about one-assert going back to normal * feat: add exe trace functionality for kani standalone * tests: add kani overall test * tests: forgot to add the exe trace test dir * chore: add safety msg back in * some more tests work * fix: clippy and license checks * tests: exe-trace test covers more primitives * docs: add func comments * small changes * docs: add comments * refactor: use named params in format strings * fix: update generic const invariants in kani lib after arbitrary change * fix: change parser to accomodate kani lib arbitrary changes * docs: add test case comment * docs: make arg comment a bit nicer * refactor: generalize rustfmt func in a nice way * feat: add any_raw_inner to more reliably get CBMC output. Also clarify test case semantics * feat: better formatting of printed test case * test: split tests into different directories per primitive * test: add f32, f64 tests * test: add comment about f32/f64 NaN values * docs: add better explanations for generic_const_exprs feature * refactor: rename parser funcs * feat: convert DET_VALS to static mut * feat: add lock around det vals for thread safety, update playback api to exe_trace_run * address some of Celina's PR comments * one way of implementing file reads using String vectors * refactor: different way of implementing src code modification, all as big strings * feat: hide exe traces under quiet flag * refactor: generate output filename once * feat: add more arg exclusions * refactor: call exe_trace_main inside run_cbmc * ui: add more info to concrete-mode assertion conversions * refactor: as much as possible, move Kani lib exe trace code to separate module * refactor: add kani::any_raw_internal exe_trace impl to sep mod also * refactor: replace whitespace with constant * refactor: add ExeTrace and DetVal structs * refactor, docs: rename bunch of funcs and add CBMC output format * refactor: remove as_slice * refactor: split up big if let in parser * fix: small typo that printed out test name instead of actual test * ui: make exe trace exists in src code comment more specific * feat: add anyhow error handling to CBMC trace parser * feat: add anyhow error handling to rest of kani driver exe_trace file * fix: clippy to_string display lints * feat: remove extra string allocation for new src file * ui: don't abreviate exe trace * docs: add todo for new parser * ui: add error handling for multiple diff asserts and checks * ui: add warning for adding unit tests for multiple failing harnesses * refactor: move have_parsed_assert_fail into parser glob var * refactor: make DET_VALS a thread_local static refcell * fix: had incorrect type info in the refcell changes * fix: changed exe_trace::any_raw_internal to pub crate * feat: add error handling around using any_raw funcs and switch to if cfg bang feature * ui: add assert around --output-format old * fix: make exe_trace mod private * refactor: change over to if cfg exe_trace instead of cfg exe_trace macro * refactor: require harness arg -> make have_parsed_assert_fail local to a single harness * fix: in CBMC output parser, write CBMC output to file if using exe_trace flag. This is for old exe_trace parser compatibility * lints: add carrots around URLs to fix clippy warning * refactor: move buf writer inside parser, but there's still an ownership issue * fix: the cbmc output parser ownership issue * fix: address PR comments around cbmc output parser changes Co-authored-by: Celina G. Val <celinval@amazon.com>
1 parent d9ec642 commit 88aace3

File tree

50 files changed

+1208
-65
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1208
-65
lines changed

cprover_bindings/src/goto_program/location.rs

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,14 @@ pub enum Location {
1313
BuiltinFunction { function_name: InternedString, line: Option<u64> },
1414
/// Location in user code.
1515
/// `function` is `None` for global, `Some(function_name)` for function local.
16-
Loc { file: InternedString, function: Option<InternedString>, line: u64, col: Option<u64> },
16+
Loc {
17+
file: InternedString,
18+
function: Option<InternedString>,
19+
start_line: u64,
20+
start_col: Option<u64>,
21+
end_line: u64,
22+
end_col: Option<u64>,
23+
},
1724
/// Location for Statements that use Property Class and Description - Assert, Assume, Cover etc.
1825
Property {
1926
file: InternedString,
@@ -50,9 +57,16 @@ impl Location {
5057
}
5158
}
5259

53-
pub fn line(&self) -> Option<u64> {
60+
pub fn start_line(&self) -> Option<u64> {
5461
match self {
55-
Location::Loc { line, .. } => Some(*line),
62+
Location::Loc { start_line, .. } => Some(*start_line),
63+
_ => None,
64+
}
65+
}
66+
67+
pub fn end_line(&self) -> Option<u64> {
68+
match self {
69+
Location::Loc { end_line, .. } => Some(*end_line),
5670
_ => None,
5771
}
5872
}
@@ -68,7 +82,7 @@ impl Location {
6882
Location::BuiltinFunction { function_name, line: None } => {
6983
format!("<{}>", function_name)
7084
}
71-
Location::Loc { file, line, .. } => format!("{}:{}", file, line),
85+
Location::Loc { file, start_line: line, .. } => format!("{}:{}", file, line),
7286
Location::Property { file, line, .. } => {
7387
format!("<{:?}>:{}", file, line)
7488
}
@@ -82,18 +96,29 @@ impl Location {
8296
pub fn new<T, U: Into<InternedString>, V: Into<InternedString>>(
8397
file: U,
8498
function: Option<V>,
85-
line: T,
86-
col: Option<T>,
99+
start_line: T,
100+
start_col: Option<T>,
101+
end_line: T,
102+
end_col: Option<T>,
87103
) -> Location
88104
where
89105
T: TryInto<u64>,
90106
T::Error: Debug,
91107
{
92-
let file = file.into();
93-
let line = line.try_into().unwrap();
94-
let col = col.map(|x| x.try_into().unwrap());
95-
let function = function.intern();
96-
Location::Loc { file, function, line, col }
108+
let file_into = file.into();
109+
let start_line_into = start_line.try_into().unwrap();
110+
let start_col_into = start_col.map(|x| x.try_into().unwrap());
111+
let end_line_into = end_line.try_into().unwrap();
112+
let end_col_into = end_col.map(|x| x.try_into().unwrap());
113+
let function_into = function.intern();
114+
Location::Loc {
115+
file: file_into,
116+
function: function_into,
117+
start_line: start_line_into,
118+
start_col: start_col_into,
119+
end_line: end_line_into,
120+
end_col: end_col_into,
121+
}
97122
}
98123

99124
/// Create a Property type Location
@@ -134,14 +159,16 @@ impl Location {
134159
comment.into(),
135160
property_name.into(),
136161
),
137-
Location::Loc { file, function, line, col } => Location::property_location(
138-
file.into(),
139-
function.intern(),
140-
line,
141-
col,
142-
comment.into(),
143-
property_name.into(),
144-
),
162+
Location::Loc { file, function, start_line, start_col, end_line: _, end_col: _ } => {
163+
Location::property_location(
164+
file.into(),
165+
function.intern(),
166+
start_line,
167+
start_col,
168+
comment.into(),
169+
property_name.into(),
170+
)
171+
}
145172
Location::Property { .. } => location,
146173
Location::PropertyUnknownLocation { .. } => location,
147174
// This converts None type Locations to PropertyUnknownLocation type which inserts Property Class and Description

cprover_bindings/src/irep/to_irep.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -351,12 +351,14 @@ impl ToIrep for Location {
351351
(IrepId::Function, Irep::just_string_id(function_name.to_string())),
352352
])
353353
.with_named_sub_option(IrepId::Line, line.map(Irep::just_int_id)),
354-
Location::Loc { file, function, line, col } => Irep::just_named_sub(linear_map![
355-
(IrepId::File, Irep::just_string_id(file.to_string())),
356-
(IrepId::Line, Irep::just_int_id(*line)),
357-
])
358-
.with_named_sub_option(IrepId::Column, col.map(Irep::just_int_id))
359-
.with_named_sub_option(IrepId::Function, function.map(Irep::just_string_id)),
354+
Location::Loc { file, function, start_line, start_col, end_line: _, end_col: _ } => {
355+
Irep::just_named_sub(linear_map![
356+
(IrepId::File, Irep::just_string_id(file.to_string())),
357+
(IrepId::Line, Irep::just_int_id(*start_line)),
358+
])
359+
.with_named_sub_option(IrepId::Column, start_col.map(Irep::just_int_id))
360+
.with_named_sub_option(IrepId::Function, function.map(Irep::just_string_id))
361+
}
360362
Location::Property { file, function, line, col, property_class, comment } => {
361363
Irep::just_named_sub(linear_map![
362364
(IrepId::File, Irep::just_string_id(file.to_string())),

kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,8 @@ impl<'tcx> GotocCtx<'tcx> {
316316
pretty_name,
317317
mangled_name,
318318
original_file: loc.filename().unwrap(),
319-
original_line: loc.line().unwrap().to_string(),
319+
original_start_line: loc.start_line().unwrap().to_string(),
320+
original_end_line: loc.end_line().unwrap().to_string(),
320321
unwind_value: None,
321322
}
322323
}

kani-compiler/src/codegen_cprover_gotoc/codegen/span.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ impl<'tcx> GotocCtx<'tcx> {
1212
pub fn codegen_span(&self, sp: &Span) -> Location {
1313
let smap = self.tcx.sess.source_map();
1414
let lo = smap.lookup_char_pos(sp.lo());
15-
let line = lo.line;
16-
let col = 1 + lo.col_display;
15+
let start_line = lo.line;
16+
let start_col = 1 + lo.col_display;
17+
let hi = smap.lookup_char_pos(sp.hi());
18+
let end_line = hi.line;
19+
let end_col = 1 + hi.col_display;
1720
let filename0 = lo.file.name.prefer_local().to_string_lossy().to_string();
1821
let filename1 = match std::fs::canonicalize(filename0.clone()) {
1922
Ok(pathbuf) => pathbuf.to_str().unwrap().to_string(),
@@ -22,8 +25,10 @@ impl<'tcx> GotocCtx<'tcx> {
2225
Location::new(
2326
filename1,
2427
self.current_fn.as_ref().map(|x| x.readable_name().to_string()),
25-
line,
26-
Some(col),
28+
start_line,
29+
Some(start_col),
30+
end_line,
31+
Some(end_col),
2732
)
2833
}
2934

kani-driver/src/args.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,18 @@ pub struct KaniArgs {
4343
/// Generate visualizer report to <target-dir>/report/html/index.html
4444
#[structopt(long)]
4545
pub visualize: bool,
46+
/// Generate executable trace test case and print it to stdout.
47+
/// This option does not work with `--output-format old`.
48+
#[structopt(
49+
long,
50+
requires("enable-unstable"),
51+
requires("harness"),
52+
conflicts_with_all(&["visualize", "dry-run"]),
53+
)]
54+
pub gen_exe_trace: bool,
55+
/// Additionally add executable trace test case to the source code
56+
#[structopt(long, requires("gen-exe-trace"))]
57+
pub add_exe_trace_to_src: bool,
4658
/// Keep temporary files generated throughout Kani process
4759
#[structopt(long, hidden_short_help(true))]
4860
pub keep_temps: bool,

kani-driver/src/call_cbmc.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,9 @@ impl KaniSession {
2222
/// Verify a goto binary that's been prepared with goto-instrument
2323
pub fn run_cbmc(&self, file: &Path, harness: &HarnessMetadata) -> Result<VerificationStatus> {
2424
let output_filename = crate::util::append_path(file, "cbmc_output");
25-
2625
{
2726
let mut temps = self.temporaries.borrow_mut();
28-
temps.push(output_filename);
27+
temps.push(output_filename.clone());
2928
}
3029

3130
let args: Vec<OsString> = self.cbmc_flags(file, harness)?;
@@ -52,10 +51,13 @@ impl KaniSession {
5251
if let Some(cbmc_process) = cbmc_process_opt {
5352
// The introduction of reachability checks forces us to decide
5453
// the verification result based on the postprocessing of CBMC results.
54+
let output_filename_opt: Option<&Path> =
55+
if self.args.gen_exe_trace { Some(&output_filename) } else { None };
5556
let processed_result = process_cbmc_output(
5657
cbmc_process,
5758
self.args.extra_pointer_checks,
5859
&self.args.output_format,
60+
output_filename_opt,
5961
);
6062
Ok(processed_result)
6163
} else {
@@ -68,6 +70,10 @@ impl KaniSession {
6870
println!("Verification Time: {}s", elapsed);
6971
}
7072

73+
if let Ok(VerificationStatus::Failure) = verification_result {
74+
self.gen_and_add_exe_trace(&output_filename, harness);
75+
}
76+
7177
verification_result
7278
}
7379

@@ -115,6 +121,10 @@ impl KaniSession {
115121
args.push("--slice-formula".into());
116122
}
117123

124+
if self.args.gen_exe_trace {
125+
args.push("--trace".into());
126+
}
127+
118128
args.extend(self.args.cbmc_args.iter().cloned());
119129

120130
args.push(file.to_owned().into_os_string());

kani-driver/src/cbmc_output_parser.rs

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ use serde::Deserialize;
2929
use std::{
3030
collections::HashMap,
3131
env,
32-
io::{BufRead, BufReader},
33-
path::PathBuf,
32+
fs::File,
33+
io::{BufRead, BufReader, BufWriter, Write},
34+
path::{Path, PathBuf},
3435
process::{Child, ChildStdout},
3536
};
3637

@@ -351,11 +352,26 @@ enum Action {
351352
struct Parser<'a, 'b> {
352353
pub input_so_far: String,
353354
pub buffer: &'a mut BufReader<&'b mut ChildStdout>,
355+
/// Buffered writer over the CBMC output file.
356+
/// This is needed for old parsers (e.g., like the one in the executable trace code) that require the actual CBMC output file.
357+
/// TODO: This can be removed once we overhaul the executable trace parser.
358+
/// See this tracking issue: <https://github.com/model-checking/kani/issues/1477>.
359+
cbmc_out_buf_writer: Option<BufWriter<File>>,
354360
}
355361

356362
impl<'a, 'b> Parser<'a, 'b> {
357-
pub fn new(buffer: &'a mut BufReader<&'b mut ChildStdout>) -> Self {
358-
Parser { input_so_far: String::new(), buffer }
363+
pub fn new(
364+
buffer: &'a mut BufReader<&'b mut ChildStdout>,
365+
output_filename_opt: Option<&Path>,
366+
) -> Self {
367+
let cbmc_out_buf_writer = output_filename_opt.map(|output_filename| {
368+
let cbmc_out_file = File::create(output_filename).expect(&format!(
369+
"In CBMC output parser, couldn't create CBMC output file `{}`",
370+
output_filename.display()
371+
));
372+
BufWriter::new(cbmc_out_file)
373+
});
374+
Parser { input_so_far: String::new(), buffer, cbmc_out_buf_writer }
359375
}
360376

361377
/// Triggers an action based on the input:
@@ -423,6 +439,7 @@ impl<'a, 'b> Parser<'a, 'b> {
423439
/// Processes a line to determine if an action must be triggered.
424440
/// The action may result in a `ParserItem`, which is then returned.
425441
pub fn process_line(&mut self, input: String) -> Option<ParserItem> {
442+
self.add_line_to_buf_writer(&input);
426443
self.add_to_input(input.clone());
427444
let action_required = self.triggers_action(input);
428445
if let Some(action) = action_required {
@@ -431,6 +448,26 @@ impl<'a, 'b> Parser<'a, 'b> {
431448
}
432449
None
433450
}
451+
452+
/// Adds a single line to the CBMC output buffered writer.
453+
pub fn add_line_to_buf_writer(&mut self, line: &str) {
454+
if let Some(buf_writer) = self.cbmc_out_buf_writer.as_mut() {
455+
write!(buf_writer, "{}", line).expect(&format!(
456+
"In CBMC output parser, couldn't write `{}` to CBMC output buffered writer",
457+
line
458+
));
459+
}
460+
}
461+
462+
/// Flushes the CBMC output buffered writer.
463+
/// This should be called after the parser has processed the entire CBMC output.
464+
pub fn flush_buf_writer(&mut self) {
465+
if let Some(buf_writer) = self.cbmc_out_buf_writer.as_mut() {
466+
buf_writer.flush().expect(
467+
"In CBMC output parser, couldn't flush buffered writer to CBMC output file",
468+
);
469+
}
470+
}
434471
}
435472

436473
/// The iterator implementation for `Parser` reads the buffer line by line,
@@ -443,6 +480,7 @@ impl<'a, 'b> Iterator for Parser<'a, 'b> {
443480
match self.buffer.read_line(&mut input) {
444481
Ok(len) => {
445482
if len == 0 {
483+
self.flush_buf_writer();
446484
return None;
447485
}
448486
let item = self.process_line(input);
@@ -510,10 +548,11 @@ pub fn process_cbmc_output(
510548
mut process: Child,
511549
extra_ptr_checks: bool,
512550
output_format: &OutputFormat,
551+
output_filename_opt: Option<&Path>,
513552
) -> VerificationStatus {
514553
let stdout = process.stdout.as_mut().unwrap();
515554
let mut stdout_reader = BufReader::new(stdout);
516-
let parser = Parser::new(&mut stdout_reader);
555+
let parser = Parser::new(&mut stdout_reader, output_filename_opt);
517556
let mut result = false;
518557

519558
for item in parser {
@@ -532,6 +571,7 @@ pub fn process_cbmc_output(
532571
// TODO: Record processed items and dump them into a JSON file
533572
// <https://github.com/model-checking/kani/issues/942>
534573
}
574+
535575
if result { VerificationStatus::Success } else { VerificationStatus::Failure }
536576
}
537577

0 commit comments

Comments
 (0)