Skip to content

Commit 96d1147

Browse files
Support for disabling automatically generated pointer checks to avoid reinstrumentation (rust-lang#3344)
Recently added memory initialization checks (see rust-lang#3300 for an overview) code suffers from re-instrumentation due to automatically added pointer checks to the instrumentation code. This PR adds an internal `kanitool::disable_checks` attribute to disable automatically generated CBMC checks inside internal instrumentation. This, in turn, relies on CBMC pragmas (https://www.cprover.org/cprover-manual/properties/#usinggotoinstrument). By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.
1 parent ac823d3 commit 96d1147

File tree

9 files changed

+204
-23
lines changed

9 files changed

+204
-23
lines changed

cprover_bindings/src/goto_program/location.rs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub enum Location {
1919
start_col: Option<u64>,
2020
end_line: u64,
2121
end_col: Option<u64>,
22+
pragmas: &'static [&'static str], // CBMC `#pragma check` statements per location.
2223
},
2324
/// Location for Statements that use Property Class and Description - Assert, Assume, Cover etc.
2425
Property {
@@ -28,6 +29,7 @@ pub enum Location {
2829
col: Option<u64>,
2930
comment: InternedString,
3031
property_class: InternedString,
32+
pragmas: &'static [&'static str], // CBMC `#pragma check` statements per location.
3133
},
3234
/// Covers cases where Location Details are unknown or set as None but Property Class is needed.
3335
PropertyUnknownLocation { comment: InternedString, property_class: InternedString },
@@ -99,6 +101,7 @@ impl Location {
99101
start_col: Option<T>,
100102
end_line: T,
101103
end_col: Option<T>,
104+
pragmas: &'static [&'static str],
102105
) -> Location
103106
where
104107
T: TryInto<u64>,
@@ -117,6 +120,7 @@ impl Location {
117120
start_col: start_col_into,
118121
end_line: end_line_into,
119122
end_col: end_col_into,
123+
pragmas,
120124
}
121125
}
122126

@@ -128,6 +132,7 @@ impl Location {
128132
col: Option<T>,
129133
comment: U,
130134
property_name: U,
135+
pragmas: &'static [&'static str],
131136
) -> Location
132137
where
133138
T: TryInto<u64>,
@@ -140,7 +145,7 @@ impl Location {
140145
let function = function.intern();
141146
let property_class = property_name.into();
142147
let comment = comment.into();
143-
Location::Property { file, function, line, col, comment, property_class }
148+
Location::Property { file, function, line, col, comment, property_class, pragmas }
144149
}
145150

146151
/// Create a Property type Location from an already existing Location type
@@ -157,17 +162,25 @@ impl Location {
157162
None,
158163
comment.into(),
159164
property_name.into(),
165+
&[],
166+
),
167+
Location::Loc {
168+
file,
169+
function,
170+
start_line,
171+
start_col,
172+
end_line: _,
173+
end_col: _,
174+
pragmas,
175+
} => Location::property_location(
176+
file.into(),
177+
function.intern(),
178+
start_line,
179+
start_col,
180+
comment.into(),
181+
property_name.into(),
182+
pragmas,
160183
),
161-
Location::Loc { file, function, start_line, start_col, end_line: _, end_col: _ } => {
162-
Location::property_location(
163-
file.into(),
164-
function.intern(),
165-
start_line,
166-
start_col,
167-
comment.into(),
168-
property_name.into(),
169-
)
170-
}
171184
Location::Property { .. } => location,
172185
Location::PropertyUnknownLocation { .. } => location,
173186
// This converts None type Locations to PropertyUnknownLocation type which inserts Property Class and Description

