Skip to content

Commit c9abaf8

Browse files
celinvaltedinski
authored andcommitted
A few output improvements (rust-lang#976)
* Disable reach assertions in visualize mode * Make file location relative to current dir * Add unit tests
1 parent ea20849 commit c9abaf8

File tree

10 files changed

+191
-3
lines changed

10 files changed

+191
-3
lines changed

scripts/cbmc_json_parser.py

+25-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
from colorama import Fore, Style
2828
from enum import Enum
29+
from os import path
2930

3031
# Enum to store the style of output that is given by the argument flags
3132
output_style_switcher = {
@@ -95,9 +96,32 @@ def __init__(self, source_location={}):
9596
self.column = source_location.get("column", None)
9697
self.line = source_location.get("line", None)
9798

99+
def filepath(self):
100+
""" Return a more user friendly path.
101+
102+
- If the path is inside the current directory, return a relative path.
103+
- If not but the path is in the ${HOME} directory, return relative to ${HOME}
104+
- Otherwise, return the path as is.
105+
"""
106+
if not self.filename:
107+
return None
108+
109+
# Reference to C files use relative paths, while rust uses absolute.
110+
# Normalize both to be absolute first.
111+
full_path = path.abspath(self.filename)
112+
cwd = os.getcwd()
113+
if path.commonpath([full_path, cwd]) == cwd:
114+
return path.relpath(full_path)
115+
116+
home_path = path.expanduser("~")
117+
if path.commonpath([full_path, home_path]) == home_path:
118+
return "~/{}".format(path.relpath(full_path, home_path))
119+
120+
return self.filename
121+
98122
def __str__(self):
99123
if self.filename:
100-
s = f"{self.filename}"
124+
s = f"{self.filepath()}"
101125
if self.line:
102126
s += f":{self.line}"
103127
if self.column:

scripts/kani-regression.sh

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ check-cbmc-viewer-version.py --major 2 --minor 10
2525
# Formatting check
2626
${SCRIPT_DIR}/kani-fmt.sh --check
2727

28+
# Parser tests
29+
PYTHONPATH=${SCRIPT_DIR} python3 -m unittest ${SCRIPT_DIR}/test_cbmc_json_parser.py
30+
2831
# Build all packages in the workspace
2932
cargo build
3033

scripts/test_cbmc_json_parser.py

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0 OR MIT
3+
import unittest
4+
import os
5+
import tempfile
6+
from cbmc_json_parser import SourceLocation
7+
8+
9+
def source_json(filename=None, function=None, line=None, column=None):
10+
result = dict()
11+
if filename:
12+
result["file"] = filename
13+
if function:
14+
result["function"] = function
15+
if column:
16+
result["column"] = column
17+
if line:
18+
result["line"] = line
19+
return result
20+
21+
22+
class SourceLocationTest(unittest.TestCase):
23+
""" Unit tests for SourceLocation """
24+
25+
def test_source_location_valid_path(self):
26+
"""Path returned by filepath() works for valid paths
27+
28+
Note: Check is loose because I couldn't find a reliable way to control a real path location.
29+
"""
30+
path = tempfile.gettempdir()
31+
json = source_json(path)
32+
src_loc = SourceLocation(json)
33+
possible_output = {path, os.path.relpath(path), os.path.relpath(path, os.path.expanduser("~"))}
34+
self.assertIn(src_loc.filepath(), possible_output)
35+
36+
def test_source_location_invalid_path(self):
37+
"""Path returned by filepath() returns the input path if it doesn't exist"""
38+
path = "/rust/made/up/path/lib.rs"
39+
json = source_json(path)
40+
src_loc = SourceLocation(json)
41+
self.assertEqual(src_loc.filepath(), path)
42+
43+
def test_source_location_relative_path(self):
44+
"""Path returned by filepath() is relative if the input is also relative"""
45+
relpath = "dummy/target.rs"
46+
json = source_json(relpath)
47+
src_loc = SourceLocation(json)
48+
self.assertEqual(src_loc.filepath(), relpath)
49+
50+
def test_source_location_absolute_cwd_path(self):
51+
"""Path returned by filepath() is relative to current directory
52+
53+
Note that the file may not exist that this should still hold.
54+
"""
55+
relpath = "dummy/target.rs"
56+
path = os.path.join(os.getcwd(), relpath)
57+
self.assertNotEqual(relpath, path)
58+
59+
json = source_json(path)
60+
src_loc = SourceLocation(json)
61+
self.assertEqual(src_loc.filepath(), relpath)
62+
63+
def test_source_location_absolute_user_path(self):
64+
"""Path returned by filepath() is relative to current directory
65+
66+
Note that the file may not exist that this should still hold.
67+
"""
68+
relpath = "~/dummy/target.rs"
69+
path = os.path.expanduser(relpath)
70+
self.assertNotEqual(relpath, path)
71+
72+
json = source_json(path)
73+
src_loc = SourceLocation(json)
74+
self.assertEqual(src_loc.filepath(), relpath)
75+
76+
def test_source_location_relative_user_path(self):
77+
"""Path returned by filepath() is relative to current directory
78+
79+
Note that the file may not exist that this should still hold.
80+
"""
81+
relpath = "~/dummy/target.rs"
82+
json = source_json(relpath)
83+
src_loc = SourceLocation(json)
84+
self.assertEqual(src_loc.filepath(), relpath)
85+
86+
def test_source_location_with_no_path(self):
87+
"""Function filepath() is None if no file is given in the input"""
88+
json = source_json(function="main")
89+
src_loc = SourceLocation(json)
90+
self.assertIsNone(src_loc.filepath())
91+
92+
def test_source_location_output_format(self):
93+
"""Check that output includes all the information provided"""
94+
path = "/rust/made/up/path/lib.rs"
95+
function = "harness"
96+
line = 10
97+
column = 8
98+
json = source_json(path, function, line, column)
99+
src_loc = str(SourceLocation(json))
100+
self.assertIn(path, src_loc)
101+
self.assertIn(f"{path}:{line}:{column}", src_loc)
102+
self.assertIn(function, src_loc)
103+
104+
105+
if __name__ == '__main__':
106+
unittest.main()

src/cargo-kani/src/args.rs

+5
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@ impl KaniArgs {
142142
self.restrict_vtable
143143
// if we flip the default, this will become: !self.no_restrict_vtable
144144
}
145+
146+
pub fn assertion_reach_checks(&self) -> bool {
147+
// Turn them off when visualizing an error trace.
148+
!self.no_assertion_reach_checks && !self.visualize
149+
}
145150
}
146151

147152
arg_enum! {

src/cargo-kani/src/call_single_file.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ impl KaniSession {
107107
if self.args.restrict_vtable() {
108108
flags.push("--restrict-vtable-fn-ptrs".into());
109109
}
110-
if !self.args.no_assertion_reach_checks {
110+
if self.args.assertion_reach_checks() {
111111
flags.push("--assertion-reach-checks".into());
112112
}
113113

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ impl<'tcx> GotocCtx<'tcx> {
1414
let lo = smap.lookup_char_pos(sp.lo());
1515
let line = lo.line;
1616
let col = 1 + lo.col_display;
17-
let filename0 = lo.file.name.prefer_remapped().to_string_lossy().to_string();
17+
let filename0 = lo.file.name.prefer_local().to_string_lossy().to_string();
1818
let filename1 = match std::fs::canonicalize(filename0.clone()) {
1919
Ok(pathbuf) => pathbuf.to_str().unwrap().to_string(),
2020
Err(_) => filename0,

tests/ui/code-location/expected

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module/mod.rs:10:5 in function module::not_empty
2+
main.rs:13:5 in function same_file
3+
/toolchains/
4+
alloc/src/vec/mod.rs:2821:81 in function <std::vec::Vec<T, A> as std::ops::Drop>::drop
5+
6+
VERIFICATION:- SUCCESSFUL
7+

tests/ui/code-location/main.rs

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//
4+
// kani-flags: --function harness
5+
//
6+
//! This test is to check how file names are displayed in the Kani output.
7+
8+
mod module;
9+
10+
use module::not_empty;
11+
12+
fn same_file(cond: bool) {
13+
assert!(cond);
14+
}
15+
16+
#[kani::proof]
17+
fn harness() {
18+
let x = true;
19+
same_file(x);
20+
21+
let v = vec![1];
22+
not_empty(&v);
23+
}
24+
25+
fn main() {}
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
VERIFICATION:- SUCCESSFUL
2+

tests/ui/code-location/module/mod.rs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// kani-flags: --function empty_harness
5+
6+
// This file is to be used as a module on a different test, but the compiletest will still run
7+
// kani on this file. Use an empty harness instead.
8+
9+
pub fn not_empty(v: &[i32]) {
10+
assert!(!v.is_empty());
11+
}
12+
13+
#[kani::proof]
14+
fn empty_harness() {
15+
// No-op to overcome compile test.
16+
}

0 commit comments

Comments
 (0)