From 52c01aa18b8a0d80bb006a9e2d143b9eaebc8fb3 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Tue, 11 Jun 2024 14:36:20 -0700 Subject: [PATCH 01/87] Towards implementing uninitialized memory detection in raw pointers --- kani-compiler/src/args.rs | 2 + .../codegen_cprover_gotoc/overrides/hooks.rs | 26 +- .../src/kani_middle/transform/body.rs | 27 + .../src/kani_middle/transform/check_uninit.rs | 818 ++++++++++++++++++ .../src/kani_middle/transform/mod.rs | 3 + kani-driver/src/call_single_file.rs | 4 + kani_metadata/src/unstable.rs | 2 + library/kani/src/shadow.rs | 79 ++ tests/uninit/after-aggregate-assign.rs | 28 + .../alloc-diagnostic-with-provenance.rs | 32 + tests/uninit/alloc-diagnostic.rs | 21 + tests/uninit/byte-read.rs | 6 + 12 files changed, 1044 insertions(+), 4 deletions(-) create mode 100644 kani-compiler/src/kani_middle/transform/check_uninit.rs create mode 100644 tests/uninit/after-aggregate-assign.rs create mode 100644 tests/uninit/alloc-diagnostic-with-provenance.rs create mode 100644 tests/uninit/alloc-diagnostic.rs create mode 100644 tests/uninit/byte-read.rs diff --git a/kani-compiler/src/args.rs b/kani-compiler/src/args.rs index b4d4eb3718d8..e4b7a4435b0f 100644 --- a/kani-compiler/src/args.rs +++ b/kani-compiler/src/args.rs @@ -88,4 +88,6 @@ pub enum ExtraChecks { /// Check pointer validity when casting pointers to references. /// See https://github.com/model-checking/kani/issues/2975. PtrToRefCast, + /// Check for using uninitialized memory. + Uninit, } diff --git a/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs b/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs index d66e16b6ce59..89be6096794d 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs @@ -252,14 +252,23 @@ impl GotocHook for PointerObject { fn handle( &self, gcx: &mut GotocCtx, - _instance: Instance, + instance: Instance, mut fargs: Vec, assign_to: &Place, target: Option, span: Span, ) -> Stmt { assert_eq!(fargs.len(), 1); - let ptr = fargs.pop().unwrap().cast_to(Type::void_pointer()); + let ptr = { + let ptr = fargs.pop().unwrap(); + let place_ty = instance.args().0[0].expect_ty().clone(); + if gcx.use_thin_pointer_stable(place_ty) { + ptr + } else { + ptr.member("data", &gcx.symbol_table) + } + } + .cast_to(Type::void_pointer()); let target = target.unwrap(); let loc = gcx.codegen_caller_span_stable(span); let ret_place = @@ -286,14 +295,23 @@ impl GotocHook for PointerOffset { fn handle( &self, gcx: &mut GotocCtx, - _instance: Instance, + instance: Instance, mut fargs: Vec, assign_to: &Place, target: Option, span: Span, ) -> Stmt { assert_eq!(fargs.len(), 1); - let ptr = fargs.pop().unwrap().cast_to(Type::void_pointer()); + let ptr = { + let ptr = fargs.pop().unwrap(); + let place_ty = instance.args().0[0].expect_ty().clone(); + if gcx.use_thin_pointer_stable(place_ty) { + ptr + } else { + ptr.member("data", &gcx.symbol_table) + } + } + .cast_to(Type::void_pointer()); let target = target.unwrap(); let loc = gcx.codegen_caller_span_stable(span); let ret_place = diff --git a/kani-compiler/src/kani_middle/transform/body.rs b/kani-compiler/src/kani_middle/transform/body.rs index 9402835c2ff2..9c1ad96b9467 100644 --- a/kani-compiler/src/kani_middle/transform/body.rs +++ b/kani-compiler/src/kani_middle/transform/body.rs @@ -186,6 +186,33 @@ impl MutableBody { } } + /// Add a new call to the basic block indicated by the given index. + /// + /// The new call will have the same span as the source instruction, and the basic block + /// will be split. The source instruction will be adjusted to point to the first instruction in + /// the new basic block. + pub fn add_call( + &mut self, + callee: &Instance, + source: &mut SourceInstruction, + args: Vec, + destination: Place, + ) { + let new_bb = self.blocks.len(); + let span = source.span(&self.blocks); + let callee_op = + Operand::Copy(Place::from(self.new_local(callee.ty(), span, Mutability::Not))); + let kind = TerminatorKind::Call { + func: callee_op, + args, + destination, + target: Some(new_bb), + unwind: UnwindAction::Terminate, + }; + let terminator = Terminator { kind, span }; + self.split_bb(source, terminator); + } + /// Split a basic block right before the source location and use the new terminator /// in the basic block that was split. /// diff --git a/kani-compiler/src/kani_middle/transform/check_uninit.rs b/kani-compiler/src/kani_middle/transform/check_uninit.rs new file mode 100644 index 000000000000..bb29b60c16d7 --- /dev/null +++ b/kani-compiler/src/kani_middle/transform/check_uninit.rs @@ -0,0 +1,818 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +//! Implement a transformation pass that instruments the code to detect possible UB due to +//! the accesses to uninitialized memory. + +use crate::args::ExtraChecks; +use crate::kani_middle::find_fn_def; +use crate::kani_middle::transform::body::{CheckType, MutableBody, SourceInstruction}; +use crate::kani_middle::transform::{TransformPass, TransformationType}; +use crate::kani_queries::QueryDb; +use rustc_middle::ty::TyCtxt; +use stable_mir::abi::{FieldsShape, Scalar, TagEncoding, ValueAbi, VariantsShape}; +use stable_mir::mir::mono::{Instance, InstanceKind}; +use stable_mir::mir::visit::{Location, PlaceContext}; +use stable_mir::mir::{ + AggregateKind, BasicBlockIdx, Body, Constant, LocalDecl, MirVisitor, Mutability, + NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, Statement, StatementKind, + Terminator, TerminatorKind, +}; +use stable_mir::target::{MachineInfo, MachineSize}; +use stable_mir::ty::{ + AdtKind, Const, GenericArgKind, GenericArgs, IndexedVal, RigidTy, Ty, TyKind, UintTy, +}; +use stable_mir::CrateDef; +use std::fmt::Debug; +use strum_macros::AsRefStr; +use tracing::{debug, trace}; + +const UNINIT_ALLOWLIST: &[&str] = + &["kani::shadow::global_sm_get", "kani::shadow::global_sm_set", "std::alloc::alloc"]; + +/// Instrument the code with checks for uninitialized memory. +#[derive(Debug)] +pub struct UninitPass { + pub check_type: CheckType, +} + +impl TransformPass for UninitPass { + fn transformation_type() -> TransformationType + where + Self: Sized, + { + TransformationType::Instrumentation + } + + fn is_enabled(&self, query_db: &QueryDb) -> bool + where + Self: Sized, + { + let args = query_db.args(); + args.ub_check.contains(&ExtraChecks::Uninit) + } + + fn transform(&self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { + trace!(function=?instance.name(), "transform"); + + if UNINIT_ALLOWLIST.iter().any(|allowlist_item| instance.name().contains(allowlist_item)) { + return (false, body); + } + + let mut new_body = MutableBody::from(body); + let orig_len = new_body.blocks().len(); + // Do not cache body.blocks().len() since it will change as we add new checks. + let mut bb_idx = 0; + while bb_idx < new_body.blocks().len() { + if let Some(candidate) = + CheckUninitVisitor::find_next(&new_body, bb_idx, bb_idx >= orig_len) + { + self.build_check(tcx, &mut new_body, candidate); + bb_idx += 1 + } else { + bb_idx += 1; + }; + } + (orig_len != new_body.blocks().len(), new_body.into()) + } +} + +impl UninitPass { + fn build_check( + &self, + tcx: TyCtxt, + body: &mut MutableBody, + instruction: InitRelevantInstruction, + ) { + debug!(?instruction, "build_check"); + let mut source = instruction.source; + for operation in instruction.operations { + let place = match &operation { + SourceOp::Get { place, .. } + | SourceOp::Set { place, .. } + | SourceOp::GetN { place, .. } + | SourceOp::SetN { place, .. } => place, + SourceOp::Unsupported { instruction, place } => { + let place_ty = place.ty(body.locals()).unwrap(); + let reason = format!( + "Kani currently doesn't support checking memory initialization using instruction `{instruction}` for type `{place_ty}`", + ); + self.unsupported_check(tcx, body, &mut source, &reason); + continue; + } + }; + + let place_ty = place.ty(body.locals()).unwrap(); + let pointee_ty = match place_ty.kind() { + TyKind::RigidTy(RigidTy::RawPtr(pointee_ty, _)) => pointee_ty, + _ => unreachable!(), + }; + + let layout_mask = layout_mask(pointee_ty).unwrap(); + + // if layout_mask.iter().all(|byte| *byte) { + // match operation { + // SourceOp::Get { .. } => { + // let sm_get = Instance::resolve( + // find_fn_def(tcx, "KaniShadowMemoryGet").unwrap(), + // &GenericArgs(vec![GenericArgKind::Type(pointee_ty)]), + // ) + // .unwrap(); + + // let ret_place = Place { + // local: body.new_local( + // Ty::bool_ty(), + // source.span(body.blocks()), + // Mutability::Not, + // ), + // projection: vec![], + // }; + + // body.add_call( + // &sm_get, + // &mut source, + // vec![Operand::Copy(place.clone())], + // ret_place.clone(), + // ); + // body.add_check( + // tcx, + // &self.check_type, + // &mut source, + // ret_place.local, + // &format!("Undefined Behavior: Reading from an uninitialized pointer of type `{place_ty}`"), + // ) + // } + // SourceOp::Set { value, .. } => { + // let sm_set = Instance::resolve( + // find_fn_def(tcx, "KaniShadowMemorySet").unwrap(), + // &GenericArgs(vec![GenericArgKind::Type(pointee_ty)]), + // ) + // .unwrap(); + // let ret_place = Place { + // local: body.new_local( + // Ty::new_tuple(&[]), + // source.span(body.blocks()), + // Mutability::Not, + // ), + // projection: vec![], + // }; + // let span = source.span(body.blocks()); + // body.add_call( + // &sm_set, + // &mut source, + // vec![ + // Operand::Copy(place.clone()), + // Operand::Constant(Constant { + // span, + // user_ty: None, + // literal: Const::from_bool(value), + // }), + // ], + // ret_place, + // ); + // } + // SourceOp::Unsupported { .. } => { + // unreachable!() + // } + // } + // } else { + let span = source.span(body.blocks()); + let layout_local = body.new_assignment( + Rvalue::Aggregate( + AggregateKind::Array(Ty::bool_ty()), + layout_mask + .iter() + .map(|byte| { + Operand::Constant(Constant { + span, + user_ty: None, + literal: Const::from_bool(*byte), + }) + }) + .collect(), + ), + &mut source, + ); + let ptr_local = body.new_cast_ptr( + Operand::Copy(place.clone()), + Ty::new_tuple(&[]), + Mutability::Not, + &mut source, + ); + + match operation { + SourceOp::Get { .. } => { + let sm_get = Instance::resolve( + find_fn_def(tcx, "KaniShadowMemoryGetWithLayout").unwrap(), + &GenericArgs(vec![GenericArgKind::Const( + Const::try_from_uint(layout_mask.len() as u128, UintTy::Usize).unwrap(), + )]), + ) + .unwrap(); + + let ret_place = Place { + local: body.new_local( + Ty::bool_ty(), + source.span(body.blocks()), + Mutability::Not, + ), + projection: vec![], + }; + + body.add_call( + &sm_get, + &mut source, + vec![ + Operand::Copy(Place { local: ptr_local, projection: vec![] }), + Operand::Move(Place { local: layout_local, projection: vec![] }), + ], + ret_place.clone(), + ); + body.add_check( + tcx, + &self.check_type, + &mut source, + ret_place.local, + &format!("Undefined Behavior: Reading from an uninitialized pointer of type `{place_ty}`"), + ) + } + SourceOp::GetN { count, .. } => { + let sm_get = Instance::resolve( + find_fn_def(tcx, "KaniShadowMemoryGetWithLayoutDynamic").unwrap(), + &GenericArgs(vec![GenericArgKind::Const( + Const::try_from_uint(layout_mask.len() as u128, UintTy::Usize).unwrap(), + )]), + ) + .unwrap(); + + let ret_place = Place { + local: body.new_local( + Ty::bool_ty(), + source.span(body.blocks()), + Mutability::Not, + ), + projection: vec![], + }; + + body.add_call( + &sm_get, + &mut source, + vec![ + Operand::Copy(Place { local: ptr_local, projection: vec![] }), + Operand::Move(Place { local: layout_local, projection: vec![] }), + count, + ], + ret_place.clone(), + ); + body.add_check( + tcx, + &self.check_type, + &mut source, + ret_place.local, + &format!("Undefined Behavior: Reading from an uninitialized pointer of type `{place_ty}`"), + ) + } + SourceOp::Set { value, .. } => { + let sm_set = Instance::resolve( + find_fn_def(tcx, "KaniShadowMemorySetWithLayout").unwrap(), + &GenericArgs(vec![GenericArgKind::Const( + Const::try_from_uint(layout_mask.len() as u128, UintTy::Usize).unwrap(), + )]), + ) + .unwrap(); + let ret_place = Place { + local: body.new_local( + Ty::new_tuple(&[]), + source.span(body.blocks()), + Mutability::Not, + ), + projection: vec![], + }; + body.add_call( + &sm_set, + &mut source, + vec![ + Operand::Copy(Place { local: ptr_local, projection: vec![] }), + Operand::Move(Place { local: layout_local, projection: vec![] }), + Operand::Constant(Constant { + span, + user_ty: None, + literal: Const::from_bool(value), + }), + ], + ret_place, + ); + } + SourceOp::SetN { count, value, .. } => { + let sm_set = Instance::resolve( + find_fn_def(tcx, "KaniShadowMemorySetWithLayoutDynamic").unwrap(), + &GenericArgs(vec![GenericArgKind::Const( + Const::try_from_uint(layout_mask.len() as u128, UintTy::Usize).unwrap(), + )]), + ) + .unwrap(); + let ret_place = Place { + local: body.new_local( + Ty::new_tuple(&[]), + source.span(body.blocks()), + Mutability::Not, + ), + projection: vec![], + }; + body.add_call( + &sm_set, + &mut source, + vec![ + Operand::Copy(Place { local: ptr_local, projection: vec![] }), + Operand::Move(Place { local: layout_local, projection: vec![] }), + count, + Operand::Constant(Constant { + span, + user_ty: None, + literal: Const::from_bool(value), + }), + ], + ret_place, + ); + } + SourceOp::Unsupported { .. } => { + unreachable!() + } + } + // } + } + } + + fn unsupported_check( + &self, + tcx: TyCtxt, + body: &mut MutableBody, + source: &mut SourceInstruction, + reason: &str, + ) { + let span = source.span(body.blocks()); + let rvalue = Rvalue::Use(Operand::Constant(Constant { + literal: Const::from_bool(false), + span, + user_ty: None, + })); + let result = body.new_assignment(rvalue, source); + body.add_check(tcx, &self.check_type, source, result, reason); + } +} + +#[derive(AsRefStr, Clone, Debug)] +enum SourceOp { + Get { place: Place }, + GetN { place: Place, count: Operand }, + Set { place: Place, value: bool }, + SetN { place: Place, count: Operand, value: bool }, + Unsupported { instruction: String, place: Place }, +} + +#[derive(Clone, Debug)] +struct InitRelevantInstruction { + /// The instruction that affects the state of the memory. + source: SourceInstruction, + /// All memory-related operations in this instructions. + operations: Vec, +} + +struct CheckUninitVisitor<'a> { + locals: &'a [LocalDecl], + /// Whether we should skip the next instruction, since it might've been instrumented already. + /// When we instrument an instruction, we partition the basic block, and the instruction that + /// may trigger UB becomes the first instruction of the basic block, which we need to skip + /// later. + skip_next: bool, + /// The instruction being visited at a given point. + current: SourceInstruction, + /// The target instruction that should be verified. + pub target: Option, + /// The basic block being visited. + bb: BasicBlockIdx, +} + +fn expect_place(op: &Operand) -> &Place { + match op { + Operand::Copy(place) | Operand::Move(place) => place, + Operand::Constant(_) => unreachable!(), + } +} + +fn try_remove_topmost_deref(place: &Place) -> Option { + let mut new_place = place.clone(); + if let Some(ProjectionElem::Deref) = new_place.projection.pop() { + Some(new_place) + } else { + None + } +} + +/// Retrieve instance for the given function operand. +/// +/// This will panic if the operand is not a function or if it cannot be resolved. +fn expect_instance(locals: &[LocalDecl], func: &Operand) -> Instance { + let ty = func.ty(locals).unwrap(); + match ty.kind() { + TyKind::RigidTy(RigidTy::FnDef(def, args)) => Instance::resolve(def, &args).unwrap(), + _ => unreachable!(), + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct DataBytes { + /// Offset in bytes. + offset: usize, + /// Size of this requirement. + size: MachineSize, +} + +fn scalar_ty_size(machine_info: &MachineInfo, ty: Ty) -> Option { + let shape = ty.layout().unwrap().shape(); + match shape.abi { + ValueAbi::Scalar(Scalar::Initialized { value, .. }) + | ValueAbi::ScalarPair(Scalar::Initialized { value, .. }, _) => { + Some(DataBytes { offset: 0, size: value.size(machine_info) }) + } + ValueAbi::Scalar(_) + | ValueAbi::ScalarPair(_, _) + | ValueAbi::Uninhabited + | ValueAbi::Vector { .. } + | ValueAbi::Aggregate { .. } => None, + } +} + +fn ty_layout( + machine_info: &MachineInfo, + ty: Ty, + current_offset: usize, +) -> Result, String> { + let layout = ty.layout().unwrap().shape(); + let ty_size = || { + if let Some(mut size) = scalar_ty_size(machine_info, ty) { + size.offset = current_offset; + vec![size] + } else { + vec![] + } + }; + match layout.fields { + FieldsShape::Primitive => Ok(ty_size()), + FieldsShape::Array { stride, count } if count > 0 => { + let TyKind::RigidTy(RigidTy::Array(elem_ty, _)) = ty.kind() else { unreachable!() }; + let elem_validity = ty_layout(machine_info, elem_ty, current_offset)?; + let mut result = vec![]; + if !elem_validity.is_empty() { + for idx in 0..count { + let idx: usize = idx.try_into().unwrap(); + let elem_offset = idx * stride.bytes(); + let mut next_validity = elem_validity + .iter() + .cloned() + .map(|mut req| { + req.offset += elem_offset; + req + }) + .collect::>(); + result.append(&mut next_validity) + } + } + Ok(result) + } + FieldsShape::Arbitrary { ref offsets } => { + match ty.kind().rigid().expect(&format!("unexpected type: {ty:?}")) { + RigidTy::Adt(def, args) => { + match def.kind() { + AdtKind::Enum => { + // Support basic enumeration forms + let ty_variants = def.variants(); + match layout.variants { + VariantsShape::Single { index } => { + // Only one variant is reachable. This behaves like a struct. + let fields = ty_variants[index.to_index()].fields(); + let mut fields_validity = vec![]; + for idx in layout.fields.fields_by_offset_order() { + let field_offset = offsets[idx].bytes(); + let field_ty = fields[idx].ty_with_args(&args); + fields_validity.append(&mut ty_layout( + machine_info, + field_ty, + field_offset + current_offset, + )?); + } + Ok(fields_validity) + } + VariantsShape::Multiple { + tag_encoding: TagEncoding::Niche { .. }, + .. + } => { + Err(format!("Unsupported Enum `{}` check", def.trimmed_name()))? + } + VariantsShape::Multiple { variants, .. } => { + let enum_validity = ty_size(); + let mut fields_validity = vec![]; + for (index, variant) in variants.iter().enumerate() { + let fields = ty_variants[index].fields(); + for field_idx in variant.fields.fields_by_offset_order() { + let field_offset = offsets[field_idx].bytes(); + let field_ty = fields[field_idx].ty_with_args(&args); + fields_validity.append(&mut ty_layout( + machine_info, + field_ty, + field_offset + current_offset, + )?); + } + } + if fields_validity.is_empty() { + Ok(enum_validity) + } else { + Err(format!( + "Unsupported Enum `{}` check", + def.trimmed_name() + )) + } + } + } + } + AdtKind::Union => unreachable!(), + AdtKind::Struct => { + // If the struct range has niche add that. + let mut struct_validity = ty_size(); + let fields = def.variants_iter().next().unwrap().fields(); + for idx in layout.fields.fields_by_offset_order() { + let field_offset = offsets[idx].bytes(); + let field_ty = fields[idx].ty_with_args(&args); + struct_validity.append(&mut ty_layout( + machine_info, + field_ty, + field_offset + current_offset, + )?); + } + Ok(struct_validity) + } + } + } + RigidTy::Pat(base_ty, ..) => { + // This is similar to a structure with one field and with niche defined. + let mut pat_validity = ty_size(); + pat_validity.append(&mut ty_layout(machine_info, *base_ty, 0)?); + Ok(pat_validity) + } + RigidTy::Tuple(tys) => { + let mut tuple_validity = vec![]; + for idx in layout.fields.fields_by_offset_order() { + let field_offset = offsets[idx].bytes(); + let field_ty = tys[idx]; + tuple_validity.append(&mut ty_layout( + machine_info, + field_ty, + field_offset + current_offset, + )?); + } + Ok(tuple_validity) + } + RigidTy::Bool + | RigidTy::Char + | RigidTy::Int(_) + | RigidTy::Uint(_) + | RigidTy::Float(_) + | RigidTy::Never => { + unreachable!("Expected primitive layout for {ty:?}") + } + RigidTy::Str | RigidTy::Slice(_) | RigidTy::Array(_, _) => { + unreachable!("Expected array layout for {ty:?}") + } + RigidTy::RawPtr(_, _) | RigidTy::Ref(_, _, _) => { + // Fat pointer has arbitrary shape. + Ok(ty_size()) + } + RigidTy::FnDef(_, _) + | RigidTy::FnPtr(_) + | RigidTy::Closure(_, _) + | RigidTy::Coroutine(_, _, _) + | RigidTy::CoroutineWitness(_, _) + | RigidTy::Foreign(_) + | RigidTy::Dynamic(_, _, _) => Err(format!("Unsupported {ty:?}")), + } + } + FieldsShape::Union(_) | FieldsShape::Array { .. } => { + /* Anything is valid */ + Ok(vec![]) + } + } +} + +fn layout_mask(ty: Ty) -> Result, String> { + let ty_layout = ty_layout(&MachineInfo::target(), ty, 0)?; + let ty_size = ty.layout().unwrap().shape().size.bytes(); + let mut layout_mask = vec![false; ty_size]; + for data_bytes in ty_layout.iter() { + for i in data_bytes.offset..data_bytes.offset + data_bytes.size.bytes() { + layout_mask[i] = true; + } + } + Ok(layout_mask) +} + +impl<'a> CheckUninitVisitor<'a> { + fn find_next( + body: &'a MutableBody, + bb: BasicBlockIdx, + skip_first: bool, + ) -> Option { + let mut visitor = CheckUninitVisitor { + locals: body.locals(), + skip_next: skip_first, + current: SourceInstruction::Statement { idx: 0, bb }, + target: None, + bb, + }; + visitor.visit_basic_block(&body.blocks()[bb]); + visitor.target + } + + fn push_target(&mut self, op: SourceOp) { + let target = self.target.get_or_insert_with(|| InitRelevantInstruction { + source: self.current, + operations: vec![], + }); + target.operations.push(op); + } +} + +impl<'a> MirVisitor for CheckUninitVisitor<'a> { + fn visit_statement(&mut self, stmt: &Statement, location: Location) { + if self.skip_next { + self.skip_next = false; + } else if self.target.is_none() { + // Leave it as an exhaustive match to be notified when a new kind is added. + match &stmt.kind { + StatementKind::Intrinsic(NonDivergingIntrinsic::CopyNonOverlapping(copy)) => { + // Source is a *const T and it must be initialized. + self.push_target(SourceOp::GetN { + place: expect_place(©.src).clone(), + count: copy.count.clone(), + }); + // Destimation is a *mut T so it gets initialized. + self.push_target(SourceOp::SetN { + place: expect_place(©.dst).clone(), + count: copy.count.clone(), + value: true, + }); + self.super_statement(stmt, location) + } + StatementKind::Assign(place, rvalue) => { + // First check rvalue. + self.visit_rvalue(rvalue, location); + // Then check the destination place. + if let Some(place_without_deref) = try_remove_topmost_deref(place) { + if place_without_deref.ty(&self.locals).unwrap().kind().is_raw_ptr() { + self.push_target(SourceOp::Set { + place: place_without_deref, + value: true, + }); + } + } + } + StatementKind::Deinit(place) => { + self.push_target(SourceOp::Set { place: place.clone(), value: false }); + } + StatementKind::FakeRead(_, _) + | StatementKind::SetDiscriminant { .. } + | StatementKind::StorageLive(_) + | StatementKind::StorageDead(_) + | StatementKind::Retag(_, _) + | StatementKind::PlaceMention(_) + | StatementKind::AscribeUserType { .. } + | StatementKind::Coverage(_) + | StatementKind::ConstEvalCounter + | StatementKind::Intrinsic(NonDivergingIntrinsic::Assume(_)) + | StatementKind::Nop => self.super_statement(stmt, location), + } + } + let SourceInstruction::Statement { idx, bb } = self.current else { unreachable!() }; + self.current = SourceInstruction::Statement { idx: idx + 1, bb }; + } + fn visit_terminator(&mut self, term: &Terminator, location: Location) { + if !(self.skip_next || self.target.is_some()) { + self.current = SourceInstruction::Terminator { bb: self.bb }; + // Leave it as an exhaustive match to be notified when a new kind is added. + match &term.kind { + TerminatorKind::Call { func, args, .. } => { + self.super_terminator(term, location); + let instance = expect_instance(self.locals, func); + if instance.kind == InstanceKind::Intrinsic { + match instance.intrinsic_name().unwrap().as_str() { + "write_bytes" => { + assert_eq!( + args.len(), + 3, + "Unexpected number of arguments for `write_bytes`" + ); + assert!(matches!( + args[0].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) + )); + self.push_target(SourceOp::SetN { + place: expect_place(&args[0]).clone(), + count: args[2].clone(), + value: true, + }) + } + "compare_bytes" => { + assert_eq!( + args.len(), + 3, + "Unexpected number of arguments for `compare_bytes`" + ); + assert!(matches!( + args[0].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) + )); + assert!(matches!( + args[1].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) + )); + self.push_target(SourceOp::GetN { + place: expect_place(&args[0]).clone(), + count: args[2].clone(), + }); + self.push_target(SourceOp::GetN { + place: expect_place(&args[1]).clone(), + count: args[2].clone(), + }); + } + "raw_eq" => { + assert_eq!( + args.len(), + 3, + "Unexpected number of arguments for `raw_eq`" + ); + assert!(matches!( + args[0].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::Ref(_, _, Mutability::Not)) + )); + assert!(matches!( + args[1].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::Ref(_, _, Mutability::Not)) + )); + self.push_target(SourceOp::Get { + place: expect_place(&args[0]).clone(), + }); + self.push_target(SourceOp::Get { + place: expect_place(&args[1]).clone(), + }); + } + "transmute" | "transmute_copy" => { + unreachable!("Should've been lowered") + } + _ => {} + } + } + } + TerminatorKind::Goto { .. } + | TerminatorKind::SwitchInt { .. } + | TerminatorKind::Resume + | TerminatorKind::Abort + | TerminatorKind::Return + | TerminatorKind::Unreachable + | TerminatorKind::Drop { .. } + | TerminatorKind::Assert { .. } + | TerminatorKind::InlineAsm { .. } => self.super_terminator(term, location), + } + } + } + + fn visit_place(&mut self, place: &Place, ptx: PlaceContext, location: Location) { + for (idx, elem) in place.projection.iter().enumerate() { + let intermediate_place = + Place { local: place.local, projection: place.projection[..idx].to_vec() }; + match elem { + ProjectionElem::Deref => { + let ptr_ty = intermediate_place.ty(self.locals).unwrap(); + if ptr_ty.kind().is_raw_ptr() { + self.push_target(SourceOp::Get { place: intermediate_place.clone() }); + } + } + ProjectionElem::Field(idx, target_ty) => { + if target_ty.kind().is_union() + && (!ptx.is_mutating() || place.projection.len() > idx + 1) + { + self.push_target(SourceOp::Unsupported { + instruction: "union access".to_string(), + place: intermediate_place.clone(), + }); + } + } + ProjectionElem::Downcast(_) => {} + ProjectionElem::OpaqueCast(_) => {} + ProjectionElem::Subtype(_) => {} + ProjectionElem::Index(_) + | ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Subslice { .. } => {} + } + } + self.super_place(place, ptx, location) + } +} diff --git a/kani-compiler/src/kani_middle/transform/mod.rs b/kani-compiler/src/kani_middle/transform/mod.rs index d4e06a785fb3..afd2e4a4feec 100644 --- a/kani-compiler/src/kani_middle/transform/mod.rs +++ b/kani-compiler/src/kani_middle/transform/mod.rs @@ -17,6 +17,7 @@ //! For all instrumentation passes, always use exhaustive matches to ensure soundness in case a new //! case is added. use crate::kani_middle::transform::body::CheckType; +use crate::kani_middle::transform::check_uninit::UninitPass; use crate::kani_middle::transform::check_values::ValidValuePass; use crate::kani_middle::transform::kani_intrinsics::IntrinsicGeneratorPass; use crate::kani_queries::QueryDb; @@ -27,6 +28,7 @@ use std::collections::HashMap; use std::fmt::Debug; mod body; +mod check_uninit; mod check_values; mod kani_intrinsics; @@ -55,6 +57,7 @@ impl BodyTransformation { }; let check_type = CheckType::new(tcx); transformer.add_pass(queries, ValidValuePass { check_type: check_type.clone() }); + transformer.add_pass(queries, UninitPass { check_type: check_type.clone() }); transformer.add_pass(queries, IntrinsicGeneratorPass { check_type }); transformer } diff --git a/kani-driver/src/call_single_file.rs b/kani-driver/src/call_single_file.rs index 29128a02dc2a..117e8b08fc50 100644 --- a/kani-driver/src/call_single_file.rs +++ b/kani-driver/src/call_single_file.rs @@ -143,6 +143,10 @@ impl KaniSession { flags.push("--ub-check=ptr_to_ref_cast".into()) } + if self.args.common_args.unstable_features.contains(UnstableFeature::UninitChecks) { + flags.push("--ub-check=uninit".into()) + } + if self.args.ignore_locals_lifetime { flags.push("--ignore-storage-markers".into()) } diff --git a/kani_metadata/src/unstable.rs b/kani_metadata/src/unstable.rs index 9f82e4b02a64..68e4fba28819 100644 --- a/kani_metadata/src/unstable.rs +++ b/kani_metadata/src/unstable.rs @@ -91,6 +91,8 @@ pub enum UnstableFeature { GhostState, /// Automatically check that pointers are valid when casting them to references. PtrToRefCastChecks, + /// Automatically check that uninitialized memory is not used. + UninitChecks, /// Enable an unstable option or subcommand. UnstableOptions, } diff --git a/library/kani/src/shadow.rs b/library/kani/src/shadow.rs index a7ea57c6fd40..f79ebb390e63 100644 --- a/library/kani/src/shadow.rs +++ b/library/kani/src/shadow.rs @@ -81,3 +81,82 @@ impl ShadowMem { self.mem[obj][offset] = val; } } + +pub static mut GLOBAL_SM: ShadowMem = ShadowMem::new(false); + +#[rustc_diagnostic_item = "KaniShadowMemoryGet"] +pub fn global_sm_get(ptr: *const U) -> bool { + return unsafe { GLOBAL_SM.get(ptr) }; +} + +#[rustc_diagnostic_item = "KaniShadowMemorySet"] +pub fn global_sm_set(ptr: *const U, val: bool) { + return unsafe { GLOBAL_SM.set(ptr, val) }; +} + +#[rustc_diagnostic_item = "KaniShadowMemoryGetWithLayout"] +pub fn global_sm_get_with_layout(ptr: *const (), layout: [bool; N]) -> bool { + let mut offset: usize = 0; + while offset < N { + unsafe { + if layout[offset] && !GLOBAL_SM.get((ptr as *const u8).add(offset)) { + return false; + } + offset += 1; + } + } + return true; +} + +#[rustc_diagnostic_item = "KaniShadowMemorySetWithLayout"] +pub fn global_sm_set_with_layout(ptr: *const (), layout: [bool; N], value: bool) { + let mut offset: usize = 0; + while offset < N { + unsafe { + GLOBAL_SM.set((ptr as *const u8).add(offset), value && layout[offset]); + } + offset += 1; + } +} + +#[rustc_diagnostic_item = "KaniShadowMemoryGetWithLayoutDynamic"] +pub fn global_sm_get_with_layout_dynamic( + ptr: *const (), + layout: [bool; N], + n: usize, +) -> bool { + let mut count: usize = 0; + while count < n { + let mut offset: usize = 0; + while offset < N { + unsafe { + if layout[offset] && !GLOBAL_SM.get((ptr as *const u8).add(count * N + offset)) { + return false; + } + offset += 1; + } + } + count += 1; + } + return true; +} + +#[rustc_diagnostic_item = "KaniShadowMemorySetWithLayoutDynamic"] +pub fn global_sm_set_with_layout_dynamic( + ptr: *const (), + layout: [bool; N], + n: usize, + value: bool, +) { + let mut count: usize = 0; + while count < n { + let mut offset: usize = 0; + while offset < N { + unsafe { + GLOBAL_SM.set((ptr as *const u8).add(count * N + offset), value && layout[offset]); + } + offset += 1; + } + count += 1; + } +} diff --git a/tests/uninit/after-aggregate-assign.rs b/tests/uninit/after-aggregate-assign.rs new file mode 100644 index 000000000000..581716d27658 --- /dev/null +++ b/tests/uninit/after-aggregate-assign.rs @@ -0,0 +1,28 @@ +#![feature(core_intrinsics)] +#![feature(custom_mir)] + +use std::intrinsics::mir::*; +use std::ptr; + +#[repr(C)] +struct S(u8, u16); + +#[kani::proof] +#[custom_mir(dialect = "runtime", phase = "optimized")] +fn main() { + mir! { + let s: S; + let sptr; + let sptr2; + let _val; + { + sptr = ptr::addr_of_mut!(s); + sptr2 = sptr as *mut [u8; 4]; + *sptr2 = [0; 4]; + *sptr = S(0, 0); // should reset the padding + _val = *sptr2; // should hence be UB + //~^ERROR: encountered uninitialized memory + Return() + } + } +} diff --git a/tests/uninit/alloc-diagnostic-with-provenance.rs b/tests/uninit/alloc-diagnostic-with-provenance.rs new file mode 100644 index 000000000000..4635ebda36db --- /dev/null +++ b/tests/uninit/alloc-diagnostic-with-provenance.rs @@ -0,0 +1,32 @@ +#![feature(strict_provenance)] +#![allow(dropping_copy_types)] + +// Test printing allocations that contain single-byte provenance. + +use std::alloc::{alloc, dealloc, Layout}; +use std::mem::{self, MaybeUninit}; +use std::slice::from_raw_parts; + +fn byte_with_provenance(val: u8, prov: *const T) -> MaybeUninit { + let ptr = prov.with_addr(val as usize); + let bytes: [MaybeUninit; mem::size_of::<*const ()>()] = unsafe { mem::transmute(ptr) }; + let lsb = if cfg!(target_endian = "little") { 0 } else { bytes.len() - 1 }; + bytes[lsb] +} + +#[kani::proof] +fn main() { + let layout = Layout::from_size_align(16, 8).unwrap(); + unsafe { + let ptr = alloc(layout); + let ptr_raw = ptr.cast::>(); + *ptr_raw.add(0) = byte_with_provenance(0x42, &42u8); + *ptr.add(1) = 0x12; + *ptr.add(2) = 0x13; + *ptr_raw.add(3) = byte_with_provenance(0x43, &0u8); + let slice1 = from_raw_parts(ptr, 8); + let slice2 = from_raw_parts(ptr.add(8), 8); + drop(slice1.cmp(slice2)); + dealloc(ptr, layout); + } +} diff --git a/tests/uninit/alloc-diagnostic.rs b/tests/uninit/alloc-diagnostic.rs new file mode 100644 index 000000000000..e95c7ca5ecb9 --- /dev/null +++ b/tests/uninit/alloc-diagnostic.rs @@ -0,0 +1,21 @@ +#![allow(dropping_copy_types)] + +use std::alloc::{alloc, dealloc, Layout}; +use std::slice::from_raw_parts; + +#[kani::proof] +fn main() { + let layout = Layout::from_size_align(32, 8).unwrap(); + unsafe { + let ptr = alloc(layout); + *ptr = 0x41; + *ptr.add(1) = 0x42; + *ptr.add(2) = 0x43; + *ptr.add(3) = 0x44; + *ptr.add(16) = 0x00; + let slice1 = from_raw_parts(ptr, 16); + let slice2 = from_raw_parts(ptr.add(16), 16); + drop(slice1.cmp(slice2)); + dealloc(ptr, layout); + } +} \ No newline at end of file diff --git a/tests/uninit/byte-read.rs b/tests/uninit/byte-read.rs new file mode 100644 index 000000000000..e65ee7c8c757 --- /dev/null +++ b/tests/uninit/byte-read.rs @@ -0,0 +1,6 @@ +#[kani::proof] +fn main() { + let v: Vec = Vec::with_capacity(10); + let undef = unsafe { *v.as_ptr().add(5) }; //~ ERROR: uninitialized + let x = undef + 1; +} From 2b73af79c54413ddb586a78f85c2c79c8cabb296 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Wed, 12 Jun 2024 15:15:26 -0700 Subject: [PATCH 02/87] Implement uninitialized memory detection in raw pointers for certain pointee types --- .../codegen_cprover_gotoc/overrides/hooks.rs | 2 + .../src/kani_middle/transform/body.rs | 14 + .../src/kani_middle/transform/check_uninit.rs | 818 ------------------ .../kani_middle/transform/check_uninit/mod.rs | 279 ++++++ .../transform/check_uninit/ty_layout.rs | 234 +++++ .../transform/check_uninit/uninit_visitor.rs | 275 ++++++ library/kani/src/shadow.rs | 59 +- tests/uninit/after-aggregate-assign-init.rs | 32 + ...gn.rs => after-aggregate-assign-uninit.rs} | 5 + .../alloc-diagnostic-with-provenance.rs | 32 - tests/uninit/alloc-diagnostic.rs | 7 +- tests/uninit/byte-read-init.rs | 11 + tests/uninit/byte-read-semi-init.rs | 12 + .../{byte-read.rs => byte-read-uninit.rs} | 5 + 14 files changed, 887 insertions(+), 898 deletions(-) delete mode 100644 kani-compiler/src/kani_middle/transform/check_uninit.rs create mode 100644 kani-compiler/src/kani_middle/transform/check_uninit/mod.rs create mode 100644 kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs create mode 100644 kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs create mode 100644 tests/uninit/after-aggregate-assign-init.rs rename tests/uninit/{after-aggregate-assign.rs => after-aggregate-assign-uninit.rs} (80%) delete mode 100644 tests/uninit/alloc-diagnostic-with-provenance.rs create mode 100644 tests/uninit/byte-read-init.rs create mode 100644 tests/uninit/byte-read-semi-init.rs rename tests/uninit/{byte-read.rs => byte-read-uninit.rs} (53%) diff --git a/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs b/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs index 89be6096794d..6994d0f97cd3 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs @@ -262,6 +262,7 @@ impl GotocHook for PointerObject { let ptr = { let ptr = fargs.pop().unwrap(); let place_ty = instance.args().0[0].expect_ty().clone(); + // Handle both fat and thin pointers. if gcx.use_thin_pointer_stable(place_ty) { ptr } else { @@ -305,6 +306,7 @@ impl GotocHook for PointerOffset { let ptr = { let ptr = fargs.pop().unwrap(); let place_ty = instance.args().0[0].expect_ty().clone(); + // Handle both fat and thin pointers. if gcx.use_thin_pointer_stable(place_ty) { ptr } else { diff --git a/kani-compiler/src/kani_middle/transform/body.rs b/kani-compiler/src/kani_middle/transform/body.rs index 9c1ad96b9467..79011c5954e7 100644 --- a/kani-compiler/src/kani_middle/transform/body.rs +++ b/kani-compiler/src/kani_middle/transform/body.rs @@ -107,6 +107,20 @@ impl MutableBody { self.new_assignment(rvalue, before) } + /// Transmute to a raw pointer of `*mut type` and return a new local where that value is stored. + pub fn new_cast_transmute( + &mut self, + from: Operand, + pointee_ty: Ty, + mutability: Mutability, + before: &mut SourceInstruction, + ) -> Local { + assert!(from.ty(self.locals()).unwrap().kind().is_raw_ptr()); + let target_ty = Ty::new_ptr(pointee_ty, mutability); + let rvalue = Rvalue::Cast(CastKind::Transmute, from, target_ty); + self.new_assignment(rvalue, before) + } + /// Add a new assignment for the given binary operation. /// /// Return the local where the result is saved. diff --git a/kani-compiler/src/kani_middle/transform/check_uninit.rs b/kani-compiler/src/kani_middle/transform/check_uninit.rs deleted file mode 100644 index bb29b60c16d7..000000000000 --- a/kani-compiler/src/kani_middle/transform/check_uninit.rs +++ /dev/null @@ -1,818 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// -//! Implement a transformation pass that instruments the code to detect possible UB due to -//! the accesses to uninitialized memory. - -use crate::args::ExtraChecks; -use crate::kani_middle::find_fn_def; -use crate::kani_middle::transform::body::{CheckType, MutableBody, SourceInstruction}; -use crate::kani_middle::transform::{TransformPass, TransformationType}; -use crate::kani_queries::QueryDb; -use rustc_middle::ty::TyCtxt; -use stable_mir::abi::{FieldsShape, Scalar, TagEncoding, ValueAbi, VariantsShape}; -use stable_mir::mir::mono::{Instance, InstanceKind}; -use stable_mir::mir::visit::{Location, PlaceContext}; -use stable_mir::mir::{ - AggregateKind, BasicBlockIdx, Body, Constant, LocalDecl, MirVisitor, Mutability, - NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, Statement, StatementKind, - Terminator, TerminatorKind, -}; -use stable_mir::target::{MachineInfo, MachineSize}; -use stable_mir::ty::{ - AdtKind, Const, GenericArgKind, GenericArgs, IndexedVal, RigidTy, Ty, TyKind, UintTy, -}; -use stable_mir::CrateDef; -use std::fmt::Debug; -use strum_macros::AsRefStr; -use tracing::{debug, trace}; - -const UNINIT_ALLOWLIST: &[&str] = - &["kani::shadow::global_sm_get", "kani::shadow::global_sm_set", "std::alloc::alloc"]; - -/// Instrument the code with checks for uninitialized memory. -#[derive(Debug)] -pub struct UninitPass { - pub check_type: CheckType, -} - -impl TransformPass for UninitPass { - fn transformation_type() -> TransformationType - where - Self: Sized, - { - TransformationType::Instrumentation - } - - fn is_enabled(&self, query_db: &QueryDb) -> bool - where - Self: Sized, - { - let args = query_db.args(); - args.ub_check.contains(&ExtraChecks::Uninit) - } - - fn transform(&self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { - trace!(function=?instance.name(), "transform"); - - if UNINIT_ALLOWLIST.iter().any(|allowlist_item| instance.name().contains(allowlist_item)) { - return (false, body); - } - - let mut new_body = MutableBody::from(body); - let orig_len = new_body.blocks().len(); - // Do not cache body.blocks().len() since it will change as we add new checks. - let mut bb_idx = 0; - while bb_idx < new_body.blocks().len() { - if let Some(candidate) = - CheckUninitVisitor::find_next(&new_body, bb_idx, bb_idx >= orig_len) - { - self.build_check(tcx, &mut new_body, candidate); - bb_idx += 1 - } else { - bb_idx += 1; - }; - } - (orig_len != new_body.blocks().len(), new_body.into()) - } -} - -impl UninitPass { - fn build_check( - &self, - tcx: TyCtxt, - body: &mut MutableBody, - instruction: InitRelevantInstruction, - ) { - debug!(?instruction, "build_check"); - let mut source = instruction.source; - for operation in instruction.operations { - let place = match &operation { - SourceOp::Get { place, .. } - | SourceOp::Set { place, .. } - | SourceOp::GetN { place, .. } - | SourceOp::SetN { place, .. } => place, - SourceOp::Unsupported { instruction, place } => { - let place_ty = place.ty(body.locals()).unwrap(); - let reason = format!( - "Kani currently doesn't support checking memory initialization using instruction `{instruction}` for type `{place_ty}`", - ); - self.unsupported_check(tcx, body, &mut source, &reason); - continue; - } - }; - - let place_ty = place.ty(body.locals()).unwrap(); - let pointee_ty = match place_ty.kind() { - TyKind::RigidTy(RigidTy::RawPtr(pointee_ty, _)) => pointee_ty, - _ => unreachable!(), - }; - - let layout_mask = layout_mask(pointee_ty).unwrap(); - - // if layout_mask.iter().all(|byte| *byte) { - // match operation { - // SourceOp::Get { .. } => { - // let sm_get = Instance::resolve( - // find_fn_def(tcx, "KaniShadowMemoryGet").unwrap(), - // &GenericArgs(vec![GenericArgKind::Type(pointee_ty)]), - // ) - // .unwrap(); - - // let ret_place = Place { - // local: body.new_local( - // Ty::bool_ty(), - // source.span(body.blocks()), - // Mutability::Not, - // ), - // projection: vec![], - // }; - - // body.add_call( - // &sm_get, - // &mut source, - // vec![Operand::Copy(place.clone())], - // ret_place.clone(), - // ); - // body.add_check( - // tcx, - // &self.check_type, - // &mut source, - // ret_place.local, - // &format!("Undefined Behavior: Reading from an uninitialized pointer of type `{place_ty}`"), - // ) - // } - // SourceOp::Set { value, .. } => { - // let sm_set = Instance::resolve( - // find_fn_def(tcx, "KaniShadowMemorySet").unwrap(), - // &GenericArgs(vec![GenericArgKind::Type(pointee_ty)]), - // ) - // .unwrap(); - // let ret_place = Place { - // local: body.new_local( - // Ty::new_tuple(&[]), - // source.span(body.blocks()), - // Mutability::Not, - // ), - // projection: vec![], - // }; - // let span = source.span(body.blocks()); - // body.add_call( - // &sm_set, - // &mut source, - // vec![ - // Operand::Copy(place.clone()), - // Operand::Constant(Constant { - // span, - // user_ty: None, - // literal: Const::from_bool(value), - // }), - // ], - // ret_place, - // ); - // } - // SourceOp::Unsupported { .. } => { - // unreachable!() - // } - // } - // } else { - let span = source.span(body.blocks()); - let layout_local = body.new_assignment( - Rvalue::Aggregate( - AggregateKind::Array(Ty::bool_ty()), - layout_mask - .iter() - .map(|byte| { - Operand::Constant(Constant { - span, - user_ty: None, - literal: Const::from_bool(*byte), - }) - }) - .collect(), - ), - &mut source, - ); - let ptr_local = body.new_cast_ptr( - Operand::Copy(place.clone()), - Ty::new_tuple(&[]), - Mutability::Not, - &mut source, - ); - - match operation { - SourceOp::Get { .. } => { - let sm_get = Instance::resolve( - find_fn_def(tcx, "KaniShadowMemoryGetWithLayout").unwrap(), - &GenericArgs(vec![GenericArgKind::Const( - Const::try_from_uint(layout_mask.len() as u128, UintTy::Usize).unwrap(), - )]), - ) - .unwrap(); - - let ret_place = Place { - local: body.new_local( - Ty::bool_ty(), - source.span(body.blocks()), - Mutability::Not, - ), - projection: vec![], - }; - - body.add_call( - &sm_get, - &mut source, - vec![ - Operand::Copy(Place { local: ptr_local, projection: vec![] }), - Operand::Move(Place { local: layout_local, projection: vec![] }), - ], - ret_place.clone(), - ); - body.add_check( - tcx, - &self.check_type, - &mut source, - ret_place.local, - &format!("Undefined Behavior: Reading from an uninitialized pointer of type `{place_ty}`"), - ) - } - SourceOp::GetN { count, .. } => { - let sm_get = Instance::resolve( - find_fn_def(tcx, "KaniShadowMemoryGetWithLayoutDynamic").unwrap(), - &GenericArgs(vec![GenericArgKind::Const( - Const::try_from_uint(layout_mask.len() as u128, UintTy::Usize).unwrap(), - )]), - ) - .unwrap(); - - let ret_place = Place { - local: body.new_local( - Ty::bool_ty(), - source.span(body.blocks()), - Mutability::Not, - ), - projection: vec![], - }; - - body.add_call( - &sm_get, - &mut source, - vec![ - Operand::Copy(Place { local: ptr_local, projection: vec![] }), - Operand::Move(Place { local: layout_local, projection: vec![] }), - count, - ], - ret_place.clone(), - ); - body.add_check( - tcx, - &self.check_type, - &mut source, - ret_place.local, - &format!("Undefined Behavior: Reading from an uninitialized pointer of type `{place_ty}`"), - ) - } - SourceOp::Set { value, .. } => { - let sm_set = Instance::resolve( - find_fn_def(tcx, "KaniShadowMemorySetWithLayout").unwrap(), - &GenericArgs(vec![GenericArgKind::Const( - Const::try_from_uint(layout_mask.len() as u128, UintTy::Usize).unwrap(), - )]), - ) - .unwrap(); - let ret_place = Place { - local: body.new_local( - Ty::new_tuple(&[]), - source.span(body.blocks()), - Mutability::Not, - ), - projection: vec![], - }; - body.add_call( - &sm_set, - &mut source, - vec![ - Operand::Copy(Place { local: ptr_local, projection: vec![] }), - Operand::Move(Place { local: layout_local, projection: vec![] }), - Operand::Constant(Constant { - span, - user_ty: None, - literal: Const::from_bool(value), - }), - ], - ret_place, - ); - } - SourceOp::SetN { count, value, .. } => { - let sm_set = Instance::resolve( - find_fn_def(tcx, "KaniShadowMemorySetWithLayoutDynamic").unwrap(), - &GenericArgs(vec![GenericArgKind::Const( - Const::try_from_uint(layout_mask.len() as u128, UintTy::Usize).unwrap(), - )]), - ) - .unwrap(); - let ret_place = Place { - local: body.new_local( - Ty::new_tuple(&[]), - source.span(body.blocks()), - Mutability::Not, - ), - projection: vec![], - }; - body.add_call( - &sm_set, - &mut source, - vec![ - Operand::Copy(Place { local: ptr_local, projection: vec![] }), - Operand::Move(Place { local: layout_local, projection: vec![] }), - count, - Operand::Constant(Constant { - span, - user_ty: None, - literal: Const::from_bool(value), - }), - ], - ret_place, - ); - } - SourceOp::Unsupported { .. } => { - unreachable!() - } - } - // } - } - } - - fn unsupported_check( - &self, - tcx: TyCtxt, - body: &mut MutableBody, - source: &mut SourceInstruction, - reason: &str, - ) { - let span = source.span(body.blocks()); - let rvalue = Rvalue::Use(Operand::Constant(Constant { - literal: Const::from_bool(false), - span, - user_ty: None, - })); - let result = body.new_assignment(rvalue, source); - body.add_check(tcx, &self.check_type, source, result, reason); - } -} - -#[derive(AsRefStr, Clone, Debug)] -enum SourceOp { - Get { place: Place }, - GetN { place: Place, count: Operand }, - Set { place: Place, value: bool }, - SetN { place: Place, count: Operand, value: bool }, - Unsupported { instruction: String, place: Place }, -} - -#[derive(Clone, Debug)] -struct InitRelevantInstruction { - /// The instruction that affects the state of the memory. - source: SourceInstruction, - /// All memory-related operations in this instructions. - operations: Vec, -} - -struct CheckUninitVisitor<'a> { - locals: &'a [LocalDecl], - /// Whether we should skip the next instruction, since it might've been instrumented already. - /// When we instrument an instruction, we partition the basic block, and the instruction that - /// may trigger UB becomes the first instruction of the basic block, which we need to skip - /// later. - skip_next: bool, - /// The instruction being visited at a given point. - current: SourceInstruction, - /// The target instruction that should be verified. - pub target: Option, - /// The basic block being visited. - bb: BasicBlockIdx, -} - -fn expect_place(op: &Operand) -> &Place { - match op { - Operand::Copy(place) | Operand::Move(place) => place, - Operand::Constant(_) => unreachable!(), - } -} - -fn try_remove_topmost_deref(place: &Place) -> Option { - let mut new_place = place.clone(); - if let Some(ProjectionElem::Deref) = new_place.projection.pop() { - Some(new_place) - } else { - None - } -} - -/// Retrieve instance for the given function operand. -/// -/// This will panic if the operand is not a function or if it cannot be resolved. -fn expect_instance(locals: &[LocalDecl], func: &Operand) -> Instance { - let ty = func.ty(locals).unwrap(); - match ty.kind() { - TyKind::RigidTy(RigidTy::FnDef(def, args)) => Instance::resolve(def, &args).unwrap(), - _ => unreachable!(), - } -} - -#[derive(Clone, Debug, Eq, PartialEq, Hash)] -pub struct DataBytes { - /// Offset in bytes. - offset: usize, - /// Size of this requirement. - size: MachineSize, -} - -fn scalar_ty_size(machine_info: &MachineInfo, ty: Ty) -> Option { - let shape = ty.layout().unwrap().shape(); - match shape.abi { - ValueAbi::Scalar(Scalar::Initialized { value, .. }) - | ValueAbi::ScalarPair(Scalar::Initialized { value, .. }, _) => { - Some(DataBytes { offset: 0, size: value.size(machine_info) }) - } - ValueAbi::Scalar(_) - | ValueAbi::ScalarPair(_, _) - | ValueAbi::Uninhabited - | ValueAbi::Vector { .. } - | ValueAbi::Aggregate { .. } => None, - } -} - -fn ty_layout( - machine_info: &MachineInfo, - ty: Ty, - current_offset: usize, -) -> Result, String> { - let layout = ty.layout().unwrap().shape(); - let ty_size = || { - if let Some(mut size) = scalar_ty_size(machine_info, ty) { - size.offset = current_offset; - vec![size] - } else { - vec![] - } - }; - match layout.fields { - FieldsShape::Primitive => Ok(ty_size()), - FieldsShape::Array { stride, count } if count > 0 => { - let TyKind::RigidTy(RigidTy::Array(elem_ty, _)) = ty.kind() else { unreachable!() }; - let elem_validity = ty_layout(machine_info, elem_ty, current_offset)?; - let mut result = vec![]; - if !elem_validity.is_empty() { - for idx in 0..count { - let idx: usize = idx.try_into().unwrap(); - let elem_offset = idx * stride.bytes(); - let mut next_validity = elem_validity - .iter() - .cloned() - .map(|mut req| { - req.offset += elem_offset; - req - }) - .collect::>(); - result.append(&mut next_validity) - } - } - Ok(result) - } - FieldsShape::Arbitrary { ref offsets } => { - match ty.kind().rigid().expect(&format!("unexpected type: {ty:?}")) { - RigidTy::Adt(def, args) => { - match def.kind() { - AdtKind::Enum => { - // Support basic enumeration forms - let ty_variants = def.variants(); - match layout.variants { - VariantsShape::Single { index } => { - // Only one variant is reachable. This behaves like a struct. - let fields = ty_variants[index.to_index()].fields(); - let mut fields_validity = vec![]; - for idx in layout.fields.fields_by_offset_order() { - let field_offset = offsets[idx].bytes(); - let field_ty = fields[idx].ty_with_args(&args); - fields_validity.append(&mut ty_layout( - machine_info, - field_ty, - field_offset + current_offset, - )?); - } - Ok(fields_validity) - } - VariantsShape::Multiple { - tag_encoding: TagEncoding::Niche { .. }, - .. - } => { - Err(format!("Unsupported Enum `{}` check", def.trimmed_name()))? - } - VariantsShape::Multiple { variants, .. } => { - let enum_validity = ty_size(); - let mut fields_validity = vec![]; - for (index, variant) in variants.iter().enumerate() { - let fields = ty_variants[index].fields(); - for field_idx in variant.fields.fields_by_offset_order() { - let field_offset = offsets[field_idx].bytes(); - let field_ty = fields[field_idx].ty_with_args(&args); - fields_validity.append(&mut ty_layout( - machine_info, - field_ty, - field_offset + current_offset, - )?); - } - } - if fields_validity.is_empty() { - Ok(enum_validity) - } else { - Err(format!( - "Unsupported Enum `{}` check", - def.trimmed_name() - )) - } - } - } - } - AdtKind::Union => unreachable!(), - AdtKind::Struct => { - // If the struct range has niche add that. - let mut struct_validity = ty_size(); - let fields = def.variants_iter().next().unwrap().fields(); - for idx in layout.fields.fields_by_offset_order() { - let field_offset = offsets[idx].bytes(); - let field_ty = fields[idx].ty_with_args(&args); - struct_validity.append(&mut ty_layout( - machine_info, - field_ty, - field_offset + current_offset, - )?); - } - Ok(struct_validity) - } - } - } - RigidTy::Pat(base_ty, ..) => { - // This is similar to a structure with one field and with niche defined. - let mut pat_validity = ty_size(); - pat_validity.append(&mut ty_layout(machine_info, *base_ty, 0)?); - Ok(pat_validity) - } - RigidTy::Tuple(tys) => { - let mut tuple_validity = vec![]; - for idx in layout.fields.fields_by_offset_order() { - let field_offset = offsets[idx].bytes(); - let field_ty = tys[idx]; - tuple_validity.append(&mut ty_layout( - machine_info, - field_ty, - field_offset + current_offset, - )?); - } - Ok(tuple_validity) - } - RigidTy::Bool - | RigidTy::Char - | RigidTy::Int(_) - | RigidTy::Uint(_) - | RigidTy::Float(_) - | RigidTy::Never => { - unreachable!("Expected primitive layout for {ty:?}") - } - RigidTy::Str | RigidTy::Slice(_) | RigidTy::Array(_, _) => { - unreachable!("Expected array layout for {ty:?}") - } - RigidTy::RawPtr(_, _) | RigidTy::Ref(_, _, _) => { - // Fat pointer has arbitrary shape. - Ok(ty_size()) - } - RigidTy::FnDef(_, _) - | RigidTy::FnPtr(_) - | RigidTy::Closure(_, _) - | RigidTy::Coroutine(_, _, _) - | RigidTy::CoroutineWitness(_, _) - | RigidTy::Foreign(_) - | RigidTy::Dynamic(_, _, _) => Err(format!("Unsupported {ty:?}")), - } - } - FieldsShape::Union(_) | FieldsShape::Array { .. } => { - /* Anything is valid */ - Ok(vec![]) - } - } -} - -fn layout_mask(ty: Ty) -> Result, String> { - let ty_layout = ty_layout(&MachineInfo::target(), ty, 0)?; - let ty_size = ty.layout().unwrap().shape().size.bytes(); - let mut layout_mask = vec![false; ty_size]; - for data_bytes in ty_layout.iter() { - for i in data_bytes.offset..data_bytes.offset + data_bytes.size.bytes() { - layout_mask[i] = true; - } - } - Ok(layout_mask) -} - -impl<'a> CheckUninitVisitor<'a> { - fn find_next( - body: &'a MutableBody, - bb: BasicBlockIdx, - skip_first: bool, - ) -> Option { - let mut visitor = CheckUninitVisitor { - locals: body.locals(), - skip_next: skip_first, - current: SourceInstruction::Statement { idx: 0, bb }, - target: None, - bb, - }; - visitor.visit_basic_block(&body.blocks()[bb]); - visitor.target - } - - fn push_target(&mut self, op: SourceOp) { - let target = self.target.get_or_insert_with(|| InitRelevantInstruction { - source: self.current, - operations: vec![], - }); - target.operations.push(op); - } -} - -impl<'a> MirVisitor for CheckUninitVisitor<'a> { - fn visit_statement(&mut self, stmt: &Statement, location: Location) { - if self.skip_next { - self.skip_next = false; - } else if self.target.is_none() { - // Leave it as an exhaustive match to be notified when a new kind is added. - match &stmt.kind { - StatementKind::Intrinsic(NonDivergingIntrinsic::CopyNonOverlapping(copy)) => { - // Source is a *const T and it must be initialized. - self.push_target(SourceOp::GetN { - place: expect_place(©.src).clone(), - count: copy.count.clone(), - }); - // Destimation is a *mut T so it gets initialized. - self.push_target(SourceOp::SetN { - place: expect_place(©.dst).clone(), - count: copy.count.clone(), - value: true, - }); - self.super_statement(stmt, location) - } - StatementKind::Assign(place, rvalue) => { - // First check rvalue. - self.visit_rvalue(rvalue, location); - // Then check the destination place. - if let Some(place_without_deref) = try_remove_topmost_deref(place) { - if place_without_deref.ty(&self.locals).unwrap().kind().is_raw_ptr() { - self.push_target(SourceOp::Set { - place: place_without_deref, - value: true, - }); - } - } - } - StatementKind::Deinit(place) => { - self.push_target(SourceOp::Set { place: place.clone(), value: false }); - } - StatementKind::FakeRead(_, _) - | StatementKind::SetDiscriminant { .. } - | StatementKind::StorageLive(_) - | StatementKind::StorageDead(_) - | StatementKind::Retag(_, _) - | StatementKind::PlaceMention(_) - | StatementKind::AscribeUserType { .. } - | StatementKind::Coverage(_) - | StatementKind::ConstEvalCounter - | StatementKind::Intrinsic(NonDivergingIntrinsic::Assume(_)) - | StatementKind::Nop => self.super_statement(stmt, location), - } - } - let SourceInstruction::Statement { idx, bb } = self.current else { unreachable!() }; - self.current = SourceInstruction::Statement { idx: idx + 1, bb }; - } - fn visit_terminator(&mut self, term: &Terminator, location: Location) { - if !(self.skip_next || self.target.is_some()) { - self.current = SourceInstruction::Terminator { bb: self.bb }; - // Leave it as an exhaustive match to be notified when a new kind is added. - match &term.kind { - TerminatorKind::Call { func, args, .. } => { - self.super_terminator(term, location); - let instance = expect_instance(self.locals, func); - if instance.kind == InstanceKind::Intrinsic { - match instance.intrinsic_name().unwrap().as_str() { - "write_bytes" => { - assert_eq!( - args.len(), - 3, - "Unexpected number of arguments for `write_bytes`" - ); - assert!(matches!( - args[0].ty(self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) - )); - self.push_target(SourceOp::SetN { - place: expect_place(&args[0]).clone(), - count: args[2].clone(), - value: true, - }) - } - "compare_bytes" => { - assert_eq!( - args.len(), - 3, - "Unexpected number of arguments for `compare_bytes`" - ); - assert!(matches!( - args[0].ty(self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) - )); - assert!(matches!( - args[1].ty(self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) - )); - self.push_target(SourceOp::GetN { - place: expect_place(&args[0]).clone(), - count: args[2].clone(), - }); - self.push_target(SourceOp::GetN { - place: expect_place(&args[1]).clone(), - count: args[2].clone(), - }); - } - "raw_eq" => { - assert_eq!( - args.len(), - 3, - "Unexpected number of arguments for `raw_eq`" - ); - assert!(matches!( - args[0].ty(self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::Ref(_, _, Mutability::Not)) - )); - assert!(matches!( - args[1].ty(self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::Ref(_, _, Mutability::Not)) - )); - self.push_target(SourceOp::Get { - place: expect_place(&args[0]).clone(), - }); - self.push_target(SourceOp::Get { - place: expect_place(&args[1]).clone(), - }); - } - "transmute" | "transmute_copy" => { - unreachable!("Should've been lowered") - } - _ => {} - } - } - } - TerminatorKind::Goto { .. } - | TerminatorKind::SwitchInt { .. } - | TerminatorKind::Resume - | TerminatorKind::Abort - | TerminatorKind::Return - | TerminatorKind::Unreachable - | TerminatorKind::Drop { .. } - | TerminatorKind::Assert { .. } - | TerminatorKind::InlineAsm { .. } => self.super_terminator(term, location), - } - } - } - - fn visit_place(&mut self, place: &Place, ptx: PlaceContext, location: Location) { - for (idx, elem) in place.projection.iter().enumerate() { - let intermediate_place = - Place { local: place.local, projection: place.projection[..idx].to_vec() }; - match elem { - ProjectionElem::Deref => { - let ptr_ty = intermediate_place.ty(self.locals).unwrap(); - if ptr_ty.kind().is_raw_ptr() { - self.push_target(SourceOp::Get { place: intermediate_place.clone() }); - } - } - ProjectionElem::Field(idx, target_ty) => { - if target_ty.kind().is_union() - && (!ptx.is_mutating() || place.projection.len() > idx + 1) - { - self.push_target(SourceOp::Unsupported { - instruction: "union access".to_string(), - place: intermediate_place.clone(), - }); - } - } - ProjectionElem::Downcast(_) => {} - ProjectionElem::OpaqueCast(_) => {} - ProjectionElem::Subtype(_) => {} - ProjectionElem::Index(_) - | ProjectionElem::ConstantIndex { .. } - | ProjectionElem::Subslice { .. } => {} - } - } - self.super_place(place, ptx, location) - } -} diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs new file mode 100644 index 000000000000..e5498a1c2133 --- /dev/null +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -0,0 +1,279 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +//! Implement a transformation pass that instruments the code to detect possible UB due to +//! the accesses to uninitialized memory. + +use crate::args::ExtraChecks; +use crate::kani_middle::find_fn_def; +use crate::kani_middle::transform::body::{CheckType, MutableBody, SourceInstruction}; +use crate::kani_middle::transform::{TransformPass, TransformationType}; +use crate::kani_queries::QueryDb; +use rustc_middle::ty::TyCtxt; +use stable_mir::mir::mono::Instance; +use stable_mir::mir::{AggregateKind, Body, Constant, Mutability, Operand, Place, Rvalue}; +use stable_mir::ty::{Const, GenericArgKind, GenericArgs, RigidTy, Ty, TyKind, UintTy}; +use std::fmt::Debug; +use tracing::{debug, trace}; + +mod ty_layout; +mod uninit_visitor; + +use ty_layout::TypeLayout; +use uninit_visitor::{CheckUninitVisitor, InitRelevantInstruction, SourceOp}; + +const UNINIT_ALLOWLIST: &[&str] = &["kani::shadow", "std::alloc::alloc", "std::ptr::drop_in_place"]; + +/// Instrument the code with checks for uninitialized memory. +#[derive(Debug)] +pub struct UninitPass { + pub check_type: CheckType, +} + +impl TransformPass for UninitPass { + fn transformation_type() -> TransformationType + where + Self: Sized, + { + TransformationType::Instrumentation + } + + fn is_enabled(&self, query_db: &QueryDb) -> bool + where + Self: Sized, + { + let args = query_db.args(); + args.ub_check.contains(&ExtraChecks::Uninit) + } + + fn transform(&self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { + trace!(function=?instance.name(), "transform"); + + if UNINIT_ALLOWLIST.iter().any(|allowlist_item| instance.name().contains(allowlist_item)) { + return (false, body); + } + + let mut new_body = MutableBody::from(body); + let orig_len = new_body.blocks().len(); + // Do not cache body.blocks().len() since it will change as we add new checks. + let mut bb_idx = 0; + while bb_idx < new_body.blocks().len() { + if let Some(candidate) = + CheckUninitVisitor::find_next(&new_body, bb_idx, bb_idx >= orig_len) + { + self.build_check(tcx, &mut new_body, candidate); + bb_idx += 1 + } else { + bb_idx += 1; + }; + } + (orig_len != new_body.blocks().len(), new_body.into()) + } +} + +impl UninitPass { + fn build_check( + &self, + tcx: TyCtxt, + body: &mut MutableBody, + instruction: InitRelevantInstruction, + ) { + debug!(?instruction, "build_check"); + let mut source = instruction.source; + for operation in instruction.operations { + let place = match &operation { + SourceOp::Get { place, .. } | SourceOp::Set { place, .. } => place, + SourceOp::Unsupported { instruction, place } => { + let place_ty = place.ty(body.locals()).unwrap(); + let reason = format!( + "Kani currently doesn't support checking memory initialization using instruction `{instruction}` for type `{place_ty}`", + ); + self.unsupported_check(tcx, body, &mut source, &reason); + continue; + } + }; + + let place_ty = place.ty(body.locals()).unwrap(); + let pointee_ty = match place_ty.kind() { + TyKind::RigidTy(RigidTy::RawPtr(pointee_ty, _)) => pointee_ty, + _ => { + unreachable!( + "Should only build checks for raw pointers, `{place_ty}` encountered" + ) + } + }; + + let type_layout = match TypeLayout::get_mask(pointee_ty) { + Ok(type_layout) => type_layout, + Err(err) => { + let place_ty = place.ty(body.locals()).unwrap(); + let reason = format!( + "Kani currently doesn't support checking memory initialization using instruction for type `{place_ty}` due to the following: `{err}`", + ); + self.unsupported_check(tcx, body, &mut source, &reason); + continue; + } + }; + + let count = match &type_layout { + TypeLayout::StaticallySized { .. } => match &operation { + SourceOp::Get { count, .. } | SourceOp::Set { count, .. } => count.clone(), + SourceOp::Unsupported { .. } => unreachable!(), + }, + TypeLayout::DynamicallySized { .. } => { + let slice_local = body.new_cast_transmute( + Operand::Copy(place.clone()), + Ty::from_rigid_kind(RigidTy::Slice(Ty::new_tuple(&[]))), + Mutability::Not, + &mut source, + ); + + let get_slice_size = Instance::resolve( + find_fn_def(tcx, "KaniGetSliceSizeHelper").unwrap(), + &GenericArgs(vec![]), + ) + .unwrap(); + + let ret_place = Place { + local: body.new_local( + Ty::usize_ty(), + source.span(body.blocks()), + Mutability::Not, + ), + projection: vec![], + }; + body.add_call( + &get_slice_size, + &mut source, + vec![Operand::Copy(Place { local: slice_local, projection: vec![] })], + ret_place.clone(), + ); + Operand::Copy(ret_place) + } + }; + + let span = source.span(body.blocks()); + let layout_local = body.new_assignment( + Rvalue::Aggregate( + AggregateKind::Array(Ty::bool_ty()), + type_layout + .as_byte_layout() + .iter() + .map(|byte| { + Operand::Constant(Constant { + span, + user_ty: None, + literal: Const::from_bool(*byte), + }) + }) + .collect(), + ), + &mut source, + ); + + let ptr_local = body.new_cast_ptr( + Operand::Copy(place.clone()), + Ty::new_tuple(&[]), + Mutability::Not, + &mut source, + ); + + match operation { + SourceOp::Get { .. } => { + let sm_get = Instance::resolve( + find_fn_def(tcx, "KaniShadowMemoryGet").unwrap(), + &GenericArgs(vec![GenericArgKind::Const( + Const::try_from_uint( + type_layout.as_byte_layout().len() as u128, + UintTy::Usize, + ) + .unwrap(), + )]), + ) + .unwrap(); + let ret_place = Place { + local: body.new_local( + Ty::bool_ty(), + source.span(body.blocks()), + Mutability::Not, + ), + projection: vec![], + }; + body.add_call( + &sm_get, + &mut source, + vec![ + Operand::Copy(Place { local: ptr_local, projection: vec![] }), + Operand::Move(Place { local: layout_local, projection: vec![] }), + count, + ], + ret_place.clone(), + ); + body.add_check( + tcx, + &self.check_type, + &mut source, + ret_place.local, + &format!("Undefined Behavior: Reading from an uninitialized pointer of type `{place_ty}`"), + ) + } + SourceOp::Set { value, .. } => { + let sm_set = Instance::resolve( + find_fn_def(tcx, "KaniShadowMemorySet").unwrap(), + &GenericArgs(vec![GenericArgKind::Const( + Const::try_from_uint( + type_layout.as_byte_layout().len() as u128, + UintTy::Usize, + ) + .unwrap(), + )]), + ) + .unwrap(); + let ret_place = Place { + local: body.new_local( + Ty::new_tuple(&[]), + source.span(body.blocks()), + Mutability::Not, + ), + projection: vec![], + }; + body.add_call( + &sm_set, + &mut source, + vec![ + Operand::Copy(Place { local: ptr_local, projection: vec![] }), + Operand::Move(Place { local: layout_local, projection: vec![] }), + count, + Operand::Constant(Constant { + span, + user_ty: None, + literal: Const::from_bool(value), + }), + ], + ret_place, + ); + } + SourceOp::Unsupported { .. } => { + unreachable!() + } + } + } + } + + fn unsupported_check( + &self, + tcx: TyCtxt, + body: &mut MutableBody, + source: &mut SourceInstruction, + reason: &str, + ) { + let span = source.span(body.blocks()); + let rvalue = Rvalue::Use(Operand::Constant(Constant { + literal: Const::from_bool(false), + span, + user_ty: None, + })); + let result = body.new_assignment(rvalue, source); + body.add_check(tcx, &self.check_type, source, result, reason); + } +} diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs new file mode 100644 index 000000000000..282bc193aaa6 --- /dev/null +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs @@ -0,0 +1,234 @@ +use stable_mir::abi::{FieldsShape, Scalar, TagEncoding, ValueAbi, VariantsShape}; +use stable_mir::target::{MachineInfo, MachineSize}; +use stable_mir::ty::{AdtKind, IndexedVal, RigidTy, Ty, TyKind}; +use stable_mir::CrateDef; + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +struct DataBytes { + /// Offset in bytes. + offset: usize, + /// Size of this requirement. + size: MachineSize, +} + +pub type ByteLayout = Vec; + +pub enum TypeLayout { + StaticallySized { layout: ByteLayout }, + DynamicallySized { element_layout: ByteLayout }, +} + +impl TypeLayout { + pub fn get_mask(ty: Ty) -> Result { + if ty.layout().unwrap().shape().is_sized() { + let ty_layout = data_bytes_for_ty(&MachineInfo::target(), ty, 0)?; + let ty_size = ty.layout().unwrap().shape().size.bytes(); + let mut layout_mask = vec![false; ty_size]; + for data_bytes in ty_layout.iter() { + for i in data_bytes.offset..data_bytes.offset + data_bytes.size.bytes() { + layout_mask[i] = true; + } + } + Ok(Self::StaticallySized { layout: layout_mask }) + } else { + let layout_mask = { + let data_bytes = DataBytes { + offset: 0, + size: match ty.layout().unwrap().shape().fields { + FieldsShape::Array { stride, count } if count == 0 => stride, + _ => { + return Err(format!("Unsupported DST for invalid memory check: `{ty}`")); + } + }, + }; + let ty_size = data_bytes.size.bytes(); + let mut layout_mask = vec![false; ty_size]; + for i in data_bytes.offset..data_bytes.offset + data_bytes.size.bytes() { + layout_mask[i] = true; + } + layout_mask + }; + + Ok(Self::DynamicallySized { element_layout: layout_mask }) + } + } + + pub fn as_byte_layout(&self) -> &ByteLayout { + match self { + TypeLayout::StaticallySized { layout } => layout, + TypeLayout::DynamicallySized { element_layout } => element_layout, + } + } +} + +fn scalar_ty_size(machine_info: &MachineInfo, ty: Ty) -> Option { + let shape = ty.layout().unwrap().shape(); + match shape.abi { + ValueAbi::Scalar(Scalar::Initialized { value, .. }) + | ValueAbi::ScalarPair(Scalar::Initialized { value, .. }, _) => { + Some(DataBytes { offset: 0, size: value.size(machine_info) }) + } + ValueAbi::Scalar(_) + | ValueAbi::ScalarPair(_, _) + | ValueAbi::Uninhabited + | ValueAbi::Vector { .. } + | ValueAbi::Aggregate { .. } => None, + } +} + +fn data_bytes_for_ty( + machine_info: &MachineInfo, + ty: Ty, + current_offset: usize, +) -> Result, String> { + let layout = ty.layout().unwrap().shape(); + let ty_size = || { + if let Some(mut size) = scalar_ty_size(machine_info, ty) { + size.offset = current_offset; + vec![size] + } else { + vec![] + } + }; + match layout.fields { + FieldsShape::Primitive => Ok(ty_size()), + FieldsShape::Array { stride, count } if count > 0 => { + let TyKind::RigidTy(RigidTy::Array(elem_ty, _)) = ty.kind() else { unreachable!() }; + let elem_validity = data_bytes_for_ty(machine_info, elem_ty, current_offset)?; + let mut result = vec![]; + if !elem_validity.is_empty() { + for idx in 0..count { + let idx: usize = idx.try_into().unwrap(); + let elem_offset = idx * stride.bytes(); + let mut next_validity = elem_validity + .iter() + .cloned() + .map(|mut req| { + req.offset += elem_offset; + req + }) + .collect::>(); + result.append(&mut next_validity) + } + } + Ok(result) + } + FieldsShape::Arbitrary { ref offsets } => { + match ty.kind().rigid().expect(&format!("unexpected type: {ty:?}")) { + RigidTy::Adt(def, args) => { + match def.kind() { + AdtKind::Enum => { + // Support basic enumeration forms + let ty_variants = def.variants(); + match layout.variants { + VariantsShape::Single { index } => { + // Only one variant is reachable. This behaves like a struct. + let fields = ty_variants[index.to_index()].fields(); + let mut fields_validity = vec![]; + for idx in layout.fields.fields_by_offset_order() { + let field_offset = offsets[idx].bytes(); + let field_ty = fields[idx].ty_with_args(&args); + fields_validity.append(&mut data_bytes_for_ty( + machine_info, + field_ty, + field_offset + current_offset, + )?); + } + Ok(fields_validity) + } + VariantsShape::Multiple { + tag_encoding: TagEncoding::Niche { .. }, + .. + } => { + Err(format!("Unsupported Enum `{}` check", def.trimmed_name()))? + } + VariantsShape::Multiple { variants, .. } => { + let enum_validity = ty_size(); + let mut fields_validity = vec![]; + for (index, variant) in variants.iter().enumerate() { + let fields = ty_variants[index].fields(); + for field_idx in variant.fields.fields_by_offset_order() { + let field_offset = offsets[field_idx].bytes(); + let field_ty = fields[field_idx].ty_with_args(&args); + fields_validity.append(&mut data_bytes_for_ty( + machine_info, + field_ty, + field_offset + current_offset, + )?); + } + } + if fields_validity.is_empty() { + Ok(enum_validity) + } else { + Err(format!( + "Unsupported Enum `{}` check", + def.trimmed_name() + )) + } + } + } + } + AdtKind::Union => unreachable!(), + AdtKind::Struct => { + // If the struct range has niche add that. + let mut struct_validity = ty_size(); + let fields = def.variants_iter().next().unwrap().fields(); + for idx in layout.fields.fields_by_offset_order() { + let field_offset = offsets[idx].bytes(); + let field_ty = fields[idx].ty_with_args(&args); + struct_validity.append(&mut data_bytes_for_ty( + machine_info, + field_ty, + field_offset + current_offset, + )?); + } + Ok(struct_validity) + } + } + } + RigidTy::Pat(base_ty, ..) => { + // This is similar to a structure with one field and with niche defined. + let mut pat_validity = ty_size(); + pat_validity.append(&mut data_bytes_for_ty(machine_info, *base_ty, 0)?); + Ok(pat_validity) + } + RigidTy::Tuple(tys) => { + let mut tuple_validity = vec![]; + for idx in layout.fields.fields_by_offset_order() { + let field_offset = offsets[idx].bytes(); + let field_ty = tys[idx]; + tuple_validity.append(&mut data_bytes_for_ty( + machine_info, + field_ty, + field_offset + current_offset, + )?); + } + Ok(tuple_validity) + } + RigidTy::Bool + | RigidTy::Char + | RigidTy::Int(_) + | RigidTy::Uint(_) + | RigidTy::Float(_) + | RigidTy::Never => { + unreachable!("Expected primitive layout for {ty:?}") + } + RigidTy::Str | RigidTy::Slice(_) | RigidTy::Array(_, _) => { + unreachable!("Expected array layout for {ty:?}") + } + RigidTy::RawPtr(_, _) | RigidTy::Ref(_, _, _) => Ok(ty_size()), + RigidTy::FnDef(_, _) + | RigidTy::FnPtr(_) + | RigidTy::Closure(_, _) + | RigidTy::Coroutine(_, _, _) + | RigidTy::CoroutineWitness(_, _) + | RigidTy::Foreign(_) + | RigidTy::Dynamic(_, _, _) => Err(format!("Unsupported {ty:?}")), + } + } + FieldsShape::Union(_) => Err(format!("Unsupported {ty:?}")), + FieldsShape::Array { .. } => { + unreachable!("Expected dynamically sized type for {ty:?}") + } + } +} diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs new file mode 100644 index 000000000000..4c24c7ef0e75 --- /dev/null +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -0,0 +1,275 @@ +use crate::kani_middle::transform::body::{MutableBody, SourceInstruction}; +use stable_mir::mir::mono::{Instance, InstanceKind}; +use stable_mir::mir::visit::{Location, PlaceContext}; +use stable_mir::mir::{ + BasicBlockIdx, Constant, LocalDecl, MirVisitor, Mutability, NonDivergingIntrinsic, Operand, + Place, ProjectionElem, Statement, StatementKind, Terminator, TerminatorKind, +}; +use stable_mir::ty::{Const, RigidTy, Span, TyKind, UintTy}; +use strum_macros::AsRefStr; + +#[derive(AsRefStr, Clone, Debug)] +pub enum SourceOp { + Get { place: Place, count: Operand }, + Set { place: Place, count: Operand, value: bool }, + Unsupported { instruction: String, place: Place }, +} + +#[derive(Clone, Debug)] +pub struct InitRelevantInstruction { + /// The instruction that affects the state of the memory. + pub source: SourceInstruction, + /// All memory-related operations in this instructions. + pub operations: Vec, +} + +pub struct CheckUninitVisitor<'a> { + locals: &'a [LocalDecl], + /// Whether we should skip the next instruction, since it might've been instrumented already. + /// When we instrument an instruction, we partition the basic block, and the instruction that + /// may trigger UB becomes the first instruction of the basic block, which we need to skip + /// later. + skip_next: bool, + /// The instruction being visited at a given point. + current: SourceInstruction, + /// The target instruction that should be verified. + pub target: Option, + /// The basic block being visited. + bb: BasicBlockIdx, +} + +fn expect_place(op: &Operand) -> &Place { + match op { + Operand::Copy(place) | Operand::Move(place) => place, + Operand::Constant(_) => unreachable!(), + } +} + +fn mk_const_operand(value: usize, span: Span) -> Operand { + Operand::Constant(Constant { + span, + user_ty: None, + literal: Const::try_from_uint(value as u128, UintTy::Usize).unwrap(), + }) +} + +fn try_remove_topmost_deref(place: &Place) -> Option { + let mut new_place = place.clone(); + if let Some(ProjectionElem::Deref) = new_place.projection.pop() { + Some(new_place) + } else { + None + } +} + +/// Retrieve instance for the given function operand. +/// +/// This will panic if the operand is not a function or if it cannot be resolved. +fn expect_instance(locals: &[LocalDecl], func: &Operand) -> Instance { + let ty = func.ty(locals).unwrap(); + match ty.kind() { + TyKind::RigidTy(RigidTy::FnDef(def, args)) => Instance::resolve(def, &args).unwrap(), + _ => unreachable!(), + } +} + +impl<'a> CheckUninitVisitor<'a> { + pub fn find_next( + body: &'a MutableBody, + bb: BasicBlockIdx, + skip_first: bool, + ) -> Option { + let mut visitor = CheckUninitVisitor { + locals: body.locals(), + skip_next: skip_first, + current: SourceInstruction::Statement { idx: 0, bb }, + target: None, + bb, + }; + visitor.visit_basic_block(&body.blocks()[bb]); + visitor.target + } + + fn push_target(&mut self, op: SourceOp) { + let target = self.target.get_or_insert_with(|| InitRelevantInstruction { + source: self.current, + operations: vec![], + }); + target.operations.push(op); + } +} + +impl<'a> MirVisitor for CheckUninitVisitor<'a> { + fn visit_statement(&mut self, stmt: &Statement, location: Location) { + if self.skip_next { + self.skip_next = false; + } else if self.target.is_none() { + // Leave it as an exhaustive match to be notified when a new kind is added. + match &stmt.kind { + StatementKind::Intrinsic(NonDivergingIntrinsic::CopyNonOverlapping(copy)) => { + self.super_statement(stmt, location); + // Source is a *const T and it must be initialized. + self.push_target(SourceOp::Get { + place: expect_place(©.src).clone(), + count: copy.count.clone(), + }); + // Destimation is a *mut T so it gets initialized. + self.push_target(SourceOp::Set { + place: expect_place(©.dst).clone(), + count: copy.count.clone(), + value: true, + }); + } + StatementKind::Assign(place, rvalue) => { + // First check rvalue. + self.visit_rvalue(rvalue, location); + // Then check the destination place. + if let Some(place_without_deref) = try_remove_topmost_deref(place) { + if place_without_deref.ty(&self.locals).unwrap().kind().is_raw_ptr() { + self.push_target(SourceOp::Set { + place: place_without_deref, + count: mk_const_operand(1, location.span()), + value: true, + }); + } + } + } + StatementKind::Deinit(place) => { + self.super_statement(stmt, location); + self.push_target(SourceOp::Set { + place: place.clone(), + count: mk_const_operand(1, location.span()), + value: false, + }); + } + StatementKind::FakeRead(_, _) + | StatementKind::SetDiscriminant { .. } + | StatementKind::StorageLive(_) + | StatementKind::StorageDead(_) + | StatementKind::Retag(_, _) + | StatementKind::PlaceMention(_) + | StatementKind::AscribeUserType { .. } + | StatementKind::Coverage(_) + | StatementKind::ConstEvalCounter + | StatementKind::Intrinsic(NonDivergingIntrinsic::Assume(_)) + | StatementKind::Nop => self.super_statement(stmt, location), + } + } + let SourceInstruction::Statement { idx, bb } = self.current else { unreachable!() }; + self.current = SourceInstruction::Statement { idx: idx + 1, bb }; + } + fn visit_terminator(&mut self, term: &Terminator, location: Location) { + if !(self.skip_next || self.target.is_some()) { + self.current = SourceInstruction::Terminator { bb: self.bb }; + // Leave it as an exhaustive match to be notified when a new kind is added. + match &term.kind { + TerminatorKind::Call { func, args, .. } => { + self.super_terminator(term, location); + let instance = expect_instance(self.locals, func); + if instance.kind == InstanceKind::Intrinsic { + match instance.intrinsic_name().unwrap().as_str() { + "write_bytes" => { + assert_eq!( + args.len(), + 3, + "Unexpected number of arguments for `write_bytes`" + ); + assert!(matches!( + args[0].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) + )); + self.push_target(SourceOp::Set { + place: expect_place(&args[0]).clone(), + count: args[2].clone(), + value: true, + }) + } + "compare_bytes" => { + assert_eq!( + args.len(), + 3, + "Unexpected number of arguments for `compare_bytes`" + ); + assert!(matches!( + args[0].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) + )); + assert!(matches!( + args[1].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) + )); + self.push_target(SourceOp::Get { + place: expect_place(&args[0]).clone(), + count: args[2].clone(), + }); + self.push_target(SourceOp::Get { + place: expect_place(&args[1]).clone(), + count: args[2].clone(), + }); + } + "transmute" | "transmute_copy" => { + unreachable!("Should've been lowered") + } + _ => {} + } + } + } + TerminatorKind::Drop { place, .. } => { + self.super_terminator(term, location); + if place.ty(&self.locals).unwrap().kind().is_raw_ptr() { + self.push_target(SourceOp::Set { + place: place.clone(), + count: mk_const_operand(1, location.span()), + value: false, + }); + } + } + TerminatorKind::Goto { .. } + | TerminatorKind::SwitchInt { .. } + | TerminatorKind::Resume + | TerminatorKind::Abort + | TerminatorKind::Return + | TerminatorKind::Unreachable + | TerminatorKind::Assert { .. } + | TerminatorKind::InlineAsm { .. } => self.super_terminator(term, location), + } + } + } + + fn visit_place(&mut self, place: &Place, ptx: PlaceContext, location: Location) { + for (idx, elem) in place.projection.iter().enumerate() { + let intermediate_place = + Place { local: place.local, projection: place.projection[..idx].to_vec() }; + match elem { + ProjectionElem::Deref => { + let ptr_ty = intermediate_place.ty(self.locals).unwrap(); + if ptr_ty.kind().is_raw_ptr() { + self.push_target(SourceOp::Get { + place: intermediate_place.clone(), + count: mk_const_operand(1, location.span()), + }); + } + } + ProjectionElem::Field(idx, target_ty) => { + if target_ty.kind().is_union() + && (!ptx.is_mutating() || place.projection.len() > idx + 1) + { + self.push_target(SourceOp::Unsupported { + instruction: "union access".to_string(), + place: intermediate_place.clone(), + }); + } + } + ProjectionElem::Index(_) + | ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Subslice { .. } => { + /* For a slice to be indexed, it should be valid first. */ + } + ProjectionElem::Downcast(_) => {} + ProjectionElem::OpaqueCast(_) => {} + ProjectionElem::Subtype(_) => {} + } + } + self.super_place(place, ptx, location) + } +} diff --git a/library/kani/src/shadow.rs b/library/kani/src/shadow.rs index f79ebb390e63..a519d950029e 100644 --- a/library/kani/src/shadow.rs +++ b/library/kani/src/shadow.rs @@ -82,49 +82,18 @@ impl ShadowMem { } } -pub static mut GLOBAL_SM: ShadowMem = ShadowMem::new(false); - -#[rustc_diagnostic_item = "KaniShadowMemoryGet"] -pub fn global_sm_get(ptr: *const U) -> bool { - return unsafe { GLOBAL_SM.get(ptr) }; -} - -#[rustc_diagnostic_item = "KaniShadowMemorySet"] -pub fn global_sm_set(ptr: *const U, val: bool) { - return unsafe { GLOBAL_SM.set(ptr, val) }; +/// Small helper to access the `len` field of a slice DST. +#[rustc_diagnostic_item = "KaniGetSliceSizeHelper"] +pub fn get_slice_size(slice: *const [()]) -> usize { + slice.len() } -#[rustc_diagnostic_item = "KaniShadowMemoryGetWithLayout"] -pub fn global_sm_get_with_layout(ptr: *const (), layout: [bool; N]) -> bool { - let mut offset: usize = 0; - while offset < N { - unsafe { - if layout[offset] && !GLOBAL_SM.get((ptr as *const u8).add(offset)) { - return false; - } - offset += 1; - } - } - return true; -} - -#[rustc_diagnostic_item = "KaniShadowMemorySetWithLayout"] -pub fn global_sm_set_with_layout(ptr: *const (), layout: [bool; N], value: bool) { - let mut offset: usize = 0; - while offset < N { - unsafe { - GLOBAL_SM.set((ptr as *const u8).add(offset), value && layout[offset]); - } - offset += 1; - } -} +/// Global shadow memory object. +pub static mut GLOBAL_SM: ShadowMem = ShadowMem::new(false); -#[rustc_diagnostic_item = "KaniShadowMemoryGetWithLayoutDynamic"] -pub fn global_sm_get_with_layout_dynamic( - ptr: *const (), - layout: [bool; N], - n: usize, -) -> bool { +// Get initialization setate of `n` items laid out according to the `layout` starting at address `ptr`. +#[rustc_diagnostic_item = "KaniShadowMemoryGet"] +pub fn global_sm_get(ptr: *const (), layout: [bool; N], n: usize) -> bool { let mut count: usize = 0; while count < n { let mut offset: usize = 0; @@ -141,13 +110,9 @@ pub fn global_sm_get_with_layout_dynamic( return true; } -#[rustc_diagnostic_item = "KaniShadowMemorySetWithLayoutDynamic"] -pub fn global_sm_set_with_layout_dynamic( - ptr: *const (), - layout: [bool; N], - n: usize, - value: bool, -) { +// Set initialization setate to `value` for `n` items laid out according to the `layout` starting at address `ptr`. +#[rustc_diagnostic_item = "KaniShadowMemorySet"] +pub fn global_sm_set(ptr: *const (), layout: [bool; N], n: usize, value: bool) { let mut count: usize = 0; while count < n { let mut offset: usize = 0; diff --git a/tests/uninit/after-aggregate-assign-init.rs b/tests/uninit/after-aggregate-assign-init.rs new file mode 100644 index 000000000000..02c1cf6854cb --- /dev/null +++ b/tests/uninit/after-aggregate-assign-init.rs @@ -0,0 +1,32 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z ghost-state -Z uninit-checks + +#![feature(core_intrinsics)] +#![feature(custom_mir)] + +use std::intrinsics::mir::*; +use std::ptr; + +#[repr(C)] +struct S(u16, u16); + +#[kani::proof] +#[custom_mir(dialect = "runtime", phase = "optimized")] +fn main() { + mir! { + let s: S; + let sptr; + let sptr2; + let _val; + { + sptr = ptr::addr_of_mut!(s); + sptr2 = sptr as *mut [u8; 4]; + *sptr2 = [0; 4]; + *sptr = S(0, 0); // should reset the padding + _val = *sptr2; // should hence be UB + //~^ERROR: encountered uninitialized memory + Return() + } + } +} diff --git a/tests/uninit/after-aggregate-assign.rs b/tests/uninit/after-aggregate-assign-uninit.rs similarity index 80% rename from tests/uninit/after-aggregate-assign.rs rename to tests/uninit/after-aggregate-assign-uninit.rs index 581716d27658..d60ae3a774b9 100644 --- a/tests/uninit/after-aggregate-assign.rs +++ b/tests/uninit/after-aggregate-assign-uninit.rs @@ -1,3 +1,7 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z ghost-state -Z uninit-checks + #![feature(core_intrinsics)] #![feature(custom_mir)] @@ -8,6 +12,7 @@ use std::ptr; struct S(u8, u16); #[kani::proof] +#[kani::should_panic] #[custom_mir(dialect = "runtime", phase = "optimized")] fn main() { mir! { diff --git a/tests/uninit/alloc-diagnostic-with-provenance.rs b/tests/uninit/alloc-diagnostic-with-provenance.rs deleted file mode 100644 index 4635ebda36db..000000000000 --- a/tests/uninit/alloc-diagnostic-with-provenance.rs +++ /dev/null @@ -1,32 +0,0 @@ -#![feature(strict_provenance)] -#![allow(dropping_copy_types)] - -// Test printing allocations that contain single-byte provenance. - -use std::alloc::{alloc, dealloc, Layout}; -use std::mem::{self, MaybeUninit}; -use std::slice::from_raw_parts; - -fn byte_with_provenance(val: u8, prov: *const T) -> MaybeUninit { - let ptr = prov.with_addr(val as usize); - let bytes: [MaybeUninit; mem::size_of::<*const ()>()] = unsafe { mem::transmute(ptr) }; - let lsb = if cfg!(target_endian = "little") { 0 } else { bytes.len() - 1 }; - bytes[lsb] -} - -#[kani::proof] -fn main() { - let layout = Layout::from_size_align(16, 8).unwrap(); - unsafe { - let ptr = alloc(layout); - let ptr_raw = ptr.cast::>(); - *ptr_raw.add(0) = byte_with_provenance(0x42, &42u8); - *ptr.add(1) = 0x12; - *ptr.add(2) = 0x13; - *ptr_raw.add(3) = byte_with_provenance(0x43, &0u8); - let slice1 = from_raw_parts(ptr, 8); - let slice2 = from_raw_parts(ptr.add(8), 8); - drop(slice1.cmp(slice2)); - dealloc(ptr, layout); - } -} diff --git a/tests/uninit/alloc-diagnostic.rs b/tests/uninit/alloc-diagnostic.rs index e95c7ca5ecb9..75cf0c085698 100644 --- a/tests/uninit/alloc-diagnostic.rs +++ b/tests/uninit/alloc-diagnostic.rs @@ -1,9 +1,14 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z ghost-state -Z uninit-checks + #![allow(dropping_copy_types)] use std::alloc::{alloc, dealloc, Layout}; use std::slice::from_raw_parts; #[kani::proof] +#[kani::should_panic] fn main() { let layout = Layout::from_size_align(32, 8).unwrap(); unsafe { @@ -18,4 +23,4 @@ fn main() { drop(slice1.cmp(slice2)); dealloc(ptr, layout); } -} \ No newline at end of file +} diff --git a/tests/uninit/byte-read-init.rs b/tests/uninit/byte-read-init.rs new file mode 100644 index 000000000000..ab5f7e9fdda2 --- /dev/null +++ b/tests/uninit/byte-read-init.rs @@ -0,0 +1,11 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z ghost-state -Z uninit-checks + +#[kani::proof] +fn main() { + let mut v: Vec = Vec::with_capacity(10); + unsafe { *v.as_mut_ptr().add(5) = 0x42 }; + let def = unsafe { *v.as_ptr().add(5) }; + let x = def + 1; +} diff --git a/tests/uninit/byte-read-semi-init.rs b/tests/uninit/byte-read-semi-init.rs new file mode 100644 index 000000000000..3bcbf0705cb6 --- /dev/null +++ b/tests/uninit/byte-read-semi-init.rs @@ -0,0 +1,12 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z ghost-state -Z uninit-checks + +#[kani::proof] +#[kani::should_panic] +fn main() { + let mut v: Vec = Vec::with_capacity(10); + unsafe { *v.as_mut_ptr().add(4) = 0x42 }; + let def = unsafe { *v.as_ptr().add(5) }; + let x = def + 1; +} diff --git a/tests/uninit/byte-read.rs b/tests/uninit/byte-read-uninit.rs similarity index 53% rename from tests/uninit/byte-read.rs rename to tests/uninit/byte-read-uninit.rs index e65ee7c8c757..dbadd896132d 100644 --- a/tests/uninit/byte-read.rs +++ b/tests/uninit/byte-read-uninit.rs @@ -1,4 +1,9 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z ghost-state -Z uninit-checks + #[kani::proof] +#[kani::should_panic] fn main() { let v: Vec = Vec::with_capacity(10); let undef = unsafe { *v.as_ptr().add(5) }; //~ ERROR: uninitialized From f3edc52adb5bb87bdc5a68b2a9967b15eafe0cef Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 13 Jun 2024 10:18:04 -0700 Subject: [PATCH 03/87] Implement wide pointer decomposition as part of kani library --- .../codegen_cprover_gotoc/overrides/hooks.rs | 30 +-- .../kani_middle/transform/check_uninit/mod.rs | 179 ++++++++++-------- .../transform/check_uninit/uninit_visitor.rs | 101 +++++----- library/kani/src/shadow.rs | 45 ++++- tests/uninit/alloc-zeroed.rs | 25 +++ tests/uninit/fixme/partially_uninit.rs | 20 ++ tests/uninit/fixme/transmute-pair-uninit.rs | 30 +++ 7 files changed, 271 insertions(+), 159 deletions(-) create mode 100644 tests/uninit/alloc-zeroed.rs create mode 100644 tests/uninit/fixme/partially_uninit.rs create mode 100644 tests/uninit/fixme/transmute-pair-uninit.rs diff --git a/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs b/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs index 6994d0f97cd3..eb17c33f5bee 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs @@ -252,25 +252,15 @@ impl GotocHook for PointerObject { fn handle( &self, gcx: &mut GotocCtx, - instance: Instance, + _instance: Instance, mut fargs: Vec, assign_to: &Place, target: Option, span: Span, ) -> Stmt { assert_eq!(fargs.len(), 1); - let ptr = { - let ptr = fargs.pop().unwrap(); - let place_ty = instance.args().0[0].expect_ty().clone(); - // Handle both fat and thin pointers. - if gcx.use_thin_pointer_stable(place_ty) { - ptr - } else { - ptr.member("data", &gcx.symbol_table) - } - } - .cast_to(Type::void_pointer()); - let target = target.unwrap(); + let ptr = fargs.pop().unwrap().cast_to(Type::void_pointer()); + let target: usize = target.unwrap(); let loc = gcx.codegen_caller_span_stable(span); let ret_place = unwrap_or_return_codegen_unimplemented_stmt!(gcx, gcx.codegen_place_stable(assign_to)); @@ -296,24 +286,14 @@ impl GotocHook for PointerOffset { fn handle( &self, gcx: &mut GotocCtx, - instance: Instance, + _instance: Instance, mut fargs: Vec, assign_to: &Place, target: Option, span: Span, ) -> Stmt { assert_eq!(fargs.len(), 1); - let ptr = { - let ptr = fargs.pop().unwrap(); - let place_ty = instance.args().0[0].expect_ty().clone(); - // Handle both fat and thin pointers. - if gcx.use_thin_pointer_stable(place_ty) { - ptr - } else { - ptr.member("data", &gcx.symbol_table) - } - } - .cast_to(Type::void_pointer()); + let ptr = fargs.pop().unwrap().cast_to(Type::void_pointer()); let target = target.unwrap(); let loc = gcx.codegen_caller_span_stable(span); let ret_place = diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index e5498a1c2133..fc37fab6882d 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -22,7 +22,7 @@ mod uninit_visitor; use ty_layout::TypeLayout; use uninit_visitor::{CheckUninitVisitor, InitRelevantInstruction, SourceOp}; -const UNINIT_ALLOWLIST: &[&str] = &["kani::shadow", "std::alloc::alloc", "std::ptr::drop_in_place"]; +const UNINIT_ALLOWLIST: &[&str] = &["kani::shadow"]; /// Instrument the code with checks for uninitialized memory. #[derive(Debug)] @@ -115,82 +115,81 @@ impl UninitPass { } }; - let count = match &type_layout { - TypeLayout::StaticallySized { .. } => match &operation { - SourceOp::Get { count, .. } | SourceOp::Set { count, .. } => count.clone(), - SourceOp::Unsupported { .. } => unreachable!(), - }, - TypeLayout::DynamicallySized { .. } => { - let slice_local = body.new_cast_transmute( - Operand::Copy(place.clone()), - Ty::from_rigid_kind(RigidTy::Slice(Ty::new_tuple(&[]))), - Mutability::Not, - &mut source, - ); - - let get_slice_size = Instance::resolve( - find_fn_def(tcx, "KaniGetSliceSizeHelper").unwrap(), - &GenericArgs(vec![]), - ) - .unwrap(); - - let ret_place = Place { - local: body.new_local( - Ty::usize_ty(), - source.span(body.blocks()), - Mutability::Not, - ), - projection: vec![], - }; - body.add_call( - &get_slice_size, - &mut source, - vec![Operand::Copy(Place { local: slice_local, projection: vec![] })], - ret_place.clone(), - ); - Operand::Copy(ret_place) - } + let count = match &operation { + SourceOp::Get { count, .. } | SourceOp::Set { count, .. } => count.clone(), + SourceOp::Unsupported { .. } => unreachable!(), }; let span = source.span(body.blocks()); - let layout_local = body.new_assignment( - Rvalue::Aggregate( - AggregateKind::Array(Ty::bool_ty()), - type_layout - .as_byte_layout() - .iter() - .map(|byte| { - Operand::Constant(Constant { - span, - user_ty: None, - literal: Const::from_bool(*byte), + let layout_place = Place { + local: body.new_assignment( + Rvalue::Aggregate( + AggregateKind::Array(Ty::bool_ty()), + type_layout + .as_byte_layout() + .iter() + .map(|byte| { + Operand::Constant(Constant { + span, + user_ty: None, + literal: Const::from_bool(*byte), + }) }) - }) - .collect(), + .collect(), + ), + &mut source, ), - &mut source, - ); + projection: vec![], + }; - let ptr_local = body.new_cast_ptr( - Operand::Copy(place.clone()), - Ty::new_tuple(&[]), - Mutability::Not, - &mut source, - ); + let ptr_local = match pointee_ty.kind() { + TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => body + .new_cast_transmute( + Operand::Copy(place.clone()), + Ty::from_rigid_kind(RigidTy::Slice(Ty::new_tuple(&[]))), + Mutability::Not, + &mut source, + ), + _ => body.new_cast_ptr( + Operand::Copy(place.clone()), + Ty::new_tuple(&[]), + Mutability::Not, + &mut source, + ), + }; match operation { SourceOp::Get { .. } => { - let sm_get = Instance::resolve( - find_fn_def(tcx, "KaniShadowMemoryGet").unwrap(), - &GenericArgs(vec![GenericArgKind::Const( - Const::try_from_uint( - type_layout.as_byte_layout().len() as u128, - UintTy::Usize, + let shadow_memory_get = match pointee_ty.kind() { + TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => { + Instance::resolve( + find_fn_def(tcx, "KaniShadowMemoryGetSlice").unwrap(), + &GenericArgs(vec![GenericArgKind::Const( + Const::try_from_uint( + type_layout.as_byte_layout().len() as u128, + UintTy::Usize, + ) + .unwrap(), + )]), ) - .unwrap(), - )]), - ) - .unwrap(); + .unwrap() + } + _ => Instance::resolve( + find_fn_def(tcx, "KaniShadowMemoryGet").unwrap(), + &GenericArgs(vec![ + GenericArgKind::Const( + Const::try_from_uint( + type_layout.as_byte_layout().len() as u128, + UintTy::Usize, + ) + .unwrap(), + ), + GenericArgKind::Type(pointee_ty), + ]), + ) + .unwrap(), + }; + let ret_place = Place { local: body.new_local( Ty::bool_ty(), @@ -200,11 +199,11 @@ impl UninitPass { projection: vec![], }; body.add_call( - &sm_get, + &shadow_memory_get, &mut source, vec![ Operand::Copy(Place { local: ptr_local, projection: vec![] }), - Operand::Move(Place { local: layout_local, projection: vec![] }), + Operand::Move(layout_place), count, ], ret_place.clone(), @@ -218,17 +217,35 @@ impl UninitPass { ) } SourceOp::Set { value, .. } => { - let sm_set = Instance::resolve( - find_fn_def(tcx, "KaniShadowMemorySet").unwrap(), - &GenericArgs(vec![GenericArgKind::Const( - Const::try_from_uint( - type_layout.as_byte_layout().len() as u128, - UintTy::Usize, + let shadow_memory_set = match pointee_ty.kind() { + TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => { + Instance::resolve( + find_fn_def(tcx, "KaniShadowMemorySetSlice").unwrap(), + &GenericArgs(vec![GenericArgKind::Const( + Const::try_from_uint( + type_layout.as_byte_layout().len() as u128, + UintTy::Usize, + ) + .unwrap(), + )]), ) - .unwrap(), - )]), - ) - .unwrap(); + .unwrap() + } + _ => Instance::resolve( + find_fn_def(tcx, "KaniShadowMemorySet").unwrap(), + &GenericArgs(vec![ + GenericArgKind::Const( + Const::try_from_uint( + type_layout.as_byte_layout().len() as u128, + UintTy::Usize, + ) + .unwrap(), + ), + GenericArgKind::Type(pointee_ty), + ]), + ) + .unwrap(), + }; let ret_place = Place { local: body.new_local( Ty::new_tuple(&[]), @@ -238,11 +255,11 @@ impl UninitPass { projection: vec![], }; body.add_call( - &sm_set, + &shadow_memory_set, &mut source, vec![ Operand::Copy(Place { local: ptr_local, projection: vec![] }), - Operand::Move(Place { local: layout_local, projection: vec![] }), + Operand::Move(layout_place), count, Operand::Constant(Constant { span, diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index 4c24c7ef0e75..b4a4e2bdc17a 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -166,52 +166,67 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { TerminatorKind::Call { func, args, .. } => { self.super_terminator(term, location); let instance = expect_instance(self.locals, func); - if instance.kind == InstanceKind::Intrinsic { - match instance.intrinsic_name().unwrap().as_str() { - "write_bytes" => { - assert_eq!( - args.len(), - 3, - "Unexpected number of arguments for `write_bytes`" - ); - assert!(matches!( - args[0].ty(self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) - )); - self.push_target(SourceOp::Set { - place: expect_place(&args[0]).clone(), - count: args[2].clone(), - value: true, - }) + match instance.kind { + InstanceKind::Intrinsic => { + match instance.intrinsic_name().unwrap().as_str() { + "write_bytes" => { + assert_eq!( + args.len(), + 3, + "Unexpected number of arguments for `write_bytes`" + ); + assert!(matches!( + args[0].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) + )); + self.push_target(SourceOp::Set { + place: expect_place(&args[0]).clone(), + count: args[2].clone(), + value: true, + }) + } + "compare_bytes" => { + assert_eq!( + args.len(), + 3, + "Unexpected number of arguments for `compare_bytes`" + ); + assert!(matches!( + args[0].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) + )); + assert!(matches!( + args[1].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) + )); + self.push_target(SourceOp::Get { + place: expect_place(&args[0]).clone(), + count: args[2].clone(), + }); + self.push_target(SourceOp::Get { + place: expect_place(&args[1]).clone(), + count: args[2].clone(), + }); + } + "transmute" | "transmute_copy" => { + unreachable!("Should've been lowered") + } + _ => { /* TODO: add other intrinsics */ } } - "compare_bytes" => { - assert_eq!( - args.len(), - 3, - "Unexpected number of arguments for `compare_bytes`" - ); - assert!(matches!( - args[0].ty(self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) - )); - assert!(matches!( - args[1].ty(self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) - )); - self.push_target(SourceOp::Get { - place: expect_place(&args[0]).clone(), - count: args[2].clone(), - }); - self.push_target(SourceOp::Get { - place: expect_place(&args[1]).clone(), - count: args[2].clone(), - }); - } - "transmute" | "transmute_copy" => { - unreachable!("Should've been lowered") + } + InstanceKind::Item => { + if instance.is_foreign_item() { + match instance.name().as_str() { + /* TODO: implement those */ + "alloc::alloc::__rust_alloc" => {} + "alloc::alloc::__rust_realloc" => {} + "alloc::alloc::__rust_alloc_zeroed" => {} + "alloc::alloc::__rust_dealloc" => {} + _ => {} + } } - _ => {} } + _ => {} } } TerminatorKind::Drop { place, .. } => { diff --git a/library/kani/src/shadow.rs b/library/kani/src/shadow.rs index a519d950029e..0ac28e384f58 100644 --- a/library/kani/src/shadow.rs +++ b/library/kani/src/shadow.rs @@ -82,18 +82,11 @@ impl ShadowMem { } } -/// Small helper to access the `len` field of a slice DST. -#[rustc_diagnostic_item = "KaniGetSliceSizeHelper"] -pub fn get_slice_size(slice: *const [()]) -> usize { - slice.len() -} - /// Global shadow memory object. pub static mut GLOBAL_SM: ShadowMem = ShadowMem::new(false); // Get initialization setate of `n` items laid out according to the `layout` starting at address `ptr`. -#[rustc_diagnostic_item = "KaniShadowMemoryGet"] -pub fn global_sm_get(ptr: *const (), layout: [bool; N], n: usize) -> bool { +fn global_sm_get_inner(ptr: *const (), layout: [bool; N], n: usize) -> bool { let mut count: usize = 0; while count < n { let mut offset: usize = 0; @@ -111,8 +104,7 @@ pub fn global_sm_get(ptr: *const (), layout: [bool; N], n: usize } // Set initialization setate to `value` for `n` items laid out according to the `layout` starting at address `ptr`. -#[rustc_diagnostic_item = "KaniShadowMemorySet"] -pub fn global_sm_set(ptr: *const (), layout: [bool; N], n: usize, value: bool) { +fn global_sm_set_inner(ptr: *const (), layout: [bool; N], n: usize, value: bool) { let mut count: usize = 0; while count < n { let mut offset: usize = 0; @@ -125,3 +117,36 @@ pub fn global_sm_set(ptr: *const (), layout: [bool; N], n: usize count += 1; } } + +#[rustc_diagnostic_item = "KaniShadowMemoryGet"] +pub fn global_sm_get(ptr: *const (), layout: [bool; N], n: usize) -> bool { + let (ptr, _) = ptr.to_raw_parts(); + global_sm_get_inner(ptr, layout, n) +} + +#[rustc_diagnostic_item = "KaniShadowMemorySet"] +pub fn global_sm_set(ptr: *const (), layout: [bool; N], n: usize, value: bool) { + let (ptr, _) = ptr.to_raw_parts(); + global_sm_set_inner(ptr, layout, n, value); +} + +#[rustc_diagnostic_item = "KaniShadowMemoryGetSlice"] +pub fn global_sm_get_slice(ptr: *const [()], layout: [bool; N], n: usize) -> bool { + let (ptr, meta) = ptr.to_raw_parts(); + // The pointee type is a DST, more than `n` objects can be accessed. + let n = n * meta; + global_sm_get_inner(ptr, layout, n) +} + +#[rustc_diagnostic_item = "KaniShadowMemorySetSlice"] +pub fn global_sm_set_slice( + ptr: *const [()], + layout: [bool; N], + n: usize, + value: bool, +) { + let (ptr, meta) = ptr.to_raw_parts(); + // The pointee type is a DST, more than `n` objects can be accessed. + let n = n * meta; + global_sm_set_inner(ptr, layout, n, value); +} diff --git a/tests/uninit/alloc-zeroed.rs b/tests/uninit/alloc-zeroed.rs new file mode 100644 index 000000000000..7dc7f0e5296f --- /dev/null +++ b/tests/uninit/alloc-zeroed.rs @@ -0,0 +1,25 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z ghost-state -Z uninit-checks + +#![allow(dropping_copy_types)] + +use std::alloc::{alloc_zeroed, dealloc, Layout}; +use std::slice::from_raw_parts; + +#[kani::proof] +fn main() { + let layout = Layout::from_size_align(32, 8).unwrap(); + unsafe { + let ptr = alloc_zeroed(layout); + *ptr = 0x41; + *ptr.add(1) = 0x42; + *ptr.add(2) = 0x43; + *ptr.add(3) = 0x44; + *ptr.add(16) = 0x00; + let slice1 = from_raw_parts(ptr, 16); + let slice2 = from_raw_parts(ptr.add(16), 16); + drop(slice1.cmp(slice2)); + dealloc(ptr, layout); + } +} diff --git a/tests/uninit/fixme/partially_uninit.rs b/tests/uninit/fixme/partially_uninit.rs new file mode 100644 index 000000000000..1ecd579ae317 --- /dev/null +++ b/tests/uninit/fixme/partially_uninit.rs @@ -0,0 +1,20 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z ghost-state -Z uninit-checks + +use std::mem::{self, MaybeUninit}; + +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq)] +struct Demo(bool, u16); + +#[kani::proof] +fn main() { + unsafe { + // Transmute-round-trip through a type with Scalar layout is lossless. + // This is tricky because that 'scalar' is *partially* uninitialized. + let x = Demo(true, 3); + let y: MaybeUninit = mem::transmute(x); + assert_eq!(x, mem::transmute(y)); + } +} diff --git a/tests/uninit/fixme/transmute-pair-uninit.rs b/tests/uninit/fixme/transmute-pair-uninit.rs new file mode 100644 index 000000000000..025360298771 --- /dev/null +++ b/tests/uninit/fixme/transmute-pair-uninit.rs @@ -0,0 +1,30 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z ghost-state -Z uninit-checks + +#![feature(core_intrinsics)] + +use std::mem; + +#[kani::proof] +#[kani::unwind(33)] +fn main() { + let x: Option> = unsafe { + let z = std::intrinsics::add_with_overflow(0usize, 0usize); + std::mem::transmute::<(usize, bool), Option>>(z) + }; + let y = &x; + // Now read this bytewise. There should be (`ptr_size + 1`) def bytes followed by + // (`ptr_size - 1`) undef bytes (the padding after the bool) in there. + let z: *const u8 = y as *const _ as *const _; + let first_undef = mem::size_of::() as isize + 1; + for i in 0..first_undef { + let byte = unsafe { *z.offset(i) }; + assert_eq!(byte, 0); + } + let v = unsafe { *z.offset(first_undef) }; + //~^ ERROR: uninitialized + if v == 0 { + println!("it is zero"); + } +} \ No newline at end of file From 57c31d40c4f03a62e392f62be7262b0248034200 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 13 Jun 2024 15:49:09 -0700 Subject: [PATCH 04/87] Ensure checks are executed before instructions and statements -- after --- .../src/kani_middle/transform/body.rs | 180 ++++++++++++++---- .../kani_middle/transform/check_uninit/mod.rs | 55 ++++-- .../transform/check_uninit/ty_layout.rs | 4 +- .../transform/check_uninit/uninit_visitor.rs | 30 ++- .../src/kani_middle/transform/check_values.rs | 78 ++++++-- .../kani_middle/transform/kani_intrinsics.rs | 20 +- library/kani/src/shadow.rs | 43 +++-- ...c-zeroed.rs => alloc-diagnostic-zeroed.rs} | 0 tests/uninit/fixme/transmute-pair-uninit.rs | 2 +- 9 files changed, 322 insertions(+), 90 deletions(-) rename tests/uninit/{alloc-zeroed.rs => alloc-diagnostic-zeroed.rs} (100%) diff --git a/kani-compiler/src/kani_middle/transform/body.rs b/kani-compiler/src/kani_middle/transform/body.rs index 79011c5954e7..bfb657184886 100644 --- a/kani-compiler/src/kani_middle/transform/body.rs +++ b/kani-compiler/src/kani_middle/transform/body.rs @@ -12,6 +12,7 @@ use stable_mir::mir::{ VarDebugInfo, }; use stable_mir::ty::{Const, GenericArgs, Span, Ty, UintTy}; +use std::collections::HashSet; use std::fmt::Debug; use std::mem; @@ -39,6 +40,15 @@ pub struct MutableBody { /// The span that covers the entire function body. span: Span, + + /// Set of basic block indices for which analyzing first statement should be skipped. + skip_first: HashSet, +} + +#[derive(Debug, Clone, Copy)] +pub enum InsertPosition { + Before, + After, } impl MutableBody { @@ -60,9 +70,14 @@ impl MutableBody { blocks: body.blocks, var_debug_info: body.var_debug_info, span: body.span, + skip_first: HashSet::new(), } } + pub fn skip_first(&self, bb_idx: usize) -> bool { + self.skip_first.contains(&bb_idx) + } + /// Create the new body consuming this mutable body. pub fn into(self) -> Body { Body::new( @@ -99,12 +114,13 @@ impl MutableBody { from: Operand, pointee_ty: Ty, mutability: Mutability, - before: &mut SourceInstruction, + where_to: &mut SourceInstruction, + insert_position: InsertPosition, ) -> Local { assert!(from.ty(self.locals()).unwrap().kind().is_raw_ptr()); let target_ty = Ty::new_ptr(pointee_ty, mutability); let rvalue = Rvalue::Cast(CastKind::PtrToPtr, from, target_ty); - self.new_assignment(rvalue, before) + self.new_assignment(rvalue, where_to, insert_position) } /// Transmute to a raw pointer of `*mut type` and return a new local where that value is stored. @@ -113,12 +129,13 @@ impl MutableBody { from: Operand, pointee_ty: Ty, mutability: Mutability, - before: &mut SourceInstruction, + where_to: &mut SourceInstruction, + insert_position: InsertPosition, ) -> Local { assert!(from.ty(self.locals()).unwrap().kind().is_raw_ptr()); let target_ty = Ty::new_ptr(pointee_ty, mutability); let rvalue = Rvalue::Cast(CastKind::Transmute, from, target_ty); - self.new_assignment(rvalue, before) + self.new_assignment(rvalue, where_to, insert_position) } /// Add a new assignment for the given binary operation. @@ -129,21 +146,27 @@ impl MutableBody { bin_op: BinOp, lhs: Operand, rhs: Operand, - before: &mut SourceInstruction, + where_to: &mut SourceInstruction, + insert_position: InsertPosition, ) -> Local { let rvalue = Rvalue::BinaryOp(bin_op, lhs, rhs); - self.new_assignment(rvalue, before) + self.new_assignment(rvalue, where_to, insert_position) } /// Add a new assignment. /// /// Return local where the result is saved. - pub fn new_assignment(&mut self, rvalue: Rvalue, before: &mut SourceInstruction) -> Local { - let span = before.span(&self.blocks); + pub fn new_assignment( + &mut self, + rvalue: Rvalue, + where_to: &mut SourceInstruction, + insert_position: InsertPosition, + ) -> Local { + let span = where_to.span(&self.blocks); let ret_ty = rvalue.ty(&self.locals).unwrap(); let result = self.new_local(ret_ty, span, Mutability::Not); let stmt = Statement { kind: StatementKind::Assign(Place::from(result), rvalue), span }; - self.insert_stmt(stmt, before); + self.insert_stmt(stmt, where_to, insert_position); result } @@ -157,6 +180,7 @@ impl MutableBody { tcx: TyCtxt, check_type: &CheckType, source: &mut SourceInstruction, + insert_position: InsertPosition, value: Local, msg: &str, ) { @@ -186,7 +210,7 @@ impl MutableBody { unwind: UnwindAction::Terminate, }; let terminator = Terminator { kind, span }; - self.split_bb(source, terminator); + self.split_bb(source, insert_position, terminator); } CheckType::Panic | CheckType::NoCore => { tcx.sess @@ -209,6 +233,7 @@ impl MutableBody { &mut self, callee: &Instance, source: &mut SourceInstruction, + insert_position: InsertPosition, args: Vec, destination: Place, ) { @@ -224,45 +249,128 @@ impl MutableBody { unwind: UnwindAction::Terminate, }; let terminator = Terminator { kind, span }; - self.split_bb(source, terminator); + self.split_bb(source, insert_position, terminator); } /// Split a basic block right before the source location and use the new terminator /// in the basic block that was split. /// /// The source is updated to point to the same instruction which is now in the new basic block. - pub fn split_bb(&mut self, source: &mut SourceInstruction, new_term: Terminator) { + pub fn split_bb( + &mut self, + source: &mut SourceInstruction, + insert_position: InsertPosition, + new_term: Terminator, + ) { let new_bb_idx = self.blocks.len(); - let (idx, bb) = match source { - SourceInstruction::Statement { idx, bb } => { - let (orig_idx, orig_bb) = (*idx, *bb); - *idx = 0; - *bb = new_bb_idx; - (orig_idx, orig_bb) + match insert_position { + InsertPosition::Before => { + let (idx, bb) = match source { + SourceInstruction::Statement { idx, bb } => { + let (orig_idx, orig_bb) = (*idx, *bb); + *idx = 0; + *bb = new_bb_idx; + (orig_idx, orig_bb) + } + SourceInstruction::Terminator { bb } => { + let (orig_idx, orig_bb) = (self.blocks[*bb].statements.len(), *bb); + *bb = new_bb_idx; + (orig_idx, orig_bb) + } + }; + let old_term = mem::replace(&mut self.blocks[bb].terminator, new_term); + let bb_stmts = &mut self.blocks[bb].statements; + let remaining = bb_stmts.split_off(idx); + let new_bb = BasicBlock { statements: remaining, terminator: old_term }; + self.blocks.push(new_bb); + self.skip_first.insert(new_bb_idx); } - SourceInstruction::Terminator { bb } => { - let orig_bb = *bb; - *bb = new_bb_idx; - (self.blocks[orig_bb].statements.len(), orig_bb) + InsertPosition::After => { + let span = source.span(&self.blocks); + match source { + SourceInstruction::Statement { idx, bb } => { + let (orig_idx, orig_bb) = (*idx, *bb); + *idx = 0; + *bb = new_bb_idx; + let old_term = mem::replace(&mut self.blocks[orig_bb].terminator, new_term); + let bb_stmts = &mut self.blocks[orig_bb].statements; + let remaining = bb_stmts.split_off(orig_idx + 1); + let new_bb = BasicBlock { statements: remaining, terminator: old_term }; + self.blocks.push(new_bb); + } + SourceInstruction::Terminator { bb } => { + let current_terminator = &mut self.blocks.get_mut(*bb).unwrap().terminator; + // Kani can only instrument function calls in this way. + match &mut current_terminator.kind { + TerminatorKind::Call { target: Some(target_bb), .. } => { + *bb = new_bb_idx; + let new_bb = BasicBlock { + statements: vec![], + terminator: Terminator { + kind: TerminatorKind::Goto { target: *target_bb }, + span, + }, + }; + *target_bb = new_bb_idx; + self.blocks.push(new_bb); + } + _ => unimplemented!("Kani can only split blocks after calls."), + }; + } + }; } - }; - let old_term = mem::replace(&mut self.blocks[bb].terminator, new_term); - let bb_stmts = &mut self.blocks[bb].statements; - let remaining = bb_stmts.split_off(idx); - let new_bb = BasicBlock { statements: remaining, terminator: old_term }; - self.blocks.push(new_bb); + } } /// Insert statement before the source instruction and update the source as needed. - pub fn insert_stmt(&mut self, new_stmt: Statement, before: &mut SourceInstruction) { - match before { - SourceInstruction::Statement { idx, bb } => { - self.blocks[*bb].statements.insert(*idx, new_stmt); - *idx += 1; + pub fn insert_stmt( + &mut self, + new_stmt: Statement, + where_to: &mut SourceInstruction, + insert_position: InsertPosition, + ) { + match insert_position { + InsertPosition::Before => { + match where_to { + SourceInstruction::Statement { idx, bb } => { + self.blocks[*bb].statements.insert(*idx, new_stmt); + *idx += 1; + } + SourceInstruction::Terminator { bb } => { + // Append statements at the end of the basic block. + self.blocks[*bb].statements.push(new_stmt); + } + } } - SourceInstruction::Terminator { bb } => { - // Append statements at the end of the basic block. - self.blocks[*bb].statements.push(new_stmt); + InsertPosition::After => { + let new_bb_idx = self.blocks.len(); + let span = where_to.span(&self.blocks); + match where_to { + SourceInstruction::Statement { idx, bb } => { + self.blocks[*bb].statements.insert(*idx + 1, new_stmt); + *idx += 1; + } + SourceInstruction::Terminator { bb } => { + // Create a new basic block, as we need to append a statement after the terminator. + let current_terminator = &mut self.blocks.get_mut(*bb).unwrap().terminator; + // Kani can only instrument function calls in this way. + match &mut current_terminator.kind { + TerminatorKind::Call { target: Some(target_bb), .. } => { + *where_to = SourceInstruction::Statement { idx: 0, bb: new_bb_idx }; + let new_bb = BasicBlock { + statements: vec![new_stmt], + terminator: Terminator { + kind: TerminatorKind::Goto { target: *target_bb }, + span, + }, + }; + *target_bb = new_bb_idx; + self.blocks.push(new_bb); + } + _ => unimplemented!("Kani can only insert statements after calls."), + }; + } + } } } } diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index fc37fab6882d..1558993a36ff 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -6,9 +6,12 @@ use crate::args::ExtraChecks; use crate::kani_middle::find_fn_def; -use crate::kani_middle::transform::body::{CheckType, MutableBody, SourceInstruction}; +use crate::kani_middle::transform::body::{ + CheckType, InsertPosition, MutableBody, SourceInstruction, +}; use crate::kani_middle::transform::{TransformPass, TransformationType}; use crate::kani_queries::QueryDb; +use itertools::Itertools; use rustc_middle::ty::TyCtxt; use stable_mir::mir::mono::Instance; use stable_mir::mir::{AggregateKind, Body, Constant, Mutability, Operand, Place, Rvalue}; @@ -22,7 +25,15 @@ mod uninit_visitor; use ty_layout::TypeLayout; use uninit_visitor::{CheckUninitVisitor, InitRelevantInstruction, SourceOp}; -const UNINIT_ALLOWLIST: &[&str] = &["kani::shadow"]; +const UNINIT_ALLOWLIST: &[&str] = &[ + "kani::shadow::__kani_global_sm_get_inner", + "kani::shadow::__kani_global_sm_set_inner", + "kani::shadow::__kani_global_sm_get", + "kani::shadow::__kani_global_sm_set", + "kani::shadow::__kani_global_sm_get_slice", + "kani::shadow::__kani_global_sm_set_slice", + "std::alloc::alloc", +]; /// Instrument the code with checks for uninitialized memory. #[derive(Debug)] @@ -49,7 +60,12 @@ impl TransformPass for UninitPass { fn transform(&self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { trace!(function=?instance.name(), "transform"); - if UNINIT_ALLOWLIST.iter().any(|allowlist_item| instance.name().contains(allowlist_item)) { + let name_without_generics = instance + .name() + .split("::") + .filter(|chunk| !chunk.starts_with("<") && !chunk.ends_with(">")) + .join("::"); + if UNINIT_ALLOWLIST.iter().any(|allowlist_item| name_without_generics == *allowlist_item) { return (false, body); } @@ -59,7 +75,7 @@ impl TransformPass for UninitPass { let mut bb_idx = 0; while bb_idx < new_body.blocks().len() { if let Some(candidate) = - CheckUninitVisitor::find_next(&new_body, bb_idx, bb_idx >= orig_len) + CheckUninitVisitor::find_next(&new_body, bb_idx, new_body.skip_first(bb_idx)) { self.build_check(tcx, &mut new_body, candidate); bb_idx += 1 @@ -79,18 +95,29 @@ impl UninitPass { instruction: InitRelevantInstruction, ) { debug!(?instruction, "build_check"); - let mut source = instruction.source; for operation in instruction.operations { - let place = match &operation { - SourceOp::Get { place, .. } | SourceOp::Set { place, .. } => place, - SourceOp::Unsupported { instruction, place } => { + let mut source = instruction.source; + match &operation { + SourceOp::Unsupported { unsupported_instruction, place } => { let place_ty = place.ty(body.locals()).unwrap(); let reason = format!( - "Kani currently doesn't support checking memory initialization using instruction `{instruction}` for type `{place_ty}`", + "Kani currently doesn't support checking memory initialization using instruction `{unsupported_instruction}` for type `{place_ty}`", ); self.unsupported_check(tcx, body, &mut source, &reason); continue; } + _ => {} + }; + + let insert_position = match &operation { + SourceOp::Get { .. } => InsertPosition::Before, + SourceOp::Set { .. } => InsertPosition::After, + SourceOp::Unsupported { .. } => unreachable!(), + }; + + let place = match &operation { + SourceOp::Get { place, .. } | SourceOp::Set { place, .. } => place, + SourceOp::Unsupported { .. } => unreachable!(), }; let place_ty = place.ty(body.locals()).unwrap(); @@ -138,6 +165,7 @@ impl UninitPass { .collect(), ), &mut source, + insert_position, ), projection: vec![], }; @@ -149,12 +177,14 @@ impl UninitPass { Ty::from_rigid_kind(RigidTy::Slice(Ty::new_tuple(&[]))), Mutability::Not, &mut source, + insert_position, ), _ => body.new_cast_ptr( Operand::Copy(place.clone()), Ty::new_tuple(&[]), Mutability::Not, &mut source, + insert_position, ), }; @@ -201,6 +231,7 @@ impl UninitPass { body.add_call( &shadow_memory_get, &mut source, + insert_position, vec![ Operand::Copy(Place { local: ptr_local, projection: vec![] }), Operand::Move(layout_place), @@ -212,6 +243,7 @@ impl UninitPass { tcx, &self.check_type, &mut source, + insert_position, ret_place.local, &format!("Undefined Behavior: Reading from an uninitialized pointer of type `{place_ty}`"), ) @@ -257,6 +289,7 @@ impl UninitPass { body.add_call( &shadow_memory_set, &mut source, + insert_position, vec![ Operand::Copy(Place { local: ptr_local, projection: vec![] }), Operand::Move(layout_place), @@ -290,7 +323,7 @@ impl UninitPass { span, user_ty: None, })); - let result = body.new_assignment(rvalue, source); - body.add_check(tcx, &self.check_type, source, result, reason); + let result = body.new_assignment(rvalue, source, InsertPosition::Before); + body.add_check(tcx, &self.check_type, source, InsertPosition::Before, result, reason); } } diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs index 282bc193aaa6..2d76cda520e2 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs @@ -37,7 +37,9 @@ impl TypeLayout { size: match ty.layout().unwrap().shape().fields { FieldsShape::Array { stride, count } if count == 0 => stride, _ => { - return Err(format!("Unsupported DST for invalid memory check: `{ty}`")); + return Err(format!( + "Unsupported DST for invalid memory check: `{ty}`" + )); } }, }; diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index b4a4e2bdc17a..fd0296bd51a3 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -12,7 +12,7 @@ use strum_macros::AsRefStr; pub enum SourceOp { Get { place: Place, count: Operand }, Set { place: Place, count: Operand, value: bool }, - Unsupported { instruction: String, place: Place }, + Unsupported { unsupported_instruction: String, place: Place }, } #[derive(Clone, Debug)] @@ -163,7 +163,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { self.current = SourceInstruction::Terminator { bb: self.bb }; // Leave it as an exhaustive match to be notified when a new kind is added. match &term.kind { - TerminatorKind::Call { func, args, .. } => { + TerminatorKind::Call { func, args, destination, .. } => { self.super_terminator(term, location); let instance = expect_instance(self.locals, func); match instance.kind { @@ -218,10 +218,26 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { if instance.is_foreign_item() { match instance.name().as_str() { /* TODO: implement those */ - "alloc::alloc::__rust_alloc" => {} - "alloc::alloc::__rust_realloc" => {} - "alloc::alloc::__rust_alloc_zeroed" => {} - "alloc::alloc::__rust_dealloc" => {} + "alloc::alloc::__rust_alloc" + | "alloc::alloc::__rust_realloc" => { + /* Memory is uninitialized, nothing to do here. */ + } + "alloc::alloc::__rust_alloc_zeroed" => { + /* Memory is initialized here, need to update shadow memory. */ + self.push_target(SourceOp::Set { + place: destination.clone(), + count: args[0].clone(), + value: true, + }); + } + "alloc::alloc::__rust_dealloc" => { + /* Memory is uninitialized here, need to update shadow memory. */ + self.push_target(SourceOp::Set { + place: expect_place(&args[0]).clone(), + count: args[1].clone(), + value: false, + }); + } _ => {} } } @@ -270,7 +286,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { && (!ptx.is_mutating() || place.projection.len() > idx + 1) { self.push_target(SourceOp::Unsupported { - instruction: "union access".to_string(), + unsupported_instruction: "union access".to_string(), place: intermediate_place.clone(), }); } diff --git a/kani-compiler/src/kani_middle/transform/check_values.rs b/kani-compiler/src/kani_middle/transform/check_values.rs index 2c05c07be7f2..ec18d0be31da 100644 --- a/kani-compiler/src/kani_middle/transform/check_values.rs +++ b/kani-compiler/src/kani_middle/transform/check_values.rs @@ -14,7 +14,9 @@ //! 1. We could merge the invalid values by the offset. //! 2. We could avoid checking places that have been checked before. use crate::args::ExtraChecks; -use crate::kani_middle::transform::body::{CheckType, MutableBody, SourceInstruction}; +use crate::kani_middle::transform::body::{ + CheckType, InsertPosition, MutableBody, SourceInstruction, +}; use crate::kani_middle::transform::check_values::SourceOp::UnsupportedCheck; use crate::kani_middle::transform::{TransformPass, TransformationType}; use crate::kani_queries::QueryDb; @@ -82,13 +84,20 @@ impl ValidValuePass { for operation in instruction.operations { match operation { SourceOp::BytesValidity { ranges, target_ty, rvalue } => { - let value = body.new_assignment(rvalue, &mut source); + let value = body.new_assignment(rvalue, &mut source, InsertPosition::Before); let rvalue_ptr = Rvalue::AddressOf(Mutability::Not, Place::from(value)); for range in ranges { let result = build_limits(body, &range, rvalue_ptr.clone(), &mut source); let msg = format!("Undefined Behavior: Invalid value of type `{target_ty}`",); - body.add_check(tcx, &self.check_type, &mut source, result, &msg); + body.add_check( + tcx, + &self.check_type, + &mut source, + InsertPosition::Before, + result, + &msg, + ); } } SourceOp::DerefValidity { pointee_ty, rvalue, ranges } => { @@ -96,7 +105,14 @@ impl ValidValuePass { let result = build_limits(body, &range, rvalue.clone(), &mut source); let msg = format!("Undefined Behavior: Invalid value of type `{pointee_ty}`",); - body.add_check(tcx, &self.check_type, &mut source, result, &msg); + body.add_check( + tcx, + &self.check_type, + &mut source, + InsertPosition::Before, + result, + &msg, + ); } } SourceOp::UnsupportedCheck { check, ty } => { @@ -122,8 +138,8 @@ impl ValidValuePass { span, user_ty: None, })); - let result = body.new_assignment(rvalue, source); - body.add_check(tcx, &self.check_type, source, result, reason); + let result = body.new_assignment(rvalue, source, InsertPosition::Before); + body.add_check(tcx, &self.check_type, source, InsertPosition::Before, result, reason); } } @@ -755,30 +771,60 @@ pub fn build_limits( let start_const = body.new_const_operand(req.valid_range.start, primitive_ty, span); let end_const = body.new_const_operand(req.valid_range.end, primitive_ty, span); let orig_ptr = if req.offset != 0 { - let start_ptr = move_local(body.new_assignment(rvalue_ptr, source)); + let start_ptr = move_local(body.new_assignment(rvalue_ptr, source, InsertPosition::Before)); let byte_ptr = move_local(body.new_cast_ptr( start_ptr, Ty::unsigned_ty(UintTy::U8), Mutability::Not, source, + InsertPosition::Before, )); let offset_const = body.new_const_operand(req.offset as _, UintTy::Usize, span); - let offset = move_local(body.new_assignment(Rvalue::Use(offset_const), source)); - move_local(body.new_binary_op(BinOp::Offset, byte_ptr, offset, source)) + let offset = move_local(body.new_assignment( + Rvalue::Use(offset_const), + source, + InsertPosition::Before, + )); + move_local(body.new_binary_op( + BinOp::Offset, + byte_ptr, + offset, + source, + InsertPosition::Before, + )) } else { - move_local(body.new_assignment(rvalue_ptr, source)) + move_local(body.new_assignment(rvalue_ptr, source, InsertPosition::Before)) }; - let value_ptr = - body.new_cast_ptr(orig_ptr, Ty::unsigned_ty(primitive_ty), Mutability::Not, source); + let value_ptr = body.new_cast_ptr( + orig_ptr, + Ty::unsigned_ty(primitive_ty), + Mutability::Not, + source, + InsertPosition::Before, + ); let value = Operand::Copy(Place { local: value_ptr, projection: vec![ProjectionElem::Deref] }); - let start_result = body.new_binary_op(BinOp::Ge, value.clone(), start_const, source); - let end_result = body.new_binary_op(BinOp::Le, value, end_const, source); + let start_result = + body.new_binary_op(BinOp::Ge, value.clone(), start_const, source, InsertPosition::Before); + let end_result = + body.new_binary_op(BinOp::Le, value, end_const, source, InsertPosition::Before); if req.valid_range.wraps_around() { // valid >= start || valid <= end - body.new_binary_op(BinOp::BitOr, move_local(start_result), move_local(end_result), source) + body.new_binary_op( + BinOp::BitOr, + move_local(start_result), + move_local(end_result), + source, + InsertPosition::Before, + ) } else { // valid >= start && valid <= end - body.new_binary_op(BinOp::BitAnd, move_local(start_result), move_local(end_result), source) + body.new_binary_op( + BinOp::BitAnd, + move_local(start_result), + move_local(end_result), + source, + InsertPosition::Before, + ) } } diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index 6354e3777aca..9411e1e780fa 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -8,7 +8,9 @@ //! by the transformation. use crate::kani_middle::attributes::matches_diagnostic; -use crate::kani_middle::transform::body::{CheckType, MutableBody, SourceInstruction}; +use crate::kani_middle::transform::body::{ + CheckType, InsertPosition, MutableBody, SourceInstruction, +}; use crate::kani_middle::transform::check_values::{build_limits, ty_validity_per_offset}; use crate::kani_middle::transform::{TransformPass, TransformationType}; use crate::kani_queries::QueryDb; @@ -86,7 +88,7 @@ impl IntrinsicGeneratorPass { })), ); let stmt = Statement { kind: assign, span }; - new_body.insert_stmt(stmt, &mut terminator); + new_body.insert_stmt(stmt, &mut terminator, InsertPosition::Before); let machine_info = MachineInfo::target(); // The first and only argument type. @@ -110,7 +112,7 @@ impl IntrinsicGeneratorPass { ); let assign = StatementKind::Assign(Place::from(ret_var), rvalue); let stmt = Statement { kind: assign, span }; - new_body.insert_stmt(stmt, &mut terminator); + new_body.insert_stmt(stmt, &mut terminator, InsertPosition::Before); } } Err(msg) => { @@ -120,11 +122,19 @@ impl IntrinsicGeneratorPass { span, user_ty: None, })); - let result = new_body.new_assignment(rvalue, &mut terminator); + let result = + new_body.new_assignment(rvalue, &mut terminator, InsertPosition::Before); let reason = format!( "Kani currently doesn't support checking validity of `{target_ty}`. {msg}" ); - new_body.add_check(tcx, &self.check_type, &mut terminator, result, &reason); + new_body.add_check( + tcx, + &self.check_type, + &mut terminator, + InsertPosition::Before, + result, + &reason, + ); } } new_body.into() diff --git a/library/kani/src/shadow.rs b/library/kani/src/shadow.rs index 0ac28e384f58..06f07cdf1bb2 100644 --- a/library/kani/src/shadow.rs +++ b/library/kani/src/shadow.rs @@ -83,16 +83,18 @@ impl ShadowMem { } /// Global shadow memory object. -pub static mut GLOBAL_SM: ShadowMem = ShadowMem::new(false); +static mut __KANI_GLOBAL_SM: ShadowMem = ShadowMem::new(false); // Get initialization setate of `n` items laid out according to the `layout` starting at address `ptr`. -fn global_sm_get_inner(ptr: *const (), layout: [bool; N], n: usize) -> bool { +fn __kani_global_sm_get_inner(ptr: *const (), layout: [bool; N], n: usize) -> bool { let mut count: usize = 0; while count < n { let mut offset: usize = 0; while offset < N { unsafe { - if layout[offset] && !GLOBAL_SM.get((ptr as *const u8).add(count * N + offset)) { + if layout[offset] + && !__KANI_GLOBAL_SM.get((ptr as *const u8).add(count * N + offset)) + { return false; } offset += 1; @@ -104,13 +106,19 @@ fn global_sm_get_inner(ptr: *const (), layout: [bool; N], n: usi } // Set initialization setate to `value` for `n` items laid out according to the `layout` starting at address `ptr`. -fn global_sm_set_inner(ptr: *const (), layout: [bool; N], n: usize, value: bool) { +fn __kani_global_sm_set_inner( + ptr: *const (), + layout: [bool; N], + n: usize, + value: bool, +) { let mut count: usize = 0; while count < n { let mut offset: usize = 0; while offset < N { unsafe { - GLOBAL_SM.set((ptr as *const u8).add(count * N + offset), value && layout[offset]); + __KANI_GLOBAL_SM + .set((ptr as *const u8).add(count * N + offset), value && layout[offset]); } offset += 1; } @@ -119,27 +127,36 @@ fn global_sm_set_inner(ptr: *const (), layout: [bool; N], n: usi } #[rustc_diagnostic_item = "KaniShadowMemoryGet"] -pub fn global_sm_get(ptr: *const (), layout: [bool; N], n: usize) -> bool { +pub fn __kani_global_sm_get(ptr: *const (), layout: [bool; N], n: usize) -> bool { let (ptr, _) = ptr.to_raw_parts(); - global_sm_get_inner(ptr, layout, n) + __kani_global_sm_get_inner(ptr, layout, n) } #[rustc_diagnostic_item = "KaniShadowMemorySet"] -pub fn global_sm_set(ptr: *const (), layout: [bool; N], n: usize, value: bool) { +pub fn __kani_global_sm_set( + ptr: *const (), + layout: [bool; N], + n: usize, + value: bool, +) { let (ptr, _) = ptr.to_raw_parts(); - global_sm_set_inner(ptr, layout, n, value); + __kani_global_sm_set_inner(ptr, layout, n, value); } #[rustc_diagnostic_item = "KaniShadowMemoryGetSlice"] -pub fn global_sm_get_slice(ptr: *const [()], layout: [bool; N], n: usize) -> bool { +pub fn __kani_global_sm_get_slice( + ptr: *const [()], + layout: [bool; N], + n: usize, +) -> bool { let (ptr, meta) = ptr.to_raw_parts(); // The pointee type is a DST, more than `n` objects can be accessed. let n = n * meta; - global_sm_get_inner(ptr, layout, n) + __kani_global_sm_get_inner(ptr, layout, n) } #[rustc_diagnostic_item = "KaniShadowMemorySetSlice"] -pub fn global_sm_set_slice( +pub fn __kani_global_sm_set_slice( ptr: *const [()], layout: [bool; N], n: usize, @@ -148,5 +165,5 @@ pub fn global_sm_set_slice( let (ptr, meta) = ptr.to_raw_parts(); // The pointee type is a DST, more than `n` objects can be accessed. let n = n * meta; - global_sm_set_inner(ptr, layout, n, value); + __kani_global_sm_set_inner(ptr, layout, n, value); } diff --git a/tests/uninit/alloc-zeroed.rs b/tests/uninit/alloc-diagnostic-zeroed.rs similarity index 100% rename from tests/uninit/alloc-zeroed.rs rename to tests/uninit/alloc-diagnostic-zeroed.rs diff --git a/tests/uninit/fixme/transmute-pair-uninit.rs b/tests/uninit/fixme/transmute-pair-uninit.rs index 025360298771..28351c4fd8ea 100644 --- a/tests/uninit/fixme/transmute-pair-uninit.rs +++ b/tests/uninit/fixme/transmute-pair-uninit.rs @@ -27,4 +27,4 @@ fn main() { if v == 0 { println!("it is zero"); } -} \ No newline at end of file +} From 1006fbe82fc82ca5dda93bb046fe86fa705120b2 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Fri, 14 Jun 2024 09:26:27 -0700 Subject: [PATCH 05/87] Move tests under tests/kani --- tests/kani/Uninit/access-padding-init.rs | 16 ++++++++++++++++ tests/kani/Uninit/access-padding-uninit.rs | 16 ++++++++++++++++ .../Uninit/alloc-to-slice.rs} | 1 + .../Uninit/alloc-zeroed-to-slice.rs} | 1 + .../Uninit/fixme/partially-maybe-uninit.rs} | 0 .../Uninit}/fixme/transmute-pair-uninit.rs | 0 .../Uninit/struct-padding-and-arr-init.rs} | 6 +++--- .../Uninit/struct-padding-and-arr-uninit.rs} | 0 .../Uninit/vec-read-init.rs} | 3 ++- .../Uninit/vec-read-semi-init.rs} | 2 +- .../Uninit/vec-read-uninit.rs} | 0 11 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 tests/kani/Uninit/access-padding-init.rs create mode 100644 tests/kani/Uninit/access-padding-uninit.rs rename tests/{uninit/alloc-diagnostic.rs => kani/Uninit/alloc-to-slice.rs} (92%) rename tests/{uninit/alloc-diagnostic-zeroed.rs => kani/Uninit/alloc-zeroed-to-slice.rs} (93%) rename tests/{uninit/fixme/partially_uninit.rs => kani/Uninit/fixme/partially-maybe-uninit.rs} (100%) rename tests/{uninit => kani/Uninit}/fixme/transmute-pair-uninit.rs (100%) rename tests/{uninit/after-aggregate-assign-init.rs => kani/Uninit/struct-padding-and-arr-init.rs} (78%) rename tests/{uninit/after-aggregate-assign-uninit.rs => kani/Uninit/struct-padding-and-arr-uninit.rs} (100%) rename tests/{uninit/byte-read-init.rs => kani/Uninit/vec-read-init.rs} (55%) rename tests/{uninit/byte-read-semi-init.rs => kani/Uninit/vec-read-semi-init.rs} (78%) rename tests/{uninit/byte-read-uninit.rs => kani/Uninit/vec-read-uninit.rs} (100%) diff --git a/tests/kani/Uninit/access-padding-init.rs b/tests/kani/Uninit/access-padding-init.rs new file mode 100644 index 000000000000..8e86873a7bf2 --- /dev/null +++ b/tests/kani/Uninit/access-padding-init.rs @@ -0,0 +1,16 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z ghost-state -Z uninit-checks + +use std::ptr::addr_of; + +#[repr(C)] +struct S(u32, u8); + +#[kani::proof] +fn main() { + let s = S(0, 0); + let ptr: *const u8 = addr_of!(s) as *const u8; + let padding = unsafe { *(ptr.add(4)) }; +} + diff --git a/tests/kani/Uninit/access-padding-uninit.rs b/tests/kani/Uninit/access-padding-uninit.rs new file mode 100644 index 000000000000..75804519d6b2 --- /dev/null +++ b/tests/kani/Uninit/access-padding-uninit.rs @@ -0,0 +1,16 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z ghost-state -Z uninit-checks + +use std::ptr::addr_of; + +#[repr(C)] +struct S(u32, u8); + +#[kani::proof] +fn main() { + let s = S(0, 0); + let ptr: *const u8 = addr_of!(s) as *const u8; + let padding = unsafe { *(ptr.add(5)) }; +} + diff --git a/tests/uninit/alloc-diagnostic.rs b/tests/kani/Uninit/alloc-to-slice.rs similarity index 92% rename from tests/uninit/alloc-diagnostic.rs rename to tests/kani/Uninit/alloc-to-slice.rs index 75cf0c085698..0b93ec0e7f8b 100644 --- a/tests/uninit/alloc-diagnostic.rs +++ b/tests/kani/Uninit/alloc-to-slice.rs @@ -18,6 +18,7 @@ fn main() { *ptr.add(2) = 0x43; *ptr.add(3) = 0x44; *ptr.add(16) = 0x00; + // Forming a slice from unitialized memory is UB. let slice1 = from_raw_parts(ptr, 16); let slice2 = from_raw_parts(ptr.add(16), 16); drop(slice1.cmp(slice2)); diff --git a/tests/uninit/alloc-diagnostic-zeroed.rs b/tests/kani/Uninit/alloc-zeroed-to-slice.rs similarity index 93% rename from tests/uninit/alloc-diagnostic-zeroed.rs rename to tests/kani/Uninit/alloc-zeroed-to-slice.rs index 7dc7f0e5296f..aab656ed7167 100644 --- a/tests/uninit/alloc-diagnostic-zeroed.rs +++ b/tests/kani/Uninit/alloc-zeroed-to-slice.rs @@ -11,6 +11,7 @@ use std::slice::from_raw_parts; fn main() { let layout = Layout::from_size_align(32, 8).unwrap(); unsafe { + // This returns initialized memory. let ptr = alloc_zeroed(layout); *ptr = 0x41; *ptr.add(1) = 0x42; diff --git a/tests/uninit/fixme/partially_uninit.rs b/tests/kani/Uninit/fixme/partially-maybe-uninit.rs similarity index 100% rename from tests/uninit/fixme/partially_uninit.rs rename to tests/kani/Uninit/fixme/partially-maybe-uninit.rs diff --git a/tests/uninit/fixme/transmute-pair-uninit.rs b/tests/kani/Uninit/fixme/transmute-pair-uninit.rs similarity index 100% rename from tests/uninit/fixme/transmute-pair-uninit.rs rename to tests/kani/Uninit/fixme/transmute-pair-uninit.rs diff --git a/tests/uninit/after-aggregate-assign-init.rs b/tests/kani/Uninit/struct-padding-and-arr-init.rs similarity index 78% rename from tests/uninit/after-aggregate-assign-init.rs rename to tests/kani/Uninit/struct-padding-and-arr-init.rs index 02c1cf6854cb..46736e52b2f8 100644 --- a/tests/uninit/after-aggregate-assign-init.rs +++ b/tests/kani/Uninit/struct-padding-and-arr-init.rs @@ -23,9 +23,9 @@ fn main() { sptr = ptr::addr_of_mut!(s); sptr2 = sptr as *mut [u8; 4]; *sptr2 = [0; 4]; - *sptr = S(0, 0); // should reset the padding - _val = *sptr2; // should hence be UB - //~^ERROR: encountered uninitialized memory + *sptr = S(0, 0); + // Both S(u16, u16) and [u8; 4] have the same layout, so the memory is initialized. + _val = *sptr2; Return() } } diff --git a/tests/uninit/after-aggregate-assign-uninit.rs b/tests/kani/Uninit/struct-padding-and-arr-uninit.rs similarity index 100% rename from tests/uninit/after-aggregate-assign-uninit.rs rename to tests/kani/Uninit/struct-padding-and-arr-uninit.rs diff --git a/tests/uninit/byte-read-init.rs b/tests/kani/Uninit/vec-read-init.rs similarity index 55% rename from tests/uninit/byte-read-init.rs rename to tests/kani/Uninit/vec-read-init.rs index ab5f7e9fdda2..02e42c8754b9 100644 --- a/tests/uninit/byte-read-init.rs +++ b/tests/kani/Uninit/vec-read-init.rs @@ -6,6 +6,7 @@ fn main() { let mut v: Vec = Vec::with_capacity(10); unsafe { *v.as_mut_ptr().add(5) = 0x42 }; - let def = unsafe { *v.as_ptr().add(5) }; + let def = unsafe { *v.as_ptr().add(5) }; // Not UB since accessing initialized memory. let x = def + 1; + // However, uninit memory is read on drop_in_place; not clear whether this should count as UB (MIRI doesn't catch it). } diff --git a/tests/uninit/byte-read-semi-init.rs b/tests/kani/Uninit/vec-read-semi-init.rs similarity index 78% rename from tests/uninit/byte-read-semi-init.rs rename to tests/kani/Uninit/vec-read-semi-init.rs index 3bcbf0705cb6..ba6be2e12619 100644 --- a/tests/uninit/byte-read-semi-init.rs +++ b/tests/kani/Uninit/vec-read-semi-init.rs @@ -7,6 +7,6 @@ fn main() { let mut v: Vec = Vec::with_capacity(10); unsafe { *v.as_mut_ptr().add(4) = 0x42 }; - let def = unsafe { *v.as_ptr().add(5) }; + let def = unsafe { *v.as_ptr().add(5) }; // Accessing uninit memory here. let x = def + 1; } diff --git a/tests/uninit/byte-read-uninit.rs b/tests/kani/Uninit/vec-read-uninit.rs similarity index 100% rename from tests/uninit/byte-read-uninit.rs rename to tests/kani/Uninit/vec-read-uninit.rs From fc224def170658f7d48037058092f31a44fb1903 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Fri, 14 Jun 2024 09:27:00 -0700 Subject: [PATCH 06/87] Implement drop blessing --- .../kani_middle/transform/check_uninit/mod.rs | 15 ++++++++-- .../transform/check_uninit/uninit_visitor.rs | 28 +++++++++++++++++-- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index 1558993a36ff..3b75cc3e22ad 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -112,11 +112,20 @@ impl UninitPass { let insert_position = match &operation { SourceOp::Get { .. } => InsertPosition::Before, SourceOp::Set { .. } => InsertPosition::After, + SourceOp::BlessDrop { .. } => InsertPosition::Before, SourceOp::Unsupported { .. } => unreachable!(), }; let place = match &operation { SourceOp::Get { place, .. } | SourceOp::Set { place, .. } => place, + SourceOp::BlessDrop { place, .. } => &Place { + local: body.new_assignment( + Rvalue::AddressOf(Mutability::Not, place.clone()), + &mut source, + insert_position, + ), + projection: vec![], + }, SourceOp::Unsupported { .. } => unreachable!(), }; @@ -143,7 +152,9 @@ impl UninitPass { }; let count = match &operation { - SourceOp::Get { count, .. } | SourceOp::Set { count, .. } => count.clone(), + SourceOp::Get { count, .. } + | SourceOp::Set { count, .. } + | SourceOp::BlessDrop { count, .. } => count.clone(), SourceOp::Unsupported { .. } => unreachable!(), }; @@ -248,7 +259,7 @@ impl UninitPass { &format!("Undefined Behavior: Reading from an uninitialized pointer of type `{place_ty}`"), ) } - SourceOp::Set { value, .. } => { + SourceOp::Set { value, .. } | SourceOp::BlessDrop { value, .. } => { let shadow_memory_set = match pointee_ty.kind() { TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => { Instance::resolve( diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index fd0296bd51a3..b4eef2cb796f 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -3,7 +3,7 @@ use stable_mir::mir::mono::{Instance, InstanceKind}; use stable_mir::mir::visit::{Location, PlaceContext}; use stable_mir::mir::{ BasicBlockIdx, Constant, LocalDecl, MirVisitor, Mutability, NonDivergingIntrinsic, Operand, - Place, ProjectionElem, Statement, StatementKind, Terminator, TerminatorKind, + Place, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, }; use stable_mir::ty::{Const, RigidTy, Span, TyKind, UintTy}; use strum_macros::AsRefStr; @@ -12,6 +12,7 @@ use strum_macros::AsRefStr; pub enum SourceOp { Get { place: Place, count: Operand }, Set { place: Place, count: Operand, value: bool }, + BlessDrop { place: Place, count: Operand, value: bool }, Unsupported { unsupported_instruction: String, place: Place }, } @@ -123,7 +124,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { StatementKind::Assign(place, rvalue) => { // First check rvalue. self.visit_rvalue(rvalue, location); - // Then check the destination place. + // Check whether we are assigning into a dereference (*ptr = _). if let Some(place_without_deref) = try_remove_topmost_deref(place) { if place_without_deref.ty(&self.locals).unwrap().kind().is_raw_ptr() { self.push_target(SourceOp::Set { @@ -133,6 +134,19 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { }); } } + // Check whether Rvalue creates a new initialized pointer previously not captured inside shadow memory. + if place.ty(&self.locals).unwrap().kind().is_raw_ptr() { + match rvalue { + Rvalue::AddressOf(..) => { + self.push_target(SourceOp::Set { + place: place.clone(), + count: mk_const_operand(1, location.span()), + value: true, + }); + } + _ => {} + } + } } StatementKind::Deinit(place) => { self.super_statement(stmt, location); @@ -247,7 +261,15 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { } TerminatorKind::Drop { place, .. } => { self.super_terminator(term, location); - if place.ty(&self.locals).unwrap().kind().is_raw_ptr() { + let place_ty = place.ty(&self.locals).unwrap(); + // When drop is codegen'ed, a reference is taken to the place which is later implicitly coerced to a pointer. + // Hence, we need to bless this pointer as initialized. + self.push_target(SourceOp::BlessDrop { + place: place.clone(), + count: mk_const_operand(1, location.span()), + value: true, + }); + if place_ty.kind().is_raw_ptr() { self.push_target(SourceOp::Set { place: place.clone(), count: mk_const_operand(1, location.span()), From c755ac98f7c69500dbedf4612314dc3de917cfb5 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Fri, 14 Jun 2024 10:04:03 -0700 Subject: [PATCH 07/87] Fix check insertion order --- .../src/kani_middle/transform/check_uninit/mod.rs | 14 ++++++++++++-- tests/kani/Uninit/fixme/vec-read-drop-uninit.rs | 12 ++++++++++++ tests/kani/Uninit/vec-read-init.rs | 1 - 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 tests/kani/Uninit/fixme/vec-read-drop-uninit.rs diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index 3b75cc3e22ad..044b26fa9e0d 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -95,8 +95,18 @@ impl UninitPass { instruction: InitRelevantInstruction, ) { debug!(?instruction, "build_check"); - for operation in instruction.operations { - let mut source = instruction.source; + let (operations_before, operations_after): (Vec<_>, Vec<_>) = + instruction.operations.into_iter().partition(|operation| match operation { + SourceOp::Get { .. } + | SourceOp::BlessDrop { .. } + | SourceOp::Unsupported { .. } => true, + SourceOp::Set { .. } => false, + }); + let operations: Vec<_> = + vec![operations_before, operations_after].into_iter().flatten().collect(); + + let mut source = instruction.source; + for operation in operations { match &operation { SourceOp::Unsupported { unsupported_instruction, place } => { let place_ty = place.ty(body.locals()).unwrap(); diff --git a/tests/kani/Uninit/fixme/vec-read-drop-uninit.rs b/tests/kani/Uninit/fixme/vec-read-drop-uninit.rs new file mode 100644 index 000000000000..fc770d73aac0 --- /dev/null +++ b/tests/kani/Uninit/fixme/vec-read-drop-uninit.rs @@ -0,0 +1,12 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z ghost-state -Z uninit-checks + +#[kani::proof] +fn main() { + let mut v: Vec = Vec::with_capacity(10); + unsafe { + v.set_len(5); + } + // We would read uninitialized values on drop, but MIRI doesn't seem to complain about it either. +} diff --git a/tests/kani/Uninit/vec-read-init.rs b/tests/kani/Uninit/vec-read-init.rs index 02e42c8754b9..492a49523a01 100644 --- a/tests/kani/Uninit/vec-read-init.rs +++ b/tests/kani/Uninit/vec-read-init.rs @@ -8,5 +8,4 @@ fn main() { unsafe { *v.as_mut_ptr().add(5) = 0x42 }; let def = unsafe { *v.as_ptr().add(5) }; // Not UB since accessing initialized memory. let x = def + 1; - // However, uninit memory is read on drop_in_place; not clear whether this should count as UB (MIRI doesn't catch it). } From 9b2be021d6b783849b643699d743e8a58101e8c6 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Fri, 14 Jun 2024 12:33:46 -0700 Subject: [PATCH 08/87] Autoinitialize extern in alloc::alloc --- .../src/kani_middle/transform/body.rs | 2 +- .../kani_middle/transform/check_uninit/mod.rs | 134 +++++++----------- .../transform/check_uninit/uninit_visitor.rs | 83 ++++++++++- tests/kani/Uninit/access-padding-uninit.rs | 1 + tests/kani/Uninit/vec-read-bad-len.rs | 15 ++ 5 files changed, 146 insertions(+), 89 deletions(-) create mode 100644 tests/kani/Uninit/vec-read-bad-len.rs diff --git a/kani-compiler/src/kani_middle/transform/body.rs b/kani-compiler/src/kani_middle/transform/body.rs index bfb657184886..eb4c4e444526 100644 --- a/kani-compiler/src/kani_middle/transform/body.rs +++ b/kani-compiler/src/kani_middle/transform/body.rs @@ -45,7 +45,7 @@ pub struct MutableBody { skip_first: HashSet, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum InsertPosition { Before, After, diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index 044b26fa9e0d..3553914f1183 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -11,7 +11,6 @@ use crate::kani_middle::transform::body::{ }; use crate::kani_middle::transform::{TransformPass, TransformationType}; use crate::kani_queries::QueryDb; -use itertools::Itertools; use rustc_middle::ty::TyCtxt; use stable_mir::mir::mono::Instance; use stable_mir::mir::{AggregateKind, Body, Constant, Mutability, Operand, Place, Rvalue}; @@ -25,15 +24,7 @@ mod uninit_visitor; use ty_layout::TypeLayout; use uninit_visitor::{CheckUninitVisitor, InitRelevantInstruction, SourceOp}; -const UNINIT_ALLOWLIST: &[&str] = &[ - "kani::shadow::__kani_global_sm_get_inner", - "kani::shadow::__kani_global_sm_set_inner", - "kani::shadow::__kani_global_sm_get", - "kani::shadow::__kani_global_sm_set", - "kani::shadow::__kani_global_sm_get_slice", - "kani::shadow::__kani_global_sm_set_slice", - "std::alloc::alloc", -]; +const KANI_SHADOW_MEMORY_PREFIX: &str = "__kani_global_sm"; /// Instrument the code with checks for uninitialized memory. #[derive(Debug)] @@ -60,17 +51,14 @@ impl TransformPass for UninitPass { fn transform(&self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { trace!(function=?instance.name(), "transform"); - let name_without_generics = instance - .name() - .split("::") - .filter(|chunk| !chunk.starts_with("<") && !chunk.ends_with(">")) - .join("::"); - if UNINIT_ALLOWLIST.iter().any(|allowlist_item| name_without_generics == *allowlist_item) { + // Need to break infinite recursion when shadow memory checks are inserted. + if instance.name().contains(KANI_SHADOW_MEMORY_PREFIX) { return (false, body); } let mut new_body = MutableBody::from(body); let orig_len = new_body.blocks().len(); + // Do not cache body.blocks().len() since it will change as we add new checks. let mut bb_idx = 0; while bb_idx < new_body.blocks().len() { @@ -95,13 +83,11 @@ impl UninitPass { instruction: InitRelevantInstruction, ) { debug!(?instruction, "build_check"); - let (operations_before, operations_after): (Vec<_>, Vec<_>) = - instruction.operations.into_iter().partition(|operation| match operation { - SourceOp::Get { .. } - | SourceOp::BlessDrop { .. } - | SourceOp::Unsupported { .. } => true, - SourceOp::Set { .. } => false, - }); + // Need to partition operations to make sure we add prefix operations before postfix operations. + let (operations_before, operations_after): (Vec<_>, Vec<_>) = instruction + .operations + .into_iter() + .partition(|operation| operation.should_be_inserted_before()); let operations: Vec<_> = vec![operations_before, operations_after].into_iter().flatten().collect(); @@ -113,63 +99,40 @@ impl UninitPass { let reason = format!( "Kani currently doesn't support checking memory initialization using instruction `{unsupported_instruction}` for type `{place_ty}`", ); - self.unsupported_check(tcx, body, &mut source, &reason); + self.unsupported_check(tcx, body, &mut source, operation.position(), &reason); continue; } _ => {} }; - let insert_position = match &operation { - SourceOp::Get { .. } => InsertPosition::Before, - SourceOp::Set { .. } => InsertPosition::After, - SourceOp::BlessDrop { .. } => InsertPosition::Before, - SourceOp::Unsupported { .. } => unreachable!(), - }; - - let place = match &operation { - SourceOp::Get { place, .. } | SourceOp::Set { place, .. } => place, - SourceOp::BlessDrop { place, .. } => &Place { - local: body.new_assignment( - Rvalue::AddressOf(Mutability::Not, place.clone()), - &mut source, - insert_position, - ), - projection: vec![], - }, - SourceOp::Unsupported { .. } => unreachable!(), - }; - - let place_ty = place.ty(body.locals()).unwrap(); - let pointee_ty = match place_ty.kind() { + let insert_position = operation.position(); + let ptr_operand = operation.mk_operand(body, &mut source); + let ptr_operand_ty = ptr_operand.ty(body.locals()).unwrap(); + let pointee_ty = match ptr_operand_ty.kind() { TyKind::RigidTy(RigidTy::RawPtr(pointee_ty, _)) => pointee_ty, _ => { unreachable!( - "Should only build checks for raw pointers, `{place_ty}` encountered" + "Should only build checks for raw pointers, `{ptr_operand_ty}` encountered" ) } }; + // Generate type layout for the item. let type_layout = match TypeLayout::get_mask(pointee_ty) { Ok(type_layout) => type_layout, Err(err) => { - let place_ty = place.ty(body.locals()).unwrap(); let reason = format!( - "Kani currently doesn't support checking memory initialization using instruction for type `{place_ty}` due to the following: `{err}`", + "Kani currently doesn't support checking memory initialization using instruction for type `{ptr_operand_ty}` due to the following: `{err}`", ); - self.unsupported_check(tcx, body, &mut source, &reason); + self.unsupported_check(tcx, body, &mut source, operation.position(), &reason); continue; } }; - let count = match &operation { - SourceOp::Get { count, .. } - | SourceOp::Set { count, .. } - | SourceOp::BlessDrop { count, .. } => count.clone(), - SourceOp::Unsupported { .. } => unreachable!(), - }; - + let count = operation.expect_count(); let span = source.span(body.blocks()); - let layout_place = Place { + // Generate a corresponding array of data & padding bits. + let layout_operand = Operand::Move(Place { local: body.new_assignment( Rvalue::Aggregate( AggregateKind::Array(Ty::bool_ty()), @@ -189,28 +152,33 @@ impl UninitPass { insert_position, ), projection: vec![], - }; + }); - let ptr_local = match pointee_ty.kind() { - TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => body - .new_cast_transmute( - Operand::Copy(place.clone()), - Ty::from_rigid_kind(RigidTy::Slice(Ty::new_tuple(&[]))), + // Cast the operand to the appropriate unit type (fat vs thin pointer). + let ptr_cast_operand = Operand::Move(Place { + local: match pointee_ty.kind() { + TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => body + .new_cast_transmute( + ptr_operand, + Ty::from_rigid_kind(RigidTy::Slice(Ty::new_tuple(&[]))), + Mutability::Not, + &mut source, + insert_position, + ), + _ => body.new_cast_ptr( + ptr_operand, + Ty::new_tuple(&[]), Mutability::Not, &mut source, insert_position, ), - _ => body.new_cast_ptr( - Operand::Copy(place.clone()), - Ty::new_tuple(&[]), - Mutability::Not, - &mut source, - insert_position, - ), - }; + }, + projection: vec![], + }); match operation { SourceOp::Get { .. } => { + // Resolve appropriate function depending on the pointer type. let shadow_memory_get = match pointee_ty.kind() { TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => { Instance::resolve( @@ -253,11 +221,7 @@ impl UninitPass { &shadow_memory_get, &mut source, insert_position, - vec![ - Operand::Copy(Place { local: ptr_local, projection: vec![] }), - Operand::Move(layout_place), - count, - ], + vec![ptr_cast_operand, layout_operand, count], ret_place.clone(), ); body.add_check( @@ -266,10 +230,13 @@ impl UninitPass { &mut source, insert_position, ret_place.local, - &format!("Undefined Behavior: Reading from an uninitialized pointer of type `{place_ty}`"), + &format!("Undefined Behavior: Reading from an uninitialized pointer of type `{ptr_operand_ty}`"), ) } - SourceOp::Set { value, .. } | SourceOp::BlessDrop { value, .. } => { + SourceOp::Set { value, .. } + | SourceOp::BlessConst { value, .. } + | SourceOp::BlessRef { value, .. } => { + // Resolve appropriate function depending on the pointer type. let shadow_memory_set = match pointee_ty.kind() { TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => { Instance::resolve( @@ -312,8 +279,8 @@ impl UninitPass { &mut source, insert_position, vec![ - Operand::Copy(Place { local: ptr_local, projection: vec![] }), - Operand::Move(layout_place), + ptr_cast_operand, + layout_operand, count, Operand::Constant(Constant { span, @@ -336,6 +303,7 @@ impl UninitPass { tcx: TyCtxt, body: &mut MutableBody, source: &mut SourceInstruction, + position: InsertPosition, reason: &str, ) { let span = source.span(body.blocks()); @@ -344,7 +312,7 @@ impl UninitPass { span, user_ty: None, })); - let result = body.new_assignment(rvalue, source, InsertPosition::Before); - body.add_check(tcx, &self.check_type, source, InsertPosition::Before, result, reason); + let result = body.new_assignment(rvalue, source, position); + body.add_check(tcx, &self.check_type, source, position, result, reason); } } diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index b4eef2cb796f..730e5b9f6240 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -1,19 +1,65 @@ -use crate::kani_middle::transform::body::{MutableBody, SourceInstruction}; +use crate::kani_middle::transform::body::{InsertPosition, MutableBody, SourceInstruction}; +use stable_mir::mir::alloc::GlobalAlloc; use stable_mir::mir::mono::{Instance, InstanceKind}; use stable_mir::mir::visit::{Location, PlaceContext}; use stable_mir::mir::{ BasicBlockIdx, Constant, LocalDecl, MirVisitor, Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, }; -use stable_mir::ty::{Const, RigidTy, Span, TyKind, UintTy}; +use stable_mir::ty::{Const, ConstantKind, RigidTy, Span, TyKind, UintTy}; use strum_macros::AsRefStr; #[derive(AsRefStr, Clone, Debug)] pub enum SourceOp { Get { place: Place, count: Operand }, Set { place: Place, count: Operand, value: bool }, - BlessDrop { place: Place, count: Operand, value: bool }, - Unsupported { unsupported_instruction: String, place: Place }, + BlessConst { constant: Constant, count: Operand, value: bool }, + BlessRef { place: Place, count: Operand, value: bool }, + Unsupported { place: Place, unsupported_instruction: String }, +} + +impl SourceOp { + pub fn mk_operand(&self, body: &mut MutableBody, source: &mut SourceInstruction) -> Operand { + match self { + SourceOp::Get { place, .. } | SourceOp::Set { place, .. } => { + Operand::Copy(place.clone()) + } + SourceOp::BlessRef { place, .. } => Operand::Copy(Place { + local: body.new_assignment( + Rvalue::AddressOf(Mutability::Not, place.clone()), + source, + self.position(), + ), + projection: vec![], + }), + SourceOp::BlessConst { constant, .. } => Operand::Constant(constant.clone()), + SourceOp::Unsupported { .. } => unreachable!(), + } + } + + pub fn expect_count(&self) -> Operand { + match self { + SourceOp::Get { count, .. } + | SourceOp::Set { count, .. } + | SourceOp::BlessConst { count, .. } + | SourceOp::BlessRef { count, .. } => count.clone(), + SourceOp::Unsupported { .. } => unreachable!(), + } + } + + pub fn position(&self) -> InsertPosition { + match self { + SourceOp::Get { .. } + | SourceOp::BlessConst { .. } + | SourceOp::BlessRef { .. } + | SourceOp::Unsupported { .. } => InsertPosition::Before, + SourceOp::Set { .. } => InsertPosition::After, + } + } + + pub fn should_be_inserted_before(&self) -> bool { + self.position() == InsertPosition::Before + } } #[derive(Clone, Debug)] @@ -264,7 +310,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { let place_ty = place.ty(&self.locals).unwrap(); // When drop is codegen'ed, a reference is taken to the place which is later implicitly coerced to a pointer. // Hence, we need to bless this pointer as initialized. - self.push_target(SourceOp::BlessDrop { + self.push_target(SourceOp::BlessRef { place: place.clone(), count: mk_const_operand(1, location.span()), value: true, @@ -325,4 +371,31 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { } self.super_place(place, ptx, location) } + + fn visit_operand(&mut self, operand: &Operand, location: Location) { + match operand { + Operand::Constant(constant) => match constant.literal.kind() { + ConstantKind::Allocated(allocation) => { + for (_, prov) in &allocation.provenance.ptrs { + match GlobalAlloc::from(prov.0) { + GlobalAlloc::Static(static_def) => { + let dbg_string = format!("{:?}", static_def); + if dbg_string.contains("__rust_no_alloc_shim_is_unstable") { + self.push_target(SourceOp::BlessConst { + constant: constant.clone(), + count: mk_const_operand(1, location.span()), + value: true, + }); + } + } + _ => {} + }; + } + } + _ => {} + }, + _ => {} + } + self.super_operand(operand, location); + } } diff --git a/tests/kani/Uninit/access-padding-uninit.rs b/tests/kani/Uninit/access-padding-uninit.rs index 75804519d6b2..c8204e9adb10 100644 --- a/tests/kani/Uninit/access-padding-uninit.rs +++ b/tests/kani/Uninit/access-padding-uninit.rs @@ -8,6 +8,7 @@ use std::ptr::addr_of; struct S(u32, u8); #[kani::proof] +#[kani::should_panic] fn main() { let s = S(0, 0); let ptr: *const u8 = addr_of!(s) as *const u8; diff --git a/tests/kani/Uninit/vec-read-bad-len.rs b/tests/kani/Uninit/vec-read-bad-len.rs new file mode 100644 index 000000000000..836aea9903c2 --- /dev/null +++ b/tests/kani/Uninit/vec-read-bad-len.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z ghost-state -Z uninit-checks + +use std::ops::Index; + +#[kani::proof] +#[kani::should_panic] +fn main() { + let mut v: Vec = Vec::with_capacity(10); + unsafe { + v.set_len(5); + } + let el = v.index(0); +} From e901ebbc00fe072455a41d2b88ef33369de00d4e Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Fri, 14 Jun 2024 13:34:05 -0700 Subject: [PATCH 09/87] Implement support for vtable wide pointers --- .../kani_middle/transform/check_uninit/mod.rs | 51 ++++++++++++++----- .../transform/check_uninit/ty_layout.rs | 6 +-- library/kani/src/shadow.rs | 33 +++++++++++- tests/kani/Uninit/box-vtable-init.rs | 18 +++++++ tests/kani/Uninit/box-vtable-uninit.rs | 18 +++++++ 5 files changed, 107 insertions(+), 19 deletions(-) create mode 100644 tests/kani/Uninit/box-vtable-init.rs create mode 100644 tests/kani/Uninit/box-vtable-uninit.rs diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index 3553914f1183..2207be387bac 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -155,26 +155,31 @@ impl UninitPass { }); // Cast the operand to the appropriate unit type (fat vs thin pointer). - let ptr_cast_operand = Operand::Move(Place { - local: match pointee_ty.kind() { - TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => body - .new_cast_transmute( + let ptr_cast_operand = match pointee_ty.kind() { + TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => { + Operand::Move(Place { + local: body.new_cast_transmute( ptr_operand, Ty::from_rigid_kind(RigidTy::Slice(Ty::new_tuple(&[]))), Mutability::Not, &mut source, insert_position, ), - _ => body.new_cast_ptr( + projection: vec![], + }) + } + TyKind::RigidTy(RigidTy::Dynamic(..)) => ptr_operand, + _ => Operand::Move(Place { + local: body.new_cast_ptr( ptr_operand, Ty::new_tuple(&[]), Mutability::Not, &mut source, insert_position, ), - }, - projection: vec![], - }); + projection: vec![], + }), + }; match operation { SourceOp::Get { .. } => { @@ -193,8 +198,8 @@ impl UninitPass { ) .unwrap() } - _ => Instance::resolve( - find_fn_def(tcx, "KaniShadowMemoryGet").unwrap(), + TyKind::RigidTy(RigidTy::Dynamic(..)) => Instance::resolve( + find_fn_def(tcx, "KaniShadowMemoryGetDynamic").unwrap(), &GenericArgs(vec![ GenericArgKind::Const( Const::try_from_uint( @@ -207,6 +212,17 @@ impl UninitPass { ]), ) .unwrap(), + _ => Instance::resolve( + find_fn_def(tcx, "KaniShadowMemoryGet").unwrap(), + &GenericArgs(vec![GenericArgKind::Const( + Const::try_from_uint( + type_layout.as_byte_layout().len() as u128, + UintTy::Usize, + ) + .unwrap(), + )]), + ) + .unwrap(), }; let ret_place = Place { @@ -251,8 +267,8 @@ impl UninitPass { ) .unwrap() } - _ => Instance::resolve( - find_fn_def(tcx, "KaniShadowMemorySet").unwrap(), + TyKind::RigidTy(RigidTy::Dynamic(..)) => Instance::resolve( + find_fn_def(tcx, "KaniShadowMemorySetDynamic").unwrap(), &GenericArgs(vec![ GenericArgKind::Const( Const::try_from_uint( @@ -265,6 +281,17 @@ impl UninitPass { ]), ) .unwrap(), + _ => Instance::resolve( + find_fn_def(tcx, "KaniShadowMemorySet").unwrap(), + &GenericArgs(vec![GenericArgKind::Const( + Const::try_from_uint( + type_layout.as_byte_layout().len() as u128, + UintTy::Usize, + ) + .unwrap(), + )]), + ) + .unwrap(), }; let ret_place = Place { local: body.new_local( diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs index 2d76cda520e2..dcc8511f86d2 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs @@ -36,11 +36,7 @@ impl TypeLayout { offset: 0, size: match ty.layout().unwrap().shape().fields { FieldsShape::Array { stride, count } if count == 0 => stride, - _ => { - return Err(format!( - "Unsupported DST for invalid memory check: `{ty}`" - )); - } + _ => MachineSize::from_bits(8), }, }; let ty_size = data_bytes.size.bytes(); diff --git a/library/kani/src/shadow.rs b/library/kani/src/shadow.rs index 06f07cdf1bb2..94d920425984 100644 --- a/library/kani/src/shadow.rs +++ b/library/kani/src/shadow.rs @@ -27,6 +27,8 @@ //! } //! ``` +use std::ptr::DynMetadata; + const MAX_NUM_OBJECTS: usize = 1024; const MAX_OBJECT_SIZE: usize = 64; @@ -150,7 +152,7 @@ pub fn __kani_global_sm_get_slice( n: usize, ) -> bool { let (ptr, meta) = ptr.to_raw_parts(); - // The pointee type is a DST, more than `n` objects can be accessed. + // The pointee type is a slice, more than `n` objects can be accessed. let n = n * meta; __kani_global_sm_get_inner(ptr, layout, n) } @@ -163,7 +165,34 @@ pub fn __kani_global_sm_set_slice( value: bool, ) { let (ptr, meta) = ptr.to_raw_parts(); - // The pointee type is a DST, more than `n` objects can be accessed. + // The pointee type is a slice, more than `n` objects can be accessed. let n = n * meta; __kani_global_sm_set_inner(ptr, layout, n, value); } + +#[rustc_diagnostic_item = "KaniShadowMemoryGetDynamic"] +pub fn __kani_global_sm_get_dynamic( + ptr: *const T, + layout: [bool; N], + n: usize, +) -> bool { + let (ptr, meta) = ptr.to_raw_parts(); + let meta: DynMetadata = unsafe { std::mem::transmute_copy(&meta) }; + // The pointee type is a dyn Trait, more than `n` objects can be accessed. + let n = n * meta.size_of(); + __kani_global_sm_get_inner(ptr, layout, n) +} + +#[rustc_diagnostic_item = "KaniShadowMemorySetDynamic"] +pub fn __kani_global_sm_set_dynamic( + ptr: *const T, + layout: [bool; N], + n: usize, + value: bool, +) { + let (ptr, meta) = ptr.to_raw_parts(); + let meta: DynMetadata = unsafe { std::mem::transmute_copy(&meta) }; + // The pointee type is a slice, more than `n` objects can be accessed. + let n = n * meta.size_of(); + __kani_global_sm_set_inner(ptr, layout, n, value); +} diff --git a/tests/kani/Uninit/box-vtable-init.rs b/tests/kani/Uninit/box-vtable-init.rs new file mode 100644 index 000000000000..595fd388fd07 --- /dev/null +++ b/tests/kani/Uninit/box-vtable-init.rs @@ -0,0 +1,18 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z ghost-state -Z uninit-checks + +use std::alloc::{alloc, Layout}; +use std::fmt::Debug; + +#[kani::proof] +fn main() { + let layout = Layout::new::(); + unsafe { + let ptr = alloc(layout); + *(ptr.add(0)) = 0x42; + *(ptr.add(1)) = 0x42; + let b: Box = Box::from_raw(ptr as *mut u16); + let v = &*b; + } +} diff --git a/tests/kani/Uninit/box-vtable-uninit.rs b/tests/kani/Uninit/box-vtable-uninit.rs new file mode 100644 index 000000000000..f3cbd948f54e --- /dev/null +++ b/tests/kani/Uninit/box-vtable-uninit.rs @@ -0,0 +1,18 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z ghost-state -Z uninit-checks + +use std::alloc::{alloc, Layout}; +use std::fmt::Debug; + +#[kani::proof] +#[kani::should_panic] +fn main() { + let layout = Layout::new::(); + unsafe { + let ptr = alloc(layout); + *(ptr.add(0)) = 0x42; + let b: Box = Box::from_raw(ptr as *mut u16); + let v = &*b; + } +} From 4e0c3483f90c6bf15400491240fe5350d4968673 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 17 Jun 2024 09:46:41 -0700 Subject: [PATCH 10/87] Clean up tests, preserve types in shadow memory calls --- .../kani_middle/transform/check_uninit/mod.rs | 99 ++++++++----------- library/kani/src/shadow.rs | 24 +++-- .../access-padding-uninit.rs | 2 - .../uninit/access-padding-uninit/expected | 5 + .../uninit/alloc-to-slice}/alloc-to-slice.rs | 1 - tests/expected/uninit/alloc-to-slice/expected | 5 + .../box-vtable-uninit}/box-vtable-uninit.rs | 1 - .../uninit/box-vtable-uninit/expected | 5 + .../struct-padding-and-arr-uninit/expected | 5 + .../struct-padding-and-arr-uninit.rs | 1 - .../expected/uninit/vec-read-bad-len/expected | 5 + .../vec-read-bad-len}/vec-read-bad-len.rs | 1 - .../uninit/vec-read-semi-init/expected | 5 + .../vec-read-semi-init}/vec-read-semi-init.rs | 1 - .../expected/uninit/vec-read-uninit/expected | 5 + .../vec-read-uninit}/vec-read-uninit.rs | 1 - .../Uninit/fixme/partially-maybe-uninit.rs | 20 ---- .../Uninit/fixme/transmute-pair-uninit.rs | 30 ------ .../kani/Uninit/fixme/vec-read-drop-uninit.rs | 12 --- 19 files changed, 94 insertions(+), 134 deletions(-) rename tests/{kani/Uninit => expected/uninit/access-padding-uninit}/access-padding-uninit.rs (93%) create mode 100644 tests/expected/uninit/access-padding-uninit/expected rename tests/{kani/Uninit => expected/uninit/alloc-to-slice}/alloc-to-slice.rs (97%) create mode 100644 tests/expected/uninit/alloc-to-slice/expected rename tests/{kani/Uninit => expected/uninit/box-vtable-uninit}/box-vtable-uninit.rs (94%) create mode 100644 tests/expected/uninit/box-vtable-uninit/expected create mode 100644 tests/expected/uninit/struct-padding-and-arr-uninit/expected rename tests/{kani/Uninit => expected/uninit/struct-padding-and-arr-uninit}/struct-padding-and-arr-uninit.rs (97%) create mode 100644 tests/expected/uninit/vec-read-bad-len/expected rename tests/{kani/Uninit => expected/uninit/vec-read-bad-len}/vec-read-bad-len.rs (92%) create mode 100644 tests/expected/uninit/vec-read-semi-init/expected rename tests/{kani/Uninit => expected/uninit/vec-read-semi-init}/vec-read-semi-init.rs (94%) create mode 100644 tests/expected/uninit/vec-read-uninit/expected rename tests/{kani/Uninit => expected/uninit/vec-read-uninit}/vec-read-uninit.rs (93%) delete mode 100644 tests/kani/Uninit/fixme/partially-maybe-uninit.rs delete mode 100644 tests/kani/Uninit/fixme/transmute-pair-uninit.rs delete mode 100644 tests/kani/Uninit/fixme/vec-read-drop-uninit.rs diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index 2207be387bac..e84e12edec18 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -154,33 +154,6 @@ impl UninitPass { projection: vec![], }); - // Cast the operand to the appropriate unit type (fat vs thin pointer). - let ptr_cast_operand = match pointee_ty.kind() { - TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => { - Operand::Move(Place { - local: body.new_cast_transmute( - ptr_operand, - Ty::from_rigid_kind(RigidTy::Slice(Ty::new_tuple(&[]))), - Mutability::Not, - &mut source, - insert_position, - ), - projection: vec![], - }) - } - TyKind::RigidTy(RigidTy::Dynamic(..)) => ptr_operand, - _ => Operand::Move(Place { - local: body.new_cast_ptr( - ptr_operand, - Ty::new_tuple(&[]), - Mutability::Not, - &mut source, - insert_position, - ), - projection: vec![], - }), - }; - match operation { SourceOp::Get { .. } => { // Resolve appropriate function depending on the pointer type. @@ -188,13 +161,16 @@ impl UninitPass { TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => { Instance::resolve( find_fn_def(tcx, "KaniShadowMemoryGetSlice").unwrap(), - &GenericArgs(vec![GenericArgKind::Const( - Const::try_from_uint( - type_layout.as_byte_layout().len() as u128, - UintTy::Usize, - ) - .unwrap(), - )]), + &GenericArgs(vec![ + GenericArgKind::Const( + Const::try_from_uint( + type_layout.as_byte_layout().len() as u128, + UintTy::Usize, + ) + .unwrap(), + ), + GenericArgKind::Type(pointee_ty), + ]), ) .unwrap() } @@ -214,13 +190,16 @@ impl UninitPass { .unwrap(), _ => Instance::resolve( find_fn_def(tcx, "KaniShadowMemoryGet").unwrap(), - &GenericArgs(vec![GenericArgKind::Const( - Const::try_from_uint( - type_layout.as_byte_layout().len() as u128, - UintTy::Usize, - ) - .unwrap(), - )]), + &GenericArgs(vec![ + GenericArgKind::Const( + Const::try_from_uint( + type_layout.as_byte_layout().len() as u128, + UintTy::Usize, + ) + .unwrap(), + ), + GenericArgKind::Type(pointee_ty), + ]), ) .unwrap(), }; @@ -237,7 +216,7 @@ impl UninitPass { &shadow_memory_get, &mut source, insert_position, - vec![ptr_cast_operand, layout_operand, count], + vec![ptr_operand, layout_operand, count], ret_place.clone(), ); body.add_check( @@ -257,13 +236,16 @@ impl UninitPass { TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => { Instance::resolve( find_fn_def(tcx, "KaniShadowMemorySetSlice").unwrap(), - &GenericArgs(vec![GenericArgKind::Const( - Const::try_from_uint( - type_layout.as_byte_layout().len() as u128, - UintTy::Usize, - ) - .unwrap(), - )]), + &GenericArgs(vec![ + GenericArgKind::Const( + Const::try_from_uint( + type_layout.as_byte_layout().len() as u128, + UintTy::Usize, + ) + .unwrap(), + ), + GenericArgKind::Type(pointee_ty), + ]), ) .unwrap() } @@ -283,13 +265,16 @@ impl UninitPass { .unwrap(), _ => Instance::resolve( find_fn_def(tcx, "KaniShadowMemorySet").unwrap(), - &GenericArgs(vec![GenericArgKind::Const( - Const::try_from_uint( - type_layout.as_byte_layout().len() as u128, - UintTy::Usize, - ) - .unwrap(), - )]), + &GenericArgs(vec![ + GenericArgKind::Const( + Const::try_from_uint( + type_layout.as_byte_layout().len() as u128, + UintTy::Usize, + ) + .unwrap(), + ), + GenericArgKind::Type(pointee_ty), + ]), ) .unwrap(), }; @@ -306,7 +291,7 @@ impl UninitPass { &mut source, insert_position, vec![ - ptr_cast_operand, + ptr_operand, layout_operand, count, Operand::Constant(Constant { diff --git a/library/kani/src/shadow.rs b/library/kani/src/shadow.rs index 94d920425984..057e6dbb0a66 100644 --- a/library/kani/src/shadow.rs +++ b/library/kani/src/shadow.rs @@ -129,14 +129,18 @@ fn __kani_global_sm_set_inner( } #[rustc_diagnostic_item = "KaniShadowMemoryGet"] -pub fn __kani_global_sm_get(ptr: *const (), layout: [bool; N], n: usize) -> bool { +pub fn __kani_global_sm_get( + ptr: *const T, + layout: [bool; N], + n: usize, +) -> bool { let (ptr, _) = ptr.to_raw_parts(); __kani_global_sm_get_inner(ptr, layout, n) } #[rustc_diagnostic_item = "KaniShadowMemorySet"] -pub fn __kani_global_sm_set( - ptr: *const (), +pub fn __kani_global_sm_set( + ptr: *const T, layout: [bool; N], n: usize, value: bool, @@ -145,31 +149,36 @@ pub fn __kani_global_sm_set( __kani_global_sm_set_inner(ptr, layout, n, value); } +// This method should only be called if T is known to be a slice. #[rustc_diagnostic_item = "KaniShadowMemoryGetSlice"] -pub fn __kani_global_sm_get_slice( - ptr: *const [()], +pub fn __kani_global_sm_get_slice( + ptr: *const T, layout: [bool; N], n: usize, ) -> bool { let (ptr, meta) = ptr.to_raw_parts(); + let meta: usize = unsafe { std::mem::transmute_copy(&meta) }; // The pointee type is a slice, more than `n` objects can be accessed. let n = n * meta; __kani_global_sm_get_inner(ptr, layout, n) } +// This method should only be called if T is known to be a slice. #[rustc_diagnostic_item = "KaniShadowMemorySetSlice"] -pub fn __kani_global_sm_set_slice( - ptr: *const [()], +pub fn __kani_global_sm_set_slice( + ptr: *const T, layout: [bool; N], n: usize, value: bool, ) { let (ptr, meta) = ptr.to_raw_parts(); + let meta: usize = unsafe { std::mem::transmute_copy(&meta) }; // The pointee type is a slice, more than `n` objects can be accessed. let n = n * meta; __kani_global_sm_set_inner(ptr, layout, n, value); } +// This method should only be called if T is known to be a trait object. #[rustc_diagnostic_item = "KaniShadowMemoryGetDynamic"] pub fn __kani_global_sm_get_dynamic( ptr: *const T, @@ -183,6 +192,7 @@ pub fn __kani_global_sm_get_dynamic( __kani_global_sm_get_inner(ptr, layout, n) } +// This method should only be called if T is known to be a trait object. #[rustc_diagnostic_item = "KaniShadowMemorySetDynamic"] pub fn __kani_global_sm_set_dynamic( ptr: *const T, diff --git a/tests/kani/Uninit/access-padding-uninit.rs b/tests/expected/uninit/access-padding-uninit/access-padding-uninit.rs similarity index 93% rename from tests/kani/Uninit/access-padding-uninit.rs rename to tests/expected/uninit/access-padding-uninit/access-padding-uninit.rs index c8204e9adb10..35c1782466d4 100644 --- a/tests/kani/Uninit/access-padding-uninit.rs +++ b/tests/expected/uninit/access-padding-uninit/access-padding-uninit.rs @@ -8,10 +8,8 @@ use std::ptr::addr_of; struct S(u32, u8); #[kani::proof] -#[kani::should_panic] fn main() { let s = S(0, 0); let ptr: *const u8 = addr_of!(s) as *const u8; let padding = unsafe { *(ptr.add(5)) }; } - diff --git a/tests/expected/uninit/access-padding-uninit/expected b/tests/expected/uninit/access-padding-uninit/expected new file mode 100644 index 000000000000..da8d15b2dbb9 --- /dev/null +++ b/tests/expected/uninit/access-padding-uninit/expected @@ -0,0 +1,5 @@ +Failed Checks: Undefined Behavior: Reading from an uninitialized pointer of type `*const u8` + +VERIFICATION:- FAILED + +Complete - 0 successfully verified harnesses, 1 failures, 1 total. \ No newline at end of file diff --git a/tests/kani/Uninit/alloc-to-slice.rs b/tests/expected/uninit/alloc-to-slice/alloc-to-slice.rs similarity index 97% rename from tests/kani/Uninit/alloc-to-slice.rs rename to tests/expected/uninit/alloc-to-slice/alloc-to-slice.rs index 0b93ec0e7f8b..76fb2994794e 100644 --- a/tests/kani/Uninit/alloc-to-slice.rs +++ b/tests/expected/uninit/alloc-to-slice/alloc-to-slice.rs @@ -8,7 +8,6 @@ use std::alloc::{alloc, dealloc, Layout}; use std::slice::from_raw_parts; #[kani::proof] -#[kani::should_panic] fn main() { let layout = Layout::from_size_align(32, 8).unwrap(); unsafe { diff --git a/tests/expected/uninit/alloc-to-slice/expected b/tests/expected/uninit/alloc-to-slice/expected new file mode 100644 index 000000000000..f8347a591edf --- /dev/null +++ b/tests/expected/uninit/alloc-to-slice/expected @@ -0,0 +1,5 @@ +Failed Checks: Undefined Behavior: Reading from an uninitialized pointer of type `*const [u8]` + +VERIFICATION:- FAILED + +Complete - 0 successfully verified harnesses, 1 failures, 1 total. \ No newline at end of file diff --git a/tests/kani/Uninit/box-vtable-uninit.rs b/tests/expected/uninit/box-vtable-uninit/box-vtable-uninit.rs similarity index 94% rename from tests/kani/Uninit/box-vtable-uninit.rs rename to tests/expected/uninit/box-vtable-uninit/box-vtable-uninit.rs index f3cbd948f54e..edcf8860e5ed 100644 --- a/tests/kani/Uninit/box-vtable-uninit.rs +++ b/tests/expected/uninit/box-vtable-uninit/box-vtable-uninit.rs @@ -6,7 +6,6 @@ use std::alloc::{alloc, Layout}; use std::fmt::Debug; #[kani::proof] -#[kani::should_panic] fn main() { let layout = Layout::new::(); unsafe { diff --git a/tests/expected/uninit/box-vtable-uninit/expected b/tests/expected/uninit/box-vtable-uninit/expected new file mode 100644 index 000000000000..ee36737f6522 --- /dev/null +++ b/tests/expected/uninit/box-vtable-uninit/expected @@ -0,0 +1,5 @@ +Failed Checks: Undefined Behavior: Reading from an uninitialized pointer of type `*const dyn std::fmt::Debug` + +VERIFICATION:- FAILED + +Complete - 0 successfully verified harnesses, 1 failures, 1 total. \ No newline at end of file diff --git a/tests/expected/uninit/struct-padding-and-arr-uninit/expected b/tests/expected/uninit/struct-padding-and-arr-uninit/expected new file mode 100644 index 000000000000..12c5c0a4a439 --- /dev/null +++ b/tests/expected/uninit/struct-padding-and-arr-uninit/expected @@ -0,0 +1,5 @@ +Failed Checks: Undefined Behavior: Reading from an uninitialized pointer of type `*mut [u8; 4]` + +VERIFICATION:- FAILED + +Complete - 0 successfully verified harnesses, 1 failures, 1 total. \ No newline at end of file diff --git a/tests/kani/Uninit/struct-padding-and-arr-uninit.rs b/tests/expected/uninit/struct-padding-and-arr-uninit/struct-padding-and-arr-uninit.rs similarity index 97% rename from tests/kani/Uninit/struct-padding-and-arr-uninit.rs rename to tests/expected/uninit/struct-padding-and-arr-uninit/struct-padding-and-arr-uninit.rs index d60ae3a774b9..254aece61871 100644 --- a/tests/kani/Uninit/struct-padding-and-arr-uninit.rs +++ b/tests/expected/uninit/struct-padding-and-arr-uninit/struct-padding-and-arr-uninit.rs @@ -12,7 +12,6 @@ use std::ptr; struct S(u8, u16); #[kani::proof] -#[kani::should_panic] #[custom_mir(dialect = "runtime", phase = "optimized")] fn main() { mir! { diff --git a/tests/expected/uninit/vec-read-bad-len/expected b/tests/expected/uninit/vec-read-bad-len/expected new file mode 100644 index 000000000000..f8347a591edf --- /dev/null +++ b/tests/expected/uninit/vec-read-bad-len/expected @@ -0,0 +1,5 @@ +Failed Checks: Undefined Behavior: Reading from an uninitialized pointer of type `*const [u8]` + +VERIFICATION:- FAILED + +Complete - 0 successfully verified harnesses, 1 failures, 1 total. \ No newline at end of file diff --git a/tests/kani/Uninit/vec-read-bad-len.rs b/tests/expected/uninit/vec-read-bad-len/vec-read-bad-len.rs similarity index 92% rename from tests/kani/Uninit/vec-read-bad-len.rs rename to tests/expected/uninit/vec-read-bad-len/vec-read-bad-len.rs index 836aea9903c2..ef8f72de8d16 100644 --- a/tests/kani/Uninit/vec-read-bad-len.rs +++ b/tests/expected/uninit/vec-read-bad-len/vec-read-bad-len.rs @@ -5,7 +5,6 @@ use std::ops::Index; #[kani::proof] -#[kani::should_panic] fn main() { let mut v: Vec = Vec::with_capacity(10); unsafe { diff --git a/tests/expected/uninit/vec-read-semi-init/expected b/tests/expected/uninit/vec-read-semi-init/expected new file mode 100644 index 000000000000..da8d15b2dbb9 --- /dev/null +++ b/tests/expected/uninit/vec-read-semi-init/expected @@ -0,0 +1,5 @@ +Failed Checks: Undefined Behavior: Reading from an uninitialized pointer of type `*const u8` + +VERIFICATION:- FAILED + +Complete - 0 successfully verified harnesses, 1 failures, 1 total. \ No newline at end of file diff --git a/tests/kani/Uninit/vec-read-semi-init.rs b/tests/expected/uninit/vec-read-semi-init/vec-read-semi-init.rs similarity index 94% rename from tests/kani/Uninit/vec-read-semi-init.rs rename to tests/expected/uninit/vec-read-semi-init/vec-read-semi-init.rs index ba6be2e12619..5222001fa787 100644 --- a/tests/kani/Uninit/vec-read-semi-init.rs +++ b/tests/expected/uninit/vec-read-semi-init/vec-read-semi-init.rs @@ -3,7 +3,6 @@ // kani-flags: -Z ghost-state -Z uninit-checks #[kani::proof] -#[kani::should_panic] fn main() { let mut v: Vec = Vec::with_capacity(10); unsafe { *v.as_mut_ptr().add(4) = 0x42 }; diff --git a/tests/expected/uninit/vec-read-uninit/expected b/tests/expected/uninit/vec-read-uninit/expected new file mode 100644 index 000000000000..da8d15b2dbb9 --- /dev/null +++ b/tests/expected/uninit/vec-read-uninit/expected @@ -0,0 +1,5 @@ +Failed Checks: Undefined Behavior: Reading from an uninitialized pointer of type `*const u8` + +VERIFICATION:- FAILED + +Complete - 0 successfully verified harnesses, 1 failures, 1 total. \ No newline at end of file diff --git a/tests/kani/Uninit/vec-read-uninit.rs b/tests/expected/uninit/vec-read-uninit/vec-read-uninit.rs similarity index 93% rename from tests/kani/Uninit/vec-read-uninit.rs rename to tests/expected/uninit/vec-read-uninit/vec-read-uninit.rs index dbadd896132d..6c7e25ea0226 100644 --- a/tests/kani/Uninit/vec-read-uninit.rs +++ b/tests/expected/uninit/vec-read-uninit/vec-read-uninit.rs @@ -3,7 +3,6 @@ // kani-flags: -Z ghost-state -Z uninit-checks #[kani::proof] -#[kani::should_panic] fn main() { let v: Vec = Vec::with_capacity(10); let undef = unsafe { *v.as_ptr().add(5) }; //~ ERROR: uninitialized diff --git a/tests/kani/Uninit/fixme/partially-maybe-uninit.rs b/tests/kani/Uninit/fixme/partially-maybe-uninit.rs deleted file mode 100644 index 1ecd579ae317..000000000000 --- a/tests/kani/Uninit/fixme/partially-maybe-uninit.rs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: -Z ghost-state -Z uninit-checks - -use std::mem::{self, MaybeUninit}; - -#[repr(C)] -#[derive(Copy, Clone, Debug, PartialEq)] -struct Demo(bool, u16); - -#[kani::proof] -fn main() { - unsafe { - // Transmute-round-trip through a type with Scalar layout is lossless. - // This is tricky because that 'scalar' is *partially* uninitialized. - let x = Demo(true, 3); - let y: MaybeUninit = mem::transmute(x); - assert_eq!(x, mem::transmute(y)); - } -} diff --git a/tests/kani/Uninit/fixme/transmute-pair-uninit.rs b/tests/kani/Uninit/fixme/transmute-pair-uninit.rs deleted file mode 100644 index 28351c4fd8ea..000000000000 --- a/tests/kani/Uninit/fixme/transmute-pair-uninit.rs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: -Z ghost-state -Z uninit-checks - -#![feature(core_intrinsics)] - -use std::mem; - -#[kani::proof] -#[kani::unwind(33)] -fn main() { - let x: Option> = unsafe { - let z = std::intrinsics::add_with_overflow(0usize, 0usize); - std::mem::transmute::<(usize, bool), Option>>(z) - }; - let y = &x; - // Now read this bytewise. There should be (`ptr_size + 1`) def bytes followed by - // (`ptr_size - 1`) undef bytes (the padding after the bool) in there. - let z: *const u8 = y as *const _ as *const _; - let first_undef = mem::size_of::() as isize + 1; - for i in 0..first_undef { - let byte = unsafe { *z.offset(i) }; - assert_eq!(byte, 0); - } - let v = unsafe { *z.offset(first_undef) }; - //~^ ERROR: uninitialized - if v == 0 { - println!("it is zero"); - } -} diff --git a/tests/kani/Uninit/fixme/vec-read-drop-uninit.rs b/tests/kani/Uninit/fixme/vec-read-drop-uninit.rs deleted file mode 100644 index fc770d73aac0..000000000000 --- a/tests/kani/Uninit/fixme/vec-read-drop-uninit.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: -Z ghost-state -Z uninit-checks - -#[kani::proof] -fn main() { - let mut v: Vec = Vec::with_capacity(10); - unsafe { - v.set_len(5); - } - // We would read uninitialized values on drop, but MIRI doesn't seem to complain about it either. -} From 1d6e167d5e9363132070918d36a741c1459cd59f Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 17 Jun 2024 11:52:09 -0700 Subject: [PATCH 11/87] Skip analyzing pointers to trait objects for now --- .../src/kani_middle/transform/body.rs | 15 ------- .../kani_middle/transform/check_uninit/mod.rs | 36 ++-------------- .../transform/check_uninit/ty_layout.rs | 2 +- .../transform/check_uninit/uninit_visitor.rs | 43 +++++++++++++------ library/kani/src/shadow.rs | 31 ------------- .../box-vtable-uninit/box-vtable-uninit.rs | 17 -------- .../uninit/box-vtable-uninit/expected | 5 --- tests/kani/Uninit/box-vtable-init.rs | 18 -------- 8 files changed, 35 insertions(+), 132 deletions(-) delete mode 100644 tests/expected/uninit/box-vtable-uninit/box-vtable-uninit.rs delete mode 100644 tests/expected/uninit/box-vtable-uninit/expected delete mode 100644 tests/kani/Uninit/box-vtable-init.rs diff --git a/kani-compiler/src/kani_middle/transform/body.rs b/kani-compiler/src/kani_middle/transform/body.rs index eb4c4e444526..ac74e02d7ee9 100644 --- a/kani-compiler/src/kani_middle/transform/body.rs +++ b/kani-compiler/src/kani_middle/transform/body.rs @@ -123,21 +123,6 @@ impl MutableBody { self.new_assignment(rvalue, where_to, insert_position) } - /// Transmute to a raw pointer of `*mut type` and return a new local where that value is stored. - pub fn new_cast_transmute( - &mut self, - from: Operand, - pointee_ty: Ty, - mutability: Mutability, - where_to: &mut SourceInstruction, - insert_position: InsertPosition, - ) -> Local { - assert!(from.ty(self.locals()).unwrap().kind().is_raw_ptr()); - let target_ty = Ty::new_ptr(pointee_ty, mutability); - let rvalue = Rvalue::Cast(CastKind::Transmute, from, target_ty); - self.new_assignment(rvalue, where_to, insert_position) - } - /// Add a new assignment for the given binary operation. /// /// Return the local where the result is saved. diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index e84e12edec18..baf62f4094c8 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -94,11 +94,7 @@ impl UninitPass { let mut source = instruction.source; for operation in operations { match &operation { - SourceOp::Unsupported { unsupported_instruction, place } => { - let place_ty = place.ty(body.locals()).unwrap(); - let reason = format!( - "Kani currently doesn't support checking memory initialization using instruction `{unsupported_instruction}` for type `{place_ty}`", - ); + SourceOp::Unsupported { reason } => { self.unsupported_check(tcx, body, &mut source, operation.position(), &reason); continue; } @@ -174,20 +170,7 @@ impl UninitPass { ) .unwrap() } - TyKind::RigidTy(RigidTy::Dynamic(..)) => Instance::resolve( - find_fn_def(tcx, "KaniShadowMemoryGetDynamic").unwrap(), - &GenericArgs(vec![ - GenericArgKind::Const( - Const::try_from_uint( - type_layout.as_byte_layout().len() as u128, - UintTy::Usize, - ) - .unwrap(), - ), - GenericArgKind::Type(pointee_ty), - ]), - ) - .unwrap(), + TyKind::RigidTy(RigidTy::Dynamic(..)) => continue, // Any layout is valid when dereferencing a pointer to `dyn Trait`. _ => Instance::resolve( find_fn_def(tcx, "KaniShadowMemoryGet").unwrap(), &GenericArgs(vec![ @@ -249,20 +232,7 @@ impl UninitPass { ) .unwrap() } - TyKind::RigidTy(RigidTy::Dynamic(..)) => Instance::resolve( - find_fn_def(tcx, "KaniShadowMemorySetDynamic").unwrap(), - &GenericArgs(vec![ - GenericArgKind::Const( - Const::try_from_uint( - type_layout.as_byte_layout().len() as u128, - UintTy::Usize, - ) - .unwrap(), - ), - GenericArgKind::Type(pointee_ty), - ]), - ) - .unwrap(), + TyKind::RigidTy(RigidTy::Dynamic(..)) => continue, // Any layout is valid when dereferencing a pointer to `dyn Trait`. _ => Instance::resolve( find_fn_def(tcx, "KaniShadowMemorySet").unwrap(), &GenericArgs(vec![ diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs index dcc8511f86d2..2d3a9b4c86f1 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs @@ -36,7 +36,7 @@ impl TypeLayout { offset: 0, size: match ty.layout().unwrap().shape().fields { FieldsShape::Array { stride, count } if count == 0 => stride, - _ => MachineSize::from_bits(8), + _ => MachineSize::from_bits(0), }, }; let ty_size = data_bytes.size.bytes(); diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index 730e5b9f6240..223f0da960e9 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -3,8 +3,9 @@ use stable_mir::mir::alloc::GlobalAlloc; use stable_mir::mir::mono::{Instance, InstanceKind}; use stable_mir::mir::visit::{Location, PlaceContext}; use stable_mir::mir::{ - BasicBlockIdx, Constant, LocalDecl, MirVisitor, Mutability, NonDivergingIntrinsic, Operand, - Place, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, + BasicBlockIdx, CastKind, Constant, LocalDecl, MirVisitor, Mutability, NonDivergingIntrinsic, + Operand, Place, PointerCoercion, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, + TerminatorKind, }; use stable_mir::ty::{Const, ConstantKind, RigidTy, Span, TyKind, UintTy}; use strum_macros::AsRefStr; @@ -15,7 +16,7 @@ pub enum SourceOp { Set { place: Place, count: Operand, value: bool }, BlessConst { constant: Constant, count: Operand, value: bool }, BlessRef { place: Place, count: Operand, value: bool }, - Unsupported { place: Place, unsupported_instruction: String }, + Unsupported { reason: String }, } impl SourceOp { @@ -109,14 +110,14 @@ fn try_remove_topmost_deref(place: &Place) -> Option { } } -/// Retrieve instance for the given function operand. -/// -/// This will panic if the operand is not a function or if it cannot be resolved. -fn expect_instance(locals: &[LocalDecl], func: &Operand) -> Instance { +/// Try retrieving instance for the given function operand. +fn try_resolve_instance(locals: &[LocalDecl], func: &Operand) -> Result { let ty = func.ty(locals).unwrap(); match ty.kind() { - TyKind::RigidTy(RigidTy::FnDef(def, args)) => Instance::resolve(def, &args).unwrap(), - _ => unreachable!(), + TyKind::RigidTy(RigidTy::FnDef(def, args)) => Ok(Instance::resolve(def, &args).unwrap()), + _ => Err(format!( + "Kani does not support reasoning about memory initialization of arguments to `{ty:?}`." + )), } } @@ -225,7 +226,14 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { match &term.kind { TerminatorKind::Call { func, args, destination, .. } => { self.super_terminator(term, location); - let instance = expect_instance(self.locals, func); + let instance = match try_resolve_instance(self.locals, func) { + Ok(instance) => instance, + Err(reason) => { + self.super_terminator(term, location); + self.push_target(SourceOp::Unsupported { reason }); + return; + } + }; match instance.kind { InstanceKind::Intrinsic => { match instance.intrinsic_name().unwrap().as_str() { @@ -354,8 +362,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { && (!ptx.is_mutating() || place.projection.len() > idx + 1) { self.push_target(SourceOp::Unsupported { - unsupported_instruction: "union access".to_string(), - place: intermediate_place.clone(), + reason: "Kani does not support reasoning about memory initialization of unions.".to_string(), }); } } @@ -398,4 +405,16 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { } self.super_operand(operand, location); } + + fn visit_rvalue(&mut self, rvalue: &Rvalue, location: Location) { + match rvalue { + Rvalue::Cast(CastKind::PointerCoercion(PointerCoercion::Unsize), _, _) => { + self.push_target(SourceOp::Unsupported { + reason: "Kani does not support reasoning about memory initialization of unsized pointers.".to_string(), + }); + } + _ => {} + }; + self.super_rvalue(rvalue, location); + } } diff --git a/library/kani/src/shadow.rs b/library/kani/src/shadow.rs index 057e6dbb0a66..44106c1b88f9 100644 --- a/library/kani/src/shadow.rs +++ b/library/kani/src/shadow.rs @@ -27,8 +27,6 @@ //! } //! ``` -use std::ptr::DynMetadata; - const MAX_NUM_OBJECTS: usize = 1024; const MAX_OBJECT_SIZE: usize = 64; @@ -177,32 +175,3 @@ pub fn __kani_global_sm_set_slice( let n = n * meta; __kani_global_sm_set_inner(ptr, layout, n, value); } - -// This method should only be called if T is known to be a trait object. -#[rustc_diagnostic_item = "KaniShadowMemoryGetDynamic"] -pub fn __kani_global_sm_get_dynamic( - ptr: *const T, - layout: [bool; N], - n: usize, -) -> bool { - let (ptr, meta) = ptr.to_raw_parts(); - let meta: DynMetadata = unsafe { std::mem::transmute_copy(&meta) }; - // The pointee type is a dyn Trait, more than `n` objects can be accessed. - let n = n * meta.size_of(); - __kani_global_sm_get_inner(ptr, layout, n) -} - -// This method should only be called if T is known to be a trait object. -#[rustc_diagnostic_item = "KaniShadowMemorySetDynamic"] -pub fn __kani_global_sm_set_dynamic( - ptr: *const T, - layout: [bool; N], - n: usize, - value: bool, -) { - let (ptr, meta) = ptr.to_raw_parts(); - let meta: DynMetadata = unsafe { std::mem::transmute_copy(&meta) }; - // The pointee type is a slice, more than `n` objects can be accessed. - let n = n * meta.size_of(); - __kani_global_sm_set_inner(ptr, layout, n, value); -} diff --git a/tests/expected/uninit/box-vtable-uninit/box-vtable-uninit.rs b/tests/expected/uninit/box-vtable-uninit/box-vtable-uninit.rs deleted file mode 100644 index edcf8860e5ed..000000000000 --- a/tests/expected/uninit/box-vtable-uninit/box-vtable-uninit.rs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: -Z ghost-state -Z uninit-checks - -use std::alloc::{alloc, Layout}; -use std::fmt::Debug; - -#[kani::proof] -fn main() { - let layout = Layout::new::(); - unsafe { - let ptr = alloc(layout); - *(ptr.add(0)) = 0x42; - let b: Box = Box::from_raw(ptr as *mut u16); - let v = &*b; - } -} diff --git a/tests/expected/uninit/box-vtable-uninit/expected b/tests/expected/uninit/box-vtable-uninit/expected deleted file mode 100644 index ee36737f6522..000000000000 --- a/tests/expected/uninit/box-vtable-uninit/expected +++ /dev/null @@ -1,5 +0,0 @@ -Failed Checks: Undefined Behavior: Reading from an uninitialized pointer of type `*const dyn std::fmt::Debug` - -VERIFICATION:- FAILED - -Complete - 0 successfully verified harnesses, 1 failures, 1 total. \ No newline at end of file diff --git a/tests/kani/Uninit/box-vtable-init.rs b/tests/kani/Uninit/box-vtable-init.rs deleted file mode 100644 index 595fd388fd07..000000000000 --- a/tests/kani/Uninit/box-vtable-init.rs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: -Z ghost-state -Z uninit-checks - -use std::alloc::{alloc, Layout}; -use std::fmt::Debug; - -#[kani::proof] -fn main() { - let layout = Layout::new::(); - unsafe { - let ptr = alloc(layout); - *(ptr.add(0)) = 0x42; - *(ptr.add(1)) = 0x42; - let b: Box = Box::from_raw(ptr as *mut u16); - let v = &*b; - } -} From 7e26dacfc232e2d668d58b8339d3181743bd3af0 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 17 Jun 2024 14:59:33 -0700 Subject: [PATCH 12/87] Slight cleanup --- .../codegen_cprover_gotoc/overrides/hooks.rs | 2 +- .../src/kani_middle/transform/body.rs | 55 ++++++++++--------- .../kani_middle/transform/check_uninit/mod.rs | 10 +++- .../transform/check_uninit/ty_layout.rs | 8 ++- tests/kani/Uninit/access-padding-init.rs | 1 - .../Uninit/struct-padding-and-arr-init.rs | 2 +- 6 files changed, 46 insertions(+), 32 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs b/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs index eb17c33f5bee..d66e16b6ce59 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs @@ -260,7 +260,7 @@ impl GotocHook for PointerObject { ) -> Stmt { assert_eq!(fargs.len(), 1); let ptr = fargs.pop().unwrap().cast_to(Type::void_pointer()); - let target: usize = target.unwrap(); + let target = target.unwrap(); let loc = gcx.codegen_caller_span_stable(span); let ret_place = unwrap_or_return_codegen_unimplemented_stmt!(gcx, gcx.codegen_place_stable(assign_to)); diff --git a/kani-compiler/src/kani_middle/transform/body.rs b/kani-compiler/src/kani_middle/transform/body.rs index ac74e02d7ee9..66f86294ee98 100644 --- a/kani-compiler/src/kani_middle/transform/body.rs +++ b/kani-compiler/src/kani_middle/transform/body.rs @@ -45,6 +45,7 @@ pub struct MutableBody { skip_first: HashSet, } +/// Denotes whether instrumentation should be inserted before or after the statement. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum InsertPosition { Before, @@ -114,13 +115,13 @@ impl MutableBody { from: Operand, pointee_ty: Ty, mutability: Mutability, - where_to: &mut SourceInstruction, - insert_position: InsertPosition, + source: &mut SourceInstruction, + position: InsertPosition, ) -> Local { assert!(from.ty(self.locals()).unwrap().kind().is_raw_ptr()); let target_ty = Ty::new_ptr(pointee_ty, mutability); let rvalue = Rvalue::Cast(CastKind::PtrToPtr, from, target_ty); - self.new_assignment(rvalue, where_to, insert_position) + self.new_assignment(rvalue, source, position) } /// Add a new assignment for the given binary operation. @@ -131,11 +132,11 @@ impl MutableBody { bin_op: BinOp, lhs: Operand, rhs: Operand, - where_to: &mut SourceInstruction, - insert_position: InsertPosition, + source: &mut SourceInstruction, + position: InsertPosition, ) -> Local { let rvalue = Rvalue::BinaryOp(bin_op, lhs, rhs); - self.new_assignment(rvalue, where_to, insert_position) + self.new_assignment(rvalue, source, position) } /// Add a new assignment. @@ -144,14 +145,14 @@ impl MutableBody { pub fn new_assignment( &mut self, rvalue: Rvalue, - where_to: &mut SourceInstruction, - insert_position: InsertPosition, + source: &mut SourceInstruction, + position: InsertPosition, ) -> Local { - let span = where_to.span(&self.blocks); + let span = source.span(&self.blocks); let ret_ty = rvalue.ty(&self.locals).unwrap(); let result = self.new_local(ret_ty, span, Mutability::Not); let stmt = Statement { kind: StatementKind::Assign(Place::from(result), rvalue), span }; - self.insert_stmt(stmt, where_to, insert_position); + self.insert_stmt(stmt, source, position); result } @@ -165,7 +166,7 @@ impl MutableBody { tcx: TyCtxt, check_type: &CheckType, source: &mut SourceInstruction, - insert_position: InsertPosition, + position: InsertPosition, value: Local, msg: &str, ) { @@ -195,7 +196,7 @@ impl MutableBody { unwind: UnwindAction::Terminate, }; let terminator = Terminator { kind, span }; - self.split_bb(source, insert_position, terminator); + self.split_bb(source, position, terminator); } CheckType::Panic | CheckType::NoCore => { tcx.sess @@ -218,7 +219,7 @@ impl MutableBody { &mut self, callee: &Instance, source: &mut SourceInstruction, - insert_position: InsertPosition, + position: InsertPosition, args: Vec, destination: Place, ) { @@ -234,7 +235,7 @@ impl MutableBody { unwind: UnwindAction::Terminate, }; let terminator = Terminator { kind, span }; - self.split_bb(source, insert_position, terminator); + self.split_bb(source, position, terminator); } /// Split a basic block right before the source location and use the new terminator @@ -244,11 +245,11 @@ impl MutableBody { pub fn split_bb( &mut self, source: &mut SourceInstruction, - insert_position: InsertPosition, + position: InsertPosition, new_term: Terminator, ) { let new_bb_idx = self.blocks.len(); - match insert_position { + match position { InsertPosition::Before => { let (idx, bb) = match source { SourceInstruction::Statement { idx, bb } => { @@ -273,6 +274,8 @@ impl MutableBody { InsertPosition::After => { let span = source.span(&self.blocks); match source { + // Split the current block after the statement located at `source` + // and move the remaining statements into the new one. SourceInstruction::Statement { idx, bb } => { let (orig_idx, orig_bb) = (*idx, *bb); *idx = 0; @@ -283,9 +286,11 @@ impl MutableBody { let new_bb = BasicBlock { statements: remaining, terminator: old_term }; self.blocks.push(new_bb); } + // Make the terminator at `source` point at the new block, + // the terminator of which is a simple Goto instruction. SourceInstruction::Terminator { bb } => { let current_terminator = &mut self.blocks.get_mut(*bb).unwrap().terminator; - // Kani can only instrument function calls in this way. + // Kani can only instrument function calls like this. match &mut current_terminator.kind { TerminatorKind::Call { target: Some(target_bb), .. } => { *bb = new_bb_idx; @@ -307,16 +312,16 @@ impl MutableBody { } } - /// Insert statement before the source instruction and update the source as needed. + /// Insert statement before or after the source instruction and update the source as needed. pub fn insert_stmt( &mut self, new_stmt: Statement, - where_to: &mut SourceInstruction, - insert_position: InsertPosition, + source: &mut SourceInstruction, + position: InsertPosition, ) { - match insert_position { + match position { InsertPosition::Before => { - match where_to { + match source { SourceInstruction::Statement { idx, bb } => { self.blocks[*bb].statements.insert(*idx, new_stmt); *idx += 1; @@ -329,8 +334,8 @@ impl MutableBody { } InsertPosition::After => { let new_bb_idx = self.blocks.len(); - let span = where_to.span(&self.blocks); - match where_to { + let span = source.span(&self.blocks); + match source { SourceInstruction::Statement { idx, bb } => { self.blocks[*bb].statements.insert(*idx + 1, new_stmt); *idx += 1; @@ -341,7 +346,7 @@ impl MutableBody { // Kani can only instrument function calls in this way. match &mut current_terminator.kind { TerminatorKind::Call { target: Some(target_bb), .. } => { - *where_to = SourceInstruction::Statement { idx: 0, bb: new_bb_idx }; + *source = SourceInstruction::Statement { idx: 0, bb: new_bb_idx }; let new_bb = BasicBlock { statements: vec![new_stmt], terminator: Terminator { diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index baf62f4094c8..e33b9f1229ad 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -83,7 +83,8 @@ impl UninitPass { instruction: InitRelevantInstruction, ) { debug!(?instruction, "build_check"); - // Need to partition operations to make sure we add prefix operations before postfix operations. + // Need to partition operations to make sure we add prefix operations before postfix operations + // to ensure instruction pointer shifts correctly. let (operations_before, operations_after): (Vec<_>, Vec<_>) = instruction .operations .into_iter() @@ -108,7 +109,7 @@ impl UninitPass { TyKind::RigidTy(RigidTy::RawPtr(pointee_ty, _)) => pointee_ty, _ => { unreachable!( - "Should only build checks for raw pointers, `{ptr_operand_ty}` encountered" + "Should only build checks for raw pointers, `{ptr_operand_ty}` encountered." ) } }; @@ -118,7 +119,7 @@ impl UninitPass { Ok(type_layout) => type_layout, Err(err) => { let reason = format!( - "Kani currently doesn't support checking memory initialization using instruction for type `{ptr_operand_ty}` due to the following: `{err}`", + "Kani currently doesn't support checking memory initialization for `{ptr_operand_ty}` due to the following error: `{err}`", ); self.unsupported_check(tcx, body, &mut source, operation.position(), &reason); continue; @@ -195,6 +196,7 @@ impl UninitPass { ), projection: vec![], }; + // Retrieve current shadow memory info. body.add_call( &shadow_memory_get, &mut source, @@ -202,6 +204,7 @@ impl UninitPass { vec![ptr_operand, layout_operand, count], ret_place.clone(), ); + // Make sure all non-padding bytes are initialized. body.add_check( tcx, &self.check_type, @@ -256,6 +259,7 @@ impl UninitPass { ), projection: vec![], }; + // Initialize all non-padding bytes. body.add_call( &shadow_memory_set, &mut source, diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs index 2d3a9b4c86f1..b3528a464011 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs @@ -7,18 +7,21 @@ use stable_mir::CrateDef; struct DataBytes { /// Offset in bytes. offset: usize, - /// Size of this requirement. + /// Size of this data chunk. size: MachineSize, } pub type ByteLayout = Vec; +// Depending on whether the type is statically or dynamically sized, +// the layout of the element or the layout of the actual type is returned. pub enum TypeLayout { StaticallySized { layout: ByteLayout }, DynamicallySized { element_layout: ByteLayout }, } impl TypeLayout { + /// Retrieve data layout for a type. pub fn get_mask(ty: Ty) -> Result { if ty.layout().unwrap().shape().is_sized() { let ty_layout = data_bytes_for_ty(&MachineInfo::target(), ty, 0)?; @@ -51,6 +54,7 @@ impl TypeLayout { } } + // Convert type layout to a vector of byte flags. pub fn as_byte_layout(&self) -> &ByteLayout { match self { TypeLayout::StaticallySized { layout } => layout, @@ -59,6 +63,7 @@ impl TypeLayout { } } +/// Get a size of an initialized scalar. fn scalar_ty_size(machine_info: &MachineInfo, ty: Ty) -> Option { let shape = ty.layout().unwrap().shape(); match shape.abi { @@ -74,6 +79,7 @@ fn scalar_ty_size(machine_info: &MachineInfo, ty: Ty) -> Option { } } +/// Retrieve a set of data bytes with offsets for a type. fn data_bytes_for_ty( machine_info: &MachineInfo, ty: Ty, diff --git a/tests/kani/Uninit/access-padding-init.rs b/tests/kani/Uninit/access-padding-init.rs index 8e86873a7bf2..59a720c1af10 100644 --- a/tests/kani/Uninit/access-padding-init.rs +++ b/tests/kani/Uninit/access-padding-init.rs @@ -13,4 +13,3 @@ fn main() { let ptr: *const u8 = addr_of!(s) as *const u8; let padding = unsafe { *(ptr.add(4)) }; } - diff --git a/tests/kani/Uninit/struct-padding-and-arr-init.rs b/tests/kani/Uninit/struct-padding-and-arr-init.rs index 46736e52b2f8..b071f60a674c 100644 --- a/tests/kani/Uninit/struct-padding-and-arr-init.rs +++ b/tests/kani/Uninit/struct-padding-and-arr-init.rs @@ -25,7 +25,7 @@ fn main() { *sptr2 = [0; 4]; *sptr = S(0, 0); // Both S(u16, u16) and [u8; 4] have the same layout, so the memory is initialized. - _val = *sptr2; + _val = *sptr2; Return() } } From c8a8d1fb4335fbeb8620d76c3bb0e44862e8f617 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 17 Jun 2024 16:31:01 -0700 Subject: [PATCH 13/87] Update code to work with nightly-2024-06-17 --- .../kani_middle/transform/check_uninit/mod.rs | 36 +++++++++---------- .../transform/check_uninit/uninit_visitor.rs | 16 ++++----- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index e33b9f1229ad..ff041a7aa6ed 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -13,8 +13,8 @@ use crate::kani_middle::transform::{TransformPass, TransformationType}; use crate::kani_queries::QueryDb; use rustc_middle::ty::TyCtxt; use stable_mir::mir::mono::Instance; -use stable_mir::mir::{AggregateKind, Body, Constant, Mutability, Operand, Place, Rvalue}; -use stable_mir::ty::{Const, GenericArgKind, GenericArgs, RigidTy, Ty, TyKind, UintTy}; +use stable_mir::mir::{AggregateKind, Body, ConstOperand, Mutability, Operand, Place, Rvalue}; +use stable_mir::ty::{GenericArgKind, GenericArgs, MirConst, RigidTy, Ty, TyConst, TyKind}; use std::fmt::Debug; use tracing::{debug, trace}; @@ -137,10 +137,10 @@ impl UninitPass { .as_byte_layout() .iter() .map(|byte| { - Operand::Constant(Constant { + Operand::Constant(ConstOperand { span, user_ty: None, - literal: Const::from_bool(*byte), + const_: MirConst::from_bool(*byte), }) }) .collect(), @@ -160,9 +160,8 @@ impl UninitPass { find_fn_def(tcx, "KaniShadowMemoryGetSlice").unwrap(), &GenericArgs(vec![ GenericArgKind::Const( - Const::try_from_uint( - type_layout.as_byte_layout().len() as u128, - UintTy::Usize, + TyConst::try_from_target_usize( + type_layout.as_byte_layout().len() as u64, ) .unwrap(), ), @@ -176,9 +175,8 @@ impl UninitPass { find_fn_def(tcx, "KaniShadowMemoryGet").unwrap(), &GenericArgs(vec![ GenericArgKind::Const( - Const::try_from_uint( - type_layout.as_byte_layout().len() as u128, - UintTy::Usize, + TyConst::try_from_target_usize( + type_layout.as_byte_layout().len() as u64, ) .unwrap(), ), @@ -224,9 +222,8 @@ impl UninitPass { find_fn_def(tcx, "KaniShadowMemorySetSlice").unwrap(), &GenericArgs(vec![ GenericArgKind::Const( - Const::try_from_uint( - type_layout.as_byte_layout().len() as u128, - UintTy::Usize, + TyConst::try_from_target_usize( + type_layout.as_byte_layout().len() as u64, ) .unwrap(), ), @@ -240,9 +237,8 @@ impl UninitPass { find_fn_def(tcx, "KaniShadowMemorySet").unwrap(), &GenericArgs(vec![ GenericArgKind::Const( - Const::try_from_uint( - type_layout.as_byte_layout().len() as u128, - UintTy::Usize, + TyConst::try_from_target_usize( + type_layout.as_byte_layout().len() as u64, ) .unwrap(), ), @@ -268,10 +264,10 @@ impl UninitPass { ptr_operand, layout_operand, count, - Operand::Constant(Constant { + Operand::Constant(ConstOperand { span, user_ty: None, - literal: Const::from_bool(value), + const_: MirConst::from_bool(value), }), ], ret_place, @@ -293,8 +289,8 @@ impl UninitPass { reason: &str, ) { let span = source.span(body.blocks()); - let rvalue = Rvalue::Use(Operand::Constant(Constant { - literal: Const::from_bool(false), + let rvalue = Rvalue::Use(Operand::Constant(ConstOperand { + const_: MirConst::from_bool(false), span, user_ty: None, })); diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index 223f0da960e9..5baf1eb8d120 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -3,18 +3,18 @@ use stable_mir::mir::alloc::GlobalAlloc; use stable_mir::mir::mono::{Instance, InstanceKind}; use stable_mir::mir::visit::{Location, PlaceContext}; use stable_mir::mir::{ - BasicBlockIdx, CastKind, Constant, LocalDecl, MirVisitor, Mutability, NonDivergingIntrinsic, - Operand, Place, PointerCoercion, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, - TerminatorKind, + BasicBlockIdx, CastKind, ConstOperand, LocalDecl, MirVisitor, Mutability, + NonDivergingIntrinsic, Operand, Place, PointerCoercion, ProjectionElem, Rvalue, Statement, + StatementKind, Terminator, TerminatorKind, }; -use stable_mir::ty::{Const, ConstantKind, RigidTy, Span, TyKind, UintTy}; +use stable_mir::ty::{ConstantKind, MirConst, RigidTy, Span, TyKind, UintTy}; use strum_macros::AsRefStr; #[derive(AsRefStr, Clone, Debug)] pub enum SourceOp { Get { place: Place, count: Operand }, Set { place: Place, count: Operand, value: bool }, - BlessConst { constant: Constant, count: Operand, value: bool }, + BlessConst { constant: ConstOperand, count: Operand, value: bool }, BlessRef { place: Place, count: Operand, value: bool }, Unsupported { reason: String }, } @@ -94,10 +94,10 @@ fn expect_place(op: &Operand) -> &Place { } fn mk_const_operand(value: usize, span: Span) -> Operand { - Operand::Constant(Constant { + Operand::Constant(ConstOperand { span, user_ty: None, - literal: Const::try_from_uint(value as u128, UintTy::Usize).unwrap(), + const_: MirConst::try_from_uint(value as u128, UintTy::Usize).unwrap(), }) } @@ -381,7 +381,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { fn visit_operand(&mut self, operand: &Operand, location: Location) { match operand { - Operand::Constant(constant) => match constant.literal.kind() { + Operand::Constant(constant) => match constant.const_.kind() { ConstantKind::Allocated(allocation) => { for (_, prov) in &allocation.provenance.ptrs { match GlobalAlloc::from(prov.0) { From 087090a6a806586be0e4a39b726dd9715629c4bd Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 17 Jun 2024 16:34:33 -0700 Subject: [PATCH 14/87] Add missing copyright statements --- .../src/kani_middle/transform/check_uninit/ty_layout.rs | 5 +++++ .../src/kani_middle/transform/check_uninit/uninit_visitor.rs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs index b3528a464011..e91952770b97 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs @@ -1,3 +1,8 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +//! Utility functions that help calculate type layout. + use stable_mir::abi::{FieldsShape, Scalar, TagEncoding, ValueAbi, VariantsShape}; use stable_mir::target::{MachineInfo, MachineSize}; use stable_mir::ty::{AdtKind, IndexedVal, RigidTy, Ty, TyKind}; diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index 5baf1eb8d120..902daf477712 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -1,3 +1,8 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +//! Visitor that collects all instructions relevant to uninitialized memory access. + use crate::kani_middle::transform::body::{InsertPosition, MutableBody, SourceInstruction}; use stable_mir::mir::alloc::GlobalAlloc; use stable_mir::mir::mono::{Instance, InstanceKind}; From 73a7ccbfb90ff52124a4c18e5e69bea4365ebb6b Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 17 Jun 2024 16:35:32 -0700 Subject: [PATCH 15/87] Make clippy happy --- library/kani/src/shadow.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/kani/src/shadow.rs b/library/kani/src/shadow.rs index 44106c1b88f9..c161f0f260e1 100644 --- a/library/kani/src/shadow.rs +++ b/library/kani/src/shadow.rs @@ -102,7 +102,7 @@ fn __kani_global_sm_get_inner(ptr: *const (), layout: [bool; N], } count += 1; } - return true; + true } // Set initialization setate to `value` for `n` items laid out according to the `layout` starting at address `ptr`. From 1e55cb743acb7d2cc7a5323e1a9cf26d1df44a34 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 17 Jun 2024 16:44:02 -0700 Subject: [PATCH 16/87] Make clippy even happier --- .../kani_middle/transform/check_uninit/mod.rs | 9 +-- .../transform/check_uninit/ty_layout.rs | 14 +++-- .../transform/check_uninit/uninit_visitor.rs | 61 ++++++++----------- 3 files changed, 36 insertions(+), 48 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index ff041a7aa6ed..9aaaa253cecd 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -94,12 +94,9 @@ impl UninitPass { let mut source = instruction.source; for operation in operations { - match &operation { - SourceOp::Unsupported { reason } => { - self.unsupported_check(tcx, body, &mut source, operation.position(), &reason); - continue; - } - _ => {} + if let SourceOp::Unsupported { reason } = &operation { + self.unsupported_check(tcx, body, &mut source, operation.position(), &reason); + continue; }; let insert_position = operation.position(); diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs index e91952770b97..b6303a76b6c3 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs @@ -33,8 +33,10 @@ impl TypeLayout { let ty_size = ty.layout().unwrap().shape().size.bytes(); let mut layout_mask = vec![false; ty_size]; for data_bytes in ty_layout.iter() { - for i in data_bytes.offset..data_bytes.offset + data_bytes.size.bytes() { - layout_mask[i] = true; + for layout_item in + layout_mask.iter_mut().skip(data_bytes.offset).take(data_bytes.size.bytes()) + { + *layout_item = true; } } Ok(Self::StaticallySized { layout: layout_mask }) @@ -43,14 +45,16 @@ impl TypeLayout { let data_bytes = DataBytes { offset: 0, size: match ty.layout().unwrap().shape().fields { - FieldsShape::Array { stride, count } if count == 0 => stride, + FieldsShape::Array { stride, count: 0 } => stride, _ => MachineSize::from_bits(0), }, }; let ty_size = data_bytes.size.bytes(); let mut layout_mask = vec![false; ty_size]; - for i in data_bytes.offset..data_bytes.offset + data_bytes.size.bytes() { - layout_mask[i] = true; + for layout_item in + layout_mask.iter_mut().skip(data_bytes.offset).take(data_bytes.size.bytes()) + { + *layout_item = true; } layout_mask }; diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index 902daf477712..44e54c03b9f9 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -188,15 +188,12 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { } // Check whether Rvalue creates a new initialized pointer previously not captured inside shadow memory. if place.ty(&self.locals).unwrap().kind().is_raw_ptr() { - match rvalue { - Rvalue::AddressOf(..) => { - self.push_target(SourceOp::Set { - place: place.clone(), - count: mk_const_operand(1, location.span()), - value: true, - }); - } - _ => {} + if let Rvalue::AddressOf(..) = rvalue { + self.push_target(SourceOp::Set { + place: place.clone(), + count: mk_const_operand(1, location.span()), + value: true, + }); } } } @@ -385,40 +382,30 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { } fn visit_operand(&mut self, operand: &Operand, location: Location) { - match operand { - Operand::Constant(constant) => match constant.const_.kind() { - ConstantKind::Allocated(allocation) => { - for (_, prov) in &allocation.provenance.ptrs { - match GlobalAlloc::from(prov.0) { - GlobalAlloc::Static(static_def) => { - let dbg_string = format!("{:?}", static_def); - if dbg_string.contains("__rust_no_alloc_shim_is_unstable") { - self.push_target(SourceOp::BlessConst { - constant: constant.clone(), - count: mk_const_operand(1, location.span()), - value: true, - }); - } - } - _ => {} - }; - } + if let Operand::Constant(constant) = operand { + if let ConstantKind::Allocated(allocation) = constant.const_.kind() { + for (_, prov) in &allocation.provenance.ptrs { + if let GlobalAlloc::Static(static_def) = GlobalAlloc::from(prov.0) { + let dbg_string = format!("{:?}", static_def); + if dbg_string.contains("__rust_no_alloc_shim_is_unstable") { + self.push_target(SourceOp::BlessConst { + constant: constant.clone(), + count: mk_const_operand(1, location.span()), + value: true, + }); + } + }; } - _ => {} - }, - _ => {} + } } self.super_operand(operand, location); } fn visit_rvalue(&mut self, rvalue: &Rvalue, location: Location) { - match rvalue { - Rvalue::Cast(CastKind::PointerCoercion(PointerCoercion::Unsize), _, _) => { - self.push_target(SourceOp::Unsupported { - reason: "Kani does not support reasoning about memory initialization of unsized pointers.".to_string(), - }); - } - _ => {} + if let Rvalue::Cast(CastKind::PointerCoercion(PointerCoercion::Unsize), _, _) = rvalue { + self.push_target(SourceOp::Unsupported { + reason: "Kani does not support reasoning about memory initialization of unsized pointers.".to_string(), + }); }; self.super_rvalue(rvalue, location); } From 97ce8a284ccad6261e787ba540253fb0c06e1f0d Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Tue, 18 Jun 2024 12:34:24 -0700 Subject: [PATCH 17/87] Implement an option to enable initialization checks as a part of function contracts --- .../kani_middle/transform/check_uninit/mod.rs | 2 +- .../transform/check_uninit/uninit_visitor.rs | 15 +- .../kani_middle/transform/kani_intrinsics.rs | 130 +++++++++++++++++- library/kani/src/mem.rs | 12 +- tests/kani/Uninit/alloc-to-slice.rs | 20 +++ 5 files changed, 166 insertions(+), 13 deletions(-) create mode 100644 tests/kani/Uninit/alloc-to-slice.rs diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index 9aaaa253cecd..62db7e04dda3 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -21,7 +21,7 @@ use tracing::{debug, trace}; mod ty_layout; mod uninit_visitor; -use ty_layout::TypeLayout; +pub use ty_layout::TypeLayout; use uninit_visitor::{CheckUninitVisitor, InitRelevantInstruction, SourceOp}; const KANI_SHADOW_MEMORY_PREFIX: &str = "__kani_global_sm"; diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index 44e54c03b9f9..207517946a1f 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -385,15 +385,12 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { if let Operand::Constant(constant) = operand { if let ConstantKind::Allocated(allocation) = constant.const_.kind() { for (_, prov) in &allocation.provenance.ptrs { - if let GlobalAlloc::Static(static_def) = GlobalAlloc::from(prov.0) { - let dbg_string = format!("{:?}", static_def); - if dbg_string.contains("__rust_no_alloc_shim_is_unstable") { - self.push_target(SourceOp::BlessConst { - constant: constant.clone(), - count: mk_const_operand(1, location.span()), - value: true, - }); - } + if let GlobalAlloc::Static(_) = GlobalAlloc::from(prov.0) { + self.push_target(SourceOp::BlessConst { + constant: constant.clone(), + count: mk_const_operand(1, location.span()), + value: true, + }); }; } } diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index 9c4427847b3d..b7a4205e1d75 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -8,19 +8,22 @@ //! by the transformation. use crate::kani_middle::attributes::matches_diagnostic; +use crate::kani_middle::find_fn_def; use crate::kani_middle::transform::body::{ CheckType, InsertPosition, MutableBody, SourceInstruction, }; +use crate::kani_middle::transform::check_uninit::TypeLayout; use crate::kani_middle::transform::check_values::{build_limits, ty_validity_per_offset}; use crate::kani_middle::transform::{TransformPass, TransformationType}; use crate::kani_queries::QueryDb; use rustc_middle::ty::TyCtxt; use stable_mir::mir::mono::Instance; use stable_mir::mir::{ - BinOp, Body, ConstOperand, Operand, Place, Rvalue, Statement, StatementKind, RETURN_LOCAL, + AggregateKind, BinOp, Body, ConstOperand, Operand, Place, Rvalue, Statement, StatementKind, + RETURN_LOCAL, }; use stable_mir::target::MachineInfo; -use stable_mir::ty::{MirConst, RigidTy, TyKind}; +use stable_mir::ty::{GenericArgKind, GenericArgs, MirConst, RigidTy, Ty, TyConst, TyKind, UintTy}; use std::fmt::Debug; use strum_macros::AsRefStr; use tracing::trace; @@ -52,6 +55,8 @@ impl TransformPass for IntrinsicGeneratorPass { trace!(function=?instance.name(), "transform"); if matches_diagnostic(tcx, instance.def, Intrinsics::KaniValidValue.as_ref()) { (true, self.valid_value_body(tcx, body)) + } else if matches_diagnostic(tcx, instance.def, Intrinsics::KaniIsInitialized.as_ref()) { + (true, self.is_initialized_body(tcx, body)) } else { (false, body) } @@ -139,10 +144,131 @@ impl IntrinsicGeneratorPass { } new_body.into() } + + fn is_initialized_body(&self, tcx: TyCtxt, body: Body) -> Body { + let mut new_body = MutableBody::from(body); + new_body.clear_body(); + + // Initialize return variable with True. + let ret_var = RETURN_LOCAL; + let mut terminator = SourceInstruction::Terminator { bb: 0 }; + let span = new_body.locals()[ret_var].span; + let assign = StatementKind::Assign( + Place::from(ret_var), + Rvalue::Use(Operand::Constant(ConstOperand { + span, + user_ty: None, + const_: MirConst::from_bool(true), + })), + ); + let stmt = Statement { kind: assign, span }; + new_body.insert_stmt(stmt, &mut terminator, InsertPosition::Before); + + // The first and only argument type. + let arg_ty = new_body.locals()[1].ty; + let TyKind::RigidTy(RigidTy::RawPtr(target_ty, _)) = arg_ty.kind() else { unreachable!() }; + let type_layout = TypeLayout::get_mask(target_ty); + match type_layout { + Ok(type_layout) => { + // Given the pointer argument, call the shadow memory + let layout_operand = Operand::Move(Place { + local: new_body.new_assignment( + Rvalue::Aggregate( + AggregateKind::Array(Ty::bool_ty()), + type_layout + .as_byte_layout() + .iter() + .map(|byte| { + Operand::Constant(ConstOperand { + span, + user_ty: None, + const_: MirConst::from_bool(*byte), + }) + }) + .collect(), + ), + &mut terminator, + InsertPosition::Before, + ), + projection: vec![], + }); + let shadow_memory_get = match target_ty.kind() { + TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => { + Instance::resolve( + find_fn_def(tcx, "KaniShadowMemoryGetSlice").unwrap(), + &GenericArgs(vec![ + GenericArgKind::Const( + TyConst::try_from_target_usize( + type_layout.as_byte_layout().len() as u64, + ) + .unwrap(), + ), + GenericArgKind::Type(target_ty), + ]), + ) + .unwrap() + } + _ => Instance::resolve( + find_fn_def(tcx, "KaniShadowMemoryGet").unwrap(), + &GenericArgs(vec![ + GenericArgKind::Const( + TyConst::try_from_target_usize( + type_layout.as_byte_layout().len() as u64 + ) + .unwrap(), + ), + GenericArgKind::Type(target_ty), + ]), + ) + .unwrap(), + }; + + // Retrieve current shadow memory info. + new_body.add_call( + &shadow_memory_get, + &mut terminator, + InsertPosition::Before, + vec![ + Operand::Copy(Place::from(1)), + layout_operand, + Operand::Constant(ConstOperand { + span, + user_ty: None, + const_: MirConst::try_from_uint(1, UintTy::Usize).unwrap(), + }), + ], + Place::from(ret_var), + ); + } + Err(msg) => { + // We failed to retrieve the type layout. + let rvalue = Rvalue::Use(Operand::Constant(ConstOperand { + const_: MirConst::from_bool(false), + span, + user_ty: None, + })); + let result = + new_body.new_assignment(rvalue, &mut terminator, InsertPosition::Before); + let reason = format!( + "Kani currently doesn't support checking memory initialization of `{target_ty}`. {msg}" + ); + new_body.add_check( + tcx, + &self.check_type, + &mut terminator, + InsertPosition::Before, + result, + &reason, + ); + } + } + new_body.into() + } } #[derive(Debug, Copy, Clone, Eq, PartialEq, AsRefStr)] #[strum(serialize_all = "PascalCase")] enum Intrinsics { KaniValidValue, + KaniIsInitialized, } diff --git a/library/kani/src/mem.rs b/library/kani/src/mem.rs index c40a1aa696e3..38af1c5f78c4 100644 --- a/library/kani/src/mem.rs +++ b/library/kani/src/mem.rs @@ -121,6 +121,7 @@ where metadata.is_ptr_aligned(thin_ptr, Internal) && is_inbounds(&metadata, thin_ptr) && unsafe { has_valid_value(ptr) } + && unsafe { is_initialized(ptr) } } /// Checks that pointer `ptr` point to a valid value of type `T`. @@ -147,7 +148,9 @@ where ::Metadata: PtrProperties, { let (thin_ptr, metadata) = ptr.to_raw_parts(); - is_inbounds(&metadata, thin_ptr) && unsafe { has_valid_value(ptr) } + is_inbounds(&metadata, thin_ptr) + && unsafe { has_valid_value(ptr) } + && unsafe { is_initialized(ptr) } } /// Checks that `data_ptr` points to an allocation that can hold data of size calculated from `T`. @@ -290,6 +293,13 @@ unsafe fn has_valid_value(_ptr: *const T) -> bool { kani_intrinsic() } +/// Extract the layout of the pointee type. +#[rustc_diagnostic_item = "KaniIsInitialized"] +#[inline(never)] +unsafe fn is_initialized(_ptr: *const T) -> bool { + kani_intrinsic() +} + /// Get the object ID of the given pointer. #[rustc_diagnostic_item = "KaniPointerObject"] #[inline(never)] diff --git a/tests/kani/Uninit/alloc-to-slice.rs b/tests/kani/Uninit/alloc-to-slice.rs new file mode 100644 index 000000000000..707e34e164af --- /dev/null +++ b/tests/kani/Uninit/alloc-to-slice.rs @@ -0,0 +1,20 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z ghost-state -Z uninit-checks + +use std::alloc::{alloc, Layout}; + +#[kani::proof] +fn main() { + let layout = Layout::from_size_align(32, 8).unwrap(); + unsafe { + // This returns initialized memory. + let ptr = alloc(layout); + *ptr = 0x41; + *ptr.add(1) = 0x42; + *ptr.add(2) = 0x43; + *ptr.add(3) = 0x44; + *ptr.add(16) = 0x00; + let val = *(ptr.add(2)); + } +} From 459acba3a168f993ba9dd5deb42194e0195c3f79 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Tue, 18 Jun 2024 14:11:28 -0700 Subject: [PATCH 18/87] Separate `can_dereference` from `is_initialized` for now --- library/kani/src/mem.rs | 4 +--- tests/std-checks/core/src/ptr.rs | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/kani/src/mem.rs b/library/kani/src/mem.rs index 38af1c5f78c4..8bcf3bd60ad9 100644 --- a/library/kani/src/mem.rs +++ b/library/kani/src/mem.rs @@ -121,7 +121,6 @@ where metadata.is_ptr_aligned(thin_ptr, Internal) && is_inbounds(&metadata, thin_ptr) && unsafe { has_valid_value(ptr) } - && unsafe { is_initialized(ptr) } } /// Checks that pointer `ptr` point to a valid value of type `T`. @@ -150,7 +149,6 @@ where let (thin_ptr, metadata) = ptr.to_raw_parts(); is_inbounds(&metadata, thin_ptr) && unsafe { has_valid_value(ptr) } - && unsafe { is_initialized(ptr) } } /// Checks that `data_ptr` points to an allocation that can hold data of size calculated from `T`. @@ -296,7 +294,7 @@ unsafe fn has_valid_value(_ptr: *const T) -> bool { /// Extract the layout of the pointee type. #[rustc_diagnostic_item = "KaniIsInitialized"] #[inline(never)] -unsafe fn is_initialized(_ptr: *const T) -> bool { +pub fn is_initialized(_ptr: *const T) -> bool { kani_intrinsic() } diff --git a/tests/std-checks/core/src/ptr.rs b/tests/std-checks/core/src/ptr.rs index 49cf9e168214..e66badfbfc43 100644 --- a/tests/std-checks/core/src/ptr.rs +++ b/tests/std-checks/core/src/ptr.rs @@ -29,6 +29,7 @@ pub mod contracts { /// the pointer points to must not get mutated (except inside UnsafeCell). /// Taken from: #[requires(can_dereference(obj.as_ptr()))] + #[requires(is_initialized(obj.as_ptr()))] pub unsafe fn as_ref<'a, T>(obj: &NonNull) -> &'a T { obj.as_ref() } @@ -48,6 +49,7 @@ pub mod contracts { /// /// Note that even if `T` has size 0, the pointer must be non-null and properly aligned. #[requires(can_dereference(dst))] + #[requires(is_initialized(dst))] #[modifies(dst)] pub unsafe fn replace(dst: *mut T, src: T) -> T { std::ptr::replace(dst, src) From d3ab562a0f8006c23a36a8408fef37620a8ea821 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Tue, 18 Jun 2024 14:11:57 -0700 Subject: [PATCH 19/87] Filter out all instances without body to fix MIR dumping --- kani-compiler/src/kani_middle/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kani-compiler/src/kani_middle/mod.rs b/kani-compiler/src/kani_middle/mod.rs index ce9a80fe1f80..e44e7858b274 100644 --- a/kani-compiler/src/kani_middle/mod.rs +++ b/kani-compiler/src/kani_middle/mod.rs @@ -116,7 +116,8 @@ pub fn dump_mir_items( let mut writer = BufWriter::new(out_file); // For each def_id, dump their MIR - for instance in items.iter().filter_map(get_instance) { + for instance in items.iter().filter_map(get_instance).filter(|instance| instance.has_body()) + { writeln!(writer, "// Item: {} ({})", instance.name(), instance.mangled_name()).unwrap(); let body = transformer.body(tcx, instance); let _ = body.dump(&mut writer, &instance.name()); From 73ae7236e4b7215981f01233b45f09121c99c26f Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Tue, 18 Jun 2024 15:39:58 -0700 Subject: [PATCH 20/87] Handle intrinsics exhaustively --- .../transform/check_uninit/uninit_visitor.rs | 347 +++++++++++++++++- 1 file changed, 340 insertions(+), 7 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index 207517946a1f..0d5d695e01c9 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -239,11 +239,66 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { match instance.kind { InstanceKind::Intrinsic => { match instance.intrinsic_name().unwrap().as_str() { - "write_bytes" => { + "add_with_overflow" + | "arith_offset" + | "assert_inhabited" + | "assert_mem_uninitialized_valid" + | "assert_zero_valid" + | "assume" => {} + "atomic_and_seqcst" + | "atomic_and_acquire" + | "atomic_and_acqrel" + | "atomic_and_release" + | "atomic_and_relaxed" + | "atomic_max_seqcst" + | "atomic_max_acquire" + | "atomic_max_acqrel" + | "atomic_max_release" + | "atomic_max_relaxed" + | "atomic_min_seqcst" + | "atomic_min_acquire" + | "atomic_min_acqrel" + | "atomic_min_release" + | "atomic_min_relaxed" + | "atomic_nand_seqcst" + | "atomic_nand_acquire" + | "atomic_nand_acqrel" + | "atomic_nand_release" + | "atomic_nand_relaxed" + | "atomic_or_seqcst" + | "atomic_or_acquire" + | "atomic_or_acqrel" + | "atomic_or_release" + | "atomic_or_relaxed" + | "atomic_umax_seqcst" + | "atomic_umax_acquire" + | "atomic_umax_acqrel" + | "atomic_umax_release" + | "atomic_umax_relaxed" + | "atomic_umin_seqcst" + | "atomic_umin_acquire" + | "atomic_umin_acqrel" + | "atomic_umin_release" + | "atomic_umin_relaxed" + | "atomic_xadd_seqcst" + | "atomic_xadd_acquire" + | "atomic_xadd_acqrel" + | "atomic_xadd_release" + | "atomic_xadd_relaxed" + | "atomic_xor_seqcst" + | "atomic_xor_acquire" + | "atomic_xor_acqrel" + | "atomic_xor_release" + | "atomic_xor_relaxed" + | "atomic_xsub_seqcst" + | "atomic_xsub_acquire" + | "atomic_xsub_acqrel" + | "atomic_xsub_release" + | "atomic_xsub_relaxed" => { assert_eq!( args.len(), - 3, - "Unexpected number of arguments for `write_bytes`" + 2, + "Unexpected number of arguments for `atomic_binop`" ); assert!(matches!( args[0].ty(self.locals).unwrap().kind(), @@ -251,10 +306,73 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { )); self.push_target(SourceOp::Set { place: expect_place(&args[0]).clone(), - count: args[2].clone(), + count: mk_const_operand(1, location.span()), value: true, - }) + }); + } + "atomic_xchg_seqcst" + | "atomic_xchg_acquire" + | "atomic_xchg_acqrel" + | "atomic_xchg_release" + | "atomic_xchg_relaxed" + | "atomic_store_seqcst" + | "atomic_store_release" + | "atomic_store_relaxed" + | "atomic_store_unordered" => { + assert_eq!( + args.len(), + 2, + "Unexpected number of arguments for `atomic_store`" + ); + assert!(matches!( + args[0].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) + )); + self.push_target(SourceOp::Set { + place: expect_place(&args[0]).clone(), + count: mk_const_operand(1, location.span()), + value: true, + }); + } + "atomic_load_seqcst" + | "atomic_load_acquire" + | "atomic_load_relaxed" + | "atomic_load_unordered" => { + assert_eq!( + args.len(), + 2, + "Unexpected number of arguments for `atomic_load`" + ); + assert!(matches!( + args[0].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) + )); + self.push_target(SourceOp::Get { + place: expect_place(&args[0]).clone(), + count: mk_const_operand(1, location.span()), + }); + } + name if name.starts_with("atomic_cxchg") => { + assert_eq!( + args.len(), + 3, + "Unexpected number of arguments for `atomic_cxchg" + ); + assert!(matches!( + args[0].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) + )); + self.push_target(SourceOp::Get { + place: expect_place(&args[0]).clone(), + count: mk_const_operand(1, location.span()), + }); + } + "bitreverse" | "black_box" | "breakpoint" | "bswap" + | "caller_location" => {} + "catch_unwind" => { + unimplemented!() } + "ceilf32" | "ceilf64" => {} "compare_bytes" => { assert_eq!( args.len(), @@ -278,16 +396,231 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { count: args[2].clone(), }); } + "copy" => { + assert_eq!( + args.len(), + 3, + "Unexpected number of arguments for `copy`" + ); + assert!(matches!( + args[0].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) + )); + assert!(matches!( + args[1].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) + )); + self.push_target(SourceOp::Get { + place: expect_place(&args[0]).clone(), + count: args[2].clone(), + }); + self.push_target(SourceOp::Set { + place: expect_place(&args[1]).clone(), + count: args[2].clone(), + value: true, + }); + } + "copy_nonoverlapping" => unreachable!( + "Expected `core::intrinsics::unreachable` to be handled by `StatementKind::CopyNonOverlapping`" + ), + "copysignf32" + | "copysignf64" + | "cosf32" + | "cosf64" + | "ctlz" + | "ctlz_nonzero" + | "ctpop" + | "cttz" + | "cttz_nonzero" + | "discriminant_value" + | "exact_div" + | "exp2f32" + | "exp2f64" + | "expf32" + | "expf64" + | "fabsf32" + | "fabsf64" + | "fadd_fast" + | "fdiv_fast" + | "floorf32" + | "floorf64" + | "fmaf32" + | "fmaf64" + | "fmul_fast" + | "forget" + | "fsub_fast" + | "is_val_statically_known" + | "likely" + | "log10f32" + | "log10f64" + | "log2f32" + | "log2f64" + | "logf32" + | "logf64" + | "maxnumf32" + | "maxnumf64" + | "min_align_of" + | "min_align_of_val" + | "minnumf32" + | "minnumf64" + | "mul_with_overflow" + | "nearbyintf32" + | "nearbyintf64" + | "needs_drop" => {} + "offset" => unreachable!( + "Expected `core::intrinsics::unreachable` to be handled by `BinOp::OffSet`" + ), + "powf32" | "powf64" | "powif32" | "powif64" | "pref_align_of" => {} + "ptr_guaranteed_cmp" + | "ptr_offset_from" + | "ptr_offset_from_unsigned" => { + /* AFAICS from the documentation, none of those require the pointer arguments to be actually initialized. */ + } + "raw_eq" | "retag_box_to_raw" => { + unreachable!("This was removed in the latest Rust version.") + } + "rintf32" | "rintf64" | "rotate_left" | "rotate_right" + | "roundf32" | "roundf64" | "saturating_add" | "saturating_sub" + | "sinf32" | "sinf64" => {} + name if name.starts_with("simd") => { /* SIMD operations */ } + "size_of" => unreachable!(), + "size_of_val" => { + /* AFAICS from the documentation, this does not require the pointer argument to be initialized. */ + } + "sqrtf32" | "sqrtf64" | "sub_with_overflow" => {} "transmute" | "transmute_copy" => { unreachable!("Should've been lowered") } - _ => { /* TODO: add other intrinsics */ } + "truncf32" | "truncf64" | "type_id" | "type_name" => {} + "typed_swap" => { + assert_eq!( + args.len(), + 2, + "Unexpected number of arguments for `typed_swap`" + ); + assert!(matches!( + args[0].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) + )); + assert!(matches!( + args[1].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) + )); + self.push_target(SourceOp::Get { + place: expect_place(&args[0]).clone(), + count: mk_const_operand(1, location.span()), + }); + self.push_target(SourceOp::Get { + place: expect_place(&args[1]).clone(), + count: mk_const_operand(1, location.span()), + }); + } + "unaligned_volatile_load" => { + assert_eq!( + args.len(), + 1, + "Unexpected number of arguments for `unaligned_volatile_load`" + ); + assert!(matches!( + args[0].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) + )); + self.push_target(SourceOp::Get { + place: expect_place(&args[0]).clone(), + count: mk_const_operand(1, location.span()), + }); + } + "unchecked_add" | "unchecked_mul" | "unchecked_shl" + | "unchecked_shr" | "unchecked_sub" => { + unreachable!("Expected intrinsic to be lowered before codegen") + } + "unchecked_div" | "unchecked_rem" | "unlikely" => {} + "unreachable" => unreachable!( + "Expected `std::intrinsics::unreachable` to be handled by `TerminatorKind::Unreachable`" + ), + "volatile_copy_memory" | "volatile_copy_nonoverlapping_memory" => { + assert_eq!( + args.len(), + 3, + "Unexpected number of arguments for `volatile_copy`" + ); + assert!(matches!( + args[0].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) + )); + assert!(matches!( + args[1].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) + )); + self.push_target(SourceOp::Get { + place: expect_place(&args[0]).clone(), + count: args[2].clone(), + }); + self.push_target(SourceOp::Set { + place: expect_place(&args[1]).clone(), + count: args[2].clone(), + value: true, + }); + } + "volatile_load" => { + assert_eq!( + args.len(), + 1, + "Unexpected number of arguments for `volatile_load`" + ); + assert!(matches!( + args[0].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) + )); + self.push_target(SourceOp::Get { + place: expect_place(&args[0]).clone(), + count: mk_const_operand(1, location.span()), + }); + } + "volatile_store" => { + assert_eq!( + args.len(), + 2, + "Unexpected number of arguments for `volatile_store`" + ); + assert!(matches!( + args[0].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) + )); + self.push_target(SourceOp::Set { + place: expect_place(&args[0]).clone(), + count: mk_const_operand(1, location.span()), + value: true, + }); + } + "vtable_size" | "vtable_align" | "wrapping_add" + | "wrapping_mul" | "wrapping_sub" => {} + "write_bytes" => { + assert_eq!( + args.len(), + 3, + "Unexpected number of arguments for `write_bytes`" + ); + assert!(matches!( + args[0].ty(self.locals).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) + )); + self.push_target(SourceOp::Set { + place: expect_place(&args[0]).clone(), + count: args[2].clone(), + value: true, + }) + } + intrinsic => { + self.push_target(SourceOp::Unsupported { + reason: format!("Kani does not support reasoning about memory initialization of intrinsic `{intrinsic}`.") + }); + } } } InstanceKind::Item => { if instance.is_foreign_item() { match instance.name().as_str() { - /* TODO: implement those */ "alloc::alloc::__rust_alloc" | "alloc::alloc::__rust_realloc" => { /* Memory is uninitialized, nothing to do here. */ From 54a397020222fdebd244935b67909d2512f12231 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Tue, 18 Jun 2024 15:42:23 -0700 Subject: [PATCH 21/87] Fix code formatting --- library/kani/src/mem.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/kani/src/mem.rs b/library/kani/src/mem.rs index 8bcf3bd60ad9..efd7722e7626 100644 --- a/library/kani/src/mem.rs +++ b/library/kani/src/mem.rs @@ -147,8 +147,7 @@ where ::Metadata: PtrProperties, { let (thin_ptr, metadata) = ptr.to_raw_parts(); - is_inbounds(&metadata, thin_ptr) - && unsafe { has_valid_value(ptr) } + is_inbounds(&metadata, thin_ptr) && unsafe { has_valid_value(ptr) } } /// Checks that `data_ptr` points to an allocation that can hold data of size calculated from `T`. From 8aa56e3754439b37f65c7eef837ecece0635ecd5 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Wed, 19 Jun 2024 08:13:02 -0700 Subject: [PATCH 22/87] Disable initializations checks in `std-checks` to make CI pass --- tests/std-checks/core/src/ptr.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/std-checks/core/src/ptr.rs b/tests/std-checks/core/src/ptr.rs index e66badfbfc43..be0bbe6f4cfb 100644 --- a/tests/std-checks/core/src/ptr.rs +++ b/tests/std-checks/core/src/ptr.rs @@ -29,7 +29,7 @@ pub mod contracts { /// the pointer points to must not get mutated (except inside UnsafeCell). /// Taken from: #[requires(can_dereference(obj.as_ptr()))] - #[requires(is_initialized(obj.as_ptr()))] + // #[requires(is_initialized(obj.as_ptr()))] pub unsafe fn as_ref<'a, T>(obj: &NonNull) -> &'a T { obj.as_ref() } @@ -49,7 +49,7 @@ pub mod contracts { /// /// Note that even if `T` has size 0, the pointer must be non-null and properly aligned. #[requires(can_dereference(dst))] - #[requires(is_initialized(dst))] + // #[requires(is_initialized(dst))] #[modifies(dst)] pub unsafe fn replace(dst: *mut T, src: T) -> T { std::ptr::replace(dst, src) From aefec835adf05bd00b088121598c9aa404c5b321 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Wed, 19 Jun 2024 09:12:18 -0700 Subject: [PATCH 23/87] Automatically add global shadow memory to `modifies` contract clause --- .../codegen_cprover_gotoc/codegen/contract.rs | 25 +++++++++++++++++++ library/kani/src/shadow.rs | 1 + 2 files changed, 26 insertions(+) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs index 5494a3a666bd..7ffae6aee85f 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs @@ -118,6 +118,26 @@ impl<'tcx> GotocCtx<'tcx> { .typ .clone(); + let shadow_memory_symbol = { + let attr_id = self + .tcx + .all_diagnostic_items(()) + .name_to_id + .get(&rustc_span::symbol::Symbol::intern("KaniShadowMemory")) + .unwrap(); + let shadow_memory_table = self + .tcx + .symbol_name(rustc_middle::ty::Instance::mono(self.tcx, *attr_id)) + .name + .to_string(); + self.symbol_table + .lookup(&shadow_memory_table) + .unwrap_or_else(|| { + panic!("Static `{shadow_memory_table}` should've been declared before usage") + }) + .clone() + }; + let assigns = modified_places .into_iter() .map(|local| { @@ -127,6 +147,11 @@ impl<'tcx> GotocCtx<'tcx> { self.codegen_place_stable(&local.into(), loc).unwrap().goto_expr.dereference(), ) }) + .chain([Lambda::as_contract_for( + &goto_annotated_fn_typ, + None, + shadow_memory_symbol.to_expr(), + )]) .collect(); FunctionContract::new(assigns) diff --git a/library/kani/src/shadow.rs b/library/kani/src/shadow.rs index c161f0f260e1..dd57e39af45e 100644 --- a/library/kani/src/shadow.rs +++ b/library/kani/src/shadow.rs @@ -83,6 +83,7 @@ impl ShadowMem { } /// Global shadow memory object. +#[rustc_diagnostic_item = "KaniShadowMemory"] static mut __KANI_GLOBAL_SM: ShadowMem = ShadowMem::new(false); // Get initialization setate of `n` items laid out according to the `layout` starting at address `ptr`. From 7a72bbd6b0fdeca7d123ea8cbb0b916b1becc500 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Wed, 19 Jun 2024 09:30:59 -0700 Subject: [PATCH 24/87] Only inject shadow memory symbol if defined --- .../codegen_cprover_gotoc/codegen/contract.rs | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs index 7ffae6aee85f..a7f1317fedad 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs @@ -130,12 +130,7 @@ impl<'tcx> GotocCtx<'tcx> { .symbol_name(rustc_middle::ty::Instance::mono(self.tcx, *attr_id)) .name .to_string(); - self.symbol_table - .lookup(&shadow_memory_table) - .unwrap_or_else(|| { - panic!("Static `{shadow_memory_table}` should've been declared before usage") - }) - .clone() + self.symbol_table.lookup(&shadow_memory_table).cloned() }; let assigns = modified_places @@ -147,11 +142,17 @@ impl<'tcx> GotocCtx<'tcx> { self.codegen_place_stable(&local.into(), loc).unwrap().goto_expr.dereference(), ) }) - .chain([Lambda::as_contract_for( - &goto_annotated_fn_typ, - None, - shadow_memory_symbol.to_expr(), - )]) + .chain( + shadow_memory_symbol + .and_then(|shadow_memory_symbol| { + Some(vec![Lambda::as_contract_for( + &goto_annotated_fn_typ, + None, + shadow_memory_symbol.to_expr(), + )]) + }) + .unwrap_or(vec![]), + ) .collect(); FunctionContract::new(assigns) From 09f8ecc187289e8bcd166154e147b8c748d79c51 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Wed, 19 Jun 2024 09:36:29 -0700 Subject: [PATCH 25/87] Make clippy happy --- kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs index a7f1317fedad..37d22e5ff9cb 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs @@ -144,12 +144,12 @@ impl<'tcx> GotocCtx<'tcx> { }) .chain( shadow_memory_symbol - .and_then(|shadow_memory_symbol| { - Some(vec![Lambda::as_contract_for( + .map(|shadow_memory_symbol| { + vec![Lambda::as_contract_for( &goto_annotated_fn_typ, None, shadow_memory_symbol.to_expr(), - )]) + )] }) .unwrap_or(vec![]), ) From ca4da40a012582fe36cec697e09fc3be3b39c7f8 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Wed, 19 Jun 2024 09:57:15 -0700 Subject: [PATCH 26/87] Remove stray unwrap --- .../codegen_cprover_gotoc/codegen/contract.rs | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs index 37d22e5ff9cb..b440fcdc3513 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs @@ -118,20 +118,26 @@ impl<'tcx> GotocCtx<'tcx> { .typ .clone(); - let shadow_memory_symbol = { - let attr_id = self - .tcx - .all_diagnostic_items(()) - .name_to_id - .get(&rustc_span::symbol::Symbol::intern("KaniShadowMemory")) - .unwrap(); - let shadow_memory_table = self - .tcx - .symbol_name(rustc_middle::ty::Instance::mono(self.tcx, *attr_id)) - .name - .to_string(); - self.symbol_table.lookup(&shadow_memory_table).cloned() - }; + let shadow_memory_assign = self + .tcx + .all_diagnostic_items(()) + .name_to_id + .get(&rustc_span::symbol::Symbol::intern("KaniShadowMemory")) + .map(|attr_id| { + self.tcx + .symbol_name(rustc_middle::ty::Instance::mono(self.tcx, *attr_id)) + .name + .to_string() + }) + .and_then(|shadow_memory_table| self.symbol_table.lookup(&shadow_memory_table).cloned()) + .map(|shadow_memory_symbol| { + vec![Lambda::as_contract_for( + &goto_annotated_fn_typ, + None, + shadow_memory_symbol.to_expr(), + )] + }) + .unwrap_or_default(); let assigns = modified_places .into_iter() @@ -142,17 +148,7 @@ impl<'tcx> GotocCtx<'tcx> { self.codegen_place_stable(&local.into(), loc).unwrap().goto_expr.dereference(), ) }) - .chain( - shadow_memory_symbol - .map(|shadow_memory_symbol| { - vec![Lambda::as_contract_for( - &goto_annotated_fn_typ, - None, - shadow_memory_symbol.to_expr(), - )] - }) - .unwrap_or(vec![]), - ) + .chain(shadow_memory_assign) .collect(); FunctionContract::new(assigns) From 3bdc9497e7f0f7f9ffcdd2d6303335a14f20005f Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Wed, 19 Jun 2024 15:34:50 -0700 Subject: [PATCH 27/87] Add memory initialization checks to `std-verify` --- .../kani_middle/transform/kani_intrinsics.rs | 10 +--- library/kani/src/mem.rs | 2 +- tests/std-checks/core/Cargo.toml | 2 +- tests/std-checks/core/mem.expected | 3 - tests/std-checks/core/mem_replace.expected | 1 + tests/std-checks/core/mem_swap.expected | 22 +++++++ tests/std-checks/core/slice.expected | 1 + tests/std-checks/core/src/lib.rs | 4 +- tests/std-checks/core/src/mem_replace.rs | 57 +++++++++++++++++++ .../core/src/{mem.rs => mem_swap.rs} | 0 tests/std-checks/core/src/ptr.rs | 4 +- tests/std-checks/core/src/slice.rs | 32 +++++++++++ 12 files changed, 123 insertions(+), 15 deletions(-) delete mode 100644 tests/std-checks/core/mem.expected create mode 100644 tests/std-checks/core/mem_replace.expected create mode 100644 tests/std-checks/core/mem_swap.expected create mode 100644 tests/std-checks/core/slice.expected create mode 100644 tests/std-checks/core/src/mem_replace.rs rename tests/std-checks/core/src/{mem.rs => mem_swap.rs} (100%) create mode 100644 tests/std-checks/core/src/slice.rs diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index b7a4205e1d75..94965d6a4552 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -23,7 +23,7 @@ use stable_mir::mir::{ RETURN_LOCAL, }; use stable_mir::target::MachineInfo; -use stable_mir::ty::{GenericArgKind, GenericArgs, MirConst, RigidTy, Ty, TyConst, TyKind, UintTy}; +use stable_mir::ty::{GenericArgKind, GenericArgs, MirConst, RigidTy, Ty, TyConst, TyKind}; use std::fmt::Debug; use strum_macros::AsRefStr; use tracing::trace; @@ -164,7 +164,7 @@ impl IntrinsicGeneratorPass { let stmt = Statement { kind: assign, span }; new_body.insert_stmt(stmt, &mut terminator, InsertPosition::Before); - // The first and only argument type. + // The first argument type. let arg_ty = new_body.locals()[1].ty; let TyKind::RigidTy(RigidTy::RawPtr(target_ty, _)) = arg_ty.kind() else { unreachable!() }; let type_layout = TypeLayout::get_mask(target_ty); @@ -231,11 +231,7 @@ impl IntrinsicGeneratorPass { vec![ Operand::Copy(Place::from(1)), layout_operand, - Operand::Constant(ConstOperand { - span, - user_ty: None, - const_: MirConst::try_from_uint(1, UintTy::Usize).unwrap(), - }), + Operand::Copy(Place::from(2)), ], Place::from(ret_var), ); diff --git a/library/kani/src/mem.rs b/library/kani/src/mem.rs index efd7722e7626..06b205eae4df 100644 --- a/library/kani/src/mem.rs +++ b/library/kani/src/mem.rs @@ -293,7 +293,7 @@ unsafe fn has_valid_value(_ptr: *const T) -> bool { /// Extract the layout of the pointee type. #[rustc_diagnostic_item = "KaniIsInitialized"] #[inline(never)] -pub fn is_initialized(_ptr: *const T) -> bool { +pub fn is_initialized(_ptr: *const T, _n: usize) -> bool { kani_intrinsic() } diff --git a/tests/std-checks/core/Cargo.toml b/tests/std-checks/core/Cargo.toml index f6e1645c3a39..115ab40c90bd 100644 --- a/tests/std-checks/core/Cargo.toml +++ b/tests/std-checks/core/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" description = "This crate contains contracts and harnesses for core library" [package.metadata.kani] -unstable = { function-contracts = true, mem-predicates = true, ptr-to-ref-cast-checks = true } +unstable = { function-contracts = true, mem-predicates = true, ptr-to-ref-cast-checks = true, ghost-state = true, uninit-checks = true } [package.metadata.kani.flags] output-format = "terse" diff --git a/tests/std-checks/core/mem.expected b/tests/std-checks/core/mem.expected deleted file mode 100644 index 8a3b89a8f66a..000000000000 --- a/tests/std-checks/core/mem.expected +++ /dev/null @@ -1,3 +0,0 @@ -Summary: -Verification failed for - mem::verify::check_swap_unit -Complete - 3 successfully verified harnesses, 1 failures, 4 total. diff --git a/tests/std-checks/core/mem_replace.expected b/tests/std-checks/core/mem_replace.expected new file mode 100644 index 000000000000..9427535ab675 --- /dev/null +++ b/tests/std-checks/core/mem_replace.expected @@ -0,0 +1 @@ +Complete - 3 successfully verified harnesses, 0 failures, 3 total. diff --git a/tests/std-checks/core/mem_swap.expected b/tests/std-checks/core/mem_swap.expected new file mode 100644 index 000000000000..7c8d238994cd --- /dev/null +++ b/tests/std-checks/core/mem_swap.expected @@ -0,0 +1,22 @@ +Checking harness mem_swap::verify::check_swap_large_adt_no_drop... + +Failed Checks: The object size exceeds the maximum size supported by Kani's shadow memory model (64) + +Checking harness mem_swap::verify::check_swap_adt_no_drop... + +Failed Checks: Kani currently doesn't support checking memory initialization for `*const std::mem::MaybeUninit>` due to the following error: `Unsupported Ty { id: 1414, kind: RigidTy(Adt(AdtDef(DefId { id: 187, name: "std::mem::MaybeUninit" }), GenericArgs([Type(Ty { id: 26, kind: RigidTy(Adt(AdtDef(DefId { id: 584, name: "mem_swap::verify::CannotDrop" }), GenericArgs([Type(Ty { id: 6, kind: RigidTy(Uint(U8)) })]))) })]))) }` + +Checking harness mem_swap::verify::check_swap_unit... + +Failed Checks: ptr NULL or writable up to size + +Checking harness mem_swap::verify::check_swap_primitive... + +Failed Checks: Kani currently doesn't support checking memory initialization for `*const std::mem::MaybeUninit` due to the following error: `Unsupported Ty { id: 1402, kind: RigidTy(Adt(AdtDef(DefId { id: 187, name: "std::mem::MaybeUninit" }), GenericArgs([Type(Ty { id: 6, kind: RigidTy(Uint(U8)) })]))) }` + +Summary: +Verification failed for - mem_swap::verify::check_swap_large_adt_no_drop +Verification failed for - mem_swap::verify::check_swap_adt_no_drop +Verification failed for - mem_swap::verify::check_swap_unit +Verification failed for - mem_swap::verify::check_swap_primitive +Complete - 0 successfully verified harnesses, 4 failures, 4 total. diff --git a/tests/std-checks/core/slice.expected b/tests/std-checks/core/slice.expected new file mode 100644 index 000000000000..01a90d50b557 --- /dev/null +++ b/tests/std-checks/core/slice.expected @@ -0,0 +1 @@ +Complete - 1 successfully verified harnesses, 0 failures, 1 total. diff --git a/tests/std-checks/core/src/lib.rs b/tests/std-checks/core/src/lib.rs index f0e5b480a2d2..3c6b390dfb4f 100644 --- a/tests/std-checks/core/src/lib.rs +++ b/tests/std-checks/core/src/lib.rs @@ -5,5 +5,7 @@ extern crate kani; -pub mod mem; +pub mod mem_replace; +pub mod mem_swap; pub mod ptr; +pub mod slice; diff --git a/tests/std-checks/core/src/mem_replace.rs b/tests/std-checks/core/src/mem_replace.rs new file mode 100644 index 000000000000..039c53aa1d54 --- /dev/null +++ b/tests/std-checks/core/src/mem_replace.rs @@ -0,0 +1,57 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +/// Create wrapper functions to standard library functions that contains their contract. +pub mod contracts { + #[kani::modifies(dest)] + pub fn replace(dest: &mut T, src: T) -> T { + std::mem::replace(dest, src) + } +} + +#[cfg(kani)] +mod verify { + use super::*; + + /// Use this type to ensure that mem replace does not drop the value. + #[derive(kani::Arbitrary)] + struct CannotDrop { + inner: T, + } + + impl Drop for CannotDrop { + fn drop(&mut self) { + unreachable!("Cannot drop") + } + } + + #[kani::proof_for_contract(contracts::replace)] + pub fn check_replace_primitive() { + let mut x: u8 = kani::any(); + let x_before = x; + + let y: u8 = kani::any(); + let x_returned = contracts::replace(&mut x, y); + + kani::assert(x_before == x_returned, "x_before == x_returned"); + } + + #[kani::proof_for_contract(contracts::replace)] + pub fn check_replace_adt_no_drop() { + let mut x: CannotDrop = kani::any(); + let y: CannotDrop = kani::any(); + let new_x = contracts::replace(&mut x, y); + std::mem::forget(x); + std::mem::forget(new_x); + } + + /// Memory replace logic is optimized according to the size and alignment of a type. + #[kani::proof_for_contract(contracts::replace)] + pub fn check_replace_large_adt_no_drop() { + let mut x: CannotDrop<[u128; 4]> = kani::any(); + let y: CannotDrop<[u128; 4]> = kani::any(); + let new_x = contracts::replace(&mut x, y); + std::mem::forget(x); + std::mem::forget(new_x); + } +} diff --git a/tests/std-checks/core/src/mem.rs b/tests/std-checks/core/src/mem_swap.rs similarity index 100% rename from tests/std-checks/core/src/mem.rs rename to tests/std-checks/core/src/mem_swap.rs diff --git a/tests/std-checks/core/src/ptr.rs b/tests/std-checks/core/src/ptr.rs index be0bbe6f4cfb..94297ba35e93 100644 --- a/tests/std-checks/core/src/ptr.rs +++ b/tests/std-checks/core/src/ptr.rs @@ -29,7 +29,7 @@ pub mod contracts { /// the pointer points to must not get mutated (except inside UnsafeCell). /// Taken from: #[requires(can_dereference(obj.as_ptr()))] - // #[requires(is_initialized(obj.as_ptr()))] + #[requires(is_initialized(obj.as_ptr(), 1))] pub unsafe fn as_ref<'a, T>(obj: &NonNull) -> &'a T { obj.as_ref() } @@ -49,7 +49,7 @@ pub mod contracts { /// /// Note that even if `T` has size 0, the pointer must be non-null and properly aligned. #[requires(can_dereference(dst))] - // #[requires(is_initialized(dst))] + #[requires(is_initialized(dst, 1))] #[modifies(dst)] pub unsafe fn replace(dst: *mut T, src: T) -> T { std::ptr::replace(dst, src) diff --git a/tests/std-checks/core/src/slice.rs b/tests/std-checks/core/src/slice.rs new file mode 100644 index 000000000000..7cdde4d74c3d --- /dev/null +++ b/tests/std-checks/core/src/slice.rs @@ -0,0 +1,32 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +/// Create wrapper functions to standard library functions that contains their contract. +pub mod contracts { + use kani::{mem::*, requires}; + + #[requires(can_dereference(data))] + #[requires(is_initialized(data, len))] + pub unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T] { + std::slice::from_raw_parts(data, len) + } +} + +#[cfg(kani)] +mod verify { + use super::*; + + const LEN_MIN: usize = 1; + const LEN_MAX: usize = 4; + + #[kani::proof_for_contract(contracts::from_raw_parts)] + #[kani::unwind(25)] + pub fn check_from_raw_parts_primitive() { + let len: usize = kani::any(); + kani::assume(len >= LEN_MIN); + kani::assume(len < LEN_MAX); + + let arr = vec![0u8; len]; + let _slice = unsafe { contracts::from_raw_parts(arr.as_ptr(), len) }; + } +} From 55b62c47e8960f747dbba10c2c7dbafd47a2637a Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Wed, 19 Jun 2024 15:49:56 -0700 Subject: [PATCH 28/87] Fix `expected` files --- tests/std-checks/core/mem_swap.expected | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/std-checks/core/mem_swap.expected b/tests/std-checks/core/mem_swap.expected index 7c8d238994cd..37a7e87c6ee4 100644 --- a/tests/std-checks/core/mem_swap.expected +++ b/tests/std-checks/core/mem_swap.expected @@ -4,7 +4,7 @@ Failed Checks: The object size exceeds the maximum size supported by Kani's shad Checking harness mem_swap::verify::check_swap_adt_no_drop... -Failed Checks: Kani currently doesn't support checking memory initialization for `*const std::mem::MaybeUninit>` due to the following error: `Unsupported Ty { id: 1414, kind: RigidTy(Adt(AdtDef(DefId { id: 187, name: "std::mem::MaybeUninit" }), GenericArgs([Type(Ty { id: 26, kind: RigidTy(Adt(AdtDef(DefId { id: 584, name: "mem_swap::verify::CannotDrop" }), GenericArgs([Type(Ty { id: 6, kind: RigidTy(Uint(U8)) })]))) })]))) }` +Failed Checks: Kani currently doesn't support checking memory initialization for `*const std::mem::MaybeUninit>` Checking harness mem_swap::verify::check_swap_unit... @@ -12,7 +12,7 @@ Failed Checks: ptr NULL or writable up to size Checking harness mem_swap::verify::check_swap_primitive... -Failed Checks: Kani currently doesn't support checking memory initialization for `*const std::mem::MaybeUninit` due to the following error: `Unsupported Ty { id: 1402, kind: RigidTy(Adt(AdtDef(DefId { id: 187, name: "std::mem::MaybeUninit" }), GenericArgs([Type(Ty { id: 6, kind: RigidTy(Uint(U8)) })]))) }` +Failed Checks: Kani currently doesn't support checking memory initialization for `*const std::mem::MaybeUninit` Summary: Verification failed for - mem_swap::verify::check_swap_large_adt_no_drop From e983934cc439e459b1b57df1a05a36f4f3f61b97 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Wed, 19 Jun 2024 15:59:41 -0700 Subject: [PATCH 29/87] Adjust container size for the harness --- tests/std-checks/core/src/slice.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/std-checks/core/src/slice.rs b/tests/std-checks/core/src/slice.rs index 7cdde4d74c3d..9be19af1154b 100644 --- a/tests/std-checks/core/src/slice.rs +++ b/tests/std-checks/core/src/slice.rs @@ -17,7 +17,7 @@ mod verify { use super::*; const LEN_MIN: usize = 1; - const LEN_MAX: usize = 4; + const LEN_MAX: usize = 2; #[kani::proof_for_contract(contracts::from_raw_parts)] #[kani::unwind(25)] From 0c8ec39adf055baf324331585d71a860bd54f22f Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 20 Jun 2024 08:30:18 -0700 Subject: [PATCH 30/87] Skip initialization checks based on the attribute --- .../src/kani_middle/transform/check_uninit/mod.rs | 15 ++++++++++++--- library/kani/src/shadow.rs | 2 ++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index 62db7e04dda3..c6c6b723d47a 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -12,9 +12,11 @@ use crate::kani_middle::transform::body::{ use crate::kani_middle::transform::{TransformPass, TransformationType}; use crate::kani_queries::QueryDb; use rustc_middle::ty::TyCtxt; +use rustc_smir::rustc_internal; use stable_mir::mir::mono::Instance; use stable_mir::mir::{AggregateKind, Body, ConstOperand, Mutability, Operand, Place, Rvalue}; use stable_mir::ty::{GenericArgKind, GenericArgs, MirConst, RigidTy, Ty, TyConst, TyKind}; +use stable_mir::CrateDef; use std::fmt::Debug; use tracing::{debug, trace}; @@ -24,7 +26,7 @@ mod uninit_visitor; pub use ty_layout::TypeLayout; use uninit_visitor::{CheckUninitVisitor, InitRelevantInstruction, SourceOp}; -const KANI_SHADOW_MEMORY_PREFIX: &str = "__kani_global_sm"; +const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = &["KaniShadowMemoryGetInner", "KaniShadowMemorySetInner"]; /// Instrument the code with checks for uninitialized memory. #[derive(Debug)] @@ -51,8 +53,15 @@ impl TransformPass for UninitPass { fn transform(&self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { trace!(function=?instance.name(), "transform"); - // Need to break infinite recursion when shadow memory checks are inserted. - if instance.name().contains(KANI_SHADOW_MEMORY_PREFIX) { + // Need to break infinite recursion when shadow memory checks are inserted, + // so the internal function responsible for shadow memory checks are skipped. + if tcx + .get_diagnostic_name(rustc_internal::internal(tcx, instance.def.def_id())) + .map(|diagnostic_name| { + SKIPPED_DIAGNOSTIC_ITEMS.contains(&diagnostic_name.to_ident_string().as_str()) + }) + .unwrap_or(false) + { return (false, body); } diff --git a/library/kani/src/shadow.rs b/library/kani/src/shadow.rs index dd57e39af45e..921e8cccf7d2 100644 --- a/library/kani/src/shadow.rs +++ b/library/kani/src/shadow.rs @@ -87,6 +87,7 @@ impl ShadowMem { static mut __KANI_GLOBAL_SM: ShadowMem = ShadowMem::new(false); // Get initialization setate of `n` items laid out according to the `layout` starting at address `ptr`. +#[rustc_diagnostic_item = "KaniShadowMemoryGetInner"] fn __kani_global_sm_get_inner(ptr: *const (), layout: [bool; N], n: usize) -> bool { let mut count: usize = 0; while count < n { @@ -107,6 +108,7 @@ fn __kani_global_sm_get_inner(ptr: *const (), layout: [bool; N], } // Set initialization setate to `value` for `n` items laid out according to the `layout` starting at address `ptr`. +#[rustc_diagnostic_item = "KaniShadowMemorySetInner"] fn __kani_global_sm_set_inner( ptr: *const (), layout: [bool; N], From 29867045ce649b186b8d3cf5fe16ce577b67171c Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 20 Jun 2024 08:36:38 -0700 Subject: [PATCH 31/87] Add comment explaining `skip_first` --- kani-compiler/src/kani_middle/transform/body.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/kani-compiler/src/kani_middle/transform/body.rs b/kani-compiler/src/kani_middle/transform/body.rs index 78215069d529..874e34d7d926 100644 --- a/kani-compiler/src/kani_middle/transform/body.rs +++ b/kani-compiler/src/kani_middle/transform/body.rs @@ -38,6 +38,13 @@ pub struct MutableBody { span: Span, /// Set of basic block indices for which analyzing first statement should be skipped. + /// + /// This is necessary because some checks are inserted before the source instruction, which, + /// in turn, gets moved to the next basic block. Hence, we would not need to look at the + /// instruction again as a part of new basic block. However, if the check is inserted after + /// the source instruction, we still need to look at the first statement of the new basic block, + /// so we need to keep track of which basic blocks were created as a part of injecting checks after + /// the source instruction. skip_first: HashSet, } From 4a43c95cc1a9b4ebf6152d516b9401e6f1feda4b Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 20 Jun 2024 09:05:07 -0700 Subject: [PATCH 32/87] Cache the result of `find_fn_def` --- .../kani_middle/transform/check_uninit/mod.rs | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index c6c6b723d47a..d69e11de236e 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -11,13 +11,16 @@ use crate::kani_middle::transform::body::{ }; use crate::kani_middle::transform::{TransformPass, TransformationType}; use crate::kani_queries::QueryDb; +use lazy_static::lazy_static; use rustc_middle::ty::TyCtxt; use rustc_smir::rustc_internal; use stable_mir::mir::mono::Instance; use stable_mir::mir::{AggregateKind, Body, ConstOperand, Mutability, Operand, Place, Rvalue}; -use stable_mir::ty::{GenericArgKind, GenericArgs, MirConst, RigidTy, Ty, TyConst, TyKind}; +use stable_mir::ty::{FnDef, GenericArgKind, GenericArgs, MirConst, RigidTy, Ty, TyConst, TyKind}; use stable_mir::CrateDef; +use std::collections::HashMap; use std::fmt::Debug; +use std::sync::Mutex; use tracing::{debug, trace}; mod ty_layout; @@ -28,6 +31,19 @@ use uninit_visitor::{CheckUninitVisitor, InitRelevantInstruction, SourceOp}; const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = &["KaniShadowMemoryGetInner", "KaniShadowMemorySetInner"]; +/// Retrieve a function definition by diagnostic string, caching the result. +fn get_kani_sm_function(tcx: TyCtxt, diagnostic: &'static str) -> FnDef { + lazy_static! { + static ref KANI_SM_FUNCTIONS: Mutex> = + Mutex::new(HashMap::new()); + } + let mut kani_sm_functions = KANI_SM_FUNCTIONS.lock().unwrap(); + let entry = kani_sm_functions + .entry(diagnostic) + .or_insert_with(|| find_fn_def(tcx, diagnostic).unwrap()); + entry.clone() +} + /// Instrument the code with checks for uninitialized memory. #[derive(Debug)] pub struct UninitPass { @@ -163,7 +179,7 @@ impl UninitPass { let shadow_memory_get = match pointee_ty.kind() { TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => { Instance::resolve( - find_fn_def(tcx, "KaniShadowMemoryGetSlice").unwrap(), + get_kani_sm_function(tcx, "KaniShadowMemoryGetSlice"), &GenericArgs(vec![ GenericArgKind::Const( TyConst::try_from_target_usize( @@ -178,7 +194,7 @@ impl UninitPass { } TyKind::RigidTy(RigidTy::Dynamic(..)) => continue, // Any layout is valid when dereferencing a pointer to `dyn Trait`. _ => Instance::resolve( - find_fn_def(tcx, "KaniShadowMemoryGet").unwrap(), + get_kani_sm_function(tcx, "KaniShadowMemoryGet"), &GenericArgs(vec![ GenericArgKind::Const( TyConst::try_from_target_usize( @@ -225,7 +241,7 @@ impl UninitPass { let shadow_memory_set = match pointee_ty.kind() { TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => { Instance::resolve( - find_fn_def(tcx, "KaniShadowMemorySetSlice").unwrap(), + get_kani_sm_function(tcx, "KaniShadowMemorySetSlice"), &GenericArgs(vec![ GenericArgKind::Const( TyConst::try_from_target_usize( @@ -240,7 +256,7 @@ impl UninitPass { } TyKind::RigidTy(RigidTy::Dynamic(..)) => continue, // Any layout is valid when dereferencing a pointer to `dyn Trait`. _ => Instance::resolve( - find_fn_def(tcx, "KaniShadowMemorySet").unwrap(), + get_kani_sm_function(tcx, "KaniShadowMemorySet"), &GenericArgs(vec![ GenericArgKind::Const( TyConst::try_from_target_usize( From 405e34a1ccef03dff960710cf32c495bf5c2ecc9 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 20 Jun 2024 11:18:57 -0700 Subject: [PATCH 33/87] Refactor code re: comments --- .../kani_middle/transform/check_uninit/mod.rs | 375 ++++++++++-------- .../transform/check_uninit/ty_layout.rs | 89 +++-- .../transform/check_uninit/uninit_visitor.rs | 172 ++++---- .../kani_middle/transform/kani_intrinsics.rs | 4 +- 4 files changed, 359 insertions(+), 281 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index d69e11de236e..244255a86f6c 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -26,7 +26,7 @@ use tracing::{debug, trace}; mod ty_layout; mod uninit_visitor; -pub use ty_layout::TypeLayout; +pub use ty_layout::{TypeInfo, TypeLayout}; use uninit_visitor::{CheckUninitVisitor, InitRelevantInstruction, SourceOp}; const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = &["KaniShadowMemoryGetInner", "KaniShadowMemorySetInner"]; @@ -41,7 +41,7 @@ fn get_kani_sm_function(tcx: TyCtxt, diagnostic: &'static str) -> FnDef { let entry = kani_sm_functions .entry(diagnostic) .or_insert_with(|| find_fn_def(tcx, diagnostic).unwrap()); - entry.clone() + *entry } /// Instrument the code with checks for uninitialized memory. @@ -90,7 +90,7 @@ impl TransformPass for UninitPass { if let Some(candidate) = CheckUninitVisitor::find_next(&new_body, bb_idx, new_body.skip_first(bb_idx)) { - self.build_check(tcx, &mut new_body, candidate); + self.build_check_for_instruction(tcx, &mut new_body, candidate); bb_idx += 1 } else { bb_idx += 1; @@ -101,31 +101,36 @@ impl TransformPass for UninitPass { } impl UninitPass { - fn build_check( + fn build_check_for_instruction( &self, tcx: TyCtxt, body: &mut MutableBody, instruction: InitRelevantInstruction, ) { debug!(?instruction, "build_check"); - // Need to partition operations to make sure we add prefix operations before postfix operations - // to ensure instruction pointer shifts correctly. - let (operations_before, operations_after): (Vec<_>, Vec<_>) = instruction - .operations - .into_iter() - .partition(|operation| operation.should_be_inserted_before()); - let operations: Vec<_> = - vec![operations_before, operations_after].into_iter().flatten().collect(); - let mut source = instruction.source; - for operation in operations { - if let SourceOp::Unsupported { reason } = &operation { - self.unsupported_check(tcx, body, &mut source, operation.position(), &reason); - continue; - }; + for operation in instruction.before_instruction { + self.build_check_for_operation(tcx, body, &mut source, operation); + } + for operation in instruction.after_instruction { + self.build_check_for_operation(tcx, body, &mut source, operation); + } + } - let insert_position = operation.position(); - let ptr_operand = operation.mk_operand(body, &mut source); + fn build_check_for_operation( + &self, + tcx: TyCtxt, + body: &mut MutableBody, + source: &mut SourceInstruction, + operation: SourceOp, + ) { + if let SourceOp::Unsupported { reason, position } = &operation { + self.unsupported_check(tcx, body, source, *position, reason); + return; + }; + + let pointee_ty_info = { + let ptr_operand = operation.mk_operand(body, source); let ptr_operand_ty = ptr_operand.ty(body.locals()).unwrap(); let pointee_ty = match ptr_operand_ty.kind() { TyKind::RigidTy(RigidTy::RawPtr(pointee_ty, _)) => pointee_ty, @@ -135,171 +140,166 @@ impl UninitPass { ) } }; - - // Generate type layout for the item. - let type_layout = match TypeLayout::get_mask(pointee_ty) { - Ok(type_layout) => type_layout, - Err(err) => { + match TypeInfo::from_ty(pointee_ty) { + Ok(type_info) => type_info, + Err(_) => { let reason = format!( - "Kani currently doesn't support checking memory initialization for `{ptr_operand_ty}` due to the following error: `{err}`", + "Kani currently doesn't support checking memory initialization for pointers to `{pointee_ty}.", ); - self.unsupported_check(tcx, body, &mut source, operation.position(), &reason); - continue; + self.unsupported_check(tcx, body, source, operation.position(), &reason); + return; } - }; + } + }; - let count = operation.expect_count(); - let span = source.span(body.blocks()); - // Generate a corresponding array of data & padding bits. - let layout_operand = Operand::Move(Place { - local: body.new_assignment( - Rvalue::Aggregate( - AggregateKind::Array(Ty::bool_ty()), - type_layout - .as_byte_layout() - .iter() - .map(|byte| { - Operand::Constant(ConstOperand { - span, - user_ty: None, - const_: MirConst::from_bool(*byte), - }) - }) - .collect(), - ), - &mut source, - insert_position, - ), - projection: vec![], - }); + match operation { + SourceOp::Get { .. } => { + self.build_get_and_check(tcx, body, source, operation, pointee_ty_info) + } + SourceOp::Set { value, .. } | SourceOp::SetRef { value, .. } => { + self.build_set(tcx, body, source, operation, pointee_ty_info, value) + } + SourceOp::Unsupported { .. } => { + unreachable!() + } + } + } - match operation { - SourceOp::Get { .. } => { - // Resolve appropriate function depending on the pointer type. - let shadow_memory_get = match pointee_ty.kind() { - TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => { - Instance::resolve( - get_kani_sm_function(tcx, "KaniShadowMemoryGetSlice"), - &GenericArgs(vec![ - GenericArgKind::Const( - TyConst::try_from_target_usize( - type_layout.as_byte_layout().len() as u64, - ) - .unwrap(), - ), - GenericArgKind::Type(pointee_ty), - ]), + fn build_get_and_check( + &self, + tcx: TyCtxt, + body: &mut MutableBody, + source: &mut SourceInstruction, + operation: SourceOp, + pointee_info: TypeInfo, + ) { + let pointee_ty_layout = pointee_info.get_mask(); + // Resolve appropriate function depending on the pointer type. + let shadow_memory_get = match pointee_info.ty().kind() { + TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => { + Instance::resolve( + get_kani_sm_function(tcx, "KaniShadowMemoryGetSlice"), + &GenericArgs(vec![ + GenericArgKind::Const( + TyConst::try_from_target_usize( + pointee_ty_layout.as_byte_layout().len() as u64, ) - .unwrap() - } - TyKind::RigidTy(RigidTy::Dynamic(..)) => continue, // Any layout is valid when dereferencing a pointer to `dyn Trait`. - _ => Instance::resolve( - get_kani_sm_function(tcx, "KaniShadowMemoryGet"), - &GenericArgs(vec![ - GenericArgKind::Const( - TyConst::try_from_target_usize( - type_layout.as_byte_layout().len() as u64, - ) - .unwrap(), - ), - GenericArgKind::Type(pointee_ty), - ]), + .unwrap(), + ), + GenericArgKind::Type(*pointee_info.ty()), + ]), + ) + .unwrap() + } + TyKind::RigidTy(RigidTy::Dynamic(..)) => return, // Any layout is valid when dereferencing a pointer to `dyn Trait`. + _ => Instance::resolve( + get_kani_sm_function(tcx, "KaniShadowMemoryGet"), + &GenericArgs(vec![ + GenericArgKind::Const( + TyConst::try_from_target_usize( + pointee_ty_layout.as_byte_layout().len() as u64 ) .unwrap(), - }; + ), + GenericArgKind::Type(*pointee_info.ty()), + ]), + ) + .unwrap(), + }; - let ret_place = Place { - local: body.new_local( - Ty::bool_ty(), - source.span(body.blocks()), - Mutability::Not, - ), - projection: vec![], - }; - // Retrieve current shadow memory info. - body.add_call( - &shadow_memory_get, - &mut source, - insert_position, - vec![ptr_operand, layout_operand, count], - ret_place.clone(), - ); - // Make sure all non-padding bytes are initialized. - body.add_check( - tcx, - &self.check_type, - &mut source, - insert_position, - ret_place.local, - &format!("Undefined Behavior: Reading from an uninitialized pointer of type `{ptr_operand_ty}`"), - ) - } - SourceOp::Set { value, .. } - | SourceOp::BlessConst { value, .. } - | SourceOp::BlessRef { value, .. } => { - // Resolve appropriate function depending on the pointer type. - let shadow_memory_set = match pointee_ty.kind() { - TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => { - Instance::resolve( - get_kani_sm_function(tcx, "KaniShadowMemorySetSlice"), - &GenericArgs(vec![ - GenericArgKind::Const( - TyConst::try_from_target_usize( - type_layout.as_byte_layout().len() as u64, - ) - .unwrap(), - ), - GenericArgKind::Type(pointee_ty), - ]), + let ret_place = Place { + local: body.new_local(Ty::bool_ty(), source.span(body.blocks()), Mutability::Not), + projection: vec![], + }; + // Retrieve current shadow memory info. + let ptr_operand = operation.mk_operand(body, source); + let layout_operand = + mk_layout_operand(body, source, operation.position(), pointee_ty_layout); + body.add_call( + &shadow_memory_get, + source, + operation.position(), + vec![ptr_operand.clone(), layout_operand, operation.expect_count()], + ret_place.clone(), + ); + // Make sure all non-padding bytes are initialized. + let ptr_operand_ty = ptr_operand.ty(body.locals()).unwrap(); + body.add_check( + tcx, + &self.check_type, + source, + operation.position(), + ret_place.local, + &format!("Undefined Behavior: Reading from an uninitialized pointer of type `{ptr_operand_ty}`"), + ) + } + + fn build_set( + &self, + tcx: TyCtxt, + body: &mut MutableBody, + source: &mut SourceInstruction, + operation: SourceOp, + pointee_info: TypeInfo, + value: bool, + ) { + let pointee_ty_layout = pointee_info.get_mask(); + // Resolve appropriate function depending on the pointer type. + let shadow_memory_set = match pointee_info.ty().kind() { + TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => { + Instance::resolve( + get_kani_sm_function(tcx, "KaniShadowMemorySetSlice"), + &GenericArgs(vec![ + GenericArgKind::Const( + TyConst::try_from_target_usize( + pointee_ty_layout.as_byte_layout().len() as u64, ) - .unwrap() - } - TyKind::RigidTy(RigidTy::Dynamic(..)) => continue, // Any layout is valid when dereferencing a pointer to `dyn Trait`. - _ => Instance::resolve( - get_kani_sm_function(tcx, "KaniShadowMemorySet"), - &GenericArgs(vec![ - GenericArgKind::Const( - TyConst::try_from_target_usize( - type_layout.as_byte_layout().len() as u64, - ) - .unwrap(), - ), - GenericArgKind::Type(pointee_ty), - ]), - ) - .unwrap(), - }; - let ret_place = Place { - local: body.new_local( - Ty::new_tuple(&[]), - source.span(body.blocks()), - Mutability::Not, + .unwrap(), ), - projection: vec![], - }; - // Initialize all non-padding bytes. - body.add_call( - &shadow_memory_set, - &mut source, - insert_position, - vec![ - ptr_operand, - layout_operand, - count, - Operand::Constant(ConstOperand { - span, - user_ty: None, - const_: MirConst::from_bool(value), - }), - ], - ret_place, - ); - } - SourceOp::Unsupported { .. } => { - unreachable!() - } + GenericArgKind::Type(*pointee_info.ty()), + ]), + ) + .unwrap() } - } + TyKind::RigidTy(RigidTy::Dynamic(..)) => return, // Any layout is valid when dereferencing a pointer to `dyn Trait`. + _ => Instance::resolve( + get_kani_sm_function(tcx, "KaniShadowMemorySet"), + &GenericArgs(vec![ + GenericArgKind::Const( + TyConst::try_from_target_usize( + pointee_ty_layout.as_byte_layout().len() as u64 + ) + .unwrap(), + ), + GenericArgKind::Type(*pointee_info.ty()), + ]), + ) + .unwrap(), + }; + let ret_place = Place { + local: body.new_local(Ty::new_tuple(&[]), source.span(body.blocks()), Mutability::Not), + projection: vec![], + }; + // Initialize all non-padding bytes. + let ptr_operand = operation.mk_operand(body, source); + let layout_operand = + mk_layout_operand(body, source, operation.position(), pointee_ty_layout); + body.add_call( + &shadow_memory_set, + source, + operation.position(), + vec![ + ptr_operand, + layout_operand, + operation.expect_count(), + Operand::Constant(ConstOperand { + span: source.span(body.blocks()), + user_ty: None, + const_: MirConst::from_bool(value), + }), + ], + ret_place, + ); } fn unsupported_check( @@ -320,3 +320,32 @@ impl UninitPass { body.add_check(tcx, &self.check_type, source, position, result, reason); } } + +fn mk_layout_operand( + body: &mut MutableBody, + source: &mut SourceInstruction, + position: InsertPosition, + layout: TypeLayout, +) -> Operand { + Operand::Move(Place { + local: body.new_assignment( + Rvalue::Aggregate( + AggregateKind::Array(Ty::bool_ty()), + layout + .as_byte_layout() + .iter() + .map(|byte| { + Operand::Constant(ConstOperand { + span: source.span(body.blocks()), + user_ty: None, + const_: MirConst::from_bool(*byte), + }) + }) + .collect(), + ), + source, + position, + ), + projection: vec![], + }) +} diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs index b6303a76b6c3..6d8081b25c87 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs @@ -21,53 +21,70 @@ pub type ByteLayout = Vec; // Depending on whether the type is statically or dynamically sized, // the layout of the element or the layout of the actual type is returned. pub enum TypeLayout { - StaticallySized { layout: ByteLayout }, - DynamicallySized { element_layout: ByteLayout }, + Static { layout: ByteLayout }, + Slice { element_layout: ByteLayout }, + TraitObject, } impl TypeLayout { + // Convert type layout to a vector of byte flags. + pub fn as_byte_layout(&self) -> ByteLayout { + match self { + TypeLayout::Static { layout } => layout.clone(), + TypeLayout::Slice { element_layout } => element_layout.clone(), + TypeLayout::TraitObject => vec![], + } + } +} + +pub struct TypeInfo { + ty: Ty, + data_bytes: Vec, +} + +impl TypeInfo { + pub fn from_ty(ty: Ty) -> Result { + Ok(Self { ty, data_bytes: data_bytes_for_ty(&MachineInfo::target(), ty, 0)? }) + } + + pub fn ty(&self) -> &Ty { + &self.ty + } + /// Retrieve data layout for a type. - pub fn get_mask(ty: Ty) -> Result { - if ty.layout().unwrap().shape().is_sized() { - let ty_layout = data_bytes_for_ty(&MachineInfo::target(), ty, 0)?; - let ty_size = ty.layout().unwrap().shape().size.bytes(); + pub fn get_mask(&self) -> TypeLayout { + if self.ty.layout().unwrap().shape().is_sized() { + let ty_size = self.ty.layout().unwrap().shape().size.bytes(); let mut layout_mask = vec![false; ty_size]; - for data_bytes in ty_layout.iter() { + for data_bytes in self.data_bytes.iter() { for layout_item in layout_mask.iter_mut().skip(data_bytes.offset).take(data_bytes.size.bytes()) { *layout_item = true; } } - Ok(Self::StaticallySized { layout: layout_mask }) + TypeLayout::Static { layout: layout_mask } } else { - let layout_mask = { - let data_bytes = DataBytes { - offset: 0, - size: match ty.layout().unwrap().shape().fields { - FieldsShape::Array { stride, count: 0 } => stride, - _ => MachineSize::from_bits(0), - }, - }; - let ty_size = data_bytes.size.bytes(); - let mut layout_mask = vec![false; ty_size]; - for layout_item in - layout_mask.iter_mut().skip(data_bytes.offset).take(data_bytes.size.bytes()) - { - *layout_item = true; + match self.ty.layout().unwrap().shape().fields { + FieldsShape::Array { stride, count: 0 } => { + let layout_mask = { + let data_bytes = DataBytes { offset: 0, size: stride }; + let ty_size = data_bytes.size.bytes(); + let mut layout_mask = vec![false; ty_size]; + for layout_item in layout_mask + .iter_mut() + .skip(data_bytes.offset) + .take(data_bytes.size.bytes()) + { + *layout_item = true; + } + layout_mask + }; + TypeLayout::Slice { element_layout: layout_mask } } - layout_mask - }; - - Ok(Self::DynamicallySized { element_layout: layout_mask }) - } - } - - // Convert type layout to a vector of byte flags. - pub fn as_byte_layout(&self) -> &ByteLayout { - match self { - TypeLayout::StaticallySized { layout } => layout, - TypeLayout::DynamicallySized { element_layout } => element_layout, + FieldsShape::Arbitrary { .. } => TypeLayout::TraitObject, + _ => unreachable!(), + } } } } @@ -240,8 +257,6 @@ fn data_bytes_for_ty( } } FieldsShape::Union(_) => Err(format!("Unsupported {ty:?}")), - FieldsShape::Array { .. } => { - unreachable!("Expected dynamically sized type for {ty:?}") - } + FieldsShape::Array { .. } => Ok(vec![]), } } diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index 0d5d695e01c9..b9c49b637bd3 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -17,28 +17,30 @@ use strum_macros::AsRefStr; #[derive(AsRefStr, Clone, Debug)] pub enum SourceOp { - Get { place: Place, count: Operand }, - Set { place: Place, count: Operand, value: bool }, - BlessConst { constant: ConstOperand, count: Operand, value: bool }, - BlessRef { place: Place, count: Operand, value: bool }, - Unsupported { reason: String }, + Get { operand: Operand, count: Operand, position: InsertPosition }, + Set { operand: Operand, count: Operand, value: bool, position: InsertPosition }, + SetRef { operand: Operand, count: Operand, value: bool, position: InsertPosition }, + Unsupported { reason: String, position: InsertPosition }, } impl SourceOp { pub fn mk_operand(&self, body: &mut MutableBody, source: &mut SourceInstruction) -> Operand { match self { - SourceOp::Get { place, .. } | SourceOp::Set { place, .. } => { - Operand::Copy(place.clone()) - } - SourceOp::BlessRef { place, .. } => Operand::Copy(Place { - local: body.new_assignment( - Rvalue::AddressOf(Mutability::Not, place.clone()), - source, - self.position(), - ), + SourceOp::Get { operand, .. } | SourceOp::Set { operand, .. } => operand.clone(), + SourceOp::SetRef { operand, .. } => Operand::Copy(Place { + local: { + let place = match operand { + Operand::Copy(place) | Operand::Move(place) => place, + Operand::Constant(_) => unreachable!(), + }; + body.new_assignment( + Rvalue::AddressOf(Mutability::Not, place.clone()), + source, + self.position(), + ) + }, projection: vec![], }), - SourceOp::BlessConst { constant, .. } => Operand::Constant(constant.clone()), SourceOp::Unsupported { .. } => unreachable!(), } } @@ -47,33 +49,38 @@ impl SourceOp { match self { SourceOp::Get { count, .. } | SourceOp::Set { count, .. } - | SourceOp::BlessConst { count, .. } - | SourceOp::BlessRef { count, .. } => count.clone(), + | SourceOp::SetRef { count, .. } => count.clone(), SourceOp::Unsupported { .. } => unreachable!(), } } pub fn position(&self) -> InsertPosition { match self { - SourceOp::Get { .. } - | SourceOp::BlessConst { .. } - | SourceOp::BlessRef { .. } - | SourceOp::Unsupported { .. } => InsertPosition::Before, - SourceOp::Set { .. } => InsertPosition::After, + SourceOp::Get { position, .. } + | SourceOp::SetRef { position, .. } + | SourceOp::Unsupported { position, .. } + | SourceOp::Set { position, .. } => *position, } } - - pub fn should_be_inserted_before(&self) -> bool { - self.position() == InsertPosition::Before - } } #[derive(Clone, Debug)] pub struct InitRelevantInstruction { /// The instruction that affects the state of the memory. pub source: SourceInstruction, - /// All memory-related operations in this instructions. - pub operations: Vec, + /// All memory-related operations that should happen after the instruction. + pub before_instruction: Vec, + /// All memory-related operations that should happen after the instruction. + pub after_instruction: Vec, +} + +impl InitRelevantInstruction { + pub fn push_operation(&mut self, source_op: SourceOp) { + match source_op.position() { + InsertPosition::Before => self.before_instruction.push(source_op), + InsertPosition::After => self.after_instruction.push(source_op), + } + } } pub struct CheckUninitVisitor<'a> { @@ -91,13 +98,6 @@ pub struct CheckUninitVisitor<'a> { bb: BasicBlockIdx, } -fn expect_place(op: &Operand) -> &Place { - match op { - Operand::Copy(place) | Operand::Move(place) => place, - Operand::Constant(_) => unreachable!(), - } -} - fn mk_const_operand(value: usize, span: Span) -> Operand { Operand::Constant(ConstOperand { span, @@ -143,12 +143,13 @@ impl<'a> CheckUninitVisitor<'a> { visitor.target } - fn push_target(&mut self, op: SourceOp) { + fn push_target(&mut self, source_op: SourceOp) { let target = self.target.get_or_insert_with(|| InitRelevantInstruction { source: self.current, - operations: vec![], + after_instruction: vec![], + before_instruction: vec![], }); - target.operations.push(op); + target.push_operation(source_op); } } @@ -163,14 +164,16 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { self.super_statement(stmt, location); // Source is a *const T and it must be initialized. self.push_target(SourceOp::Get { - place: expect_place(©.src).clone(), + operand: copy.src.clone(), count: copy.count.clone(), + position: InsertPosition::Before, }); // Destimation is a *mut T so it gets initialized. self.push_target(SourceOp::Set { - place: expect_place(©.dst).clone(), + operand: copy.dst.clone(), count: copy.count.clone(), value: true, + position: InsertPosition::After, }); } StatementKind::Assign(place, rvalue) => { @@ -180,9 +183,10 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { if let Some(place_without_deref) = try_remove_topmost_deref(place) { if place_without_deref.ty(&self.locals).unwrap().kind().is_raw_ptr() { self.push_target(SourceOp::Set { - place: place_without_deref, + operand: Operand::Copy(place_without_deref), count: mk_const_operand(1, location.span()), value: true, + position: InsertPosition::After, }); } } @@ -190,9 +194,10 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { if place.ty(&self.locals).unwrap().kind().is_raw_ptr() { if let Rvalue::AddressOf(..) = rvalue { self.push_target(SourceOp::Set { - place: place.clone(), + operand: Operand::Copy(place.clone()), count: mk_const_operand(1, location.span()), value: true, + position: InsertPosition::After, }); } } @@ -200,9 +205,10 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { StatementKind::Deinit(place) => { self.super_statement(stmt, location); self.push_target(SourceOp::Set { - place: place.clone(), + operand: Operand::Copy(place.clone()), count: mk_const_operand(1, location.span()), value: false, + position: InsertPosition::After, }); } StatementKind::FakeRead(_, _) @@ -232,7 +238,10 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { Ok(instance) => instance, Err(reason) => { self.super_terminator(term, location); - self.push_target(SourceOp::Unsupported { reason }); + self.push_target(SourceOp::Unsupported { + reason, + position: InsertPosition::Before, + }); return; } }; @@ -305,9 +314,10 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); self.push_target(SourceOp::Set { - place: expect_place(&args[0]).clone(), + operand: args[0].clone(), count: mk_const_operand(1, location.span()), value: true, + position: InsertPosition::After, }); } "atomic_xchg_seqcst" @@ -329,9 +339,10 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); self.push_target(SourceOp::Set { - place: expect_place(&args[0]).clone(), + operand: args[0].clone(), count: mk_const_operand(1, location.span()), value: true, + position: InsertPosition::After, }); } "atomic_load_seqcst" @@ -348,8 +359,9 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) )); self.push_target(SourceOp::Get { - place: expect_place(&args[0]).clone(), + operand: args[0].clone(), count: mk_const_operand(1, location.span()), + position: InsertPosition::Before, }); } name if name.starts_with("atomic_cxchg") => { @@ -363,8 +375,9 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); self.push_target(SourceOp::Get { - place: expect_place(&args[0]).clone(), + operand: args[0].clone(), count: mk_const_operand(1, location.span()), + position: InsertPosition::Before, }); } "bitreverse" | "black_box" | "breakpoint" | "bswap" @@ -388,12 +401,14 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) )); self.push_target(SourceOp::Get { - place: expect_place(&args[0]).clone(), + operand: args[0].clone(), count: args[2].clone(), + position: InsertPosition::Before, }); self.push_target(SourceOp::Get { - place: expect_place(&args[1]).clone(), + operand: args[1].clone(), count: args[2].clone(), + position: InsertPosition::Before, }); } "copy" => { @@ -411,13 +426,15 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); self.push_target(SourceOp::Get { - place: expect_place(&args[0]).clone(), + operand: args[0].clone(), count: args[2].clone(), + position: InsertPosition::Before, }); self.push_target(SourceOp::Set { - place: expect_place(&args[1]).clone(), + operand: args[1].clone(), count: args[2].clone(), value: true, + position: InsertPosition::After, }); } "copy_nonoverlapping" => unreachable!( @@ -507,12 +524,14 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); self.push_target(SourceOp::Get { - place: expect_place(&args[0]).clone(), + operand: args[0].clone(), count: mk_const_operand(1, location.span()), + position: InsertPosition::Before, }); self.push_target(SourceOp::Get { - place: expect_place(&args[1]).clone(), + operand: args[1].clone(), count: mk_const_operand(1, location.span()), + position: InsertPosition::Before, }); } "unaligned_volatile_load" => { @@ -526,8 +545,9 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) )); self.push_target(SourceOp::Get { - place: expect_place(&args[0]).clone(), + operand: args[0].clone(), count: mk_const_operand(1, location.span()), + position: InsertPosition::Before, }); } "unchecked_add" | "unchecked_mul" | "unchecked_shl" @@ -553,13 +573,15 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); self.push_target(SourceOp::Get { - place: expect_place(&args[0]).clone(), + operand: args[0].clone(), count: args[2].clone(), + position: InsertPosition::Before, }); self.push_target(SourceOp::Set { - place: expect_place(&args[1]).clone(), + operand: args[1].clone(), count: args[2].clone(), value: true, + position: InsertPosition::After, }); } "volatile_load" => { @@ -573,8 +595,9 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) )); self.push_target(SourceOp::Get { - place: expect_place(&args[0]).clone(), + operand: args[0].clone(), count: mk_const_operand(1, location.span()), + position: InsertPosition::Before, }); } "volatile_store" => { @@ -588,9 +611,10 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); self.push_target(SourceOp::Set { - place: expect_place(&args[0]).clone(), + operand: args[0].clone(), count: mk_const_operand(1, location.span()), value: true, + position: InsertPosition::After, }); } "vtable_size" | "vtable_align" | "wrapping_add" @@ -606,14 +630,16 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); self.push_target(SourceOp::Set { - place: expect_place(&args[0]).clone(), + operand: args[0].clone(), count: args[2].clone(), value: true, + position: InsertPosition::After, }) } intrinsic => { self.push_target(SourceOp::Unsupported { - reason: format!("Kani does not support reasoning about memory initialization of intrinsic `{intrinsic}`.") + reason: format!("Kani does not support reasoning about memory initialization of intrinsic `{intrinsic}`."), + position: InsertPosition::Before }); } } @@ -628,17 +654,19 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { "alloc::alloc::__rust_alloc_zeroed" => { /* Memory is initialized here, need to update shadow memory. */ self.push_target(SourceOp::Set { - place: destination.clone(), + operand: Operand::Copy(destination.clone()), count: args[0].clone(), value: true, + position: InsertPosition::After, }); } "alloc::alloc::__rust_dealloc" => { /* Memory is uninitialized here, need to update shadow memory. */ self.push_target(SourceOp::Set { - place: expect_place(&args[0]).clone(), + operand: args[0].clone(), count: args[1].clone(), value: false, + position: InsertPosition::After, }); } _ => {} @@ -653,16 +681,18 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { let place_ty = place.ty(&self.locals).unwrap(); // When drop is codegen'ed, a reference is taken to the place which is later implicitly coerced to a pointer. // Hence, we need to bless this pointer as initialized. - self.push_target(SourceOp::BlessRef { - place: place.clone(), + self.push_target(SourceOp::SetRef { + operand: Operand::Copy(place.clone()), count: mk_const_operand(1, location.span()), value: true, + position: InsertPosition::Before, }); if place_ty.kind().is_raw_ptr() { self.push_target(SourceOp::Set { - place: place.clone(), + operand: Operand::Copy(place.clone()), count: mk_const_operand(1, location.span()), value: false, + position: InsertPosition::After, }); } } @@ -687,8 +717,9 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { let ptr_ty = intermediate_place.ty(self.locals).unwrap(); if ptr_ty.kind().is_raw_ptr() { self.push_target(SourceOp::Get { - place: intermediate_place.clone(), + operand: Operand::Copy(intermediate_place.clone()), count: mk_const_operand(1, location.span()), + position: InsertPosition::Before, }); } } @@ -698,6 +729,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { { self.push_target(SourceOp::Unsupported { reason: "Kani does not support reasoning about memory initialization of unions.".to_string(), + position: InsertPosition::Before }); } } @@ -719,10 +751,11 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { if let ConstantKind::Allocated(allocation) = constant.const_.kind() { for (_, prov) in &allocation.provenance.ptrs { if let GlobalAlloc::Static(_) = GlobalAlloc::from(prov.0) { - self.push_target(SourceOp::BlessConst { - constant: constant.clone(), + self.push_target(SourceOp::Set { + operand: Operand::Constant(constant.clone()), count: mk_const_operand(1, location.span()), value: true, + position: InsertPosition::Before, }); }; } @@ -735,6 +768,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { if let Rvalue::Cast(CastKind::PointerCoercion(PointerCoercion::Unsize), _, _) = rvalue { self.push_target(SourceOp::Unsupported { reason: "Kani does not support reasoning about memory initialization of unsized pointers.".to_string(), + position: InsertPosition::Before }); }; self.super_rvalue(rvalue, location); diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index 94965d6a4552..034d7bf59d52 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -12,7 +12,7 @@ use crate::kani_middle::find_fn_def; use crate::kani_middle::transform::body::{ CheckType, InsertPosition, MutableBody, SourceInstruction, }; -use crate::kani_middle::transform::check_uninit::TypeLayout; +use crate::kani_middle::transform::check_uninit::TypeInfo; use crate::kani_middle::transform::check_values::{build_limits, ty_validity_per_offset}; use crate::kani_middle::transform::{TransformPass, TransformationType}; use crate::kani_queries::QueryDb; @@ -167,7 +167,7 @@ impl IntrinsicGeneratorPass { // The first argument type. let arg_ty = new_body.locals()[1].ty; let TyKind::RigidTy(RigidTy::RawPtr(target_ty, _)) = arg_ty.kind() else { unreachable!() }; - let type_layout = TypeLayout::get_mask(target_ty); + let type_layout = TypeInfo::from_ty(target_ty).map(|type_info| type_info.get_mask()); match type_layout { Ok(type_layout) => { // Given the pointer argument, call the shadow memory From ba2d5dfe6fdc930114d70ea90efd298e6f05f506 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 20 Jun 2024 13:04:41 -0700 Subject: [PATCH 34/87] More refactoring for cleaner type layout --- .../codegen_cprover_gotoc/codegen/contract.rs | 2 +- .../kani_middle/transform/check_uninit/mod.rs | 204 ++++++++++-------- .../transform/check_uninit/ty_layout.rs | 125 ++++++----- .../kani_middle/transform/kani_intrinsics.rs | 124 ++++++----- library/kani/src/shadow.rs | 54 ++--- 5 files changed, 269 insertions(+), 240 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs index b440fcdc3513..bed10fbea0fa 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs @@ -122,7 +122,7 @@ impl<'tcx> GotocCtx<'tcx> { .tcx .all_diagnostic_items(()) .name_to_id - .get(&rustc_span::symbol::Symbol::intern("KaniShadowMemory")) + .get(&rustc_span::symbol::Symbol::intern("KaniMemInitSM")) .map(|attr_id| { self.tcx .symbol_name(rustc_middle::ty::Instance::mono(self.tcx, *attr_id)) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index 244255a86f6c..de51387df6b0 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -26,13 +26,13 @@ use tracing::{debug, trace}; mod ty_layout; mod uninit_visitor; -pub use ty_layout::{TypeInfo, TypeLayout}; +pub use ty_layout::{PointeeInfo, PointeeLayout, TypeLayout}; use uninit_visitor::{CheckUninitVisitor, InitRelevantInstruction, SourceOp}; -const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = &["KaniShadowMemoryGetInner", "KaniShadowMemorySetInner"]; +const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = &["KaniMemInitSMGetInner", "KaniMemInitSMSetInner"]; /// Retrieve a function definition by diagnostic string, caching the result. -fn get_kani_sm_function(tcx: TyCtxt, diagnostic: &'static str) -> FnDef { +pub fn get_kani_sm_function(tcx: TyCtxt, diagnostic: &'static str) -> FnDef { lazy_static! { static ref KANI_SM_FUNCTIONS: Mutex> = Mutex::new(HashMap::new()); @@ -140,7 +140,7 @@ impl UninitPass { ) } }; - match TypeInfo::from_ty(pointee_ty) { + match PointeeInfo::from_ty(pointee_ty) { Ok(type_info) => type_info, Err(_) => { let reason = format!( @@ -171,57 +171,61 @@ impl UninitPass { body: &mut MutableBody, source: &mut SourceInstruction, operation: SourceOp, - pointee_info: TypeInfo, + pointee_info: PointeeInfo, ) { - let pointee_ty_layout = pointee_info.get_mask(); - // Resolve appropriate function depending on the pointer type. - let shadow_memory_get = match pointee_info.ty().kind() { - TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => { - Instance::resolve( - get_kani_sm_function(tcx, "KaniShadowMemoryGetSlice"), + let ret_place = Place { + local: body.new_local(Ty::bool_ty(), source.span(body.blocks()), Mutability::Not), + projection: vec![], + }; + let ptr_operand = operation.mk_operand(body, source); + match pointee_info.layout() { + PointeeLayout::Static { layout } => { + let shadow_memory_get_instance = Instance::resolve( + get_kani_sm_function(tcx, "KaniMemInitSMGet"), + &GenericArgs(vec![ + GenericArgKind::Const( + TyConst::try_from_target_usize(layout.to_byte_mask().len() as u64) + .unwrap(), + ), + GenericArgKind::Type(*pointee_info.ty()), + ]), + ) + .unwrap(); + let layout_operand = mk_layout_operand(body, source, operation.position(), layout); + body.add_call( + &shadow_memory_get_instance, + source, + operation.position(), + vec![ptr_operand.clone(), layout_operand, operation.expect_count()], + ret_place.clone(), + ); + } + PointeeLayout::Slice { element_layout } => { + let shadow_memory_get_instance = Instance::resolve( + get_kani_sm_function(tcx, "KaniMemInitSMGetSlice"), &GenericArgs(vec![ GenericArgKind::Const( TyConst::try_from_target_usize( - pointee_ty_layout.as_byte_layout().len() as u64, + element_layout.to_byte_mask().len() as u64 ) .unwrap(), ), GenericArgKind::Type(*pointee_info.ty()), ]), ) - .unwrap() + .unwrap(); + let layout_operand = + mk_layout_operand(body, source, operation.position(), element_layout); + body.add_call( + &shadow_memory_get_instance, + source, + operation.position(), + vec![ptr_operand.clone(), layout_operand], + ret_place.clone(), + ); } - TyKind::RigidTy(RigidTy::Dynamic(..)) => return, // Any layout is valid when dereferencing a pointer to `dyn Trait`. - _ => Instance::resolve( - get_kani_sm_function(tcx, "KaniShadowMemoryGet"), - &GenericArgs(vec![ - GenericArgKind::Const( - TyConst::try_from_target_usize( - pointee_ty_layout.as_byte_layout().len() as u64 - ) - .unwrap(), - ), - GenericArgKind::Type(*pointee_info.ty()), - ]), - ) - .unwrap(), + PointeeLayout::TraitObject => return, }; - - let ret_place = Place { - local: body.new_local(Ty::bool_ty(), source.span(body.blocks()), Mutability::Not), - projection: vec![], - }; - // Retrieve current shadow memory info. - let ptr_operand = operation.mk_operand(body, source); - let layout_operand = - mk_layout_operand(body, source, operation.position(), pointee_ty_layout); - body.add_call( - &shadow_memory_get, - source, - operation.position(), - vec![ptr_operand.clone(), layout_operand, operation.expect_count()], - ret_place.clone(), - ); // Make sure all non-padding bytes are initialized. let ptr_operand_ty = ptr_operand.ty(body.locals()).unwrap(); body.add_check( @@ -240,66 +244,80 @@ impl UninitPass { body: &mut MutableBody, source: &mut SourceInstruction, operation: SourceOp, - pointee_info: TypeInfo, + pointee_info: PointeeInfo, value: bool, ) { - let pointee_ty_layout = pointee_info.get_mask(); - // Resolve appropriate function depending on the pointer type. - let shadow_memory_set = match pointee_info.ty().kind() { - TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => { - Instance::resolve( - get_kani_sm_function(tcx, "KaniShadowMemorySetSlice"), + let ret_place = Place { + local: body.new_local(Ty::new_tuple(&[]), source.span(body.blocks()), Mutability::Not), + projection: vec![], + }; + let ptr_operand = operation.mk_operand(body, source); + + match pointee_info.layout() { + PointeeLayout::Static { layout } => { + let shadow_memory_set_instance = Instance::resolve( + get_kani_sm_function(tcx, "KaniMemInitSMSet"), + &GenericArgs(vec![ + GenericArgKind::Const( + TyConst::try_from_target_usize(layout.to_byte_mask().len() as u64) + .unwrap(), + ), + GenericArgKind::Type(*pointee_info.ty()), + ]), + ) + .unwrap(); + let layout_operand = mk_layout_operand(body, source, operation.position(), layout); + body.add_call( + &shadow_memory_set_instance, + source, + operation.position(), + vec![ + ptr_operand, + layout_operand, + operation.expect_count(), + Operand::Constant(ConstOperand { + span: source.span(body.blocks()), + user_ty: None, + const_: MirConst::from_bool(value), + }), + ], + ret_place, + ); + } + PointeeLayout::Slice { element_layout } => { + let shadow_memory_set_instance = Instance::resolve( + get_kani_sm_function(tcx, "KaniMemInitSMSetSlice"), &GenericArgs(vec![ GenericArgKind::Const( TyConst::try_from_target_usize( - pointee_ty_layout.as_byte_layout().len() as u64, + element_layout.to_byte_mask().len() as u64 ) .unwrap(), ), GenericArgKind::Type(*pointee_info.ty()), ]), ) - .unwrap() + .unwrap(); + let layout_operand = + mk_layout_operand(body, source, operation.position(), element_layout); + body.add_call( + &shadow_memory_set_instance, + source, + operation.position(), + vec![ + ptr_operand, + layout_operand, + Operand::Constant(ConstOperand { + span: source.span(body.blocks()), + user_ty: None, + const_: MirConst::from_bool(value), + }), + ], + ret_place, + ); } - TyKind::RigidTy(RigidTy::Dynamic(..)) => return, // Any layout is valid when dereferencing a pointer to `dyn Trait`. - _ => Instance::resolve( - get_kani_sm_function(tcx, "KaniShadowMemorySet"), - &GenericArgs(vec![ - GenericArgKind::Const( - TyConst::try_from_target_usize( - pointee_ty_layout.as_byte_layout().len() as u64 - ) - .unwrap(), - ), - GenericArgKind::Type(*pointee_info.ty()), - ]), - ) - .unwrap(), + PointeeLayout::TraitObject => {} }; - let ret_place = Place { - local: body.new_local(Ty::new_tuple(&[]), source.span(body.blocks()), Mutability::Not), - projection: vec![], - }; - // Initialize all non-padding bytes. - let ptr_operand = operation.mk_operand(body, source); - let layout_operand = - mk_layout_operand(body, source, operation.position(), pointee_ty_layout); - body.add_call( - &shadow_memory_set, - source, - operation.position(), - vec![ - ptr_operand, - layout_operand, - operation.expect_count(), - Operand::Constant(ConstOperand { - span: source.span(body.blocks()), - user_ty: None, - const_: MirConst::from_bool(value), - }), - ], - ret_place, - ); } fn unsupported_check( @@ -321,18 +339,18 @@ impl UninitPass { } } -fn mk_layout_operand( +pub fn mk_layout_operand( body: &mut MutableBody, source: &mut SourceInstruction, position: InsertPosition, - layout: TypeLayout, + layout: &TypeLayout, ) -> Operand { Operand::Move(Place { local: body.new_assignment( Rvalue::Aggregate( AggregateKind::Array(Ty::bool_ty()), layout - .as_byte_layout() + .to_byte_mask() .iter() .map(|byte| { Operand::Constant(ConstOperand { diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs index 6d8081b25c87..5cbba3d700d3 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs @@ -5,7 +5,7 @@ use stable_mir::abi::{FieldsShape, Scalar, TagEncoding, ValueAbi, VariantsShape}; use stable_mir::target::{MachineInfo, MachineSize}; -use stable_mir::ty::{AdtKind, IndexedVal, RigidTy, Ty, TyKind}; +use stable_mir::ty::{AdtKind, IndexedVal, RigidTy, Ty, TyKind, UintTy}; use stable_mir::CrateDef; #[derive(Clone, Debug, Eq, PartialEq, Hash)] @@ -16,77 +16,90 @@ struct DataBytes { size: MachineSize, } -pub type ByteLayout = Vec; - -// Depending on whether the type is statically or dynamically sized, -// the layout of the element or the layout of the actual type is returned. -pub enum TypeLayout { - Static { layout: ByteLayout }, - Slice { element_layout: ByteLayout }, - TraitObject, +pub struct TypeLayout { + size_in_bytes: usize, + data_chunks: Vec, } impl TypeLayout { - // Convert type layout to a vector of byte flags. - pub fn as_byte_layout(&self) -> ByteLayout { - match self { - TypeLayout::Static { layout } => layout.clone(), - TypeLayout::Slice { element_layout } => element_layout.clone(), - TypeLayout::TraitObject => vec![], + pub fn to_byte_mask(&self) -> Vec { + let mut layout_mask = vec![false; self.size_in_bytes]; + for data_bytes in self.data_chunks.iter() { + for layout_item in + layout_mask.iter_mut().skip(data_bytes.offset).take(data_bytes.size.bytes()) + { + *layout_item = true; + } } + layout_mask } } -pub struct TypeInfo { - ty: Ty, - data_bytes: Vec, +// Depending on whether the type is statically or dynamically sized, +// the layout of the element or the layout of the actual type is returned. +pub enum PointeeLayout { + Static { layout: TypeLayout }, + Slice { element_layout: TypeLayout }, + TraitObject, } -impl TypeInfo { - pub fn from_ty(ty: Ty) -> Result { - Ok(Self { ty, data_bytes: data_bytes_for_ty(&MachineInfo::target(), ty, 0)? }) - } - - pub fn ty(&self) -> &Ty { - &self.ty - } +pub struct PointeeInfo { + pointee_ty: Ty, + layout: PointeeLayout, +} - /// Retrieve data layout for a type. - pub fn get_mask(&self) -> TypeLayout { - if self.ty.layout().unwrap().shape().is_sized() { - let ty_size = self.ty.layout().unwrap().shape().size.bytes(); - let mut layout_mask = vec![false; ty_size]; - for data_bytes in self.data_bytes.iter() { - for layout_item in - layout_mask.iter_mut().skip(data_bytes.offset).take(data_bytes.size.bytes()) - { - *layout_item = true; +impl PointeeInfo { + pub fn from_ty(ty: Ty) -> Result { + match ty.kind() { + TyKind::RigidTy(rigid_ty) => match rigid_ty { + RigidTy::Str => { + let slicee_ty = Ty::unsigned_ty(UintTy::U8); + let size_in_bytes = slicee_ty.layout().unwrap().shape().size.bytes(); + let data_chunks = data_bytes_for_ty(&MachineInfo::target(), slicee_ty, 0)?; + let layout = PointeeLayout::Slice { + element_layout: TypeLayout { size_in_bytes, data_chunks }, + }; + Ok(PointeeInfo { pointee_ty: ty, layout }) } - } - TypeLayout::Static { layout: layout_mask } - } else { - match self.ty.layout().unwrap().shape().fields { - FieldsShape::Array { stride, count: 0 } => { - let layout_mask = { - let data_bytes = DataBytes { offset: 0, size: stride }; - let ty_size = data_bytes.size.bytes(); - let mut layout_mask = vec![false; ty_size]; - for layout_item in layout_mask - .iter_mut() - .skip(data_bytes.offset) - .take(data_bytes.size.bytes()) - { - *layout_item = true; - } - layout_mask + RigidTy::Slice(slicee_ty) => { + let size_in_bytes = slicee_ty.layout().unwrap().shape().size.bytes(); + let data_chunks = data_bytes_for_ty(&MachineInfo::target(), slicee_ty, 0)?; + let layout = PointeeLayout::Slice { + element_layout: TypeLayout { size_in_bytes, data_chunks }, }; - TypeLayout::Slice { element_layout: layout_mask } + Ok(PointeeInfo { pointee_ty: ty, layout }) + } + RigidTy::Dynamic(..) => { + Ok(PointeeInfo { pointee_ty: ty, layout: PointeeLayout::TraitObject }) } - FieldsShape::Arbitrary { .. } => TypeLayout::TraitObject, - _ => unreachable!(), + _ => { + if ty.layout().unwrap().shape().is_sized() { + let size_in_bytes = ty.layout().unwrap().shape().size.bytes(); + let data_chunks = data_bytes_for_ty(&MachineInfo::target(), ty, 0)?; + let layout = PointeeLayout::Static { + layout: TypeLayout { size_in_bytes, data_chunks }, + }; + Ok(PointeeInfo { pointee_ty: ty, layout }) + } else { + Err( + "Can only determine unsized type layout information for slices and trait objects.".to_string(), + ) + } + } + }, + TyKind::Alias(..) | TyKind::Param(..) | TyKind::Bound(..) => { + Err("Can only determine type layout information for RigidTy.".to_string()) } } } + + pub fn ty(&self) -> &Ty { + &self.pointee_ty + } + + pub fn layout(&self) -> &PointeeLayout { + &self.layout + } } /// Get a size of an initialized scalar. diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index 034d7bf59d52..91bb0e9dd1e5 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -8,26 +8,26 @@ //! by the transformation. use crate::kani_middle::attributes::matches_diagnostic; -use crate::kani_middle::find_fn_def; use crate::kani_middle::transform::body::{ CheckType, InsertPosition, MutableBody, SourceInstruction, }; -use crate::kani_middle::transform::check_uninit::TypeInfo; +use crate::kani_middle::transform::check_uninit::PointeeInfo; use crate::kani_middle::transform::check_values::{build_limits, ty_validity_per_offset}; use crate::kani_middle::transform::{TransformPass, TransformationType}; use crate::kani_queries::QueryDb; use rustc_middle::ty::TyCtxt; use stable_mir::mir::mono::Instance; use stable_mir::mir::{ - AggregateKind, BinOp, Body, ConstOperand, Operand, Place, Rvalue, Statement, StatementKind, - RETURN_LOCAL, + BinOp, Body, ConstOperand, Operand, Place, Rvalue, Statement, StatementKind, RETURN_LOCAL, }; use stable_mir::target::MachineInfo; -use stable_mir::ty::{GenericArgKind, GenericArgs, MirConst, RigidTy, Ty, TyConst, TyKind}; +use stable_mir::ty::{GenericArgKind, GenericArgs, MirConst, RigidTy, TyConst, TyKind}; use std::fmt::Debug; use strum_macros::AsRefStr; use tracing::trace; +use super::check_uninit::{get_kani_sm_function, mk_layout_operand, PointeeLayout}; + /// Generate the body for a few Kani intrinsics. #[derive(Debug)] pub struct IntrinsicGeneratorPass { @@ -167,74 +167,72 @@ impl IntrinsicGeneratorPass { // The first argument type. let arg_ty = new_body.locals()[1].ty; let TyKind::RigidTy(RigidTy::RawPtr(target_ty, _)) = arg_ty.kind() else { unreachable!() }; - let type_layout = TypeInfo::from_ty(target_ty).map(|type_info| type_info.get_mask()); - match type_layout { - Ok(type_layout) => { - // Given the pointer argument, call the shadow memory - let layout_operand = Operand::Move(Place { - local: new_body.new_assignment( - Rvalue::Aggregate( - AggregateKind::Array(Ty::bool_ty()), - type_layout - .as_byte_layout() - .iter() - .map(|byte| { - Operand::Constant(ConstOperand { - span, - user_ty: None, - const_: MirConst::from_bool(*byte), - }) - }) - .collect(), - ), - &mut terminator, - InsertPosition::Before, - ), - projection: vec![], - }); - let shadow_memory_get = match target_ty.kind() { - TyKind::RigidTy(RigidTy::Slice(_)) | TyKind::RigidTy(RigidTy::Str) => { - Instance::resolve( - find_fn_def(tcx, "KaniShadowMemoryGetSlice").unwrap(), + let pointee_info = PointeeInfo::from_ty(target_ty); + match pointee_info { + Ok(pointee_info) => { + match pointee_info.layout() { + PointeeLayout::Static { layout } => { + let shadow_memory_get_instance = Instance::resolve( + get_kani_sm_function(tcx, "KaniMemInitSMGet"), + &GenericArgs(vec![ + GenericArgKind::Const( + TyConst::try_from_target_usize( + layout.to_byte_mask().len() as u64 + ) + .unwrap(), + ), + GenericArgKind::Type(*pointee_info.ty()), + ]), + ) + .unwrap(); + let layout_operand = mk_layout_operand( + &mut new_body, + &mut terminator, + InsertPosition::Before, + layout, + ); + new_body.add_call( + &shadow_memory_get_instance, + &mut terminator, + InsertPosition::Before, + vec![ + Operand::Copy(Place::from(1)), + layout_operand, + Operand::Copy(Place::from(2)), + ], + Place::from(ret_var), + ); + } + PointeeLayout::Slice { element_layout } => { + let shadow_memory_get_instance = Instance::resolve( + get_kani_sm_function(tcx, "KaniMemInitSMGetSlice"), &GenericArgs(vec![ GenericArgKind::Const( TyConst::try_from_target_usize( - type_layout.as_byte_layout().len() as u64, + element_layout.to_byte_mask().len() as u64, ) .unwrap(), ), - GenericArgKind::Type(target_ty), + GenericArgKind::Type(*pointee_info.ty()), ]), ) - .unwrap() + .unwrap(); + let layout_operand = mk_layout_operand( + &mut new_body, + &mut terminator, + InsertPosition::Before, + element_layout, + ); + new_body.add_call( + &shadow_memory_get_instance, + &mut terminator, + InsertPosition::Before, + vec![Operand::Copy(Place::from(1)), layout_operand], + Place::from(ret_var), + ); } - _ => Instance::resolve( - find_fn_def(tcx, "KaniShadowMemoryGet").unwrap(), - &GenericArgs(vec![ - GenericArgKind::Const( - TyConst::try_from_target_usize( - type_layout.as_byte_layout().len() as u64 - ) - .unwrap(), - ), - GenericArgKind::Type(target_ty), - ]), - ) - .unwrap(), + PointeeLayout::TraitObject => unimplemented!(), }; - - // Retrieve current shadow memory info. - new_body.add_call( - &shadow_memory_get, - &mut terminator, - InsertPosition::Before, - vec![ - Operand::Copy(Place::from(1)), - layout_operand, - Operand::Copy(Place::from(2)), - ], - Place::from(ret_var), - ); } Err(msg) => { // We failed to retrieve the type layout. diff --git a/library/kani/src/shadow.rs b/library/kani/src/shadow.rs index 921e8cccf7d2..71e0aa496196 100644 --- a/library/kani/src/shadow.rs +++ b/library/kani/src/shadow.rs @@ -82,20 +82,24 @@ impl ShadowMem { } } -/// Global shadow memory object. -#[rustc_diagnostic_item = "KaniShadowMemory"] -static mut __KANI_GLOBAL_SM: ShadowMem = ShadowMem::new(false); +/// Global shadow memory object for tracking memory initialization. +#[rustc_diagnostic_item = "KaniMemInitSM"] +static mut __KANI_MEM_INIT_SM: ShadowMem = ShadowMem::new(false); // Get initialization setate of `n` items laid out according to the `layout` starting at address `ptr`. -#[rustc_diagnostic_item = "KaniShadowMemoryGetInner"] -fn __kani_global_sm_get_inner(ptr: *const (), layout: [bool; N], n: usize) -> bool { +#[rustc_diagnostic_item = "KaniMemInitSMGetInner"] +fn __kani_mem_init_sm_get_inner( + ptr: *const (), + layout: [bool; N], + n: usize, +) -> bool { let mut count: usize = 0; while count < n { let mut offset: usize = 0; while offset < N { unsafe { if layout[offset] - && !__KANI_GLOBAL_SM.get((ptr as *const u8).add(count * N + offset)) + && !__KANI_MEM_INIT_SM.get((ptr as *const u8).add(count * N + offset)) { return false; } @@ -108,8 +112,8 @@ fn __kani_global_sm_get_inner(ptr: *const (), layout: [bool; N], } // Set initialization setate to `value` for `n` items laid out according to the `layout` starting at address `ptr`. -#[rustc_diagnostic_item = "KaniShadowMemorySetInner"] -fn __kani_global_sm_set_inner( +#[rustc_diagnostic_item = "KaniMemInitSMSetInner"] +fn __kani_mem_init_sm_set_inner( ptr: *const (), layout: [bool; N], n: usize, @@ -120,7 +124,7 @@ fn __kani_global_sm_set_inner( let mut offset: usize = 0; while offset < N { unsafe { - __KANI_GLOBAL_SM + __KANI_MEM_INIT_SM .set((ptr as *const u8).add(count * N + offset), value && layout[offset]); } offset += 1; @@ -129,52 +133,48 @@ fn __kani_global_sm_set_inner( } } -#[rustc_diagnostic_item = "KaniShadowMemoryGet"] -pub fn __kani_global_sm_get( +#[rustc_diagnostic_item = "KaniMemInitSMGet"] +pub fn __kani_mem_init_sm_get( ptr: *const T, layout: [bool; N], n: usize, ) -> bool { let (ptr, _) = ptr.to_raw_parts(); - __kani_global_sm_get_inner(ptr, layout, n) + __kani_mem_init_sm_get_inner(ptr, layout, n) } -#[rustc_diagnostic_item = "KaniShadowMemorySet"] -pub fn __kani_global_sm_set( +#[rustc_diagnostic_item = "KaniMemInitSMSet"] +pub fn __kani_mem_init_sm_set( ptr: *const T, layout: [bool; N], n: usize, value: bool, ) { let (ptr, _) = ptr.to_raw_parts(); - __kani_global_sm_set_inner(ptr, layout, n, value); + __kani_mem_init_sm_set_inner(ptr, layout, n, value); } // This method should only be called if T is known to be a slice. -#[rustc_diagnostic_item = "KaniShadowMemoryGetSlice"] -pub fn __kani_global_sm_get_slice( +#[rustc_diagnostic_item = "KaniMemInitSMGetSlice"] +pub fn __kani_mem_init_sm_get_slice( ptr: *const T, layout: [bool; N], - n: usize, ) -> bool { let (ptr, meta) = ptr.to_raw_parts(); - let meta: usize = unsafe { std::mem::transmute_copy(&meta) }; + let n: usize = unsafe { std::mem::transmute_copy(&meta) }; // The pointee type is a slice, more than `n` objects can be accessed. - let n = n * meta; - __kani_global_sm_get_inner(ptr, layout, n) + __kani_mem_init_sm_get_inner(ptr, layout, n) } // This method should only be called if T is known to be a slice. -#[rustc_diagnostic_item = "KaniShadowMemorySetSlice"] -pub fn __kani_global_sm_set_slice( +#[rustc_diagnostic_item = "KaniMemInitSMSetSlice"] +pub fn __kani_mem_init_sm_set_slice( ptr: *const T, layout: [bool; N], - n: usize, value: bool, ) { let (ptr, meta) = ptr.to_raw_parts(); - let meta: usize = unsafe { std::mem::transmute_copy(&meta) }; + let n: usize = unsafe { std::mem::transmute_copy(&meta) }; // The pointee type is a slice, more than `n` objects can be accessed. - let n = n * meta; - __kani_global_sm_set_inner(ptr, layout, n, value); + __kani_mem_init_sm_set_inner(ptr, layout, n, value); } From a729cd20a9f7481189eaabc06165438f39891a5d Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 20 Jun 2024 13:18:12 -0700 Subject: [PATCH 35/87] Undo temporary fix to `--emit mir` --- kani-compiler/src/kani_middle/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kani-compiler/src/kani_middle/mod.rs b/kani-compiler/src/kani_middle/mod.rs index e44e7858b274..ce9a80fe1f80 100644 --- a/kani-compiler/src/kani_middle/mod.rs +++ b/kani-compiler/src/kani_middle/mod.rs @@ -116,8 +116,7 @@ pub fn dump_mir_items( let mut writer = BufWriter::new(out_file); // For each def_id, dump their MIR - for instance in items.iter().filter_map(get_instance).filter(|instance| instance.has_body()) - { + for instance in items.iter().filter_map(get_instance) { writeln!(writer, "// Item: {} ({})", instance.name(), instance.mangled_name()).unwrap(); let body = transformer.body(tcx, instance); let _ = body.dump(&mut writer, &instance.name()); From c61b16d5b81bd11e4c05736d6a6fe9cc695d58f7 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 20 Jun 2024 14:51:33 -0700 Subject: [PATCH 36/87] Move `skip_first` from MutableBody to transformation pass --- .../src/kani_middle/transform/body.rs | 17 ------ .../kani_middle/transform/check_uninit/mod.rs | 53 +++++++++++++++---- .../transform/check_uninit/uninit_visitor.rs | 7 +++ 3 files changed, 51 insertions(+), 26 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/body.rs b/kani-compiler/src/kani_middle/transform/body.rs index 874e34d7d926..6c846d862920 100644 --- a/kani-compiler/src/kani_middle/transform/body.rs +++ b/kani-compiler/src/kani_middle/transform/body.rs @@ -8,7 +8,6 @@ use rustc_middle::ty::TyCtxt; use stable_mir::mir::mono::Instance; use stable_mir::mir::*; use stable_mir::ty::{GenericArgs, MirConst, Span, Ty, UintTy}; -use std::collections::HashSet; use std::fmt::Debug; use std::mem; @@ -36,16 +35,6 @@ pub struct MutableBody { /// The span that covers the entire function body. span: Span, - - /// Set of basic block indices for which analyzing first statement should be skipped. - /// - /// This is necessary because some checks are inserted before the source instruction, which, - /// in turn, gets moved to the next basic block. Hence, we would not need to look at the - /// instruction again as a part of new basic block. However, if the check is inserted after - /// the source instruction, we still need to look at the first statement of the new basic block, - /// so we need to keep track of which basic blocks were created as a part of injecting checks after - /// the source instruction. - skip_first: HashSet, } /// Denotes whether instrumentation should be inserted before or after the statement. @@ -74,14 +63,9 @@ impl MutableBody { blocks: body.blocks, var_debug_info: body.var_debug_info, span: body.span, - skip_first: HashSet::new(), } } - pub fn skip_first(&self, bb_idx: usize) -> bool { - self.skip_first.contains(&bb_idx) - } - /// Create the new body consuming this mutable body. pub fn into(self) -> Body { Body::new( @@ -272,7 +256,6 @@ impl MutableBody { let remaining = bb_stmts.split_off(idx); let new_bb = BasicBlock { statements: remaining, terminator: old_term }; self.blocks.push(new_bb); - self.skip_first.insert(new_bb_idx); } InsertPosition::After => { let span = source.span(&self.blocks); diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index de51387df6b0..9b26965123b9 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -18,7 +18,7 @@ use stable_mir::mir::mono::Instance; use stable_mir::mir::{AggregateKind, Body, ConstOperand, Mutability, Operand, Place, Rvalue}; use stable_mir::ty::{FnDef, GenericArgKind, GenericArgs, MirConst, RigidTy, Ty, TyConst, TyKind}; use stable_mir::CrateDef; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fmt::Debug; use std::sync::Mutex; use tracing::{debug, trace}; @@ -84,13 +84,23 @@ impl TransformPass for UninitPass { let mut new_body = MutableBody::from(body); let orig_len = new_body.blocks().len(); + // Set of basic block indices for which analyzing first statement should be skipped. + // + // This is necessary because some checks are inserted before the source instruction, which, in + // turn, gets moved to the next basic block. Hence, we would not need to look at the + // instruction again as a part of new basic block. However, if the check is inserted after the + // source instruction, we still need to look at the first statement of the new basic block, so + // we need to keep track of which basic blocks were created as a part of injecting checks after + // the source instruction. + let mut skip_first = HashSet::new(); + // Do not cache body.blocks().len() since it will change as we add new checks. let mut bb_idx = 0; while bb_idx < new_body.blocks().len() { if let Some(candidate) = - CheckUninitVisitor::find_next(&new_body, bb_idx, new_body.skip_first(bb_idx)) + CheckUninitVisitor::find_next(&new_body, bb_idx, skip_first.contains(&bb_idx)) { - self.build_check_for_instruction(tcx, &mut new_body, candidate); + self.build_check_for_instruction(tcx, &mut new_body, candidate, &mut skip_first); bb_idx += 1 } else { bb_idx += 1; @@ -106,14 +116,15 @@ impl UninitPass { tcx: TyCtxt, body: &mut MutableBody, instruction: InitRelevantInstruction, + skip_first: &mut HashSet, ) { debug!(?instruction, "build_check"); let mut source = instruction.source; for operation in instruction.before_instruction { - self.build_check_for_operation(tcx, body, &mut source, operation); + self.build_check_for_operation(tcx, body, &mut source, operation, skip_first); } for operation in instruction.after_instruction { - self.build_check_for_operation(tcx, body, &mut source, operation); + self.build_check_for_operation(tcx, body, &mut source, operation, skip_first); } } @@ -123,8 +134,10 @@ impl UninitPass { body: &mut MutableBody, source: &mut SourceInstruction, operation: SourceOp, + skip_first: &mut HashSet, ) { if let SourceOp::Unsupported { reason, position } = &operation { + try_mark_new_bb_as_skipped(&operation, body, skip_first); self.unsupported_check(tcx, body, source, *position, reason); return; }; @@ -146,6 +159,7 @@ impl UninitPass { let reason = format!( "Kani currently doesn't support checking memory initialization for pointers to `{pointee_ty}.", ); + try_mark_new_bb_as_skipped(&operation, body, skip_first); self.unsupported_check(tcx, body, source, operation.position(), &reason); return; } @@ -154,10 +168,10 @@ impl UninitPass { match operation { SourceOp::Get { .. } => { - self.build_get_and_check(tcx, body, source, operation, pointee_ty_info) + self.build_get_and_check(tcx, body, source, operation, pointee_ty_info, skip_first) } - SourceOp::Set { value, .. } | SourceOp::SetRef { value, .. } => { - self.build_set(tcx, body, source, operation, pointee_ty_info, value) + SourceOp::Set { .. } | SourceOp::SetRef { .. } => { + self.build_set(tcx, body, source, operation, pointee_ty_info, skip_first) } SourceOp::Unsupported { .. } => { unreachable!() @@ -172,6 +186,7 @@ impl UninitPass { source: &mut SourceInstruction, operation: SourceOp, pointee_info: PointeeInfo, + skip_first: &mut HashSet, ) { let ret_place = Place { local: body.new_local(Ty::bool_ty(), source.span(body.blocks()), Mutability::Not), @@ -192,6 +207,7 @@ impl UninitPass { ) .unwrap(); let layout_operand = mk_layout_operand(body, source, operation.position(), layout); + try_mark_new_bb_as_skipped(&operation, body, skip_first); body.add_call( &shadow_memory_get_instance, source, @@ -216,6 +232,7 @@ impl UninitPass { .unwrap(); let layout_operand = mk_layout_operand(body, source, operation.position(), element_layout); + try_mark_new_bb_as_skipped(&operation, body, skip_first); body.add_call( &shadow_memory_get_instance, source, @@ -226,7 +243,9 @@ impl UninitPass { } PointeeLayout::TraitObject => return, }; + // Make sure all non-padding bytes are initialized. + try_mark_new_bb_as_skipped(&operation, body, skip_first); let ptr_operand_ty = ptr_operand.ty(body.locals()).unwrap(); body.add_check( tcx, @@ -245,13 +264,14 @@ impl UninitPass { source: &mut SourceInstruction, operation: SourceOp, pointee_info: PointeeInfo, - value: bool, + skip_first: &mut HashSet, ) { let ret_place = Place { local: body.new_local(Ty::new_tuple(&[]), source.span(body.blocks()), Mutability::Not), projection: vec![], }; let ptr_operand = operation.mk_operand(body, source); + let value = operation.expect_value(); match pointee_info.layout() { PointeeLayout::Static { layout } => { @@ -267,6 +287,7 @@ impl UninitPass { ) .unwrap(); let layout_operand = mk_layout_operand(body, source, operation.position(), layout); + try_mark_new_bb_as_skipped(&operation, body, skip_first); body.add_call( &shadow_memory_set_instance, source, @@ -300,6 +321,7 @@ impl UninitPass { .unwrap(); let layout_operand = mk_layout_operand(body, source, operation.position(), element_layout); + try_mark_new_bb_as_skipped(&operation, body, skip_first); body.add_call( &shadow_memory_set_instance, source, @@ -367,3 +389,16 @@ pub fn mk_layout_operand( projection: vec![], }) } + +/// If injecting a new call to the function before the current statement, need to skip the original +/// statement when analyzing it as a part of the new basic block. +fn try_mark_new_bb_as_skipped( + operation: &SourceOp, + body: &MutableBody, + skip_first: &mut HashSet, +) { + if operation.position() == InsertPosition::Before { + let new_bb_idx = body.blocks().len(); + skip_first.insert(new_bb_idx); + } +} diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index b9c49b637bd3..9dbcea96d4df 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -54,6 +54,13 @@ impl SourceOp { } } + pub fn expect_value(&self) -> bool { + match self { + SourceOp::Set { value, .. } | SourceOp::SetRef { value, .. } => *value, + SourceOp::Get { .. } | SourceOp::Unsupported { .. } => unreachable!(), + } + } + pub fn position(&self) -> InsertPosition { match self { SourceOp::Get { position, .. } From 4feda97100473d6363c12ad77be806149f089868 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 20 Jun 2024 15:00:40 -0700 Subject: [PATCH 37/87] Add some docs for `is_initialized_body` --- .../src/kani_middle/transform/kani_intrinsics.rs | 8 ++++++++ library/kani/src/mem.rs | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index 91bb0e9dd1e5..a06052da693e 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -145,6 +145,14 @@ impl IntrinsicGeneratorPass { new_body.into() } + /// Generate the body for `is_initialized`, which looks like the following + /// + /// ``` + /// pub fn is_initialized(ptr: *const T, len: usize) -> bool { + /// let layout = ... // Byte mask representing the layout of T. + /// __kani_mem_init_sm_get(ptr, layout, len) + /// } + /// ``` fn is_initialized_body(&self, tcx: TyCtxt, body: Body) -> Body { let mut new_body = MutableBody::from(body); new_body.clear_body(); diff --git a/library/kani/src/mem.rs b/library/kani/src/mem.rs index 06b205eae4df..aaec88c1bb87 100644 --- a/library/kani/src/mem.rs +++ b/library/kani/src/mem.rs @@ -290,10 +290,10 @@ unsafe fn has_valid_value(_ptr: *const T) -> bool { kani_intrinsic() } -/// Extract the layout of the pointee type. +/// Check whether `len * size_of::()` bytes are initialized starting from `ptr`. #[rustc_diagnostic_item = "KaniIsInitialized"] #[inline(never)] -pub fn is_initialized(_ptr: *const T, _n: usize) -> bool { +pub fn is_initialized(_ptr: *const T, _len: usize) -> bool { kani_intrinsic() } From d9eacd7893cdd6697ff5f120e27b88e991ea7e7d Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 20 Jun 2024 15:33:08 -0700 Subject: [PATCH 38/87] Remove `transmute` inside `shadow.rs`, document type layout, carve out an exception for *const str --- .../kani_middle/transform/check_uninit/mod.rs | 32 +++++-- library/kani/src/shadow.rs | 87 +++++++++++-------- 2 files changed, 79 insertions(+), 40 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index 9b26965123b9..9d6aecbf9584 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -16,7 +16,9 @@ use rustc_middle::ty::TyCtxt; use rustc_smir::rustc_internal; use stable_mir::mir::mono::Instance; use stable_mir::mir::{AggregateKind, Body, ConstOperand, Mutability, Operand, Place, Rvalue}; -use stable_mir::ty::{FnDef, GenericArgKind, GenericArgs, MirConst, RigidTy, Ty, TyConst, TyKind}; +use stable_mir::ty::{ + FnDef, GenericArgKind, GenericArgs, MirConst, RigidTy, Ty, TyConst, TyKind, UintTy, +}; use stable_mir::CrateDef; use std::collections::{HashMap, HashSet}; use std::fmt::Debug; @@ -217,8 +219,18 @@ impl UninitPass { ); } PointeeLayout::Slice { element_layout } => { + // Since `str`` is a separate type, need to differentiate between [T] and str. + let (slicee_ty, diagnostic) = match pointee_info.ty().kind() { + TyKind::RigidTy(RigidTy::Slice(slicee_ty)) => { + (slicee_ty, "KaniMemInitSMGetSlice") + } + TyKind::RigidTy(RigidTy::Str) => { + (Ty::unsigned_ty(UintTy::U8), "KaniMemInitSMGetStr") + } + _ => unreachable!(), + }; let shadow_memory_get_instance = Instance::resolve( - get_kani_sm_function(tcx, "KaniMemInitSMGetSlice"), + get_kani_sm_function(tcx, diagnostic), &GenericArgs(vec![ GenericArgKind::Const( TyConst::try_from_target_usize( @@ -226,7 +238,7 @@ impl UninitPass { ) .unwrap(), ), - GenericArgKind::Type(*pointee_info.ty()), + GenericArgKind::Type(slicee_ty), ]), ) .unwrap(); @@ -306,8 +318,18 @@ impl UninitPass { ); } PointeeLayout::Slice { element_layout } => { + // Since `str`` is a separate type, need to differentiate between [T] and str. + let (slicee_ty, diagnostic) = match pointee_info.ty().kind() { + TyKind::RigidTy(RigidTy::Slice(slicee_ty)) => { + (slicee_ty, "KaniMemInitSMSetSlice") + } + TyKind::RigidTy(RigidTy::Str) => { + (Ty::unsigned_ty(UintTy::U8), "KaniMemInitSMSetStr") + } + _ => unreachable!(), + }; let shadow_memory_set_instance = Instance::resolve( - get_kani_sm_function(tcx, "KaniMemInitSMSetSlice"), + get_kani_sm_function(tcx, diagnostic), &GenericArgs(vec![ GenericArgKind::Const( TyConst::try_from_target_usize( @@ -315,7 +337,7 @@ impl UninitPass { ) .unwrap(), ), - GenericArgKind::Type(*pointee_info.ty()), + GenericArgKind::Type(slicee_ty), ]), ) .unwrap(); diff --git a/library/kani/src/shadow.rs b/library/kani/src/shadow.rs index 71e0aa496196..f8f5b47b14fc 100644 --- a/library/kani/src/shadow.rs +++ b/library/kani/src/shadow.rs @@ -82,19 +82,31 @@ impl ShadowMem { } } +/// Bytewise mask, representing which bytes of a type are data and which are padding. +/// For example, for a type like this: +/// ``` +/// #[repr(C)] +/// struct Foo { +/// a: u16, +/// b: u8, +/// } +/// ``` +/// the layout would be [true, true, true, false]; +type Layout = [bool; N]; + /// Global shadow memory object for tracking memory initialization. #[rustc_diagnostic_item = "KaniMemInitSM"] static mut __KANI_MEM_INIT_SM: ShadowMem = ShadowMem::new(false); -// Get initialization setate of `n` items laid out according to the `layout` starting at address `ptr`. +/// Get initialization state of `len` items laid out according to the `layout` starting at address `ptr`. #[rustc_diagnostic_item = "KaniMemInitSMGetInner"] fn __kani_mem_init_sm_get_inner( ptr: *const (), - layout: [bool; N], - n: usize, + layout: Layout, + len: usize, ) -> bool { let mut count: usize = 0; - while count < n { + while count < len { let mut offset: usize = 0; while offset < N { unsafe { @@ -111,16 +123,16 @@ fn __kani_mem_init_sm_get_inner( true } -// Set initialization setate to `value` for `n` items laid out according to the `layout` starting at address `ptr`. +/// Set initialization state to `value` for `len` items laid out according to the `layout` starting at address `ptr`. #[rustc_diagnostic_item = "KaniMemInitSMSetInner"] fn __kani_mem_init_sm_set_inner( ptr: *const (), - layout: [bool; N], - n: usize, + layout: Layout, + len: usize, value: bool, ) { let mut count: usize = 0; - while count < n { + while count < len { let mut offset: usize = 0; while offset < N { unsafe { @@ -133,48 +145,53 @@ fn __kani_mem_init_sm_set_inner( } } +/// Get initialization state of `len` items laid out according to the `layout` starting at address `ptr`. #[rustc_diagnostic_item = "KaniMemInitSMGet"] -pub fn __kani_mem_init_sm_get( - ptr: *const T, - layout: [bool; N], - n: usize, -) -> bool { +fn __kani_mem_init_sm_get(ptr: *const T, layout: Layout, len: usize) -> bool { let (ptr, _) = ptr.to_raw_parts(); - __kani_mem_init_sm_get_inner(ptr, layout, n) + __kani_mem_init_sm_get_inner(ptr, layout, len) } +/// Set initialization state to `value` for `len` items laid out according to the `layout` starting at address `ptr`. #[rustc_diagnostic_item = "KaniMemInitSMSet"] -pub fn __kani_mem_init_sm_set( +fn __kani_mem_init_sm_set( ptr: *const T, - layout: [bool; N], - n: usize, + layout: Layout, + len: usize, value: bool, ) { let (ptr, _) = ptr.to_raw_parts(); - __kani_mem_init_sm_set_inner(ptr, layout, n, value); + __kani_mem_init_sm_set_inner(ptr, layout, len, value); } -// This method should only be called if T is known to be a slice. +/// Get initialization state of the slice, items of which are laid out according to the `layout` starting at address `ptr`. #[rustc_diagnostic_item = "KaniMemInitSMGetSlice"] -pub fn __kani_mem_init_sm_get_slice( - ptr: *const T, - layout: [bool; N], -) -> bool { - let (ptr, meta) = ptr.to_raw_parts(); - let n: usize = unsafe { std::mem::transmute_copy(&meta) }; - // The pointee type is a slice, more than `n` objects can be accessed. - __kani_mem_init_sm_get_inner(ptr, layout, n) +fn __kani_mem_init_sm_get_slice(ptr: *const [T], layout: Layout) -> bool { + let (ptr, len) = ptr.to_raw_parts(); + __kani_mem_init_sm_get_inner(ptr, layout, len) } -// This method should only be called if T is known to be a slice. +/// Set initialization state of the slice, items of which are laid out according to the `layout` starting at address `ptr` to `value`. #[rustc_diagnostic_item = "KaniMemInitSMSetSlice"] -pub fn __kani_mem_init_sm_set_slice( - ptr: *const T, - layout: [bool; N], +fn __kani_mem_init_sm_set_slice( + ptr: *const [T], + layout: Layout, value: bool, ) { - let (ptr, meta) = ptr.to_raw_parts(); - let n: usize = unsafe { std::mem::transmute_copy(&meta) }; - // The pointee type is a slice, more than `n` objects can be accessed. - __kani_mem_init_sm_set_inner(ptr, layout, n, value); + let (ptr, len) = ptr.to_raw_parts(); + __kani_mem_init_sm_set_inner(ptr, layout, len, value); +} + +/// Get initialization state of the string slice, items of which are laid out according to the `layout` starting at address `ptr`. +#[rustc_diagnostic_item = "KaniMemInitSMGetStr"] +fn __kani_mem_init_sm_get_str(ptr: *const str, layout: Layout) -> bool { + let (ptr, len) = ptr.to_raw_parts(); + __kani_mem_init_sm_get_inner(ptr, layout, len) +} + +/// Set initialization state of the string slice, items of which are laid out according to the `layout` starting at address `ptr` to `value`. +#[rustc_diagnostic_item = "KaniMemInitSMSetStr"] +fn __kani_mem_init_sm_set_str(ptr: *const str, layout: Layout, value: bool) { + let (ptr, len) = ptr.to_raw_parts(); + __kani_mem_init_sm_set_inner(ptr, layout, len, value); } From 7174ebabb1e2a304349daa0bb3b4d257d40558ab Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 20 Jun 2024 15:42:19 -0700 Subject: [PATCH 39/87] Incorporate `is_initialized` into `can_dereference` and `can_read_unaligned` --- library/kani_core/src/mem.rs | 5 ++++- tests/std-checks/core/src/ptr.rs | 2 -- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/library/kani_core/src/mem.rs b/library/kani_core/src/mem.rs index 8dd4a27cb03a..8cb26f18663b 100644 --- a/library/kani_core/src/mem.rs +++ b/library/kani_core/src/mem.rs @@ -125,6 +125,7 @@ macro_rules! kani_mem { let (thin_ptr, metadata) = ptr.to_raw_parts(); metadata.is_ptr_aligned(thin_ptr, Internal) && is_inbounds(&metadata, thin_ptr) + && is_initialized(ptr, 1) && unsafe { has_valid_value(ptr) } } @@ -153,7 +154,9 @@ macro_rules! kani_mem { ::Metadata: PtrProperties, { let (thin_ptr, metadata) = ptr.to_raw_parts(); - is_inbounds(&metadata, thin_ptr) && unsafe { has_valid_value(ptr) } + is_inbounds(&metadata, thin_ptr) + && is_initialized(ptr, 1) + && unsafe { has_valid_value(ptr) } } /// Checks that `data_ptr` points to an allocation that can hold data of size calculated from `T`. diff --git a/tests/std-checks/core/src/ptr.rs b/tests/std-checks/core/src/ptr.rs index 94297ba35e93..49cf9e168214 100644 --- a/tests/std-checks/core/src/ptr.rs +++ b/tests/std-checks/core/src/ptr.rs @@ -29,7 +29,6 @@ pub mod contracts { /// the pointer points to must not get mutated (except inside UnsafeCell). /// Taken from: #[requires(can_dereference(obj.as_ptr()))] - #[requires(is_initialized(obj.as_ptr(), 1))] pub unsafe fn as_ref<'a, T>(obj: &NonNull) -> &'a T { obj.as_ref() } @@ -49,7 +48,6 @@ pub mod contracts { /// /// Note that even if `T` has size 0, the pointer must be non-null and properly aligned. #[requires(can_dereference(dst))] - #[requires(is_initialized(dst, 1))] #[modifies(dst)] pub unsafe fn replace(dst: *mut T, src: T) -> T { std::ptr::replace(dst, src) From d4ced45f544f98dedb8e7965d395e2b70e3660b6 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Fri, 21 Jun 2024 08:56:10 -0700 Subject: [PATCH 40/87] Add more comments to `UninitPass` --- .../src/kani_middle/transform/check_uninit/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index 9d6aecbf9584..46df340cb0a2 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -113,6 +113,7 @@ impl TransformPass for UninitPass { } impl UninitPass { + /// Inject memory initialization checks for each operation in an instruction. fn build_check_for_instruction( &self, tcx: TyCtxt, @@ -130,6 +131,7 @@ impl UninitPass { } } + /// Inject memory initialization check for an operation. fn build_check_for_operation( &self, tcx: TyCtxt, @@ -181,6 +183,8 @@ impl UninitPass { } } + // Inject a load from shadow memory tracking memory initialization and an assertion that all + // non-padding bytes are initialized. fn build_get_and_check( &self, tcx: TyCtxt, @@ -269,6 +273,8 @@ impl UninitPass { ) } + // Inject a store into shadow memory tracking memory initialization to initialize or + // deinitialize all non-padding bytes. fn build_set( &self, tcx: TyCtxt, @@ -383,6 +389,7 @@ impl UninitPass { } } +/// Generate a bit array denoting padding vs data bytes for a layout. pub fn mk_layout_operand( body: &mut MutableBody, source: &mut SourceInstruction, From 2640b3f55c5a1744cc5c6cfba9d8bc4cf6e3e859 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Fri, 21 Jun 2024 13:30:51 -0700 Subject: [PATCH 41/87] Fix bug when inserting a terminator after a terminator --- .../src/kani_middle/transform/body.rs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/body.rs b/kani-compiler/src/kani_middle/transform/body.rs index 6c846d862920..4aa587e3bb0a 100644 --- a/kani-compiler/src/kani_middle/transform/body.rs +++ b/kani-compiler/src/kani_middle/transform/body.rs @@ -233,7 +233,7 @@ impl MutableBody { &mut self, source: &mut SourceInstruction, position: InsertPosition, - new_term: Terminator, + mut new_term: Terminator, ) { let new_bb_idx = self.blocks.len(); match position { @@ -258,7 +258,6 @@ impl MutableBody { self.blocks.push(new_bb); } InsertPosition::After => { - let span = source.span(&self.blocks); match source { // Split the current block after the statement located at `source` // and move the remaining statements into the new one. @@ -277,18 +276,19 @@ impl MutableBody { SourceInstruction::Terminator { bb } => { let current_terminator = &mut self.blocks.get_mut(*bb).unwrap().terminator; // Kani can only instrument function calls like this. - match &mut current_terminator.kind { - TerminatorKind::Call { target: Some(target_bb), .. } => { - *bb = new_bb_idx; - let new_bb = BasicBlock { - statements: vec![], - terminator: Terminator { - kind: TerminatorKind::Goto { target: *target_bb }, - span, - }, - }; + match (&mut current_terminator.kind, &mut new_term.kind) { + ( + TerminatorKind::Call { target: Some(target_bb), .. }, + TerminatorKind::Call { target: Some(new_target_bb), .. }, + ) => { + // Set the new terminator to point where previous terminator pointed. + *new_target_bb = *target_bb; + // Point the current terminator to the new terminator's basic block. *target_bb = new_bb_idx; - self.blocks.push(new_bb); + // Update the current poisition. + *bb = new_bb_idx; + self.blocks + .push(BasicBlock { statements: vec![], terminator: new_term }); } _ => unimplemented!("Kani can only split blocks after calls."), }; From 98e8a37c254b2c0baad83df408ebb82ee51f3607 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 24 Jun 2024 09:05:19 -0700 Subject: [PATCH 42/87] Put memory initialization checking into a separate module --- .../codegen_cprover_gotoc/codegen/contract.rs | 2 +- .../kani_middle/transform/check_uninit/mod.rs | 14 +- .../kani_middle/transform/kani_intrinsics.rs | 4 +- library/kani/src/lib.rs | 1 + library/kani/src/mem_init.rs | 122 ++++++++++++++++++ library/kani/src/shadow.rs | 114 ---------------- 6 files changed, 133 insertions(+), 124 deletions(-) create mode 100644 library/kani/src/mem_init.rs diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs index ed9afe249c5b..d35015aa040d 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs @@ -122,7 +122,7 @@ impl<'tcx> GotocCtx<'tcx> { .tcx .all_diagnostic_items(()) .name_to_id - .get(&rustc_span::symbol::Symbol::intern("KaniMemInitSM")) + .get(&rustc_span::symbol::Symbol::intern("KaniMemInitShadowMem")) .map(|attr_id| { self.tcx .symbol_name(rustc_middle::ty::Instance::mono(self.tcx, *attr_id)) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index 46df340cb0a2..d30ddfb6102b 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -31,7 +31,7 @@ mod uninit_visitor; pub use ty_layout::{PointeeInfo, PointeeLayout, TypeLayout}; use uninit_visitor::{CheckUninitVisitor, InitRelevantInstruction, SourceOp}; -const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = &["KaniMemInitSMGetInner", "KaniMemInitSMSetInner"]; +const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = &["KaniIsUnitPtrInitialized", "KaniSetUnitPtrInitialized"]; /// Retrieve a function definition by diagnostic string, caching the result. pub fn get_kani_sm_function(tcx: TyCtxt, diagnostic: &'static str) -> FnDef { @@ -202,7 +202,7 @@ impl UninitPass { match pointee_info.layout() { PointeeLayout::Static { layout } => { let shadow_memory_get_instance = Instance::resolve( - get_kani_sm_function(tcx, "KaniMemInitSMGet"), + get_kani_sm_function(tcx, "KaniIsPtrInitialized"), &GenericArgs(vec![ GenericArgKind::Const( TyConst::try_from_target_usize(layout.to_byte_mask().len() as u64) @@ -226,10 +226,10 @@ impl UninitPass { // Since `str`` is a separate type, need to differentiate between [T] and str. let (slicee_ty, diagnostic) = match pointee_info.ty().kind() { TyKind::RigidTy(RigidTy::Slice(slicee_ty)) => { - (slicee_ty, "KaniMemInitSMGetSlice") + (slicee_ty, "KaniIsSlicePtrInitialized") } TyKind::RigidTy(RigidTy::Str) => { - (Ty::unsigned_ty(UintTy::U8), "KaniMemInitSMGetStr") + (Ty::unsigned_ty(UintTy::U8), "KaniIsStrPtrInitialized") } _ => unreachable!(), }; @@ -294,7 +294,7 @@ impl UninitPass { match pointee_info.layout() { PointeeLayout::Static { layout } => { let shadow_memory_set_instance = Instance::resolve( - get_kani_sm_function(tcx, "KaniMemInitSMSet"), + get_kani_sm_function(tcx, "KaniSetPtrInitialized"), &GenericArgs(vec![ GenericArgKind::Const( TyConst::try_from_target_usize(layout.to_byte_mask().len() as u64) @@ -327,10 +327,10 @@ impl UninitPass { // Since `str`` is a separate type, need to differentiate between [T] and str. let (slicee_ty, diagnostic) = match pointee_info.ty().kind() { TyKind::RigidTy(RigidTy::Slice(slicee_ty)) => { - (slicee_ty, "KaniMemInitSMSetSlice") + (slicee_ty, "KaniSetSlicePtrInitialized") } TyKind::RigidTy(RigidTy::Str) => { - (Ty::unsigned_ty(UintTy::U8), "KaniMemInitSMSetStr") + (Ty::unsigned_ty(UintTy::U8), "KaniSetStrPtrInitialized") } _ => unreachable!(), }; diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index a06052da693e..38933d5af36d 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -181,7 +181,7 @@ impl IntrinsicGeneratorPass { match pointee_info.layout() { PointeeLayout::Static { layout } => { let shadow_memory_get_instance = Instance::resolve( - get_kani_sm_function(tcx, "KaniMemInitSMGet"), + get_kani_sm_function(tcx, "KaniIsPtrInitialized"), &GenericArgs(vec![ GenericArgKind::Const( TyConst::try_from_target_usize( @@ -213,7 +213,7 @@ impl IntrinsicGeneratorPass { } PointeeLayout::Slice { element_layout } => { let shadow_memory_get_instance = Instance::resolve( - get_kani_sm_function(tcx, "KaniMemInitSMGetSlice"), + get_kani_sm_function(tcx, "KaniIsSlicePtrInitialized"), &GenericArgs(vec![ GenericArgKind::Const( TyConst::try_from_target_usize( diff --git a/library/kani/src/lib.rs b/library/kani/src/lib.rs index acf1e08e0441..99d0e9297faf 100644 --- a/library/kani/src/lib.rs +++ b/library/kani/src/lib.rs @@ -33,6 +33,7 @@ pub mod vec; #[doc(hidden)] pub mod internal; +mod mem_init; mod models; pub use arbitrary::Arbitrary; diff --git a/library/kani/src/mem_init.rs b/library/kani/src/mem_init.rs new file mode 100644 index 000000000000..37832ea32604 --- /dev/null +++ b/library/kani/src/mem_init.rs @@ -0,0 +1,122 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This module uses shadow memory API to track memory initialization of raw pointers. +//! +//! Currently, memory initialization is tracked on per-byte basis, so each byte of memory pointed to +//! by raw pointers could be either initialized or uninitialized. Compiler automatically inserts +//! calls to `is_xxx_initialized` and `set_xxx_initialized` at appropriate locations to get or set +//! the initialization status of the memory pointed to. Padding bytes are always considered +//! uninitialized: type layout is determined at compile time and statically injected into the +//! program (see `Layout`). + +// Definitions in this module are not meant to be visible to the end user, only the compiler. +#![allow(dead_code)] + +use crate::shadow::ShadowMem; + +/// Bytewise mask, representing which bytes of a type are data and which are padding. +/// For example, for a type like this: +/// ``` +/// #[repr(C)] +/// struct Foo { +/// a: u16, +/// b: u8, +/// } +/// ``` +/// the layout would be [true, true, true, false]; +type Layout = [bool; N]; + +/// Global shadow memory object for tracking memory initialization. +#[rustc_diagnostic_item = "KaniMemInitShadowMem"] +static mut MEM_INIT_SHADOW_MEM: ShadowMem = ShadowMem::new(false); + +/// Get initialization state of `len` items laid out according to the `layout` starting at address `ptr`. +#[rustc_diagnostic_item = "KaniIsUnitPtrInitialized"] +fn is_unit_ptr_initialized(ptr: *const (), layout: Layout, len: usize) -> bool { + let mut count: usize = 0; + while count < len { + let mut offset: usize = 0; + while offset < N { + unsafe { + if layout[offset] + && !MEM_INIT_SHADOW_MEM.get((ptr as *const u8).add(count * N + offset)) + { + return false; + } + offset += 1; + } + } + count += 1; + } + true +} + +/// Set initialization state to `value` for `len` items laid out according to the `layout` starting at address `ptr`. +#[rustc_diagnostic_item = "KaniSetUnitPtrInitialized"] +fn set_unit_ptr_initialized( + ptr: *const (), + layout: Layout, + len: usize, + value: bool, +) { + let mut count: usize = 0; + while count < len { + let mut offset: usize = 0; + while offset < N { + unsafe { + MEM_INIT_SHADOW_MEM + .set((ptr as *const u8).add(count * N + offset), value && layout[offset]); + } + offset += 1; + } + count += 1; + } +} + +/// Get initialization state of `len` items laid out according to the `layout` starting at address `ptr`. +#[rustc_diagnostic_item = "KaniIsPtrInitialized"] +fn is_ptr_initialized(ptr: *const T, layout: Layout, len: usize) -> bool { + let (ptr, _) = ptr.to_raw_parts(); + is_unit_ptr_initialized(ptr, layout, len) +} + +/// Set initialization state to `value` for `len` items laid out according to the `layout` starting at address `ptr`. +#[rustc_diagnostic_item = "KaniSetPtrInitialized"] +fn set_ptr_initialized( + ptr: *const T, + layout: Layout, + len: usize, + value: bool, +) { + let (ptr, _) = ptr.to_raw_parts(); + set_unit_ptr_initialized(ptr, layout, len, value); +} + +/// Get initialization state of the slice, items of which are laid out according to the `layout` starting at address `ptr`. +#[rustc_diagnostic_item = "KaniIsSlicePtrInitialized"] +fn is_slice_ptr_initialized(ptr: *const [T], layout: Layout) -> bool { + let (ptr, len) = ptr.to_raw_parts(); + is_unit_ptr_initialized(ptr, layout, len) +} + +/// Set initialization state of the slice, items of which are laid out according to the `layout` starting at address `ptr` to `value`. +#[rustc_diagnostic_item = "KaniSetSlicePtrInitialized"] +fn set_slice_ptr_initialized(ptr: *const [T], layout: Layout, value: bool) { + let (ptr, len) = ptr.to_raw_parts(); + set_unit_ptr_initialized(ptr, layout, len, value); +} + +/// Get initialization state of the string slice, items of which are laid out according to the `layout` starting at address `ptr`. +#[rustc_diagnostic_item = "KaniIsStrPtrInitialized"] +fn is_str_ptr_initialized(ptr: *const str, layout: Layout) -> bool { + let (ptr, len) = ptr.to_raw_parts(); + is_unit_ptr_initialized(ptr, layout, len) +} + +/// Set initialization state of the string slice, items of which are laid out according to the `layout` starting at address `ptr` to `value`. +#[rustc_diagnostic_item = "KaniSetStrPtrInitialized"] +fn set_str_ptr_initialized(ptr: *const str, layout: Layout, value: bool) { + let (ptr, len) = ptr.to_raw_parts(); + set_unit_ptr_initialized(ptr, layout, len, value); +} diff --git a/library/kani/src/shadow.rs b/library/kani/src/shadow.rs index f8f5b47b14fc..a7ea57c6fd40 100644 --- a/library/kani/src/shadow.rs +++ b/library/kani/src/shadow.rs @@ -81,117 +81,3 @@ impl ShadowMem { self.mem[obj][offset] = val; } } - -/// Bytewise mask, representing which bytes of a type are data and which are padding. -/// For example, for a type like this: -/// ``` -/// #[repr(C)] -/// struct Foo { -/// a: u16, -/// b: u8, -/// } -/// ``` -/// the layout would be [true, true, true, false]; -type Layout = [bool; N]; - -/// Global shadow memory object for tracking memory initialization. -#[rustc_diagnostic_item = "KaniMemInitSM"] -static mut __KANI_MEM_INIT_SM: ShadowMem = ShadowMem::new(false); - -/// Get initialization state of `len` items laid out according to the `layout` starting at address `ptr`. -#[rustc_diagnostic_item = "KaniMemInitSMGetInner"] -fn __kani_mem_init_sm_get_inner( - ptr: *const (), - layout: Layout, - len: usize, -) -> bool { - let mut count: usize = 0; - while count < len { - let mut offset: usize = 0; - while offset < N { - unsafe { - if layout[offset] - && !__KANI_MEM_INIT_SM.get((ptr as *const u8).add(count * N + offset)) - { - return false; - } - offset += 1; - } - } - count += 1; - } - true -} - -/// Set initialization state to `value` for `len` items laid out according to the `layout` starting at address `ptr`. -#[rustc_diagnostic_item = "KaniMemInitSMSetInner"] -fn __kani_mem_init_sm_set_inner( - ptr: *const (), - layout: Layout, - len: usize, - value: bool, -) { - let mut count: usize = 0; - while count < len { - let mut offset: usize = 0; - while offset < N { - unsafe { - __KANI_MEM_INIT_SM - .set((ptr as *const u8).add(count * N + offset), value && layout[offset]); - } - offset += 1; - } - count += 1; - } -} - -/// Get initialization state of `len` items laid out according to the `layout` starting at address `ptr`. -#[rustc_diagnostic_item = "KaniMemInitSMGet"] -fn __kani_mem_init_sm_get(ptr: *const T, layout: Layout, len: usize) -> bool { - let (ptr, _) = ptr.to_raw_parts(); - __kani_mem_init_sm_get_inner(ptr, layout, len) -} - -/// Set initialization state to `value` for `len` items laid out according to the `layout` starting at address `ptr`. -#[rustc_diagnostic_item = "KaniMemInitSMSet"] -fn __kani_mem_init_sm_set( - ptr: *const T, - layout: Layout, - len: usize, - value: bool, -) { - let (ptr, _) = ptr.to_raw_parts(); - __kani_mem_init_sm_set_inner(ptr, layout, len, value); -} - -/// Get initialization state of the slice, items of which are laid out according to the `layout` starting at address `ptr`. -#[rustc_diagnostic_item = "KaniMemInitSMGetSlice"] -fn __kani_mem_init_sm_get_slice(ptr: *const [T], layout: Layout) -> bool { - let (ptr, len) = ptr.to_raw_parts(); - __kani_mem_init_sm_get_inner(ptr, layout, len) -} - -/// Set initialization state of the slice, items of which are laid out according to the `layout` starting at address `ptr` to `value`. -#[rustc_diagnostic_item = "KaniMemInitSMSetSlice"] -fn __kani_mem_init_sm_set_slice( - ptr: *const [T], - layout: Layout, - value: bool, -) { - let (ptr, len) = ptr.to_raw_parts(); - __kani_mem_init_sm_set_inner(ptr, layout, len, value); -} - -/// Get initialization state of the string slice, items of which are laid out according to the `layout` starting at address `ptr`. -#[rustc_diagnostic_item = "KaniMemInitSMGetStr"] -fn __kani_mem_init_sm_get_str(ptr: *const str, layout: Layout) -> bool { - let (ptr, len) = ptr.to_raw_parts(); - __kani_mem_init_sm_get_inner(ptr, layout, len) -} - -/// Set initialization state of the string slice, items of which are laid out according to the `layout` starting at address `ptr` to `value`. -#[rustc_diagnostic_item = "KaniMemInitSMSetStr"] -fn __kani_mem_init_sm_set_str(ptr: *const str, layout: Layout, value: bool) { - let (ptr, len) = ptr.to_raw_parts(); - __kani_mem_init_sm_set_inner(ptr, layout, len, value); -} From cc326503358050ed4baca1eacbd724f7af712864 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 24 Jun 2024 09:15:34 -0700 Subject: [PATCH 43/87] Add initialization checks into `kani::mem` --- library/kani/src/mem.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/kani/src/mem.rs b/library/kani/src/mem.rs index aaec88c1bb87..0b390e74288d 100644 --- a/library/kani/src/mem.rs +++ b/library/kani/src/mem.rs @@ -120,6 +120,7 @@ where let (thin_ptr, metadata) = ptr.to_raw_parts(); metadata.is_ptr_aligned(thin_ptr, Internal) && is_inbounds(&metadata, thin_ptr) + && is_initialized(ptr, 1) && unsafe { has_valid_value(ptr) } } @@ -147,7 +148,7 @@ where ::Metadata: PtrProperties, { let (thin_ptr, metadata) = ptr.to_raw_parts(); - is_inbounds(&metadata, thin_ptr) && unsafe { has_valid_value(ptr) } + is_inbounds(&metadata, thin_ptr) && is_initialized(ptr, 1) && unsafe { has_valid_value(ptr) } } /// Checks that `data_ptr` points to an allocation that can hold data of size calculated from `T`. From 3dbf70d96071836d8afd931d2a04dda582859f07 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 24 Jun 2024 09:31:07 -0700 Subject: [PATCH 44/87] Perform FnDef caching inside instrumentation passes --- .../kani_middle/transform/check_uninit/mod.rs | 48 +++++++++---------- .../src/kani_middle/transform/check_values.rs | 2 +- .../src/kani_middle/transform/contracts.rs | 2 +- .../kani_middle/transform/kani_intrinsics.rs | 23 ++++++--- .../src/kani_middle/transform/mod.rs | 14 ++++-- .../src/kani_middle/transform/stubs.rs | 4 +- 6 files changed, 54 insertions(+), 39 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index d30ddfb6102b..35b971a16c30 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -11,7 +11,6 @@ use crate::kani_middle::transform::body::{ }; use crate::kani_middle::transform::{TransformPass, TransformationType}; use crate::kani_queries::QueryDb; -use lazy_static::lazy_static; use rustc_middle::ty::TyCtxt; use rustc_smir::rustc_internal; use stable_mir::mir::mono::Instance; @@ -22,7 +21,6 @@ use stable_mir::ty::{ use stable_mir::CrateDef; use std::collections::{HashMap, HashSet}; use std::fmt::Debug; -use std::sync::Mutex; use tracing::{debug, trace}; mod ty_layout; @@ -31,25 +29,15 @@ mod uninit_visitor; pub use ty_layout::{PointeeInfo, PointeeLayout, TypeLayout}; use uninit_visitor::{CheckUninitVisitor, InitRelevantInstruction, SourceOp}; -const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = &["KaniIsUnitPtrInitialized", "KaniSetUnitPtrInitialized"]; - -/// Retrieve a function definition by diagnostic string, caching the result. -pub fn get_kani_sm_function(tcx: TyCtxt, diagnostic: &'static str) -> FnDef { - lazy_static! { - static ref KANI_SM_FUNCTIONS: Mutex> = - Mutex::new(HashMap::new()); - } - let mut kani_sm_functions = KANI_SM_FUNCTIONS.lock().unwrap(); - let entry = kani_sm_functions - .entry(diagnostic) - .or_insert_with(|| find_fn_def(tcx, diagnostic).unwrap()); - *entry -} +const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = + &["KaniIsUnitPtrInitialized", "KaniSetUnitPtrInitialized"]; /// Instrument the code with checks for uninitialized memory. #[derive(Debug)] pub struct UninitPass { pub check_type: CheckType, + /// Used to cache FnDef lookups of injected memory initialization functions. + pub mem_init_fn_cache: HashMap<&'static str, FnDef>, } impl TransformPass for UninitPass { @@ -68,7 +56,7 @@ impl TransformPass for UninitPass { args.ub_check.contains(&ExtraChecks::Uninit) } - fn transform(&self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { + fn transform(&mut self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { trace!(function=?instance.name(), "transform"); // Need to break infinite recursion when shadow memory checks are inserted, @@ -115,7 +103,7 @@ impl TransformPass for UninitPass { impl UninitPass { /// Inject memory initialization checks for each operation in an instruction. fn build_check_for_instruction( - &self, + &mut self, tcx: TyCtxt, body: &mut MutableBody, instruction: InitRelevantInstruction, @@ -133,7 +121,7 @@ impl UninitPass { /// Inject memory initialization check for an operation. fn build_check_for_operation( - &self, + &mut self, tcx: TyCtxt, body: &mut MutableBody, source: &mut SourceInstruction, @@ -186,7 +174,7 @@ impl UninitPass { // Inject a load from shadow memory tracking memory initialization and an assertion that all // non-padding bytes are initialized. fn build_get_and_check( - &self, + &mut self, tcx: TyCtxt, body: &mut MutableBody, source: &mut SourceInstruction, @@ -202,7 +190,7 @@ impl UninitPass { match pointee_info.layout() { PointeeLayout::Static { layout } => { let shadow_memory_get_instance = Instance::resolve( - get_kani_sm_function(tcx, "KaniIsPtrInitialized"), + get_mem_init_fn(tcx, "KaniIsPtrInitialized", &mut self.mem_init_fn_cache), &GenericArgs(vec![ GenericArgKind::Const( TyConst::try_from_target_usize(layout.to_byte_mask().len() as u64) @@ -234,7 +222,7 @@ impl UninitPass { _ => unreachable!(), }; let shadow_memory_get_instance = Instance::resolve( - get_kani_sm_function(tcx, diagnostic), + get_mem_init_fn(tcx, diagnostic, &mut self.mem_init_fn_cache), &GenericArgs(vec![ GenericArgKind::Const( TyConst::try_from_target_usize( @@ -276,7 +264,7 @@ impl UninitPass { // Inject a store into shadow memory tracking memory initialization to initialize or // deinitialize all non-padding bytes. fn build_set( - &self, + &mut self, tcx: TyCtxt, body: &mut MutableBody, source: &mut SourceInstruction, @@ -294,7 +282,7 @@ impl UninitPass { match pointee_info.layout() { PointeeLayout::Static { layout } => { let shadow_memory_set_instance = Instance::resolve( - get_kani_sm_function(tcx, "KaniSetPtrInitialized"), + get_mem_init_fn(tcx, "KaniSetPtrInitialized", &mut self.mem_init_fn_cache), &GenericArgs(vec![ GenericArgKind::Const( TyConst::try_from_target_usize(layout.to_byte_mask().len() as u64) @@ -335,7 +323,7 @@ impl UninitPass { _ => unreachable!(), }; let shadow_memory_set_instance = Instance::resolve( - get_kani_sm_function(tcx, diagnostic), + get_mem_init_fn(tcx, diagnostic, &mut self.mem_init_fn_cache), &GenericArgs(vec![ GenericArgKind::Const( TyConst::try_from_target_usize( @@ -431,3 +419,13 @@ fn try_mark_new_bb_as_skipped( skip_first.insert(new_bb_idx); } } + +/// Retrieve a function definition by diagnostic string, caching the result. +pub fn get_mem_init_fn( + tcx: TyCtxt, + diagnostic: &'static str, + cache: &mut HashMap<&'static str, FnDef>, +) -> FnDef { + let entry = cache.entry(diagnostic).or_insert_with(|| find_fn_def(tcx, diagnostic).unwrap()); + *entry +} diff --git a/kani-compiler/src/kani_middle/transform/check_values.rs b/kani-compiler/src/kani_middle/transform/check_values.rs index 7c1d4e086159..a7d0f14d270f 100644 --- a/kani-compiler/src/kani_middle/transform/check_values.rs +++ b/kani-compiler/src/kani_middle/transform/check_values.rs @@ -61,7 +61,7 @@ impl TransformPass for ValidValuePass { /// Transform the function body by inserting checks one-by-one. /// For every unsafe dereference or a transmute operation, we check all values are valid. - fn transform(&self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { + fn transform(&mut self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { trace!(function=?instance.name(), "transform"); let mut new_body = MutableBody::from(body); let orig_len = new_body.blocks().len(); diff --git a/kani-compiler/src/kani_middle/transform/contracts.rs b/kani-compiler/src/kani_middle/transform/contracts.rs index f5760bd4d829..eb5266e0a0eb 100644 --- a/kani-compiler/src/kani_middle/transform/contracts.rs +++ b/kani-compiler/src/kani_middle/transform/contracts.rs @@ -47,7 +47,7 @@ impl TransformPass for AnyModifiesPass { } /// Transform the function body by replacing it with the stub body. - fn transform(&self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { + fn transform(&mut self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { trace!(function=?instance.name(), "AnyModifiesPass::transform"); if instance.def.def_id() == self.kani_any.unwrap().def_id() { diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index 38933d5af36d..8495502374ce 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -21,17 +21,20 @@ use stable_mir::mir::{ BinOp, Body, ConstOperand, Operand, Place, Rvalue, Statement, StatementKind, RETURN_LOCAL, }; use stable_mir::target::MachineInfo; -use stable_mir::ty::{GenericArgKind, GenericArgs, MirConst, RigidTy, TyConst, TyKind}; +use stable_mir::ty::{FnDef, GenericArgKind, GenericArgs, MirConst, RigidTy, TyConst, TyKind}; +use std::collections::HashMap; use std::fmt::Debug; use strum_macros::AsRefStr; use tracing::trace; -use super::check_uninit::{get_kani_sm_function, mk_layout_operand, PointeeLayout}; +use super::check_uninit::{get_mem_init_fn, mk_layout_operand, PointeeLayout}; /// Generate the body for a few Kani intrinsics. #[derive(Debug)] pub struct IntrinsicGeneratorPass { pub check_type: CheckType, + /// Used to cache FnDef lookups of injected memory initialization functions. + pub mem_init_fn_cache: HashMap<&'static str, FnDef>, } impl TransformPass for IntrinsicGeneratorPass { @@ -51,7 +54,7 @@ impl TransformPass for IntrinsicGeneratorPass { /// Transform the function body by inserting checks one-by-one. /// For every unsafe dereference or a transmute operation, we check all values are valid. - fn transform(&self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { + fn transform(&mut self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { trace!(function=?instance.name(), "transform"); if matches_diagnostic(tcx, instance.def, Intrinsics::KaniValidValue.as_ref()) { (true, self.valid_value_body(tcx, body)) @@ -153,7 +156,7 @@ impl IntrinsicGeneratorPass { /// __kani_mem_init_sm_get(ptr, layout, len) /// } /// ``` - fn is_initialized_body(&self, tcx: TyCtxt, body: Body) -> Body { + fn is_initialized_body(&mut self, tcx: TyCtxt, body: Body) -> Body { let mut new_body = MutableBody::from(body); new_body.clear_body(); @@ -181,7 +184,11 @@ impl IntrinsicGeneratorPass { match pointee_info.layout() { PointeeLayout::Static { layout } => { let shadow_memory_get_instance = Instance::resolve( - get_kani_sm_function(tcx, "KaniIsPtrInitialized"), + get_mem_init_fn( + tcx, + "KaniIsPtrInitialized", + &mut self.mem_init_fn_cache, + ), &GenericArgs(vec![ GenericArgKind::Const( TyConst::try_from_target_usize( @@ -213,7 +220,11 @@ impl IntrinsicGeneratorPass { } PointeeLayout::Slice { element_layout } => { let shadow_memory_get_instance = Instance::resolve( - get_kani_sm_function(tcx, "KaniIsSlicePtrInitialized"), + get_mem_init_fn( + tcx, + "KaniIsSlicePtrInitialized", + &mut self.mem_init_fn_cache, + ), &GenericArgs(vec![ GenericArgKind::Const( TyConst::try_from_target_usize( diff --git a/kani-compiler/src/kani_middle/transform/mod.rs b/kani-compiler/src/kani_middle/transform/mod.rs index 837af870434b..46e249cf813a 100644 --- a/kani-compiler/src/kani_middle/transform/mod.rs +++ b/kani-compiler/src/kani_middle/transform/mod.rs @@ -66,8 +66,14 @@ impl BodyTransformation { // This has to come after stubs since we want this to replace the stubbed body. transformer.add_pass(queries, AnyModifiesPass::new(tcx, &unit)); transformer.add_pass(queries, ValidValuePass { check_type: check_type.clone() }); - transformer.add_pass(queries, UninitPass { check_type: check_type.clone() }); - transformer.add_pass(queries, IntrinsicGeneratorPass { check_type }); + transformer.add_pass( + queries, + UninitPass { check_type: check_type.clone(), mem_init_fn_cache: HashMap::new() }, + ); + transformer.add_pass( + queries, + IntrinsicGeneratorPass { check_type, mem_init_fn_cache: HashMap::new() }, + ); transformer } @@ -82,7 +88,7 @@ impl BodyTransformation { None => { let mut body = instance.body().unwrap(); let mut modified = false; - for pass in self.stub_passes.iter().chain(self.inst_passes.iter()) { + for pass in self.stub_passes.iter_mut().chain(self.inst_passes.iter_mut()) { let result = pass.transform(tcx, body, instance); modified |= result.0; body = result.1; @@ -130,7 +136,7 @@ pub(crate) trait TransformPass: Debug { Self: Sized; /// Run a transformation pass in the function body. - fn transform(&self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body); + fn transform(&mut self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body); } /// The transformation result. diff --git a/kani-compiler/src/kani_middle/transform/stubs.rs b/kani-compiler/src/kani_middle/transform/stubs.rs index 4f249afdd45a..3dbd667c3943 100644 --- a/kani-compiler/src/kani_middle/transform/stubs.rs +++ b/kani-compiler/src/kani_middle/transform/stubs.rs @@ -43,7 +43,7 @@ impl TransformPass for FnStubPass { } /// Transform the function body by replacing it with the stub body. - fn transform(&self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { + fn transform(&mut self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { trace!(function=?instance.name(), "transform"); let ty = instance.ty(); if let TyKind::RigidTy(RigidTy::FnDef(fn_def, mut args)) = ty.kind() { @@ -103,7 +103,7 @@ impl TransformPass for ExternFnStubPass { /// /// We need to find function calls and function pointers. /// We should replace this with a visitor once StableMIR includes a mutable one. - fn transform(&self, _tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { + fn transform(&mut self, _tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { trace!(function=?instance.name(), "transform"); let mut new_body = MutableBody::from(body); let changed = false; From 226c50251d3070a46a6deae6bb33e280362f69ee Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 24 Jun 2024 09:51:43 -0700 Subject: [PATCH 45/87] Only expose byte mask for type layout --- .../kani_middle/transform/check_uninit/mod.rs | 35 ++++++---------- .../transform/check_uninit/ty_layout.rs | 41 ++++++++++--------- .../kani_middle/transform/kani_intrinsics.rs | 10 ++--- 3 files changed, 39 insertions(+), 47 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index 35b971a16c30..f68b0461562c 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -26,7 +26,7 @@ use tracing::{debug, trace}; mod ty_layout; mod uninit_visitor; -pub use ty_layout::{PointeeInfo, PointeeLayout, TypeLayout}; +pub use ty_layout::{PointeeInfo, PointeeLayout}; use uninit_visitor::{CheckUninitVisitor, InitRelevantInstruction, SourceOp}; const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = @@ -188,19 +188,18 @@ impl UninitPass { }; let ptr_operand = operation.mk_operand(body, source); match pointee_info.layout() { - PointeeLayout::Static { layout } => { + PointeeLayout::Sized { layout } => { let shadow_memory_get_instance = Instance::resolve( get_mem_init_fn(tcx, "KaniIsPtrInitialized", &mut self.mem_init_fn_cache), &GenericArgs(vec![ GenericArgKind::Const( - TyConst::try_from_target_usize(layout.to_byte_mask().len() as u64) - .unwrap(), + TyConst::try_from_target_usize(layout.len() as u64).unwrap(), ), GenericArgKind::Type(*pointee_info.ty()), ]), ) .unwrap(); - let layout_operand = mk_layout_operand(body, source, operation.position(), layout); + let layout_operand = mk_layout_operand(body, source, operation.position(), &layout); try_mark_new_bb_as_skipped(&operation, body, skip_first); body.add_call( &shadow_memory_get_instance, @@ -225,17 +224,14 @@ impl UninitPass { get_mem_init_fn(tcx, diagnostic, &mut self.mem_init_fn_cache), &GenericArgs(vec![ GenericArgKind::Const( - TyConst::try_from_target_usize( - element_layout.to_byte_mask().len() as u64 - ) - .unwrap(), + TyConst::try_from_target_usize(element_layout.len() as u64).unwrap(), ), GenericArgKind::Type(slicee_ty), ]), ) .unwrap(); let layout_operand = - mk_layout_operand(body, source, operation.position(), element_layout); + mk_layout_operand(body, source, operation.position(), &element_layout); try_mark_new_bb_as_skipped(&operation, body, skip_first); body.add_call( &shadow_memory_get_instance, @@ -280,19 +276,18 @@ impl UninitPass { let value = operation.expect_value(); match pointee_info.layout() { - PointeeLayout::Static { layout } => { + PointeeLayout::Sized { layout } => { let shadow_memory_set_instance = Instance::resolve( get_mem_init_fn(tcx, "KaniSetPtrInitialized", &mut self.mem_init_fn_cache), &GenericArgs(vec![ GenericArgKind::Const( - TyConst::try_from_target_usize(layout.to_byte_mask().len() as u64) - .unwrap(), + TyConst::try_from_target_usize(layout.len() as u64).unwrap(), ), GenericArgKind::Type(*pointee_info.ty()), ]), ) .unwrap(); - let layout_operand = mk_layout_operand(body, source, operation.position(), layout); + let layout_operand = mk_layout_operand(body, source, operation.position(), &layout); try_mark_new_bb_as_skipped(&operation, body, skip_first); body.add_call( &shadow_memory_set_instance, @@ -326,17 +321,14 @@ impl UninitPass { get_mem_init_fn(tcx, diagnostic, &mut self.mem_init_fn_cache), &GenericArgs(vec![ GenericArgKind::Const( - TyConst::try_from_target_usize( - element_layout.to_byte_mask().len() as u64 - ) - .unwrap(), + TyConst::try_from_target_usize(element_layout.len() as u64).unwrap(), ), GenericArgKind::Type(slicee_ty), ]), ) .unwrap(); let layout_operand = - mk_layout_operand(body, source, operation.position(), element_layout); + mk_layout_operand(body, source, operation.position(), &element_layout); try_mark_new_bb_as_skipped(&operation, body, skip_first); body.add_call( &shadow_memory_set_instance, @@ -382,14 +374,13 @@ pub fn mk_layout_operand( body: &mut MutableBody, source: &mut SourceInstruction, position: InsertPosition, - layout: &TypeLayout, + layout_byte_mask: &Vec, ) -> Operand { Operand::Move(Place { local: body.new_assignment( Rvalue::Aggregate( AggregateKind::Array(Ty::bool_ty()), - layout - .to_byte_mask() + layout_byte_mask .iter() .map(|byte| { Operand::Constant(ConstOperand { diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs index 5cbba3d700d3..ea9b800d0f7c 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs @@ -8,6 +8,7 @@ use stable_mir::target::{MachineInfo, MachineSize}; use stable_mir::ty::{AdtKind, IndexedVal, RigidTy, Ty, TyKind, UintTy}; use stable_mir::CrateDef; +/// Represents a chunk of data bytes in a data structure. #[derive(Clone, Debug, Eq, PartialEq, Hash)] struct DataBytes { /// Offset in bytes. @@ -16,30 +17,30 @@ struct DataBytes { size: MachineSize, } -pub struct TypeLayout { - size_in_bytes: usize, - data_chunks: Vec, -} +/// Bytewise mask, representing which bytes of a type are data and which are padding. +type Layout = Vec; -impl TypeLayout { - pub fn to_byte_mask(&self) -> Vec { - let mut layout_mask = vec![false; self.size_in_bytes]; - for data_bytes in self.data_chunks.iter() { - for layout_item in - layout_mask.iter_mut().skip(data_bytes.offset).take(data_bytes.size.bytes()) - { - *layout_item = true; - } +/// Create a byte-wise mask from known chunks of data bytes. +fn generate_byte_mask(size_in_bytes: usize, data_chunks: Vec) -> Vec { + let mut layout_mask = vec![false; size_in_bytes]; + for data_bytes in data_chunks.iter() { + for layout_item in + layout_mask.iter_mut().skip(data_bytes.offset).take(data_bytes.size.bytes()) + { + *layout_item = true; } - layout_mask } + layout_mask } // Depending on whether the type is statically or dynamically sized, // the layout of the element or the layout of the actual type is returned. pub enum PointeeLayout { - Static { layout: TypeLayout }, - Slice { element_layout: TypeLayout }, + /// Layout of sized objects. + Sized { layout: Layout }, + /// Layout of slices, &str is included in this case and treated as &[u8]. + Slice { element_layout: Layout }, + /// Trait objects have an arbitrary layout. TraitObject, } @@ -57,7 +58,7 @@ impl PointeeInfo { let size_in_bytes = slicee_ty.layout().unwrap().shape().size.bytes(); let data_chunks = data_bytes_for_ty(&MachineInfo::target(), slicee_ty, 0)?; let layout = PointeeLayout::Slice { - element_layout: TypeLayout { size_in_bytes, data_chunks }, + element_layout: generate_byte_mask(size_in_bytes, data_chunks), }; Ok(PointeeInfo { pointee_ty: ty, layout }) } @@ -65,7 +66,7 @@ impl PointeeInfo { let size_in_bytes = slicee_ty.layout().unwrap().shape().size.bytes(); let data_chunks = data_bytes_for_ty(&MachineInfo::target(), slicee_ty, 0)?; let layout = PointeeLayout::Slice { - element_layout: TypeLayout { size_in_bytes, data_chunks }, + element_layout: generate_byte_mask(size_in_bytes, data_chunks), }; Ok(PointeeInfo { pointee_ty: ty, layout }) } @@ -76,8 +77,8 @@ impl PointeeInfo { if ty.layout().unwrap().shape().is_sized() { let size_in_bytes = ty.layout().unwrap().shape().size.bytes(); let data_chunks = data_bytes_for_ty(&MachineInfo::target(), ty, 0)?; - let layout = PointeeLayout::Static { - layout: TypeLayout { size_in_bytes, data_chunks }, + let layout = PointeeLayout::Sized { + layout: generate_byte_mask(size_in_bytes, data_chunks), }; Ok(PointeeInfo { pointee_ty: ty, layout }) } else { diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index 8495502374ce..2b5c3a0ce1d0 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -182,7 +182,7 @@ impl IntrinsicGeneratorPass { match pointee_info { Ok(pointee_info) => { match pointee_info.layout() { - PointeeLayout::Static { layout } => { + PointeeLayout::Sized { layout } => { let shadow_memory_get_instance = Instance::resolve( get_mem_init_fn( tcx, @@ -192,7 +192,7 @@ impl IntrinsicGeneratorPass { &GenericArgs(vec![ GenericArgKind::Const( TyConst::try_from_target_usize( - layout.to_byte_mask().len() as u64 + layout.len() as u64 ) .unwrap(), ), @@ -204,7 +204,7 @@ impl IntrinsicGeneratorPass { &mut new_body, &mut terminator, InsertPosition::Before, - layout, + &layout, ); new_body.add_call( &shadow_memory_get_instance, @@ -228,7 +228,7 @@ impl IntrinsicGeneratorPass { &GenericArgs(vec![ GenericArgKind::Const( TyConst::try_from_target_usize( - element_layout.to_byte_mask().len() as u64, + element_layout.len() as u64, ) .unwrap(), ), @@ -240,7 +240,7 @@ impl IntrinsicGeneratorPass { &mut new_body, &mut terminator, InsertPosition::Before, - element_layout, + &element_layout, ); new_body.add_call( &shadow_memory_get_instance, From 839a79c479df5f7dd616e12d7275d20ece5d6757 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 24 Jun 2024 09:54:50 -0700 Subject: [PATCH 46/87] Update a comment --- .../src/kani_middle/transform/check_uninit/ty_layout.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs index ea9b800d0f7c..6ba57ea3b705 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs @@ -38,7 +38,7 @@ fn generate_byte_mask(size_in_bytes: usize, data_chunks: Vec) -> Vec< pub enum PointeeLayout { /// Layout of sized objects. Sized { layout: Layout }, - /// Layout of slices, &str is included in this case and treated as &[u8]. + /// Layout of slices, *const/mut str is included in this case and treated as *const/mut [u8]. Slice { element_layout: Layout }, /// Trait objects have an arbitrary layout. TraitObject, From d6423ce8653e9459fec9f1b3f623f77d78167b14 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 24 Jun 2024 10:34:50 -0700 Subject: [PATCH 47/87] Further changes to `ty_layout` --- .../transform/check_uninit/ty_layout.rs | 61 ++++++++++--------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs index 6ba57ea3b705..ad8f93b7c319 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs @@ -82,14 +82,12 @@ impl PointeeInfo { }; Ok(PointeeInfo { pointee_ty: ty, layout }) } else { - Err( - "Can only determine unsized type layout information for slices and trait objects.".to_string(), - ) + Err(format!("Cannot determine type layout for type `{ty}`")) } } }, TyKind::Alias(..) | TyKind::Param(..) | TyKind::Bound(..) => { - Err("Can only determine type layout information for RigidTy.".to_string()) + unreachable!("Should only encounter monomorphized types at this point.") } } } @@ -107,10 +105,18 @@ impl PointeeInfo { fn scalar_ty_size(machine_info: &MachineInfo, ty: Ty) -> Option { let shape = ty.layout().unwrap().shape(); match shape.abi { - ValueAbi::Scalar(Scalar::Initialized { value, .. }) - | ValueAbi::ScalarPair(Scalar::Initialized { value, .. }, _) => { + ValueAbi::Scalar(Scalar::Initialized { value, .. }) => { Some(DataBytes { offset: 0, size: value.size(machine_info) }) } + ValueAbi::ScalarPair( + Scalar::Initialized { value: value_first, .. }, + Scalar::Initialized { value: value_second, .. }, + ) => Some(DataBytes { + offset: 0, + size: MachineSize::from_bits( + value_first.size(machine_info).bits() + value_second.size(machine_info).bits(), + ), + }), ValueAbi::Scalar(_) | ValueAbi::ScalarPair(_, _) | ValueAbi::Uninhabited @@ -138,13 +144,13 @@ fn data_bytes_for_ty( FieldsShape::Primitive => Ok(ty_size()), FieldsShape::Array { stride, count } if count > 0 => { let TyKind::RigidTy(RigidTy::Array(elem_ty, _)) = ty.kind() else { unreachable!() }; - let elem_validity = data_bytes_for_ty(machine_info, elem_ty, current_offset)?; + let elem_data_bytes = data_bytes_for_ty(machine_info, elem_ty, current_offset)?; let mut result = vec![]; - if !elem_validity.is_empty() { + if !elem_data_bytes.is_empty() { for idx in 0..count { let idx: usize = idx.try_into().unwrap(); let elem_offset = idx * stride.bytes(); - let mut next_validity = elem_validity + let mut next_data_bytes = elem_data_bytes .iter() .cloned() .map(|mut req| { @@ -152,7 +158,7 @@ fn data_bytes_for_ty( req }) .collect::>(); - result.append(&mut next_validity) + result.append(&mut next_data_bytes) } } Ok(result) @@ -168,17 +174,17 @@ fn data_bytes_for_ty( VariantsShape::Single { index } => { // Only one variant is reachable. This behaves like a struct. let fields = ty_variants[index.to_index()].fields(); - let mut fields_validity = vec![]; + let mut fields_data_bytes = vec![]; for idx in layout.fields.fields_by_offset_order() { let field_offset = offsets[idx].bytes(); let field_ty = fields[idx].ty_with_args(&args); - fields_validity.append(&mut data_bytes_for_ty( + fields_data_bytes.append(&mut data_bytes_for_ty( machine_info, field_ty, field_offset + current_offset, )?); } - Ok(fields_validity) + Ok(fields_data_bytes) } VariantsShape::Multiple { tag_encoding: TagEncoding::Niche { .. }, @@ -187,22 +193,22 @@ fn data_bytes_for_ty( Err(format!("Unsupported Enum `{}` check", def.trimmed_name()))? } VariantsShape::Multiple { variants, .. } => { - let enum_validity = ty_size(); - let mut fields_validity = vec![]; + let enum_data_bytes = ty_size(); + let mut fields_data_bytes = vec![]; for (index, variant) in variants.iter().enumerate() { let fields = ty_variants[index].fields(); for field_idx in variant.fields.fields_by_offset_order() { let field_offset = offsets[field_idx].bytes(); let field_ty = fields[field_idx].ty_with_args(&args); - fields_validity.append(&mut data_bytes_for_ty( + fields_data_bytes.append(&mut data_bytes_for_ty( machine_info, field_ty, field_offset + current_offset, )?); } } - if fields_validity.is_empty() { - Ok(enum_validity) + if fields_data_bytes.is_empty() { + Ok(enum_data_bytes) } else { Err(format!( "Unsupported Enum `{}` check", @@ -214,40 +220,39 @@ fn data_bytes_for_ty( } AdtKind::Union => unreachable!(), AdtKind::Struct => { - // If the struct range has niche add that. - let mut struct_validity = ty_size(); + let mut struct_data_bytes = ty_size(); let fields = def.variants_iter().next().unwrap().fields(); for idx in layout.fields.fields_by_offset_order() { let field_offset = offsets[idx].bytes(); let field_ty = fields[idx].ty_with_args(&args); - struct_validity.append(&mut data_bytes_for_ty( + struct_data_bytes.append(&mut data_bytes_for_ty( machine_info, field_ty, field_offset + current_offset, )?); } - Ok(struct_validity) + Ok(struct_data_bytes) } } } RigidTy::Pat(base_ty, ..) => { // This is similar to a structure with one field and with niche defined. - let mut pat_validity = ty_size(); - pat_validity.append(&mut data_bytes_for_ty(machine_info, *base_ty, 0)?); - Ok(pat_validity) + let mut pat_data_bytes = ty_size(); + pat_data_bytes.append(&mut data_bytes_for_ty(machine_info, *base_ty, 0)?); + Ok(pat_data_bytes) } RigidTy::Tuple(tys) => { - let mut tuple_validity = vec![]; + let mut tuple_data_bytes = vec![]; for idx in layout.fields.fields_by_offset_order() { let field_offset = offsets[idx].bytes(); let field_ty = tys[idx]; - tuple_validity.append(&mut data_bytes_for_ty( + tuple_data_bytes.append(&mut data_bytes_for_ty( machine_info, field_ty, field_offset + current_offset, )?); } - Ok(tuple_validity) + Ok(tuple_data_bytes) } RigidTy::Bool | RigidTy::Char From a74bad098a55ae20007d624edb1b0bfb82a1bca4 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 24 Jun 2024 10:35:28 -0700 Subject: [PATCH 48/87] Further changed to`kani_intrinsics` --- .../kani_middle/transform/kani_intrinsics.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index 2b5c3a0ce1d0..4e1cae943d03 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -183,6 +183,10 @@ impl IntrinsicGeneratorPass { Ok(pointee_info) => { match pointee_info.layout() { PointeeLayout::Sized { layout } => { + if layout.is_empty() { + // Encountered a ZST, so we can short-circut here. + return new_body.into(); + } let shadow_memory_get_instance = Instance::resolve( get_mem_init_fn( tcx, @@ -191,10 +195,7 @@ impl IntrinsicGeneratorPass { ), &GenericArgs(vec![ GenericArgKind::Const( - TyConst::try_from_target_usize( - layout.len() as u64 - ) - .unwrap(), + TyConst::try_from_target_usize(layout.len() as u64).unwrap(), ), GenericArgKind::Type(*pointee_info.ty()), ]), @@ -227,10 +228,8 @@ impl IntrinsicGeneratorPass { ), &GenericArgs(vec![ GenericArgKind::Const( - TyConst::try_from_target_usize( - element_layout.len() as u64, - ) - .unwrap(), + TyConst::try_from_target_usize(element_layout.len() as u64) + .unwrap(), ), GenericArgKind::Type(*pointee_info.ty()), ]), @@ -250,7 +249,7 @@ impl IntrinsicGeneratorPass { Place::from(ret_var), ); } - PointeeLayout::TraitObject => unimplemented!(), + PointeeLayout::TraitObject => {} }; } Err(msg) => { From 54f628450551f4e98ee14ba43ddbfd6189ce3bd4 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 24 Jun 2024 12:14:56 -0700 Subject: [PATCH 49/87] Extract function resolution associated with `get_kani_sm_function` into a separate function --- .../kani_middle/transform/check_uninit/mod.rs | 82 +++++++++---------- .../kani_middle/transform/kani_intrinsics.rs | 55 ++++++------- 2 files changed, 63 insertions(+), 74 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index f68b0461562c..0bde1ae11483 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -189,20 +189,15 @@ impl UninitPass { let ptr_operand = operation.mk_operand(body, source); match pointee_info.layout() { PointeeLayout::Sized { layout } => { - let shadow_memory_get_instance = Instance::resolve( - get_mem_init_fn(tcx, "KaniIsPtrInitialized", &mut self.mem_init_fn_cache), - &GenericArgs(vec![ - GenericArgKind::Const( - TyConst::try_from_target_usize(layout.len() as u64).unwrap(), - ), - GenericArgKind::Type(*pointee_info.ty()), - ]), - ) - .unwrap(); + let is_ptr_initialized_instance = resolve_mem_init_fn( + get_mem_init_fn_def(tcx, "KaniIsPtrInitialized", &mut self.mem_init_fn_cache), + layout.len(), + *pointee_info.ty(), + ); let layout_operand = mk_layout_operand(body, source, operation.position(), &layout); try_mark_new_bb_as_skipped(&operation, body, skip_first); body.add_call( - &shadow_memory_get_instance, + &is_ptr_initialized_instance, source, operation.position(), vec![ptr_operand.clone(), layout_operand, operation.expect_count()], @@ -220,21 +215,16 @@ impl UninitPass { } _ => unreachable!(), }; - let shadow_memory_get_instance = Instance::resolve( - get_mem_init_fn(tcx, diagnostic, &mut self.mem_init_fn_cache), - &GenericArgs(vec![ - GenericArgKind::Const( - TyConst::try_from_target_usize(element_layout.len() as u64).unwrap(), - ), - GenericArgKind::Type(slicee_ty), - ]), - ) - .unwrap(); + let is_ptr_initialized_instance = resolve_mem_init_fn( + get_mem_init_fn_def(tcx, diagnostic, &mut self.mem_init_fn_cache), + element_layout.len(), + slicee_ty, + ); let layout_operand = mk_layout_operand(body, source, operation.position(), &element_layout); try_mark_new_bb_as_skipped(&operation, body, skip_first); body.add_call( - &shadow_memory_get_instance, + &is_ptr_initialized_instance, source, operation.position(), vec![ptr_operand.clone(), layout_operand], @@ -277,20 +267,15 @@ impl UninitPass { match pointee_info.layout() { PointeeLayout::Sized { layout } => { - let shadow_memory_set_instance = Instance::resolve( - get_mem_init_fn(tcx, "KaniSetPtrInitialized", &mut self.mem_init_fn_cache), - &GenericArgs(vec![ - GenericArgKind::Const( - TyConst::try_from_target_usize(layout.len() as u64).unwrap(), - ), - GenericArgKind::Type(*pointee_info.ty()), - ]), - ) - .unwrap(); + let set_ptr_initialized_instance = resolve_mem_init_fn( + get_mem_init_fn_def(tcx, "KaniSetPtrInitialized", &mut self.mem_init_fn_cache), + layout.len(), + *pointee_info.ty(), + ); let layout_operand = mk_layout_operand(body, source, operation.position(), &layout); try_mark_new_bb_as_skipped(&operation, body, skip_first); body.add_call( - &shadow_memory_set_instance, + &set_ptr_initialized_instance, source, operation.position(), vec![ @@ -317,21 +302,16 @@ impl UninitPass { } _ => unreachable!(), }; - let shadow_memory_set_instance = Instance::resolve( - get_mem_init_fn(tcx, diagnostic, &mut self.mem_init_fn_cache), - &GenericArgs(vec![ - GenericArgKind::Const( - TyConst::try_from_target_usize(element_layout.len() as u64).unwrap(), - ), - GenericArgKind::Type(slicee_ty), - ]), - ) - .unwrap(); + let set_ptr_initialized_instance = resolve_mem_init_fn( + get_mem_init_fn_def(tcx, diagnostic, &mut self.mem_init_fn_cache), + element_layout.len(), + slicee_ty, + ); let layout_operand = mk_layout_operand(body, source, operation.position(), &element_layout); try_mark_new_bb_as_skipped(&operation, body, skip_first); body.add_call( - &shadow_memory_set_instance, + &set_ptr_initialized_instance, source, operation.position(), vec![ @@ -412,7 +392,7 @@ fn try_mark_new_bb_as_skipped( } /// Retrieve a function definition by diagnostic string, caching the result. -pub fn get_mem_init_fn( +pub fn get_mem_init_fn_def( tcx: TyCtxt, diagnostic: &'static str, cache: &mut HashMap<&'static str, FnDef>, @@ -420,3 +400,15 @@ pub fn get_mem_init_fn( let entry = cache.entry(diagnostic).or_insert_with(|| find_fn_def(tcx, diagnostic).unwrap()); *entry } + +/// Resolves a given memory initialization function with passed type parameters. +pub fn resolve_mem_init_fn(fn_def: FnDef, layout_size: usize, associated_type: Ty) -> Instance { + Instance::resolve( + fn_def, + &GenericArgs(vec![ + GenericArgKind::Const(TyConst::try_from_target_usize(layout_size as u64).unwrap()), + GenericArgKind::Type(associated_type), + ]), + ) + .unwrap() +} diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index 4e1cae943d03..3639844eccbb 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -21,13 +21,15 @@ use stable_mir::mir::{ BinOp, Body, ConstOperand, Operand, Place, Rvalue, Statement, StatementKind, RETURN_LOCAL, }; use stable_mir::target::MachineInfo; -use stable_mir::ty::{FnDef, GenericArgKind, GenericArgs, MirConst, RigidTy, TyConst, TyKind}; +use stable_mir::ty::{FnDef, MirConst, RigidTy, Ty, TyKind, UintTy}; use std::collections::HashMap; use std::fmt::Debug; use strum_macros::AsRefStr; use tracing::trace; -use super::check_uninit::{get_mem_init_fn, mk_layout_operand, PointeeLayout}; +use super::check_uninit::{ + get_mem_init_fn_def, mk_layout_operand, resolve_mem_init_fn, PointeeLayout, +}; /// Generate the body for a few Kani intrinsics. #[derive(Debug)] @@ -187,20 +189,15 @@ impl IntrinsicGeneratorPass { // Encountered a ZST, so we can short-circut here. return new_body.into(); } - let shadow_memory_get_instance = Instance::resolve( - get_mem_init_fn( + let is_ptr_initialized_instance = resolve_mem_init_fn( + get_mem_init_fn_def( tcx, "KaniIsPtrInitialized", &mut self.mem_init_fn_cache, ), - &GenericArgs(vec![ - GenericArgKind::Const( - TyConst::try_from_target_usize(layout.len() as u64).unwrap(), - ), - GenericArgKind::Type(*pointee_info.ty()), - ]), - ) - .unwrap(); + layout.len(), + *pointee_info.ty(), + ); let layout_operand = mk_layout_operand( &mut new_body, &mut terminator, @@ -208,7 +205,7 @@ impl IntrinsicGeneratorPass { &layout, ); new_body.add_call( - &shadow_memory_get_instance, + &is_ptr_initialized_instance, &mut terminator, InsertPosition::Before, vec![ @@ -220,21 +217,21 @@ impl IntrinsicGeneratorPass { ); } PointeeLayout::Slice { element_layout } => { - let shadow_memory_get_instance = Instance::resolve( - get_mem_init_fn( - tcx, - "KaniIsSlicePtrInitialized", - &mut self.mem_init_fn_cache, - ), - &GenericArgs(vec![ - GenericArgKind::Const( - TyConst::try_from_target_usize(element_layout.len() as u64) - .unwrap(), - ), - GenericArgKind::Type(*pointee_info.ty()), - ]), - ) - .unwrap(); + // Since `str`` is a separate type, need to differentiate between [T] and str. + let (slicee_ty, diagnostic) = match pointee_info.ty().kind() { + TyKind::RigidTy(RigidTy::Slice(slicee_ty)) => { + (slicee_ty, "KaniIsSlicePtrInitialized") + } + TyKind::RigidTy(RigidTy::Str) => { + (Ty::unsigned_ty(UintTy::U8), "KaniIsStrPtrInitialized") + } + _ => unreachable!(), + }; + let is_ptr_initialized_instance = resolve_mem_init_fn( + get_mem_init_fn_def(tcx, diagnostic, &mut self.mem_init_fn_cache), + element_layout.len(), + slicee_ty, + ); let layout_operand = mk_layout_operand( &mut new_body, &mut terminator, @@ -242,7 +239,7 @@ impl IntrinsicGeneratorPass { &element_layout, ); new_body.add_call( - &shadow_memory_get_instance, + &is_ptr_initialized_instance, &mut terminator, InsertPosition::Before, vec![Operand::Copy(Place::from(1)), layout_operand], From 17603691349fa6aba5c633317e47ec14bb585912 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 24 Jun 2024 13:59:38 -0700 Subject: [PATCH 50/87] Support determining type layout of enums with variants of the exact same padding --- .../transform/check_uninit/ty_layout.rs | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs index ad8f93b7c319..b06e408df3b9 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs @@ -196,19 +196,35 @@ fn data_bytes_for_ty( let enum_data_bytes = ty_size(); let mut fields_data_bytes = vec![]; for (index, variant) in variants.iter().enumerate() { + let mut field_data_bytes_for_variant = vec![]; let fields = ty_variants[index].fields(); for field_idx in variant.fields.fields_by_offset_order() { let field_offset = offsets[field_idx].bytes(); let field_ty = fields[field_idx].ty_with_args(&args); - fields_data_bytes.append(&mut data_bytes_for_ty( - machine_info, - field_ty, - field_offset + current_offset, - )?); + field_data_bytes_for_variant.append( + &mut data_bytes_for_ty( + machine_info, + field_ty, + field_offset + current_offset, + )?, + ); } + fields_data_bytes.push(field_data_bytes_for_variant); } + if fields_data_bytes.is_empty() { Ok(enum_data_bytes) + } else if fields_data_bytes.iter().all( + |data_bytes_for_variant| { + *data_bytes_for_variant + == *fields_data_bytes.first().unwrap() + }, + ) { + let mut total_data_bytes = enum_data_bytes; + let mut field_data_bytes = + fields_data_bytes.first().unwrap().clone(); + total_data_bytes.append(&mut field_data_bytes); + Ok(total_data_bytes) } else { Err(format!( "Unsupported Enum `{}` check", From 48942969256b7375fb0aa37ae24165673165fc8f Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 24 Jun 2024 14:08:27 -0700 Subject: [PATCH 51/87] `assert(false)` on using pointers to trait objects --- .../kani_middle/transform/check_uninit/mod.rs | 14 +++++++++-- .../kani_middle/transform/kani_intrinsics.rs | 23 ++++++++++++++++++- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index 0bde1ae11483..b0fd143aa511 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -231,7 +231,12 @@ impl UninitPass { ret_place.clone(), ); } - PointeeLayout::TraitObject => return, + PointeeLayout::TraitObject => { + try_mark_new_bb_as_skipped(&operation, body, skip_first); + let reason = "Kani does not support reasoning about memory initialization of pointers to trait objects."; + self.unsupported_check(tcx, body, source, operation.position(), reason); + return; + } }; // Make sure all non-padding bytes are initialized. @@ -326,7 +331,12 @@ impl UninitPass { ret_place, ); } - PointeeLayout::TraitObject => {} + PointeeLayout::TraitObject => { + try_mark_new_bb_as_skipped(&operation, body, skip_first); + let reason = "Kani does not support reasoning about memory initialization of pointers to trait objects."; + self.unsupported_check(tcx, body, source, operation.position(), reason); + return; + } }; } diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index 3639844eccbb..f34f3979988c 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -246,7 +246,28 @@ impl IntrinsicGeneratorPass { Place::from(ret_var), ); } - PointeeLayout::TraitObject => {} + PointeeLayout::TraitObject => { + let rvalue = Rvalue::Use(Operand::Constant(ConstOperand { + const_: MirConst::from_bool(false), + span, + user_ty: None, + })); + let result = new_body.new_assignment( + rvalue, + &mut terminator, + InsertPosition::Before, + ); + let reason: &str = "Kani does not support reasoning about memory initialization of pointers to trait objects."; + + new_body.add_check( + tcx, + &self.check_type, + &mut terminator, + InsertPosition::Before, + result, + &reason, + ); + } }; } Err(msg) => { From db309e06ab82af43ff9f362682701bd250bf900a Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 24 Jun 2024 14:11:35 -0700 Subject: [PATCH 52/87] Make clippy happy --- kani-compiler/src/kani_middle/transform/check_uninit/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index b0fd143aa511..e44587de59e3 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -335,7 +335,6 @@ impl UninitPass { try_mark_new_bb_as_skipped(&operation, body, skip_first); let reason = "Kani does not support reasoning about memory initialization of pointers to trait objects."; self.unsupported_check(tcx, body, source, operation.position(), reason); - return; } }; } @@ -364,7 +363,7 @@ pub fn mk_layout_operand( body: &mut MutableBody, source: &mut SourceInstruction, position: InsertPosition, - layout_byte_mask: &Vec, + layout_byte_mask: &[bool], ) -> Operand { Operand::Move(Place { local: body.new_assignment( From 171866679181cfef8faed9fb6a47ddb057120adb Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 24 Jun 2024 16:29:45 -0700 Subject: [PATCH 53/87] Remove unnecessary call to `ty_size` --- .../src/kani_middle/transform/check_uninit/ty_layout.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs index b06e408df3b9..c34f784f5220 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs @@ -236,7 +236,7 @@ fn data_bytes_for_ty( } AdtKind::Union => unreachable!(), AdtKind::Struct => { - let mut struct_data_bytes = ty_size(); + let mut struct_data_bytes = vec![]; let fields = def.variants_iter().next().unwrap().fields(); for idx in layout.fields.fields_by_offset_order() { let field_offset = offsets[idx].bytes(); @@ -253,7 +253,7 @@ fn data_bytes_for_ty( } RigidTy::Pat(base_ty, ..) => { // This is similar to a structure with one field and with niche defined. - let mut pat_data_bytes = ty_size(); + let mut pat_data_bytes = vec![]; pat_data_bytes.append(&mut data_bytes_for_ty(machine_info, *base_ty, 0)?); Ok(pat_data_bytes) } From dd71adf85b813aa212eafd7aef05d281b46aa1ba Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 24 Jun 2024 16:32:13 -0700 Subject: [PATCH 54/87] Move tests around, temporarily disable `std-checks` --- scripts/kani-regression.sh | 2 +- tests/kani/Uninit/access-padding-init.rs | 15 ---- tests/kani/Uninit/alloc-to-slice.rs | 20 ----- tests/kani/Uninit/alloc-zeroed-to-slice.rs | 26 ------ .../Uninit/struct-padding-and-arr-init.rs | 32 ------- tests/kani/Uninit/vec-read-init.rs | 11 --- .../const-fn-with-effect/Cargo.toml | 14 ++++ .../const-fn-with-effect/expected} | 0 .../const-fn-with-effect/src/lib.rs} | 1 - .../function-contract/const-fn/Cargo.toml | 14 ++++ .../function-contract/const-fn/expected} | 0 .../function-contract/const-fn/src/lib.rs} | 1 - .../function-contract/valid-ptr/Cargo.toml | 14 ++++ .../function-contract/valid-ptr/expected} | 0 .../function-contract/valid-ptr/src/lib.rs} | 1 - .../fat-ptr-validity/Cargo.toml | 14 ++++ .../mem-predicates/fat-ptr-validity/expected | 27 ++++++ .../fat-ptr-validity/src/lib.rs} | 2 +- .../thin-ptr-validity/Cargo.toml | 14 ++++ .../mem-predicates/thin-ptr-validity/expected | 20 +++++ .../thin-ptr-validity/src/lib.rs} | 2 +- tests/perf/uninit/Cargo.toml | 14 ++++ tests/perf/uninit/expected | 1 + tests/perf/uninit/src/lib.rs | 83 +++++++++++++++++++ 24 files changed, 218 insertions(+), 110 deletions(-) delete mode 100644 tests/kani/Uninit/access-padding-init.rs delete mode 100644 tests/kani/Uninit/alloc-to-slice.rs delete mode 100644 tests/kani/Uninit/alloc-zeroed-to-slice.rs delete mode 100644 tests/kani/Uninit/struct-padding-and-arr-init.rs delete mode 100644 tests/kani/Uninit/vec-read-init.rs create mode 100644 tests/perf/function-contract/const-fn-with-effect/Cargo.toml rename tests/{expected/function-contract/const_fn.expected => perf/function-contract/const-fn-with-effect/expected} (100%) rename tests/{expected/function-contract/const_fn_with_effect.rs => perf/function-contract/const-fn-with-effect/src/lib.rs} (89%) create mode 100644 tests/perf/function-contract/const-fn/Cargo.toml rename tests/{expected/function-contract/const_fn_with_effect.expected => perf/function-contract/const-fn/expected} (100%) rename tests/{expected/function-contract/const_fn.rs => perf/function-contract/const-fn/src/lib.rs} (88%) create mode 100644 tests/perf/function-contract/valid-ptr/Cargo.toml rename tests/{expected/function-contract/valid_ptr.expected => perf/function-contract/valid-ptr/expected} (100%) rename tests/{expected/function-contract/valid_ptr.rs => perf/function-contract/valid-ptr/src/lib.rs} (97%) create mode 100644 tests/perf/mem-predicates/fat-ptr-validity/Cargo.toml create mode 100644 tests/perf/mem-predicates/fat-ptr-validity/expected rename tests/{kani/MemPredicates/fat_ptr_validity.rs => perf/mem-predicates/fat-ptr-validity/src/lib.rs} (98%) create mode 100644 tests/perf/mem-predicates/thin-ptr-validity/Cargo.toml create mode 100644 tests/perf/mem-predicates/thin-ptr-validity/expected rename tests/{kani/MemPredicates/thin_ptr_validity.rs => perf/mem-predicates/thin-ptr-validity/src/lib.rs} (97%) create mode 100644 tests/perf/uninit/Cargo.toml create mode 100644 tests/perf/uninit/expected create mode 100644 tests/perf/uninit/src/lib.rs diff --git a/scripts/kani-regression.sh b/scripts/kani-regression.sh index b1de293d533c..5bf1885083e3 100755 --- a/scripts/kani-regression.sh +++ b/scripts/kani-regression.sh @@ -52,7 +52,7 @@ TESTS=( "kani kani" "expected expected" "ui expected" - "std-checks cargo-kani" + # "std-checks cargo-kani" "firecracker kani" "prusti kani" "smack kani" diff --git a/tests/kani/Uninit/access-padding-init.rs b/tests/kani/Uninit/access-padding-init.rs deleted file mode 100644 index 59a720c1af10..000000000000 --- a/tests/kani/Uninit/access-padding-init.rs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: -Z ghost-state -Z uninit-checks - -use std::ptr::addr_of; - -#[repr(C)] -struct S(u32, u8); - -#[kani::proof] -fn main() { - let s = S(0, 0); - let ptr: *const u8 = addr_of!(s) as *const u8; - let padding = unsafe { *(ptr.add(4)) }; -} diff --git a/tests/kani/Uninit/alloc-to-slice.rs b/tests/kani/Uninit/alloc-to-slice.rs deleted file mode 100644 index 707e34e164af..000000000000 --- a/tests/kani/Uninit/alloc-to-slice.rs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: -Z ghost-state -Z uninit-checks - -use std::alloc::{alloc, Layout}; - -#[kani::proof] -fn main() { - let layout = Layout::from_size_align(32, 8).unwrap(); - unsafe { - // This returns initialized memory. - let ptr = alloc(layout); - *ptr = 0x41; - *ptr.add(1) = 0x42; - *ptr.add(2) = 0x43; - *ptr.add(3) = 0x44; - *ptr.add(16) = 0x00; - let val = *(ptr.add(2)); - } -} diff --git a/tests/kani/Uninit/alloc-zeroed-to-slice.rs b/tests/kani/Uninit/alloc-zeroed-to-slice.rs deleted file mode 100644 index aab656ed7167..000000000000 --- a/tests/kani/Uninit/alloc-zeroed-to-slice.rs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: -Z ghost-state -Z uninit-checks - -#![allow(dropping_copy_types)] - -use std::alloc::{alloc_zeroed, dealloc, Layout}; -use std::slice::from_raw_parts; - -#[kani::proof] -fn main() { - let layout = Layout::from_size_align(32, 8).unwrap(); - unsafe { - // This returns initialized memory. - let ptr = alloc_zeroed(layout); - *ptr = 0x41; - *ptr.add(1) = 0x42; - *ptr.add(2) = 0x43; - *ptr.add(3) = 0x44; - *ptr.add(16) = 0x00; - let slice1 = from_raw_parts(ptr, 16); - let slice2 = from_raw_parts(ptr.add(16), 16); - drop(slice1.cmp(slice2)); - dealloc(ptr, layout); - } -} diff --git a/tests/kani/Uninit/struct-padding-and-arr-init.rs b/tests/kani/Uninit/struct-padding-and-arr-init.rs deleted file mode 100644 index b071f60a674c..000000000000 --- a/tests/kani/Uninit/struct-padding-and-arr-init.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: -Z ghost-state -Z uninit-checks - -#![feature(core_intrinsics)] -#![feature(custom_mir)] - -use std::intrinsics::mir::*; -use std::ptr; - -#[repr(C)] -struct S(u16, u16); - -#[kani::proof] -#[custom_mir(dialect = "runtime", phase = "optimized")] -fn main() { - mir! { - let s: S; - let sptr; - let sptr2; - let _val; - { - sptr = ptr::addr_of_mut!(s); - sptr2 = sptr as *mut [u8; 4]; - *sptr2 = [0; 4]; - *sptr = S(0, 0); - // Both S(u16, u16) and [u8; 4] have the same layout, so the memory is initialized. - _val = *sptr2; - Return() - } - } -} diff --git a/tests/kani/Uninit/vec-read-init.rs b/tests/kani/Uninit/vec-read-init.rs deleted file mode 100644 index 492a49523a01..000000000000 --- a/tests/kani/Uninit/vec-read-init.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: -Z ghost-state -Z uninit-checks - -#[kani::proof] -fn main() { - let mut v: Vec = Vec::with_capacity(10); - unsafe { *v.as_mut_ptr().add(5) = 0x42 }; - let def = unsafe { *v.as_ptr().add(5) }; // Not UB since accessing initialized memory. - let x = def + 1; -} diff --git a/tests/perf/function-contract/const-fn-with-effect/Cargo.toml b/tests/perf/function-contract/const-fn-with-effect/Cargo.toml new file mode 100644 index 000000000000..a7827f1ae84e --- /dev/null +++ b/tests/perf/function-contract/const-fn-with-effect/Cargo.toml @@ -0,0 +1,14 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +[package] +name = "const-fn-with-effect" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[package.metadata.kani] +unstable = { function-contracts = true, mem-predicates = true, ghost-state = true, uninit-checks = true } diff --git a/tests/expected/function-contract/const_fn.expected b/tests/perf/function-contract/const-fn-with-effect/expected similarity index 100% rename from tests/expected/function-contract/const_fn.expected rename to tests/perf/function-contract/const-fn-with-effect/expected diff --git a/tests/expected/function-contract/const_fn_with_effect.rs b/tests/perf/function-contract/const-fn-with-effect/src/lib.rs similarity index 89% rename from tests/expected/function-contract/const_fn_with_effect.rs rename to tests/perf/function-contract/const-fn-with-effect/src/lib.rs index d57c1f42fe16..33027728ab9b 100644 --- a/tests/expected/function-contract/const_fn_with_effect.rs +++ b/tests/perf/function-contract/const-fn-with-effect/src/lib.rs @@ -1,6 +1,5 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: -Zfunction-contracts -Zmem-predicates //! Check that Kani contract can be applied to a constant function. //! diff --git a/tests/perf/function-contract/const-fn/Cargo.toml b/tests/perf/function-contract/const-fn/Cargo.toml new file mode 100644 index 000000000000..7b586019831e --- /dev/null +++ b/tests/perf/function-contract/const-fn/Cargo.toml @@ -0,0 +1,14 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +[package] +name = "const-fn" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[package.metadata.kani] +unstable = { function-contracts = true, mem-predicates = true, ghost-state = true, uninit-checks = true } diff --git a/tests/expected/function-contract/const_fn_with_effect.expected b/tests/perf/function-contract/const-fn/expected similarity index 100% rename from tests/expected/function-contract/const_fn_with_effect.expected rename to tests/perf/function-contract/const-fn/expected diff --git a/tests/expected/function-contract/const_fn.rs b/tests/perf/function-contract/const-fn/src/lib.rs similarity index 88% rename from tests/expected/function-contract/const_fn.rs rename to tests/perf/function-contract/const-fn/src/lib.rs index 44b937cedce4..82af3cb5218e 100644 --- a/tests/expected/function-contract/const_fn.rs +++ b/tests/perf/function-contract/const-fn/src/lib.rs @@ -1,6 +1,5 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: -Zfunction-contracts -Zmem-predicates //! Check that Kani contract can be applied to a constant function. //! diff --git a/tests/perf/function-contract/valid-ptr/Cargo.toml b/tests/perf/function-contract/valid-ptr/Cargo.toml new file mode 100644 index 000000000000..f3f7221560b3 --- /dev/null +++ b/tests/perf/function-contract/valid-ptr/Cargo.toml @@ -0,0 +1,14 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +[package] +name = "valid-ptr" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[package.metadata.kani] +unstable = { function-contracts = true, mem-predicates = true, ghost-state = true, uninit-checks = true } diff --git a/tests/expected/function-contract/valid_ptr.expected b/tests/perf/function-contract/valid-ptr/expected similarity index 100% rename from tests/expected/function-contract/valid_ptr.expected rename to tests/perf/function-contract/valid-ptr/expected diff --git a/tests/expected/function-contract/valid_ptr.rs b/tests/perf/function-contract/valid-ptr/src/lib.rs similarity index 97% rename from tests/expected/function-contract/valid_ptr.rs rename to tests/perf/function-contract/valid-ptr/src/lib.rs index 2047a46caf4f..af8b6a96cef3 100644 --- a/tests/expected/function-contract/valid_ptr.rs +++ b/tests/perf/function-contract/valid-ptr/src/lib.rs @@ -1,6 +1,5 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: -Zfunction-contracts -Zmem-predicates //! Test that it is sound to use memory predicates inside a contract pre-condition. //! We cannot validate post-condition yet. This can be done once we fix: diff --git a/tests/perf/mem-predicates/fat-ptr-validity/Cargo.toml b/tests/perf/mem-predicates/fat-ptr-validity/Cargo.toml new file mode 100644 index 000000000000..4082e1ffd253 --- /dev/null +++ b/tests/perf/mem-predicates/fat-ptr-validity/Cargo.toml @@ -0,0 +1,14 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +[package] +name = "fat-ptr-validity" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[package.metadata.kani] +unstable = { mem-predicates = true, ghost-state = true, uninit-checks = true } diff --git a/tests/perf/mem-predicates/fat-ptr-validity/expected b/tests/perf/mem-predicates/fat-ptr-validity/expected new file mode 100644 index 000000000000..c38008e5ec8c --- /dev/null +++ b/tests/perf/mem-predicates/fat-ptr-validity/expected @@ -0,0 +1,27 @@ +Checking harness invalid_access::check_invalid_slice_len... +Failed Checks: The object size exceeds the maximum size supported by Kani's shadow memory model (64) +VERIFICATION:- SUCCESSFUL (encountered one or more panics as expected) + +Checking harness invalid_access::check_invalid_slice_ptr... +Failed Checks: Kani does not support reasoning about memory initialization of unsized pointers. +VERIFICATION:- SUCCESSFUL (encountered one or more panics as expected) + +Checking harness invalid_access::check_invalid_dyn_ptr... +Failed Checks: Kani does not support reasoning about memory initialization of unsized pointers. +VERIFICATION:- SUCCESSFUL (encountered one or more panics as expected) + +Checking harness valid_access::check_valid_zst... +VERIFICATION:- SUCCESSFUL + +Checking harness valid_access::check_valid_slice_ptr... +Failed Checks: Kani does not support reasoning about memory initialization of unsized pointers. +VERIFICATION:- FAILED + +Checking harness valid_access::check_valid_dyn_ptr... +Failed Checks: Kani does not support reasoning about memory initialization of unsized pointers. +VERIFICATION:- FAILED + +Summary: +Verification failed for - valid_access::check_valid_slice_ptr +Verification failed for - valid_access::check_valid_dyn_ptr +Complete - 4 successfully verified harnesses, 2 failures, 6 total. diff --git a/tests/kani/MemPredicates/fat_ptr_validity.rs b/tests/perf/mem-predicates/fat-ptr-validity/src/lib.rs similarity index 98% rename from tests/kani/MemPredicates/fat_ptr_validity.rs rename to tests/perf/mem-predicates/fat-ptr-validity/src/lib.rs index c4f037f3a646..ca0bcff54feb 100644 --- a/tests/kani/MemPredicates/fat_ptr_validity.rs +++ b/tests/perf/mem-predicates/fat-ptr-validity/src/lib.rs @@ -1,6 +1,6 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: -Z mem-predicates + //! Check that Kani's memory predicates work for fat pointers. extern crate kani; diff --git a/tests/perf/mem-predicates/thin-ptr-validity/Cargo.toml b/tests/perf/mem-predicates/thin-ptr-validity/Cargo.toml new file mode 100644 index 000000000000..94badb22a0cc --- /dev/null +++ b/tests/perf/mem-predicates/thin-ptr-validity/Cargo.toml @@ -0,0 +1,14 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +[package] +name = "thin-ptr-validity" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[package.metadata.kani] +unstable = { mem-predicates = true, ghost-state = true, uninit-checks = true } diff --git a/tests/perf/mem-predicates/thin-ptr-validity/expected b/tests/perf/mem-predicates/thin-ptr-validity/expected new file mode 100644 index 000000000000..71da2581fa06 --- /dev/null +++ b/tests/perf/mem-predicates/thin-ptr-validity/expected @@ -0,0 +1,20 @@ +Checking harness invalid_access::check_invalid_zst... +VERIFICATION:- SUCCESSFUL + +Checking harness invalid_access::check_invalid_array... +Failed Checks: assertion failed: can_dereference(raw_ptr) +Failed Checks: Kani does not support reasoning about pointer to unallocated memory +VERIFICATION:- SUCCESSFUL (encountered one or more panics as expected) + +Checking harness invalid_access::check_invalid_ptr... +Failed Checks: assertion failed: !can_dereference(raw_ptr) +Failed Checks: Kani does not support reasoning about pointer to unallocated memory +VERIFICATION:- SUCCESSFUL (encountered one or more panics as expected) + +Checking harness valid_access::check_valid_array... +VERIFICATION:- SUCCESSFUL + +Checking harness valid_access::check_dangling_zst... +VERIFICATION:- SUCCESSFUL + +Complete - 5 successfully verified harnesses, 0 failures, 5 total. diff --git a/tests/kani/MemPredicates/thin_ptr_validity.rs b/tests/perf/mem-predicates/thin-ptr-validity/src/lib.rs similarity index 97% rename from tests/kani/MemPredicates/thin_ptr_validity.rs rename to tests/perf/mem-predicates/thin-ptr-validity/src/lib.rs index 553c5beab9f8..521c2cf6def7 100644 --- a/tests/kani/MemPredicates/thin_ptr_validity.rs +++ b/tests/perf/mem-predicates/thin-ptr-validity/src/lib.rs @@ -1,6 +1,6 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: -Z mem-predicates + //! Check that Kani's memory predicates work for thin pointers. extern crate kani; diff --git a/tests/perf/uninit/Cargo.toml b/tests/perf/uninit/Cargo.toml new file mode 100644 index 000000000000..9f44cb3fe103 --- /dev/null +++ b/tests/perf/uninit/Cargo.toml @@ -0,0 +1,14 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +[package] +name = "uninit" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[package.metadata.kani] +unstable = { ghost-state = true, uninit-checks = true } diff --git a/tests/perf/uninit/expected b/tests/perf/uninit/expected new file mode 100644 index 000000000000..f7b4fd303a77 --- /dev/null +++ b/tests/perf/uninit/expected @@ -0,0 +1 @@ +Complete - 5 successfully verified harnesses, 0 failures, 5 total. \ No newline at end of file diff --git a/tests/perf/uninit/src/lib.rs b/tests/perf/uninit/src/lib.rs new file mode 100644 index 000000000000..468d418362ae --- /dev/null +++ b/tests/perf/uninit/src/lib.rs @@ -0,0 +1,83 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#![feature(core_intrinsics)] +#![feature(custom_mir)] +#![allow(dropping_copy_types)] + +use std::alloc::{alloc, alloc_zeroed, dealloc, Layout}; +use std::intrinsics::mir::*; +use std::ptr; +use std::ptr::addr_of; +use std::slice::from_raw_parts; + +#[repr(C)] +struct S(u32, u8); + +#[kani::proof] +fn access_padding_init() { + let s = S(0, 0); + let ptr: *const u8 = addr_of!(s) as *const u8; + let padding = unsafe { *(ptr.add(4)) }; +} + +#[kani::proof] +fn alloc_to_slice() { + let layout = Layout::from_size_align(32, 8).unwrap(); + unsafe { + // This returns initialized memory. + let ptr = alloc(layout); + *ptr = 0x41; + *ptr.add(1) = 0x42; + *ptr.add(2) = 0x43; + *ptr.add(3) = 0x44; + *ptr.add(16) = 0x00; + let val = *(ptr.add(2)); + } +} + +#[kani::proof] +fn alloc_zeroed_to_slice() { + let layout = Layout::from_size_align(32, 8).unwrap(); + unsafe { + // This returns initialized memory. + let ptr = alloc_zeroed(layout); + *ptr = 0x41; + *ptr.add(1) = 0x42; + *ptr.add(2) = 0x43; + *ptr.add(3) = 0x44; + *ptr.add(16) = 0x00; + let slice1 = from_raw_parts(ptr, 16); + let slice2 = from_raw_parts(ptr.add(16), 16); + drop(slice1.cmp(slice2)); + dealloc(ptr, layout); + } +} + +#[kani::proof] +#[custom_mir(dialect = "runtime", phase = "optimized")] +fn struct_padding_and_arr_init() { + mir! { + let s: S; + let sptr; + let sptr2; + let _val; + { + sptr = ptr::addr_of_mut!(s); + sptr2 = sptr as *mut [u8; 4]; + *sptr2 = [0; 4]; + *sptr = S(0, 0); + // Both S(u16, u16) and [u8; 4] have the same layout, so the memory is initialized. + _val = *sptr2; + Return() + } + } +} + +#[kani::proof] +fn vec_read_init() { + let mut v: Vec = Vec::with_capacity(10); + unsafe { *v.as_mut_ptr().add(5) = 0x42 }; + let def = unsafe { *v.as_ptr().add(5) }; // Not UB since accessing initialized memory. + let x = def + 1; +} From 224239f511f670a26ce87b69778af4599b9f8670 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Tue, 25 Jun 2024 08:08:58 -0700 Subject: [PATCH 55/87] Split `split_bb` into two functions --- .../src/kani_middle/transform/body.rs | 119 ++++++++++-------- 1 file changed, 64 insertions(+), 55 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/body.rs b/kani-compiler/src/kani_middle/transform/body.rs index 4aa587e3bb0a..22895bd8d20d 100644 --- a/kani-compiler/src/kani_middle/transform/body.rs +++ b/kani-compiler/src/kani_middle/transform/body.rs @@ -225,77 +225,86 @@ impl MutableBody { self.split_bb(source, position, terminator); } - /// Split a basic block right before the source location and use the new terminator - /// in the basic block that was split. + /// Split a basic block and use the new terminator in the basic block that was split. /// /// The source is updated to point to the same instruction which is now in the new basic block. pub fn split_bb( &mut self, source: &mut SourceInstruction, position: InsertPosition, - mut new_term: Terminator, + new_term: Terminator, ) { - let new_bb_idx = self.blocks.len(); match position { InsertPosition::Before => { - let (idx, bb) = match source { - SourceInstruction::Statement { idx, bb } => { - let (orig_idx, orig_bb) = (*idx, *bb); - *idx = 0; - *bb = new_bb_idx; - (orig_idx, orig_bb) - } - SourceInstruction::Terminator { bb } => { - let (orig_idx, orig_bb) = (self.blocks[*bb].statements.len(), *bb); - *bb = new_bb_idx; - (orig_idx, orig_bb) - } - }; - let old_term = mem::replace(&mut self.blocks[bb].terminator, new_term); - let bb_stmts = &mut self.blocks[bb].statements; - let remaining = bb_stmts.split_off(idx); + self.split_bb_before(source, new_term); + } + InsertPosition::After => { + self.split_bb_after(source, new_term); + } + } + } + + /// Split a basic block right before the source location. + fn split_bb_before(&mut self, source: &mut SourceInstruction, new_term: Terminator) { + let new_bb_idx = self.blocks.len(); + let (idx, bb) = match source { + SourceInstruction::Statement { idx, bb } => { + let (orig_idx, orig_bb) = (*idx, *bb); + *idx = 0; + *bb = new_bb_idx; + (orig_idx, orig_bb) + } + SourceInstruction::Terminator { bb } => { + let (orig_idx, orig_bb) = (self.blocks[*bb].statements.len(), *bb); + *bb = new_bb_idx; + (orig_idx, orig_bb) + } + }; + let old_term = mem::replace(&mut self.blocks[bb].terminator, new_term); + let bb_stmts = &mut self.blocks[bb].statements; + let remaining = bb_stmts.split_off(idx); + let new_bb = BasicBlock { statements: remaining, terminator: old_term }; + self.blocks.push(new_bb); + } + + /// Split a basic block right after the source location. + fn split_bb_after(&mut self, source: &mut SourceInstruction, mut new_term: Terminator) { + let new_bb_idx = self.blocks.len(); + match source { + // Split the current block after the statement located at `source` + // and move the remaining statements into the new one. + SourceInstruction::Statement { idx, bb } => { + let (orig_idx, orig_bb) = (*idx, *bb); + *idx = 0; + *bb = new_bb_idx; + let old_term = mem::replace(&mut self.blocks[orig_bb].terminator, new_term); + let bb_stmts = &mut self.blocks[orig_bb].statements; + let remaining = bb_stmts.split_off(orig_idx + 1); let new_bb = BasicBlock { statements: remaining, terminator: old_term }; self.blocks.push(new_bb); } - InsertPosition::After => { - match source { - // Split the current block after the statement located at `source` - // and move the remaining statements into the new one. - SourceInstruction::Statement { idx, bb } => { - let (orig_idx, orig_bb) = (*idx, *bb); - *idx = 0; + // Make the terminator at `source` point at the new block, + // the terminator of which is a simple Goto instruction. + SourceInstruction::Terminator { bb } => { + let current_terminator = &mut self.blocks.get_mut(*bb).unwrap().terminator; + // Kani can only instrument function calls like this. + match (&mut current_terminator.kind, &mut new_term.kind) { + ( + TerminatorKind::Call { target: Some(target_bb), .. }, + TerminatorKind::Call { target: Some(new_target_bb), .. }, + ) => { + // Set the new terminator to point where previous terminator pointed. + *new_target_bb = *target_bb; + // Point the current terminator to the new terminator's basic block. + *target_bb = new_bb_idx; + // Update the current poisition. *bb = new_bb_idx; - let old_term = mem::replace(&mut self.blocks[orig_bb].terminator, new_term); - let bb_stmts = &mut self.blocks[orig_bb].statements; - let remaining = bb_stmts.split_off(orig_idx + 1); - let new_bb = BasicBlock { statements: remaining, terminator: old_term }; - self.blocks.push(new_bb); - } - // Make the terminator at `source` point at the new block, - // the terminator of which is a simple Goto instruction. - SourceInstruction::Terminator { bb } => { - let current_terminator = &mut self.blocks.get_mut(*bb).unwrap().terminator; - // Kani can only instrument function calls like this. - match (&mut current_terminator.kind, &mut new_term.kind) { - ( - TerminatorKind::Call { target: Some(target_bb), .. }, - TerminatorKind::Call { target: Some(new_target_bb), .. }, - ) => { - // Set the new terminator to point where previous terminator pointed. - *new_target_bb = *target_bb; - // Point the current terminator to the new terminator's basic block. - *target_bb = new_bb_idx; - // Update the current poisition. - *bb = new_bb_idx; - self.blocks - .push(BasicBlock { statements: vec![], terminator: new_term }); - } - _ => unimplemented!("Kani can only split blocks after calls."), - }; + self.blocks.push(BasicBlock { statements: vec![], terminator: new_term }); } + _ => unimplemented!("Kani can only split blocks after calls."), }; } - } + }; } /// Insert statement before or after the source instruction and update the source as needed. From 9706df2ad4243c049cf18f6696a58a984f9111b5 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Tue, 25 Jun 2024 08:59:09 -0700 Subject: [PATCH 56/87] Add missing `is_initialized` definition into `kani_core` --- library/kani_core/src/mem.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/library/kani_core/src/mem.rs b/library/kani_core/src/mem.rs index 8cb26f18663b..3b10856765a5 100644 --- a/library/kani_core/src/mem.rs +++ b/library/kani_core/src/mem.rs @@ -299,6 +299,13 @@ macro_rules! kani_mem { kani_intrinsic() } + /// Check whether `len * size_of::()` bytes are initialized starting from `ptr`. + #[rustc_diagnostic_item = "KaniIsInitialized"] + #[inline(never)] + pub fn is_initialized(_ptr: *const T, _len: usize) -> bool { + kani_intrinsic() + } + /// Get the object ID of the given pointer. #[rustc_diagnostic_item = "KaniPointerObject"] #[inline(never)] From 43f88c6d464f7efaaffc0781c62999f4fbb9226a Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Tue, 25 Jun 2024 09:46:05 -0700 Subject: [PATCH 57/87] Temporarily decouple `is_initialized` and `can_dereference` so that CI can run --- library/kani/src/mem.rs | 3 +-- library/kani_core/src/mem.rs | 9 ------- scripts/kani-regression.sh | 2 +- .../function-contract/const_fn.expected} | 0 .../function-contract/const_fn.rs} | 1 + .../const_fn_with_effect.expected} | 0 .../const_fn_with_effect.rs} | 1 + .../function-contract/valid_ptr.expected} | 0 .../function-contract/valid_ptr.rs} | 1 + .../MemPredicates/fat_ptr_validity.rs} | 2 +- .../MemPredicates/thin_ptr_validity.rs} | 0 .../const-fn-with-effect/Cargo.toml | 14 ---------- .../function-contract/const-fn/Cargo.toml | 14 ---------- .../function-contract/valid-ptr/Cargo.toml | 14 ---------- .../fat-ptr-validity/Cargo.toml | 14 ---------- .../mem-predicates/fat-ptr-validity/expected | 27 ------------------- .../thin-ptr-validity/Cargo.toml | 14 ---------- .../mem-predicates/thin-ptr-validity/expected | 20 -------------- tests/std-checks/core/src/slice.rs | 2 +- 19 files changed, 7 insertions(+), 131 deletions(-) rename tests/{perf/function-contract/const-fn-with-effect/expected => expected/function-contract/const_fn.expected} (100%) rename tests/{perf/function-contract/const-fn/src/lib.rs => expected/function-contract/const_fn.rs} (88%) rename tests/{perf/function-contract/const-fn/expected => expected/function-contract/const_fn_with_effect.expected} (100%) rename tests/{perf/function-contract/const-fn-with-effect/src/lib.rs => expected/function-contract/const_fn_with_effect.rs} (89%) rename tests/{perf/function-contract/valid-ptr/expected => expected/function-contract/valid_ptr.expected} (100%) rename tests/{perf/function-contract/valid-ptr/src/lib.rs => expected/function-contract/valid_ptr.rs} (97%) rename tests/{perf/mem-predicates/fat-ptr-validity/src/lib.rs => kani/MemPredicates/fat_ptr_validity.rs} (98%) rename tests/{perf/mem-predicates/thin-ptr-validity/src/lib.rs => kani/MemPredicates/thin_ptr_validity.rs} (100%) delete mode 100644 tests/perf/function-contract/const-fn-with-effect/Cargo.toml delete mode 100644 tests/perf/function-contract/const-fn/Cargo.toml delete mode 100644 tests/perf/function-contract/valid-ptr/Cargo.toml delete mode 100644 tests/perf/mem-predicates/fat-ptr-validity/Cargo.toml delete mode 100644 tests/perf/mem-predicates/fat-ptr-validity/expected delete mode 100644 tests/perf/mem-predicates/thin-ptr-validity/Cargo.toml delete mode 100644 tests/perf/mem-predicates/thin-ptr-validity/expected diff --git a/library/kani/src/mem.rs b/library/kani/src/mem.rs index 0b390e74288d..aaec88c1bb87 100644 --- a/library/kani/src/mem.rs +++ b/library/kani/src/mem.rs @@ -120,7 +120,6 @@ where let (thin_ptr, metadata) = ptr.to_raw_parts(); metadata.is_ptr_aligned(thin_ptr, Internal) && is_inbounds(&metadata, thin_ptr) - && is_initialized(ptr, 1) && unsafe { has_valid_value(ptr) } } @@ -148,7 +147,7 @@ where ::Metadata: PtrProperties, { let (thin_ptr, metadata) = ptr.to_raw_parts(); - is_inbounds(&metadata, thin_ptr) && is_initialized(ptr, 1) && unsafe { has_valid_value(ptr) } + is_inbounds(&metadata, thin_ptr) && unsafe { has_valid_value(ptr) } } /// Checks that `data_ptr` points to an allocation that can hold data of size calculated from `T`. diff --git a/library/kani_core/src/mem.rs b/library/kani_core/src/mem.rs index 3b10856765a5..f0d79ab44593 100644 --- a/library/kani_core/src/mem.rs +++ b/library/kani_core/src/mem.rs @@ -125,7 +125,6 @@ macro_rules! kani_mem { let (thin_ptr, metadata) = ptr.to_raw_parts(); metadata.is_ptr_aligned(thin_ptr, Internal) && is_inbounds(&metadata, thin_ptr) - && is_initialized(ptr, 1) && unsafe { has_valid_value(ptr) } } @@ -155,7 +154,6 @@ macro_rules! kani_mem { { let (thin_ptr, metadata) = ptr.to_raw_parts(); is_inbounds(&metadata, thin_ptr) - && is_initialized(ptr, 1) && unsafe { has_valid_value(ptr) } } @@ -299,13 +297,6 @@ macro_rules! kani_mem { kani_intrinsic() } - /// Check whether `len * size_of::()` bytes are initialized starting from `ptr`. - #[rustc_diagnostic_item = "KaniIsInitialized"] - #[inline(never)] - pub fn is_initialized(_ptr: *const T, _len: usize) -> bool { - kani_intrinsic() - } - /// Get the object ID of the given pointer. #[rustc_diagnostic_item = "KaniPointerObject"] #[inline(never)] diff --git a/scripts/kani-regression.sh b/scripts/kani-regression.sh index 5bf1885083e3..b1de293d533c 100755 --- a/scripts/kani-regression.sh +++ b/scripts/kani-regression.sh @@ -52,7 +52,7 @@ TESTS=( "kani kani" "expected expected" "ui expected" - # "std-checks cargo-kani" + "std-checks cargo-kani" "firecracker kani" "prusti kani" "smack kani" diff --git a/tests/perf/function-contract/const-fn-with-effect/expected b/tests/expected/function-contract/const_fn.expected similarity index 100% rename from tests/perf/function-contract/const-fn-with-effect/expected rename to tests/expected/function-contract/const_fn.expected diff --git a/tests/perf/function-contract/const-fn/src/lib.rs b/tests/expected/function-contract/const_fn.rs similarity index 88% rename from tests/perf/function-contract/const-fn/src/lib.rs rename to tests/expected/function-contract/const_fn.rs index 82af3cb5218e..44b937cedce4 100644 --- a/tests/perf/function-contract/const-fn/src/lib.rs +++ b/tests/expected/function-contract/const_fn.rs @@ -1,5 +1,6 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts -Zmem-predicates //! Check that Kani contract can be applied to a constant function. //! diff --git a/tests/perf/function-contract/const-fn/expected b/tests/expected/function-contract/const_fn_with_effect.expected similarity index 100% rename from tests/perf/function-contract/const-fn/expected rename to tests/expected/function-contract/const_fn_with_effect.expected diff --git a/tests/perf/function-contract/const-fn-with-effect/src/lib.rs b/tests/expected/function-contract/const_fn_with_effect.rs similarity index 89% rename from tests/perf/function-contract/const-fn-with-effect/src/lib.rs rename to tests/expected/function-contract/const_fn_with_effect.rs index 33027728ab9b..d57c1f42fe16 100644 --- a/tests/perf/function-contract/const-fn-with-effect/src/lib.rs +++ b/tests/expected/function-contract/const_fn_with_effect.rs @@ -1,5 +1,6 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts -Zmem-predicates //! Check that Kani contract can be applied to a constant function. //! diff --git a/tests/perf/function-contract/valid-ptr/expected b/tests/expected/function-contract/valid_ptr.expected similarity index 100% rename from tests/perf/function-contract/valid-ptr/expected rename to tests/expected/function-contract/valid_ptr.expected diff --git a/tests/perf/function-contract/valid-ptr/src/lib.rs b/tests/expected/function-contract/valid_ptr.rs similarity index 97% rename from tests/perf/function-contract/valid-ptr/src/lib.rs rename to tests/expected/function-contract/valid_ptr.rs index af8b6a96cef3..2047a46caf4f 100644 --- a/tests/perf/function-contract/valid-ptr/src/lib.rs +++ b/tests/expected/function-contract/valid_ptr.rs @@ -1,5 +1,6 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts -Zmem-predicates //! Test that it is sound to use memory predicates inside a contract pre-condition. //! We cannot validate post-condition yet. This can be done once we fix: diff --git a/tests/perf/mem-predicates/fat-ptr-validity/src/lib.rs b/tests/kani/MemPredicates/fat_ptr_validity.rs similarity index 98% rename from tests/perf/mem-predicates/fat-ptr-validity/src/lib.rs rename to tests/kani/MemPredicates/fat_ptr_validity.rs index ca0bcff54feb..c4f037f3a646 100644 --- a/tests/perf/mem-predicates/fat-ptr-validity/src/lib.rs +++ b/tests/kani/MemPredicates/fat_ptr_validity.rs @@ -1,6 +1,6 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT - +// kani-flags: -Z mem-predicates //! Check that Kani's memory predicates work for fat pointers. extern crate kani; diff --git a/tests/perf/mem-predicates/thin-ptr-validity/src/lib.rs b/tests/kani/MemPredicates/thin_ptr_validity.rs similarity index 100% rename from tests/perf/mem-predicates/thin-ptr-validity/src/lib.rs rename to tests/kani/MemPredicates/thin_ptr_validity.rs diff --git a/tests/perf/function-contract/const-fn-with-effect/Cargo.toml b/tests/perf/function-contract/const-fn-with-effect/Cargo.toml deleted file mode 100644 index a7827f1ae84e..000000000000 --- a/tests/perf/function-contract/const-fn-with-effect/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright Kani Contributors -# SPDX-License-Identifier: Apache-2.0 OR MIT - -[package] -name = "const-fn-with-effect" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] - -[package.metadata.kani] -unstable = { function-contracts = true, mem-predicates = true, ghost-state = true, uninit-checks = true } diff --git a/tests/perf/function-contract/const-fn/Cargo.toml b/tests/perf/function-contract/const-fn/Cargo.toml deleted file mode 100644 index 7b586019831e..000000000000 --- a/tests/perf/function-contract/const-fn/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright Kani Contributors -# SPDX-License-Identifier: Apache-2.0 OR MIT - -[package] -name = "const-fn" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] - -[package.metadata.kani] -unstable = { function-contracts = true, mem-predicates = true, ghost-state = true, uninit-checks = true } diff --git a/tests/perf/function-contract/valid-ptr/Cargo.toml b/tests/perf/function-contract/valid-ptr/Cargo.toml deleted file mode 100644 index f3f7221560b3..000000000000 --- a/tests/perf/function-contract/valid-ptr/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright Kani Contributors -# SPDX-License-Identifier: Apache-2.0 OR MIT - -[package] -name = "valid-ptr" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] - -[package.metadata.kani] -unstable = { function-contracts = true, mem-predicates = true, ghost-state = true, uninit-checks = true } diff --git a/tests/perf/mem-predicates/fat-ptr-validity/Cargo.toml b/tests/perf/mem-predicates/fat-ptr-validity/Cargo.toml deleted file mode 100644 index 4082e1ffd253..000000000000 --- a/tests/perf/mem-predicates/fat-ptr-validity/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright Kani Contributors -# SPDX-License-Identifier: Apache-2.0 OR MIT - -[package] -name = "fat-ptr-validity" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] - -[package.metadata.kani] -unstable = { mem-predicates = true, ghost-state = true, uninit-checks = true } diff --git a/tests/perf/mem-predicates/fat-ptr-validity/expected b/tests/perf/mem-predicates/fat-ptr-validity/expected deleted file mode 100644 index c38008e5ec8c..000000000000 --- a/tests/perf/mem-predicates/fat-ptr-validity/expected +++ /dev/null @@ -1,27 +0,0 @@ -Checking harness invalid_access::check_invalid_slice_len... -Failed Checks: The object size exceeds the maximum size supported by Kani's shadow memory model (64) -VERIFICATION:- SUCCESSFUL (encountered one or more panics as expected) - -Checking harness invalid_access::check_invalid_slice_ptr... -Failed Checks: Kani does not support reasoning about memory initialization of unsized pointers. -VERIFICATION:- SUCCESSFUL (encountered one or more panics as expected) - -Checking harness invalid_access::check_invalid_dyn_ptr... -Failed Checks: Kani does not support reasoning about memory initialization of unsized pointers. -VERIFICATION:- SUCCESSFUL (encountered one or more panics as expected) - -Checking harness valid_access::check_valid_zst... -VERIFICATION:- SUCCESSFUL - -Checking harness valid_access::check_valid_slice_ptr... -Failed Checks: Kani does not support reasoning about memory initialization of unsized pointers. -VERIFICATION:- FAILED - -Checking harness valid_access::check_valid_dyn_ptr... -Failed Checks: Kani does not support reasoning about memory initialization of unsized pointers. -VERIFICATION:- FAILED - -Summary: -Verification failed for - valid_access::check_valid_slice_ptr -Verification failed for - valid_access::check_valid_dyn_ptr -Complete - 4 successfully verified harnesses, 2 failures, 6 total. diff --git a/tests/perf/mem-predicates/thin-ptr-validity/Cargo.toml b/tests/perf/mem-predicates/thin-ptr-validity/Cargo.toml deleted file mode 100644 index 94badb22a0cc..000000000000 --- a/tests/perf/mem-predicates/thin-ptr-validity/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright Kani Contributors -# SPDX-License-Identifier: Apache-2.0 OR MIT - -[package] -name = "thin-ptr-validity" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] - -[package.metadata.kani] -unstable = { mem-predicates = true, ghost-state = true, uninit-checks = true } diff --git a/tests/perf/mem-predicates/thin-ptr-validity/expected b/tests/perf/mem-predicates/thin-ptr-validity/expected deleted file mode 100644 index 71da2581fa06..000000000000 --- a/tests/perf/mem-predicates/thin-ptr-validity/expected +++ /dev/null @@ -1,20 +0,0 @@ -Checking harness invalid_access::check_invalid_zst... -VERIFICATION:- SUCCESSFUL - -Checking harness invalid_access::check_invalid_array... -Failed Checks: assertion failed: can_dereference(raw_ptr) -Failed Checks: Kani does not support reasoning about pointer to unallocated memory -VERIFICATION:- SUCCESSFUL (encountered one or more panics as expected) - -Checking harness invalid_access::check_invalid_ptr... -Failed Checks: assertion failed: !can_dereference(raw_ptr) -Failed Checks: Kani does not support reasoning about pointer to unallocated memory -VERIFICATION:- SUCCESSFUL (encountered one or more panics as expected) - -Checking harness valid_access::check_valid_array... -VERIFICATION:- SUCCESSFUL - -Checking harness valid_access::check_dangling_zst... -VERIFICATION:- SUCCESSFUL - -Complete - 5 successfully verified harnesses, 0 failures, 5 total. diff --git a/tests/std-checks/core/src/slice.rs b/tests/std-checks/core/src/slice.rs index 9be19af1154b..dc43dd906397 100644 --- a/tests/std-checks/core/src/slice.rs +++ b/tests/std-checks/core/src/slice.rs @@ -6,7 +6,7 @@ pub mod contracts { use kani::{mem::*, requires}; #[requires(can_dereference(data))] - #[requires(is_initialized(data, len))] + // #[requires(is_initialized(data, len))] pub unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T] { std::slice::from_raw_parts(data, len) } From 4d7a97f2ccd0663deddf90484b8694e8f6815ed7 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Tue, 25 Jun 2024 09:47:21 -0700 Subject: [PATCH 58/87] Add missing flag --- tests/kani/MemPredicates/thin_ptr_validity.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/kani/MemPredicates/thin_ptr_validity.rs b/tests/kani/MemPredicates/thin_ptr_validity.rs index 521c2cf6def7..553c5beab9f8 100644 --- a/tests/kani/MemPredicates/thin_ptr_validity.rs +++ b/tests/kani/MemPredicates/thin_ptr_validity.rs @@ -1,6 +1,6 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT - +// kani-flags: -Z mem-predicates //! Check that Kani's memory predicates work for thin pointers. extern crate kani; From 1a80d983be4c9c26a16977c75d34e447e25651b9 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Tue, 25 Jun 2024 09:50:48 -0700 Subject: [PATCH 59/87] Formatting change --- library/kani_core/src/mem.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/kani_core/src/mem.rs b/library/kani_core/src/mem.rs index f0d79ab44593..8dd4a27cb03a 100644 --- a/library/kani_core/src/mem.rs +++ b/library/kani_core/src/mem.rs @@ -153,8 +153,7 @@ macro_rules! kani_mem { ::Metadata: PtrProperties, { let (thin_ptr, metadata) = ptr.to_raw_parts(); - is_inbounds(&metadata, thin_ptr) - && unsafe { has_valid_value(ptr) } + is_inbounds(&metadata, thin_ptr) && unsafe { has_valid_value(ptr) } } /// Checks that `data_ptr` points to an allocation that can hold data of size calculated from `T`. From ab6fbc951b29f367d2c6770c108c9b78c3e9d919 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Tue, 25 Jun 2024 10:24:20 -0700 Subject: [PATCH 60/87] Disable memory initialization checks for `std-core` --- tests/std-checks/core/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/std-checks/core/Cargo.toml b/tests/std-checks/core/Cargo.toml index 115ab40c90bd..f6e1645c3a39 100644 --- a/tests/std-checks/core/Cargo.toml +++ b/tests/std-checks/core/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" description = "This crate contains contracts and harnesses for core library" [package.metadata.kani] -unstable = { function-contracts = true, mem-predicates = true, ptr-to-ref-cast-checks = true, ghost-state = true, uninit-checks = true } +unstable = { function-contracts = true, mem-predicates = true, ptr-to-ref-cast-checks = true } [package.metadata.kani.flags] output-format = "terse" From 438a3b4e40acf38a0956f563fc56c1efd3195994 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Tue, 25 Jun 2024 10:59:38 -0700 Subject: [PATCH 61/87] Fix determining type layout for enums --- .../transform/check_uninit/ty_layout.rs | 83 +++++++++++++------ .../access-padding-enum-diverging-variants.rs | 34 ++++++++ .../access-padding-enum-multiple-variants.rs | 50 +++++++++++ .../access-padding-enum-single-field.rs | 35 ++++++++ .../access-padding-enum-single-variant.rs | 33 ++++++++ 5 files changed, 209 insertions(+), 26 deletions(-) create mode 100644 tests/kani/Uninit/access-padding-enum-diverging-variants.rs create mode 100644 tests/kani/Uninit/access-padding-enum-multiple-variants.rs create mode 100644 tests/kani/Uninit/access-padding-enum-single-field.rs create mode 100644 tests/kani/Uninit/access-padding-enum-single-variant.rs diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs index c34f784f5220..371cf1892ef2 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs @@ -3,7 +3,7 @@ // //! Utility functions that help calculate type layout. -use stable_mir::abi::{FieldsShape, Scalar, TagEncoding, ValueAbi, VariantsShape}; +use stable_mir::abi::{FieldsShape, LayoutShape, Scalar, TagEncoding, ValueAbi, VariantsShape}; use stable_mir::target::{MachineInfo, MachineSize}; use stable_mir::ty::{AdtKind, IndexedVal, RigidTy, Ty, TyKind, UintTy}; use stable_mir::CrateDef; @@ -102,26 +102,29 @@ impl PointeeInfo { } /// Get a size of an initialized scalar. -fn scalar_ty_size(machine_info: &MachineInfo, ty: Ty) -> Option { - let shape = ty.layout().unwrap().shape(); - match shape.abi { +fn scalar_ty_size( + machine_info: &MachineInfo, + layout_shape: LayoutShape, + current_offset: usize, +) -> DataBytes { + match layout_shape.abi { ValueAbi::Scalar(Scalar::Initialized { value, .. }) => { - Some(DataBytes { offset: 0, size: value.size(machine_info) }) + DataBytes { offset: current_offset, size: value.size(machine_info) } } ValueAbi::ScalarPair( Scalar::Initialized { value: value_first, .. }, Scalar::Initialized { value: value_second, .. }, - ) => Some(DataBytes { - offset: 0, + ) => DataBytes { + offset: current_offset, size: MachineSize::from_bits( value_first.size(machine_info).bits() + value_second.size(machine_info).bits(), ), - }), + }, ValueAbi::Scalar(_) | ValueAbi::ScalarPair(_, _) | ValueAbi::Uninhabited | ValueAbi::Vector { .. } - | ValueAbi::Aggregate { .. } => None, + | ValueAbi::Aggregate { .. } => unreachable!(), } } @@ -132,16 +135,9 @@ fn data_bytes_for_ty( current_offset: usize, ) -> Result, String> { let layout = ty.layout().unwrap().shape(); - let ty_size = || { - if let Some(mut size) = scalar_ty_size(machine_info, ty) { - size.offset = current_offset; - vec![size] - } else { - vec![] - } - }; + match layout.fields { - FieldsShape::Primitive => Ok(ty_size()), + FieldsShape::Primitive => Ok(vec![scalar_ty_size(machine_info, layout, current_offset)]), FieldsShape::Array { stride, count } if count > 0 => { let TyKind::RigidTy(RigidTy::Array(elem_ty, _)) = ty.kind() else { unreachable!() }; let elem_data_bytes = data_bytes_for_ty(machine_info, elem_ty, current_offset)?; @@ -192,14 +188,33 @@ fn data_bytes_for_ty( } => { Err(format!("Unsupported Enum `{}` check", def.trimmed_name()))? } - VariantsShape::Multiple { variants, .. } => { - let enum_data_bytes = ty_size(); + VariantsShape::Multiple { variants, tag, .. } => { + // Retrieve data bytes for the tag. + let tag_size = match tag { + Scalar::Initialized { value, .. } => { + value.size(&machine_info) + } + Scalar::Union { .. } => { + unreachable!("Enum tag should not be a union.") + } + }; + let tag_data_bytes = + vec![DataBytes { offset: current_offset, size: tag_size }]; + + // Retrieve data bytes for the fields. let mut fields_data_bytes = vec![]; + // Iterate over all variants for the enum. for (index, variant) in variants.iter().enumerate() { let mut field_data_bytes_for_variant = vec![]; let fields = ty_variants[index].fields(); + // Get offsets of all fields in a variant. + let FieldsShape::Arbitrary { offsets: field_offsets } = + variant.fields.clone() + else { + unreachable!() + }; for field_idx in variant.fields.fields_by_offset_order() { - let field_offset = offsets[field_idx].bytes(); + let field_offset = field_offsets[field_idx].bytes(); let field_ty = fields[field_idx].ty_with_args(&args); field_data_bytes_for_variant.append( &mut data_bytes_for_ty( @@ -213,19 +228,33 @@ fn data_bytes_for_ty( } if fields_data_bytes.is_empty() { - Ok(enum_data_bytes) + // If there are no fields, return the tag data bytes. + Ok(tag_data_bytes) } else if fields_data_bytes.iter().all( |data_bytes_for_variant| { - *data_bytes_for_variant - == *fields_data_bytes.first().unwrap() + // Byte layout for variant N. + let byte_mask_for_variant = generate_byte_mask( + layout.size.bytes(), + data_bytes_for_variant.clone(), + ); + // Byte layout for variant 0. + let byte_mask_for_first = generate_byte_mask( + layout.size.bytes(), + fields_data_bytes.first().unwrap().clone(), + ); + byte_mask_for_variant == byte_mask_for_first }, ) { - let mut total_data_bytes = enum_data_bytes; + // If all fields have the same layout, return fields data + // bytes. + let mut total_data_bytes = tag_data_bytes; let mut field_data_bytes = fields_data_bytes.first().unwrap().clone(); total_data_bytes.append(&mut field_data_bytes); Ok(total_data_bytes) } else { + // Struct has multiple padding variants, Kani cannot + // differentiate between them. Err(format!( "Unsupported Enum `{}` check", def.trimmed_name() @@ -281,7 +310,9 @@ fn data_bytes_for_ty( RigidTy::Str | RigidTy::Slice(_) | RigidTy::Array(_, _) => { unreachable!("Expected array layout for {ty:?}") } - RigidTy::RawPtr(_, _) | RigidTy::Ref(_, _, _) => Ok(ty_size()), + RigidTy::RawPtr(_, _) | RigidTy::Ref(_, _, _) => { + Ok(vec![scalar_ty_size(machine_info, layout, current_offset)]) + } RigidTy::FnDef(_, _) | RigidTy::FnPtr(_) | RigidTy::Closure(_, _) diff --git a/tests/kani/Uninit/access-padding-enum-diverging-variants.rs b/tests/kani/Uninit/access-padding-enum-diverging-variants.rs new file mode 100644 index 000000000000..ce561447dbc1 --- /dev/null +++ b/tests/kani/Uninit/access-padding-enum-diverging-variants.rs @@ -0,0 +1,34 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z ghost-state -Z uninit-checks + +use std::ptr; +use std::ptr::addr_of; + +// The layout of this enum is variable with, so Kani cannot check memory initialization statically. +#[repr(C)] +enum E1 { + A(u16, u8), + B(u16), +} + +// The layout of this enum is variable but both of the arms have the same padding, so Kani should +// support that. +#[repr(C)] +enum E2 { + A(u16), + B(u8, u8), +} + +#[kani::proof] +#[kani::should_panic] +fn access_padding_unsupported_e1() { + let s = E1::A(0, 0); + let ptr: *const u8 = addr_of!(s) as *const u8; +} + +#[kani::proof] +fn access_padding_unsupported_e2() { + let s = E2::A(0); + let ptr: *const u8 = addr_of!(s) as *const u8; +} diff --git a/tests/kani/Uninit/access-padding-enum-multiple-variants.rs b/tests/kani/Uninit/access-padding-enum-multiple-variants.rs new file mode 100644 index 000000000000..bea2332753af --- /dev/null +++ b/tests/kani/Uninit/access-padding-enum-multiple-variants.rs @@ -0,0 +1,50 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z ghost-state -Z uninit-checks + +use std::ptr; +use std::ptr::addr_of; + +// The layout of this enum is the following (D = data, P = padding): +// 0 1 2 3 4 5 6 7 +// [D, D, D, D, D, D, D, P] +// ---------- ------- +// \_ tag (i32) \_ A|B(u16, u8) + +#[repr(C)] +enum E { + A(u16, u8), + B(u16, u8), +} + +#[kani::proof] +fn access_padding_init_a() { + let s = E::A(0, 0); + let ptr: *const u8 = addr_of!(s) as *const u8; + let at_0 = unsafe { *(ptr.add(0)) }; + let at_4 = unsafe { *(ptr.add(4)) }; +} + +#[kani::proof] +fn access_padding_init_b() { + let s = E::A(0, 0); + let ptr: *const u8 = addr_of!(s) as *const u8; + let at_0 = unsafe { *(ptr.add(0)) }; + let at_4 = unsafe { *(ptr.add(4)) }; +} + +#[kani::proof] +#[kani::should_panic] +fn access_padding_uninit_a() { + let s = E::A(0, 0); + let ptr: *const u8 = addr_of!(s) as *const u8; + let at_7 = unsafe { *(ptr.add(7)) }; +} + +#[kani::proof] +#[kani::should_panic] +fn access_padding_uninit_b() { + let s = E::A(0, 0); + let ptr: *const u8 = addr_of!(s) as *const u8; + let at_7 = unsafe { *(ptr.add(7)) }; +} diff --git a/tests/kani/Uninit/access-padding-enum-single-field.rs b/tests/kani/Uninit/access-padding-enum-single-field.rs new file mode 100644 index 000000000000..cfbd4800695c --- /dev/null +++ b/tests/kani/Uninit/access-padding-enum-single-field.rs @@ -0,0 +1,35 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z ghost-state -Z uninit-checks + +use std::ptr; +use std::ptr::addr_of; + +// The layout of this enum is the following (D = data, P = padding): +// 0 1 2 3 4 5 6 7 8 9 A B C D E F +// [D, D, D, D, P, P, P, P, D, D, D, D, D, D, D, D] +// ---------- ---------------------- +// \_ tag (i32) \_ A(u64) + +#[repr(C)] +enum E { + A(u64), +} + +#[kani::proof] +fn access_padding_init() { + let s = E::A(0); + let ptr: *const u8 = addr_of!(s) as *const u8; + let at_0 = unsafe { *(ptr.add(0)) }; + let at_3 = unsafe { *(ptr.add(3)) }; + let at_9 = unsafe { *(ptr.add(9)) }; +} + +#[kani::proof] +#[kani::should_panic] +fn access_padding_uninit() { + let s = E::A(0); + let ptr: *const u8 = addr_of!(s) as *const u8; + let at_4 = unsafe { *(ptr.add(4)) }; + let at_7 = unsafe { *(ptr.add(7)) }; +} diff --git a/tests/kani/Uninit/access-padding-enum-single-variant.rs b/tests/kani/Uninit/access-padding-enum-single-variant.rs new file mode 100644 index 000000000000..6606c0f99bab --- /dev/null +++ b/tests/kani/Uninit/access-padding-enum-single-variant.rs @@ -0,0 +1,33 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z ghost-state -Z uninit-checks + +use std::ptr; +use std::ptr::addr_of; + +// The layout of this enum is the following (D = data, P = padding): +// 0 1 2 3 4 5 6 7 +// [D, D, D, D, D, D, D, P] +// ---------- ------- +// \_ tag (i32) \_ A(u16, u8) + +#[repr(C)] +enum E { + A(u16, u8), +} + +#[kani::proof] +fn access_padding_init() { + let s = E::A(0, 0); + let ptr: *const u8 = addr_of!(s) as *const u8; + let at_0 = unsafe { *(ptr.add(0)) }; + let at_4 = unsafe { *(ptr.add(4)) }; +} + +#[kani::proof] +#[kani::should_panic] +fn access_padding_uninit() { + let s = E::A(0, 0); + let ptr: *const u8 = addr_of!(s) as *const u8; + let at_7 = unsafe { *(ptr.add(7)) }; +} From e7f3972e84c84d3ee36f5eab42431ff50867dd4e Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Tue, 25 Jun 2024 11:23:50 -0700 Subject: [PATCH 62/87] Make `is_initialized` a noop if `-Z uninit-checks` flag is not set --- .../src/kani_middle/transform/kani_intrinsics.rs | 8 ++++++++ kani-compiler/src/kani_middle/transform/mod.rs | 6 +++++- library/kani/src/mem.rs | 3 ++- library/kani_core/src/mem.rs | 12 +++++++++++- .../Uninit/access-padding-enum-multiple-variants.rs | 2 +- .../Uninit/access-padding-enum-single-variant.rs | 2 +- tests/std-checks/core/mem_swap.expected | 11 +---------- tests/std-checks/core/src/slice.rs | 2 +- 8 files changed, 30 insertions(+), 16 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index f34f3979988c..ea7bf8625228 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -7,6 +7,7 @@ //! information; thus, they are implemented as a transformation pass where their body get generated //! by the transformation. +use crate::args::{Arguments, ExtraChecks}; use crate::kani_middle::attributes::matches_diagnostic; use crate::kani_middle::transform::body::{ CheckType, InsertPosition, MutableBody, SourceInstruction, @@ -37,6 +38,8 @@ pub struct IntrinsicGeneratorPass { pub check_type: CheckType, /// Used to cache FnDef lookups of injected memory initialization functions. pub mem_init_fn_cache: HashMap<&'static str, FnDef>, + /// Used to enable intrinsics depending on the flags passed. + pub arguments: Arguments, } impl TransformPass for IntrinsicGeneratorPass { @@ -177,6 +180,11 @@ impl IntrinsicGeneratorPass { let stmt = Statement { kind: assign, span }; new_body.insert_stmt(stmt, &mut terminator, InsertPosition::Before); + if !self.arguments.ub_check.contains(&ExtraChecks::Uninit) { + // Short-circut if uninitialized memory checks are not enabled. + return new_body.into(); + } + // The first argument type. let arg_ty = new_body.locals()[1].ty; let TyKind::RigidTy(RigidTy::RawPtr(target_ty, _)) = arg_ty.kind() else { unreachable!() }; diff --git a/kani-compiler/src/kani_middle/transform/mod.rs b/kani-compiler/src/kani_middle/transform/mod.rs index 46e249cf813a..6feb70c9eff3 100644 --- a/kani-compiler/src/kani_middle/transform/mod.rs +++ b/kani-compiler/src/kani_middle/transform/mod.rs @@ -72,7 +72,11 @@ impl BodyTransformation { ); transformer.add_pass( queries, - IntrinsicGeneratorPass { check_type, mem_init_fn_cache: HashMap::new() }, + IntrinsicGeneratorPass { + check_type, + mem_init_fn_cache: HashMap::new(), + arguments: queries.args().clone(), + }, ); transformer } diff --git a/library/kani/src/mem.rs b/library/kani/src/mem.rs index aaec88c1bb87..0b390e74288d 100644 --- a/library/kani/src/mem.rs +++ b/library/kani/src/mem.rs @@ -120,6 +120,7 @@ where let (thin_ptr, metadata) = ptr.to_raw_parts(); metadata.is_ptr_aligned(thin_ptr, Internal) && is_inbounds(&metadata, thin_ptr) + && is_initialized(ptr, 1) && unsafe { has_valid_value(ptr) } } @@ -147,7 +148,7 @@ where ::Metadata: PtrProperties, { let (thin_ptr, metadata) = ptr.to_raw_parts(); - is_inbounds(&metadata, thin_ptr) && unsafe { has_valid_value(ptr) } + is_inbounds(&metadata, thin_ptr) && is_initialized(ptr, 1) && unsafe { has_valid_value(ptr) } } /// Checks that `data_ptr` points to an allocation that can hold data of size calculated from `T`. diff --git a/library/kani_core/src/mem.rs b/library/kani_core/src/mem.rs index 8dd4a27cb03a..3b10856765a5 100644 --- a/library/kani_core/src/mem.rs +++ b/library/kani_core/src/mem.rs @@ -125,6 +125,7 @@ macro_rules! kani_mem { let (thin_ptr, metadata) = ptr.to_raw_parts(); metadata.is_ptr_aligned(thin_ptr, Internal) && is_inbounds(&metadata, thin_ptr) + && is_initialized(ptr, 1) && unsafe { has_valid_value(ptr) } } @@ -153,7 +154,9 @@ macro_rules! kani_mem { ::Metadata: PtrProperties, { let (thin_ptr, metadata) = ptr.to_raw_parts(); - is_inbounds(&metadata, thin_ptr) && unsafe { has_valid_value(ptr) } + is_inbounds(&metadata, thin_ptr) + && is_initialized(ptr, 1) + && unsafe { has_valid_value(ptr) } } /// Checks that `data_ptr` points to an allocation that can hold data of size calculated from `T`. @@ -296,6 +299,13 @@ macro_rules! kani_mem { kani_intrinsic() } + /// Check whether `len * size_of::()` bytes are initialized starting from `ptr`. + #[rustc_diagnostic_item = "KaniIsInitialized"] + #[inline(never)] + pub fn is_initialized(_ptr: *const T, _len: usize) -> bool { + kani_intrinsic() + } + /// Get the object ID of the given pointer. #[rustc_diagnostic_item = "KaniPointerObject"] #[inline(never)] diff --git a/tests/kani/Uninit/access-padding-enum-multiple-variants.rs b/tests/kani/Uninit/access-padding-enum-multiple-variants.rs index bea2332753af..2d824847e8cd 100644 --- a/tests/kani/Uninit/access-padding-enum-multiple-variants.rs +++ b/tests/kani/Uninit/access-padding-enum-multiple-variants.rs @@ -8,7 +8,7 @@ use std::ptr::addr_of; // The layout of this enum is the following (D = data, P = padding): // 0 1 2 3 4 5 6 7 // [D, D, D, D, D, D, D, P] -// ---------- ------- +// ---------- ------- // \_ tag (i32) \_ A|B(u16, u8) #[repr(C)] diff --git a/tests/kani/Uninit/access-padding-enum-single-variant.rs b/tests/kani/Uninit/access-padding-enum-single-variant.rs index 6606c0f99bab..5de721a9a9fb 100644 --- a/tests/kani/Uninit/access-padding-enum-single-variant.rs +++ b/tests/kani/Uninit/access-padding-enum-single-variant.rs @@ -8,7 +8,7 @@ use std::ptr::addr_of; // The layout of this enum is the following (D = data, P = padding): // 0 1 2 3 4 5 6 7 // [D, D, D, D, D, D, D, P] -// ---------- ------- +// ---------- ------- // \_ tag (i32) \_ A(u16, u8) #[repr(C)] diff --git a/tests/std-checks/core/mem_swap.expected b/tests/std-checks/core/mem_swap.expected index 37a7e87c6ee4..bdc69c25a077 100644 --- a/tests/std-checks/core/mem_swap.expected +++ b/tests/std-checks/core/mem_swap.expected @@ -1,22 +1,13 @@ Checking harness mem_swap::verify::check_swap_large_adt_no_drop... -Failed Checks: The object size exceeds the maximum size supported by Kani's shadow memory model (64) - Checking harness mem_swap::verify::check_swap_adt_no_drop... -Failed Checks: Kani currently doesn't support checking memory initialization for `*const std::mem::MaybeUninit>` - Checking harness mem_swap::verify::check_swap_unit... Failed Checks: ptr NULL or writable up to size Checking harness mem_swap::verify::check_swap_primitive... -Failed Checks: Kani currently doesn't support checking memory initialization for `*const std::mem::MaybeUninit` - Summary: -Verification failed for - mem_swap::verify::check_swap_large_adt_no_drop -Verification failed for - mem_swap::verify::check_swap_adt_no_drop Verification failed for - mem_swap::verify::check_swap_unit -Verification failed for - mem_swap::verify::check_swap_primitive -Complete - 0 successfully verified harnesses, 4 failures, 4 total. +Complete - 3 successfully verified harnesses, 1 failures, 4 total. diff --git a/tests/std-checks/core/src/slice.rs b/tests/std-checks/core/src/slice.rs index dc43dd906397..9be19af1154b 100644 --- a/tests/std-checks/core/src/slice.rs +++ b/tests/std-checks/core/src/slice.rs @@ -6,7 +6,7 @@ pub mod contracts { use kani::{mem::*, requires}; #[requires(can_dereference(data))] - // #[requires(is_initialized(data, len))] + #[requires(is_initialized(data, len))] pub unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T] { std::slice::from_raw_parts(data, len) } From 910f5557a00e8d681c4d8bfa8a7a854776090d8a Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Wed, 26 Jun 2024 16:40:37 -0700 Subject: [PATCH 63/87] Clean up test files --- .../access-padding-uninit.rs | 5 +-- .../access-padding-via-cast.rs | 21 ++++++++++++ .../expected | 0 .../uninit/alloc-to-slice/alloc-to-slice.rs | 14 +++----- .../struct-padding-and-arr-uninit.rs | 32 ------------------- .../vec-read-bad-len/vec-read-bad-len.rs | 7 ++-- .../vec-read-semi-init/vec-read-semi-init.rs | 6 ++-- .../uninit/vec-read-uninit/vec-read-uninit.rs | 6 ++-- 8 files changed, 38 insertions(+), 53 deletions(-) create mode 100644 tests/expected/uninit/access-padding-via-cast/access-padding-via-cast.rs rename tests/expected/uninit/{struct-padding-and-arr-uninit => access-padding-via-cast}/expected (100%) delete mode 100644 tests/expected/uninit/struct-padding-and-arr-uninit/struct-padding-and-arr-uninit.rs diff --git a/tests/expected/uninit/access-padding-uninit/access-padding-uninit.rs b/tests/expected/uninit/access-padding-uninit/access-padding-uninit.rs index 35c1782466d4..d6e735e219ad 100644 --- a/tests/expected/uninit/access-padding-uninit/access-padding-uninit.rs +++ b/tests/expected/uninit/access-padding-uninit/access-padding-uninit.rs @@ -7,9 +7,10 @@ use std::ptr::addr_of; #[repr(C)] struct S(u32, u8); +/// Checks that Kani catches an attempt to access padding of a struct using raw pointers. #[kani::proof] -fn main() { +fn check_uninit_padding() { let s = S(0, 0); let ptr: *const u8 = addr_of!(s) as *const u8; - let padding = unsafe { *(ptr.add(5)) }; + let padding = unsafe { *(ptr.add(5)) }; // ~ERROR: padding bytes are uninitialized, so reading them is UB. } diff --git a/tests/expected/uninit/access-padding-via-cast/access-padding-via-cast.rs b/tests/expected/uninit/access-padding-via-cast/access-padding-via-cast.rs new file mode 100644 index 000000000000..2cce33551511 --- /dev/null +++ b/tests/expected/uninit/access-padding-via-cast/access-padding-via-cast.rs @@ -0,0 +1,21 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z ghost-state -Z uninit-checks + +use std::ptr; +#[repr(C)] +struct S(u8, u16); + +/// Checks that Kani catches an attempt to access padding of a struct using casting to different types. +#[kani::proof] +fn check_uninit_padding_after_cast() { + unsafe { + let mut s = S(0, 0); + let sptr = ptr::addr_of_mut!(s); + let sptr2 = sptr as *mut [u8; 4]; + *sptr2 = [0; 4]; + *sptr = S(0, 0); // should reset the padding + let val = *sptr2; // should hence be UB + //~^ERROR: encountered uninitialized memory + } +} diff --git a/tests/expected/uninit/struct-padding-and-arr-uninit/expected b/tests/expected/uninit/access-padding-via-cast/expected similarity index 100% rename from tests/expected/uninit/struct-padding-and-arr-uninit/expected rename to tests/expected/uninit/access-padding-via-cast/expected diff --git a/tests/expected/uninit/alloc-to-slice/alloc-to-slice.rs b/tests/expected/uninit/alloc-to-slice/alloc-to-slice.rs index 76fb2994794e..3c4420f5791f 100644 --- a/tests/expected/uninit/alloc-to-slice/alloc-to-slice.rs +++ b/tests/expected/uninit/alloc-to-slice/alloc-to-slice.rs @@ -2,25 +2,19 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // kani-flags: -Z ghost-state -Z uninit-checks -#![allow(dropping_copy_types)] - use std::alloc::{alloc, dealloc, Layout}; use std::slice::from_raw_parts; +/// Checks that Kani catches an attempt to form a slice from uninitialized memory. #[kani::proof] -fn main() { - let layout = Layout::from_size_align(32, 8).unwrap(); +fn check_uninit_slice() { + let layout = Layout::from_size_align(16, 8).unwrap(); unsafe { let ptr = alloc(layout); *ptr = 0x41; *ptr.add(1) = 0x42; *ptr.add(2) = 0x43; *ptr.add(3) = 0x44; - *ptr.add(16) = 0x00; - // Forming a slice from unitialized memory is UB. - let slice1 = from_raw_parts(ptr, 16); - let slice2 = from_raw_parts(ptr.add(16), 16); - drop(slice1.cmp(slice2)); - dealloc(ptr, layout); + let uninit_slice = from_raw_parts(ptr, 16); // ~ERROR: forming a slice from unitialized memory is UB. } } diff --git a/tests/expected/uninit/struct-padding-and-arr-uninit/struct-padding-and-arr-uninit.rs b/tests/expected/uninit/struct-padding-and-arr-uninit/struct-padding-and-arr-uninit.rs deleted file mode 100644 index 254aece61871..000000000000 --- a/tests/expected/uninit/struct-padding-and-arr-uninit/struct-padding-and-arr-uninit.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: -Z ghost-state -Z uninit-checks - -#![feature(core_intrinsics)] -#![feature(custom_mir)] - -use std::intrinsics::mir::*; -use std::ptr; - -#[repr(C)] -struct S(u8, u16); - -#[kani::proof] -#[custom_mir(dialect = "runtime", phase = "optimized")] -fn main() { - mir! { - let s: S; - let sptr; - let sptr2; - let _val; - { - sptr = ptr::addr_of_mut!(s); - sptr2 = sptr as *mut [u8; 4]; - *sptr2 = [0; 4]; - *sptr = S(0, 0); // should reset the padding - _val = *sptr2; // should hence be UB - //~^ERROR: encountered uninitialized memory - Return() - } - } -} diff --git a/tests/expected/uninit/vec-read-bad-len/vec-read-bad-len.rs b/tests/expected/uninit/vec-read-bad-len/vec-read-bad-len.rs index ef8f72de8d16..9778bb11a277 100644 --- a/tests/expected/uninit/vec-read-bad-len/vec-read-bad-len.rs +++ b/tests/expected/uninit/vec-read-bad-len/vec-read-bad-len.rs @@ -4,11 +4,12 @@ use std::ops::Index; +/// Checks that Kani catches an attempt to read uninitialized memory from a vector with bad length. #[kani::proof] -fn main() { +fn check_vec_read_bad_len() { let mut v: Vec = Vec::with_capacity(10); unsafe { - v.set_len(5); + v.set_len(5); // even though length is now 5, vector is still uninitialized } - let el = v.index(0); + let uninit = v.index(0); // ~ERROR: reading from unitialized memory is UB. } diff --git a/tests/expected/uninit/vec-read-semi-init/vec-read-semi-init.rs b/tests/expected/uninit/vec-read-semi-init/vec-read-semi-init.rs index 5222001fa787..4330f5f53023 100644 --- a/tests/expected/uninit/vec-read-semi-init/vec-read-semi-init.rs +++ b/tests/expected/uninit/vec-read-semi-init/vec-read-semi-init.rs @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // kani-flags: -Z ghost-state -Z uninit-checks +/// Checks that Kani catches an attempt to read uninitialized memory from a semi-initialized vector. #[kani::proof] -fn main() { +fn check_vec_read_semi_init() { let mut v: Vec = Vec::with_capacity(10); unsafe { *v.as_mut_ptr().add(4) = 0x42 }; - let def = unsafe { *v.as_ptr().add(5) }; // Accessing uninit memory here. - let x = def + 1; + let uninit = unsafe { *v.as_ptr().add(5) }; // ~ERROR: reading from unitialized memory is UB. } diff --git a/tests/expected/uninit/vec-read-uninit/vec-read-uninit.rs b/tests/expected/uninit/vec-read-uninit/vec-read-uninit.rs index 6c7e25ea0226..c322b4d33bb2 100644 --- a/tests/expected/uninit/vec-read-uninit/vec-read-uninit.rs +++ b/tests/expected/uninit/vec-read-uninit/vec-read-uninit.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // kani-flags: -Z ghost-state -Z uninit-checks +/// Checks that Kani catches an attempt to read uninitialized memory from an uninitialized vector. #[kani::proof] -fn main() { +fn check_vec_read_uninit() { let v: Vec = Vec::with_capacity(10); - let undef = unsafe { *v.as_ptr().add(5) }; //~ ERROR: uninitialized - let x = undef + 1; + let uninit = unsafe { *v.as_ptr().add(5) }; // ~ERROR: reading from unitialized memory is UB. } From 5966c81a2ad2017c37b65953708d9cf18803c192 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Wed, 26 Jun 2024 16:45:34 -0700 Subject: [PATCH 64/87] Fix comments inside some tests --- .../Uninit/access-padding-enum-diverging-variants.rs | 10 +++++----- .../Uninit/access-padding-enum-multiple-variants.rs | 11 +++++------ tests/kani/Uninit/access-padding-enum-single-field.rs | 11 +++++------ .../kani/Uninit/access-padding-enum-single-variant.rs | 11 +++++------ 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/tests/kani/Uninit/access-padding-enum-diverging-variants.rs b/tests/kani/Uninit/access-padding-enum-diverging-variants.rs index ce561447dbc1..7feb493a5b3f 100644 --- a/tests/kani/Uninit/access-padding-enum-diverging-variants.rs +++ b/tests/kani/Uninit/access-padding-enum-diverging-variants.rs @@ -5,15 +5,15 @@ use std::ptr; use std::ptr::addr_of; -// The layout of this enum is variable with, so Kani cannot check memory initialization statically. +/// The layout of this enum is variable, so Kani cannot check memory initialization statically. #[repr(C)] enum E1 { A(u16, u8), B(u16), } -// The layout of this enum is variable but both of the arms have the same padding, so Kani should -// support that. +/// The layout of this enum is variable, but both of the arms have the same padding, so Kani should +/// support that. #[repr(C)] enum E2 { A(u16), @@ -22,13 +22,13 @@ enum E2 { #[kani::proof] #[kani::should_panic] -fn access_padding_unsupported_e1() { +fn access_padding_unsupported() { let s = E1::A(0, 0); let ptr: *const u8 = addr_of!(s) as *const u8; } #[kani::proof] -fn access_padding_unsupported_e2() { +fn access_padding_supported() { let s = E2::A(0); let ptr: *const u8 = addr_of!(s) as *const u8; } diff --git a/tests/kani/Uninit/access-padding-enum-multiple-variants.rs b/tests/kani/Uninit/access-padding-enum-multiple-variants.rs index 2d824847e8cd..fb8fae06ca59 100644 --- a/tests/kani/Uninit/access-padding-enum-multiple-variants.rs +++ b/tests/kani/Uninit/access-padding-enum-multiple-variants.rs @@ -5,12 +5,11 @@ use std::ptr; use std::ptr::addr_of; -// The layout of this enum is the following (D = data, P = padding): -// 0 1 2 3 4 5 6 7 -// [D, D, D, D, D, D, D, P] -// ---------- ------- -// \_ tag (i32) \_ A|B(u16, u8) - +/// The layout of this enum is the following (D = data, P = padding): +/// 0 1 2 3 4 5 6 7 +/// [D, D, D, D, D, D, D, P] +/// ---------- ------- +/// \_ tag (i32) \_ A|B(u16, u8) #[repr(C)] enum E { A(u16, u8), diff --git a/tests/kani/Uninit/access-padding-enum-single-field.rs b/tests/kani/Uninit/access-padding-enum-single-field.rs index cfbd4800695c..c283b603f705 100644 --- a/tests/kani/Uninit/access-padding-enum-single-field.rs +++ b/tests/kani/Uninit/access-padding-enum-single-field.rs @@ -5,12 +5,11 @@ use std::ptr; use std::ptr::addr_of; -// The layout of this enum is the following (D = data, P = padding): -// 0 1 2 3 4 5 6 7 8 9 A B C D E F -// [D, D, D, D, P, P, P, P, D, D, D, D, D, D, D, D] -// ---------- ---------------------- -// \_ tag (i32) \_ A(u64) - +/// The layout of this enum is the following (D = data, P = padding): +/// 0 1 2 3 4 5 6 7 8 9 A B C D E F +/// [D, D, D, D, P, P, P, P, D, D, D, D, D, D, D, D] +/// ---------- ---------------------- +/// \_ tag (i32) \_ A(u64) #[repr(C)] enum E { A(u64), diff --git a/tests/kani/Uninit/access-padding-enum-single-variant.rs b/tests/kani/Uninit/access-padding-enum-single-variant.rs index 5de721a9a9fb..f33cfe7ce6fb 100644 --- a/tests/kani/Uninit/access-padding-enum-single-variant.rs +++ b/tests/kani/Uninit/access-padding-enum-single-variant.rs @@ -5,12 +5,11 @@ use std::ptr; use std::ptr::addr_of; -// The layout of this enum is the following (D = data, P = padding): -// 0 1 2 3 4 5 6 7 -// [D, D, D, D, D, D, D, P] -// ---------- ------- -// \_ tag (i32) \_ A(u16, u8) - +/// The layout of this enum is the following (D = data, P = padding): +/// 0 1 2 3 4 5 6 7 +/// [D, D, D, D, D, D, D, P] +/// ---------- ------- +/// \_ tag (i32) \_ A(u16, u8) #[repr(C)] enum E { A(u16, u8), From 37c03c451d0531312a3fbc9f45db05a9beaa03a1 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Wed, 26 Jun 2024 16:47:38 -0700 Subject: [PATCH 65/87] Fix function documentation --- .../src/kani_middle/transform/check_uninit/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index e44587de59e3..d55441c6073f 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -171,8 +171,8 @@ impl UninitPass { } } - // Inject a load from shadow memory tracking memory initialization and an assertion that all - // non-padding bytes are initialized. + /// Inject a load from shadow memory tracking memory initialization and an assertion that all + /// non-padding bytes are initialized. fn build_get_and_check( &mut self, tcx: TyCtxt, @@ -252,8 +252,8 @@ impl UninitPass { ) } - // Inject a store into shadow memory tracking memory initialization to initialize or - // deinitialize all non-padding bytes. + /// Inject a store into shadow memory tracking memory initialization to initialize or + /// deinitialize all non-padding bytes. fn build_set( &mut self, tcx: TyCtxt, From 6a7ea3195b5ef04e6654a83b74be1f50c0f8bb03 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Wed, 26 Jun 2024 16:49:46 -0700 Subject: [PATCH 66/87] Small formatting change --- .../uninit/access-padding-via-cast/access-padding-via-cast.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/expected/uninit/access-padding-via-cast/access-padding-via-cast.rs b/tests/expected/uninit/access-padding-via-cast/access-padding-via-cast.rs index 2cce33551511..b73bebc827bc 100644 --- a/tests/expected/uninit/access-padding-via-cast/access-padding-via-cast.rs +++ b/tests/expected/uninit/access-padding-via-cast/access-padding-via-cast.rs @@ -15,7 +15,6 @@ fn check_uninit_padding_after_cast() { let sptr2 = sptr as *mut [u8; 4]; *sptr2 = [0; 4]; *sptr = S(0, 0); // should reset the padding - let val = *sptr2; // should hence be UB - //~^ERROR: encountered uninitialized memory + let val = *sptr2; // ~ERROR: encountered uninitialized memory } } From 6146a4bcc0764f6baf76e4ecf298433694e081d9 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 27 Jun 2024 08:11:58 -0700 Subject: [PATCH 67/87] Changes to `slice.rs` --- tests/std-checks/core/src/slice.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/std-checks/core/src/slice.rs b/tests/std-checks/core/src/slice.rs index 9be19af1154b..044f4bd38586 100644 --- a/tests/std-checks/core/src/slice.rs +++ b/tests/std-checks/core/src/slice.rs @@ -5,7 +5,7 @@ pub mod contracts { use kani::{mem::*, requires}; - #[requires(can_dereference(data))] + #[requires(can_dereference(std::ptr::slice_from_raw_parts(data, len)))] #[requires(is_initialized(data, len))] pub unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T] { std::slice::from_raw_parts(data, len) @@ -16,15 +16,13 @@ pub mod contracts { mod verify { use super::*; - const LEN_MIN: usize = 1; - const LEN_MAX: usize = 2; + const MAX_LEN: usize = 2; #[kani::proof_for_contract(contracts::from_raw_parts)] #[kani::unwind(25)] pub fn check_from_raw_parts_primitive() { let len: usize = kani::any(); - kani::assume(len >= LEN_MIN); - kani::assume(len < LEN_MAX); + kani::assume(len < MAX_LEN); let arr = vec![0u8; len]; let _slice = unsafe { contracts::from_raw_parts(arr.as_ptr(), len) }; From ce454a3379a289d44aa196a599a7de6c8c3c9fe1 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 27 Jun 2024 08:19:33 -0700 Subject: [PATCH 68/87] Merge `mem_swap` and `mem_replace` into `mem` --- tests/std-checks/core/mem.expected | 7 +++ tests/std-checks/core/mem_replace.expected | 1 - tests/std-checks/core/mem_swap.expected | 13 ----- tests/std-checks/core/src/lib.rs | 3 +- .../core/src/{mem_swap.rs => mem.rs} | 35 ++++++++++++ tests/std-checks/core/src/mem_replace.rs | 57 ------------------- 6 files changed, 43 insertions(+), 73 deletions(-) create mode 100644 tests/std-checks/core/mem.expected delete mode 100644 tests/std-checks/core/mem_replace.expected delete mode 100644 tests/std-checks/core/mem_swap.expected rename tests/std-checks/core/src/{mem_swap.rs => mem.rs} (66%) delete mode 100644 tests/std-checks/core/src/mem_replace.rs diff --git a/tests/std-checks/core/mem.expected b/tests/std-checks/core/mem.expected new file mode 100644 index 000000000000..1484c83901fc --- /dev/null +++ b/tests/std-checks/core/mem.expected @@ -0,0 +1,7 @@ +Checking harness mem::verify::check_swap_unit... + +Failed Checks: ptr NULL or writable up to size + +Summary: +Verification failed for - mem::verify::check_swap_unit +Complete - 6 successfully verified harnesses, 1 failures, 7 total. diff --git a/tests/std-checks/core/mem_replace.expected b/tests/std-checks/core/mem_replace.expected deleted file mode 100644 index 9427535ab675..000000000000 --- a/tests/std-checks/core/mem_replace.expected +++ /dev/null @@ -1 +0,0 @@ -Complete - 3 successfully verified harnesses, 0 failures, 3 total. diff --git a/tests/std-checks/core/mem_swap.expected b/tests/std-checks/core/mem_swap.expected deleted file mode 100644 index bdc69c25a077..000000000000 --- a/tests/std-checks/core/mem_swap.expected +++ /dev/null @@ -1,13 +0,0 @@ -Checking harness mem_swap::verify::check_swap_large_adt_no_drop... - -Checking harness mem_swap::verify::check_swap_adt_no_drop... - -Checking harness mem_swap::verify::check_swap_unit... - -Failed Checks: ptr NULL or writable up to size - -Checking harness mem_swap::verify::check_swap_primitive... - -Summary: -Verification failed for - mem_swap::verify::check_swap_unit -Complete - 3 successfully verified harnesses, 1 failures, 4 total. diff --git a/tests/std-checks/core/src/lib.rs b/tests/std-checks/core/src/lib.rs index 3c6b390dfb4f..b0a4fbc6154f 100644 --- a/tests/std-checks/core/src/lib.rs +++ b/tests/std-checks/core/src/lib.rs @@ -5,7 +5,6 @@ extern crate kani; -pub mod mem_replace; -pub mod mem_swap; +pub mod mem; pub mod ptr; pub mod slice; diff --git a/tests/std-checks/core/src/mem_swap.rs b/tests/std-checks/core/src/mem.rs similarity index 66% rename from tests/std-checks/core/src/mem_swap.rs rename to tests/std-checks/core/src/mem.rs index 4f41d176a73a..b0400d0a75f5 100644 --- a/tests/std-checks/core/src/mem_swap.rs +++ b/tests/std-checks/core/src/mem.rs @@ -18,6 +18,11 @@ pub mod contracts { pub fn swap(x: &mut T, y: &mut T) { std::mem::swap(x, y) } + + #[kani::modifies(dest)] + pub fn replace(dest: &mut T, src: T) -> T { + std::mem::replace(dest, src) + } } #[cfg(kani)] @@ -70,4 +75,34 @@ mod verify { std::mem::forget(x); std::mem::forget(y); } + + #[kani::proof_for_contract(contracts::replace)] + pub fn check_replace_primitive() { + let mut x: u8 = kani::any(); + let x_before = x; + + let y: u8 = kani::any(); + let x_returned = contracts::replace(&mut x, y); + + kani::assert(x_before == x_returned, "x_before == x_returned"); + } + + #[kani::proof_for_contract(contracts::replace)] + pub fn check_replace_adt_no_drop() { + let mut x: CannotDrop = kani::any(); + let y: CannotDrop = kani::any(); + let new_x = contracts::replace(&mut x, y); + std::mem::forget(x); + std::mem::forget(new_x); + } + + /// Memory replace logic is optimized according to the size and alignment of a type. + #[kani::proof_for_contract(contracts::replace)] + pub fn check_replace_large_adt_no_drop() { + let mut x: CannotDrop<[u128; 4]> = kani::any(); + let y: CannotDrop<[u128; 4]> = kani::any(); + let new_x = contracts::replace(&mut x, y); + std::mem::forget(x); + std::mem::forget(new_x); + } } diff --git a/tests/std-checks/core/src/mem_replace.rs b/tests/std-checks/core/src/mem_replace.rs deleted file mode 100644 index 039c53aa1d54..000000000000 --- a/tests/std-checks/core/src/mem_replace.rs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -/// Create wrapper functions to standard library functions that contains their contract. -pub mod contracts { - #[kani::modifies(dest)] - pub fn replace(dest: &mut T, src: T) -> T { - std::mem::replace(dest, src) - } -} - -#[cfg(kani)] -mod verify { - use super::*; - - /// Use this type to ensure that mem replace does not drop the value. - #[derive(kani::Arbitrary)] - struct CannotDrop { - inner: T, - } - - impl Drop for CannotDrop { - fn drop(&mut self) { - unreachable!("Cannot drop") - } - } - - #[kani::proof_for_contract(contracts::replace)] - pub fn check_replace_primitive() { - let mut x: u8 = kani::any(); - let x_before = x; - - let y: u8 = kani::any(); - let x_returned = contracts::replace(&mut x, y); - - kani::assert(x_before == x_returned, "x_before == x_returned"); - } - - #[kani::proof_for_contract(contracts::replace)] - pub fn check_replace_adt_no_drop() { - let mut x: CannotDrop = kani::any(); - let y: CannotDrop = kani::any(); - let new_x = contracts::replace(&mut x, y); - std::mem::forget(x); - std::mem::forget(new_x); - } - - /// Memory replace logic is optimized according to the size and alignment of a type. - #[kani::proof_for_contract(contracts::replace)] - pub fn check_replace_large_adt_no_drop() { - let mut x: CannotDrop<[u128; 4]> = kani::any(); - let y: CannotDrop<[u128; 4]> = kani::any(); - let new_x = contracts::replace(&mut x, y); - std::mem::forget(x); - std::mem::forget(new_x); - } -} From 3dab8d36c9081273b243fbc661959bf8768c4faa Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 27 Jun 2024 08:23:00 -0700 Subject: [PATCH 69/87] Rename `try_mark_new_bb_as_skipped` --- .../kani_middle/transform/check_uninit/mod.rs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index d55441c6073f..463974217002 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -129,7 +129,7 @@ impl UninitPass { skip_first: &mut HashSet, ) { if let SourceOp::Unsupported { reason, position } = &operation { - try_mark_new_bb_as_skipped(&operation, body, skip_first); + collect_skipped(&operation, body, skip_first); self.unsupported_check(tcx, body, source, *position, reason); return; }; @@ -151,7 +151,7 @@ impl UninitPass { let reason = format!( "Kani currently doesn't support checking memory initialization for pointers to `{pointee_ty}.", ); - try_mark_new_bb_as_skipped(&operation, body, skip_first); + collect_skipped(&operation, body, skip_first); self.unsupported_check(tcx, body, source, operation.position(), &reason); return; } @@ -195,7 +195,7 @@ impl UninitPass { *pointee_info.ty(), ); let layout_operand = mk_layout_operand(body, source, operation.position(), &layout); - try_mark_new_bb_as_skipped(&operation, body, skip_first); + collect_skipped(&operation, body, skip_first); body.add_call( &is_ptr_initialized_instance, source, @@ -222,7 +222,7 @@ impl UninitPass { ); let layout_operand = mk_layout_operand(body, source, operation.position(), &element_layout); - try_mark_new_bb_as_skipped(&operation, body, skip_first); + collect_skipped(&operation, body, skip_first); body.add_call( &is_ptr_initialized_instance, source, @@ -232,7 +232,7 @@ impl UninitPass { ); } PointeeLayout::TraitObject => { - try_mark_new_bb_as_skipped(&operation, body, skip_first); + collect_skipped(&operation, body, skip_first); let reason = "Kani does not support reasoning about memory initialization of pointers to trait objects."; self.unsupported_check(tcx, body, source, operation.position(), reason); return; @@ -240,7 +240,7 @@ impl UninitPass { }; // Make sure all non-padding bytes are initialized. - try_mark_new_bb_as_skipped(&operation, body, skip_first); + collect_skipped(&operation, body, skip_first); let ptr_operand_ty = ptr_operand.ty(body.locals()).unwrap(); body.add_check( tcx, @@ -278,7 +278,7 @@ impl UninitPass { *pointee_info.ty(), ); let layout_operand = mk_layout_operand(body, source, operation.position(), &layout); - try_mark_new_bb_as_skipped(&operation, body, skip_first); + collect_skipped(&operation, body, skip_first); body.add_call( &set_ptr_initialized_instance, source, @@ -314,7 +314,7 @@ impl UninitPass { ); let layout_operand = mk_layout_operand(body, source, operation.position(), &element_layout); - try_mark_new_bb_as_skipped(&operation, body, skip_first); + collect_skipped(&operation, body, skip_first); body.add_call( &set_ptr_initialized_instance, source, @@ -332,7 +332,7 @@ impl UninitPass { ); } PointeeLayout::TraitObject => { - try_mark_new_bb_as_skipped(&operation, body, skip_first); + collect_skipped(&operation, body, skip_first); let reason = "Kani does not support reasoning about memory initialization of pointers to trait objects."; self.unsupported_check(tcx, body, source, operation.position(), reason); } @@ -389,7 +389,7 @@ pub fn mk_layout_operand( /// If injecting a new call to the function before the current statement, need to skip the original /// statement when analyzing it as a part of the new basic block. -fn try_mark_new_bb_as_skipped( +fn collect_skipped( operation: &SourceOp, body: &MutableBody, skip_first: &mut HashSet, From 2e34fa51d168b6730cba1801d55a6a6ecbf13093 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 27 Jun 2024 08:32:18 -0700 Subject: [PATCH 70/87] Mark changing the initialization state of a trait object as unreachable --- .../src/kani_middle/transform/check_uninit/mod.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index 463974217002..8f728e3b3546 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -332,9 +332,7 @@ impl UninitPass { ); } PointeeLayout::TraitObject => { - collect_skipped(&operation, body, skip_first); - let reason = "Kani does not support reasoning about memory initialization of pointers to trait objects."; - self.unsupported_check(tcx, body, source, operation.position(), reason); + unreachable!("Cannot change the initialization state of a trait object directly."); } }; } @@ -389,11 +387,7 @@ pub fn mk_layout_operand( /// If injecting a new call to the function before the current statement, need to skip the original /// statement when analyzing it as a part of the new basic block. -fn collect_skipped( - operation: &SourceOp, - body: &MutableBody, - skip_first: &mut HashSet, -) { +fn collect_skipped(operation: &SourceOp, body: &MutableBody, skip_first: &mut HashSet) { if operation.position() == InsertPosition::Before { let new_bb_idx = body.blocks().len(); skip_first.insert(new_bb_idx); From 96238df5d3a8ab616a7b933a29fff81e33c44672 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 27 Jun 2024 08:37:23 -0700 Subject: [PATCH 71/87] Add a better comment for `mk_layout_operand` --- .../src/kani_middle/transform/check_uninit/mod.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index 8f728e3b3546..6c7cc89078e3 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -356,7 +356,18 @@ impl UninitPass { } } -/// Generate a bit array denoting padding vs data bytes for a layout. +/// Create an operand from a bit array that represents a byte mask for a type layout where padding +/// bytes are marked as `false` and data bytes are marked as `true`. +/// +/// For example, the layout for: +/// ``` +/// [repr(C)] +/// struct { +/// a: u16, +/// b: u8 +/// } +/// ``` +/// will have the following byte mask `[true, true, true, false]`. pub fn mk_layout_operand( body: &mut MutableBody, source: &mut SourceInstruction, From 01fb654cb54fbf1268e890b440e6ef5c7d29d8c8 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 27 Jun 2024 08:39:07 -0700 Subject: [PATCH 72/87] Add a better doc comment to `Layout` --- .../src/kani_middle/transform/check_uninit/ty_layout.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs index 371cf1892ef2..ab46e05e1370 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs @@ -17,7 +17,8 @@ struct DataBytes { size: MachineSize, } -/// Bytewise mask, representing which bytes of a type are data and which are padding. +/// Bytewise mask, representing which bytes of a type are data and which are padding. Here, `false` +/// represents padding bytes and `true` represents data bytes. type Layout = Vec; /// Create a byte-wise mask from known chunks of data bytes. From e42bdfd47a32fdab67f9f270b29739ddabff87b3 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 27 Jun 2024 08:56:28 -0700 Subject: [PATCH 73/87] Rename `SourceOp` to `MemoryInitOp`, add comments --- .../kani_middle/transform/check_uninit/mod.rs | 22 ++-- .../transform/check_uninit/uninit_visitor.rs | 110 ++++++++++-------- 2 files changed, 71 insertions(+), 61 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index 6c7cc89078e3..16893ef3224f 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -27,7 +27,7 @@ mod ty_layout; mod uninit_visitor; pub use ty_layout::{PointeeInfo, PointeeLayout}; -use uninit_visitor::{CheckUninitVisitor, InitRelevantInstruction, SourceOp}; +use uninit_visitor::{CheckUninitVisitor, InitRelevantInstruction, MemoryInitOp}; const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = &["KaniIsUnitPtrInitialized", "KaniSetUnitPtrInitialized"]; @@ -125,10 +125,10 @@ impl UninitPass { tcx: TyCtxt, body: &mut MutableBody, source: &mut SourceInstruction, - operation: SourceOp, + operation: MemoryInitOp, skip_first: &mut HashSet, ) { - if let SourceOp::Unsupported { reason, position } = &operation { + if let MemoryInitOp::Unsupported { reason, position } = &operation { collect_skipped(&operation, body, skip_first); self.unsupported_check(tcx, body, source, *position, reason); return; @@ -159,13 +159,13 @@ impl UninitPass { }; match operation { - SourceOp::Get { .. } => { + MemoryInitOp::Get { .. } => { self.build_get_and_check(tcx, body, source, operation, pointee_ty_info, skip_first) } - SourceOp::Set { .. } | SourceOp::SetRef { .. } => { + MemoryInitOp::Set { .. } | MemoryInitOp::SetRef { .. } => { self.build_set(tcx, body, source, operation, pointee_ty_info, skip_first) } - SourceOp::Unsupported { .. } => { + MemoryInitOp::Unsupported { .. } => { unreachable!() } } @@ -178,7 +178,7 @@ impl UninitPass { tcx: TyCtxt, body: &mut MutableBody, source: &mut SourceInstruction, - operation: SourceOp, + operation: MemoryInitOp, pointee_info: PointeeInfo, skip_first: &mut HashSet, ) { @@ -259,7 +259,7 @@ impl UninitPass { tcx: TyCtxt, body: &mut MutableBody, source: &mut SourceInstruction, - operation: SourceOp, + operation: MemoryInitOp, pointee_info: PointeeInfo, skip_first: &mut HashSet, ) { @@ -357,8 +357,8 @@ impl UninitPass { } /// Create an operand from a bit array that represents a byte mask for a type layout where padding -/// bytes are marked as `false` and data bytes are marked as `true`. -/// +/// bytes are marked as `false` and data bytes are marked as `true`. +/// /// For example, the layout for: /// ``` /// [repr(C)] @@ -398,7 +398,7 @@ pub fn mk_layout_operand( /// If injecting a new call to the function before the current statement, need to skip the original /// statement when analyzing it as a part of the new basic block. -fn collect_skipped(operation: &SourceOp, body: &MutableBody, skip_first: &mut HashSet) { +fn collect_skipped(operation: &MemoryInitOp, body: &MutableBody, skip_first: &mut HashSet) { if operation.position() == InsertPosition::Before { let new_bb_idx = body.blocks().len(); skip_first.insert(new_bb_idx); diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index 9dbcea96d4df..1a3b21e5395d 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -15,19 +15,29 @@ use stable_mir::mir::{ use stable_mir::ty::{ConstantKind, MirConst, RigidTy, Span, TyKind, UintTy}; use strum_macros::AsRefStr; +/// Memory initialization operations: set or get memory initialization state for a given pointer. #[derive(AsRefStr, Clone, Debug)] -pub enum SourceOp { +pub enum MemoryInitOp { + /// Check memory initialization of data bytes in a memory region starting from the pointer + /// `operand` and of length `count * sizeof(operand)` bytes. Get { operand: Operand, count: Operand, position: InsertPosition }, + /// Set memory initialization state of data bytes in a memory region starting from the pointer + /// `operand` and of length `count * sizeof(operand)` bytes. Set { operand: Operand, count: Operand, value: bool, position: InsertPosition }, + /// Check memory initialization of data bytes in a memory region starting from the reference to + /// `operand` and of length `count * sizeof(operand)` bytes. SetRef { operand: Operand, count: Operand, value: bool, position: InsertPosition }, + /// Unsupported memory initialization operation. Unsupported { reason: String, position: InsertPosition }, } -impl SourceOp { +impl MemoryInitOp { pub fn mk_operand(&self, body: &mut MutableBody, source: &mut SourceInstruction) -> Operand { match self { - SourceOp::Get { operand, .. } | SourceOp::Set { operand, .. } => operand.clone(), - SourceOp::SetRef { operand, .. } => Operand::Copy(Place { + MemoryInitOp::Get { operand, .. } | MemoryInitOp::Set { operand, .. } => { + operand.clone() + } + MemoryInitOp::SetRef { operand, .. } => Operand::Copy(Place { local: { let place = match operand { Operand::Copy(place) | Operand::Move(place) => place, @@ -41,32 +51,32 @@ impl SourceOp { }, projection: vec![], }), - SourceOp::Unsupported { .. } => unreachable!(), + MemoryInitOp::Unsupported { .. } => unreachable!(), } } pub fn expect_count(&self) -> Operand { match self { - SourceOp::Get { count, .. } - | SourceOp::Set { count, .. } - | SourceOp::SetRef { count, .. } => count.clone(), - SourceOp::Unsupported { .. } => unreachable!(), + MemoryInitOp::Get { count, .. } + | MemoryInitOp::Set { count, .. } + | MemoryInitOp::SetRef { count, .. } => count.clone(), + MemoryInitOp::Unsupported { .. } => unreachable!(), } } pub fn expect_value(&self) -> bool { match self { - SourceOp::Set { value, .. } | SourceOp::SetRef { value, .. } => *value, - SourceOp::Get { .. } | SourceOp::Unsupported { .. } => unreachable!(), + MemoryInitOp::Set { value, .. } | MemoryInitOp::SetRef { value, .. } => *value, + MemoryInitOp::Get { .. } | MemoryInitOp::Unsupported { .. } => unreachable!(), } } pub fn position(&self) -> InsertPosition { match self { - SourceOp::Get { position, .. } - | SourceOp::SetRef { position, .. } - | SourceOp::Unsupported { position, .. } - | SourceOp::Set { position, .. } => *position, + MemoryInitOp::Get { position, .. } + | MemoryInitOp::SetRef { position, .. } + | MemoryInitOp::Unsupported { position, .. } + | MemoryInitOp::Set { position, .. } => *position, } } } @@ -76,13 +86,13 @@ pub struct InitRelevantInstruction { /// The instruction that affects the state of the memory. pub source: SourceInstruction, /// All memory-related operations that should happen after the instruction. - pub before_instruction: Vec, + pub before_instruction: Vec, /// All memory-related operations that should happen after the instruction. - pub after_instruction: Vec, + pub after_instruction: Vec, } impl InitRelevantInstruction { - pub fn push_operation(&mut self, source_op: SourceOp) { + pub fn push_operation(&mut self, source_op: MemoryInitOp) { match source_op.position() { InsertPosition::Before => self.before_instruction.push(source_op), InsertPosition::After => self.after_instruction.push(source_op), @@ -150,7 +160,7 @@ impl<'a> CheckUninitVisitor<'a> { visitor.target } - fn push_target(&mut self, source_op: SourceOp) { + fn push_target(&mut self, source_op: MemoryInitOp) { let target = self.target.get_or_insert_with(|| InitRelevantInstruction { source: self.current, after_instruction: vec![], @@ -170,13 +180,13 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { StatementKind::Intrinsic(NonDivergingIntrinsic::CopyNonOverlapping(copy)) => { self.super_statement(stmt, location); // Source is a *const T and it must be initialized. - self.push_target(SourceOp::Get { + self.push_target(MemoryInitOp::Get { operand: copy.src.clone(), count: copy.count.clone(), position: InsertPosition::Before, }); // Destimation is a *mut T so it gets initialized. - self.push_target(SourceOp::Set { + self.push_target(MemoryInitOp::Set { operand: copy.dst.clone(), count: copy.count.clone(), value: true, @@ -189,7 +199,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { // Check whether we are assigning into a dereference (*ptr = _). if let Some(place_without_deref) = try_remove_topmost_deref(place) { if place_without_deref.ty(&self.locals).unwrap().kind().is_raw_ptr() { - self.push_target(SourceOp::Set { + self.push_target(MemoryInitOp::Set { operand: Operand::Copy(place_without_deref), count: mk_const_operand(1, location.span()), value: true, @@ -200,7 +210,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { // Check whether Rvalue creates a new initialized pointer previously not captured inside shadow memory. if place.ty(&self.locals).unwrap().kind().is_raw_ptr() { if let Rvalue::AddressOf(..) = rvalue { - self.push_target(SourceOp::Set { + self.push_target(MemoryInitOp::Set { operand: Operand::Copy(place.clone()), count: mk_const_operand(1, location.span()), value: true, @@ -211,7 +221,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { } StatementKind::Deinit(place) => { self.super_statement(stmt, location); - self.push_target(SourceOp::Set { + self.push_target(MemoryInitOp::Set { operand: Operand::Copy(place.clone()), count: mk_const_operand(1, location.span()), value: false, @@ -245,7 +255,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { Ok(instance) => instance, Err(reason) => { self.super_terminator(term, location); - self.push_target(SourceOp::Unsupported { + self.push_target(MemoryInitOp::Unsupported { reason, position: InsertPosition::Before, }); @@ -320,7 +330,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { args[0].ty(self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); - self.push_target(SourceOp::Set { + self.push_target(MemoryInitOp::Set { operand: args[0].clone(), count: mk_const_operand(1, location.span()), value: true, @@ -345,7 +355,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { args[0].ty(self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); - self.push_target(SourceOp::Set { + self.push_target(MemoryInitOp::Set { operand: args[0].clone(), count: mk_const_operand(1, location.span()), value: true, @@ -365,7 +375,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { args[0].ty(self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) )); - self.push_target(SourceOp::Get { + self.push_target(MemoryInitOp::Get { operand: args[0].clone(), count: mk_const_operand(1, location.span()), position: InsertPosition::Before, @@ -381,7 +391,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { args[0].ty(self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); - self.push_target(SourceOp::Get { + self.push_target(MemoryInitOp::Get { operand: args[0].clone(), count: mk_const_operand(1, location.span()), position: InsertPosition::Before, @@ -407,12 +417,12 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { args[1].ty(self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) )); - self.push_target(SourceOp::Get { + self.push_target(MemoryInitOp::Get { operand: args[0].clone(), count: args[2].clone(), position: InsertPosition::Before, }); - self.push_target(SourceOp::Get { + self.push_target(MemoryInitOp::Get { operand: args[1].clone(), count: args[2].clone(), position: InsertPosition::Before, @@ -432,12 +442,12 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { args[1].ty(self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); - self.push_target(SourceOp::Get { + self.push_target(MemoryInitOp::Get { operand: args[0].clone(), count: args[2].clone(), position: InsertPosition::Before, }); - self.push_target(SourceOp::Set { + self.push_target(MemoryInitOp::Set { operand: args[1].clone(), count: args[2].clone(), value: true, @@ -530,12 +540,12 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { args[1].ty(self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); - self.push_target(SourceOp::Get { + self.push_target(MemoryInitOp::Get { operand: args[0].clone(), count: mk_const_operand(1, location.span()), position: InsertPosition::Before, }); - self.push_target(SourceOp::Get { + self.push_target(MemoryInitOp::Get { operand: args[1].clone(), count: mk_const_operand(1, location.span()), position: InsertPosition::Before, @@ -551,7 +561,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { args[0].ty(self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) )); - self.push_target(SourceOp::Get { + self.push_target(MemoryInitOp::Get { operand: args[0].clone(), count: mk_const_operand(1, location.span()), position: InsertPosition::Before, @@ -579,12 +589,12 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { args[1].ty(self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); - self.push_target(SourceOp::Get { + self.push_target(MemoryInitOp::Get { operand: args[0].clone(), count: args[2].clone(), position: InsertPosition::Before, }); - self.push_target(SourceOp::Set { + self.push_target(MemoryInitOp::Set { operand: args[1].clone(), count: args[2].clone(), value: true, @@ -601,7 +611,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { args[0].ty(self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) )); - self.push_target(SourceOp::Get { + self.push_target(MemoryInitOp::Get { operand: args[0].clone(), count: mk_const_operand(1, location.span()), position: InsertPosition::Before, @@ -617,7 +627,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { args[0].ty(self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); - self.push_target(SourceOp::Set { + self.push_target(MemoryInitOp::Set { operand: args[0].clone(), count: mk_const_operand(1, location.span()), value: true, @@ -636,7 +646,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { args[0].ty(self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); - self.push_target(SourceOp::Set { + self.push_target(MemoryInitOp::Set { operand: args[0].clone(), count: args[2].clone(), value: true, @@ -644,7 +654,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { }) } intrinsic => { - self.push_target(SourceOp::Unsupported { + self.push_target(MemoryInitOp::Unsupported { reason: format!("Kani does not support reasoning about memory initialization of intrinsic `{intrinsic}`."), position: InsertPosition::Before }); @@ -660,7 +670,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { } "alloc::alloc::__rust_alloc_zeroed" => { /* Memory is initialized here, need to update shadow memory. */ - self.push_target(SourceOp::Set { + self.push_target(MemoryInitOp::Set { operand: Operand::Copy(destination.clone()), count: args[0].clone(), value: true, @@ -669,7 +679,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { } "alloc::alloc::__rust_dealloc" => { /* Memory is uninitialized here, need to update shadow memory. */ - self.push_target(SourceOp::Set { + self.push_target(MemoryInitOp::Set { operand: args[0].clone(), count: args[1].clone(), value: false, @@ -688,14 +698,14 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { let place_ty = place.ty(&self.locals).unwrap(); // When drop is codegen'ed, a reference is taken to the place which is later implicitly coerced to a pointer. // Hence, we need to bless this pointer as initialized. - self.push_target(SourceOp::SetRef { + self.push_target(MemoryInitOp::SetRef { operand: Operand::Copy(place.clone()), count: mk_const_operand(1, location.span()), value: true, position: InsertPosition::Before, }); if place_ty.kind().is_raw_ptr() { - self.push_target(SourceOp::Set { + self.push_target(MemoryInitOp::Set { operand: Operand::Copy(place.clone()), count: mk_const_operand(1, location.span()), value: false, @@ -723,7 +733,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { ProjectionElem::Deref => { let ptr_ty = intermediate_place.ty(self.locals).unwrap(); if ptr_ty.kind().is_raw_ptr() { - self.push_target(SourceOp::Get { + self.push_target(MemoryInitOp::Get { operand: Operand::Copy(intermediate_place.clone()), count: mk_const_operand(1, location.span()), position: InsertPosition::Before, @@ -734,7 +744,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { if target_ty.kind().is_union() && (!ptx.is_mutating() || place.projection.len() > idx + 1) { - self.push_target(SourceOp::Unsupported { + self.push_target(MemoryInitOp::Unsupported { reason: "Kani does not support reasoning about memory initialization of unions.".to_string(), position: InsertPosition::Before }); @@ -758,7 +768,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { if let ConstantKind::Allocated(allocation) = constant.const_.kind() { for (_, prov) in &allocation.provenance.ptrs { if let GlobalAlloc::Static(_) = GlobalAlloc::from(prov.0) { - self.push_target(SourceOp::Set { + self.push_target(MemoryInitOp::Set { operand: Operand::Constant(constant.clone()), count: mk_const_operand(1, location.span()), value: true, @@ -773,7 +783,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { fn visit_rvalue(&mut self, rvalue: &Rvalue, location: Location) { if let Rvalue::Cast(CastKind::PointerCoercion(PointerCoercion::Unsize), _, _) = rvalue { - self.push_target(SourceOp::Unsupported { + self.push_target(MemoryInitOp::Unsupported { reason: "Kani does not support reasoning about memory initialization of unsized pointers.".to_string(), position: InsertPosition::Before }); From 2fe3c7c299567224a0363e9fd53810173d18c737 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 27 Jun 2024 09:03:40 -0700 Subject: [PATCH 74/87] Remove unnecessary fields in `MemoryInitOp::Get` and `MemoryInitOp::Unsupported` --- .../kani_middle/transform/check_uninit/mod.rs | 4 +-- .../transform/check_uninit/uninit_visitor.rs | 30 ++++--------------- 2 files changed, 7 insertions(+), 27 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index 16893ef3224f..e208b9cca1d4 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -128,9 +128,9 @@ impl UninitPass { operation: MemoryInitOp, skip_first: &mut HashSet, ) { - if let MemoryInitOp::Unsupported { reason, position } = &operation { + if let MemoryInitOp::Unsupported { reason } = &operation { collect_skipped(&operation, body, skip_first); - self.unsupported_check(tcx, body, source, *position, reason); + self.unsupported_check(tcx, body, source, operation.position(), reason); return; }; diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index 1a3b21e5395d..f87c762fd60b 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -20,7 +20,7 @@ use strum_macros::AsRefStr; pub enum MemoryInitOp { /// Check memory initialization of data bytes in a memory region starting from the pointer /// `operand` and of length `count * sizeof(operand)` bytes. - Get { operand: Operand, count: Operand, position: InsertPosition }, + Get { operand: Operand, count: Operand }, /// Set memory initialization state of data bytes in a memory region starting from the pointer /// `operand` and of length `count * sizeof(operand)` bytes. Set { operand: Operand, count: Operand, value: bool, position: InsertPosition }, @@ -28,7 +28,7 @@ pub enum MemoryInitOp { /// `operand` and of length `count * sizeof(operand)` bytes. SetRef { operand: Operand, count: Operand, value: bool, position: InsertPosition }, /// Unsupported memory initialization operation. - Unsupported { reason: String, position: InsertPosition }, + Unsupported { reason: String }, } impl MemoryInitOp { @@ -73,10 +73,8 @@ impl MemoryInitOp { pub fn position(&self) -> InsertPosition { match self { - MemoryInitOp::Get { position, .. } - | MemoryInitOp::SetRef { position, .. } - | MemoryInitOp::Unsupported { position, .. } - | MemoryInitOp::Set { position, .. } => *position, + MemoryInitOp::Set { position, .. } | MemoryInitOp::SetRef { position, .. } => *position, + MemoryInitOp::Get { .. } | MemoryInitOp::Unsupported { .. } => InsertPosition::Before, } } } @@ -183,7 +181,6 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { self.push_target(MemoryInitOp::Get { operand: copy.src.clone(), count: copy.count.clone(), - position: InsertPosition::Before, }); // Destimation is a *mut T so it gets initialized. self.push_target(MemoryInitOp::Set { @@ -255,10 +252,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { Ok(instance) => instance, Err(reason) => { self.super_terminator(term, location); - self.push_target(MemoryInitOp::Unsupported { - reason, - position: InsertPosition::Before, - }); + self.push_target(MemoryInitOp::Unsupported { reason }); return; } }; @@ -378,7 +372,6 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { self.push_target(MemoryInitOp::Get { operand: args[0].clone(), count: mk_const_operand(1, location.span()), - position: InsertPosition::Before, }); } name if name.starts_with("atomic_cxchg") => { @@ -394,7 +387,6 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { self.push_target(MemoryInitOp::Get { operand: args[0].clone(), count: mk_const_operand(1, location.span()), - position: InsertPosition::Before, }); } "bitreverse" | "black_box" | "breakpoint" | "bswap" @@ -420,12 +412,10 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { self.push_target(MemoryInitOp::Get { operand: args[0].clone(), count: args[2].clone(), - position: InsertPosition::Before, }); self.push_target(MemoryInitOp::Get { operand: args[1].clone(), count: args[2].clone(), - position: InsertPosition::Before, }); } "copy" => { @@ -445,7 +435,6 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { self.push_target(MemoryInitOp::Get { operand: args[0].clone(), count: args[2].clone(), - position: InsertPosition::Before, }); self.push_target(MemoryInitOp::Set { operand: args[1].clone(), @@ -543,12 +532,10 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { self.push_target(MemoryInitOp::Get { operand: args[0].clone(), count: mk_const_operand(1, location.span()), - position: InsertPosition::Before, }); self.push_target(MemoryInitOp::Get { operand: args[1].clone(), count: mk_const_operand(1, location.span()), - position: InsertPosition::Before, }); } "unaligned_volatile_load" => { @@ -564,7 +551,6 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { self.push_target(MemoryInitOp::Get { operand: args[0].clone(), count: mk_const_operand(1, location.span()), - position: InsertPosition::Before, }); } "unchecked_add" | "unchecked_mul" | "unchecked_shl" @@ -592,7 +578,6 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { self.push_target(MemoryInitOp::Get { operand: args[0].clone(), count: args[2].clone(), - position: InsertPosition::Before, }); self.push_target(MemoryInitOp::Set { operand: args[1].clone(), @@ -614,7 +599,6 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { self.push_target(MemoryInitOp::Get { operand: args[0].clone(), count: mk_const_operand(1, location.span()), - position: InsertPosition::Before, }); } "volatile_store" => { @@ -656,7 +640,6 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { intrinsic => { self.push_target(MemoryInitOp::Unsupported { reason: format!("Kani does not support reasoning about memory initialization of intrinsic `{intrinsic}`."), - position: InsertPosition::Before }); } } @@ -736,7 +719,6 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { self.push_target(MemoryInitOp::Get { operand: Operand::Copy(intermediate_place.clone()), count: mk_const_operand(1, location.span()), - position: InsertPosition::Before, }); } } @@ -746,7 +728,6 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { { self.push_target(MemoryInitOp::Unsupported { reason: "Kani does not support reasoning about memory initialization of unions.".to_string(), - position: InsertPosition::Before }); } } @@ -785,7 +766,6 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { if let Rvalue::Cast(CastKind::PointerCoercion(PointerCoercion::Unsize), _, _) = rvalue { self.push_target(MemoryInitOp::Unsupported { reason: "Kani does not support reasoning about memory initialization of unsized pointers.".to_string(), - position: InsertPosition::Before }); }; self.super_rvalue(rvalue, location); From d924a9b8fe77bab6c0842030329437468cbbd44d Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 27 Jun 2024 09:23:54 -0700 Subject: [PATCH 75/87] Pre-filter intrinsics before instrumenting them --- .../transform/check_uninit/uninit_visitor.rs | 227 ++++++++++-------- 1 file changed, 133 insertions(+), 94 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index f87c762fd60b..c95026899b61 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -259,12 +259,9 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { match instance.kind { InstanceKind::Intrinsic => { match instance.intrinsic_name().unwrap().as_str() { - "add_with_overflow" - | "arith_offset" - | "assert_inhabited" - | "assert_mem_uninitialized_valid" - | "assert_zero_valid" - | "assume" => {} + intrinsic_name if can_skip_intrinsic(intrinsic_name) => { + /* Intrinsics that can be safely skipped */ + } "atomic_and_seqcst" | "atomic_and_acquire" | "atomic_and_acqrel" @@ -389,12 +386,6 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { count: mk_const_operand(1, location.span()), }); } - "bitreverse" | "black_box" | "breakpoint" | "bswap" - | "caller_location" => {} - "catch_unwind" => { - unimplemented!() - } - "ceilf32" | "ceilf64" => {} "compare_bytes" => { assert_eq!( args.len(), @@ -443,78 +434,6 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { position: InsertPosition::After, }); } - "copy_nonoverlapping" => unreachable!( - "Expected `core::intrinsics::unreachable` to be handled by `StatementKind::CopyNonOverlapping`" - ), - "copysignf32" - | "copysignf64" - | "cosf32" - | "cosf64" - | "ctlz" - | "ctlz_nonzero" - | "ctpop" - | "cttz" - | "cttz_nonzero" - | "discriminant_value" - | "exact_div" - | "exp2f32" - | "exp2f64" - | "expf32" - | "expf64" - | "fabsf32" - | "fabsf64" - | "fadd_fast" - | "fdiv_fast" - | "floorf32" - | "floorf64" - | "fmaf32" - | "fmaf64" - | "fmul_fast" - | "forget" - | "fsub_fast" - | "is_val_statically_known" - | "likely" - | "log10f32" - | "log10f64" - | "log2f32" - | "log2f64" - | "logf32" - | "logf64" - | "maxnumf32" - | "maxnumf64" - | "min_align_of" - | "min_align_of_val" - | "minnumf32" - | "minnumf64" - | "mul_with_overflow" - | "nearbyintf32" - | "nearbyintf64" - | "needs_drop" => {} - "offset" => unreachable!( - "Expected `core::intrinsics::unreachable` to be handled by `BinOp::OffSet`" - ), - "powf32" | "powf64" | "powif32" | "powif64" | "pref_align_of" => {} - "ptr_guaranteed_cmp" - | "ptr_offset_from" - | "ptr_offset_from_unsigned" => { - /* AFAICS from the documentation, none of those require the pointer arguments to be actually initialized. */ - } - "raw_eq" | "retag_box_to_raw" => { - unreachable!("This was removed in the latest Rust version.") - } - "rintf32" | "rintf64" | "rotate_left" | "rotate_right" - | "roundf32" | "roundf64" | "saturating_add" | "saturating_sub" - | "sinf32" | "sinf64" => {} - name if name.starts_with("simd") => { /* SIMD operations */ } - "size_of" => unreachable!(), - "size_of_val" => { - /* AFAICS from the documentation, this does not require the pointer argument to be initialized. */ - } - "sqrtf32" | "sqrtf64" | "sub_with_overflow" => {} - "transmute" | "transmute_copy" => { - unreachable!("Should've been lowered") - } - "truncf32" | "truncf64" | "type_id" | "type_name" => {} "typed_swap" => { assert_eq!( args.len(), @@ -553,14 +472,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { count: mk_const_operand(1, location.span()), }); } - "unchecked_add" | "unchecked_mul" | "unchecked_shl" - | "unchecked_shr" | "unchecked_sub" => { - unreachable!("Expected intrinsic to be lowered before codegen") - } - "unchecked_div" | "unchecked_rem" | "unlikely" => {} - "unreachable" => unreachable!( - "Expected `std::intrinsics::unreachable` to be handled by `TerminatorKind::Unreachable`" - ), + "volatile_copy_memory" | "volatile_copy_nonoverlapping_memory" => { assert_eq!( args.len(), @@ -618,8 +530,6 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { position: InsertPosition::After, }); } - "vtable_size" | "vtable_align" | "wrapping_add" - | "wrapping_mul" | "wrapping_sub" => {} "write_bytes" => { assert_eq!( args.len(), @@ -771,3 +681,132 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { self.super_rvalue(rvalue, location); } } + +/// Determines if the intrinsic has no memory initialization related function and hence can be +/// safely skipped. +fn can_skip_intrinsic(intrinsic_name: &str) -> bool { + match intrinsic_name { + "add_with_overflow" + | "arith_offset" + | "assert_inhabited" + | "assert_mem_uninitialized_valid" + | "assert_zero_valid" + | "assume" + | "bitreverse" + | "black_box" + | "breakpoint" + | "bswap" + | "caller_location" + | "ceilf32" + | "ceilf64" + | "copysignf32" + | "copysignf64" + | "cosf32" + | "cosf64" + | "ctlz" + | "ctlz_nonzero" + | "ctpop" + | "cttz" + | "cttz_nonzero" + | "discriminant_value" + | "exact_div" + | "exp2f32" + | "exp2f64" + | "expf32" + | "expf64" + | "fabsf32" + | "fabsf64" + | "fadd_fast" + | "fdiv_fast" + | "floorf32" + | "floorf64" + | "fmaf32" + | "fmaf64" + | "fmul_fast" + | "forget" + | "fsub_fast" + | "is_val_statically_known" + | "likely" + | "log10f32" + | "log10f64" + | "log2f32" + | "log2f64" + | "logf32" + | "logf64" + | "maxnumf32" + | "maxnumf64" + | "min_align_of" + | "min_align_of_val" + | "minnumf32" + | "minnumf64" + | "mul_with_overflow" + | "nearbyintf32" + | "nearbyintf64" + | "needs_drop" + | "powf32" + | "powf64" + | "powif32" + | "powif64" + | "pref_align_of" + | "raw_eq" + | "rintf32" + | "rintf64" + | "rotate_left" + | "rotate_right" + | "roundf32" + | "roundf64" + | "saturating_add" + | "saturating_sub" + | "sinf32" + | "sinf64" + | "sqrtf32" + | "sqrtf64" + | "sub_with_overflow" + | "truncf32" + | "truncf64" + | "type_id" + | "type_name" + | "unchecked_div" + | "unchecked_rem" + | "unlikely" + | "vtable_size" + | "vtable_align" + | "wrapping_add" + | "wrapping_mul" + | "wrapping_sub" => { + /* Intrinsics that do not interact with memory initialization. */ + true + } + "ptr_guaranteed_cmp" | "ptr_offset_from" | "ptr_offset_from_unsigned" | "size_of_val" => { + /* AFAICS from the documentation, none of those require the pointer arguments to be actually initialized. */ + true + } + name if name.starts_with("simd") => { + /* SIMD operations */ + true + } + "copy_nonoverlapping" => unreachable!( + "Expected `core::intrinsics::unreachable` to be handled by `StatementKind::CopyNonOverlapping`" + ), + "offset" => unreachable!( + "Expected `core::intrinsics::unreachable` to be handled by `BinOp::OffSet`" + ), + "unreachable" => unreachable!( + "Expected `std::intrinsics::unreachable` to be handled by `TerminatorKind::Unreachable`" + ), + "transmute" | "transmute_copy" | "unchecked_add" | "unchecked_mul" | "unchecked_shl" + | "size_of" | "unchecked_shr" | "unchecked_sub" => { + unreachable!("Expected intrinsic to be lowered before codegen") + } + "catch_unwind" => { + unimplemented!("") + } + "retag_box_to_raw" => { + unreachable!("This was removed in the latest Rust version.") + } + _ => { + /* Everything else */ + false + } + } +} From 0136480c2b96aff32cd9a44c8da9f24bb5e7d553 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 27 Jun 2024 09:28:07 -0700 Subject: [PATCH 76/87] Add comment about instrumentation pass order --- kani-compiler/src/kani_middle/transform/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kani-compiler/src/kani_middle/transform/mod.rs b/kani-compiler/src/kani_middle/transform/mod.rs index 6feb70c9eff3..56ab0be493c4 100644 --- a/kani-compiler/src/kani_middle/transform/mod.rs +++ b/kani-compiler/src/kani_middle/transform/mod.rs @@ -66,6 +66,11 @@ impl BodyTransformation { // This has to come after stubs since we want this to replace the stubbed body. transformer.add_pass(queries, AnyModifiesPass::new(tcx, &unit)); transformer.add_pass(queries, ValidValuePass { check_type: check_type.clone() }); + // Putting `UninitPass` after `ValidValuePass` makes sure that the code generated by + // `UninitPass` does not get unnecessarily instrumented by valid value checks. However, it + // would also make sense to check that the values are initialized before checking their + // validity. In the future, it would be nice to have a mechanism to skip automatically + // generated code for future instrumentation passes. transformer.add_pass( queries, UninitPass { check_type: check_type.clone(), mem_init_fn_cache: HashMap::new() }, From 9edcd6a28b11a93ea79a754cd6fbe4d91c0edec2 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 27 Jun 2024 11:52:50 -0700 Subject: [PATCH 77/87] Assert that enum tag has the offset of zero --- .../src/kani_middle/transform/check_uninit/ty_layout.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs index ab46e05e1370..b2d5765e0981 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs @@ -199,6 +199,8 @@ fn data_bytes_for_ty( unreachable!("Enum tag should not be a union.") } }; + // For enums, tag is the only field and should have offset of 0. + assert!(offsets.len() == 1 && offsets[0].bytes() == 0); let tag_data_bytes = vec![DataBytes { offset: current_offset, size: tag_size }]; From c75b616fcf574f742711fcf2bff108f85f8c6ee0 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 27 Jun 2024 12:13:25 -0700 Subject: [PATCH 78/87] Inline `scalar_ty_size` --- .../transform/check_uninit/ty_layout.rs | 67 ++++++++++--------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs index b2d5765e0981..09116230af80 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs @@ -3,7 +3,7 @@ // //! Utility functions that help calculate type layout. -use stable_mir::abi::{FieldsShape, LayoutShape, Scalar, TagEncoding, ValueAbi, VariantsShape}; +use stable_mir::abi::{FieldsShape, Scalar, TagEncoding, ValueAbi, VariantsShape}; use stable_mir::target::{MachineInfo, MachineSize}; use stable_mir::ty::{AdtKind, IndexedVal, RigidTy, Ty, TyKind, UintTy}; use stable_mir::CrateDef; @@ -102,33 +102,6 @@ impl PointeeInfo { } } -/// Get a size of an initialized scalar. -fn scalar_ty_size( - machine_info: &MachineInfo, - layout_shape: LayoutShape, - current_offset: usize, -) -> DataBytes { - match layout_shape.abi { - ValueAbi::Scalar(Scalar::Initialized { value, .. }) => { - DataBytes { offset: current_offset, size: value.size(machine_info) } - } - ValueAbi::ScalarPair( - Scalar::Initialized { value: value_first, .. }, - Scalar::Initialized { value: value_second, .. }, - ) => DataBytes { - offset: current_offset, - size: MachineSize::from_bits( - value_first.size(machine_info).bits() + value_second.size(machine_info).bits(), - ), - }, - ValueAbi::Scalar(_) - | ValueAbi::ScalarPair(_, _) - | ValueAbi::Uninhabited - | ValueAbi::Vector { .. } - | ValueAbi::Aggregate { .. } => unreachable!(), - } -} - /// Retrieve a set of data bytes with offsets for a type. fn data_bytes_for_ty( machine_info: &MachineInfo, @@ -138,7 +111,12 @@ fn data_bytes_for_ty( let layout = ty.layout().unwrap().shape(); match layout.fields { - FieldsShape::Primitive => Ok(vec![scalar_ty_size(machine_info, layout, current_offset)]), + FieldsShape::Primitive => Ok(vec![match layout.abi { + ValueAbi::Scalar(Scalar::Initialized { value, .. }) => { + DataBytes { offset: current_offset, size: value.size(machine_info) } + } + _ => unreachable!("FieldsShape::Primitive with a different ABI than ValueAbi::Scalar"), + }]), FieldsShape::Array { stride, count } if count > 0 => { let TyKind::RigidTy(RigidTy::Array(elem_ty, _)) = ty.kind() else { unreachable!() }; let elem_data_bytes = data_bytes_for_ty(machine_info, elem_ty, current_offset)?; @@ -313,9 +291,34 @@ fn data_bytes_for_ty( RigidTy::Str | RigidTy::Slice(_) | RigidTy::Array(_, _) => { unreachable!("Expected array layout for {ty:?}") } - RigidTy::RawPtr(_, _) | RigidTy::Ref(_, _, _) => { - Ok(vec![scalar_ty_size(machine_info, layout, current_offset)]) - } + RigidTy::RawPtr(_, _) | RigidTy::Ref(_, _, _) => Ok(match layout.abi { + ValueAbi::Scalar(Scalar::Initialized { value, .. }) => { + // Thin pointer, ABI is a single scalar. + vec![DataBytes { offset: current_offset, size: value.size(machine_info) }] + } + ValueAbi::ScalarPair( + Scalar::Initialized { value: value_first, .. }, + Scalar::Initialized { value: value_second, .. }, + ) => { + // Fat pointer, ABI is a scalar pair. + let FieldsShape::Arbitrary { offsets } = layout.fields else { + unreachable!() + }; + // Since this is a scalar pair, only 2 elements are in the offsets vec. + assert!(offsets.len() == 2); + vec![ + DataBytes { + offset: current_offset + offsets[0].bytes(), + size: value_first.size(machine_info), + }, + DataBytes { + offset: current_offset + offsets[1].bytes(), + size: value_second.size(machine_info), + }, + ] + } + _ => unreachable!("RigidTy::RawPtr | RigidTy::Ref with a non-scalar ABI."), + }), RigidTy::FnDef(_, _) | RigidTy::FnPtr(_) | RigidTy::Closure(_, _) From 683d3655cebc9c65738d136968d23141879af38d Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Fri, 28 Jun 2024 08:35:06 -0700 Subject: [PATCH 79/87] Apply changes from comments to `perf/uninit` --- tests/perf/uninit/src/lib.rs | 41 ++++++++++++------------------------ 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/tests/perf/uninit/src/lib.rs b/tests/perf/uninit/src/lib.rs index 468d418362ae..86b101c0e5d8 100644 --- a/tests/perf/uninit/src/lib.rs +++ b/tests/perf/uninit/src/lib.rs @@ -1,12 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -#![feature(core_intrinsics)] -#![feature(custom_mir)] -#![allow(dropping_copy_types)] - -use std::alloc::{alloc, alloc_zeroed, dealloc, Layout}; -use std::intrinsics::mir::*; +use std::alloc::{alloc, alloc_zeroed, Layout}; use std::ptr; use std::ptr::addr_of; use std::slice::from_raw_parts; @@ -18,21 +13,20 @@ struct S(u32, u8); fn access_padding_init() { let s = S(0, 0); let ptr: *const u8 = addr_of!(s) as *const u8; - let padding = unsafe { *(ptr.add(4)) }; + let data = unsafe { *(ptr.add(3)) }; // Accessing data bytes is valid. } #[kani::proof] fn alloc_to_slice() { let layout = Layout::from_size_align(32, 8).unwrap(); unsafe { - // This returns initialized memory. let ptr = alloc(layout); *ptr = 0x41; *ptr.add(1) = 0x42; *ptr.add(2) = 0x43; *ptr.add(3) = 0x44; *ptr.add(16) = 0x00; - let val = *(ptr.add(2)); + let val = *(ptr.add(2)); // Accessing previously initialized byte is valid. } } @@ -40,7 +34,7 @@ fn alloc_to_slice() { fn alloc_zeroed_to_slice() { let layout = Layout::from_size_align(32, 8).unwrap(); unsafe { - // This returns initialized memory. + // This returns initialized memory, so any further accesses are valid. let ptr = alloc_zeroed(layout); *ptr = 0x41; *ptr.add(1) = 0x42; @@ -49,28 +43,19 @@ fn alloc_zeroed_to_slice() { *ptr.add(16) = 0x00; let slice1 = from_raw_parts(ptr, 16); let slice2 = from_raw_parts(ptr.add(16), 16); - drop(slice1.cmp(slice2)); - dealloc(ptr, layout); } } #[kani::proof] -#[custom_mir(dialect = "runtime", phase = "optimized")] fn struct_padding_and_arr_init() { - mir! { - let s: S; - let sptr; - let sptr2; - let _val; - { - sptr = ptr::addr_of_mut!(s); - sptr2 = sptr as *mut [u8; 4]; - *sptr2 = [0; 4]; - *sptr = S(0, 0); - // Both S(u16, u16) and [u8; 4] have the same layout, so the memory is initialized. - _val = *sptr2; - Return() - } + unsafe { + let mut s = S(0, 0); + let sptr = ptr::addr_of_mut!(s); + let sptr2 = sptr as *mut [u8; 4]; + *sptr2 = [0; 4]; + *sptr = S(0, 0); + // Both S(u32, u8) and [u8; 4] have the same layout, so the memory is initialized. + let val = *sptr2; } } @@ -78,6 +63,6 @@ fn struct_padding_and_arr_init() { fn vec_read_init() { let mut v: Vec = Vec::with_capacity(10); unsafe { *v.as_mut_ptr().add(5) = 0x42 }; - let def = unsafe { *v.as_ptr().add(5) }; // Not UB since accessing initialized memory. + let def = unsafe { *v.as_ptr().add(5) }; // Accessing previously initialized byte is valid. let x = def + 1; } From 90af616735bf9fa3394f715de810c64c397cdcb1 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Fri, 28 Jun 2024 13:19:22 -0700 Subject: [PATCH 80/87] Add extra docs to `UninitVisitor` --- .../kani_middle/transform/check_uninit/uninit_visitor.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index c95026899b61..599b83953cc9 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -32,6 +32,9 @@ pub enum MemoryInitOp { } impl MemoryInitOp { + /// Produce an operand for the relevant memory initialization related operation. This is mostly + /// required so that the analysis can create a new local to take a reference in + /// `MemoryInitOp::SetRef`. pub fn mk_operand(&self, body: &mut MutableBody, source: &mut SourceInstruction) -> Operand { match self { MemoryInitOp::Get { operand, .. } | MemoryInitOp::Set { operand, .. } => { @@ -79,6 +82,9 @@ impl MemoryInitOp { } } +/// Represents an instruction in the source code together with all memory initialization checks/sets +/// that are connected to the memory used in this instruction and whether they should be inserted +/// before or after the instruction. #[derive(Clone, Debug)] pub struct InitRelevantInstruction { /// The instruction that affects the state of the memory. From 4b8dcd50eb05c6c2fa08bbf048fe7f5733c22ec0 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Fri, 28 Jun 2024 13:22:34 -0700 Subject: [PATCH 81/87] Move some functions in `uninit_visitor.rs` around --- .../transform/check_uninit/uninit_visitor.rs | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index 599b83953cc9..645e62848eb4 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -119,34 +119,6 @@ pub struct CheckUninitVisitor<'a> { bb: BasicBlockIdx, } -fn mk_const_operand(value: usize, span: Span) -> Operand { - Operand::Constant(ConstOperand { - span, - user_ty: None, - const_: MirConst::try_from_uint(value as u128, UintTy::Usize).unwrap(), - }) -} - -fn try_remove_topmost_deref(place: &Place) -> Option { - let mut new_place = place.clone(); - if let Some(ProjectionElem::Deref) = new_place.projection.pop() { - Some(new_place) - } else { - None - } -} - -/// Try retrieving instance for the given function operand. -fn try_resolve_instance(locals: &[LocalDecl], func: &Operand) -> Result { - let ty = func.ty(locals).unwrap(); - match ty.kind() { - TyKind::RigidTy(RigidTy::FnDef(def, args)) => Ok(Instance::resolve(def, &args).unwrap()), - _ => Err(format!( - "Kani does not support reasoning about memory initialization of arguments to `{ty:?}`." - )), - } -} - impl<'a> CheckUninitVisitor<'a> { pub fn find_next( body: &'a MutableBody, @@ -816,3 +788,33 @@ fn can_skip_intrinsic(intrinsic_name: &str) -> bool { } } } + +/// Create a constant operand with a given value and span. +fn mk_const_operand(value: usize, span: Span) -> Operand { + Operand::Constant(ConstOperand { + span, + user_ty: None, + const_: MirConst::try_from_uint(value as u128, UintTy::Usize).unwrap(), + }) +} + +/// Try removing a topmost deref projection from a place if it exists, returning a place without it. +fn try_remove_topmost_deref(place: &Place) -> Option { + let mut new_place = place.clone(); + if let Some(ProjectionElem::Deref) = new_place.projection.pop() { + Some(new_place) + } else { + None + } +} + +/// Try retrieving instance for the given function operand. +fn try_resolve_instance(locals: &[LocalDecl], func: &Operand) -> Result { + let ty = func.ty(locals).unwrap(); + match ty.kind() { + TyKind::RigidTy(RigidTy::FnDef(def, args)) => Ok(Instance::resolve(def, &args).unwrap()), + _ => Err(format!( + "Kani does not support reasoning about memory initialization of arguments to `{ty:?}`." + )), + } +} From 1ab140bfa15db5deff6f94d715a58805c08390b5 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Fri, 28 Jun 2024 13:45:33 -0700 Subject: [PATCH 82/87] Reorganize handling of intrinsics --- .../transform/check_uninit/uninit_visitor.rs | 149 ++---------------- 1 file changed, 9 insertions(+), 140 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index 645e62848eb4..b5438c9c51fe 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -240,124 +240,17 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { intrinsic_name if can_skip_intrinsic(intrinsic_name) => { /* Intrinsics that can be safely skipped */ } - "atomic_and_seqcst" - | "atomic_and_acquire" - | "atomic_and_acqrel" - | "atomic_and_release" - | "atomic_and_relaxed" - | "atomic_max_seqcst" - | "atomic_max_acquire" - | "atomic_max_acqrel" - | "atomic_max_release" - | "atomic_max_relaxed" - | "atomic_min_seqcst" - | "atomic_min_acquire" - | "atomic_min_acqrel" - | "atomic_min_release" - | "atomic_min_relaxed" - | "atomic_nand_seqcst" - | "atomic_nand_acquire" - | "atomic_nand_acqrel" - | "atomic_nand_release" - | "atomic_nand_relaxed" - | "atomic_or_seqcst" - | "atomic_or_acquire" - | "atomic_or_acqrel" - | "atomic_or_release" - | "atomic_or_relaxed" - | "atomic_umax_seqcst" - | "atomic_umax_acquire" - | "atomic_umax_acqrel" - | "atomic_umax_release" - | "atomic_umax_relaxed" - | "atomic_umin_seqcst" - | "atomic_umin_acquire" - | "atomic_umin_acqrel" - | "atomic_umin_release" - | "atomic_umin_relaxed" - | "atomic_xadd_seqcst" - | "atomic_xadd_acquire" - | "atomic_xadd_acqrel" - | "atomic_xadd_release" - | "atomic_xadd_relaxed" - | "atomic_xor_seqcst" - | "atomic_xor_acquire" - | "atomic_xor_acqrel" - | "atomic_xor_release" - | "atomic_xor_relaxed" - | "atomic_xsub_seqcst" - | "atomic_xsub_acquire" - | "atomic_xsub_acqrel" - | "atomic_xsub_release" - | "atomic_xsub_relaxed" => { + name if name.starts_with("atomic") => { + let num_args = + if name.starts_with("atomic_cxchg") { 3 } else { 2 }; assert_eq!( args.len(), - 2, - "Unexpected number of arguments for `atomic_binop`" - ); - assert!(matches!( - args[0].ty(self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) - )); - self.push_target(MemoryInitOp::Set { - operand: args[0].clone(), - count: mk_const_operand(1, location.span()), - value: true, - position: InsertPosition::After, - }); - } - "atomic_xchg_seqcst" - | "atomic_xchg_acquire" - | "atomic_xchg_acqrel" - | "atomic_xchg_release" - | "atomic_xchg_relaxed" - | "atomic_store_seqcst" - | "atomic_store_release" - | "atomic_store_relaxed" - | "atomic_store_unordered" => { - assert_eq!( - args.len(), - 2, - "Unexpected number of arguments for `atomic_store`" + num_args, + "Unexpected number of arguments for `{name}`" ); assert!(matches!( args[0].ty(self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) - )); - self.push_target(MemoryInitOp::Set { - operand: args[0].clone(), - count: mk_const_operand(1, location.span()), - value: true, - position: InsertPosition::After, - }); - } - "atomic_load_seqcst" - | "atomic_load_acquire" - | "atomic_load_relaxed" - | "atomic_load_unordered" => { - assert_eq!( - args.len(), - 2, - "Unexpected number of arguments for `atomic_load`" - ); - assert!(matches!( - args[0].ty(self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) - )); - self.push_target(MemoryInitOp::Get { - operand: args[0].clone(), - count: mk_const_operand(1, location.span()), - }); - } - name if name.starts_with("atomic_cxchg") => { - assert_eq!( - args.len(), - 3, - "Unexpected number of arguments for `atomic_cxchg" - ); - assert!(matches!( - args[0].ty(self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) + TyKind::RigidTy(RigidTy::RawPtr(..)) )); self.push_target(MemoryInitOp::Get { operand: args[0].clone(), @@ -387,7 +280,9 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { count: args[2].clone(), }); } - "copy" => { + "copy" + | "volatile_copy_memory" + | "volatile_copy_nonoverlapping_memory" => { assert_eq!( args.len(), 3, @@ -450,32 +345,6 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { count: mk_const_operand(1, location.span()), }); } - - "volatile_copy_memory" | "volatile_copy_nonoverlapping_memory" => { - assert_eq!( - args.len(), - 3, - "Unexpected number of arguments for `volatile_copy`" - ); - assert!(matches!( - args[0].ty(self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) - )); - assert!(matches!( - args[1].ty(self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) - )); - self.push_target(MemoryInitOp::Get { - operand: args[0].clone(), - count: args[2].clone(), - }); - self.push_target(MemoryInitOp::Set { - operand: args[1].clone(), - count: args[2].clone(), - value: true, - position: InsertPosition::After, - }); - } "volatile_load" => { assert_eq!( args.len(), From f266ea9bde48bf5891e8a4baf7c90e193c50fc20 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Fri, 28 Jun 2024 13:52:20 -0700 Subject: [PATCH 83/87] Rename `MemoryInitOp::Get` to `MemoryInitOp::Check` --- .../kani_middle/transform/check_uninit/mod.rs | 2 +- .../transform/check_uninit/uninit_visitor.rs | 30 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index e208b9cca1d4..6665ab697287 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -159,7 +159,7 @@ impl UninitPass { }; match operation { - MemoryInitOp::Get { .. } => { + MemoryInitOp::Check { .. } => { self.build_get_and_check(tcx, body, source, operation, pointee_ty_info, skip_first) } MemoryInitOp::Set { .. } | MemoryInitOp::SetRef { .. } => { diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index b5438c9c51fe..551bc660c78b 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -20,7 +20,7 @@ use strum_macros::AsRefStr; pub enum MemoryInitOp { /// Check memory initialization of data bytes in a memory region starting from the pointer /// `operand` and of length `count * sizeof(operand)` bytes. - Get { operand: Operand, count: Operand }, + Check { operand: Operand, count: Operand }, /// Set memory initialization state of data bytes in a memory region starting from the pointer /// `operand` and of length `count * sizeof(operand)` bytes. Set { operand: Operand, count: Operand, value: bool, position: InsertPosition }, @@ -37,7 +37,7 @@ impl MemoryInitOp { /// `MemoryInitOp::SetRef`. pub fn mk_operand(&self, body: &mut MutableBody, source: &mut SourceInstruction) -> Operand { match self { - MemoryInitOp::Get { operand, .. } | MemoryInitOp::Set { operand, .. } => { + MemoryInitOp::Check { operand, .. } | MemoryInitOp::Set { operand, .. } => { operand.clone() } MemoryInitOp::SetRef { operand, .. } => Operand::Copy(Place { @@ -60,7 +60,7 @@ impl MemoryInitOp { pub fn expect_count(&self) -> Operand { match self { - MemoryInitOp::Get { count, .. } + MemoryInitOp::Check { count, .. } | MemoryInitOp::Set { count, .. } | MemoryInitOp::SetRef { count, .. } => count.clone(), MemoryInitOp::Unsupported { .. } => unreachable!(), @@ -70,14 +70,14 @@ impl MemoryInitOp { pub fn expect_value(&self) -> bool { match self { MemoryInitOp::Set { value, .. } | MemoryInitOp::SetRef { value, .. } => *value, - MemoryInitOp::Get { .. } | MemoryInitOp::Unsupported { .. } => unreachable!(), + MemoryInitOp::Check { .. } | MemoryInitOp::Unsupported { .. } => unreachable!(), } } pub fn position(&self) -> InsertPosition { match self { MemoryInitOp::Set { position, .. } | MemoryInitOp::SetRef { position, .. } => *position, - MemoryInitOp::Get { .. } | MemoryInitOp::Unsupported { .. } => InsertPosition::Before, + MemoryInitOp::Check { .. } | MemoryInitOp::Unsupported { .. } => InsertPosition::Before, } } } @@ -156,7 +156,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { StatementKind::Intrinsic(NonDivergingIntrinsic::CopyNonOverlapping(copy)) => { self.super_statement(stmt, location); // Source is a *const T and it must be initialized. - self.push_target(MemoryInitOp::Get { + self.push_target(MemoryInitOp::Check { operand: copy.src.clone(), count: copy.count.clone(), }); @@ -252,7 +252,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { args[0].ty(self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(..)) )); - self.push_target(MemoryInitOp::Get { + self.push_target(MemoryInitOp::Check { operand: args[0].clone(), count: mk_const_operand(1, location.span()), }); @@ -271,11 +271,11 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { args[1].ty(self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) )); - self.push_target(MemoryInitOp::Get { + self.push_target(MemoryInitOp::Check { operand: args[0].clone(), count: args[2].clone(), }); - self.push_target(MemoryInitOp::Get { + self.push_target(MemoryInitOp::Check { operand: args[1].clone(), count: args[2].clone(), }); @@ -296,7 +296,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { args[1].ty(self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); - self.push_target(MemoryInitOp::Get { + self.push_target(MemoryInitOp::Check { operand: args[0].clone(), count: args[2].clone(), }); @@ -321,11 +321,11 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { args[1].ty(self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); - self.push_target(MemoryInitOp::Get { + self.push_target(MemoryInitOp::Check { operand: args[0].clone(), count: mk_const_operand(1, location.span()), }); - self.push_target(MemoryInitOp::Get { + self.push_target(MemoryInitOp::Check { operand: args[1].clone(), count: mk_const_operand(1, location.span()), }); @@ -340,7 +340,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { args[0].ty(self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) )); - self.push_target(MemoryInitOp::Get { + self.push_target(MemoryInitOp::Check { operand: args[0].clone(), count: mk_const_operand(1, location.span()), }); @@ -355,7 +355,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { args[0].ty(self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) )); - self.push_target(MemoryInitOp::Get { + self.push_target(MemoryInitOp::Check { operand: args[0].clone(), count: mk_const_operand(1, location.span()), }); @@ -473,7 +473,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { ProjectionElem::Deref => { let ptr_ty = intermediate_place.ty(self.locals).unwrap(); if ptr_ty.kind().is_raw_ptr() { - self.push_target(MemoryInitOp::Get { + self.push_target(MemoryInitOp::Check { operand: Operand::Copy(intermediate_place.clone()), count: mk_const_operand(1, location.span()), }); From a1dccb2c3454ed9857b6b1bc24eb10841e8b0722 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Fri, 28 Jun 2024 14:01:50 -0700 Subject: [PATCH 84/87] Throw an error only if the unsize cast happens to a pointer to trait object --- .../transform/check_uninit/uninit_visitor.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index 551bc660c78b..950cd66203c5 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -520,10 +520,14 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { } fn visit_rvalue(&mut self, rvalue: &Rvalue, location: Location) { - if let Rvalue::Cast(CastKind::PointerCoercion(PointerCoercion::Unsize), _, _) = rvalue { - self.push_target(MemoryInitOp::Unsupported { - reason: "Kani does not support reasoning about memory initialization of unsized pointers.".to_string(), - }); + if let Rvalue::Cast(CastKind::PointerCoercion(PointerCoercion::Unsize), _, ty) = rvalue { + if let TyKind::RigidTy(RigidTy::RawPtr(pointee_ty, _)) = ty.kind() { + if pointee_ty.kind().is_trait() { + self.push_target(MemoryInitOp::Unsupported { + reason: "Kani does not support reasoning about memory initialization of unsized pointers.".to_string(), + }); + } + } }; self.super_rvalue(rvalue, location); } From bbfd4438f028d42933aaee343e4fd91e8a3ca444 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Fri, 28 Jun 2024 14:21:36 -0700 Subject: [PATCH 85/87] Add logic to track additional pointer dereferences in `StatementKind::Assign` --- .../transform/check_uninit/uninit_visitor.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index 950cd66203c5..9df8aeb20774 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -173,6 +173,25 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { self.visit_rvalue(rvalue, location); // Check whether we are assigning into a dereference (*ptr = _). if let Some(place_without_deref) = try_remove_topmost_deref(place) { + // First, check that we are not dereferencing extra pointers along the way + // (e.g., **ptr = _). If yes, check whether these pointers are initialized. + let mut place_to_add_projections = + Place { local: place_without_deref.local, projection: vec![] }; + for projection_elem in place_without_deref.projection.iter() { + // If the projection is Deref and the current type is raw pointer, check + // if it points to initialized memory. + if *projection_elem == ProjectionElem::Deref { + if let TyKind::RigidTy(RigidTy::RawPtr(..)) = + place_to_add_projections.ty(&self.locals).unwrap().kind() + { + self.push_target(MemoryInitOp::Check { + operand: Operand::Copy(place_to_add_projections.clone()), + count: mk_const_operand(1, location.span()), + }); + }; + } + place_to_add_projections.projection.push(projection_elem.clone()); + } if place_without_deref.ty(&self.locals).unwrap().kind().is_raw_ptr() { self.push_target(MemoryInitOp::Set { operand: Operand::Copy(place_without_deref), From c91496ffbd44d7b4a8d3e26f8539368377034496 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Tue, 2 Jul 2024 11:32:18 -0700 Subject: [PATCH 86/87] Update kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs Co-authored-by: Celina G. Val --- .../src/kani_middle/transform/check_uninit/uninit_visitor.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index 9df8aeb20774..1f3b39c21c10 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -238,6 +238,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { let SourceInstruction::Statement { idx, bb } = self.current else { unreachable!() }; self.current = SourceInstruction::Statement { idx: idx + 1, bb }; } + fn visit_terminator(&mut self, term: &Terminator, location: Location) { if !(self.skip_next || self.target.is_some()) { self.current = SourceInstruction::Terminator { bb: self.bb }; From 839df712eeb0a6d2c7c4f92e0ba78615d48ca007 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Tue, 2 Jul 2024 11:35:11 -0700 Subject: [PATCH 87/87] Update incorrect comment in `uninit_visitor.rs` --- .../src/kani_middle/transform/check_uninit/uninit_visitor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs index 1f3b39c21c10..19b13c6ab8b6 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs @@ -24,7 +24,7 @@ pub enum MemoryInitOp { /// Set memory initialization state of data bytes in a memory region starting from the pointer /// `operand` and of length `count * sizeof(operand)` bytes. Set { operand: Operand, count: Operand, value: bool, position: InsertPosition }, - /// Check memory initialization of data bytes in a memory region starting from the reference to + /// Set memory initialization of data bytes in a memory region starting from the reference to /// `operand` and of length `count * sizeof(operand)` bytes. SetRef { operand: Operand, count: Operand, value: bool, position: InsertPosition }, /// Unsupported memory initialization operation.