cprover_bindings/src/irep/to_irep.rs

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -389,15 +389,32 @@ impl ToIrep for Location {
389389
(IrepId::Function, Irep::just_string_id(function_name.to_string())),
390390
])
391391
.with_named_sub_option(IrepId::Line, line.map(Irep::just_int_id)),
392-
Location::Loc { file, function, start_line, start_col, end_line: _, end_col: _ } => {
393-
Irep::just_named_sub(linear_map![
394-
(IrepId::File, Irep::just_string_id(file.to_string())),
395-
(IrepId::Line, Irep::just_int_id(*start_line)),
396-
])
397-
.with_named_sub_option(IrepId::Column, start_col.map(Irep::just_int_id))
398-
.with_named_sub_option(IrepId::Function, function.map(Irep::just_string_id))
399-
}
400-
Location::Property { file, function, line, col, property_class, comment } => {
392+
Location::Loc {
393+
file,
394+
function,
395+
start_line,
396+
start_col,
397+
end_line: _,
398+
end_col: _,
399+
pragmas,
400+
} => Irep::just_named_sub(linear_map![
401+
(IrepId::File, Irep::just_string_id(file.to_string())),
402+
(IrepId::Line, Irep::just_int_id(*start_line)),
403+
])
404+
.with_named_sub_option(IrepId::Column, start_col.map(Irep::just_int_id))
405+
.with_named_sub_option(IrepId::Function, function.map(Irep::just_string_id))
406+
.with_named_sub_option(
407+
IrepId::Pragma,
408+
Some(Irep::just_named_sub(
409+
pragmas
410+
.iter()
411+
.map(|pragma| {
412+
(IrepId::from_string(*pragma), Irep::just_id(IrepId::EmptyString))
413+
})
414+
.collect(),
415+
)),
416+
),
417+
Location::Property { file, function, line, col, property_class, comment, pragmas } => {
401418
Irep::just_named_sub(linear_map![
402419
(IrepId::File, Irep::just_string_id(file.to_string())),
403420
(IrepId::Line, Irep::just_int_id(*line)),
@@ -409,6 +426,17 @@ impl ToIrep for Location {
409426
IrepId::PropertyClass,
410427
Irep::just_string_id(property_class.to_string()),
411428
)
429+
.with_named_sub_option(
430+
IrepId::Pragma,
431+
Some(Irep::just_named_sub(
432+
pragmas
433+
.iter()
434+
.map(|pragma| {
435+
(IrepId::from_string(*pragma), Irep::just_id(IrepId::EmptyString))
436+
})
437+
.collect(),
438+
)),
439+
)
412440
}
413441
Location::PropertyUnknownLocation { property_class, comment } => {
414442
Irep::just_named_sub(linear_map![

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

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,68 @@
55
66
use crate::codegen_cprover_gotoc::GotocCtx;
77
use cbmc::goto_program::Location;
8+
use lazy_static::lazy_static;
9+
use rustc_ast::Attribute;
810
use rustc_smir::rustc_internal;
911
use rustc_span::Span;
1012
use stable_mir::ty::Span as SpanStable;
13+
use std::collections::HashMap;
14+
15+
lazy_static! {
16+
/// Pragmas key-value store to prevent CBMC from generating automatic checks.
17+
/// This list is taken from https://github.com/diffblue/cbmc/blob/develop/regression/cbmc/pragma_cprover_enable_all/main.c.
18+
static ref PRAGMAS: HashMap<&'static str, &'static str> =
19+
[("bounds", "disable:bounds-check"),
20+
("pointer", "disable:pointer-check"),
21+
("div-by-zero", "disable:div-by-zero-check"),
22+
("float-div-by-zero", "disable:float-div-by-zero-check"),
23+
("enum-range", "disable:enum-range-check"),
24+
("signed-overflow", "disable:signed-overflow-check"),
25+
("unsigned-overflow", "disable:unsigned-overflow-check"),
26+
("pointer-overflow", "disable:pointer-overflow-check"),
27+
("float-overflow", "disable:float-overflow-check"),
28+
("conversion", "disable:conversion-check"),
29+
("undefined-shift", "disable:undefined-shift-check"),
30+
("nan", "disable:nan-check"),
31+
("pointer-primitive", "disable:pointer-primitive-check")].iter().copied().collect();
32+
}
1133

1234
impl<'tcx> GotocCtx<'tcx> {
1335
pub fn codegen_span(&self, sp: &Span) -> Location {
1436
self.codegen_span_stable(rustc_internal::stable(sp))
1537
}
1638

1739
pub fn codegen_span_stable(&self, sp: SpanStable) -> Location {
40+
// Attribute to mark functions as where automatic pointer checks should not be generated.
41+
let should_skip_ptr_checks_attr = vec![
42+
rustc_span::symbol::Symbol::intern("kanitool"),
43+
rustc_span::symbol::Symbol::intern("disable_checks"),
44+
];
45+
let pragmas: &'static [&str] = {
46+
let disabled_checks: Vec<_> = self
47+
.current_fn
48+
.as_ref()
49+
.map(|current_fn| {
50+
let instance = current_fn.instance();
51+
self.tcx
52+
.get_attrs_by_path(instance.def.def_id(), &should_skip_ptr_checks_attr)
53+
.collect()
54+
})
55+
.unwrap_or_default();
56+
disabled_checks
57+
.iter()
58+
.map(|attr| {
59+
let arg = parse_word(attr).expect(
60+
"incorrect value passed to `disable_checks`, expected a single identifier",
61+
);
62+
*PRAGMAS.get(arg.as_str()).expect(format!(
63+
"attempting to disable an unexisting check, the possible options are {:?}",
64+
PRAGMAS.keys()
65+
).as_str())
66+
})
67+
.collect::<Vec<_>>()
68+
.leak() // This is to preserve `Location` being Copy, but could blow up the memory utilization of compiler.
69+
};
1870
let loc = sp.get_lines();
1971
Location::new(
2072
sp.get_filename().to_string(),
@@ -23,6 +75,7 @@ impl<'tcx> GotocCtx<'tcx> {
2375
Some(loc.start_col),
2476
loc.end_line,
2577
Some(loc.end_col),
78+
pragmas,
2679
)
2780
}
2881

@@ -39,3 +92,18 @@ impl<'tcx> GotocCtx<'tcx> {
3992
self.codegen_span(&topmost)
4093
}
4194
}
95+
96+
/// Extracts the single argument from the attribute provided as a string.
97+
/// For example, `disable_checks(foo)` return `Some("foo")`
98+
fn parse_word(attr: &Attribute) -> Option<String> {
99+
// Vector of meta items , that contain the arguments given the attribute
100+
let attr_args = attr.meta_item_list()?;
101+
// Only extracts one string ident as a string
102+
if attr_args.len() == 1 {
103+
attr_args[0].ident().map(|ident| ident.to_string())
104+
}
105+
// Return none if there are no attributes or if there's too many attributes
106+
else {
107+
None
108+
}
109+
}

kani-compiler/src/kani_middle/attributes.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ enum KaniAttributeKind {
7474
/// We use this attribute to properly instantiate `kani::any_modifies` in
7575
/// cases when recursion is present given our contracts instrumentation.
7676
Recursion,
77+
/// Used to mark functions where generating automatic pointer checks should be disabled. This is
78+
/// used later to automatically attach pragma statements to locations.
79+
DisableChecks,
7780
}
7881

7982
impl KaniAttributeKind {
@@ -93,7 +96,8 @@ impl KaniAttributeKind {
9396
| KaniAttributeKind::CheckedWith
9497
| KaniAttributeKind::Modifies
9598
| KaniAttributeKind::InnerCheck
96-
| KaniAttributeKind::IsContractGenerated => false,
99+
| KaniAttributeKind::IsContractGenerated
100+
| KaniAttributeKind::DisableChecks => false,
97101
}
98102
}
99103

@@ -382,6 +386,10 @@ impl<'tcx> KaniAttributes<'tcx> {
382386
KaniAttributeKind::InnerCheck => {
383387
self.inner_check();
384388
}
389+
KaniAttributeKind::DisableChecks => {
390+
// Ignored here, because it should be an internal attribute. Actual validation
391+
// happens when pragmas are generated.
392+
}
385393
}
386394
}
387395
}
@@ -490,6 +498,10 @@ impl<'tcx> KaniAttributes<'tcx> {
490498
| KaniAttributeKind::InnerCheck
491499
| KaniAttributeKind::ReplacedWith => {
492500
self.tcx.dcx().span_err(self.tcx.def_span(self.item), format!("Contracts are not supported on harnesses. (Found the kani-internal contract attribute `{}`)", kind.as_ref()));
501+
},
502+
KaniAttributeKind::DisableChecks => {
503+
// Internal attribute which shouldn't exist here.
504+
unreachable!()
493505
}
494506
};
495507
harness

0 commit comments

Comments
 (0)