Skip to content

Commit db9c45c

Browse files
Basic support for memory initialization checks for unions (#3444)
This PR introduces very basic support for memory initialization checks for unions. As of now, the following is supported: - Unions can be created - Union fields can be assigned to - Union fields can be read from - Unions can be assigned directly to another union For more information about planned functionality, see #3300 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 28f8f22 commit db9c45c

File tree

10 files changed

+583
-73
lines changed

10 files changed

+583
-73
lines changed

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

Lines changed: 153 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,29 @@ pub trait TargetFinder {
3535
fn find_all(self, body: &MutableBody) -> Vec<InitRelevantInstruction>;
3636
}
3737

38+
const KANI_IS_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniIsPtrInitialized";
39+
const KANI_SET_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniSetPtrInitialized";
40+
const KANI_IS_SLICE_CHUNK_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniIsSliceChunkPtrInitialized";
41+
const KANI_SET_SLICE_CHUNK_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniSetSliceChunkPtrInitialized";
42+
const KANI_IS_SLICE_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniIsSlicePtrInitialized";
43+
const KANI_SET_SLICE_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniSetSlicePtrInitialized";
44+
const KANI_IS_STR_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniIsStrPtrInitialized";
45+
const KANI_SET_STR_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniSetStrPtrInitialized";
46+
const KANI_COPY_INIT_STATE_DIAGNOSTIC: &str = "KaniCopyInitState";
47+
const KANI_COPY_INIT_STATE_SINGLE_DIAGNOSTIC: &str = "KaniCopyInitStateSingle";
48+
3849
// Function bodies of those functions will not be instrumented as not to cause infinite recursion.
3950
const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = &[
40-
"KaniIsPtrInitialized",
41-
"KaniSetPtrInitialized",
42-
"KaniIsSliceChunkPtrInitialized",
43-
"KaniSetSliceChunkPtrInitialized",
44-
"KaniIsSlicePtrInitialized",
45-
"KaniSetSlicePtrInitialized",
46-
"KaniIsStrPtrInitialized",
47-
"KaniSetStrPtrInitialized",
51+
KANI_IS_PTR_INITIALIZED_DIAGNOSTIC,
52+
KANI_SET_PTR_INITIALIZED_DIAGNOSTIC,
53+
KANI_IS_SLICE_CHUNK_PTR_INITIALIZED_DIAGNOSTIC,
54+
KANI_SET_SLICE_CHUNK_PTR_INITIALIZED_DIAGNOSTIC,
55+
KANI_IS_SLICE_PTR_INITIALIZED_DIAGNOSTIC,
56+
KANI_SET_SLICE_PTR_INITIALIZED_DIAGNOSTIC,
57+
KANI_IS_STR_PTR_INITIALIZED_DIAGNOSTIC,
58+
KANI_SET_STR_PTR_INITIALIZED_DIAGNOSTIC,
59+
KANI_COPY_INIT_STATE_DIAGNOSTIC,
60+
KANI_COPY_INIT_STATE_SINGLE_DIAGNOSTIC,
4861
];
4962

5063
/// Instruments the code with checks for uninitialized memory, agnostic to the source of targets.
@@ -164,8 +177,14 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> {
164177
}
165178
MemoryInitOp::SetSliceChunk { .. }
166179
| MemoryInitOp::Set { .. }
167-
| MemoryInitOp::SetRef { .. } => self.build_set(body, source, operation, pointee_info),
180+
| MemoryInitOp::SetRef { .. }
181+
| MemoryInitOp::CreateUnion { .. } => {
182+
self.build_set(body, source, operation, pointee_info)
183+
}
168184
MemoryInitOp::Copy { .. } => self.build_copy(body, source, operation, pointee_info),
185+
MemoryInitOp::AssignUnion { .. } => {
186+
self.build_assign_union(body, source, operation, pointee_info)
187+
}
169188
MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => {
170189
unreachable!()
171190
}
@@ -196,12 +215,12 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> {
196215
// pass is as an argument.
197216
let (diagnostic, args) = match &operation {
198217
MemoryInitOp::Check { .. } | MemoryInitOp::CheckRef { .. } => {
199-
let diagnostic = "KaniIsPtrInitialized";
218+
let diagnostic = KANI_IS_PTR_INITIALIZED_DIAGNOSTIC;
200219
let args = vec![ptr_operand.clone(), layout_operand];
201220
(diagnostic, args)
202221
}
203222
MemoryInitOp::CheckSliceChunk { .. } => {
204-
let diagnostic = "KaniIsSliceChunkPtrInitialized";
223+
let diagnostic = KANI_IS_SLICE_CHUNK_PTR_INITIALIZED_DIAGNOSTIC;
205224
let args =
206225
vec![ptr_operand.clone(), layout_operand, operation.expect_count()];
207226
(diagnostic, args)
@@ -232,10 +251,10 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> {
232251
// Since `str`` is a separate type, need to differentiate between [T] and str.
233252
let (slicee_ty, diagnostic) = match pointee_info.ty().kind() {
234253
TyKind::RigidTy(RigidTy::Slice(slicee_ty)) => {
235-
(slicee_ty, "KaniIsSlicePtrInitialized")
254+
(slicee_ty, KANI_IS_SLICE_PTR_INITIALIZED_DIAGNOSTIC)
236255
}
237256
TyKind::RigidTy(RigidTy::Str) => {
238-
(Ty::unsigned_ty(UintTy::U8), "KaniIsStrPtrInitialized")
257+
(Ty::unsigned_ty(UintTy::U8), KANI_IS_STR_PTR_INITIALIZED_DIAGNOSTIC)
239258
}
240259
_ => unreachable!(),
241260
};
@@ -266,6 +285,14 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> {
266285
self.inject_assert_false(self.tcx, body, source, operation.position(), reason);
267286
return;
268287
}
288+
PointeeLayout::Union { .. } => {
289+
// Here we are reading from a pointer to a union.
290+
// TODO: we perhaps need to check that the union at least contains an intersection
291+
// of all layouts initialized.
292+
let reason = "Interaction between raw pointers and unions is not yet supported.";
293+
self.inject_assert_false(self.tcx, body, source, operation.position(), reason);
294+
return;
295+
}
269296
};
270297

271298
// Construct the basic block and insert it into the body.
@@ -317,7 +344,7 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> {
317344
// pass is as an argument.
318345
let (diagnostic, args) = match &operation {
319346
MemoryInitOp::Set { .. } | MemoryInitOp::SetRef { .. } => {
320-
let diagnostic = "KaniSetPtrInitialized";
347+
let diagnostic = KANI_SET_PTR_INITIALIZED_DIAGNOSTIC;
321348
let args = vec![
322349
ptr_operand,
323350
layout_operand,
@@ -330,7 +357,7 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> {
330357
(diagnostic, args)
331358
}
332359
MemoryInitOp::SetSliceChunk { .. } => {
333-
let diagnostic = "KaniSetSliceChunkPtrInitialized";
360+
let diagnostic = KANI_SET_SLICE_CHUNK_PTR_INITIALIZED_DIAGNOSTIC;
334361
let args = vec![
335362
ptr_operand,
336363
layout_operand,
@@ -369,10 +396,10 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> {
369396
// Since `str`` is a separate type, need to differentiate between [T] and str.
370397
let (slicee_ty, diagnostic) = match pointee_info.ty().kind() {
371398
TyKind::RigidTy(RigidTy::Slice(slicee_ty)) => {
372-
(slicee_ty, "KaniSetSlicePtrInitialized")
399+
(slicee_ty, KANI_SET_SLICE_PTR_INITIALIZED_DIAGNOSTIC)
373400
}
374401
TyKind::RigidTy(RigidTy::Str) => {
375-
(Ty::unsigned_ty(UintTy::U8), "KaniSetStrPtrInitialized")
402+
(Ty::unsigned_ty(UintTy::U8), KANI_SET_STR_PTR_INITIALIZED_DIAGNOSTIC)
376403
}
377404
_ => unreachable!(),
378405
};
@@ -409,6 +436,63 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> {
409436
PointeeLayout::TraitObject => {
410437
unreachable!("Cannot change the initialization state of a trait object directly.");
411438
}
439+
PointeeLayout::Union { field_layouts } => {
440+
// Writing union data, which could be either creating a union from scratch or
441+
// performing some pointer operations with it. If we are creating a union from
442+
// scratch, an operation will contain a union field.
443+
444+
// TODO: If we don't have a union field, we are either creating a pointer to a union
445+
// or assigning to one. In the former case, it is safe to return from this function,
446+
// since the union must be already tracked (on creation and update). In the latter
447+
// case, we should have been using union assignment instead. Nevertheless, this is
448+
// currently mitigated by injecting `assert!(false)`.
449+
let union_field = match operation.union_field() {
450+
Some(field) => field,
451+
None => {
452+
let reason =
453+
"Interaction between raw pointers and unions is not yet supported.";
454+
self.inject_assert_false(
455+
self.tcx,
456+
body,
457+
source,
458+
operation.position(),
459+
reason,
460+
);
461+
return;
462+
}
463+
};
464+
let layout = &field_layouts[union_field];
465+
let layout_operand = mk_layout_operand(body, &mut statements, source, layout);
466+
let diagnostic = KANI_SET_PTR_INITIALIZED_DIAGNOSTIC;
467+
let args = vec![
468+
ptr_operand,
469+
layout_operand,
470+
Operand::Constant(ConstOperand {
471+
span: source.span(body.blocks()),
472+
user_ty: None,
473+
const_: MirConst::from_bool(value),
474+
}),
475+
];
476+
let set_ptr_initialized_instance = resolve_mem_init_fn(
477+
get_mem_init_fn_def(self.tcx, diagnostic, &mut self.mem_init_fn_cache),
478+
layout.len(),
479+
*pointee_info.ty(),
480+
);
481+
Terminator {
482+
kind: TerminatorKind::Call {
483+
func: Operand::Copy(Place::from(body.new_local(
484+
set_ptr_initialized_instance.ty(),
485+
source.span(body.blocks()),
486+
Mutability::Not,
487+
))),
488+
args,
489+
destination: ret_place.clone(),
490+
target: Some(0), // this will be overriden in add_bb
491+
unwind: UnwindAction::Terminate,
492+
},
493+
span: source.span(body.blocks()),
494+
}
495+
}
412496
};
413497
// Construct the basic block and insert it into the body.
414498
body.insert_bb(BasicBlock { statements, terminator }, source, operation.position());
@@ -426,14 +510,19 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> {
426510
local: body.new_local(Ty::new_tuple(&[]), source.span(body.blocks()), Mutability::Not),
427511
projection: vec![],
428512
};
429-
let PointeeLayout::Sized { layout } = pointee_info.layout() else { unreachable!() };
513+
let layout_size = pointee_info.layout().maybe_size().unwrap();
430514
let copy_init_state_instance = resolve_mem_init_fn(
431-
get_mem_init_fn_def(self.tcx, "KaniCopyInitState", &mut self.mem_init_fn_cache),
432-
layout.len(),
515+
get_mem_init_fn_def(
516+
self.tcx,
517+
KANI_COPY_INIT_STATE_DIAGNOSTIC,
518+
&mut self.mem_init_fn_cache,
519+
),
520+
layout_size,
433521
*pointee_info.ty(),
434522
);
435523
let position = operation.position();
436-
let MemoryInitOp::Copy { from, to, count } = operation else { unreachable!() };
524+
let (from, to) = operation.expect_copy_operands();
525+
let count = operation.expect_count();
437526
body.insert_call(
438527
&copy_init_state_instance,
439528
source,
@@ -443,6 +532,49 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> {
443532
);
444533
}
445534

535+
/// Copy memory initialization state from one union variable to another.
536+
fn build_assign_union(
537+
&mut self,
538+
body: &mut MutableBody,
539+
source: &mut SourceInstruction,
540+
operation: MemoryInitOp,
541+
pointee_info: PointeeInfo,
542+
) {
543+
let ret_place = Place {
544+
local: body.new_local(Ty::new_tuple(&[]), source.span(body.blocks()), Mutability::Not),
545+
projection: vec![],
546+
};
547+
let mut statements = vec![];
548+
let layout_size = pointee_info.layout().maybe_size().unwrap();
549+
let copy_init_state_instance = resolve_mem_init_fn(
550+
get_mem_init_fn_def(
551+
self.tcx,
552+
KANI_COPY_INIT_STATE_SINGLE_DIAGNOSTIC,
553+
&mut self.mem_init_fn_cache,
554+
),
555+
layout_size,
556+
*pointee_info.ty(),
557+
);
558+
let (from, to) = operation.expect_assign_union_operands(body, &mut statements, source);
559+
let terminator = Terminator {
560+
kind: TerminatorKind::Call {
561+
func: Operand::Copy(Place::from(body.new_local(
562+
copy_init_state_instance.ty(),
563+
source.span(body.blocks()),
564+
Mutability::Not,
565+
))),
566+
args: vec![from, to],
567+
destination: ret_place.clone(),
568+
target: Some(0), // this will be overriden in add_bb
569+
unwind: UnwindAction::Terminate,
570+
},
571+
span: source.span(body.blocks()),
572+
};
573+
574+
// Construct the basic block and insert it into the body.
575+
body.insert_bb(BasicBlock { statements, terminator }, source, operation.position());
576+
}
577+
446578
fn inject_assert_false(
447579
&self,
448580
tcx: TyCtxt,

kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ use stable_mir::{
1919
alloc::GlobalAlloc,
2020
mono::{Instance, InstanceKind},
2121
visit::{Location, PlaceContext},
22-
CastKind, LocalDecl, MirVisitor, NonDivergingIntrinsic, Operand, Place, PointerCoercion,
23-
ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind,
22+
AggregateKind, CastKind, LocalDecl, MirVisitor, NonDivergingIntrinsic, Operand, Place,
23+
PointerCoercion, ProjectionElem, Rvalue, Statement, StatementKind, Terminator,
24+
TerminatorKind,
2425
},
25-
ty::{ConstantKind, RigidTy, TyKind},
26+
ty::{AdtKind, ConstantKind, RigidTy, TyKind},
2627
};
2728

2829
pub struct CheckUninitVisitor {
@@ -112,7 +113,7 @@ impl MirVisitor for CheckUninitVisitor {
112113
}
113114
}
114115
// Check whether Rvalue creates a new initialized pointer previously not captured inside shadow memory.
115-
if place.ty(&&self.locals).unwrap().kind().is_raw_ptr() {
116+
if place.ty(&self.locals).unwrap().kind().is_raw_ptr() {
116117
if let Rvalue::AddressOf(..) = rvalue {
117118
self.push_target(MemoryInitOp::Set {
118119
operand: Operand::Copy(place.clone()),
@@ -121,6 +122,58 @@ impl MirVisitor for CheckUninitVisitor {
121122
});
122123
}
123124
}
125+
126+
// TODO: add support for ADTs which could have unions as subfields. Currently,
127+
// if a union as a subfield is detected, `assert!(false)` will be injected from
128+
// the type layout code.
129+
let is_inside_union = {
130+
let mut contains_union = false;
131+
let mut place_to_add_projections =
132+
Place { local: place.local, projection: vec![] };
133+
for projection_elem in place.projection.iter() {
134+
if place_to_add_projections.ty(&self.locals).unwrap().kind().is_union() {
135+
contains_union = true;
136+
break;
137+
}
138+
place_to_add_projections.projection.push(projection_elem.clone());
139+
}
140+
contains_union
141+
};
142+
143+
// Need to copy some information about union initialization, since lvalue is
144+
// either a union or a field inside a union.
145+
if is_inside_union {
146+
if let Rvalue::Use(operand) = rvalue {
147+
// This is a union-to-union assignment, so we need to copy the
148+
// initialization state.
149+
if place.ty(&self.locals).unwrap().kind().is_union() {
150+
self.push_target(MemoryInitOp::AssignUnion {
151+
lvalue: place.clone(),
152+
rvalue: operand.clone(),
153+
});
154+
} else {
155+
// This is assignment to a field of a union.
156+
self.push_target(MemoryInitOp::SetRef {
157+
operand: Operand::Copy(place.clone()),
158+
value: true,
159+
position: InsertPosition::After,
160+
});
161+
}
162+
}
163+
}
164+
165+
// Create a union from scratch as an aggregate. We handle it here because we
166+
// need to know which field is getting assigned.
167+
if let Rvalue::Aggregate(AggregateKind::Adt(adt_def, _, _, _, union_field), _) =
168+
rvalue
169+
{
170+
if adt_def.kind() == AdtKind::Union {
171+
self.push_target(MemoryInitOp::CreateUnion {
172+
operand: Operand::Copy(place.clone()),
173+
field: union_field.unwrap(), // Safe to unwrap because we know this is a union.
174+
});
175+
}
176+
}
124177
}
125178
StatementKind::Deinit(place) => {
126179
self.super_statement(stmt, location);
@@ -350,12 +403,22 @@ impl MirVisitor for CheckUninitVisitor {
350403
});
351404
}
352405
}
353-
ProjectionElem::Field(idx, target_ty) => {
354-
if target_ty.kind().is_union()
355-
&& (!ptx.is_mutating() || place.projection.len() > idx + 1)
406+
ProjectionElem::Field(_, _) => {
407+
if intermediate_place.ty(&self.locals).unwrap().kind().is_union()
408+
&& !ptx.is_mutating()
356409
{
357-
self.push_target(MemoryInitOp::Unsupported {
358-
reason: "Kani does not support reasoning about memory initialization of unions.".to_string(),
410+
let contains_deref_projection =
411+
{ place.projection.iter().any(|elem| *elem == ProjectionElem::Deref) };
412+
if contains_deref_projection {
413+
// We do not currently support having a deref projection in the same
414+
// place as union field access.
415+
self.push_target(MemoryInitOp::Unsupported {
416+
reason: "Kani does not yet support performing a dereference on a union field".to_string(),
417+
});
418+
}
419+
// Accessing a place inside the union, need to check if it is initialized.
420+
self.push_target(MemoryInitOp::CheckRef {
421+
operand: Operand::Copy(place.clone()),
359422
});
360423
}
361424
}

0 commit comments

Comments
 (0)