Skip to content

Commit 068f63c

Browse files
Improve performance and language support of memory initialization checks (rust-lang#3313)
This is a follow-up PR for rust-lang#3264, a part of the larger work towards rust-lang#3300. This PR introduces: - Use of demonic non-determinism (prophecy variables) to improve memory initialization checks performance; - Instead of keeping track of memory initialization of each possibly uninitialized byte pointed to by some pointer, one is chosen non-deterministically; - Tests for proper memory initialization checks injection for compiler intrinsics; - Separate functions for checking memory initialization of slice chunks. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --------- Co-authored-by: Zyad Hassan <[email protected]>
1 parent 3b4042c commit 068f63c

38 files changed

+1013
-290
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ impl<'tcx> GotocCtx<'tcx> {
122122
.tcx
123123
.all_diagnostic_items(())
124124
.name_to_id
125-
.get(&rustc_span::symbol::Symbol::intern("KaniMemInitShadowMem"))
125+
.get(&rustc_span::symbol::Symbol::intern("KaniMemoryInitializationState"))
126126
.map(|attr_id| {
127127
self.tcx
128128
.symbol_name(rustc_middle::ty::Instance::mono(self.tcx, *attr_id))

kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs

+36
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,41 @@ impl GotocHook for Assert {
143143
}
144144
}
145145

146+
struct Check;
147+
impl GotocHook for Check {
148+
fn hook_applies(&self, tcx: TyCtxt, instance: Instance) -> bool {
149+
matches_function(tcx, instance.def, "KaniCheck")
150+
}
151+
152+
fn handle(
153+
&self,
154+
gcx: &mut GotocCtx,
155+
_instance: Instance,
156+
mut fargs: Vec<Expr>,
157+
_assign_to: &Place,
158+
target: Option<BasicBlockIdx>,
159+
span: Span,
160+
) -> Stmt {
161+
assert_eq!(fargs.len(), 2);
162+
let cond = fargs.remove(0).cast_to(Type::bool());
163+
let msg = fargs.remove(0);
164+
let msg = gcx.extract_const_message(&msg).unwrap();
165+
let target = target.unwrap();
166+
let caller_loc = gcx.codegen_caller_span_stable(span);
167+
168+
let (msg, reach_stmt) = gcx.codegen_reachability_check(msg, span);
169+
170+
Stmt::block(
171+
vec![
172+
reach_stmt,
173+
gcx.codegen_assert(cond, PropertyClass::Assertion, &msg, caller_loc),
174+
Stmt::goto(bb_label(target), caller_loc),
175+
],
176+
caller_loc,
177+
)
178+
}
179+
}
180+
146181
struct Nondet;
147182

148183
impl GotocHook for Nondet {
@@ -510,6 +545,7 @@ pub fn fn_hooks() -> GotocHooks {
510545
Rc::new(Panic),
511546
Rc::new(Assume),
512547
Rc::new(Assert),
548+
Rc::new(Check),
513549
Rc::new(Cover),
514550
Rc::new(Nondet),
515551
Rc::new(IsAllocated),

kani-compiler/src/kani_middle/transform/body.rs

+19-2
Original file line numberDiff line numberDiff line change
@@ -387,12 +387,13 @@ pub enum CheckType {
387387
}
388388

389389
impl CheckType {
390-
/// This will create the type of check that is available in the current crate.
390+
/// This will create the type of check that is available in the current crate, attempting to
391+
/// create a check that generates an assertion following by an assumption of the same assertion.
391392
///
392393
/// If `kani` crate is available, this will return [CheckType::Assert], and the instance will
393394
/// point to `kani::assert`. Otherwise, we will collect the `core::panic_str` method and return
394395
/// [CheckType::Panic].
395-
pub fn new(tcx: TyCtxt) -> CheckType {
396+
pub fn new_assert_assume(tcx: TyCtxt) -> CheckType {
396397
if let Some(instance) = find_instance(tcx, "KaniAssert") {
397398
CheckType::Assert(instance)
398399
} else if find_instance(tcx, "panic_str").is_some() {
@@ -401,6 +402,22 @@ impl CheckType {
401402
CheckType::NoCore
402403
}
403404
}
405+
406+
/// This will create the type of check that is available in the current crate, attempting to
407+
/// create a check that generates an assertion, without assuming the condition afterwards.
408+
///
409+
/// If `kani` crate is available, this will return [CheckType::Assert], and the instance will
410+
/// point to `kani::assert`. Otherwise, we will collect the `core::panic_str` method and return
411+
/// [CheckType::Panic].
412+
pub fn new_assert(tcx: TyCtxt) -> CheckType {
413+
if let Some(instance) = find_instance(tcx, "KaniCheck") {
414+
CheckType::Assert(instance)
415+
} else if find_instance(tcx, "panic_str").is_some() {
416+
CheckType::Panic
417+
} else {
418+
CheckType::NoCore
419+
}
420+
}
404421
}
405422

406423
/// We store the index of an instruction to avoid borrow checker issues and unnecessary copies.

kani-compiler/src/kani_middle/transform/check_uninit/mod.rs

+136-25
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,17 @@ mod uninit_visitor;
2929
pub use ty_layout::{PointeeInfo, PointeeLayout};
3030
use uninit_visitor::{CheckUninitVisitor, InitRelevantInstruction, MemoryInitOp};
3131

32-
const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] =
33-
&["KaniIsUnitPtrInitialized", "KaniSetUnitPtrInitialized"];
32+
// Function bodies of those functions will not be instrumented as not to cause infinite recursion.
33+
const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = &[
34+
"KaniIsPtrInitialized",
35+
"KaniSetPtrInitialized",
36+
"KaniIsSliceChunkPtrInitialized",
37+
"KaniSetSliceChunkPtrInitialized",
38+
"KaniIsSlicePtrInitialized",
39+
"KaniSetSlicePtrInitialized",
40+
"KaniIsStrPtrInitialized",
41+
"KaniSetStrPtrInitialized",
42+
];
3443

3544
/// Instrument the code with checks for uninitialized memory.
3645
#[derive(Debug)]
@@ -59,8 +68,8 @@ impl TransformPass for UninitPass {
5968
fn transform(&mut self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) {
6069
trace!(function=?instance.name(), "transform");
6170

62-
// Need to break infinite recursion when shadow memory checks are inserted,
63-
// so the internal function responsible for shadow memory checks are skipped.
71+
// Need to break infinite recursion when memory initialization checks are inserted, so the
72+
// internal functions responsible for memory initialization are skipped.
6473
if tcx
6574
.get_diagnostic_name(rustc_internal::internal(tcx, instance.def.def_id()))
6675
.map(|diagnostic_name| {
@@ -74,6 +83,11 @@ impl TransformPass for UninitPass {
7483
let mut new_body = MutableBody::from(body);
7584
let orig_len = new_body.blocks().len();
7685

86+
// Inject a call to set-up memory initialization state if the function is a harness.
87+
if is_harness(instance, tcx) {
88+
inject_memory_init_setup(&mut new_body, tcx, &mut self.mem_init_fn_cache);
89+
}
90+
7791
// Set of basic block indices for which analyzing first statement should be skipped.
7892
//
7993
// This is necessary because some checks are inserted before the source instruction, which, in
@@ -159,10 +173,12 @@ impl UninitPass {
159173
};
160174

161175
match operation {
162-
MemoryInitOp::Check { .. } => {
176+
MemoryInitOp::CheckSliceChunk { .. } | MemoryInitOp::Check { .. } => {
163177
self.build_get_and_check(tcx, body, source, operation, pointee_ty_info, skip_first)
164178
}
165-
MemoryInitOp::Set { .. } | MemoryInitOp::SetRef { .. } => {
179+
MemoryInitOp::SetSliceChunk { .. }
180+
| MemoryInitOp::Set { .. }
181+
| MemoryInitOp::SetRef { .. } => {
166182
self.build_set(tcx, body, source, operation, pointee_ty_info, skip_first)
167183
}
168184
MemoryInitOp::Unsupported { .. } => {
@@ -171,8 +187,8 @@ impl UninitPass {
171187
}
172188
}
173189

174-
/// Inject a load from shadow memory tracking memory initialization and an assertion that all
175-
/// non-padding bytes are initialized.
190+
/// Inject a load from memory initialization state and an assertion that all non-padding bytes
191+
/// are initialized.
176192
fn build_get_and_check(
177193
&mut self,
178194
tcx: TyCtxt,
@@ -189,18 +205,34 @@ impl UninitPass {
189205
let ptr_operand = operation.mk_operand(body, source);
190206
match pointee_info.layout() {
191207
PointeeLayout::Sized { layout } => {
208+
let layout_operand = mk_layout_operand(body, source, operation.position(), &layout);
209+
// Depending on whether accessing the known number of elements in the slice, need to
210+
// pass is as an argument.
211+
let (diagnostic, args) = match &operation {
212+
MemoryInitOp::Check { .. } => {
213+
let diagnostic = "KaniIsPtrInitialized";
214+
let args = vec![ptr_operand.clone(), layout_operand];
215+
(diagnostic, args)
216+
}
217+
MemoryInitOp::CheckSliceChunk { .. } => {
218+
let diagnostic = "KaniIsSliceChunkPtrInitialized";
219+
let args =
220+
vec![ptr_operand.clone(), layout_operand, operation.expect_count()];
221+
(diagnostic, args)
222+
}
223+
_ => unreachable!(),
224+
};
192225
let is_ptr_initialized_instance = resolve_mem_init_fn(
193-
get_mem_init_fn_def(tcx, "KaniIsPtrInitialized", &mut self.mem_init_fn_cache),
226+
get_mem_init_fn_def(tcx, diagnostic, &mut self.mem_init_fn_cache),
194227
layout.len(),
195228
*pointee_info.ty(),
196229
);
197-
let layout_operand = mk_layout_operand(body, source, operation.position(), &layout);
198230
collect_skipped(&operation, body, skip_first);
199231
body.add_call(
200232
&is_ptr_initialized_instance,
201233
source,
202234
operation.position(),
203-
vec![ptr_operand.clone(), layout_operand, operation.expect_count()],
235+
args,
204236
ret_place.clone(),
205237
);
206238
}
@@ -252,8 +284,8 @@ impl UninitPass {
252284
)
253285
}
254286

255-
/// Inject a store into shadow memory tracking memory initialization to initialize or
256-
/// deinitialize all non-padding bytes.
287+
/// Inject a store into memory initialization state to initialize or deinitialize all
288+
/// non-padding bytes.
257289
fn build_set(
258290
&mut self,
259291
tcx: TyCtxt,
@@ -272,27 +304,50 @@ impl UninitPass {
272304

273305
match pointee_info.layout() {
274306
PointeeLayout::Sized { layout } => {
307+
let layout_operand = mk_layout_operand(body, source, operation.position(), &layout);
308+
// Depending on whether writing to the known number of elements in the slice, need to
309+
// pass is as an argument.
310+
let (diagnostic, args) = match &operation {
311+
MemoryInitOp::Set { .. } | MemoryInitOp::SetRef { .. } => {
312+
let diagnostic = "KaniSetPtrInitialized";
313+
let args = vec![
314+
ptr_operand,
315+
layout_operand,
316+
Operand::Constant(ConstOperand {
317+
span: source.span(body.blocks()),
318+
user_ty: None,
319+
const_: MirConst::from_bool(value),
320+
}),
321+
];
322+
(diagnostic, args)
323+
}
324+
MemoryInitOp::SetSliceChunk { .. } => {
325+
let diagnostic = "KaniSetSliceChunkPtrInitialized";
326+
let args = vec![
327+
ptr_operand,
328+
layout_operand,
329+
operation.expect_count(),
330+
Operand::Constant(ConstOperand {
331+
span: source.span(body.blocks()),
332+
user_ty: None,
333+
const_: MirConst::from_bool(value),
334+
}),
335+
];
336+
(diagnostic, args)
337+
}
338+
_ => unreachable!(),
339+
};
275340
let set_ptr_initialized_instance = resolve_mem_init_fn(
276-
get_mem_init_fn_def(tcx, "KaniSetPtrInitialized", &mut self.mem_init_fn_cache),
341+
get_mem_init_fn_def(tcx, diagnostic, &mut self.mem_init_fn_cache),
277342
layout.len(),
278343
*pointee_info.ty(),
279344
);
280-
let layout_operand = mk_layout_operand(body, source, operation.position(), &layout);
281345
collect_skipped(&operation, body, skip_first);
282346
body.add_call(
283347
&set_ptr_initialized_instance,
284348
source,
285349
operation.position(),
286-
vec![
287-
ptr_operand,
288-
layout_operand,
289-
operation.expect_count(),
290-
Operand::Constant(ConstOperand {
291-
span: source.span(body.blocks()),
292-
user_ty: None,
293-
const_: MirConst::from_bool(value),
294-
}),
295-
],
350+
args,
296351
ret_place,
297352
);
298353
}
@@ -426,3 +481,59 @@ pub fn resolve_mem_init_fn(fn_def: FnDef, layout_size: usize, associated_type: T
426481
)
427482
.unwrap()
428483
}
484+
485+
/// Checks if the instance is a harness -- an entry point of Kani analysis.
486+
fn is_harness(instance: Instance, tcx: TyCtxt) -> bool {
487+
let harness_identifiers = [
488+
vec![
489+
rustc_span::symbol::Symbol::intern("kanitool"),
490+
rustc_span::symbol::Symbol::intern("proof_for_contract"),
491+
],
492+
vec![
493+
rustc_span::symbol::Symbol::intern("kanitool"),
494+
rustc_span::symbol::Symbol::intern("proof"),
495+
],
496+
];
497+
harness_identifiers.iter().any(|attr_path| {
498+
tcx.has_attrs_with_path(rustc_internal::internal(tcx, instance.def.def_id()), attr_path)
499+
})
500+
}
501+
502+
/// Inject an initial call to set-up memory initialization tracking.
503+
fn inject_memory_init_setup(
504+
new_body: &mut MutableBody,
505+
tcx: TyCtxt,
506+
mem_init_fn_cache: &mut HashMap<&'static str, FnDef>,
507+
) {
508+
// First statement or terminator in the harness.
509+
let mut source = if !new_body.blocks()[0].statements.is_empty() {
510+
SourceInstruction::Statement { idx: 0, bb: 0 }
511+
} else {
512+
SourceInstruction::Terminator { bb: 0 }
513+
};
514+
515+
// Dummy return place.
516+
let ret_place = Place {
517+
local: new_body.new_local(
518+
Ty::new_tuple(&[]),
519+
source.span(new_body.blocks()),
520+
Mutability::Not,
521+
),
522+
projection: vec![],
523+
};
524+
525+
// Resolve the instance and inject a call to set-up the memory initialization state.
526+
let memory_initialization_init = Instance::resolve(
527+
get_mem_init_fn_def(tcx, "KaniInitializeMemoryInitializationState", mem_init_fn_cache),
528+
&GenericArgs(vec![]),
529+
)
530+
.unwrap();
531+
532+
new_body.add_call(
533+
&memory_initialization_init,
534+
&mut source,
535+
InsertPosition::Before,
536+
vec![],
537+
ret_place,
538+
);
539+
}

0 commit comments

Comments
 (0)