From bec5fd1a2298f7f455c559ddfc938fd7a24e9b4d Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Fri, 9 Aug 2024 16:37:57 +0200 Subject: [PATCH 01/40] Update CBMC build instructions for Amazon Linux 2 (#3431) The default compiler on Amazon Linux 2 is GCC 7, which is not sufficiently recent for building CBMC v6+. Install and use GCC 10, adjust warnings to cope with the flex version provided by Amazon Linux 2, and handle the non-default std::filesystem support. Furthermore, apply a workaround until https://github.com/diffblue/cbmc/issues/8357 has been fixed on the CBMC side. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- scripts/setup/al2/install_cbmc.sh | 79 +++++++++++++++++++++++++++++-- scripts/setup/al2/install_deps.sh | 1 + 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/scripts/setup/al2/install_cbmc.sh b/scripts/setup/al2/install_cbmc.sh index 34abcc52db93..de4b5215e083 100755 --- a/scripts/setup/al2/install_cbmc.sh +++ b/scripts/setup/al2/install_cbmc.sh @@ -21,10 +21,83 @@ git clone \ pushd "${WORK_DIR}" -mkdir build -git submodule update --init +# apply workaround for https://github.com/diffblue/cbmc/issues/8357 until it is +# properly fixed in CBMC +cat > varargs.patch << "EOF" +--- a/src/ansi-c/library/stdio.c ++++ b/src/ansi-c/library/stdio.c +@@ -1135,7 +1135,7 @@ int vfscanf(FILE *restrict stream, const char *restrict format, va_list arg) -cmake3 -S . -Bbuild -DWITH_JBMC=OFF -Dsat_impl="minisat2;cadical" + (void)*format; + while((__CPROVER_size_t)__CPROVER_POINTER_OFFSET(*(void **)&arg) < +- __CPROVER_OBJECT_SIZE(arg)) ++ __CPROVER_OBJECT_SIZE(*(void **)&arg)) + { + void *a = va_arg(arg, void *); + __CPROVER_havoc_object(a); +@@ -1233,7 +1233,7 @@ int __stdio_common_vfscanf( + + (void)*format; + while((__CPROVER_size_t)__CPROVER_POINTER_OFFSET(*(void **)&args) < +- __CPROVER_OBJECT_SIZE(args)) ++ __CPROVER_OBJECT_SIZE(*(void **)&args)) + { + void *a = va_arg(args, void *); + __CPROVER_havoc_object(a); +@@ -1312,7 +1312,7 @@ __CPROVER_HIDE:; + (void)*s; + (void)*format; + while((__CPROVER_size_t)__CPROVER_POINTER_OFFSET(*(void **)&arg) < +- __CPROVER_OBJECT_SIZE(arg)) ++ __CPROVER_OBJECT_SIZE(*(void **)&arg)) + { + void *a = va_arg(arg, void *); + __CPROVER_havoc_object(a); +@@ -1388,7 +1388,7 @@ int __stdio_common_vsscanf( + (void)*s; + (void)*format; + while((__CPROVER_size_t)__CPROVER_POINTER_OFFSET(*(void **)&args) < +- __CPROVER_OBJECT_SIZE(args)) ++ __CPROVER_OBJECT_SIZE(*(void **)&args)) + { + void *a = va_arg(args, void *); + __CPROVER_havoc_object(a); +@@ -1774,12 +1774,12 @@ int vsnprintf(char *str, size_t size, const char *fmt, va_list ap) + (void)*fmt; + + while((__CPROVER_size_t)__CPROVER_POINTER_OFFSET(*(void **)&ap) < +- __CPROVER_OBJECT_SIZE(ap)) ++ __CPROVER_OBJECT_SIZE(*(void **)&ap)) + + { + (void)va_arg(ap, int); + __CPROVER_precondition( +- __CPROVER_POINTER_OBJECT(str) != __CPROVER_POINTER_OBJECT(ap), ++ __CPROVER_POINTER_OBJECT(str) != __CPROVER_POINTER_OBJECT(*(void **)&ap), + "vsnprintf object overlap"); + } + +@@ -1822,12 +1822,12 @@ int __builtin___vsnprintf_chk( + (void)*fmt; + + while((__CPROVER_size_t)__CPROVER_POINTER_OFFSET(*(void **)&ap) < +- __CPROVER_OBJECT_SIZE(ap)) ++ __CPROVER_OBJECT_SIZE(*(void **)&ap)) + + { + (void)va_arg(ap, int); + __CPROVER_precondition( +- __CPROVER_POINTER_OBJECT(str) != __CPROVER_POINTER_OBJECT(ap), ++ __CPROVER_POINTER_OBJECT(str) != __CPROVER_POINTER_OBJECT(*(void **)&ap), + "vsnprintf object overlap"); + } + +EOF + +cmake3 -S . -Bbuild -DWITH_JBMC=OFF -Dsat_impl="minisat2;cadical" \ + -DCMAKE_C_COMPILER=gcc10-cc -DCMAKE_CXX_COMPILER=gcc10-c++ \ + -DCMAKE_CXX_STANDARD_LIBRARIES=-lstdc++fs \ + -DCMAKE_CXX_FLAGS=-Wno-error=register cmake3 --build build -- -j$(nproc) sudo make -C build install diff --git a/scripts/setup/al2/install_deps.sh b/scripts/setup/al2/install_deps.sh index 0010aa58bab4..ce901ab8afa5 100755 --- a/scripts/setup/al2/install_deps.sh +++ b/scripts/setup/al2/install_deps.sh @@ -11,6 +11,7 @@ set -eu DEPS=( cmake cmake3 + gcc10-c++ git openssl-devel python3-pip From 6553afa4dc854478da1570517f522776ae0601e2 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Fri, 9 Aug 2024 11:47:40 -0700 Subject: [PATCH 02/40] Handle intrinsics systematically (#3422) Since compiler intrinsics are handled both in codegen and instrumentation, it would make sense to have a single source of truth with regard to which intrinsics we support and update it systematically. This PR introduces an `Intrinsic` enum, which allows to parse intrinsic name into one of the predefined variants we support or a catch-all `Unsupported` variant. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../codegen/intrinsic.rs | 414 ++++----- kani-compiler/src/intrinsics.rs | 798 ++++++++++++++++++ .../points_to/points_to_analysis.rs | 411 +++++---- .../delayed_ub/initial_target_visitor.rs | 37 +- .../check_uninit/ptr_uninit/uninit_visitor.rs | 378 ++++----- kani-compiler/src/main.rs | 1 + 6 files changed, 1393 insertions(+), 646 deletions(-) create mode 100644 kani-compiler/src/intrinsics.rs diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs index c4d5396f1fe8..421d79e943c4 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs @@ -5,6 +5,7 @@ use super::typ; use super::{bb_label, PropertyClass}; use crate::codegen_cprover_gotoc::codegen::ty_stable::pointee_type_stable; use crate::codegen_cprover_gotoc::{utils, GotocCtx}; +use crate::intrinsics::Intrinsic; use crate::unwrap_or_return_codegen_unimplemented_stmt; use cbmc::goto_program::{ ArithmeticOverflowResult, BinaryOperator, BuiltinFn, Expr, Location, Stmt, Type, @@ -114,7 +115,7 @@ impl<'tcx> GotocCtx<'tcx> { span: Span, ) -> Stmt { let intrinsic_name = instance.intrinsic_name().unwrap(); - let intrinsic = intrinsic_name.as_str(); + let intrinsic_str = intrinsic_name.as_str(); let loc = self.codegen_span_stable(span); debug!(?instance, "codegen_intrinsic"); debug!(?fargs, "codegen_intrinsic"); @@ -163,7 +164,7 @@ impl<'tcx> GotocCtx<'tcx> { let div_overflow_check = self.codegen_assert_assume( div_does_not_overflow, PropertyClass::ArithmeticOverflow, - format!("attempt to compute {} which would overflow", intrinsic).as_str(), + format!("attempt to compute {} which would overflow", intrinsic_str).as_str(), loc, ); let res = a.$f(b); @@ -257,7 +258,7 @@ impl<'tcx> GotocCtx<'tcx> { macro_rules! codegen_atomic_binop { ($op: ident) => {{ let loc = self.codegen_span_stable(span); - self.store_concurrent_construct(intrinsic, loc); + self.store_concurrent_construct(intrinsic_str, loc); let var1_ref = fargs.remove(0); let var1 = var1_ref.dereference(); let (tmp, decl_stmt) = @@ -280,7 +281,7 @@ impl<'tcx> GotocCtx<'tcx> { macro_rules! unstable_codegen { ($($tt:tt)*) => {{ let expr = self.codegen_unimplemented_expr( - &format!("'{}' intrinsic", intrinsic), + &format!("'{}' intrinsic", intrinsic_str), cbmc_ret_ty, loc, "https://github.com/model-checking/kani/issues/new/choose", @@ -289,342 +290,273 @@ impl<'tcx> GotocCtx<'tcx> { }}; } - if let Some(stripped) = intrinsic.strip_prefix("simd_shuffle") { - assert!(fargs.len() == 3, "`simd_shuffle` had unexpected arguments {fargs:?}"); - let n: u64 = self.simd_shuffle_length(stripped, farg_types, span); - return self.codegen_intrinsic_simd_shuffle(fargs, place, farg_types, ret_ty, n, span); - } + let intrinsic = Intrinsic::from_instance(&instance); match intrinsic { - "add_with_overflow" => { + Intrinsic::AddWithOverflow => { self.codegen_op_with_overflow(BinaryOperator::OverflowResultPlus, fargs, place, loc) } - "arith_offset" => self.codegen_offset(intrinsic, instance, fargs, place, loc), - "assert_inhabited" => self.codegen_assert_intrinsic(instance, intrinsic, span), - "assert_mem_uninitialized_valid" => { - self.codegen_assert_intrinsic(instance, intrinsic, span) + Intrinsic::ArithOffset => { + self.codegen_offset(intrinsic_str, instance, fargs, place, loc) + } + Intrinsic::AssertInhabited => { + self.codegen_assert_intrinsic(instance, intrinsic_str, span) + } + Intrinsic::AssertMemUninitializedValid => { + self.codegen_assert_intrinsic(instance, intrinsic_str, span) + } + Intrinsic::AssertZeroValid => { + self.codegen_assert_intrinsic(instance, intrinsic_str, span) } - "assert_zero_valid" => self.codegen_assert_intrinsic(instance, intrinsic, span), // https://doc.rust-lang.org/core/intrinsics/fn.assume.html // Informs the optimizer that a condition is always true. // If the condition is false, the behavior is undefined. - "assume" => self.codegen_assert_assume( + Intrinsic::Assume => self.codegen_assert_assume( fargs.remove(0).cast_to(Type::bool()), PropertyClass::Assume, "assumption failed", loc, ), - "atomic_and_seqcst" => codegen_atomic_binop!(bitand), - "atomic_and_acquire" => codegen_atomic_binop!(bitand), - "atomic_and_acqrel" => codegen_atomic_binop!(bitand), - "atomic_and_release" => codegen_atomic_binop!(bitand), - "atomic_and_relaxed" => codegen_atomic_binop!(bitand), - name if name.starts_with("atomic_cxchg") => { - self.codegen_atomic_cxchg(intrinsic, fargs, place, loc) + Intrinsic::AtomicAnd(_) => codegen_atomic_binop!(bitand), + Intrinsic::AtomicCxchg(_) | Intrinsic::AtomicCxchgWeak(_) => { + self.codegen_atomic_cxchg(intrinsic_str, fargs, place, loc) } - "atomic_fence_seqcst" => self.codegen_atomic_noop(intrinsic, loc), - "atomic_fence_acquire" => self.codegen_atomic_noop(intrinsic, loc), - "atomic_fence_acqrel" => self.codegen_atomic_noop(intrinsic, loc), - "atomic_fence_release" => self.codegen_atomic_noop(intrinsic, loc), - "atomic_load_seqcst" => self.codegen_atomic_load(intrinsic, fargs, place, loc), - "atomic_load_acquire" => self.codegen_atomic_load(intrinsic, fargs, place, loc), - "atomic_load_relaxed" => self.codegen_atomic_load(intrinsic, fargs, place, loc), - "atomic_load_unordered" => self.codegen_atomic_load(intrinsic, fargs, place, loc), - "atomic_max_seqcst" => codegen_atomic_binop!(max), - "atomic_max_acquire" => codegen_atomic_binop!(max), - "atomic_max_acqrel" => codegen_atomic_binop!(max), - "atomic_max_release" => codegen_atomic_binop!(max), - "atomic_max_relaxed" => codegen_atomic_binop!(max), - "atomic_min_seqcst" => codegen_atomic_binop!(min), - "atomic_min_acquire" => codegen_atomic_binop!(min), - "atomic_min_acqrel" => codegen_atomic_binop!(min), - "atomic_min_release" => codegen_atomic_binop!(min), - "atomic_min_relaxed" => codegen_atomic_binop!(min), - "atomic_nand_seqcst" => codegen_atomic_binop!(bitnand), - "atomic_nand_acquire" => codegen_atomic_binop!(bitnand), - "atomic_nand_acqrel" => codegen_atomic_binop!(bitnand), - "atomic_nand_release" => codegen_atomic_binop!(bitnand), - "atomic_nand_relaxed" => codegen_atomic_binop!(bitnand), - "atomic_or_seqcst" => codegen_atomic_binop!(bitor), - "atomic_or_acquire" => codegen_atomic_binop!(bitor), - "atomic_or_acqrel" => codegen_atomic_binop!(bitor), - "atomic_or_release" => codegen_atomic_binop!(bitor), - "atomic_or_relaxed" => codegen_atomic_binop!(bitor), - "atomic_singlethreadfence_seqcst" => self.codegen_atomic_noop(intrinsic, loc), - "atomic_singlethreadfence_acquire" => self.codegen_atomic_noop(intrinsic, loc), - "atomic_singlethreadfence_acqrel" => self.codegen_atomic_noop(intrinsic, loc), - "atomic_singlethreadfence_release" => self.codegen_atomic_noop(intrinsic, loc), - "atomic_store_seqcst" => self.codegen_atomic_store(intrinsic, fargs, place, loc), - "atomic_store_release" => self.codegen_atomic_store(intrinsic, fargs, place, loc), - "atomic_store_relaxed" => self.codegen_atomic_store(intrinsic, fargs, place, loc), - "atomic_store_unordered" => self.codegen_atomic_store(intrinsic, fargs, place, loc), - "atomic_umax_seqcst" => codegen_atomic_binop!(max), - "atomic_umax_acquire" => codegen_atomic_binop!(max), - "atomic_umax_acqrel" => codegen_atomic_binop!(max), - "atomic_umax_release" => codegen_atomic_binop!(max), - "atomic_umax_relaxed" => codegen_atomic_binop!(max), - "atomic_umin_seqcst" => codegen_atomic_binop!(min), - "atomic_umin_acquire" => codegen_atomic_binop!(min), - "atomic_umin_acqrel" => codegen_atomic_binop!(min), - "atomic_umin_release" => codegen_atomic_binop!(min), - "atomic_umin_relaxed" => codegen_atomic_binop!(min), - "atomic_xadd_seqcst" => codegen_atomic_binop!(plus), - "atomic_xadd_acquire" => codegen_atomic_binop!(plus), - "atomic_xadd_acqrel" => codegen_atomic_binop!(plus), - "atomic_xadd_release" => codegen_atomic_binop!(plus), - "atomic_xadd_relaxed" => codegen_atomic_binop!(plus), - "atomic_xchg_seqcst" => self.codegen_atomic_store(intrinsic, fargs, place, loc), - "atomic_xchg_acquire" => self.codegen_atomic_store(intrinsic, fargs, place, loc), - "atomic_xchg_acqrel" => self.codegen_atomic_store(intrinsic, fargs, place, loc), - "atomic_xchg_release" => self.codegen_atomic_store(intrinsic, fargs, place, loc), - "atomic_xchg_relaxed" => self.codegen_atomic_store(intrinsic, fargs, place, loc), - "atomic_xor_seqcst" => codegen_atomic_binop!(bitxor), - "atomic_xor_acquire" => codegen_atomic_binop!(bitxor), - "atomic_xor_acqrel" => codegen_atomic_binop!(bitxor), - "atomic_xor_release" => codegen_atomic_binop!(bitxor), - "atomic_xor_relaxed" => codegen_atomic_binop!(bitxor), - "atomic_xsub_seqcst" => codegen_atomic_binop!(sub), - "atomic_xsub_acquire" => codegen_atomic_binop!(sub), - "atomic_xsub_acqrel" => codegen_atomic_binop!(sub), - "atomic_xsub_release" => codegen_atomic_binop!(sub), - "atomic_xsub_relaxed" => codegen_atomic_binop!(sub), - "bitreverse" => { + + Intrinsic::AtomicFence(_) => self.codegen_atomic_noop(intrinsic_str, loc), + Intrinsic::AtomicLoad(_) => self.codegen_atomic_load(intrinsic_str, fargs, place, loc), + Intrinsic::AtomicMax(_) => codegen_atomic_binop!(max), + Intrinsic::AtomicMin(_) => codegen_atomic_binop!(min), + Intrinsic::AtomicNand(_) => codegen_atomic_binop!(bitnand), + Intrinsic::AtomicOr(_) => codegen_atomic_binop!(bitor), + Intrinsic::AtomicSingleThreadFence(_) => self.codegen_atomic_noop(intrinsic_str, loc), + Intrinsic::AtomicStore(_) => { + self.codegen_atomic_store(intrinsic_str, fargs, place, loc) + } + Intrinsic::AtomicUmax(_) => codegen_atomic_binop!(max), + Intrinsic::AtomicUmin(_) => codegen_atomic_binop!(min), + Intrinsic::AtomicXadd(_) => codegen_atomic_binop!(plus), + Intrinsic::AtomicXchg(_) => self.codegen_atomic_store(intrinsic_str, fargs, place, loc), + Intrinsic::AtomicXor(_) => codegen_atomic_binop!(bitxor), + Intrinsic::AtomicXsub(_) => codegen_atomic_binop!(sub), + Intrinsic::Bitreverse => { self.codegen_expr_to_place_stable(place, fargs.remove(0).bitreverse(), loc) } // black_box is an identity function that hints to the compiler // to be maximally pessimistic to limit optimizations - "black_box" => self.codegen_expr_to_place_stable(place, fargs.remove(0), loc), - "breakpoint" => Stmt::skip(loc), - "bswap" => self.codegen_expr_to_place_stable(place, fargs.remove(0).bswap(), loc), - "caller_location" => self.codegen_unimplemented_stmt( - intrinsic, - loc, - "https://github.com/model-checking/kani/issues/374", - ), - "catch_unwind" => self.codegen_unimplemented_stmt( - intrinsic, - loc, - "https://github.com/model-checking/kani/issues/267", - ), - "ceilf32" => codegen_simple_intrinsic!(Ceilf), - "ceilf64" => codegen_simple_intrinsic!(Ceil), - "compare_bytes" => self.codegen_compare_bytes(fargs, place, loc), - "copy" => self.codegen_copy(intrinsic, false, fargs, farg_types, Some(place), loc), - "copy_nonoverlapping" => unreachable!( - "Expected `core::intrinsics::unreachable` to be handled by `StatementKind::CopyNonOverlapping`" - ), - "copysignf32" => codegen_simple_intrinsic!(Copysignf), - "copysignf64" => codegen_simple_intrinsic!(Copysign), - "cosf32" => codegen_simple_intrinsic!(Cosf), - "cosf64" => codegen_simple_intrinsic!(Cos), - "ctlz" => codegen_count_intrinsic!(ctlz, true), - "ctlz_nonzero" => codegen_count_intrinsic!(ctlz, false), - "ctpop" => self.codegen_ctpop(place, span, fargs.remove(0), farg_types[0]), - "cttz" => codegen_count_intrinsic!(cttz, true), - "cttz_nonzero" => codegen_count_intrinsic!(cttz, false), - "discriminant_value" => { + Intrinsic::BlackBox => self.codegen_expr_to_place_stable(place, fargs.remove(0), loc), + Intrinsic::Breakpoint => Stmt::skip(loc), + Intrinsic::Bswap => { + self.codegen_expr_to_place_stable(place, fargs.remove(0).bswap(), loc) + } + Intrinsic::CeilF32 => codegen_simple_intrinsic!(Ceilf), + Intrinsic::CeilF64 => codegen_simple_intrinsic!(Ceil), + Intrinsic::CompareBytes => self.codegen_compare_bytes(fargs, place, loc), + Intrinsic::Copy => { + self.codegen_copy(intrinsic_str, false, fargs, farg_types, Some(place), loc) + } + Intrinsic::CopySignF32 => codegen_simple_intrinsic!(Copysignf), + Intrinsic::CopySignF64 => codegen_simple_intrinsic!(Copysign), + Intrinsic::CosF32 => codegen_simple_intrinsic!(Cosf), + Intrinsic::CosF64 => codegen_simple_intrinsic!(Cos), + Intrinsic::Ctlz => codegen_count_intrinsic!(ctlz, true), + Intrinsic::CtlzNonZero => codegen_count_intrinsic!(ctlz, false), + Intrinsic::Ctpop => self.codegen_ctpop(place, span, fargs.remove(0), farg_types[0]), + Intrinsic::Cttz => codegen_count_intrinsic!(cttz, true), + Intrinsic::CttzNonZero => codegen_count_intrinsic!(cttz, false), + Intrinsic::DiscriminantValue => { let sig = instance.ty().kind().fn_sig().unwrap().skip_binder(); let ty = pointee_type_stable(sig.inputs()[0]).unwrap(); let e = self.codegen_get_discriminant(fargs.remove(0).dereference(), ty, ret_ty); self.codegen_expr_to_place_stable(place, e, loc) } - "exact_div" => self.codegen_exact_div(fargs, place, loc), - "exp2f32" => codegen_simple_intrinsic!(Exp2f), - "exp2f64" => codegen_simple_intrinsic!(Exp2), - "expf32" => codegen_simple_intrinsic!(Expf), - "expf64" => codegen_simple_intrinsic!(Exp), - "fabsf32" => codegen_simple_intrinsic!(Fabsf), - "fabsf64" => codegen_simple_intrinsic!(Fabs), - "fadd_fast" => { + Intrinsic::ExactDiv => self.codegen_exact_div(fargs, place, loc), + Intrinsic::Exp2F32 => codegen_simple_intrinsic!(Exp2f), + Intrinsic::Exp2F64 => codegen_simple_intrinsic!(Exp2), + Intrinsic::ExpF32 => codegen_simple_intrinsic!(Expf), + Intrinsic::ExpF64 => codegen_simple_intrinsic!(Exp), + Intrinsic::FabsF32 => codegen_simple_intrinsic!(Fabsf), + Intrinsic::FabsF64 => codegen_simple_intrinsic!(Fabs), + Intrinsic::FaddFast => { let fargs_clone = fargs.clone(); let binop_stmt = codegen_intrinsic_binop!(plus); - self.add_finite_args_checks(intrinsic, fargs_clone, binop_stmt, span) + self.add_finite_args_checks(intrinsic_str, fargs_clone, binop_stmt, span) } - "fdiv_fast" => { + Intrinsic::FdivFast => { let fargs_clone = fargs.clone(); let binop_stmt = codegen_intrinsic_binop!(div); - self.add_finite_args_checks(intrinsic, fargs_clone, binop_stmt, span) + self.add_finite_args_checks(intrinsic_str, fargs_clone, binop_stmt, span) } - "floorf32" => codegen_simple_intrinsic!(Floorf), - "floorf64" => codegen_simple_intrinsic!(Floor), - "fmaf32" => codegen_simple_intrinsic!(Fmaf), - "fmaf64" => codegen_simple_intrinsic!(Fma), - "fmul_fast" => { + Intrinsic::FloorF32 => codegen_simple_intrinsic!(Floorf), + Intrinsic::FloorF64 => codegen_simple_intrinsic!(Floor), + Intrinsic::FmafF32 => codegen_simple_intrinsic!(Fmaf), + Intrinsic::FmafF64 => codegen_simple_intrinsic!(Fma), + Intrinsic::FmulFast => { let fargs_clone = fargs.clone(); let binop_stmt = codegen_intrinsic_binop!(mul); - self.add_finite_args_checks(intrinsic, fargs_clone, binop_stmt, span) + self.add_finite_args_checks(intrinsic_str, fargs_clone, binop_stmt, span) } - "forget" => Stmt::skip(loc), - "fsub_fast" => { + Intrinsic::Forget => Stmt::skip(loc), + Intrinsic::FsubFast => { let fargs_clone = fargs.clone(); let binop_stmt = codegen_intrinsic_binop!(sub); - self.add_finite_args_checks(intrinsic, fargs_clone, binop_stmt, span) + self.add_finite_args_checks(intrinsic_str, fargs_clone, binop_stmt, span) } - "is_val_statically_known" => { + Intrinsic::IsValStaticallyKnown => { // Returning false is sound according do this intrinsic's documentation: // https://doc.rust-lang.org/nightly/std/intrinsics/fn.is_val_statically_known.html self.codegen_expr_to_place_stable(place, Expr::c_false(), loc) } - "likely" => self.codegen_expr_to_place_stable(place, fargs.remove(0), loc), - "log10f32" => codegen_simple_intrinsic!(Log10f), - "log10f64" => codegen_simple_intrinsic!(Log10), - "log2f32" => codegen_simple_intrinsic!(Log2f), - "log2f64" => codegen_simple_intrinsic!(Log2), - "logf32" => codegen_simple_intrinsic!(Logf), - "logf64" => codegen_simple_intrinsic!(Log), - "maxnumf32" => codegen_simple_intrinsic!(Fmaxf), - "maxnumf64" => codegen_simple_intrinsic!(Fmax), - "min_align_of" => codegen_intrinsic_const!(), - "min_align_of_val" => codegen_size_align!(align), - "minnumf32" => codegen_simple_intrinsic!(Fminf), - "minnumf64" => codegen_simple_intrinsic!(Fmin), - "mul_with_overflow" => { + Intrinsic::Likely => self.codegen_expr_to_place_stable(place, fargs.remove(0), loc), + Intrinsic::Log10F32 => codegen_simple_intrinsic!(Log10f), + Intrinsic::Log10F64 => codegen_simple_intrinsic!(Log10), + Intrinsic::Log2F32 => codegen_simple_intrinsic!(Log2f), + Intrinsic::Log2F64 => codegen_simple_intrinsic!(Log2), + Intrinsic::LogF32 => codegen_simple_intrinsic!(Logf), + Intrinsic::LogF64 => codegen_simple_intrinsic!(Log), + Intrinsic::MaxNumF32 => codegen_simple_intrinsic!(Fmaxf), + Intrinsic::MaxNumF64 => codegen_simple_intrinsic!(Fmax), + Intrinsic::MinAlignOf => codegen_intrinsic_const!(), + Intrinsic::MinAlignOfVal => codegen_size_align!(align), + Intrinsic::MinNumF32 => codegen_simple_intrinsic!(Fminf), + Intrinsic::MinNumF64 => codegen_simple_intrinsic!(Fmin), + Intrinsic::MulWithOverflow => { self.codegen_op_with_overflow(BinaryOperator::OverflowResultMult, fargs, place, loc) } - "nearbyintf32" => codegen_simple_intrinsic!(Nearbyintf), - "nearbyintf64" => codegen_simple_intrinsic!(Nearbyint), - "needs_drop" => codegen_intrinsic_const!(), - // As of https://github.com/rust-lang/rust/pull/110822 the `offset` intrinsic is lowered to `mir::BinOp::Offset` - "offset" => unreachable!( - "Expected `core::intrinsics::unreachable` to be handled by `BinOp::OffSet`" - ), - "powf32" => codegen_simple_intrinsic!(Powf), - "powf64" => codegen_simple_intrinsic!(Pow), - "powif32" => codegen_simple_intrinsic!(Powif), - "powif64" => codegen_simple_intrinsic!(Powi), - "pref_align_of" => codegen_intrinsic_const!(), - "ptr_guaranteed_cmp" => self.codegen_ptr_guaranteed_cmp(fargs, place, loc), - "ptr_offset_from" => self.codegen_ptr_offset_from(fargs, place, loc), - "ptr_offset_from_unsigned" => self.codegen_ptr_offset_from_unsigned(fargs, place, loc), - "raw_eq" => self.codegen_intrinsic_raw_eq(instance, fargs, place, loc), - "retag_box_to_raw" => self.codegen_retag_box_to_raw(fargs, place, loc), - "rintf32" => codegen_simple_intrinsic!(Rintf), - "rintf64" => codegen_simple_intrinsic!(Rint), - "rotate_left" => codegen_intrinsic_binop!(rol), - "rotate_right" => codegen_intrinsic_binop!(ror), - "roundf32" => codegen_simple_intrinsic!(Roundf), - "roundf64" => codegen_simple_intrinsic!(Round), - "saturating_add" => codegen_intrinsic_binop_with_mm!(saturating_add), - "saturating_sub" => codegen_intrinsic_binop_with_mm!(saturating_sub), - "sinf32" => codegen_simple_intrinsic!(Sinf), - "sinf64" => codegen_simple_intrinsic!(Sin), - "simd_add" => self.codegen_simd_op_with_overflow( + Intrinsic::NearbyIntF32 => codegen_simple_intrinsic!(Nearbyintf), + Intrinsic::NearbyIntF64 => codegen_simple_intrinsic!(Nearbyint), + Intrinsic::NeedsDrop => codegen_intrinsic_const!(), + Intrinsic::PowF32 => codegen_simple_intrinsic!(Powf), + Intrinsic::PowF64 => codegen_simple_intrinsic!(Pow), + Intrinsic::PowIF32 => codegen_simple_intrinsic!(Powif), + Intrinsic::PowIF64 => codegen_simple_intrinsic!(Powi), + Intrinsic::PrefAlignOf => codegen_intrinsic_const!(), + Intrinsic::PtrGuaranteedCmp => self.codegen_ptr_guaranteed_cmp(fargs, place, loc), + Intrinsic::PtrOffsetFrom => self.codegen_ptr_offset_from(fargs, place, loc), + Intrinsic::PtrOffsetFromUnsigned => { + self.codegen_ptr_offset_from_unsigned(fargs, place, loc) + } + Intrinsic::RawEq => self.codegen_intrinsic_raw_eq(instance, fargs, place, loc), + Intrinsic::RetagBoxToRaw => self.codegen_retag_box_to_raw(fargs, place, loc), + Intrinsic::RintF32 => codegen_simple_intrinsic!(Rintf), + Intrinsic::RintF64 => codegen_simple_intrinsic!(Rint), + Intrinsic::RotateLeft => codegen_intrinsic_binop!(rol), + Intrinsic::RotateRight => codegen_intrinsic_binop!(ror), + Intrinsic::RoundF32 => codegen_simple_intrinsic!(Roundf), + Intrinsic::RoundF64 => codegen_simple_intrinsic!(Round), + Intrinsic::SaturatingAdd => codegen_intrinsic_binop_with_mm!(saturating_add), + Intrinsic::SaturatingSub => codegen_intrinsic_binop_with_mm!(saturating_sub), + Intrinsic::SinF32 => codegen_simple_intrinsic!(Sinf), + Intrinsic::SinF64 => codegen_simple_intrinsic!(Sin), + Intrinsic::SimdAdd => self.codegen_simd_op_with_overflow( Expr::plus, Expr::add_overflow_p, fargs, - intrinsic, + intrinsic_str, place, loc, ), - "simd_and" => codegen_intrinsic_binop!(bitand), + Intrinsic::SimdAnd => codegen_intrinsic_binop!(bitand), // TODO: `simd_rem` doesn't check for overflow cases for floating point operands. // - "simd_div" | "simd_rem" => { - self.codegen_simd_div_with_overflow(fargs, intrinsic, place, loc) + Intrinsic::SimdDiv | Intrinsic::SimdRem => { + self.codegen_simd_div_with_overflow(fargs, intrinsic_str, place, loc) } - "simd_eq" => { + Intrinsic::SimdEq => { self.codegen_simd_cmp(Expr::vector_eq, fargs, place, span, farg_types, ret_ty) } - "simd_extract" => { + Intrinsic::SimdExtract => { self.codegen_intrinsic_simd_extract(fargs, place, farg_types, ret_ty, span) } - "simd_ge" => { + Intrinsic::SimdGe => { self.codegen_simd_cmp(Expr::vector_ge, fargs, place, span, farg_types, ret_ty) } - "simd_gt" => { + Intrinsic::SimdGt => { self.codegen_simd_cmp(Expr::vector_gt, fargs, place, span, farg_types, ret_ty) } - "simd_insert" => { + Intrinsic::SimdInsert => { self.codegen_intrinsic_simd_insert(fargs, place, cbmc_ret_ty, farg_types, span, loc) } - "simd_le" => { + Intrinsic::SimdLe => { self.codegen_simd_cmp(Expr::vector_le, fargs, place, span, farg_types, ret_ty) } - "simd_lt" => { + Intrinsic::SimdLt => { self.codegen_simd_cmp(Expr::vector_lt, fargs, place, span, farg_types, ret_ty) } - "simd_mul" => self.codegen_simd_op_with_overflow( + Intrinsic::SimdMul => self.codegen_simd_op_with_overflow( Expr::mul, Expr::mul_overflow_p, fargs, - intrinsic, + intrinsic_str, place, loc, ), - "simd_ne" => { + Intrinsic::SimdNe => { self.codegen_simd_cmp(Expr::vector_neq, fargs, place, span, farg_types, ret_ty) } - "simd_or" => codegen_intrinsic_binop!(bitor), - "simd_shl" | "simd_shr" => { - self.codegen_simd_shift_with_distance_check(fargs, intrinsic, place, loc) + Intrinsic::SimdOr => codegen_intrinsic_binop!(bitor), + Intrinsic::SimdShl | Intrinsic::SimdShr => { + self.codegen_simd_shift_with_distance_check(fargs, intrinsic_str, place, loc) + } + Intrinsic::SimdShuffle(stripped) => { + let n: u64 = self.simd_shuffle_length(stripped.as_str(), farg_types, span); + self.codegen_intrinsic_simd_shuffle(fargs, place, farg_types, ret_ty, n, span) } - // "simd_shuffle#" => handled in an `if` preceding this match - "simd_sub" => self.codegen_simd_op_with_overflow( + Intrinsic::SimdSub => self.codegen_simd_op_with_overflow( Expr::sub, Expr::sub_overflow_p, fargs, - intrinsic, + intrinsic_str, place, loc, ), - "simd_xor" => codegen_intrinsic_binop!(bitxor), - "size_of" => unreachable!(), - "size_of_val" => codegen_size_align!(size), - "sqrtf32" => codegen_simple_intrinsic!(Sqrtf), - "sqrtf64" => codegen_simple_intrinsic!(Sqrt), - "sub_with_overflow" => self.codegen_op_with_overflow( + Intrinsic::SimdXor => codegen_intrinsic_binop!(bitxor), + Intrinsic::SizeOfVal => codegen_size_align!(size), + Intrinsic::SqrtF32 => codegen_simple_intrinsic!(Sqrtf), + Intrinsic::SqrtF64 => codegen_simple_intrinsic!(Sqrt), + Intrinsic::SubWithOverflow => self.codegen_op_with_overflow( BinaryOperator::OverflowResultMinus, fargs, place, loc, ), - "transmute" => self.codegen_intrinsic_transmute(fargs, ret_ty, place, loc), - "truncf32" => codegen_simple_intrinsic!(Truncf), - "truncf64" => codegen_simple_intrinsic!(Trunc), - "type_id" => codegen_intrinsic_const!(), - "type_name" => codegen_intrinsic_const!(), - "typed_swap" => self.codegen_swap(fargs, farg_types, loc), - "unaligned_volatile_load" => { + Intrinsic::Transmute => self.codegen_intrinsic_transmute(fargs, ret_ty, place, loc), + Intrinsic::TruncF32 => codegen_simple_intrinsic!(Truncf), + Intrinsic::TruncF64 => codegen_simple_intrinsic!(Trunc), + Intrinsic::TypeId => codegen_intrinsic_const!(), + Intrinsic::TypeName => codegen_intrinsic_const!(), + Intrinsic::TypedSwap => self.codegen_swap(fargs, farg_types, loc), + Intrinsic::UnalignedVolatileLoad => { unstable_codegen!(self.codegen_expr_to_place_stable( place, fargs.remove(0).dereference(), loc )) } - "unchecked_add" | "unchecked_mul" | "unchecked_shl" | "unchecked_shr" - | "unchecked_sub" => { - unreachable!("Expected intrinsic `{intrinsic}` to be lowered before codegen") - } - "unchecked_div" => codegen_op_with_div_overflow_check!(div), - "unchecked_rem" => codegen_op_with_div_overflow_check!(rem), - "unlikely" => self.codegen_expr_to_place_stable(place, fargs.remove(0), loc), - "unreachable" => unreachable!( - "Expected `std::intrinsics::unreachable` to be handled by `TerminatorKind::Unreachable`" - ), - "volatile_copy_memory" => unstable_codegen!(codegen_intrinsic_copy!(Memmove)), - "volatile_copy_nonoverlapping_memory" => { + Intrinsic::UncheckedDiv => codegen_op_with_div_overflow_check!(div), + Intrinsic::UncheckedRem => codegen_op_with_div_overflow_check!(rem), + Intrinsic::Unlikely => self.codegen_expr_to_place_stable(place, fargs.remove(0), loc), + Intrinsic::VolatileCopyMemory => unstable_codegen!(codegen_intrinsic_copy!(Memmove)), + Intrinsic::VolatileCopyNonOverlappingMemory => { unstable_codegen!(codegen_intrinsic_copy!(Memcpy)) } - "volatile_load" => self.codegen_volatile_load(fargs, farg_types, place, loc), - "volatile_store" => { + Intrinsic::VolatileLoad => self.codegen_volatile_load(fargs, farg_types, place, loc), + Intrinsic::VolatileStore => { assert!(self.place_ty_stable(place).kind().is_unit()); self.codegen_volatile_store(fargs, farg_types, loc) } - "vtable_size" => self.vtable_info(VTableInfo::Size, fargs, place, loc), - "vtable_align" => self.vtable_info(VTableInfo::Align, fargs, place, loc), - "wrapping_add" => codegen_wrapping_op!(plus), - "wrapping_mul" => codegen_wrapping_op!(mul), - "wrapping_sub" => codegen_wrapping_op!(sub), - "write_bytes" => { + Intrinsic::VtableSize => self.vtable_info(VTableInfo::Size, fargs, place, loc), + Intrinsic::VtableAlign => self.vtable_info(VTableInfo::Align, fargs, place, loc), + Intrinsic::WrappingAdd => codegen_wrapping_op!(plus), + Intrinsic::WrappingMul => codegen_wrapping_op!(mul), + Intrinsic::WrappingSub => codegen_wrapping_op!(sub), + Intrinsic::WriteBytes => { assert!(self.place_ty_stable(place).kind().is_unit()); self.codegen_write_bytes(fargs, farg_types, loc) } // Unimplemented - _ => self.codegen_unimplemented_stmt( - intrinsic, - loc, - "https://github.com/model-checking/kani/issues/new/choose", - ), + Intrinsic::Unimplemented { name, issue_link } => { + self.codegen_unimplemented_stmt(&name, loc, &issue_link) + } } } diff --git a/kani-compiler/src/intrinsics.rs b/kani-compiler/src/intrinsics.rs new file mode 100644 index 000000000000..9485d8525410 --- /dev/null +++ b/kani-compiler/src/intrinsics.rs @@ -0,0 +1,798 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Single source of truth about which intrinsics we support. + +use stable_mir::{ + mir::{mono::Instance, Mutability}, + ty::{FloatTy, IntTy, RigidTy, TyKind, UintTy}, +}; + +// Enumeration of all intrinsics we support right now, with the last option being a catch-all. This +// way, adding an intrinsic would highlight all places where they are used. +#[allow(unused)] +#[derive(Clone, Debug)] +pub enum Intrinsic { + AddWithOverflow, + ArithOffset, + AssertInhabited, + AssertMemUninitializedValid, + AssertZeroValid, + Assume, + AtomicAnd(String), + AtomicCxchg(String), + AtomicCxchgWeak(String), + AtomicFence(String), + AtomicLoad(String), + AtomicMax(String), + AtomicMin(String), + AtomicNand(String), + AtomicOr(String), + AtomicSingleThreadFence(String), + AtomicStore(String), + AtomicUmax(String), + AtomicUmin(String), + AtomicXadd(String), + AtomicXchg(String), + AtomicXor(String), + AtomicXsub(String), + Bitreverse, + BlackBox, + Breakpoint, + Bswap, + CeilF32, + CeilF64, + CompareBytes, + Copy, + CopySignF32, + CopySignF64, + CosF32, + CosF64, + Ctlz, + CtlzNonZero, + Ctpop, + Cttz, + CttzNonZero, + DiscriminantValue, + ExactDiv, + Exp2F32, + Exp2F64, + ExpF32, + ExpF64, + FabsF32, + FabsF64, + FaddFast, + FdivFast, + FloorF32, + FloorF64, + FmafF32, + FmafF64, + FmulFast, + Forget, + FsubFast, + IsValStaticallyKnown, + Likely, + Log10F32, + Log10F64, + Log2F32, + Log2F64, + LogF32, + LogF64, + MaxNumF32, + MaxNumF64, + MinAlignOf, + MinAlignOfVal, + MinNumF32, + MinNumF64, + MulWithOverflow, + NearbyIntF32, + NearbyIntF64, + NeedsDrop, + PowF32, + PowF64, + PowIF32, + PowIF64, + PrefAlignOf, + PtrGuaranteedCmp, + PtrOffsetFrom, + PtrOffsetFromUnsigned, + RawEq, + RetagBoxToRaw, + RintF32, + RintF64, + RotateLeft, + RotateRight, + RoundF32, + RoundF64, + SaturatingAdd, + SaturatingSub, + SinF32, + SinF64, + SimdAdd, + SimdAnd, + SimdDiv, + SimdRem, + SimdEq, + SimdExtract, + SimdGe, + SimdGt, + SimdInsert, + SimdLe, + SimdLt, + SimdMul, + SimdNe, + SimdOr, + SimdShl, + SimdShr, + SimdShuffle(String), + SimdSub, + SimdXor, + SizeOfVal, + SqrtF32, + SqrtF64, + SubWithOverflow, + Transmute, + TruncF32, + TruncF64, + TypeId, + TypeName, + TypedSwap, + UnalignedVolatileLoad, + UncheckedDiv, + UncheckedRem, + Unlikely, + VolatileCopyMemory, + VolatileCopyNonOverlappingMemory, + VolatileLoad, + VolatileStore, + VtableSize, + VtableAlign, + WrappingAdd, + WrappingMul, + WrappingSub, + WriteBytes, + Unimplemented { name: String, issue_link: String }, +} + +/// Assert that top-level types of a function signature match the given patterns. +macro_rules! assert_sig_matches { + ($sig:expr, $($input_type:pat),* => $output_type:pat) => { + let inputs = $sig.inputs(); + let output = $sig.output(); + #[allow(unused_mut)] + let mut index = 0; + $( + #[allow(unused_assignments)] + { + assert!(matches!(inputs[index].kind(), TyKind::RigidTy($input_type))); + index += 1; + } + )* + assert!(inputs.len() == index); + assert!(matches!(output.kind(), TyKind::RigidTy($output_type))); + } +} + +impl Intrinsic { + /// Create an intrinsic enum from a given intrinsic instance, shallowly validating the argument types. + pub fn from_instance(intrinsic_instance: &Instance) -> Self { + let intrinsic_str = intrinsic_instance.intrinsic_name().unwrap(); + let sig = intrinsic_instance.ty().kind().fn_sig().unwrap().skip_binder(); + match intrinsic_str.as_str() { + "add_with_overflow" => { + assert_sig_matches!(sig, _, _ => RigidTy::Tuple(_)); + Self::AddWithOverflow + } + "arith_offset" => { + assert_sig_matches!(sig, + RigidTy::RawPtr(_, Mutability::Not), + RigidTy::Int(IntTy::Isize) + => RigidTy::RawPtr(_, Mutability::Not)); + Self::ArithOffset + } + "assert_inhabited" => { + assert_sig_matches!(sig, => RigidTy::Tuple(_)); + Self::AssertInhabited + } + "assert_mem_uninitialized_valid" => { + assert_sig_matches!(sig, => RigidTy::Tuple(_)); + Self::AssertMemUninitializedValid + } + "assert_zero_valid" => { + assert_sig_matches!(sig, => RigidTy::Tuple(_)); + Self::AssertZeroValid + } + "assume" => { + assert_sig_matches!(sig, RigidTy::Bool => RigidTy::Tuple(_)); + Self::Assume + } + "bitreverse" => { + assert_sig_matches!(sig, _ => _); + Self::Bitreverse + } + "black_box" => { + assert_sig_matches!(sig, _ => _); + Self::BlackBox + } + "breakpoint" => { + assert_sig_matches!(sig, => RigidTy::Tuple(_)); + Self::Breakpoint + } + "bswap" => { + assert_sig_matches!(sig, _ => _); + Self::Bswap + } + "caller_location" => { + assert_sig_matches!(sig, => RigidTy::Ref(_, _, Mutability::Not)); + Self::Unimplemented { + name: intrinsic_str, + issue_link: "https://github.com/model-checking/kani/issues/374".into(), + } + } + "catch_unwind" => { + assert_sig_matches!(sig, RigidTy::FnPtr(_), RigidTy::RawPtr(_, Mutability::Mut), RigidTy::FnPtr(_) => RigidTy::Int(IntTy::I32)); + Self::Unimplemented { + name: intrinsic_str, + issue_link: "https://github.com/model-checking/kani/issues/267".into(), + } + } + "compare_bytes" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not), RigidTy::RawPtr(_, Mutability::Not), RigidTy::Uint(UintTy::Usize) => RigidTy::Int(IntTy::I32)); + Self::CompareBytes + } + "copy" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not), RigidTy::RawPtr(_, Mutability::Mut), RigidTy::Uint(UintTy::Usize) => RigidTy::Tuple(_)); + Self::Copy + } + "copy_nonoverlapping" => unreachable!( + "Expected `core::intrinsics::unreachable` to be handled by `StatementKind::CopyNonOverlapping`" + ), + "ctlz" => { + assert_sig_matches!(sig, _ => RigidTy::Uint(UintTy::U32)); + Self::Ctlz + } + "ctlz_nonzero" => { + assert_sig_matches!(sig, _ => RigidTy::Uint(UintTy::U32)); + Self::CtlzNonZero + } + "ctpop" => { + assert_sig_matches!(sig, _ => RigidTy::Uint(UintTy::U32)); + Self::Ctpop + } + "cttz" => { + assert_sig_matches!(sig, _ => RigidTy::Uint(UintTy::U32)); + Self::Cttz + } + "cttz_nonzero" => { + assert_sig_matches!(sig, _ => RigidTy::Uint(UintTy::U32)); + Self::CttzNonZero + } + "discriminant_value" => { + assert_sig_matches!(sig, RigidTy::Ref(_, _, Mutability::Not) => _); + Self::DiscriminantValue + } + "exact_div" => { + assert_sig_matches!(sig, _, _ => _); + Self::ExactDiv + } + "fadd_fast" => { + assert_sig_matches!(sig, _, _ => _); + Self::FaddFast + } + "fdiv_fast" => { + assert_sig_matches!(sig, _, _ => _); + Self::FdivFast + } + "fmul_fast" => { + assert_sig_matches!(sig, _, _ => _); + Self::FmulFast + } + "forget" => { + assert_sig_matches!(sig, _ => RigidTy::Tuple(_)); + Self::Forget + } + "fsub_fast" => { + assert_sig_matches!(sig, _, _ => _); + Self::FsubFast + } + "is_val_statically_known" => { + assert_sig_matches!(sig, _ => RigidTy::Bool); + Self::IsValStaticallyKnown + } + "likely" => { + assert_sig_matches!(sig, RigidTy::Bool => RigidTy::Bool); + Self::Likely + } + "min_align_of" => { + assert_sig_matches!(sig, => RigidTy::Uint(UintTy::Usize)); + Self::MinAlignOf + } + "min_align_of_val" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not) => RigidTy::Uint(UintTy::Usize)); + Self::MinAlignOfVal + } + "mul_with_overflow" => { + assert_sig_matches!(sig, _, _ => RigidTy::Tuple(_)); + Self::MulWithOverflow + } + "needs_drop" => { + assert_sig_matches!(sig, => RigidTy::Bool); + Self::NeedsDrop + } + // As of https://github.com/rust-lang/rust/pull/110822 the `offset` intrinsic is lowered to `mir::BinOp::Offset` + "offset" => unreachable!( + "Expected `core::intrinsics::unreachable` to be handled by `BinOp::OffSet`" + ), + "pref_align_of" => { + assert_sig_matches!(sig, => RigidTy::Uint(UintTy::Usize)); + Self::PrefAlignOf + } + "ptr_guaranteed_cmp" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not), RigidTy::RawPtr(_, Mutability::Not) => RigidTy::Uint(UintTy::U8)); + Self::PtrGuaranteedCmp + } + "ptr_offset_from" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not), RigidTy::RawPtr(_, Mutability::Not) => RigidTy::Int(IntTy::Isize)); + Self::PtrOffsetFrom + } + "ptr_offset_from_unsigned" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not), RigidTy::RawPtr(_, Mutability::Not) => RigidTy::Uint(UintTy::Usize)); + Self::PtrOffsetFromUnsigned + } + "raw_eq" => { + assert_sig_matches!(sig, RigidTy::Ref(_, _, Mutability::Not), RigidTy::Ref(_, _, Mutability::Not) => RigidTy::Bool); + Self::RawEq + } + "rotate_left" => { + assert_sig_matches!(sig, _, RigidTy::Uint(UintTy::U32) => _); + Self::RotateLeft + } + "rotate_right" => { + assert_sig_matches!(sig, _, RigidTy::Uint(UintTy::U32) => _); + Self::RotateRight + } + "saturating_add" => { + assert_sig_matches!(sig, _, _ => _); + Self::SaturatingAdd + } + "saturating_sub" => { + assert_sig_matches!(sig, _, _ => _); + Self::SaturatingSub + } + "size_of" => unreachable!(), + "size_of_val" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not) => RigidTy::Uint(UintTy::Usize)); + Self::SizeOfVal + } + "sub_with_overflow" => { + assert_sig_matches!(sig, _, _ => RigidTy::Tuple(_)); + Self::SubWithOverflow + } + "transmute" => { + assert_sig_matches!(sig, _ => _); + Self::Transmute + } + "type_id" => { + assert_sig_matches!(sig, => RigidTy::Uint(UintTy::U128)); + Self::TypeId + } + "type_name" => { + assert_sig_matches!(sig, => RigidTy::Ref(_, _, Mutability::Not)); + Self::TypeName + } + "typed_swap" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), RigidTy::RawPtr(_, Mutability::Mut) => RigidTy::Tuple(_)); + Self::TypedSwap + } + "unaligned_volatile_load" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not) => _); + Self::UnalignedVolatileLoad + } + "unchecked_add" | "unchecked_mul" | "unchecked_shl" | "unchecked_shr" + | "unchecked_sub" => { + unreachable!("Expected intrinsic `{intrinsic_str}` to be lowered before codegen") + } + "unchecked_div" => { + assert_sig_matches!(sig, _, _ => _); + Self::UncheckedDiv + } + "unchecked_rem" => { + assert_sig_matches!(sig, _, _ => _); + Self::UncheckedRem + } + "unlikely" => { + assert_sig_matches!(sig, RigidTy::Bool => RigidTy::Bool); + Self::Unlikely + } + "unreachable" => unreachable!( + "Expected `std::intrinsics::unreachable` to be handled by `TerminatorKind::Unreachable`" + ), + "volatile_copy_memory" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), RigidTy::RawPtr(_, Mutability::Not), RigidTy::Uint(UintTy::Usize) => RigidTy::Tuple(_)); + Self::VolatileCopyMemory + } + "volatile_copy_nonoverlapping_memory" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), RigidTy::RawPtr(_, Mutability::Not), RigidTy::Uint(UintTy::Usize) => RigidTy::Tuple(_)); + Self::VolatileCopyNonOverlappingMemory + } + "volatile_load" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not) => _); + Self::VolatileLoad + } + "volatile_store" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => RigidTy::Tuple(_)); + Self::VolatileStore + } + "vtable_size" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not) => RigidTy::Uint(UintTy::Usize)); + Self::VtableSize + } + "vtable_align" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not) => RigidTy::Uint(UintTy::Usize)); + Self::VtableAlign + } + "wrapping_add" => { + assert_sig_matches!(sig, _, _ => _); + Self::WrappingAdd + } + "wrapping_mul" => { + assert_sig_matches!(sig, _, _ => _); + Self::WrappingMul + } + "wrapping_sub" => { + assert_sig_matches!(sig, _, _ => _); + Self::WrappingSub + } + "write_bytes" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), RigidTy::Uint(UintTy::U8), RigidTy::Uint(UintTy::Usize) => RigidTy::Tuple(_)); + Self::WriteBytes + } + _ => try_match_atomic(intrinsic_instance) + .or_else(|| try_match_simd(intrinsic_instance)) + .or_else(|| try_match_f32(intrinsic_instance)) + .or_else(|| try_match_f64(intrinsic_instance)) + .unwrap_or(Self::Unimplemented { + name: intrinsic_str, + issue_link: "https://github.com/model-checking/kani/issues/new/choose".into(), + }), + } + } +} + +/// Match atomic intrinsics by instance, returning an instance of the intrinsics enum if the match +/// is successful. +fn try_match_atomic(intrinsic_instance: &Instance) -> Option { + let intrinsic_str = intrinsic_instance.intrinsic_name().unwrap(); + let sig = intrinsic_instance.ty().kind().fn_sig().unwrap().skip_binder(); + if let Some(suffix) = intrinsic_str.strip_prefix("atomic_and_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicAnd(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_cxchgweak_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _, _ => RigidTy::Tuple(_)); + Some(Intrinsic::AtomicCxchgWeak(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_cxchg_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _, _ => RigidTy::Tuple(_)); + Some(Intrinsic::AtomicCxchg(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_fence_") { + assert_sig_matches!(sig, => RigidTy::Tuple(_)); + Some(Intrinsic::AtomicFence(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_load_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not) => _); + Some(Intrinsic::AtomicLoad(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_max_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicMax(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_min_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicMin(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_nand_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicNand(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_or_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicOr(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_singlethreadfence_") { + assert_sig_matches!(sig, => RigidTy::Tuple(_)); + Some(Intrinsic::AtomicSingleThreadFence(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_store_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => RigidTy::Tuple(_)); + Some(Intrinsic::AtomicStore(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_umax_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicUmax(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_umin_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicUmin(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_xadd_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicXadd(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_xchg_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicXchg(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_xor_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicXor(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_xsub_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicXsub(suffix.into())) + } else { + None + } +} + +/// Match SIMD intrinsics by instance, returning an instance of the intrinsics enum if the match +/// is successful. +fn try_match_simd(intrinsic_instance: &Instance) -> Option { + let intrinsic_str = intrinsic_instance.intrinsic_name().unwrap(); + let sig = intrinsic_instance.ty().kind().fn_sig().unwrap().skip_binder(); + match intrinsic_str.as_str() { + "simd_add" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdAdd) + } + "simd_and" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdAnd) + } + "simd_div" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdDiv) + } + "simd_rem" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdRem) + } + "simd_eq" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdEq) + } + "simd_extract" => { + assert_sig_matches!(sig, _, RigidTy::Uint(UintTy::U32) => _); + Some(Intrinsic::SimdExtract) + } + "simd_ge" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdGe) + } + "simd_gt" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdGt) + } + "simd_insert" => { + assert_sig_matches!(sig, _, RigidTy::Uint(UintTy::U32), _ => _); + Some(Intrinsic::SimdInsert) + } + "simd_le" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdLe) + } + "simd_lt" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdLt) + } + "simd_mul" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdMul) + } + "simd_ne" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdNe) + } + "simd_or" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdOr) + } + "simd_shl" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdShl) + } + "simd_shr" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdShr) + } + "simd_sub" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdSub) + } + "simd_xor" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdXor) + } + name => { + if let Some(suffix) = name.strip_prefix("simd_shuffle") { + assert_sig_matches!(sig, _, _, _ => _); + Some(Intrinsic::SimdShuffle(suffix.into())) + } else { + None + } + } + } +} + +/// Match f32 arithmetic intrinsics by instance, returning an instance of the intrinsics enum if the match +/// is successful. +fn try_match_f32(intrinsic_instance: &Instance) -> Option { + let intrinsic_str = intrinsic_instance.intrinsic_name().unwrap(); + let sig = intrinsic_instance.ty().kind().fn_sig().unwrap().skip_binder(); + match intrinsic_str.as_str() { + "ceilf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::CeilF32) + } + "copysignf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32), RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::CopySignF32) + } + "cosf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::CosF32) + } + "exp2f32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::Exp2F32) + } + "expf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::ExpF32) + } + "fabsf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::FabsF32) + } + "floorf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::FloorF32) + } + "fmaf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32), RigidTy::Float(FloatTy::F32), RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::FmafF32) + } + "log10f32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::Log10F32) + } + "log2f32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::Log2F32) + } + "logf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::LogF32) + } + "maxnumf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32), RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::MaxNumF32) + } + "minnumf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32), RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::MinNumF32) + } + "nearbyintf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::NearbyIntF32) + } + "powf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32), RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::PowF32) + } + "powif32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32), RigidTy::Int(IntTy::I32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::PowIF32) + } + "rintf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::RintF32) + } + "roundf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::RoundF32) + } + "sinf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::SinF32) + } + "sqrtf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::SqrtF32) + } + "truncf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::TruncF32) + } + _ => None, + } +} + +/// Match f64 arithmetic intrinsics by instance, returning an instance of the intrinsics enum if the match +/// is successful. +fn try_match_f64(intrinsic_instance: &Instance) -> Option { + let intrinsic_str = intrinsic_instance.intrinsic_name().unwrap(); + let sig = intrinsic_instance.ty().kind().fn_sig().unwrap().skip_binder(); + match intrinsic_str.as_str() { + "ceilf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::CeilF64) + } + "copysignf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64), RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::CopySignF64) + } + "cosf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::CosF64) + } + "exp2f64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::Exp2F64) + } + "expf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::ExpF64) + } + "fabsf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::FabsF64) + } + "floorf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::FloorF64) + } + "fmaf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64), RigidTy::Float(FloatTy::F64), RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::FmafF64) + } + "log10f64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::Log10F64) + } + "log2f64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::Log2F64) + } + "logf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::LogF64) + } + "maxnumf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64), RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::MaxNumF64) + } + "minnumf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64), RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::MinNumF64) + } + "nearbyintf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::NearbyIntF64) + } + "powf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64), RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::PowF64) + } + "powif64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64), RigidTy::Int(IntTy::I32) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::PowIF64) + } + "rintf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::RintF64) + } + "roundf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::RoundF64) + } + "sinf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::SinF64) + } + "sqrtf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::SqrtF64) + } + "truncf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::TruncF64) + } + _ => None, + } +} diff --git a/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs b/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs index 640318ccb584..15593549b690 100644 --- a/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs +++ b/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs @@ -24,12 +24,14 @@ //! Currently, the analysis is not field-sensitive: e.g., if a field of a place aliases to some //! other place, we treat it as if the place itself aliases to another place. -use crate::kani_middle::{ - points_to::{MemLoc, PointsToGraph}, - reachability::CallGraph, - transform::RustcInternalMir, +use crate::{ + intrinsics::Intrinsic, + kani_middle::{ + points_to::{MemLoc, PointsToGraph}, + reachability::CallGraph, + transform::RustcInternalMir, + }, }; -use rustc_ast::Mutability; use rustc_middle::{ mir::{ BasicBlock, BinOp, Body, CallReturnPlaces, Location, NonDivergingIntrinsic, Operand, Place, @@ -202,161 +204,117 @@ impl<'a, 'tcx> Analysis<'tcx> for PointsToAnalysis<'a, 'tcx> { match instance.def { // Intrinsics could introduce aliasing edges we care about, so need to handle them. InstanceKind::Intrinsic(def_id) => { - match self.tcx.intrinsic(def_id).unwrap().name.to_string().as_str() { - name if name.starts_with("atomic") => { - match name { - // All `atomic_cxchg` intrinsics take `dst, old, src` as arguments. - // This is equivalent to `destination = *dst; *dst = src`. - name if name.starts_with("atomic_cxchg") => { - assert_eq!( - args.len(), - 3, - "Unexpected number of arguments for `{name}`" - ); - assert!(matches!( - args[0].node.ty(self.body, self.tcx).kind(), - TyKind::RawPtr(_, Mutability::Mut) - )); - let src_set = - self.successors_for_operand(state, args[2].node.clone()); - let dst_set = - self.successors_for_deref(state, args[0].node.clone()); - let destination_set = - state.resolve_place(*destination, self.instance); - state.extend(&destination_set, &state.successors(&dst_set)); - state.extend(&dst_set, &src_set); - } - // All `atomic_load` intrinsics take `src` as an argument. - // This is equivalent to `destination = *src`. - name if name.starts_with("atomic_load") => { - assert_eq!( - args.len(), - 1, - "Unexpected number of arguments for `{name}`" - ); - assert!(matches!( - args[0].node.ty(self.body, self.tcx).kind(), - TyKind::RawPtr(_, Mutability::Not) - )); - let src_set = - self.successors_for_deref(state, args[0].node.clone()); - let destination_set = - state.resolve_place(*destination, self.instance); - state.extend(&destination_set, &state.successors(&src_set)); - } - // All `atomic_store` intrinsics take `dst, val` as arguments. - // This is equivalent to `*dst = val`. - name if name.starts_with("atomic_store") => { - assert_eq!( - args.len(), - 2, - "Unexpected number of arguments for `{name}`" - ); - assert!(matches!( - args[0].node.ty(self.body, self.tcx).kind(), - TyKind::RawPtr(_, Mutability::Mut) - )); - let dst_set = - self.successors_for_deref(state, args[0].node.clone()); - let val_set = - self.successors_for_operand(state, args[1].node.clone()); - state.extend(&dst_set, &val_set); - } - // All other `atomic` intrinsics take `dst, src` as arguments. - // This is equivalent to `destination = *dst; *dst = src`. - _ => { - assert_eq!( - args.len(), - 2, - "Unexpected number of arguments for `{name}`" - ); - assert!(matches!( - args[0].node.ty(self.body, self.tcx).kind(), - TyKind::RawPtr(_, Mutability::Mut) - )); - let src_set = - self.successors_for_operand(state, args[1].node.clone()); - let dst_set = - self.successors_for_deref(state, args[0].node.clone()); - let destination_set = - state.resolve_place(*destination, self.instance); - state.extend(&destination_set, &state.successors(&dst_set)); - state.extend(&dst_set, &src_set); - } - }; - } - // Similar to `copy_nonoverlapping`, argument order is `src`, `dst`, `count`. - "copy" => { - assert_eq!(args.len(), 3, "Unexpected number of arguments for `copy`"); - assert!(matches!( - args[0].node.ty(self.body, self.tcx).kind(), - TyKind::RawPtr(_, Mutability::Not) - )); - assert!(matches!( - args[1].node.ty(self.body, self.tcx).kind(), - TyKind::RawPtr(_, Mutability::Mut) - )); - self.apply_copy_effect( - state, - args[0].node.clone(), - args[1].node.clone(), - ); - } - // Similar to `copy_nonoverlapping`, argument order is `dst`, `src`, `count`. - "volatile_copy_memory" | "volatile_copy_nonoverlapping_memory" => { - assert_eq!(args.len(), 3, "Unexpected number of arguments for `copy`"); - assert!(matches!( - args[0].node.ty(self.body, self.tcx).kind(), - TyKind::RawPtr(_, Mutability::Mut) - )); - assert!(matches!( - args[1].node.ty(self.body, self.tcx).kind(), - TyKind::RawPtr(_, Mutability::Not) - )); - self.apply_copy_effect( - state, - args[1].node.clone(), - args[0].node.clone(), - ); - } - // Semantically equivalent to dest = *a - "volatile_load" | "unaligned_volatile_load" => { - assert_eq!( - args.len(), - 1, - "Unexpected number of arguments for `volatile_load`" - ); - assert!(matches!( - args[0].node.ty(self.body, self.tcx).kind(), - TyKind::RawPtr(_, Mutability::Not) - )); - // Destination of the return value. - let lvalue_set = state.resolve_place(*destination, self.instance); - let rvalue_set = self.successors_for_deref(state, args[0].node.clone()); - state.extend(&lvalue_set, &state.successors(&rvalue_set)); - } - // Semantically equivalent *a = b. - "volatile_store" | "unaligned_volatile_store" => { - assert_eq!( - args.len(), - 2, - "Unexpected number of arguments for `volatile_store`" - ); - assert!(matches!( - args[0].node.ty(self.body, self.tcx).kind(), - TyKind::RawPtr(_, Mutability::Mut) - )); - let lvalue_set = self.successors_for_deref(state, args[0].node.clone()); - let rvalue_set = - self.successors_for_operand(state, args[1].node.clone()); - state.extend(&lvalue_set, &rvalue_set); - } - _ => { - // TODO: this probably does not handle all relevant intrinsics, so more - // need to be added. For more information, see: - // https://github.com/model-checking/kani/issues/3300 - if self.tcx.is_mir_available(def_id) { - self.apply_regular_call_effect(state, instance, args, destination); + // Check if the intrinsic has a body we can analyze. + if self.tcx.is_mir_available(def_id) { + self.apply_regular_call_effect(state, instance, args, destination); + } else { + // Check all of the other intrinsics. + match Intrinsic::from_instance(&rustc_internal::stable(instance)) { + intrinsic if is_identity_aliasing_intrinsic(intrinsic.clone()) => { + // Treat the intrinsic as an aggregate, taking a union of all of the + // arguments' aliases. + let destination_set = + state.resolve_place(*destination, self.instance); + let operands_set = args + .into_iter() + .flat_map(|operand| { + self.successors_for_operand(state, operand.node.clone()) + }) + .collect(); + state.extend(&destination_set, &operands_set); + } + // All `atomic_cxchg` intrinsics take `dst, old, src` as arguments. + // This is equivalent to `destination = *dst; *dst = src`. + Intrinsic::AtomicCxchg(_) | Intrinsic::AtomicCxchgWeak(_) => { + let src_set = + self.successors_for_operand(state, args[2].node.clone()); + let dst_set = + self.successors_for_deref(state, args[0].node.clone()); + let destination_set = + state.resolve_place(*destination, self.instance); + state.extend(&destination_set, &state.successors(&dst_set)); + state.extend(&dst_set, &src_set); + } + // All `atomic_load` intrinsics take `src` as an argument. + // This is equivalent to `destination = *src`. + Intrinsic::AtomicLoad(_) => { + let src_set = + self.successors_for_deref(state, args[0].node.clone()); + let destination_set = + state.resolve_place(*destination, self.instance); + state.extend(&destination_set, &state.successors(&src_set)); + } + // All `atomic_store` intrinsics take `dst, val` as arguments. + // This is equivalent to `*dst = val`. + Intrinsic::AtomicStore(_) => { + let dst_set = + self.successors_for_deref(state, args[0].node.clone()); + let val_set = + self.successors_for_operand(state, args[1].node.clone()); + state.extend(&dst_set, &val_set); + } + // All other `atomic` intrinsics take `dst, src` as arguments. + // This is equivalent to `destination = *dst; *dst = src`. + Intrinsic::AtomicAnd(_) + | Intrinsic::AtomicMax(_) + | Intrinsic::AtomicMin(_) + | Intrinsic::AtomicNand(_) + | Intrinsic::AtomicOr(_) + | Intrinsic::AtomicUmax(_) + | Intrinsic::AtomicUmin(_) + | Intrinsic::AtomicXadd(_) + | Intrinsic::AtomicXchg(_) + | Intrinsic::AtomicXor(_) + | Intrinsic::AtomicXsub(_) => { + let src_set = + self.successors_for_operand(state, args[1].node.clone()); + let dst_set = + self.successors_for_deref(state, args[0].node.clone()); + let destination_set = + state.resolve_place(*destination, self.instance); + state.extend(&destination_set, &state.successors(&dst_set)); + state.extend(&dst_set, &src_set); + } + // Similar to `copy_nonoverlapping`, argument order is `src`, `dst`, `count`. + Intrinsic::Copy => { + self.apply_copy_effect( + state, + args[0].node.clone(), + args[1].node.clone(), + ); + } + // Similar to `copy_nonoverlapping`, argument order is `dst`, `src`, `count`. + Intrinsic::VolatileCopyMemory + | Intrinsic::VolatileCopyNonOverlappingMemory => { + self.apply_copy_effect( + state, + args[1].node.clone(), + args[0].node.clone(), + ); + } + // Semantically equivalent to dest = *a + Intrinsic::VolatileLoad | Intrinsic::UnalignedVolatileLoad => { + // Destination of the return value. + let lvalue_set = state.resolve_place(*destination, self.instance); + let rvalue_set = + self.successors_for_deref(state, args[0].node.clone()); + state.extend(&lvalue_set, &state.successors(&rvalue_set)); + } + // Semantically equivalent *a = b. + Intrinsic::VolatileStore => { + let lvalue_set = + self.successors_for_deref(state, args[0].node.clone()); + let rvalue_set = + self.successors_for_operand(state, args[1].node.clone()); + state.extend(&lvalue_set, &rvalue_set); + } + Intrinsic::Unimplemented { .. } => { + // This will be taken care of at the codegen level. + } + intrinsic => { + unimplemented!( + "Kani does not support reasoning about aliasing in presence of intrinsic `{intrinsic:?}`. For more information about the state of uninitialized memory checks implementation, see: https://github.com/model-checking/kani/issues/3300." + ); } } } @@ -652,3 +610,136 @@ impl<'a, 'tcx> PointsToAnalysis<'a, 'tcx> { } } } + +/// Determines if the intrinsic does not influence aliasing beyond being treated as an identity +/// function (i.e. propagate aliasing without changes). +fn is_identity_aliasing_intrinsic(intrinsic: Intrinsic) -> bool { + match intrinsic { + Intrinsic::AddWithOverflow + | Intrinsic::ArithOffset + | Intrinsic::AssertInhabited + | Intrinsic::AssertMemUninitializedValid + | Intrinsic::AssertZeroValid + | Intrinsic::Assume + | Intrinsic::Bitreverse + | Intrinsic::BlackBox + | Intrinsic::Breakpoint + | Intrinsic::Bswap + | Intrinsic::CeilF32 + | Intrinsic::CeilF64 + | Intrinsic::CompareBytes + | Intrinsic::CopySignF32 + | Intrinsic::CopySignF64 + | Intrinsic::CosF32 + | Intrinsic::CosF64 + | Intrinsic::Ctlz + | Intrinsic::CtlzNonZero + | Intrinsic::Ctpop + | Intrinsic::Cttz + | Intrinsic::CttzNonZero + | Intrinsic::DiscriminantValue + | Intrinsic::ExactDiv + | Intrinsic::Exp2F32 + | Intrinsic::Exp2F64 + | Intrinsic::ExpF32 + | Intrinsic::ExpF64 + | Intrinsic::FabsF32 + | Intrinsic::FabsF64 + | Intrinsic::FaddFast + | Intrinsic::FdivFast + | Intrinsic::FloorF32 + | Intrinsic::FloorF64 + | Intrinsic::FmafF32 + | Intrinsic::FmafF64 + | Intrinsic::FmulFast + | Intrinsic::Forget + | Intrinsic::FsubFast + | Intrinsic::IsValStaticallyKnown + | Intrinsic::Likely + | Intrinsic::Log10F32 + | Intrinsic::Log10F64 + | Intrinsic::Log2F32 + | Intrinsic::Log2F64 + | Intrinsic::LogF32 + | Intrinsic::LogF64 + | Intrinsic::MaxNumF32 + | Intrinsic::MaxNumF64 + | Intrinsic::MinAlignOf + | Intrinsic::MinAlignOfVal + | Intrinsic::MinNumF32 + | Intrinsic::MinNumF64 + | Intrinsic::MulWithOverflow + | Intrinsic::NearbyIntF32 + | Intrinsic::NearbyIntF64 + | Intrinsic::NeedsDrop + | Intrinsic::PowF32 + | Intrinsic::PowF64 + | Intrinsic::PowIF32 + | Intrinsic::PowIF64 + | Intrinsic::PrefAlignOf + | Intrinsic::PtrGuaranteedCmp + | Intrinsic::PtrOffsetFrom + | Intrinsic::PtrOffsetFromUnsigned + | Intrinsic::RawEq + | Intrinsic::RintF32 + | Intrinsic::RintF64 + | Intrinsic::RotateLeft + | Intrinsic::RotateRight + | Intrinsic::RoundF32 + | Intrinsic::RoundF64 + | Intrinsic::SaturatingAdd + | Intrinsic::SaturatingSub + | Intrinsic::SinF32 + | Intrinsic::SinF64 + | Intrinsic::SizeOfVal + | Intrinsic::SqrtF32 + | Intrinsic::SqrtF64 + | Intrinsic::SubWithOverflow + | Intrinsic::TruncF32 + | Intrinsic::TruncF64 + | Intrinsic::TypeId + | Intrinsic::TypeName + | Intrinsic::UncheckedDiv + | Intrinsic::UncheckedRem + | Intrinsic::Unlikely + | Intrinsic::VtableSize + | Intrinsic::VtableAlign + | Intrinsic::WrappingAdd + | Intrinsic::WrappingMul + | Intrinsic::WrappingSub + | Intrinsic::WriteBytes => { + /* Intrinsics that do not interact with aliasing beyond propagating it. */ + true + } + Intrinsic::SimdAdd + | Intrinsic::SimdAnd + | Intrinsic::SimdDiv + | Intrinsic::SimdRem + | Intrinsic::SimdEq + | Intrinsic::SimdExtract + | Intrinsic::SimdGe + | Intrinsic::SimdGt + | Intrinsic::SimdInsert + | Intrinsic::SimdLe + | Intrinsic::SimdLt + | Intrinsic::SimdMul + | Intrinsic::SimdNe + | Intrinsic::SimdOr + | Intrinsic::SimdShl + | Intrinsic::SimdShr + | Intrinsic::SimdShuffle(_) + | Intrinsic::SimdSub + | Intrinsic::SimdXor => { + /* SIMD operations */ + true + } + Intrinsic::AtomicFence(_) | Intrinsic::AtomicSingleThreadFence(_) => { + /* Atomic fences */ + true + } + _ => { + /* Everything else */ + false + } + } +} diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/initial_target_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/initial_target_visitor.rs index 11ac412703ae..35d0e7041704 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/initial_target_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/initial_target_visitor.rs @@ -4,14 +4,17 @@ //! This module contains the visitor responsible for collecting initial analysis targets for delayed //! UB instrumentation. -use crate::kani_middle::transform::check_uninit::ty_layout::tys_layout_equal_to_size; +use crate::{ + intrinsics::Intrinsic, + kani_middle::transform::check_uninit::ty_layout::tys_layout_equal_to_size, +}; use stable_mir::{ mir::{ alloc::GlobalAlloc, mono::{Instance, InstanceKind, StaticDef}, visit::Location, - Body, CastKind, LocalDecl, MirVisitor, Mutability, NonDivergingIntrinsic, Operand, Place, - Rvalue, Statement, StatementKind, Terminator, TerminatorKind, + Body, CastKind, LocalDecl, MirVisitor, NonDivergingIntrinsic, Operand, Place, Rvalue, + Statement, StatementKind, Terminator, TerminatorKind, }, ty::{ConstantKind, RigidTy, TyKind}, }; @@ -101,34 +104,12 @@ impl MirVisitor for InitialTargetVisitor { if let TerminatorKind::Call { func, args, .. } = &term.kind { let instance = try_resolve_instance(self.body.locals(), func).unwrap(); if instance.kind == InstanceKind::Intrinsic { - match instance.intrinsic_name().unwrap().as_str() { - "copy" => { - assert_eq!(args.len(), 3, "Unexpected number of arguments for `copy`"); - assert!(matches!( - args[0].ty(self.body.locals()).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) - )); - assert!(matches!( - args[1].ty(self.body.locals()).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) - )); + match Intrinsic::from_instance(&instance) { + Intrinsic::Copy => { // Here, `dst` is the second argument. self.push_operand(&args[1]); } - "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.body.locals()).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) - )); - assert!(matches!( - args[1].ty(self.body.locals()).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) - )); + Intrinsic::VolatileCopyMemory | Intrinsic::VolatileCopyNonOverlappingMemory => { // Here, `dst` is the first argument. self.push_operand(&args[0]); } diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs index 837e14abc886..f682f93a261e 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs @@ -3,12 +3,15 @@ // //! Visitor that collects all instructions relevant to uninitialized memory access. -use crate::kani_middle::transform::{ - body::{InsertPosition, MutableBody, SourceInstruction}, - check_uninit::{ - relevant_instruction::{InitRelevantInstruction, MemoryInitOp}, - ty_layout::tys_layout_compatible_to_size, - TargetFinder, +use crate::{ + intrinsics::Intrinsic, + kani_middle::transform::{ + body::{InsertPosition, MutableBody, SourceInstruction}, + check_uninit::{ + relevant_instruction::{InitRelevantInstruction, MemoryInitOp}, + ty_layout::tys_layout_compatible_to_size, + TargetFinder, + }, }, }; use stable_mir::{ @@ -16,8 +19,8 @@ use stable_mir::{ alloc::GlobalAlloc, mono::{Instance, InstanceKind}, visit::{Location, PlaceContext}, - BasicBlockIdx, CastKind, LocalDecl, MirVisitor, Mutability, NonDivergingIntrinsic, Operand, - Place, PointerCoercion, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, + BasicBlockIdx, CastKind, LocalDecl, MirVisitor, NonDivergingIntrinsic, Operand, Place, + PointerCoercion, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, }, ty::{ConstantKind, RigidTy, TyKind}, @@ -182,46 +185,30 @@ impl MirVisitor for CheckUninitVisitor { }; match instance.kind { InstanceKind::Intrinsic => { - match instance.intrinsic_name().unwrap().as_str() { - intrinsic_name if can_skip_intrinsic(intrinsic_name) => { + match Intrinsic::from_instance(&instance) { + intrinsic_name if can_skip_intrinsic(intrinsic_name.clone()) => { /* Intrinsics that can be safely skipped */ } - name if name.starts_with("atomic") => { - let num_args = match name { - // All `atomic_cxchg` intrinsics take `dst, old, src` as arguments. - name if name.starts_with("atomic_cxchg") => 3, - // All `atomic_load` intrinsics take `src` as an argument. - name if name.starts_with("atomic_load") => 1, - // All other `atomic` intrinsics take `dst, src` as arguments. - _ => 2, - }; - assert_eq!( - args.len(), - num_args, - "Unexpected number of arguments for `{name}`" - ); - assert!(matches!( - args[0].ty(&self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(..)) - )); + Intrinsic::AtomicAnd(_) + | Intrinsic::AtomicCxchg(_) + | Intrinsic::AtomicCxchgWeak(_) + | Intrinsic::AtomicLoad(_) + | Intrinsic::AtomicMax(_) + | Intrinsic::AtomicMin(_) + | Intrinsic::AtomicNand(_) + | Intrinsic::AtomicOr(_) + | Intrinsic::AtomicStore(_) + | Intrinsic::AtomicUmax(_) + | Intrinsic::AtomicUmin(_) + | Intrinsic::AtomicXadd(_) + | Intrinsic::AtomicXchg(_) + | Intrinsic::AtomicXor(_) + | Intrinsic::AtomicXsub(_) => { self.push_target(MemoryInitOp::Check { operand: args[0].clone(), }); } - "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)) - )); + Intrinsic::CompareBytes => { self.push_target(MemoryInitOp::CheckSliceChunk { operand: args[0].clone(), count: args[2].clone(), @@ -231,22 +218,7 @@ impl MirVisitor for CheckUninitVisitor { count: args[2].clone(), }); } - "copy" - | "volatile_copy_memory" - | "volatile_copy_nonoverlapping_memory" => { - 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)) - )); + Intrinsic::Copy => { self.push_target(MemoryInitOp::CheckSliceChunk { operand: args[0].clone(), count: args[2].clone(), @@ -258,20 +230,20 @@ impl MirVisitor for CheckUninitVisitor { position: InsertPosition::After, }); } - "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)) - )); + Intrinsic::VolatileCopyMemory + | Intrinsic::VolatileCopyNonOverlappingMemory => { + self.push_target(MemoryInitOp::CheckSliceChunk { + operand: args[1].clone(), + count: args[2].clone(), + }); + self.push_target(MemoryInitOp::SetSliceChunk { + operand: args[0].clone(), + count: args[2].clone(), + value: true, + position: InsertPosition::After, + }); + } + Intrinsic::TypedSwap => { self.push_target(MemoryInitOp::Check { operand: args[0].clone(), }); @@ -279,46 +251,19 @@ impl MirVisitor for CheckUninitVisitor { operand: args[1].clone(), }); } - "volatile_load" | "unaligned_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)) - )); + Intrinsic::VolatileLoad | Intrinsic::UnalignedVolatileLoad => { self.push_target(MemoryInitOp::Check { operand: args[0].clone(), }); } - "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)) - )); + Intrinsic::VolatileStore => { self.push_target(MemoryInitOp::Set { operand: args[0].clone(), value: true, position: InsertPosition::After, }); } - "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)) - )); + Intrinsic::WriteBytes => { self.push_target(MemoryInitOp::SetSliceChunk { operand: args[0].clone(), count: args[2].clone(), @@ -328,7 +273,7 @@ impl MirVisitor for CheckUninitVisitor { } intrinsic => { self.push_target(MemoryInitOp::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:?}`."), }); } } @@ -519,132 +464,131 @@ impl MirVisitor for CheckUninitVisitor { /// 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" => { +fn can_skip_intrinsic(intrinsic: Intrinsic) -> bool { + match intrinsic { + Intrinsic::AddWithOverflow + | Intrinsic::ArithOffset + | Intrinsic::AssertInhabited + | Intrinsic::AssertMemUninitializedValid + | Intrinsic::AssertZeroValid + | Intrinsic::Assume + | Intrinsic::Bitreverse + | Intrinsic::BlackBox + | Intrinsic::Breakpoint + | Intrinsic::Bswap + | Intrinsic::CeilF32 + | Intrinsic::CeilF64 + | Intrinsic::CopySignF32 + | Intrinsic::CopySignF64 + | Intrinsic::CosF32 + | Intrinsic::CosF64 + | Intrinsic::Ctlz + | Intrinsic::CtlzNonZero + | Intrinsic::Ctpop + | Intrinsic::Cttz + | Intrinsic::CttzNonZero + | Intrinsic::DiscriminantValue + | Intrinsic::ExactDiv + | Intrinsic::Exp2F32 + | Intrinsic::Exp2F64 + | Intrinsic::ExpF32 + | Intrinsic::ExpF64 + | Intrinsic::FabsF32 + | Intrinsic::FabsF64 + | Intrinsic::FaddFast + | Intrinsic::FdivFast + | Intrinsic::FloorF32 + | Intrinsic::FloorF64 + | Intrinsic::FmafF32 + | Intrinsic::FmafF64 + | Intrinsic::FmulFast + | Intrinsic::Forget + | Intrinsic::FsubFast + | Intrinsic::IsValStaticallyKnown + | Intrinsic::Likely + | Intrinsic::Log10F32 + | Intrinsic::Log10F64 + | Intrinsic::Log2F32 + | Intrinsic::Log2F64 + | Intrinsic::LogF32 + | Intrinsic::LogF64 + | Intrinsic::MaxNumF32 + | Intrinsic::MaxNumF64 + | Intrinsic::MinAlignOf + | Intrinsic::MinAlignOfVal + | Intrinsic::MinNumF32 + | Intrinsic::MinNumF64 + | Intrinsic::MulWithOverflow + | Intrinsic::NearbyIntF32 + | Intrinsic::NearbyIntF64 + | Intrinsic::NeedsDrop + | Intrinsic::PowF32 + | Intrinsic::PowF64 + | Intrinsic::PowIF32 + | Intrinsic::PowIF64 + | Intrinsic::PrefAlignOf + | Intrinsic::RawEq + | Intrinsic::RintF32 + | Intrinsic::RintF64 + | Intrinsic::RotateLeft + | Intrinsic::RotateRight + | Intrinsic::RoundF32 + | Intrinsic::RoundF64 + | Intrinsic::SaturatingAdd + | Intrinsic::SaturatingSub + | Intrinsic::SinF32 + | Intrinsic::SinF64 + | Intrinsic::SqrtF32 + | Intrinsic::SqrtF64 + | Intrinsic::SubWithOverflow + | Intrinsic::TruncF32 + | Intrinsic::TruncF64 + | Intrinsic::TypeId + | Intrinsic::TypeName + | Intrinsic::UncheckedDiv + | Intrinsic::UncheckedRem + | Intrinsic::Unlikely + | Intrinsic::VtableSize + | Intrinsic::VtableAlign + | Intrinsic::WrappingAdd + | Intrinsic::WrappingMul + | Intrinsic::WrappingSub => { /* Intrinsics that do not interact with memory initialization. */ true } - "ptr_guaranteed_cmp" | "ptr_offset_from" | "ptr_offset_from_unsigned" | "size_of_val" => { + Intrinsic::PtrGuaranteedCmp + | Intrinsic::PtrOffsetFrom + | Intrinsic::PtrOffsetFromUnsigned + | Intrinsic::SizeOfVal => { /* AFAICS from the documentation, none of those require the pointer arguments to be actually initialized. */ true } - name if name.starts_with("simd") => { + Intrinsic::SimdAdd + | Intrinsic::SimdAnd + | Intrinsic::SimdDiv + | Intrinsic::SimdRem + | Intrinsic::SimdEq + | Intrinsic::SimdExtract + | Intrinsic::SimdGe + | Intrinsic::SimdGt + | Intrinsic::SimdInsert + | Intrinsic::SimdLe + | Intrinsic::SimdLt + | Intrinsic::SimdMul + | Intrinsic::SimdNe + | Intrinsic::SimdOr + | Intrinsic::SimdShl + | Intrinsic::SimdShr + | Intrinsic::SimdShuffle(_) + | Intrinsic::SimdSub + | Intrinsic::SimdXor => { /* SIMD operations */ true } - name if name.starts_with("atomic_fence") - || name.starts_with("atomic_singlethreadfence") => - { + Intrinsic::AtomicFence(_) | Intrinsic::AtomicSingleThreadFence(_) => { /* Atomic fences */ 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 diff --git a/kani-compiler/src/main.rs b/kani-compiler/src/main.rs index 7f1fb144a09b..e47483fb4fa5 100644 --- a/kani-compiler/src/main.rs +++ b/kani-compiler/src/main.rs @@ -39,6 +39,7 @@ extern crate tempfile; mod args; #[cfg(feature = "cprover")] mod codegen_cprover_gotoc; +mod intrinsics; mod kani_compiler; mod kani_middle; mod kani_queries; From a55834b8715a0ef1a9b51c7d15f0e72d28b028b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:44:16 +0000 Subject: [PATCH 03/40] Bump tests/perf/s2n-quic from `445f73b` to `ab9723a` (#3434) Bumps [tests/perf/s2n-quic](https://github.com/aws/s2n-quic) from `445f73b` to `ab9723a`.
Commits

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/perf/s2n-quic | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/perf/s2n-quic b/tests/perf/s2n-quic index 445f73b27eae..ab9723a772f0 160000 --- a/tests/perf/s2n-quic +++ b/tests/perf/s2n-quic @@ -1 +1 @@ -Subproject commit 445f73b27eae529bb895a7678968e4c0c215ef8a +Subproject commit ab9723a772f03a9793c9863e73c9a48fab3c5235 From 952e75389251532259b266d9b4c27756f08b222d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 14:12:09 -0400 Subject: [PATCH 04/40] Automatic cargo update to 2024-08-12 (#3433) Dependency upgrade resulting from `cargo update`. Co-authored-by: tautschnig <1144736+tautschnig@users.noreply.github.com> --- Cargo.lock | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12c28c49c1bf..1e8e743d5e56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -146,9 +146,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.13" +version = "4.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" +checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" dependencies = [ "clap_builder", "clap_derive", @@ -156,9 +156,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.13" +version = "4.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" dependencies = [ "anstream", "anstyle", @@ -175,7 +175,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -510,7 +510,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -950,29 +950,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.204" +version = "1.0.206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "5b3e4cd94123dd520a128bcd11e34d9e9e423e7e3e50425cb1b4b1e3549d0284" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "fabfb6138d2383ea8208cf98ccf69cdfb1aff4088460681d84189aa259762f97" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] name = "serde_json" -version = "1.0.122" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" +checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" dependencies = [ "itoa", "memchr", @@ -1072,7 +1072,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -1087,9 +1087,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.72" +version = "2.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" dependencies = [ "proc-macro2", "quote", @@ -1126,7 +1126,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -1192,7 +1192,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -1467,5 +1467,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] From e2a209bcb847803e8a5abb20ef7002b109ed0bf6 Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Mon, 12 Aug 2024 23:27:29 +0200 Subject: [PATCH 05/40] Actually apply CBMC patch (#3436) The patch introduced in #3431 not only needs to be created, but also needs to be applied. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- scripts/setup/al2/install_cbmc.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/setup/al2/install_cbmc.sh b/scripts/setup/al2/install_cbmc.sh index de4b5215e083..3bac22ace3db 100755 --- a/scripts/setup/al2/install_cbmc.sh +++ b/scripts/setup/al2/install_cbmc.sh @@ -93,6 +93,7 @@ cat > varargs.patch << "EOF" } EOF +patch -p1 < varargs.patch cmake3 -S . -Bbuild -DWITH_JBMC=OFF -Dsat_impl="minisat2;cadical" \ -DCMAKE_C_COMPILER=gcc10-cc -DCMAKE_CXX_COMPILER=gcc10-c++ \ From f27a5ed1426c705ec6faa181f080579b90983436 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Thu, 15 Aug 2024 07:18:06 -0700 Subject: [PATCH 06/40] Add test related to issue 3432 (#3439) In some cases, Kani would report a spurious counter example for cases where a match arm contained more than one pattern. This was fixed by changing how we handle storage lifecycle in #2995. This PR is only adding the related test to the regression. Resolves #3432 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- tests/kani/Match/match_pattern.rs | 57 +++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tests/kani/Match/match_pattern.rs diff --git a/tests/kani/Match/match_pattern.rs b/tests/kani/Match/match_pattern.rs new file mode 100644 index 000000000000..1b8689aee881 --- /dev/null +++ b/tests/kani/Match/match_pattern.rs @@ -0,0 +1,57 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! Test that Kani can correctly handle match patterns joined with the `|` operator. +//! It contains two equivalent methods that only differ by grouping march patterns. +//! Kani used to only be able to verify one as reported in: +//! + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(kani, derive(kani::Arbitrary))] +pub enum AbstractInt { + Bottom = 0, + Zero = 1, + Top = 2, +} + +impl AbstractInt { + /// Code with exhausive match expression where each arm contains one pattern. + pub fn merge(self, other: Self) -> Self { + use AbstractInt::*; + match (self, other) { + (Bottom, x) => x, + (x, Bottom) => x, + (Zero, Zero) => Zero, + (Top, _) => Top, + (_, Top) => Top, + } + } + + /// Code with exhausive match expression where an arm may contain multiple patterns. + pub fn merge_joined(self, other: Self) -> Self { + use AbstractInt::*; + match (self, other) { + (Bottom, x) | (x, Bottom) => x, + (Zero, Zero) => Zero, + (Top, _) | (_, Top) => Top, + } + } +} + +#[cfg(kani)] +mod test { + use super::*; + + #[kani::proof] + fn merge_with_bottom() { + let x: AbstractInt = kani::any(); + assert!(x.merge(AbstractInt::Bottom) == x); + assert!(AbstractInt::Bottom.merge(x) == x) + } + + #[kani::proof] + fn check_equivalence() { + let x: AbstractInt = kani::any(); + let y: AbstractInt = kani::any(); + assert_eq!(x.merge(y), x.merge_joined(y)); + } +} From e6f8a62d689d0b0ebcbabe3661be6273c5ab9be8 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 15 Aug 2024 10:31:09 -0700 Subject: [PATCH 07/40] Implement memory initialization state copy functionality (#3350) This PR adds support of copying memory initialization state without checks in-between. Every time a copy is performed, the tracked byte is non-deterministically switched. Resolves #3347 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../kani_middle/transform/check_uninit/mod.rs | 35 ++++++++++++++++ .../check_uninit/ptr_uninit/uninit_visitor.rs | 42 +++++++------------ .../check_uninit/relevant_instruction.rs | 31 ++++++++++++-- library/kani/src/mem_init.rs | 40 ++++++++++++++++++ .../uninit/copy/copy_without_padding.expected | 1 + .../uninit/copy/copy_without_padding.rs | 23 ++++++++++ .../copy/expose_padding_via_copy.expected | 11 +++++ .../uninit/copy/expose_padding_via_copy.rs | 23 ++++++++++ ...xpose_padding_via_copy_convoluted.expected | 11 +++++ .../expose_padding_via_copy_convoluted.rs | 42 +++++++++++++++++++ .../expose_padding_via_non_byte_copy.expected | 11 +++++ .../copy/expose_padding_via_non_byte_copy.rs | 23 ++++++++++ .../non_byte_copy_without_padding.expected | 1 + .../copy/non_byte_copy_without_padding.rs | 23 ++++++++++ .../uninit/copy/read_after_copy.expected | 11 +++++ tests/expected/uninit/copy/read_after_copy.rs | 23 ++++++++++ .../expected/uninit/delayed-ub/delayed-ub.rs | 31 ++++++++++++++ tests/expected/uninit/delayed-ub/expected | 12 +++++- tests/expected/uninit/intrinsics/expected | 12 +++--- .../expected/uninit/intrinsics/intrinsics.rs | 30 ++++++++++++- 20 files changed, 397 insertions(+), 39 deletions(-) create mode 100644 tests/expected/uninit/copy/copy_without_padding.expected create mode 100644 tests/expected/uninit/copy/copy_without_padding.rs create mode 100644 tests/expected/uninit/copy/expose_padding_via_copy.expected create mode 100644 tests/expected/uninit/copy/expose_padding_via_copy.rs create mode 100644 tests/expected/uninit/copy/expose_padding_via_copy_convoluted.expected create mode 100644 tests/expected/uninit/copy/expose_padding_via_copy_convoluted.rs create mode 100644 tests/expected/uninit/copy/expose_padding_via_non_byte_copy.expected create mode 100644 tests/expected/uninit/copy/expose_padding_via_non_byte_copy.rs create mode 100644 tests/expected/uninit/copy/non_byte_copy_without_padding.expected create mode 100644 tests/expected/uninit/copy/non_byte_copy_without_padding.rs create mode 100644 tests/expected/uninit/copy/read_after_copy.expected create mode 100644 tests/expected/uninit/copy/read_after_copy.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 5c7194f879d1..0a0d3c786ea9 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -178,6 +178,9 @@ impl<'a> UninitInstrumenter<'a> { | MemoryInitOp::SetRef { .. } => { self.build_set(tcx, body, source, operation, pointee_ty_info, skip_first) } + MemoryInitOp::Copy { .. } => { + self.build_copy(tcx, body, source, operation, pointee_ty_info, skip_first) + } MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => { unreachable!() } @@ -397,6 +400,38 @@ impl<'a> UninitInstrumenter<'a> { }; } + /// Copy memory initialization state from one pointer to the other. + fn build_copy( + &mut self, + tcx: TyCtxt, + body: &mut MutableBody, + source: &mut SourceInstruction, + operation: MemoryInitOp, + pointee_info: PointeeInfo, + skip_first: &mut HashSet, + ) { + let ret_place = Place { + local: body.new_local(Ty::new_tuple(&[]), source.span(body.blocks()), Mutability::Not), + projection: vec![], + }; + let PointeeLayout::Sized { layout } = pointee_info.layout() else { unreachable!() }; + let copy_init_state_instance = resolve_mem_init_fn( + get_mem_init_fn_def(tcx, "KaniCopyInitState", &mut self.mem_init_fn_cache), + layout.len(), + *pointee_info.ty(), + ); + collect_skipped(&operation, body, skip_first); + let position = operation.position(); + let MemoryInitOp::Copy { from, to, count } = operation else { unreachable!() }; + body.insert_call( + ©_init_state_instance, + source, + position, + vec![from, to, count], + ret_place.clone(), + ); + } + fn inject_assert_false( &self, tcx: TyCtxt, diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs index f682f93a261e..1afb151a09b5 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs @@ -88,18 +88,13 @@ impl MirVisitor for CheckUninitVisitor { 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(MemoryInitOp::CheckSliceChunk { - operand: copy.src.clone(), + // The copy is untyped, so we should copy memory initialization state from `src` + // to `dst`. + self.push_target(MemoryInitOp::Copy { + from: copy.src.clone(), + to: copy.dst.clone(), count: copy.count.clone(), }); - // Destination is a *mut T so it gets initialized. - self.push_target(MemoryInitOp::SetSliceChunk { - operand: copy.dst.clone(), - count: copy.count.clone(), - value: true, - position: InsertPosition::After, - }); } StatementKind::Assign(place, rvalue) => { // First check rvalue. @@ -219,29 +214,24 @@ impl MirVisitor for CheckUninitVisitor { }); } Intrinsic::Copy => { - self.push_target(MemoryInitOp::CheckSliceChunk { - operand: args[0].clone(), + // The copy is untyped, so we should copy memory + // initialization state from `src` to `dst`. + self.push_target(MemoryInitOp::Copy { + from: args[0].clone(), + to: args[1].clone(), count: args[2].clone(), }); - self.push_target(MemoryInitOp::SetSliceChunk { - operand: args[1].clone(), - count: args[2].clone(), - value: true, - position: InsertPosition::After, - }); } Intrinsic::VolatileCopyMemory | Intrinsic::VolatileCopyNonOverlappingMemory => { - self.push_target(MemoryInitOp::CheckSliceChunk { - operand: args[1].clone(), + // The copy is untyped, so we should copy initialization state + // from `src` to `dst`. Note that the `dst` comes before `src` + // in this case. + self.push_target(MemoryInitOp::Copy { + from: args[1].clone(), + to: args[0].clone(), count: args[2].clone(), }); - self.push_target(MemoryInitOp::SetSliceChunk { - operand: args[0].clone(), - count: args[2].clone(), - value: true, - position: InsertPosition::After, - }); } Intrinsic::TypedSwap => { self.push_target(MemoryInitOp::Check { diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs b/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs index 3bc5b534a23b..cc5a27e09925 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs @@ -5,7 +5,10 @@ //! character of instrumentation needed. use crate::kani_middle::transform::body::{InsertPosition, MutableBody, SourceInstruction}; -use stable_mir::mir::{Mutability, Operand, Place, Rvalue}; +use stable_mir::{ + mir::{Mutability, Operand, Place, Rvalue}, + ty::RigidTy, +}; use strum_macros::AsRefStr; /// Memory initialization operations: set or get memory initialization state for a given pointer. @@ -33,6 +36,8 @@ pub enum MemoryInitOp { Unsupported { reason: String }, /// Operation that trivially accesses uninitialized memory, results in injecting `assert!(false)`. TriviallyUnsafe { reason: String }, + /// Operation that copies memory initialization state over to another operand. + Copy { from: Operand, to: Operand, count: Operand }, } impl MemoryInitOp { @@ -62,7 +67,22 @@ impl MemoryInitOp { }) } MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => { - unreachable!() + unreachable!("operands do not exist for this operation") + } + MemoryInitOp::Copy { from, to, .. } => { + // It does not matter which operand to return for layout generation, since both of + // them have the same pointee type, so we assert that. + let from_kind = from.ty(body.locals()).unwrap().kind(); + let to_kind = to.ty(body.locals()).unwrap().kind(); + + let RigidTy::RawPtr(from_pointee_ty, _) = from_kind.rigid().unwrap().clone() else { + unreachable!() + }; + let RigidTy::RawPtr(to_pointee_ty, _) = to_kind.rigid().unwrap().clone() else { + unreachable!() + }; + assert!(from_pointee_ty == to_pointee_ty); + from.clone() } } } @@ -70,7 +90,8 @@ impl MemoryInitOp { pub fn expect_count(&self) -> Operand { match self { MemoryInitOp::CheckSliceChunk { count, .. } - | MemoryInitOp::SetSliceChunk { count, .. } => count.clone(), + | MemoryInitOp::SetSliceChunk { count, .. } + | MemoryInitOp::Copy { count, .. } => count.clone(), MemoryInitOp::Check { .. } | MemoryInitOp::Set { .. } | MemoryInitOp::CheckRef { .. } @@ -89,7 +110,8 @@ impl MemoryInitOp { | MemoryInitOp::CheckSliceChunk { .. } | MemoryInitOp::CheckRef { .. } | MemoryInitOp::Unsupported { .. } - | MemoryInitOp::TriviallyUnsafe { .. } => unreachable!(), + | MemoryInitOp::TriviallyUnsafe { .. } + | MemoryInitOp::Copy { .. } => unreachable!(), } } @@ -103,6 +125,7 @@ impl MemoryInitOp { | MemoryInitOp::CheckRef { .. } | MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => InsertPosition::Before, + MemoryInitOp::Copy { .. } => InsertPosition::After, } } } diff --git a/library/kani/src/mem_init.rs b/library/kani/src/mem_init.rs index 88847e9c4f3c..3755fc59a510 100644 --- a/library/kani/src/mem_init.rs +++ b/library/kani/src/mem_init.rs @@ -91,6 +91,33 @@ impl MemoryInitializationState { } } + /// Copy memory initialization state by non-deterministically switching the tracked object and + /// adjusting the tracked offset. + pub fn copy( + &mut self, + from_ptr: *const u8, + to_ptr: *const u8, + num_elts: usize, + ) { + let from_obj = crate::mem::pointer_object(from_ptr); + let from_offset = crate::mem::pointer_offset(from_ptr); + + let to_obj = crate::mem::pointer_object(to_ptr); + let to_offset = crate::mem::pointer_offset(to_ptr); + + if self.tracked_object_id == from_obj + && self.tracked_offset >= from_offset + && self.tracked_offset < from_offset + num_elts * LAYOUT_SIZE + { + let should_reset: bool = crate::any(); + if should_reset { + self.tracked_object_id = to_obj; + self.tracked_offset += to_offset - from_offset; + // Note that this preserves the value. + } + } + } + /// Return currently tracked memory initialization state if `ptr` points to the currently /// tracked object and the tracked offset lies within `LAYOUT_SIZE * num_elts` bytes of `ptr`. /// Return `true` otherwise. @@ -281,3 +308,16 @@ fn set_str_ptr_initialized( MEM_INIT_STATE.set_slice(ptr as *const u8, layout, num_elts, value); } } + +/// Copy initialization state of `size_of:: * num_elts` bytes from one pointer to the other. +#[rustc_diagnostic_item = "KaniCopyInitState"] +fn copy_init_state(from: *const T, to: *const T, num_elts: usize) { + if LAYOUT_SIZE == 0 { + return; + } + let (from_ptr, _) = from.to_raw_parts(); + let (to_ptr, _) = to.to_raw_parts(); + unsafe { + MEM_INIT_STATE.copy::(from_ptr as *const u8, to_ptr as *const u8, num_elts); + } +} diff --git a/tests/expected/uninit/copy/copy_without_padding.expected b/tests/expected/uninit/copy/copy_without_padding.expected new file mode 100644 index 000000000000..01a90d50b557 --- /dev/null +++ b/tests/expected/uninit/copy/copy_without_padding.expected @@ -0,0 +1 @@ +Complete - 1 successfully verified harnesses, 0 failures, 1 total. diff --git a/tests/expected/uninit/copy/copy_without_padding.rs b/tests/expected/uninit/copy/copy_without_padding.rs new file mode 100644 index 000000000000..16df1dd5d2d0 --- /dev/null +++ b/tests/expected/uninit/copy/copy_without_padding.rs @@ -0,0 +1,23 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z uninit-checks + +#[repr(C)] +#[derive(kani::Arbitrary)] +struct S(u32, u8); // 5 bytes of data + 3 bytes of padding. + +#[kani::proof] +/// This checks that reading copied initialized bytes verifies correctly. +unsafe fn copy_without_padding() { + let from: S = kani::any(); + let mut to: u64 = kani::any(); + + let from_ptr = &from as *const S; + let to_ptr = &mut to as *mut u64; + + // This should not cause UB since `copy` is untyped. + std::ptr::copy(from_ptr as *const u8, to_ptr as *mut u8, std::mem::size_of::()); + + // Since the previous copy only copied 4 bytes, no padding was copied, so no padding is read. + let data: u64 = std::ptr::read(to_ptr); +} diff --git a/tests/expected/uninit/copy/expose_padding_via_copy.expected b/tests/expected/uninit/copy/expose_padding_via_copy.expected new file mode 100644 index 000000000000..83d8badc8bf5 --- /dev/null +++ b/tests/expected/uninit/copy/expose_padding_via_copy.expected @@ -0,0 +1,11 @@ +std::ptr::read::.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*const u64`"\ + +std::ptr::read::.assertion.2\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u64`"\ + +Summary: +Verification failed for - expose_padding_via_copy +Complete - 0 successfully verified harnesses, 1 failures, 1 total. diff --git a/tests/expected/uninit/copy/expose_padding_via_copy.rs b/tests/expected/uninit/copy/expose_padding_via_copy.rs new file mode 100644 index 000000000000..8adb772037ca --- /dev/null +++ b/tests/expected/uninit/copy/expose_padding_via_copy.rs @@ -0,0 +1,23 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z uninit-checks + +#[repr(C)] +#[derive(kani::Arbitrary)] +struct S(u32, u8); // 5 bytes of data + 3 bytes of padding. + +/// This checks that reading copied uninitialized bytes fails an assertion. +#[kani::proof] +unsafe fn expose_padding_via_copy() { + let from: S = kani::any(); + let mut to: u64 = kani::any(); + + let from_ptr = &from as *const S; + let to_ptr = &mut to as *mut u64; + + // This should not cause UB since `copy` is untyped. + std::ptr::copy(from_ptr as *const u8, to_ptr as *mut u8, std::mem::size_of::()); + + // This reads uninitialized bytes, which is UB. + let padding: u64 = std::ptr::read(to_ptr); +} diff --git a/tests/expected/uninit/copy/expose_padding_via_copy_convoluted.expected b/tests/expected/uninit/copy/expose_padding_via_copy_convoluted.expected new file mode 100644 index 000000000000..cbe7ec97cb7b --- /dev/null +++ b/tests/expected/uninit/copy/expose_padding_via_copy_convoluted.expected @@ -0,0 +1,11 @@ +std::ptr::read::.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*const u64`"\ + +std::ptr::read::.assertion.2\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u64`"\ + +Summary: +Verification failed for - expose_padding_via_copy_convoluted +Complete - 0 successfully verified harnesses, 1 failures, 1 total. diff --git a/tests/expected/uninit/copy/expose_padding_via_copy_convoluted.rs b/tests/expected/uninit/copy/expose_padding_via_copy_convoluted.rs new file mode 100644 index 000000000000..5feadace245d --- /dev/null +++ b/tests/expected/uninit/copy/expose_padding_via_copy_convoluted.rs @@ -0,0 +1,42 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z uninit-checks + +#[repr(C)] +#[derive(kani::Arbitrary)] +struct S(u32, u8); // 5 bytes of data + 3 bytes of padding. + +/// This checks that reading copied uninitialized bytes fails an assertion even if pointer are +/// passed around different functions. +#[kani::proof] +unsafe fn expose_padding_via_copy_convoluted() { + unsafe fn copy_and_read_helper(from_ptr: *const S, to_ptr: *mut u64) -> u64 { + // This should not cause UB since `copy` is untyped. + std::ptr::copy(from_ptr as *const u8, to_ptr as *mut u8, std::mem::size_of::()); + // This reads uninitialized bytes, which is UB. + let padding: u64 = std::ptr::read(to_ptr); + padding + } + + unsafe fn partial_copy_and_read_helper(from_ptr: *const S, to_ptr: *mut u64) -> u32 { + // This should not cause UB since `copy` is untyped. + std::ptr::copy(from_ptr as *const u8, to_ptr as *mut u8, std::mem::size_of::()); + // This does not read uninitialized bytes. + let not_padding: u32 = std::ptr::read(to_ptr as *mut u32); + not_padding + } + + let flag: bool = kani::any(); + + let from: S = kani::any(); + let mut to: u64 = kani::any(); + + let from_ptr = &from as *const S; + let to_ptr = &mut to as *mut u64; + + if flag { + copy_and_read_helper(from_ptr, to_ptr); + } else { + partial_copy_and_read_helper(from_ptr, to_ptr); + } +} diff --git a/tests/expected/uninit/copy/expose_padding_via_non_byte_copy.expected b/tests/expected/uninit/copy/expose_padding_via_non_byte_copy.expected new file mode 100644 index 000000000000..3fc86e45a46e --- /dev/null +++ b/tests/expected/uninit/copy/expose_padding_via_non_byte_copy.expected @@ -0,0 +1,11 @@ +std::ptr::read::.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*const u64`"\ + +std::ptr::read::.assertion.2\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u64`"\ + +Summary: +Verification failed for - expose_padding_via_non_byte_copy +Complete - 0 successfully verified harnesses, 1 failures, 1 total. diff --git a/tests/expected/uninit/copy/expose_padding_via_non_byte_copy.rs b/tests/expected/uninit/copy/expose_padding_via_non_byte_copy.rs new file mode 100644 index 000000000000..685239b267b1 --- /dev/null +++ b/tests/expected/uninit/copy/expose_padding_via_non_byte_copy.rs @@ -0,0 +1,23 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z uninit-checks + +#[repr(C)] +#[derive(kani::Arbitrary)] +struct S(u32, u8); // 5 bytes of data + 3 bytes of padding. + +/// This checks that reading copied uninitialized bytes after a multi-byte copy fails an assertion. +#[kani::proof] +unsafe fn expose_padding_via_non_byte_copy() { + let from: S = kani::any(); + let mut to: u64 = kani::any(); + + let from_ptr = &from as *const S; + let to_ptr = &mut to as *mut u64; + + // This should not cause UB since `copy` is untyped. + std::ptr::copy(from_ptr as *const u64, to_ptr as *mut u64, 1); + + // This reads uninitialized bytes, which is UB. + let padding: u64 = std::ptr::read(to_ptr); +} diff --git a/tests/expected/uninit/copy/non_byte_copy_without_padding.expected b/tests/expected/uninit/copy/non_byte_copy_without_padding.expected new file mode 100644 index 000000000000..01a90d50b557 --- /dev/null +++ b/tests/expected/uninit/copy/non_byte_copy_without_padding.expected @@ -0,0 +1 @@ +Complete - 1 successfully verified harnesses, 0 failures, 1 total. diff --git a/tests/expected/uninit/copy/non_byte_copy_without_padding.rs b/tests/expected/uninit/copy/non_byte_copy_without_padding.rs new file mode 100644 index 000000000000..6f3b380cd81f --- /dev/null +++ b/tests/expected/uninit/copy/non_byte_copy_without_padding.rs @@ -0,0 +1,23 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z uninit-checks + +#[repr(C)] +#[derive(kani::Arbitrary)] +struct S(u32, u8); // 5 bytes of data + 3 bytes of padding. + +#[kani::proof] +/// This checks that reading copied initialized bytes after a multi-byte copy verifies correctly. +unsafe fn non_byte_copy_without_padding() { + let from: S = kani::any(); + let mut to: u64 = kani::any(); + + let from_ptr = &from as *const S; + let to_ptr = &mut to as *mut u64; + + // This should not cause UB since `copy` is untyped. + std::ptr::copy(from_ptr as *const u32, to_ptr as *mut u32, 1); + + // Since the previous copy only copied 4 bytes, no padding was copied, so no padding is read. + let data: u64 = std::ptr::read(to_ptr); +} diff --git a/tests/expected/uninit/copy/read_after_copy.expected b/tests/expected/uninit/copy/read_after_copy.expected new file mode 100644 index 000000000000..56a3460a1d7b --- /dev/null +++ b/tests/expected/uninit/copy/read_after_copy.expected @@ -0,0 +1,11 @@ +std::ptr::read::.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*const u64`"\ + +std::ptr::read::.assertion.2\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u64`"\ + +Summary: +Verification failed for - read_after_copy +Complete - 0 successfully verified harnesses, 1 failures, 1 total. diff --git a/tests/expected/uninit/copy/read_after_copy.rs b/tests/expected/uninit/copy/read_after_copy.rs new file mode 100644 index 000000000000..742b74099acc --- /dev/null +++ b/tests/expected/uninit/copy/read_after_copy.rs @@ -0,0 +1,23 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z uninit-checks + +#[repr(C)] +#[derive(kani::Arbitrary)] +struct S(u32, u8); // 5 bytes of data + 3 bytes of padding. + +/// This checks that reading uninitialized bytes fails an assertion even after copy. +#[kani::proof] +unsafe fn read_after_copy() { + let from: S = kani::any(); + let mut to: u64 = kani::any(); + + let from_ptr = &from as *const S; + let to_ptr = &mut to as *mut u64; + + // This should not cause UB since `copy` is untyped. + std::ptr::copy(from_ptr as *const u8, to_ptr as *mut u8, std::mem::size_of::()); + + // Reading padding from the previous place should be UB even after copy. + let data: u64 = std::ptr::read(from_ptr as *const u64); +} diff --git a/tests/expected/uninit/delayed-ub/delayed-ub.rs b/tests/expected/uninit/delayed-ub/delayed-ub.rs index feee4bcd161f..d7f258c27ba5 100644 --- a/tests/expected/uninit/delayed-ub/delayed-ub.rs +++ b/tests/expected/uninit/delayed-ub/delayed-ub.rs @@ -124,6 +124,24 @@ fn delayed_ub_copy() { } } +/// Delayed UB via multiple mutable pointers write using `copy_nonoverlapping` and `copy` under the +/// hood. +#[kani::proof] +fn delayed_ub_double_copy() { + unsafe { + let mut value: u128 = 0; + let ptr = &mut value as *mut _ as *mut (u8, u32, u64); + // Use `copy_nonoverlapping` in an attempt to remove the taint. + std::ptr::write(ptr, (4, 4, 4)); + // Instead of assigning the value into a delayed UB place, copy it from another delayed UB + // place. + let mut value_2: u128 = 0; + let ptr_2 = &mut value_2 as *mut _ as *mut (u8, u32, u64); + std::ptr::copy(ptr, ptr_2, 1); // This should not trigger UB since the copy is untyped. + assert!(value_2 > 0); // UB: This reads a padding value! + } +} + struct S { u: U, } @@ -164,3 +182,16 @@ fn delayed_ub_slices() { let arr_copy = arr; // UB: This reads a padding value inside the array! } } + +/// Delayed UB via mutable pointer copy, which should be the only delayed UB trigger in this case. +#[kani::proof] +fn delayed_ub_trigger_copy() { + unsafe { + let mut value: u128 = 0; + let ptr = &mut value as *mut _ as *mut u8; // This cast should not be a delayed UB source. + let mut value_different_padding: (u8, u32, u64) = (4, 4, 4); + let ptr_different_padding = &mut value_different_padding as *mut _ as *mut u8; + std::ptr::copy(ptr_different_padding, ptr, std::mem::size_of::()); // This is a delayed UB source. + assert!(value > 0); // UB: This reads a padding value! + } +} diff --git a/tests/expected/uninit/delayed-ub/expected b/tests/expected/uninit/delayed-ub/expected index 46b6ababe85d..dc0411bdba9c 100644 --- a/tests/expected/uninit/delayed-ub/expected +++ b/tests/expected/uninit/delayed-ub/expected @@ -1,3 +1,7 @@ +delayed_ub_trigger_copy.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`"\ + delayed_ub_slices.assertion.4\ - Status: FAILURE\ - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `[u128; 4]`" @@ -6,6 +10,10 @@ delayed_ub_structs.assertion.2\ - Status: FAILURE\ - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `U`" +delayed_ub_double_copy.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`"\ + delayed_ub_copy.assertion.1\ - Status: FAILURE\ - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`" @@ -35,8 +43,10 @@ delayed_ub.assertion.2\ - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`" Summary: +Verification failed for - delayed_ub_trigger_copy Verification failed for - delayed_ub_slices Verification failed for - delayed_ub_structs +Verification failed for - delayed_ub_double_copy Verification failed for - delayed_ub_copy Verification failed for - delayed_ub_closure_capture_laundered Verification failed for - delayed_ub_closure_laundered @@ -44,4 +54,4 @@ Verification failed for - delayed_ub_laundered Verification failed for - delayed_ub_static Verification failed for - delayed_ub_transmute Verification failed for - delayed_ub -Complete - 0 successfully verified harnesses, 9 failures, 9 total. +Complete - 0 successfully verified harnesses, 11 failures, 11 total. diff --git a/tests/expected/uninit/intrinsics/expected b/tests/expected/uninit/intrinsics/expected index cf34d305608b..e428aa605887 100644 --- a/tests/expected/uninit/intrinsics/expected +++ b/tests/expected/uninit/intrinsics/expected @@ -34,19 +34,19 @@ check_compare_bytes.assertion.2\ - Status: FAILURE\ - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*const u8`" -std::intrinsics::copy::.assertion.1\ +std::ptr::read::.assertion.1\ - Status: FAILURE\ - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*const u8`" -std::intrinsics::copy_nonoverlapping::.assertion.1\ +std::ptr::read::.assertion.2\ - Status: FAILURE\ - - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*const u8`" + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u8`" Summary: Verification failed for - check_typed_swap_safe Verification failed for - check_typed_swap Verification failed for - check_volatile_load Verification failed for - check_compare_bytes -Verification failed for - check_copy -Verification failed for - check_copy_nonoverlapping -Complete - 5 successfully verified harnesses, 6 failures, 11 total. +Verification failed for - check_copy_read +Verification failed for - check_copy_nonoverlapping_read +Complete - 7 successfully verified harnesses, 6 failures, 13 total. diff --git a/tests/expected/uninit/intrinsics/intrinsics.rs b/tests/expected/uninit/intrinsics/intrinsics.rs index aa8a89b7b959..b023853b2fbc 100644 --- a/tests/expected/uninit/intrinsics/intrinsics.rs +++ b/tests/expected/uninit/intrinsics/intrinsics.rs @@ -14,7 +14,20 @@ fn check_copy_nonoverlapping() { let layout = Layout::from_size_align(16, 8).unwrap(); let src: *mut u8 = alloc(layout); let dst: *mut u8 = alloc(layout); - copy_nonoverlapping(src as *const u8, dst, 2); // ~ERROR: Accessing `src` here, which is uninitialized. + // This does not fail, since `copy_nonoverlapping` is untyped. + copy_nonoverlapping(src as *const u8, dst, 2); + } +} + +#[kani::proof] +fn check_copy_nonoverlapping_read() { + unsafe { + let layout = Layout::from_size_align(16, 8).unwrap(); + let src: *mut u8 = alloc(layout); + let dst: *mut u8 = alloc_zeroed(layout); + copy_nonoverlapping(src as *const u8, dst, 2); + // ~ERROR: Accessing `dst` here, which became uninitialized after copy. + let uninit = std::ptr::read(dst); } } @@ -35,7 +48,20 @@ fn check_copy() { let layout = Layout::from_size_align(16, 8).unwrap(); let src: *mut u8 = alloc(layout); let dst: *mut u8 = alloc(layout); - copy(src as *const u8, dst, 2); // ~ERROR: Accessing `src` here, which is uninitialized. + // This does not fail, since `copy` is untyped. + copy(src as *const u8, dst, 2); + } +} + +#[kani::proof] +fn check_copy_read() { + unsafe { + let layout = Layout::from_size_align(16, 8).unwrap(); + let src: *mut u8 = alloc(layout); + let dst: *mut u8 = alloc_zeroed(layout); + copy(src as *const u8, dst, 2); + // ~ERROR: Accessing `dst` here, which became uninitialized after copy. + let uninit = std::ptr::read(dst); } } From a5d4406642d2dde335a84bcd17c27622c403623c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 11:25:50 -0700 Subject: [PATCH 08/40] Bump tests/perf/s2n-quic from `ab9723a` to `80b93a7` (#3453) Bumps [tests/perf/s2n-quic](https://github.com/aws/s2n-quic) from `ab9723a` to `80b93a7`.
Commits

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/perf/s2n-quic | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/perf/s2n-quic b/tests/perf/s2n-quic index ab9723a772f0..80b93a7f1d18 160000 --- a/tests/perf/s2n-quic +++ b/tests/perf/s2n-quic @@ -1 +1 @@ -Subproject commit ab9723a772f03a9793c9863e73c9a48fab3c5235 +Subproject commit 80b93a7f1d187fef005c8c896ab99278b6865dbe From 621519aac197692c3ff92337516d8d1a8fa52dba Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 19 Aug 2024 16:01:33 -0700 Subject: [PATCH 09/40] Make points-to analysis handle all intrinsics explicitly (#3452) Initially, points-to analysis tried to determine the body of an intrinsic (if it was available) to avoid enumerating them all. However, it turned out this logic was faulty, and the analysis attempted to query the body for intrinsics that didn't have it and ICEd. I added a couple of missing intrinsics, which had a side benefit of removing some duplicate assertion failures. Resolves #3447 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../points_to/points_to_analysis.rs | 217 +++++++++--------- tests/expected/uninit/intrinsics/expected | 8 - 2 files changed, 104 insertions(+), 121 deletions(-) diff --git a/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs b/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs index 15593549b690..eff7dd1fc486 100644 --- a/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs +++ b/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs @@ -203,119 +203,108 @@ impl<'a, 'tcx> Analysis<'tcx> for PointsToAnalysis<'a, 'tcx> { }; match instance.def { // Intrinsics could introduce aliasing edges we care about, so need to handle them. - InstanceKind::Intrinsic(def_id) => { - // Check if the intrinsic has a body we can analyze. - if self.tcx.is_mir_available(def_id) { - self.apply_regular_call_effect(state, instance, args, destination); - } else { - // Check all of the other intrinsics. - match Intrinsic::from_instance(&rustc_internal::stable(instance)) { - intrinsic if is_identity_aliasing_intrinsic(intrinsic.clone()) => { - // Treat the intrinsic as an aggregate, taking a union of all of the - // arguments' aliases. - let destination_set = - state.resolve_place(*destination, self.instance); - let operands_set = args - .into_iter() - .flat_map(|operand| { - self.successors_for_operand(state, operand.node.clone()) - }) - .collect(); - state.extend(&destination_set, &operands_set); - } - // All `atomic_cxchg` intrinsics take `dst, old, src` as arguments. - // This is equivalent to `destination = *dst; *dst = src`. - Intrinsic::AtomicCxchg(_) | Intrinsic::AtomicCxchgWeak(_) => { - let src_set = - self.successors_for_operand(state, args[2].node.clone()); - let dst_set = - self.successors_for_deref(state, args[0].node.clone()); - let destination_set = - state.resolve_place(*destination, self.instance); - state.extend(&destination_set, &state.successors(&dst_set)); - state.extend(&dst_set, &src_set); - } - // All `atomic_load` intrinsics take `src` as an argument. - // This is equivalent to `destination = *src`. - Intrinsic::AtomicLoad(_) => { - let src_set = - self.successors_for_deref(state, args[0].node.clone()); - let destination_set = - state.resolve_place(*destination, self.instance); - state.extend(&destination_set, &state.successors(&src_set)); - } - // All `atomic_store` intrinsics take `dst, val` as arguments. - // This is equivalent to `*dst = val`. - Intrinsic::AtomicStore(_) => { - let dst_set = - self.successors_for_deref(state, args[0].node.clone()); - let val_set = - self.successors_for_operand(state, args[1].node.clone()); - state.extend(&dst_set, &val_set); - } - // All other `atomic` intrinsics take `dst, src` as arguments. - // This is equivalent to `destination = *dst; *dst = src`. - Intrinsic::AtomicAnd(_) - | Intrinsic::AtomicMax(_) - | Intrinsic::AtomicMin(_) - | Intrinsic::AtomicNand(_) - | Intrinsic::AtomicOr(_) - | Intrinsic::AtomicUmax(_) - | Intrinsic::AtomicUmin(_) - | Intrinsic::AtomicXadd(_) - | Intrinsic::AtomicXchg(_) - | Intrinsic::AtomicXor(_) - | Intrinsic::AtomicXsub(_) => { - let src_set = - self.successors_for_operand(state, args[1].node.clone()); - let dst_set = - self.successors_for_deref(state, args[0].node.clone()); - let destination_set = - state.resolve_place(*destination, self.instance); - state.extend(&destination_set, &state.successors(&dst_set)); - state.extend(&dst_set, &src_set); - } - // Similar to `copy_nonoverlapping`, argument order is `src`, `dst`, `count`. - Intrinsic::Copy => { - self.apply_copy_effect( - state, - args[0].node.clone(), - args[1].node.clone(), - ); - } - // Similar to `copy_nonoverlapping`, argument order is `dst`, `src`, `count`. - Intrinsic::VolatileCopyMemory - | Intrinsic::VolatileCopyNonOverlappingMemory => { - self.apply_copy_effect( - state, - args[1].node.clone(), - args[0].node.clone(), - ); - } - // Semantically equivalent to dest = *a - Intrinsic::VolatileLoad | Intrinsic::UnalignedVolatileLoad => { - // Destination of the return value. - let lvalue_set = state.resolve_place(*destination, self.instance); - let rvalue_set = - self.successors_for_deref(state, args[0].node.clone()); - state.extend(&lvalue_set, &state.successors(&rvalue_set)); - } - // Semantically equivalent *a = b. - Intrinsic::VolatileStore => { - let lvalue_set = - self.successors_for_deref(state, args[0].node.clone()); - let rvalue_set = - self.successors_for_operand(state, args[1].node.clone()); - state.extend(&lvalue_set, &rvalue_set); - } - Intrinsic::Unimplemented { .. } => { - // This will be taken care of at the codegen level. - } - intrinsic => { - unimplemented!( - "Kani does not support reasoning about aliasing in presence of intrinsic `{intrinsic:?}`. For more information about the state of uninitialized memory checks implementation, see: https://github.com/model-checking/kani/issues/3300." - ); - } + InstanceKind::Intrinsic(_) => { + match Intrinsic::from_instance(&rustc_internal::stable(instance)) { + intrinsic if is_identity_aliasing_intrinsic(intrinsic.clone()) => { + // Treat the intrinsic as an aggregate, taking a union of all of the + // arguments' aliases. + let destination_set = state.resolve_place(*destination, self.instance); + let operands_set = args + .into_iter() + .flat_map(|operand| { + self.successors_for_operand(state, operand.node.clone()) + }) + .collect(); + state.extend(&destination_set, &operands_set); + } + // All `atomic_cxchg` intrinsics take `dst, old, src` as arguments. + // This is equivalent to `destination = *dst; *dst = src`. + Intrinsic::AtomicCxchg(_) | Intrinsic::AtomicCxchgWeak(_) => { + let src_set = self.successors_for_operand(state, args[2].node.clone()); + let dst_set = self.successors_for_deref(state, args[0].node.clone()); + let destination_set = state.resolve_place(*destination, self.instance); + state.extend(&destination_set, &state.successors(&dst_set)); + state.extend(&dst_set, &src_set); + } + // All `atomic_load` intrinsics take `src` as an argument. + // This is equivalent to `destination = *src`. + Intrinsic::AtomicLoad(_) => { + let src_set = self.successors_for_deref(state, args[0].node.clone()); + let destination_set = state.resolve_place(*destination, self.instance); + state.extend(&destination_set, &state.successors(&src_set)); + } + // All `atomic_store` intrinsics take `dst, val` as arguments. + // This is equivalent to `*dst = val`. + Intrinsic::AtomicStore(_) => { + let dst_set = self.successors_for_deref(state, args[0].node.clone()); + let val_set = self.successors_for_operand(state, args[1].node.clone()); + state.extend(&dst_set, &val_set); + } + // All other `atomic` intrinsics take `dst, src` as arguments. + // This is equivalent to `destination = *dst; *dst = src`. + Intrinsic::AtomicAnd(_) + | Intrinsic::AtomicMax(_) + | Intrinsic::AtomicMin(_) + | Intrinsic::AtomicNand(_) + | Intrinsic::AtomicOr(_) + | Intrinsic::AtomicUmax(_) + | Intrinsic::AtomicUmin(_) + | Intrinsic::AtomicXadd(_) + | Intrinsic::AtomicXchg(_) + | Intrinsic::AtomicXor(_) + | Intrinsic::AtomicXsub(_) => { + let src_set = self.successors_for_operand(state, args[1].node.clone()); + let dst_set = self.successors_for_deref(state, args[0].node.clone()); + let destination_set = state.resolve_place(*destination, self.instance); + state.extend(&destination_set, &state.successors(&dst_set)); + state.extend(&dst_set, &src_set); + } + // Similar to `copy_nonoverlapping`, argument order is `src`, `dst`, `count`. + Intrinsic::Copy => { + self.apply_copy_effect( + state, + args[0].node.clone(), + args[1].node.clone(), + ); + } + Intrinsic::TypedSwap => { + // Extend from x_set to y_set and vice-versa so that both x and y alias + // to a union of places each of them alias to. + let x_set = self.successors_for_deref(state, args[0].node.clone()); + let y_set = self.successors_for_deref(state, args[1].node.clone()); + state.extend(&x_set, &state.successors(&y_set)); + state.extend(&y_set, &state.successors(&x_set)); + } + // Similar to `copy_nonoverlapping`, argument order is `dst`, `src`, `count`. + Intrinsic::VolatileCopyMemory + | Intrinsic::VolatileCopyNonOverlappingMemory => { + self.apply_copy_effect( + state, + args[1].node.clone(), + args[0].node.clone(), + ); + } + // Semantically equivalent to dest = *a + Intrinsic::VolatileLoad | Intrinsic::UnalignedVolatileLoad => { + // Destination of the return value. + let lvalue_set = state.resolve_place(*destination, self.instance); + let rvalue_set = self.successors_for_deref(state, args[0].node.clone()); + state.extend(&lvalue_set, &state.successors(&rvalue_set)); + } + // Semantically equivalent *a = b. + Intrinsic::VolatileStore => { + let lvalue_set = self.successors_for_deref(state, args[0].node.clone()); + let rvalue_set = + self.successors_for_operand(state, args[1].node.clone()); + state.extend(&lvalue_set, &rvalue_set); + } + Intrinsic::Unimplemented { .. } => { + // This will be taken care of at the codegen level. + } + intrinsic => { + unimplemented!( + "Kani does not support reasoning about aliasing in presence of intrinsic `{intrinsic:?}`. For more information about the state of uninitialized memory checks implementation, see: https://github.com/model-checking/kani/issues/3300." + ); } } } @@ -681,6 +670,7 @@ fn is_identity_aliasing_intrinsic(intrinsic: Intrinsic) -> bool { | Intrinsic::PtrOffsetFrom | Intrinsic::PtrOffsetFromUnsigned | Intrinsic::RawEq + | Intrinsic::RetagBoxToRaw | Intrinsic::RintF32 | Intrinsic::RintF64 | Intrinsic::RotateLeft @@ -695,6 +685,7 @@ fn is_identity_aliasing_intrinsic(intrinsic: Intrinsic) -> bool { | Intrinsic::SqrtF32 | Intrinsic::SqrtF64 | Intrinsic::SubWithOverflow + | Intrinsic::Transmute | Intrinsic::TruncF32 | Intrinsic::TruncF64 | Intrinsic::TypeId diff --git a/tests/expected/uninit/intrinsics/expected b/tests/expected/uninit/intrinsics/expected index e428aa605887..33392337c30b 100644 --- a/tests/expected/uninit/intrinsics/expected +++ b/tests/expected/uninit/intrinsics/expected @@ -2,18 +2,10 @@ std::ptr::read::>.assertion.1\ - Status: FAILURE\ - Description: "Kani currently doesn't support checking memory initialization for pointers to `std::mem::MaybeUninit." -std::ptr::read::>.assertion.2\ - - Status: FAILURE\ - - Description: "Kani currently doesn't support checking memory initialization for pointers to `std::mem::MaybeUninit." - std::ptr::write::>.assertion.1\ - Status: FAILURE\ - Description: "Kani currently doesn't support checking memory initialization for pointers to `std::mem::MaybeUninit." -std::ptr::write::>.assertion.2\ - - Status: FAILURE\ - - Description: "Kani currently doesn't support checking memory initialization for pointers to `std::mem::MaybeUninit." - check_typed_swap.assertion.1\ - Status: FAILURE\ - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*mut u8`" From 17dc239487c0f6277c3995bc9bf1fb560682f1b5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 20 Aug 2024 20:50:33 +0200 Subject: [PATCH 10/40] Automatic cargo update to 2024-08-19 (#3450) Dependency upgrade resulting from `cargo update`. Co-authored-by: tautschnig <1144736+tautschnig@users.noreply.github.com> --- Cargo.lock | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e8e743d5e56..931ae0bfa4fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,9 +108,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "camino" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" dependencies = [ "serde", ] @@ -146,9 +146,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.15" +version = "4.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" dependencies = [ "clap_builder", "clap_derive", @@ -175,7 +175,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -401,9 +401,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" dependencies = [ "equivalent", "hashbrown", @@ -510,7 +510,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -532,9 +532,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.157" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "374af5f94e54fa97cf75e945cce8a6b201e88a1a07e688b47dfd2a59c66dbd86" [[package]] name = "linear-map" @@ -950,29 +950,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.206" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b3e4cd94123dd520a128bcd11e34d9e9e423e7e3e50425cb1b4b1e3549d0284" +checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.206" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabfb6138d2383ea8208cf98ccf69cdfb1aff4088460681d84189aa259762f97" +checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] name = "serde_json" -version = "1.0.124" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" +checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" dependencies = [ "itoa", "memchr", @@ -1072,7 +1072,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -1087,9 +1087,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.74" +version = "2.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" dependencies = [ "proc-macro2", "quote", @@ -1126,7 +1126,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -1192,7 +1192,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -1311,9 +1311,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "which" -version = "6.0.2" +version = "6.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d9c5ed668ee1f17edb3b627225343d210006a90bb1e3745ce1f30b1fb115075" +checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" dependencies = [ "either", "home", @@ -1467,5 +1467,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] From d2141e4c5f596833d25cc89c082beec6b6889bb0 Mon Sep 17 00:00:00 2001 From: Qinheping Hu Date: Tue, 20 Aug 2024 16:18:55 -0500 Subject: [PATCH 11/40] Add loop scanner to tool-scanner (#3443) This extend #3120 with loop scanner that counts the number of loops in each function and the number of functions that contain loops. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- Cargo.lock | 30 ++++++++ .../tool-scanner/scanner-test.expected | 6 +- tests/script-based-pre/tool-scanner/test.rs | 29 ++++++- tools/scanner/Cargo.toml | 2 + tools/scanner/src/analysis.rs | 77 +++++++++++++++++-- 5 files changed, 132 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 931ae0bfa4fa..4f371c8356e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check", "zerocopy", @@ -349,6 +350,12 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "getopts" version = "0.2.21" @@ -375,6 +382,17 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "graph-cycles" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6ad932c6dd3cfaf16b66754a42f87bbeefd591530c4b6a8334270a7df3e853" +dependencies = [ + "ahash", + "petgraph", + "thiserror", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -723,6 +741,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -928,6 +956,8 @@ name = "scanner" version = "0.0.0" dependencies = [ "csv", + "graph-cycles", + "petgraph", "serde", "strum", "strum_macros", diff --git a/tests/script-based-pre/tool-scanner/scanner-test.expected b/tests/script-based-pre/tool-scanner/scanner-test.expected index c8f9af0ef1b7..f55a883434ee 100644 --- a/tests/script-based-pre/tool-scanner/scanner-test.expected +++ b/tests/script-based-pre/tool-scanner/scanner-test.expected @@ -1,6 +1,6 @@ -2 test_scan_fn_loops.csv -16 test_scan_functions.csv +5 test_scan_fn_loops.csv +19 test_scan_functions.csv 5 test_scan_input_tys.csv -14 test_scan_overall.csv +16 test_scan_overall.csv 3 test_scan_recursion.csv 5 test_scan_unsafe_ops.csv diff --git a/tests/script-based-pre/tool-scanner/test.rs b/tests/script-based-pre/tool-scanner/test.rs index 24b346e535b5..f6a141f2a708 100644 --- a/tests/script-based-pre/tool-scanner/test.rs +++ b/tests/script-based-pre/tool-scanner/test.rs @@ -33,7 +33,34 @@ pub fn with_iterator(input: &[usize]) -> usize { .iter() .copied() .find(|e| *e == 0) - .unwrap_or_else(|| input.iter().fold(0, |acc, i| acc + 1)) + .unwrap_or_else(|| input.iter().fold(0, |acc, _| acc + 1)) +} + +pub fn with_for_loop(input: &[usize]) -> usize { + let mut res = 0; + for _ in input { + res += 1; + } + res +} + +pub fn with_while_loop(input: &[usize]) -> usize { + let mut res = 0; + while res < input.len() { + res += 1; + } + return res; +} + +pub fn with_loop_loop(input: &[usize]) -> usize { + let mut res = 0; + loop { + if res == input.len() { + break; + } + res += 1; + } + res } static mut COUNTER: Option = Some(0); diff --git a/tools/scanner/Cargo.toml b/tools/scanner/Cargo.toml index edbd330bea47..f27e9e06c72c 100644 --- a/tools/scanner/Cargo.toml +++ b/tools/scanner/Cargo.toml @@ -15,6 +15,8 @@ csv = "1.3" serde = {version = "1", features = ["derive"]} strum = "0.26" strum_macros = "0.26" +petgraph = "0.6.5" +graph-cycles = "0.1.0" [package.metadata.rust-analyzer] # This crate uses rustc crates. diff --git a/tools/scanner/src/analysis.rs b/tools/scanner/src/analysis.rs index c376af9662f8..b32627939bf5 100644 --- a/tools/scanner/src/analysis.rs +++ b/tools/scanner/src/analysis.rs @@ -5,11 +5,13 @@ use crate::info; use csv::WriterBuilder; +use graph_cycles::Cycles; +use petgraph::graph::Graph; use serde::{ser::SerializeStruct, Serialize, Serializer}; use stable_mir::mir::mono::Instance; use stable_mir::mir::visit::{Location, PlaceContext, PlaceRef}; use stable_mir::mir::{ - Body, MirVisitor, Mutability, ProjectionElem, Safety, Terminator, TerminatorKind, + BasicBlock, Body, MirVisitor, Mutability, ProjectionElem, Safety, Terminator, TerminatorKind, }; use stable_mir::ty::{AdtDef, AdtKind, FnDef, GenericArgs, MirConst, RigidTy, Ty, TyKind}; use stable_mir::visitor::{Visitable, Visitor}; @@ -159,6 +161,7 @@ impl OverallStats { pub fn loops(&mut self, filename: PathBuf) { let all_items = stable_mir::all_local_items(); let (has_loops, no_loops) = all_items + .clone() .into_iter() .filter_map(|item| { let kind = item.ty().kind(); @@ -168,9 +171,37 @@ impl OverallStats { Some(FnLoops::new(item.name()).collect(&item.body())) }) .partition::, _>(|props| props.has_loops()); - self.counters - .extend_from_slice(&[("has_loops", has_loops.len()), ("no_loops", no_loops.len())]); - dump_csv(filename, &has_loops); + + let (has_iterators, no_iterators) = all_items + .clone() + .into_iter() + .filter_map(|item| { + let kind = item.ty().kind(); + if !kind.is_fn() { + return None; + }; + Some(FnLoops::new(item.name()).collect(&item.body())) + }) + .partition::, _>(|props| props.has_iterators()); + + let (has_either, _) = all_items + .into_iter() + .filter_map(|item| { + let kind = item.ty().kind(); + if !kind.is_fn() { + return None; + }; + Some(FnLoops::new(item.name()).collect(&item.body())) + }) + .partition::, _>(|props| props.has_iterators() || props.has_loops()); + + self.counters.extend_from_slice(&[ + ("has_loops", has_loops.len()), + ("no_loops", no_loops.len()), + ("has_iterators", has_iterators.len()), + ("no_iterators", no_iterators.len()), + ]); + dump_csv(filename, &has_either); } /// Create a callgraph for this crate and try to find recursive calls. @@ -436,21 +467,26 @@ impl<'a> MirVisitor for BodyVisitor<'a> { fn_props! { struct FnLoops { iterators, - nested_loops, - /// TODO: Collect loops. loops, + // TODO: Collect nested loops. + nested_loops, } } impl FnLoops { pub fn collect(self, body: &Body) -> FnLoops { - let mut visitor = IteratorVisitor { props: self, body }; + let mut visitor = + IteratorVisitor { props: self, body, graph: Vec::new(), current_bbidx: 0 }; visitor.visit_body(body); visitor.props } pub fn has_loops(&self) -> bool { - (self.iterators + self.loops + self.nested_loops) > 0 + (self.loops + self.nested_loops) > 0 + } + + pub fn has_iterators(&self) -> bool { + (self.iterators) > 0 } } @@ -461,12 +497,36 @@ impl FnLoops { struct IteratorVisitor<'a> { props: FnLoops, body: &'a Body, + graph: Vec<(u32, u32)>, + current_bbidx: u32, } impl<'a> MirVisitor for IteratorVisitor<'a> { + fn visit_body(&mut self, body: &Body) { + // First visit the body to build the control flow graph + self.super_body(body); + // Build the petgraph from the adj vec + let g = Graph::<(), ()>::from_edges(self.graph.clone()); + self.props.loops += g.cycles().len(); + } + + fn visit_basic_block(&mut self, bb: &BasicBlock) { + self.current_bbidx = self.body.blocks.iter().position(|b| *b == *bb).unwrap() as u32; + self.super_basic_block(bb); + } + fn visit_terminator(&mut self, term: &Terminator, location: Location) { + // Add edges between basic block into the adj table + let successors = term.kind.successors(); + for target in successors { + self.graph.push((self.current_bbidx, target as u32)); + } + if let TerminatorKind::Call { func, .. } = &term.kind { let kind = func.ty(self.body.locals()).unwrap().kind(); + // Check if the target is a visited block. + + // Check if the call is an iterator function that contains loops. if let TyKind::RigidTy(RigidTy::FnDef(def, _)) = kind { let fullname = def.name(); let names = fullname.split("::").collect::>(); @@ -505,6 +565,7 @@ impl<'a> MirVisitor for IteratorVisitor<'a> { } } } + self.super_terminator(term, location) } } From 0ed9a624244e666232e402bb4abdce864bae7f41 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 22 Aug 2024 12:37:59 -0400 Subject: [PATCH 12/40] Avoid corner-cases by grouping instrumentation into basic blocks and using backward iteration (#3438) This PR is a follow-up to https://github.com/model-checking/kani/pull/3374. Its main purpose is to properly handle a corner-case when multiple instrumentation instructions are added to the same instruction and not all of them are skipped during subsequent instrumentation. For example, if instrumentation is added after a terminator, a new basic block will be created, containing the added instrumentation. However, we currently only mark the first statement (or the terminator, if there are none) of the new basic block as skipped for subsequent instrumentation. That means that if instrumentation in this case contains some instrumentation targets itself, it will never terminate. Coincidentally, this is not currently the case, but could lead to subtle bugs if we decide to change instrumentation. In fact, this bug was only surfaced when I experimented with checking all memory accesses, which introduced significantly more checks. Ultimately, this shows that the current way to avoiding instrumentation is limited and needs to be changed. The PR introduces the following changes: - Group instrumentation into separate basic blocks instead of adding instructions one-by-one. - Use backward iteration to avoid having to reason about which instructions need to be skipped. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../src/kani_middle/transform/body.rs | 14 +- .../delayed_ub/instrumentation_visitor.rs | 88 +-- .../transform/check_uninit/delayed_ub/mod.rs | 23 +- .../kani_middle/transform/check_uninit/mod.rs | 322 +++++------ .../transform/check_uninit/ptr_uninit/mod.rs | 16 +- .../check_uninit/ptr_uninit/uninit_visitor.rs | 525 +++++++++--------- .../check_uninit/relevant_instruction.rs | 67 ++- .../kani_middle/transform/kani_intrinsics.rs | 134 +++-- .../uninit/multiple-instrumentations.expected | 20 + .../uninit/multiple-instrumentations.rs | 42 ++ 10 files changed, 697 insertions(+), 554 deletions(-) create mode 100644 tests/expected/uninit/multiple-instrumentations.expected create mode 100644 tests/expected/uninit/multiple-instrumentations.rs diff --git a/kani-compiler/src/kani_middle/transform/body.rs b/kani-compiler/src/kani_middle/transform/body.rs index fa4e5eb1ad97..24fe5d8b2b2e 100644 --- a/kani-compiler/src/kani_middle/transform/body.rs +++ b/kani-compiler/src/kani_middle/transform/body.rs @@ -310,16 +310,16 @@ impl MutableBody { // Update the source to point at the terminator. *source = SourceInstruction::Terminator { bb: orig_bb }; } - // Make the terminator at `source` point at the new block, - // the terminator of which is a simple Goto instruction. + // Make the terminator at `source` point at the new block, the terminator of which is + // provided by the caller. SourceInstruction::Terminator { bb } => { let current_term = &mut self.blocks.get_mut(*bb).unwrap().terminator; let target_bb = get_mut_target_ref(current_term); let new_target_bb = get_mut_target_ref(&mut new_term); - // 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; + // Swap the targets of the newly inserted terminator and the original one. This is + // an easy way to make the original terminator point to the new basic block with the + // new terminator. + std::mem::swap(new_target_bb, target_bb); // Update the source to point at the terminator. *bb = new_bb_idx; self.blocks.push(BasicBlock { statements: vec![], terminator: new_term }); @@ -484,7 +484,7 @@ impl CheckType { } /// We store the index of an instruction to avoid borrow checker issues and unnecessary copies. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum SourceInstruction { Statement { idx: usize, bb: BasicBlockIdx }, Terminator { bb: BasicBlockIdx }, diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/instrumentation_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/instrumentation_visitor.rs index f295fc76d4bf..25059297d3d5 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/instrumentation_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/instrumentation_visitor.rs @@ -18,20 +18,15 @@ use rustc_middle::ty::TyCtxt; use stable_mir::mir::{ mono::Instance, visit::{Location, PlaceContext}, - BasicBlockIdx, MirVisitor, Operand, Place, Rvalue, Statement, Terminator, + MirVisitor, Operand, Place, Rvalue, Statement, Terminator, }; use std::collections::HashSet; pub struct InstrumentationVisitor<'a, 'tcx> { - /// 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, + /// All target instructions in the body. + targets: Vec, + /// Current analysis target, eventually needs to be added to a list of all targets. + current_target: InitRelevantInstruction, /// Aliasing analysis data. points_to: &'a PointsToGraph<'tcx>, /// The list of places we should be looking for, ignoring others @@ -41,17 +36,16 @@ pub struct InstrumentationVisitor<'a, 'tcx> { } impl<'a, 'tcx> TargetFinder for InstrumentationVisitor<'a, 'tcx> { - fn find_next( - &mut self, - body: &MutableBody, - bb: BasicBlockIdx, - skip_first: bool, - ) -> Option { - self.skip_next = skip_first; - self.current = SourceInstruction::Statement { idx: 0, bb }; - self.target = None; - self.visit_basic_block(&body.blocks()[bb]); - self.target.clone() + fn find_all(mut self, body: &MutableBody) -> Vec { + for (bb_idx, bb) in body.blocks().iter().enumerate() { + self.current_target = InitRelevantInstruction { + source: SourceInstruction::Statement { idx: 0, bb: bb_idx }, + before_instruction: vec![], + after_instruction: vec![], + }; + self.visit_basic_block(bb); + } + self.targets } } @@ -63,43 +57,55 @@ impl<'a, 'tcx> InstrumentationVisitor<'a, 'tcx> { tcx: TyCtxt<'tcx>, ) -> Self { Self { - skip_next: false, - current: SourceInstruction::Statement { idx: 0, bb: 0 }, - target: None, + targets: vec![], + current_target: InitRelevantInstruction { + source: SourceInstruction::Statement { idx: 0, bb: 0 }, + before_instruction: vec![], + after_instruction: vec![], + }, points_to, analysis_targets, current_instance, tcx, } } + fn push_target(&mut self, source_op: MemoryInitOp) { - let target = self.target.get_or_insert_with(|| InitRelevantInstruction { - source: self.current, - after_instruction: vec![], - before_instruction: vec![], - }); - target.push_operation(source_op); + self.current_target.push_operation(source_op); } } impl<'a, 'tcx> MirVisitor for InstrumentationVisitor<'a, 'tcx> { fn visit_statement(&mut self, stmt: &Statement, location: Location) { - if self.skip_next { - self.skip_next = false; - } else if self.target.is_none() { - // Check all inner places. - self.super_statement(stmt, location); - } + self.super_statement(stmt, location); // Switch to the next statement. - let SourceInstruction::Statement { idx, bb } = self.current else { unreachable!() }; - self.current = SourceInstruction::Statement { idx: idx + 1, bb }; + if let SourceInstruction::Statement { idx, bb } = self.current_target.source { + self.targets.push(self.current_target.clone()); + self.current_target = InitRelevantInstruction { + source: SourceInstruction::Statement { idx: idx + 1, bb }, + after_instruction: vec![], + before_instruction: vec![], + }; + } else { + unreachable!() + } } fn visit_terminator(&mut self, term: &Terminator, location: Location) { - if !(self.skip_next || self.target.is_some()) { - self.current = SourceInstruction::Terminator { bb: self.current.bb() }; - self.super_terminator(term, location); + if let SourceInstruction::Statement { bb, .. } = self.current_target.source { + // We don't have to push the previous target, since it already happened in the statement + // handling code. + self.current_target = InitRelevantInstruction { + source: SourceInstruction::Terminator { bb }, + after_instruction: vec![], + before_instruction: vec![], + }; + } else { + unreachable!() } + self.super_terminator(term, location); + // Push the current target from the terminator onto the list. + self.targets.push(self.current_target.clone()); } fn visit_rvalue(&mut self, rvalue: &Rvalue, location: Location) { diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/mod.rs index 6b488569813f..e179947d2777 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/mod.rs @@ -11,9 +11,8 @@ use crate::kani_middle::{ points_to::{run_points_to_analysis, MemLoc, PointsToGraph}, reachability::CallGraph, transform::{ - body::{CheckType, MutableBody}, - check_uninit::UninitInstrumenter, - BodyTransformation, GlobalPass, TransformationResult, + body::CheckType, check_uninit::UninitInstrumenter, BodyTransformation, GlobalPass, + TransformationResult, }, }; use crate::kani_queries::QueryDb; @@ -112,12 +111,8 @@ impl GlobalPass for DelayedUbPass { // Instrument each instance based on the final targets we found. for instance in instances { - let mut instrumenter = UninitInstrumenter { - check_type: self.check_type.clone(), - mem_init_fn_cache: &mut self.mem_init_fn_cache, - }; // Retrieve the body with all local instrumentation passes applied. - let body = MutableBody::from(transformer.body(tcx, instance)); + let body = transformer.body(tcx, instance); // Instrument for delayed UB. let target_finder = InstrumentationVisitor::new( &global_points_to_graph, @@ -125,12 +120,18 @@ impl GlobalPass for DelayedUbPass { instance, tcx, ); - let (instrumentation_added, body) = - instrumenter.instrument(tcx, body, instance, target_finder); + let (instrumentation_added, body) = UninitInstrumenter::run( + body, + tcx, + instance, + self.check_type.clone(), + &mut self.mem_init_fn_cache, + target_finder, + ); // If some instrumentation has been performed, update the cached body in the local transformer. if instrumentation_added { transformer.cache.entry(instance).and_modify(|transformation_result| { - *transformation_result = TransformationResult::Modified(body.into()); + *transformation_result = TransformationResult::Modified(body); }); } } 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 0a0d3c786ea9..88d906aa3134 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -13,13 +13,13 @@ use rustc_middle::ty::TyCtxt; use rustc_smir::rustc_internal; use stable_mir::{ mir::{ - mono::Instance, AggregateKind, BasicBlockIdx, ConstOperand, Mutability, Operand, Place, - Rvalue, + mono::Instance, AggregateKind, BasicBlock, Body, ConstOperand, Mutability, Operand, Place, + Rvalue, Statement, StatementKind, Terminator, TerminatorKind, UnwindAction, }, ty::{FnDef, GenericArgKind, GenericArgs, MirConst, RigidTy, Ty, TyConst, TyKind, UintTy}, CrateDef, }; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; pub use delayed_ub::DelayedUbPass; pub use ptr_uninit::UninitPass; @@ -31,13 +31,8 @@ mod relevant_instruction; mod ty_layout; /// Trait that the instrumentation target providers must implement to work with the instrumenter. -trait TargetFinder { - fn find_next( - &mut self, - body: &MutableBody, - bb: BasicBlockIdx, - skip_first: bool, - ) -> Option; +pub trait TargetFinder { + fn find_all(self, body: &MutableBody) -> Vec; } // Function bodies of those functions will not be instrumented as not to cause infinite recursion. @@ -53,27 +48,42 @@ const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = &[ ]; /// Instruments the code with checks for uninitialized memory, agnostic to the source of targets. -#[derive(Debug)] -pub struct UninitInstrumenter<'a> { - pub check_type: CheckType, +pub struct UninitInstrumenter<'a, 'tcx> { + check_type: CheckType, /// Used to cache FnDef lookups of injected memory initialization functions. - pub mem_init_fn_cache: &'a mut HashMap<&'static str, FnDef>, + mem_init_fn_cache: &'a mut HashMap<&'static str, FnDef>, + tcx: TyCtxt<'tcx>, } -impl<'a> UninitInstrumenter<'a> { +impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { + /// Create the instrumenter and run it with the given parameters. + pub(crate) fn run( + body: Body, + tcx: TyCtxt<'tcx>, + instance: Instance, + check_type: CheckType, + mem_init_fn_cache: &'a mut HashMap<&'static str, FnDef>, + target_finder: impl TargetFinder, + ) -> (bool, Body) { + let mut instrumenter = Self { check_type, mem_init_fn_cache, tcx }; + let body = MutableBody::from(body); + let (changed, new_body) = instrumenter.instrument(body, instance, target_finder); + (changed, new_body.into()) + } + /// Instrument a body with memory initialization checks, the visitor that generates /// instrumentation targets must be provided via a TF type parameter. fn instrument( &mut self, - tcx: TyCtxt, mut body: MutableBody, instance: Instance, - mut target_finder: impl TargetFinder, + target_finder: impl TargetFinder, ) -> (bool, MutableBody) { // Need to break infinite recursion when memory initialization checks are inserted, so the // internal functions responsible for memory initialization are skipped. - if tcx - .get_diagnostic_name(rustc_internal::internal(tcx, instance.def.def_id())) + if self + .tcx + .get_diagnostic_name(rustc_internal::internal(self.tcx, instance.def.def_id())) .map(|diagnostic_name| { SKIPPED_DIAGNOSTIC_ITEMS.contains(&diagnostic_name.to_ident_string().as_str()) }) @@ -83,28 +93,9 @@ impl<'a> UninitInstrumenter<'a> { } let orig_len = 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 < body.blocks().len() { - if let Some(candidate) = - target_finder.find_next(&body, bb_idx, skip_first.contains(&bb_idx)) - { - self.build_check_for_instruction(tcx, &mut body, candidate, &mut skip_first); - bb_idx += 1 - } else { - bb_idx += 1; - }; + for instruction in target_finder.find_all(&body).into_iter().rev() { + let source = instruction.source; + self.build_check_for_instruction(&mut body, instruction, source); } (orig_len != body.blocks().len(), body) } @@ -112,40 +103,38 @@ impl<'a> UninitInstrumenter<'a> { /// Inject memory initialization checks for each operation in an instruction. fn build_check_for_instruction( &mut self, - tcx: TyCtxt, body: &mut MutableBody, instruction: InitRelevantInstruction, - skip_first: &mut HashSet, + mut source: SourceInstruction, ) { - let mut source = instruction.source; for operation in instruction.before_instruction { - self.build_check_for_operation(tcx, body, &mut source, operation, skip_first); + self.build_check_for_operation(body, &mut source, operation); } for operation in instruction.after_instruction { - self.build_check_for_operation(tcx, body, &mut source, operation, skip_first); + self.build_check_for_operation(body, &mut source, operation); } } /// Inject memory initialization check for an operation. fn build_check_for_operation( &mut self, - tcx: TyCtxt, body: &mut MutableBody, source: &mut SourceInstruction, operation: MemoryInitOp, - skip_first: &mut HashSet, ) { if let MemoryInitOp::Unsupported { reason } | MemoryInitOp::TriviallyUnsafe { reason } = &operation { - collect_skipped(&operation, body, skip_first); - self.inject_assert_false(tcx, body, source, operation.position(), reason); + // If the operation is unsupported or trivially accesses uninitialized memory, encode + // the check as `assert!(false)`. + self.inject_assert_false(self.tcx, body, source, operation.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_info = { + // Sanity check: since CBMC memory object primitives only accept pointers, need to + // ensure the correct type. + let ptr_operand_ty = operation.operand_ty(body); let pointee_ty = match ptr_operand_ty.kind() { TyKind::RigidTy(RigidTy::RawPtr(pointee_ty, _)) => pointee_ty, _ => { @@ -154,58 +143,55 @@ impl<'a> UninitInstrumenter<'a> { ) } }; + // Calculate pointee layout for byte-by-byte memory initialization checks. match PointeeInfo::from_ty(pointee_ty) { Ok(type_info) => type_info, Err(_) => { let reason = format!( "Kani currently doesn't support checking memory initialization for pointers to `{pointee_ty}.", ); - collect_skipped(&operation, body, skip_first); - self.inject_assert_false(tcx, body, source, operation.position(), &reason); + self.inject_assert_false(self.tcx, body, source, operation.position(), &reason); return; } } }; - match operation { + match &operation { MemoryInitOp::CheckSliceChunk { .. } | MemoryInitOp::Check { .. } | MemoryInitOp::CheckRef { .. } => { - self.build_get_and_check(tcx, body, source, operation, pointee_ty_info, skip_first) + self.build_get_and_check(body, source, operation, pointee_info) } MemoryInitOp::SetSliceChunk { .. } | MemoryInitOp::Set { .. } - | MemoryInitOp::SetRef { .. } => { - self.build_set(tcx, body, source, operation, pointee_ty_info, skip_first) - } - MemoryInitOp::Copy { .. } => { - self.build_copy(tcx, body, source, operation, pointee_ty_info, skip_first) - } + | MemoryInitOp::SetRef { .. } => self.build_set(body, source, operation, pointee_info), + MemoryInitOp::Copy { .. } => self.build_copy(body, source, operation, pointee_info), MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => { unreachable!() } - } + }; } /// Inject a load from memory initialization state and an assertion that all non-padding bytes /// are initialized. fn build_get_and_check( &mut self, - tcx: TyCtxt, body: &mut MutableBody, source: &mut SourceInstruction, operation: MemoryInitOp, pointee_info: PointeeInfo, - skip_first: &mut HashSet, ) { 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() { + // Instead of injecting the instrumentation immediately, collect it into a list of + // statements and a terminator to construct a basic block and inject it at the end. + let mut statements = vec![]; + let ptr_operand = operation.mk_operand(body, &mut statements, source); + let terminator = match pointee_info.layout() { PointeeLayout::Sized { layout } => { - let layout_operand = mk_layout_operand(body, source, operation.position(), &layout); + let layout_operand = mk_layout_operand(body, &mut statements, source, &layout); // Depending on whether accessing the known number of elements in the slice, need to // pass is as an argument. let (diagnostic, args) = match &operation { @@ -223,18 +209,24 @@ impl<'a> UninitInstrumenter<'a> { _ => unreachable!(), }; let is_ptr_initialized_instance = resolve_mem_init_fn( - get_mem_init_fn_def(tcx, diagnostic, &mut self.mem_init_fn_cache), + get_mem_init_fn_def(self.tcx, diagnostic, &mut self.mem_init_fn_cache), layout.len(), *pointee_info.ty(), ); - collect_skipped(&operation, body, skip_first); - body.insert_call( - &is_ptr_initialized_instance, - source, - operation.position(), - args, - ret_place.clone(), - ); + Terminator { + kind: TerminatorKind::Call { + func: Operand::Copy(Place::from(body.new_local( + is_ptr_initialized_instance.ty(), + source.span(body.blocks()), + Mutability::Not, + ))), + args, + destination: ret_place.clone(), + target: Some(0), // The current value does not matter, since it will be overwritten in add_bb. + unwind: UnwindAction::Terminate, + }, + span: source.span(body.blocks()), + } } PointeeLayout::Slice { element_layout } => { // Since `str`` is a separate type, need to differentiate between [T] and str. @@ -248,32 +240,39 @@ impl<'a> UninitInstrumenter<'a> { _ => unreachable!(), }; let is_ptr_initialized_instance = resolve_mem_init_fn( - get_mem_init_fn_def(tcx, diagnostic, &mut self.mem_init_fn_cache), + get_mem_init_fn_def(self.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); - collect_skipped(&operation, body, skip_first); - body.insert_call( - &is_ptr_initialized_instance, - source, - operation.position(), - vec![ptr_operand.clone(), layout_operand], - ret_place.clone(), - ); + mk_layout_operand(body, &mut statements, source, &element_layout); + Terminator { + kind: TerminatorKind::Call { + func: Operand::Copy(Place::from(body.new_local( + is_ptr_initialized_instance.ty(), + source.span(body.blocks()), + Mutability::Not, + ))), + args: vec![ptr_operand.clone(), layout_operand], + destination: ret_place.clone(), + target: Some(0), // The current value does not matter, since it will be overwritten in add_bb. + unwind: UnwindAction::Terminate, + }, + span: source.span(body.blocks()), + } } PointeeLayout::TraitObject => { - collect_skipped(&operation, body, skip_first); let reason = "Kani does not support reasoning about memory initialization of pointers to trait objects."; - self.inject_assert_false(tcx, body, source, operation.position(), reason); + self.inject_assert_false(self.tcx, body, source, operation.position(), reason); return; } }; - // Make sure all non-padding bytes are initialized. - collect_skipped(&operation, body, skip_first); - // Find the real operand type for a good error message. + // Construct the basic block and insert it into the body. + body.insert_bb(BasicBlock { statements, terminator }, source, operation.position()); + + // Since the check involves a terminator, we cannot add it to the previously constructed + // basic block. Instead, we insert the check after the basic block. let operand_ty = match &operation { MemoryInitOp::Check { operand } | MemoryInitOp::CheckSliceChunk { operand, .. } @@ -281,7 +280,7 @@ impl<'a> UninitInstrumenter<'a> { _ => unreachable!(), }; body.insert_check( - tcx, + self.tcx, &self.check_type, source, operation.position(), @@ -296,23 +295,24 @@ impl<'a> UninitInstrumenter<'a> { /// non-padding bytes. fn build_set( &mut self, - tcx: TyCtxt, body: &mut MutableBody, source: &mut SourceInstruction, operation: MemoryInitOp, pointee_info: PointeeInfo, - 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() { + // Instead of injecting the instrumentation immediately, collect it into a list of + // statements and a terminator to construct a basic block and inject it at the end. + let mut statements = vec![]; + let ptr_operand = operation.mk_operand(body, &mut statements, source); + let value = operation.expect_value(); + let terminator = match pointee_info.layout() { PointeeLayout::Sized { layout } => { - let layout_operand = mk_layout_operand(body, source, operation.position(), &layout); + let layout_operand = mk_layout_operand(body, &mut statements, source, &layout); // Depending on whether writing to the known number of elements in the slice, need to // pass is as an argument. let (diagnostic, args) = match &operation { @@ -346,18 +346,24 @@ impl<'a> UninitInstrumenter<'a> { _ => unreachable!(), }; let set_ptr_initialized_instance = resolve_mem_init_fn( - get_mem_init_fn_def(tcx, diagnostic, &mut self.mem_init_fn_cache), + get_mem_init_fn_def(self.tcx, diagnostic, &mut self.mem_init_fn_cache), layout.len(), *pointee_info.ty(), ); - collect_skipped(&operation, body, skip_first); - body.insert_call( - &set_ptr_initialized_instance, - source, - operation.position(), - args, - ret_place, - ); + Terminator { + kind: TerminatorKind::Call { + func: Operand::Copy(Place::from(body.new_local( + set_ptr_initialized_instance.ty(), + source.span(body.blocks()), + Mutability::Not, + ))), + args, + destination: ret_place.clone(), + target: Some(0), // this will be overriden in add_bb + unwind: UnwindAction::Terminate, + }, + span: source.span(body.blocks()), + } } PointeeLayout::Slice { element_layout } => { // Since `str`` is a separate type, need to differentiate between [T] and str. @@ -371,44 +377,50 @@ impl<'a> UninitInstrumenter<'a> { _ => unreachable!(), }; let set_ptr_initialized_instance = resolve_mem_init_fn( - get_mem_init_fn_def(tcx, diagnostic, &mut self.mem_init_fn_cache), + get_mem_init_fn_def(self.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); - collect_skipped(&operation, body, skip_first); - body.insert_call( - &set_ptr_initialized_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, - ); + mk_layout_operand(body, &mut statements, source, &element_layout); + Terminator { + kind: TerminatorKind::Call { + func: Operand::Copy(Place::from(body.new_local( + set_ptr_initialized_instance.ty(), + source.span(body.blocks()), + Mutability::Not, + ))), + args: vec![ + ptr_operand, + layout_operand, + Operand::Constant(ConstOperand { + span: source.span(body.blocks()), + user_ty: None, + const_: MirConst::from_bool(value), + }), + ], + destination: ret_place.clone(), + target: Some(0), // The current value does not matter, since it will be overwritten in add_bb. + unwind: UnwindAction::Terminate, + }, + span: source.span(body.blocks()), + } } PointeeLayout::TraitObject => { unreachable!("Cannot change the initialization state of a trait object directly."); } }; + // Construct the basic block and insert it into the body. + body.insert_bb(BasicBlock { statements, terminator }, source, operation.position()); } /// Copy memory initialization state from one pointer to the other. fn build_copy( &mut self, - tcx: TyCtxt, body: &mut MutableBody, source: &mut SourceInstruction, operation: MemoryInitOp, pointee_info: PointeeInfo, - skip_first: &mut HashSet, ) { let ret_place = Place { local: body.new_local(Ty::new_tuple(&[]), source.span(body.blocks()), Mutability::Not), @@ -416,11 +428,10 @@ impl<'a> UninitInstrumenter<'a> { }; let PointeeLayout::Sized { layout } = pointee_info.layout() else { unreachable!() }; let copy_init_state_instance = resolve_mem_init_fn( - get_mem_init_fn_def(tcx, "KaniCopyInitState", &mut self.mem_init_fn_cache), + get_mem_init_fn_def(self.tcx, "KaniCopyInitState", &mut self.mem_init_fn_cache), layout.len(), *pointee_info.ty(), ); - collect_skipped(&operation, body, skip_first); let position = operation.position(); let MemoryInitOp::Copy { from, to, count } = operation else { unreachable!() }; body.insert_call( @@ -465,39 +476,30 @@ impl<'a> UninitInstrumenter<'a> { /// will have the following byte mask `[true, true, true, false]`. pub fn mk_layout_operand( body: &mut MutableBody, + statements: &mut Vec, source: &mut SourceInstruction, - position: InsertPosition, layout_byte_mask: &[bool], ) -> Operand { - Operand::Move(Place { - local: body.insert_assignment( - Rvalue::Aggregate( - AggregateKind::Array(Ty::bool_ty()), - layout_byte_mask - .iter() - .map(|byte| { - Operand::Constant(ConstOperand { - span: source.span(body.blocks()), - user_ty: None, - const_: MirConst::from_bool(*byte), - }) - }) - .collect(), - ), - source, - position, - ), - projection: vec![], - }) -} + let span = source.span(body.blocks()); + let rvalue = Rvalue::Aggregate( + AggregateKind::Array(Ty::bool_ty()), + layout_byte_mask + .iter() + .map(|byte| { + Operand::Constant(ConstOperand { + span: source.span(body.blocks()), + user_ty: None, + const_: MirConst::from_bool(*byte), + }) + }) + .collect(), + ); + let ret_ty = rvalue.ty(body.locals()).unwrap(); + let result = body.new_local(ret_ty, span, Mutability::Not); + let stmt = Statement { kind: StatementKind::Assign(Place::from(result), rvalue), span }; + statements.push(stmt); -/// 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: &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); - } + Operand::Move(Place { local: result, projection: vec![] }) } /// Retrieve a function definition by diagnostic string, caching the result. diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/mod.rs index af2753ea7175..37f1d94ed744 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/mod.rs @@ -62,14 +62,16 @@ impl TransformPass for UninitPass { } // Call a helper that performs the actual instrumentation. - let mut instrumenter = UninitInstrumenter { - check_type: self.check_type.clone(), - mem_init_fn_cache: &mut self.mem_init_fn_cache, - }; - let (instrumentation_added, body) = - instrumenter.instrument(tcx, new_body, instance, CheckUninitVisitor::new()); + let (instrumentation_added, body) = UninitInstrumenter::run( + new_body.into(), + tcx, + instance, + self.check_type.clone(), + &mut self.mem_init_fn_cache, + CheckUninitVisitor::new(), + ); - (changed || instrumentation_added, body.into()) + (changed || instrumentation_added, body) } } diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs index 1afb151a09b5..a1689bdfe8c4 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs @@ -19,42 +19,32 @@ use stable_mir::{ alloc::GlobalAlloc, mono::{Instance, InstanceKind}, visit::{Location, PlaceContext}, - BasicBlockIdx, CastKind, LocalDecl, MirVisitor, NonDivergingIntrinsic, Operand, Place, - PointerCoercion, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, - TerminatorKind, + CastKind, LocalDecl, MirVisitor, NonDivergingIntrinsic, Operand, Place, PointerCoercion, + ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, }, ty::{ConstantKind, RigidTy, TyKind}, }; pub struct CheckUninitVisitor { locals: Vec, - /// 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, + /// All target instructions in the body. + targets: Vec, + /// Current analysis target, eventually needs to be added to a list of all targets. + current_target: InitRelevantInstruction, } impl TargetFinder for CheckUninitVisitor { - fn find_next( - &mut self, - body: &MutableBody, - bb: BasicBlockIdx, - skip_first: bool, - ) -> Option { + fn find_all(mut self, body: &MutableBody) -> Vec { self.locals = body.locals().to_vec(); - self.skip_next = skip_first; - self.current = SourceInstruction::Statement { idx: 0, bb }; - self.target = None; - self.bb = bb; - self.visit_basic_block(&body.blocks()[bb]); - self.target.clone() + for (bb_idx, bb) in body.blocks().iter().enumerate() { + self.current_target = InitRelevantInstruction { + source: SourceInstruction::Statement { idx: 0, bb: bb_idx }, + before_instruction: vec![], + after_instruction: vec![], + }; + self.visit_basic_block(bb); + } + self.targets } } @@ -62,286 +52,289 @@ impl CheckUninitVisitor { pub fn new() -> Self { Self { locals: vec![], - skip_next: false, - current: SourceInstruction::Statement { idx: 0, bb: 0 }, - target: None, - bb: 0, + targets: vec![], + current_target: InitRelevantInstruction { + source: SourceInstruction::Statement { idx: 0, bb: 0 }, + before_instruction: vec![], + after_instruction: vec![], + }, } } fn push_target(&mut self, source_op: MemoryInitOp) { - let target = self.target.get_or_insert_with(|| InitRelevantInstruction { - source: self.current, - after_instruction: vec![], - before_instruction: vec![], - }); - target.push_operation(source_op); + self.current_target.push_operation(source_op); } } impl MirVisitor for CheckUninitVisitor { 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); - // The copy is untyped, so we should copy memory initialization state from `src` - // to `dst`. - self.push_target(MemoryInitOp::Copy { - from: copy.src.clone(), - to: copy.dst.clone(), - count: copy.count.clone(), - }); - } - StatementKind::Assign(place, rvalue) => { - // First check rvalue. - 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()), - }); - }; - } - 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), - value: true, - position: InsertPosition::After, - }); + // 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); + // The copy is untyped, so we should copy memory initialization state from `src` + // to `dst`. + self.push_target(MemoryInitOp::Copy { + from: copy.src.clone(), + to: copy.dst.clone(), + count: copy.count.clone(), + }); + } + StatementKind::Assign(place, rvalue) => { + // First check rvalue. + 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()), + }); + }; } + place_to_add_projections.projection.push(projection_elem.clone()); } - // 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(MemoryInitOp::Set { - operand: Operand::Copy(place.clone()), - value: true, - position: InsertPosition::After, - }); - } + if place_without_deref.ty(&&self.locals).unwrap().kind().is_raw_ptr() { + self.push_target(MemoryInitOp::Set { + operand: Operand::Copy(place_without_deref), + value: true, + position: InsertPosition::After, + }); } } - StatementKind::Deinit(place) => { - self.super_statement(stmt, location); - self.push_target(MemoryInitOp::Set { - operand: Operand::Copy(place.clone()), - value: false, - position: InsertPosition::After, - }); + // 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(MemoryInitOp::Set { + operand: Operand::Copy(place.clone()), + value: true, + position: InsertPosition::After, + }); + } } - 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), } + StatementKind::Deinit(place) => { + self.super_statement(stmt, location); + self.push_target(MemoryInitOp::Set { + operand: Operand::Copy(place.clone()), + value: false, + position: InsertPosition::After, + }); + } + 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), + } + // Switch to the next statement. + if let SourceInstruction::Statement { idx, bb } = self.current_target.source { + self.targets.push(self.current_target.clone()); + self.current_target = InitRelevantInstruction { + source: SourceInstruction::Statement { idx: idx + 1, bb }, + after_instruction: vec![], + before_instruction: vec![], + }; + } else { + unreachable!() } - 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, destination, .. } => { - self.super_terminator(term, location); - let instance = match try_resolve_instance(&self.locals, func) { - Ok(instance) => instance, - Err(reason) => { - self.super_terminator(term, location); - self.push_target(MemoryInitOp::Unsupported { reason }); - return; + if let SourceInstruction::Statement { bb, .. } = self.current_target.source { + // We don't have to push the previous target, since it already happened in the statement + // handling code. + self.current_target = InitRelevantInstruction { + source: SourceInstruction::Terminator { bb }, + after_instruction: vec![], + before_instruction: vec![], + }; + } else { + unreachable!() + } + // Leave it as an exhaustive match to be notified when a new kind is added. + match &term.kind { + TerminatorKind::Call { func, args, destination, .. } => { + self.super_terminator(term, location); + let instance = match try_resolve_instance(&self.locals, func) { + Ok(instance) => instance, + Err(reason) => { + self.super_terminator(term, location); + self.push_target(MemoryInitOp::Unsupported { reason }); + return; + } + }; + match instance.kind { + InstanceKind::Intrinsic => { + match Intrinsic::from_instance(&instance) { + intrinsic_name if can_skip_intrinsic(intrinsic_name.clone()) => { + /* Intrinsics that can be safely skipped */ + } + Intrinsic::AtomicAnd(_) + | Intrinsic::AtomicCxchg(_) + | Intrinsic::AtomicCxchgWeak(_) + | Intrinsic::AtomicLoad(_) + | Intrinsic::AtomicMax(_) + | Intrinsic::AtomicMin(_) + | Intrinsic::AtomicNand(_) + | Intrinsic::AtomicOr(_) + | Intrinsic::AtomicStore(_) + | Intrinsic::AtomicUmax(_) + | Intrinsic::AtomicUmin(_) + | Intrinsic::AtomicXadd(_) + | Intrinsic::AtomicXchg(_) + | Intrinsic::AtomicXor(_) + | Intrinsic::AtomicXsub(_) => { + self.push_target(MemoryInitOp::Check { operand: args[0].clone() }); + } + Intrinsic::CompareBytes => { + self.push_target(MemoryInitOp::CheckSliceChunk { + operand: args[0].clone(), + count: args[2].clone(), + }); + self.push_target(MemoryInitOp::CheckSliceChunk { + operand: args[1].clone(), + count: args[2].clone(), + }); + } + Intrinsic::Copy => { + // The copy is untyped, so we should copy memory + // initialization state from `src` to `dst`. + self.push_target(MemoryInitOp::Copy { + from: args[0].clone(), + to: args[1].clone(), + count: args[2].clone(), + }); + } + Intrinsic::VolatileCopyMemory + | Intrinsic::VolatileCopyNonOverlappingMemory => { + // The copy is untyped, so we should copy initialization state + // from `src` to `dst`. Note that the `dst` comes before `src` + // in this case. + self.push_target(MemoryInitOp::Copy { + from: args[1].clone(), + to: args[0].clone(), + count: args[2].clone(), + }); + } + Intrinsic::TypedSwap => { + self.push_target(MemoryInitOp::Check { operand: args[0].clone() }); + self.push_target(MemoryInitOp::Check { operand: args[1].clone() }); + } + Intrinsic::VolatileLoad | Intrinsic::UnalignedVolatileLoad => { + self.push_target(MemoryInitOp::Check { operand: args[0].clone() }); + } + Intrinsic::VolatileStore => { + self.push_target(MemoryInitOp::Set { + operand: args[0].clone(), + value: true, + position: InsertPosition::After, + }); + } + Intrinsic::WriteBytes => { + self.push_target(MemoryInitOp::SetSliceChunk { + operand: args[0].clone(), + count: args[2].clone(), + value: true, + position: InsertPosition::After, + }) + } + intrinsic => { + self.push_target(MemoryInitOp::Unsupported { + reason: format!("Kani does not support reasoning about memory initialization of intrinsic `{intrinsic:?}`."), + }); + } } - }; - match instance.kind { - InstanceKind::Intrinsic => { - match Intrinsic::from_instance(&instance) { - intrinsic_name if can_skip_intrinsic(intrinsic_name.clone()) => { - /* Intrinsics that can be safely skipped */ - } - Intrinsic::AtomicAnd(_) - | Intrinsic::AtomicCxchg(_) - | Intrinsic::AtomicCxchgWeak(_) - | Intrinsic::AtomicLoad(_) - | Intrinsic::AtomicMax(_) - | Intrinsic::AtomicMin(_) - | Intrinsic::AtomicNand(_) - | Intrinsic::AtomicOr(_) - | Intrinsic::AtomicStore(_) - | Intrinsic::AtomicUmax(_) - | Intrinsic::AtomicUmin(_) - | Intrinsic::AtomicXadd(_) - | Intrinsic::AtomicXchg(_) - | Intrinsic::AtomicXor(_) - | Intrinsic::AtomicXsub(_) => { - self.push_target(MemoryInitOp::Check { - operand: args[0].clone(), - }); - } - Intrinsic::CompareBytes => { - self.push_target(MemoryInitOp::CheckSliceChunk { - operand: args[0].clone(), - count: args[2].clone(), - }); - self.push_target(MemoryInitOp::CheckSliceChunk { - operand: args[1].clone(), - count: args[2].clone(), - }); - } - Intrinsic::Copy => { - // The copy is untyped, so we should copy memory - // initialization state from `src` to `dst`. - self.push_target(MemoryInitOp::Copy { - from: args[0].clone(), - to: args[1].clone(), - count: args[2].clone(), - }); - } - Intrinsic::VolatileCopyMemory - | Intrinsic::VolatileCopyNonOverlappingMemory => { - // The copy is untyped, so we should copy initialization state - // from `src` to `dst`. Note that the `dst` comes before `src` - // in this case. - self.push_target(MemoryInitOp::Copy { - from: args[1].clone(), - to: args[0].clone(), - count: args[2].clone(), - }); - } - Intrinsic::TypedSwap => { - self.push_target(MemoryInitOp::Check { - operand: args[0].clone(), - }); - self.push_target(MemoryInitOp::Check { - operand: args[1].clone(), - }); - } - Intrinsic::VolatileLoad | Intrinsic::UnalignedVolatileLoad => { - self.push_target(MemoryInitOp::Check { - operand: args[0].clone(), - }); + } + InstanceKind::Item => { + if instance.is_foreign_item() { + match instance.name().as_str() { + "alloc::alloc::__rust_alloc" | "alloc::alloc::__rust_realloc" => { + /* Memory is uninitialized, nothing to do here. */ } - Intrinsic::VolatileStore => { - self.push_target(MemoryInitOp::Set { - operand: args[0].clone(), + "alloc::alloc::__rust_alloc_zeroed" => { + /* Memory is initialized here, need to update shadow memory. */ + self.push_target(MemoryInitOp::SetSliceChunk { + operand: Operand::Copy(destination.clone()), + count: args[0].clone(), value: true, position: InsertPosition::After, }); } - Intrinsic::WriteBytes => { + "alloc::alloc::__rust_dealloc" => { + /* Memory is uninitialized here, need to update shadow memory. */ self.push_target(MemoryInitOp::SetSliceChunk { operand: args[0].clone(), - count: args[2].clone(), - value: true, + count: args[1].clone(), + value: false, position: InsertPosition::After, - }) - } - intrinsic => { - self.push_target(MemoryInitOp::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() { - "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(MemoryInitOp::SetSliceChunk { - 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(MemoryInitOp::SetSliceChunk { - operand: args[0].clone(), - count: args[1].clone(), - value: false, - position: InsertPosition::After, - }); - } - _ => {} + }); } + _ => {} } } - _ => {} } + _ => {} } - TerminatorKind::Drop { place, .. } => { - self.super_terminator(term, location); - let place_ty = place.ty(&&self.locals).unwrap(); - - // When drop is codegen'ed for types that could define their own dropping - // behavior, a reference is taken to the place which is later implicitly coerced - // to a pointer. Hence, we need to bless this pointer as initialized. - match place - .ty(&&self.locals) - .unwrap() - .kind() - .rigid() - .expect("should be working with monomorphized code") - { - RigidTy::Adt(..) | RigidTy::Dynamic(_, _, _) => { - self.push_target(MemoryInitOp::SetRef { - operand: Operand::Copy(place.clone()), - value: true, - position: InsertPosition::Before, - }); - } - _ => {} - } + } + TerminatorKind::Drop { place, .. } => { + self.super_terminator(term, location); + let place_ty = place.ty(&&self.locals).unwrap(); - if place_ty.kind().is_raw_ptr() { - self.push_target(MemoryInitOp::Set { + // When drop is codegen'ed for types that could define their own dropping + // behavior, a reference is taken to the place which is later implicitly coerced + // to a pointer. Hence, we need to bless this pointer as initialized. + match place + .ty(&&self.locals) + .unwrap() + .kind() + .rigid() + .expect("should be working with monomorphized code") + { + RigidTy::Adt(..) | RigidTy::Dynamic(_, _, _) => { + self.push_target(MemoryInitOp::SetRef { operand: Operand::Copy(place.clone()), - value: false, - position: InsertPosition::After, + value: true, + position: InsertPosition::Before, }); } + _ => {} + } + + if place_ty.kind().is_raw_ptr() { + self.push_target(MemoryInitOp::Set { + operand: Operand::Copy(place.clone()), + value: false, + position: InsertPosition::After, + }); } - TerminatorKind::Goto { .. } - | TerminatorKind::SwitchInt { .. } - | TerminatorKind::Resume - | TerminatorKind::Abort - | TerminatorKind::Return - | TerminatorKind::Unreachable - | TerminatorKind::Assert { .. } - | TerminatorKind::InlineAsm { .. } => self.super_terminator(term, location), } + TerminatorKind::Goto { .. } + | TerminatorKind::SwitchInt { .. } + | TerminatorKind::Resume + | TerminatorKind::Abort + | TerminatorKind::Return + | TerminatorKind::Unreachable + | TerminatorKind::Assert { .. } + | TerminatorKind::InlineAsm { .. } => self.super_terminator(term, location), } + // Push the current target from the terminator onto the list. + self.targets.push(self.current_target.clone()); } fn visit_place(&mut self, place: &Place, ptx: PlaceContext, location: Location) { diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs b/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs index cc5a27e09925..05826969262d 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs @@ -6,8 +6,9 @@ use crate::kani_middle::transform::body::{InsertPosition, MutableBody, SourceInstruction}; use stable_mir::{ - mir::{Mutability, Operand, Place, Rvalue}, + mir::{Mutability, Operand, Place, Rvalue, Statement, StatementKind}, ty::RigidTy, + ty::Ty, }; use strum_macros::AsRefStr; @@ -44,27 +45,59 @@ 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 { + pub fn mk_operand( + &self, + body: &mut MutableBody, + statements: &mut Vec, + source: &mut SourceInstruction, + ) -> Operand { match self { MemoryInitOp::Check { operand, .. } | MemoryInitOp::Set { operand, .. } | MemoryInitOp::CheckSliceChunk { operand, .. } | MemoryInitOp::SetSliceChunk { operand, .. } => operand.clone(), MemoryInitOp::CheckRef { operand, .. } | MemoryInitOp::SetRef { operand, .. } => { - Operand::Copy(Place { - local: { - let place = match operand { - Operand::Copy(place) | Operand::Move(place) => place, - Operand::Constant(_) => unreachable!(), - }; - body.insert_assignment( - Rvalue::AddressOf(Mutability::Not, place.clone()), - source, - self.position(), - ) - }, - projection: vec![], - }) + let span = source.span(body.blocks()); + + let ref_local = { + let place = match operand { + Operand::Copy(place) | Operand::Move(place) => place, + Operand::Constant(_) => unreachable!(), + }; + let rvalue = Rvalue::AddressOf(Mutability::Not, place.clone()); + let ret_ty = rvalue.ty(body.locals()).unwrap(); + let result = body.new_local(ret_ty, span, Mutability::Not); + let stmt = Statement { + kind: StatementKind::Assign(Place::from(result), rvalue), + span, + }; + statements.push(stmt); + result + }; + + Operand::Copy(Place { local: ref_local, projection: vec![] }) + } + MemoryInitOp::Copy { .. } + | MemoryInitOp::Unsupported { .. } + | MemoryInitOp::TriviallyUnsafe { .. } => { + unreachable!() + } + } + } + + pub fn operand_ty(&self, body: &MutableBody) -> Ty { + match self { + MemoryInitOp::Check { operand, .. } + | MemoryInitOp::Set { operand, .. } + | MemoryInitOp::CheckSliceChunk { operand, .. } + | MemoryInitOp::SetSliceChunk { operand, .. } => operand.ty(body.locals()).unwrap(), + MemoryInitOp::SetRef { operand, .. } | MemoryInitOp::CheckRef { operand, .. } => { + let place = match operand { + Operand::Copy(place) | Operand::Move(place) => place, + Operand::Constant(_) => unreachable!(), + }; + let rvalue = Rvalue::AddressOf(Mutability::Not, place.clone()); + rvalue.ty(body.locals()).unwrap() } MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => { unreachable!("operands do not exist for this operation") @@ -82,7 +115,7 @@ impl MemoryInitOp { unreachable!() }; assert!(from_pointee_ty == to_pointee_ty); - from.clone() + from.ty(body.locals()).unwrap() } } } diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index cc748c39a7f5..dd804b7379f2 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -22,8 +22,8 @@ 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, TerminatorKind, - RETURN_LOCAL, + BasicBlock, BinOp, Body, ConstOperand, Mutability, Operand, Place, Rvalue, Statement, + StatementKind, Terminator, TerminatorKind, UnwindAction, RETURN_LOCAL, }; use stable_mir::target::MachineInfo; use stable_mir::ty::{FnDef, MirConst, RigidTy, Ty, TyKind, UintTy}; @@ -164,30 +164,39 @@ impl IntrinsicGeneratorPass { fn is_initialized_body(&mut self, tcx: TyCtxt, body: Body) -> Body { let mut new_body = MutableBody::from(body); new_body.clear_body(TerminatorKind::Return); - - // 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); + let mut source = SourceInstruction::Terminator { bb: 0 }; + // Short-circut if uninitialized memory checks are not enabled. if !self.arguments.ub_check.contains(&ExtraChecks::Uninit) { - // Short-circut if uninitialized memory checks are not enabled. + // Initialize return variable with True. + 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), + })), + ); + new_body.insert_stmt( + Statement { kind: assign, span }, + &mut source, + InsertPosition::Before, + ); return new_body.into(); } + // Instead of injecting the instrumentation immediately, collect it into a list of + // statements and a terminator to construct a basic block and inject it at the end. + let mut statements = vec![]; + // The first argument type. let arg_ty = new_body.locals()[1].ty; + // Sanity check: since CBMC memory object primitives only accept pointers, need to + // ensure the correct type. let TyKind::RigidTy(RigidTy::RawPtr(target_ty, _)) = arg_ty.kind() else { unreachable!() }; + // Calculate pointee layout for byte-by-byte memory initialization checks. let pointee_info = PointeeInfo::from_ty(target_ty); match pointee_info { Ok(pointee_info) => { @@ -195,6 +204,21 @@ impl IntrinsicGeneratorPass { PointeeLayout::Sized { layout } => { if layout.is_empty() { // Encountered a ZST, so we can short-circut here. + // Initialize return variable with True. + 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), + })), + ); + new_body.insert_stmt( + Statement { kind: assign, span }, + &mut source, + InsertPosition::Before, + ); return new_body.into(); } let is_ptr_initialized_instance = resolve_mem_init_fn( @@ -206,18 +230,28 @@ impl IntrinsicGeneratorPass { layout.len(), *pointee_info.ty(), ); - let layout_operand = mk_layout_operand( - &mut new_body, - &mut terminator, - InsertPosition::Before, - &layout, - ); - new_body.insert_call( - &is_ptr_initialized_instance, - &mut terminator, + let layout_operand = + mk_layout_operand(&mut new_body, &mut statements, &mut source, &layout); + + let terminator = Terminator { + kind: TerminatorKind::Call { + func: Operand::Copy(Place::from(new_body.new_local( + is_ptr_initialized_instance.ty(), + source.span(new_body.blocks()), + Mutability::Not, + ))), + args: vec![Operand::Copy(Place::from(1)), layout_operand], + destination: Place::from(ret_var), + target: Some(0), // The current value does not matter, since it will be overwritten in add_bb. + unwind: UnwindAction::Terminate, + }, + span: source.span(new_body.blocks()), + }; + // Construct the basic block and insert it into the body. + new_body.insert_bb( + BasicBlock { statements, terminator }, + &mut source, InsertPosition::Before, - vec![Operand::Copy(Place::from(1)), layout_operand], - Place::from(ret_var), ); } PointeeLayout::Slice { element_layout } => { @@ -238,35 +272,45 @@ impl IntrinsicGeneratorPass { ); let layout_operand = mk_layout_operand( &mut new_body, - &mut terminator, - InsertPosition::Before, + &mut statements, + &mut source, &element_layout, ); - new_body.insert_call( - &is_ptr_initialized_instance, - &mut terminator, + let terminator = Terminator { + kind: TerminatorKind::Call { + func: Operand::Copy(Place::from(new_body.new_local( + is_ptr_initialized_instance.ty(), + source.span(new_body.blocks()), + Mutability::Not, + ))), + args: vec![Operand::Copy(Place::from(1)), layout_operand], + destination: Place::from(ret_var), + target: Some(0), // The current value does not matter, since it will be overwritten in add_bb. + unwind: UnwindAction::Terminate, + }, + span: source.span(new_body.blocks()), + }; + // Construct the basic block and insert it into the body. + new_body.insert_bb( + BasicBlock { statements, terminator }, + &mut source, InsertPosition::Before, - vec![Operand::Copy(Place::from(1)), layout_operand], - Place::from(ret_var), ); } PointeeLayout::TraitObject => { let rvalue = Rvalue::Use(Operand::Constant(ConstOperand { const_: MirConst::from_bool(false), - span, + span: source.span(new_body.blocks()), user_ty: None, })); - let result = new_body.insert_assignment( - rvalue, - &mut terminator, - InsertPosition::Before, - ); + let result = + new_body.insert_assignment(rvalue, &mut source, InsertPosition::Before); let reason: &str = "Kani does not support reasoning about memory initialization of pointers to trait objects."; new_body.insert_check( tcx, &self.check_type, - &mut terminator, + &mut source, InsertPosition::Before, result, &reason, @@ -278,18 +322,18 @@ impl IntrinsicGeneratorPass { // We failed to retrieve the type layout. let rvalue = Rvalue::Use(Operand::Constant(ConstOperand { const_: MirConst::from_bool(false), - span, + span: source.span(new_body.blocks()), user_ty: None, })); let result = - new_body.insert_assignment(rvalue, &mut terminator, InsertPosition::Before); + new_body.insert_assignment(rvalue, &mut source, InsertPosition::Before); let reason = format!( "Kani currently doesn't support checking memory initialization of `{target_ty}`. {msg}" ); new_body.insert_check( tcx, &self.check_type, - &mut terminator, + &mut source, InsertPosition::Before, result, &reason, diff --git a/tests/expected/uninit/multiple-instrumentations.expected b/tests/expected/uninit/multiple-instrumentations.expected new file mode 100644 index 000000000000..153024dc692b --- /dev/null +++ b/tests/expected/uninit/multiple-instrumentations.expected @@ -0,0 +1,20 @@ +multiple_instrumentations_different_vars.assertion.3\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`" + +multiple_instrumentations_different_vars.assertion.4\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u64`" + +multiple_instrumentations.assertion.2\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`" + +multiple_instrumentations.assertion.3\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`" + +Summary: +Verification failed for - multiple_instrumentations_different_vars +Verification failed for - multiple_instrumentations +Complete - 0 successfully verified harnesses, 2 failures, 2 total. diff --git a/tests/expected/uninit/multiple-instrumentations.rs b/tests/expected/uninit/multiple-instrumentations.rs new file mode 100644 index 000000000000..8f61bb82b6d6 --- /dev/null +++ b/tests/expected/uninit/multiple-instrumentations.rs @@ -0,0 +1,42 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z uninit-checks + +/// Ensure instrumentation works correctly when a single instruction gets multiple instrumentations. +#[kani::proof] +fn multiple_instrumentations() { + unsafe { + let mut value: u128 = 0; + // Cast between two pointers of different padding. + let ptr = &mut value as *mut _ as *mut (u8, u32, u64); + *ptr = (4, 4, 4); + // Here, instrumentation is added 2 times before the function call and 1 time after. + value = helper_1(value, value); + } +} + +fn helper_1(a: u128, b: u128) -> u128 { + a + b +} + +/// Ensure instrumentation works correctly when a single instruction gets multiple instrumentations +/// (and variables are different). +#[kani::proof] +fn multiple_instrumentations_different_vars() { + unsafe { + let mut a: u128 = 0; + let mut b: u64 = 0; + // Cast between two pointers of different padding. + let ptr_a = &mut a as *mut _ as *mut (u8, u32, u64); + *ptr_a = (4, 4, 4); + // Cast between two pointers of different padding. + let ptr_b = &mut b as *mut _ as *mut (u8, u32); + *ptr_b = (4, 4); + // Here, instrumentation is added 2 times before the function call and 1 time after. + a = helper_2(a, b); + } +} + +fn helper_2(a: u128, b: u64) -> u128 { + a + (b as u128) +} From 56e2a2f21f36e1f2dea5113aa907b2db8748f635 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Thu, 22 Aug 2024 11:14:13 -0700 Subject: [PATCH 13/40] Re-enabled hierarchical logs in the compiler (#3449) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We reverted the hierarchical logs a while ago (#2581) due to an outdated dependency that has since been fixed. I think this makes the logs much more readable, by indenting the logs given the scope. More than one scope makes the lines way too long, which I think it's harder to read. This is how the logs look without this change: ``` 2024-08-17T02:42:21.874979Z DEBUG CodegenFunction{name="kani::assert"}: kani_compiler::codegen_cprover_gotoc::utils::debug: handling kani::assert 2024-08-17T02:42:21.875008Z DEBUG CodegenFunction{name="kani::assert"}: kani_compiler::codegen_cprover_gotoc::utils::debug: variables: 2024-08-17T02:42:21.875026Z DEBUG CodegenFunction{name="kani::assert"}: kani_compiler::codegen_cprover_gotoc::utils::debug: let _0: Ty { id: 4, kind: RigidTy(Tuple([])) } ``` This is how it looks after this change: ``` ┐kani_compiler::codegen_cprover_gotoc::codegen::function::CodegenFunction name="kani::assert" ├─── DEBUG kani_compiler::codegen_cprover_gotoc::utils::debug handling kani::assert ├─── DEBUG kani_compiler::codegen_cprover_gotoc::utils::debug variables: ├─── DEBUG kani_compiler::codegen_cprover_gotoc::utils::debug let _0: Ty { id: 4, kind: RigidTy(Tuple([])) } ├─── DEBUG kani_compiler::codegen_cprover_gotoc::utils::debug let _1: Ty { id: 6, kind: RigidTy(Bool) } ``` By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- Cargo.lock | 24 +++++++++++++++++++++++- kani-compiler/Cargo.toml | 1 + kani-compiler/src/session.rs | 8 ++++++-- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f371c8356e5..878d61a62889 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -475,6 +475,7 @@ dependencies = [ "strum_macros", "tracing", "tracing-subscriber", + "tracing-tree", ] [[package]] @@ -617,6 +618,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "num" version = "0.4.3" @@ -1263,7 +1273,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", - "nu-ansi-term", + "nu-ansi-term 0.46.0", "once_cell", "parking_lot", "regex", @@ -1278,6 +1288,18 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "tracing-tree" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f459ca79f1b0d5f71c54ddfde6debfc59c8b6eeb46808ae492077f739dc7b49c" +dependencies = [ + "nu-ansi-term 0.50.1", + "tracing-core", + "tracing-log", + "tracing-subscriber", +] + [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/kani-compiler/Cargo.toml b/kani-compiler/Cargo.toml index 24ed00f54338..fcb33b8074e4 100644 --- a/kani-compiler/Cargo.toml +++ b/kani-compiler/Cargo.toml @@ -24,6 +24,7 @@ strum_macros = "0.26" shell-words = "1.0.0" tracing = {version = "0.1", features = ["max_level_trace", "release_max_level_debug"]} tracing-subscriber = {version = "0.3.8", features = ["env-filter", "json", "fmt"]} +tracing-tree = "0.4.0" # Future proofing: enable backend dependencies using feature. [features] diff --git a/kani-compiler/src/session.rs b/kani-compiler/src/session.rs index 7ec2b79a0469..ecc084e0cc85 100644 --- a/kani-compiler/src/session.rs +++ b/kani-compiler/src/session.rs @@ -19,6 +19,7 @@ use std::io::IsTerminal; use std::panic; use std::sync::LazyLock; use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry}; +use tracing_tree::HierarchicalLayer; /// Environment variable used to control this session log tracing. const LOG_ENV_VAR: &str = "KANI_LOG"; @@ -114,10 +115,13 @@ fn hier_logs(args: &Arguments, filter: EnvFilter) { let use_colors = std::io::stdout().is_terminal() || args.color_output; let subscriber = Registry::default().with(filter); let subscriber = subscriber.with( - tracing_subscriber::fmt::layer() + HierarchicalLayer::default() .with_writer(std::io::stderr) + .with_indent_lines(true) .with_ansi(use_colors) - .with_target(true), + .with_targets(true) + .with_verbose_exit(true) + .with_indent_amount(4), ); tracing::subscriber::set_global_default(subscriber).unwrap(); } From 7a2866754c34cf6e47bb5d431469a1716f17a980 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Fri, 23 Aug 2024 08:00:52 -0700 Subject: [PATCH 14/40] Fix ICE due to mishandling of Aggregate rvalue for raw pointers to `str` (#3448) We were missing a match arm for the case where a raw pointer to a string slice was created from a thin pointer and the string size. Resolves #3312 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../codegen_cprover_gotoc/codegen/rvalue.rs | 8 ++++++++ tests/kani/Str/raw_ptr.rs | 20 +++++++++++++++++++ tests/perf/smol_str/Cargo.toml | 11 ++++++++++ tests/perf/smol_str/expected | 4 ++++ tests/perf/smol_str/src/lib.rs | 13 ++++++++++++ 5 files changed, 56 insertions(+) create mode 100644 tests/kani/Str/raw_ptr.rs create mode 100644 tests/perf/smol_str/Cargo.toml create mode 100644 tests/perf/smol_str/expected create mode 100644 tests/perf/smol_str/src/lib.rs diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs index a30eeb7639ce..e8f1424f09a3 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs @@ -694,6 +694,14 @@ impl<'tcx> GotocCtx<'tcx> { let meta = self.codegen_operand_stable(&operands[1]); slice_fat_ptr(typ, data_cast, meta, &self.symbol_table) } + TyKind::RigidTy(RigidTy::Str) => { + let pointee_goto_typ = Type::unsigned_int(8); + // cast data to pointer with specified type + let data_cast = + data.cast_to(Type::Pointer { typ: Box::new(pointee_goto_typ) }); + let meta = self.codegen_operand_stable(&operands[1]); + slice_fat_ptr(typ, data_cast, meta, &self.symbol_table) + } TyKind::RigidTy(RigidTy::Adt(..)) => { let pointee_goto_typ = self.codegen_ty_stable(pointee_ty); let data_cast = diff --git a/tests/kani/Str/raw_ptr.rs b/tests/kani/Str/raw_ptr.rs new file mode 100644 index 000000000000..84d33bc608cc --- /dev/null +++ b/tests/kani/Str/raw_ptr.rs @@ -0,0 +1,20 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! Checks that Kani can handle creating pointers for slices from raw parts. +//! This used to trigger an ICE reported in . +#![feature(ptr_metadata)] + +#[derive(kani::Arbitrary)] +struct AscII { + #[safety_constraint(*inner < 128)] + inner: u8, +} + +#[kani::proof] +fn check_from_raw() { + let ascii: [AscII; 5] = kani::any(); + let slice_ptr: *const [AscII] = &ascii; + let (ptr, metadata) = slice_ptr.to_raw_parts(); + let str_ptr: *const str = std::ptr::from_raw_parts(ptr, metadata); + assert!(unsafe { (&*str_ptr).is_ascii() }); +} diff --git a/tests/perf/smol_str/Cargo.toml b/tests/perf/smol_str/Cargo.toml new file mode 100644 index 000000000000..2edab49870ea --- /dev/null +++ b/tests/perf/smol_str/Cargo.toml @@ -0,0 +1,11 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +[package] +name = "check_smol_str" +version = "0.1.0" +edition = "2021" + +[dependencies] +# Make dependency fixed to ensure the test stays the same. +smol_str = "=0.2.2" diff --git a/tests/perf/smol_str/expected b/tests/perf/smol_str/expected new file mode 100644 index 000000000000..af045d69d75b --- /dev/null +++ b/tests/perf/smol_str/expected @@ -0,0 +1,4 @@ +Checking harness check_new... +VERIFICATION:- SUCCESSFUL + +Complete - 1 successfully verified harnesses, 0 failures, 1 total. diff --git a/tests/perf/smol_str/src/lib.rs b/tests/perf/smol_str/src/lib.rs new file mode 100644 index 000000000000..7fe771c1fc07 --- /dev/null +++ b/tests/perf/smol_str/src/lib.rs @@ -0,0 +1,13 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! Test that Kani can correctly verify the cedar implementation of `SmolStr` +//! An ICE was initially reported for this case in: +//! + +#[kani::proof] +#[kani::unwind(4)] +fn check_new() { + let data: [char; 3] = kani::any(); + let input: String = data.iter().collect(); + smol_str::SmolStr::new(&input); +} From 1f0b47f98383b4c9901c64ab708eb68b2ec5525f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 09:59:34 +0200 Subject: [PATCH 15/40] Automatic cargo update to 2024-08-26 (#3459) Dependency upgrade resulting from `cargo update`. Co-authored-by: tautschnig <1144736+tautschnig@users.noreply.github.com> --- Cargo.lock | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 878d61a62889..d13ff68b32ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -176,7 +176,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -346,9 +346,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fixedbitset" @@ -529,7 +529,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -551,9 +551,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.157" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374af5f94e54fa97cf75e945cce8a6b201e88a1a07e688b47dfd2a59c66dbd86" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "linear-map" @@ -811,9 +811,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -990,29 +990,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.208" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" dependencies = [ "itoa", "memchr", @@ -1112,7 +1112,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -1127,9 +1127,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.75" +version = "2.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" +checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" dependencies = [ "proc-macro2", "quote", @@ -1166,7 +1166,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -1232,7 +1232,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -1519,5 +1519,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] From ac1016416a2414f088c7ffaa2cadc2267f6a0721 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 17:31:30 -0400 Subject: [PATCH 16/40] Bump tests/perf/s2n-quic from `80b93a7` to `8f7c04b` (#3460) Bumps [tests/perf/s2n-quic](https://github.com/aws/s2n-quic) from `80b93a7` to `8f7c04b`.
Commits

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/perf/s2n-quic | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/perf/s2n-quic b/tests/perf/s2n-quic index 80b93a7f1d18..8f7c04be292f 160000 --- a/tests/perf/s2n-quic +++ b/tests/perf/s2n-quic @@ -1 +1 @@ -Subproject commit 80b93a7f1d187fef005c8c896ab99278b6865dbe +Subproject commit 8f7c04be292f6419b38e37bd7ff67f15833735d7 From 28f8f22c855e8276f04afe9600f8ae7b7f913aba Mon Sep 17 00:00:00 2001 From: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> Date: Mon, 26 Aug 2024 16:50:40 -0700 Subject: [PATCH 17/40] Update deny action (#3461) The current `cargo deny` configuration in `deny.toml` uses several keys that have been deprecated. This PR removes the deprecated keys, and updates the deny action to use v2 (as well as renames it from `audit.yml` to `deny.yml`). The only semantic difference is that `cargo deny` will now reject crates that are maintained or have a notice on them, whereas previously, our configuration set both to "warn". As mentioned in the docs though, one can add an "ignore" if needed to bypass those advisories: https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html#the-version-field-optional By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .github/workflows/{audit.yml => deny.yml} | 4 ++-- deny.toml | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) rename .github/workflows/{audit.yml => deny.yml} (87%) diff --git a/.github/workflows/audit.yml b/.github/workflows/deny.yml similarity index 87% rename from .github/workflows/audit.yml rename to .github/workflows/deny.yml index 5b75d6162c85..7ce00cabd2f9 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/deny.yml @@ -4,7 +4,7 @@ # 1. Checks licenses for allowed license. # 2. Checks Rust-Sec registry for security advisories. -name: Cargo Audit +name: Cargo Deny on: pull_request: merge_group: @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: EmbarkStudios/cargo-deny-action@v1 + - uses: EmbarkStudios/cargo-deny-action@v2 with: arguments: --all-features --workspace command-arguments: -s diff --git a/deny.toml b/deny.toml index 39be523ebd19..733f91e12f36 100644 --- a/deny.toml +++ b/deny.toml @@ -7,10 +7,7 @@ [advisories] db-path = "~/.cargo/advisory-db" db-urls = ["https://github.com/rustsec/advisory-db"] -vulnerability = "deny" -unmaintained = "warn" yanked = "deny" -notice = "warn" # A list of advisory IDs to ignore. Note that ignored advisories will still # output a note when they are encountered. ignore = [ @@ -21,19 +18,14 @@ ignore = [ # More documentation for the licenses section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html [licenses] -default = "deny" -unlicensed = "deny" -copyleft = "deny" allow = [ "MIT", "Apache-2.0", ] -allow-osi-fsf-free = "neither" confidence-threshold = 0.8 # All these exceptions should probably appear in: tools/build-kani/license-notes.txt exceptions = [ - { name = "Inflector", allow=["BSD-2-Clause"] }, { name = "unicode-ident", allow=["Unicode-DFS-2016"] }, ] From db9c45c49525819b7d1de63a14efa7fb4df4b03c Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 26 Aug 2024 22:00:26 -0400 Subject: [PATCH 18/40] Basic support for memory initialization checks for unions (#3444) This PR introduces very basic support for memory initialization checks for unions. As of now, the following is supported: - Unions can be created - Union fields can be assigned to - Union fields can be read from - Unions can be assigned directly to another union For more information about planned functionality, see #3300 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../kani_middle/transform/check_uninit/mod.rs | 174 +++++++++++++++--- .../check_uninit/ptr_uninit/uninit_visitor.rs | 81 +++++++- .../check_uninit/relevant_instruction.rs | 174 ++++++++++++++---- .../transform/check_uninit/ty_layout.rs | 43 ++++- .../kani_middle/transform/kani_intrinsics.rs | 20 ++ library/kani/src/mem_init.rs | 12 +- tests/expected/uninit/fixme_unions.rs | 61 ++++++ tests/expected/uninit/intrinsics/expected | 6 +- tests/expected/uninit/unions.expected | 17 ++ tests/expected/uninit/unions.rs | 68 +++++++ 10 files changed, 583 insertions(+), 73 deletions(-) create mode 100644 tests/expected/uninit/fixme_unions.rs create mode 100644 tests/expected/uninit/unions.expected create mode 100644 tests/expected/uninit/unions.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 88d906aa3134..0130c00d1a71 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -35,16 +35,29 @@ pub trait TargetFinder { fn find_all(self, body: &MutableBody) -> Vec; } +const KANI_IS_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniIsPtrInitialized"; +const KANI_SET_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniSetPtrInitialized"; +const KANI_IS_SLICE_CHUNK_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniIsSliceChunkPtrInitialized"; +const KANI_SET_SLICE_CHUNK_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniSetSliceChunkPtrInitialized"; +const KANI_IS_SLICE_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniIsSlicePtrInitialized"; +const KANI_SET_SLICE_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniSetSlicePtrInitialized"; +const KANI_IS_STR_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniIsStrPtrInitialized"; +const KANI_SET_STR_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniSetStrPtrInitialized"; +const KANI_COPY_INIT_STATE_DIAGNOSTIC: &str = "KaniCopyInitState"; +const KANI_COPY_INIT_STATE_SINGLE_DIAGNOSTIC: &str = "KaniCopyInitStateSingle"; + // Function bodies of those functions will not be instrumented as not to cause infinite recursion. const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = &[ - "KaniIsPtrInitialized", - "KaniSetPtrInitialized", - "KaniIsSliceChunkPtrInitialized", - "KaniSetSliceChunkPtrInitialized", - "KaniIsSlicePtrInitialized", - "KaniSetSlicePtrInitialized", - "KaniIsStrPtrInitialized", - "KaniSetStrPtrInitialized", + KANI_IS_PTR_INITIALIZED_DIAGNOSTIC, + KANI_SET_PTR_INITIALIZED_DIAGNOSTIC, + KANI_IS_SLICE_CHUNK_PTR_INITIALIZED_DIAGNOSTIC, + KANI_SET_SLICE_CHUNK_PTR_INITIALIZED_DIAGNOSTIC, + KANI_IS_SLICE_PTR_INITIALIZED_DIAGNOSTIC, + KANI_SET_SLICE_PTR_INITIALIZED_DIAGNOSTIC, + KANI_IS_STR_PTR_INITIALIZED_DIAGNOSTIC, + KANI_SET_STR_PTR_INITIALIZED_DIAGNOSTIC, + KANI_COPY_INIT_STATE_DIAGNOSTIC, + KANI_COPY_INIT_STATE_SINGLE_DIAGNOSTIC, ]; /// Instruments the code with checks for uninitialized memory, agnostic to the source of targets. @@ -164,8 +177,14 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { } MemoryInitOp::SetSliceChunk { .. } | MemoryInitOp::Set { .. } - | MemoryInitOp::SetRef { .. } => self.build_set(body, source, operation, pointee_info), + | MemoryInitOp::SetRef { .. } + | MemoryInitOp::CreateUnion { .. } => { + self.build_set(body, source, operation, pointee_info) + } MemoryInitOp::Copy { .. } => self.build_copy(body, source, operation, pointee_info), + MemoryInitOp::AssignUnion { .. } => { + self.build_assign_union(body, source, operation, pointee_info) + } MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => { unreachable!() } @@ -196,12 +215,12 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { // pass is as an argument. let (diagnostic, args) = match &operation { MemoryInitOp::Check { .. } | MemoryInitOp::CheckRef { .. } => { - let diagnostic = "KaniIsPtrInitialized"; + let diagnostic = KANI_IS_PTR_INITIALIZED_DIAGNOSTIC; let args = vec![ptr_operand.clone(), layout_operand]; (diagnostic, args) } MemoryInitOp::CheckSliceChunk { .. } => { - let diagnostic = "KaniIsSliceChunkPtrInitialized"; + let diagnostic = KANI_IS_SLICE_CHUNK_PTR_INITIALIZED_DIAGNOSTIC; let args = vec![ptr_operand.clone(), layout_operand, operation.expect_count()]; (diagnostic, args) @@ -232,10 +251,10 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { // 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") + (slicee_ty, KANI_IS_SLICE_PTR_INITIALIZED_DIAGNOSTIC) } TyKind::RigidTy(RigidTy::Str) => { - (Ty::unsigned_ty(UintTy::U8), "KaniIsStrPtrInitialized") + (Ty::unsigned_ty(UintTy::U8), KANI_IS_STR_PTR_INITIALIZED_DIAGNOSTIC) } _ => unreachable!(), }; @@ -266,6 +285,14 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { self.inject_assert_false(self.tcx, body, source, operation.position(), reason); return; } + PointeeLayout::Union { .. } => { + // Here we are reading from a pointer to a union. + // TODO: we perhaps need to check that the union at least contains an intersection + // of all layouts initialized. + let reason = "Interaction between raw pointers and unions is not yet supported."; + self.inject_assert_false(self.tcx, body, source, operation.position(), reason); + return; + } }; // Construct the basic block and insert it into the body. @@ -317,7 +344,7 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { // pass is as an argument. let (diagnostic, args) = match &operation { MemoryInitOp::Set { .. } | MemoryInitOp::SetRef { .. } => { - let diagnostic = "KaniSetPtrInitialized"; + let diagnostic = KANI_SET_PTR_INITIALIZED_DIAGNOSTIC; let args = vec![ ptr_operand, layout_operand, @@ -330,7 +357,7 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { (diagnostic, args) } MemoryInitOp::SetSliceChunk { .. } => { - let diagnostic = "KaniSetSliceChunkPtrInitialized"; + let diagnostic = KANI_SET_SLICE_CHUNK_PTR_INITIALIZED_DIAGNOSTIC; let args = vec![ ptr_operand, layout_operand, @@ -369,10 +396,10 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { // 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, "KaniSetSlicePtrInitialized") + (slicee_ty, KANI_SET_SLICE_PTR_INITIALIZED_DIAGNOSTIC) } TyKind::RigidTy(RigidTy::Str) => { - (Ty::unsigned_ty(UintTy::U8), "KaniSetStrPtrInitialized") + (Ty::unsigned_ty(UintTy::U8), KANI_SET_STR_PTR_INITIALIZED_DIAGNOSTIC) } _ => unreachable!(), }; @@ -409,6 +436,63 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { PointeeLayout::TraitObject => { unreachable!("Cannot change the initialization state of a trait object directly."); } + PointeeLayout::Union { field_layouts } => { + // Writing union data, which could be either creating a union from scratch or + // performing some pointer operations with it. If we are creating a union from + // scratch, an operation will contain a union field. + + // TODO: If we don't have a union field, we are either creating a pointer to a union + // or assigning to one. In the former case, it is safe to return from this function, + // since the union must be already tracked (on creation and update). In the latter + // case, we should have been using union assignment instead. Nevertheless, this is + // currently mitigated by injecting `assert!(false)`. + let union_field = match operation.union_field() { + Some(field) => field, + None => { + let reason = + "Interaction between raw pointers and unions is not yet supported."; + self.inject_assert_false( + self.tcx, + body, + source, + operation.position(), + reason, + ); + return; + } + }; + let layout = &field_layouts[union_field]; + let layout_operand = mk_layout_operand(body, &mut statements, source, layout); + let diagnostic = KANI_SET_PTR_INITIALIZED_DIAGNOSTIC; + let args = vec![ + ptr_operand, + layout_operand, + Operand::Constant(ConstOperand { + span: source.span(body.blocks()), + user_ty: None, + const_: MirConst::from_bool(value), + }), + ]; + let set_ptr_initialized_instance = resolve_mem_init_fn( + get_mem_init_fn_def(self.tcx, diagnostic, &mut self.mem_init_fn_cache), + layout.len(), + *pointee_info.ty(), + ); + Terminator { + kind: TerminatorKind::Call { + func: Operand::Copy(Place::from(body.new_local( + set_ptr_initialized_instance.ty(), + source.span(body.blocks()), + Mutability::Not, + ))), + args, + destination: ret_place.clone(), + target: Some(0), // this will be overriden in add_bb + unwind: UnwindAction::Terminate, + }, + span: source.span(body.blocks()), + } + } }; // Construct the basic block and insert it into the body. body.insert_bb(BasicBlock { statements, terminator }, source, operation.position()); @@ -426,14 +510,19 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { local: body.new_local(Ty::new_tuple(&[]), source.span(body.blocks()), Mutability::Not), projection: vec![], }; - let PointeeLayout::Sized { layout } = pointee_info.layout() else { unreachable!() }; + let layout_size = pointee_info.layout().maybe_size().unwrap(); let copy_init_state_instance = resolve_mem_init_fn( - get_mem_init_fn_def(self.tcx, "KaniCopyInitState", &mut self.mem_init_fn_cache), - layout.len(), + get_mem_init_fn_def( + self.tcx, + KANI_COPY_INIT_STATE_DIAGNOSTIC, + &mut self.mem_init_fn_cache, + ), + layout_size, *pointee_info.ty(), ); let position = operation.position(); - let MemoryInitOp::Copy { from, to, count } = operation else { unreachable!() }; + let (from, to) = operation.expect_copy_operands(); + let count = operation.expect_count(); body.insert_call( ©_init_state_instance, source, @@ -443,6 +532,49 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { ); } + /// Copy memory initialization state from one union variable to another. + fn build_assign_union( + &mut self, + body: &mut MutableBody, + source: &mut SourceInstruction, + operation: MemoryInitOp, + pointee_info: PointeeInfo, + ) { + let ret_place = Place { + local: body.new_local(Ty::new_tuple(&[]), source.span(body.blocks()), Mutability::Not), + projection: vec![], + }; + let mut statements = vec![]; + let layout_size = pointee_info.layout().maybe_size().unwrap(); + let copy_init_state_instance = resolve_mem_init_fn( + get_mem_init_fn_def( + self.tcx, + KANI_COPY_INIT_STATE_SINGLE_DIAGNOSTIC, + &mut self.mem_init_fn_cache, + ), + layout_size, + *pointee_info.ty(), + ); + let (from, to) = operation.expect_assign_union_operands(body, &mut statements, source); + let terminator = Terminator { + kind: TerminatorKind::Call { + func: Operand::Copy(Place::from(body.new_local( + copy_init_state_instance.ty(), + source.span(body.blocks()), + Mutability::Not, + ))), + args: vec![from, to], + destination: ret_place.clone(), + target: Some(0), // this will be overriden in add_bb + unwind: UnwindAction::Terminate, + }, + span: source.span(body.blocks()), + }; + + // Construct the basic block and insert it into the body. + body.insert_bb(BasicBlock { statements, terminator }, source, operation.position()); + } + fn inject_assert_false( &self, tcx: TyCtxt, diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs index a1689bdfe8c4..207005dafb27 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs @@ -19,10 +19,11 @@ use stable_mir::{ alloc::GlobalAlloc, mono::{Instance, InstanceKind}, visit::{Location, PlaceContext}, - CastKind, LocalDecl, MirVisitor, NonDivergingIntrinsic, Operand, Place, PointerCoercion, - ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, + AggregateKind, CastKind, LocalDecl, MirVisitor, NonDivergingIntrinsic, Operand, Place, + PointerCoercion, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, + TerminatorKind, }, - ty::{ConstantKind, RigidTy, TyKind}, + ty::{AdtKind, ConstantKind, RigidTy, TyKind}, }; pub struct CheckUninitVisitor { @@ -112,7 +113,7 @@ impl MirVisitor for CheckUninitVisitor { } } // 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 place.ty(&self.locals).unwrap().kind().is_raw_ptr() { if let Rvalue::AddressOf(..) = rvalue { self.push_target(MemoryInitOp::Set { operand: Operand::Copy(place.clone()), @@ -121,6 +122,58 @@ impl MirVisitor for CheckUninitVisitor { }); } } + + // TODO: add support for ADTs which could have unions as subfields. Currently, + // if a union as a subfield is detected, `assert!(false)` will be injected from + // the type layout code. + let is_inside_union = { + let mut contains_union = false; + let mut place_to_add_projections = + Place { local: place.local, projection: vec![] }; + for projection_elem in place.projection.iter() { + if place_to_add_projections.ty(&self.locals).unwrap().kind().is_union() { + contains_union = true; + break; + } + place_to_add_projections.projection.push(projection_elem.clone()); + } + contains_union + }; + + // Need to copy some information about union initialization, since lvalue is + // either a union or a field inside a union. + if is_inside_union { + if let Rvalue::Use(operand) = rvalue { + // This is a union-to-union assignment, so we need to copy the + // initialization state. + if place.ty(&self.locals).unwrap().kind().is_union() { + self.push_target(MemoryInitOp::AssignUnion { + lvalue: place.clone(), + rvalue: operand.clone(), + }); + } else { + // This is assignment to a field of a union. + self.push_target(MemoryInitOp::SetRef { + operand: Operand::Copy(place.clone()), + value: true, + position: InsertPosition::After, + }); + } + } + } + + // Create a union from scratch as an aggregate. We handle it here because we + // need to know which field is getting assigned. + if let Rvalue::Aggregate(AggregateKind::Adt(adt_def, _, _, _, union_field), _) = + rvalue + { + if adt_def.kind() == AdtKind::Union { + self.push_target(MemoryInitOp::CreateUnion { + operand: Operand::Copy(place.clone()), + field: union_field.unwrap(), // Safe to unwrap because we know this is a union. + }); + } + } } StatementKind::Deinit(place) => { self.super_statement(stmt, location); @@ -350,12 +403,22 @@ impl MirVisitor for CheckUninitVisitor { }); } } - ProjectionElem::Field(idx, target_ty) => { - if target_ty.kind().is_union() - && (!ptx.is_mutating() || place.projection.len() > idx + 1) + ProjectionElem::Field(_, _) => { + if intermediate_place.ty(&self.locals).unwrap().kind().is_union() + && !ptx.is_mutating() { - self.push_target(MemoryInitOp::Unsupported { - reason: "Kani does not support reasoning about memory initialization of unions.".to_string(), + let contains_deref_projection = + { place.projection.iter().any(|elem| *elem == ProjectionElem::Deref) }; + if contains_deref_projection { + // We do not currently support having a deref projection in the same + // place as union field access. + self.push_target(MemoryInitOp::Unsupported { + reason: "Kani does not yet support performing a dereference on a union field".to_string(), + }); + } + // Accessing a place inside the union, need to check if it is initialized. + self.push_target(MemoryInitOp::CheckRef { + operand: Operand::Copy(place.clone()), }); } } diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs b/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs index 05826969262d..6120f2d3d4c4 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs @@ -6,9 +6,8 @@ use crate::kani_middle::transform::body::{InsertPosition, MutableBody, SourceInstruction}; use stable_mir::{ - mir::{Mutability, Operand, Place, Rvalue, Statement, StatementKind}, - ty::RigidTy, - ty::Ty, + mir::{FieldIdx, Mutability, Operand, Place, Rvalue, Statement, StatementKind}, + ty::{RigidTy, Ty}, }; use strum_macros::AsRefStr; @@ -17,28 +16,65 @@ 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 `sizeof(operand)` bytes. - Check { operand: Operand }, + Check { + operand: Operand, + }, /// Set memory initialization state of data bytes in a memory region starting from the pointer /// `operand` and of length `sizeof(operand)` bytes. - Set { operand: Operand, value: bool, position: InsertPosition }, + Set { + operand: Operand, + value: bool, + position: InsertPosition, + }, /// Check memory initialization of data bytes in a memory region starting from the pointer /// `operand` and of length `count * sizeof(operand)` bytes. - CheckSliceChunk { operand: Operand, count: Operand }, + CheckSliceChunk { + 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. - SetSliceChunk { operand: Operand, count: Operand, value: bool, position: InsertPosition }, + SetSliceChunk { + operand: Operand, + count: Operand, + value: bool, + position: InsertPosition, + }, /// Set memory initialization of data bytes in a memory region starting from the reference to /// `operand` and of length `sizeof(operand)` bytes. - CheckRef { operand: Operand }, + CheckRef { + operand: Operand, + }, /// Set memory initialization of data bytes in a memory region starting from the reference to /// `operand` and of length `sizeof(operand)` bytes. - SetRef { operand: Operand, value: bool, position: InsertPosition }, + SetRef { + operand: Operand, + value: bool, + position: InsertPosition, + }, /// Unsupported memory initialization operation. - Unsupported { reason: String }, + Unsupported { + reason: String, + }, /// Operation that trivially accesses uninitialized memory, results in injecting `assert!(false)`. - TriviallyUnsafe { reason: String }, + TriviallyUnsafe { + reason: String, + }, /// Operation that copies memory initialization state over to another operand. - Copy { from: Operand, to: Operand, count: Operand }, + Copy { + from: Operand, + to: Operand, + count: Operand, + }, + + AssignUnion { + lvalue: Place, + rvalue: Operand, + }, + CreateUnion { + operand: Operand, + field: FieldIdx, + }, } impl MemoryInitOp { @@ -56,28 +92,13 @@ impl MemoryInitOp { | MemoryInitOp::Set { operand, .. } | MemoryInitOp::CheckSliceChunk { operand, .. } | MemoryInitOp::SetSliceChunk { operand, .. } => operand.clone(), - MemoryInitOp::CheckRef { operand, .. } | MemoryInitOp::SetRef { operand, .. } => { - let span = source.span(body.blocks()); - - let ref_local = { - let place = match operand { - Operand::Copy(place) | Operand::Move(place) => place, - Operand::Constant(_) => unreachable!(), - }; - let rvalue = Rvalue::AddressOf(Mutability::Not, place.clone()); - let ret_ty = rvalue.ty(body.locals()).unwrap(); - let result = body.new_local(ret_ty, span, Mutability::Not); - let stmt = Statement { - kind: StatementKind::Assign(Place::from(result), rvalue), - span, - }; - statements.push(stmt); - result - }; - - Operand::Copy(Place { local: ref_local, projection: vec![] }) + MemoryInitOp::CheckRef { operand, .. } + | MemoryInitOp::SetRef { operand, .. } + | MemoryInitOp::CreateUnion { operand, .. } => { + mk_ref(operand, body, statements, source) } MemoryInitOp::Copy { .. } + | MemoryInitOp::AssignUnion { .. } | MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => { unreachable!() @@ -85,13 +106,42 @@ impl MemoryInitOp { } } + /// A helper to access operands of copy operation. + pub fn expect_copy_operands(&self) -> (Operand, Operand) { + match self { + MemoryInitOp::Copy { from, to, .. } => (from.clone(), to.clone()), + _ => unreachable!(), + } + } + + /// A helper to access operands of union assign, automatically creates references to them. + pub fn expect_assign_union_operands( + &self, + body: &mut MutableBody, + statements: &mut Vec, + source: &mut SourceInstruction, + ) -> (Operand, Operand) { + match self { + MemoryInitOp::AssignUnion { lvalue, rvalue } => { + let lvalue_as_operand = Operand::Copy(lvalue.clone()); + ( + mk_ref(rvalue, body, statements, source), + mk_ref(&lvalue_as_operand, body, statements, source), + ) + } + _ => unreachable!(), + } + } + pub fn operand_ty(&self, body: &MutableBody) -> Ty { match self { MemoryInitOp::Check { operand, .. } | MemoryInitOp::Set { operand, .. } | MemoryInitOp::CheckSliceChunk { operand, .. } | MemoryInitOp::SetSliceChunk { operand, .. } => operand.ty(body.locals()).unwrap(), - MemoryInitOp::SetRef { operand, .. } | MemoryInitOp::CheckRef { operand, .. } => { + MemoryInitOp::SetRef { operand, .. } + | MemoryInitOp::CheckRef { operand, .. } + | MemoryInitOp::CreateUnion { operand, .. } => { let place = match operand { Operand::Copy(place) | Operand::Move(place) => place, Operand::Constant(_) => unreachable!(), @@ -117,6 +167,12 @@ impl MemoryInitOp { assert!(from_pointee_ty == to_pointee_ty); from.ty(body.locals()).unwrap() } + MemoryInitOp::AssignUnion { lvalue, .. } => { + // It does not matter which operand to return for layout generation, since both of + // them have the same pointee type. + let address_of = Rvalue::AddressOf(Mutability::Not, lvalue.clone()); + address_of.ty(body.locals()).unwrap() + } } } @@ -129,6 +185,8 @@ impl MemoryInitOp { | MemoryInitOp::Set { .. } | MemoryInitOp::CheckRef { .. } | MemoryInitOp::SetRef { .. } + | MemoryInitOp::CreateUnion { .. } + | MemoryInitOp::AssignUnion { .. } | MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => unreachable!(), } @@ -139,12 +197,30 @@ impl MemoryInitOp { MemoryInitOp::Set { value, .. } | MemoryInitOp::SetSliceChunk { value, .. } | MemoryInitOp::SetRef { value, .. } => *value, + MemoryInitOp::CreateUnion { .. } => true, MemoryInitOp::Check { .. } | MemoryInitOp::CheckSliceChunk { .. } | MemoryInitOp::CheckRef { .. } | MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } - | MemoryInitOp::Copy { .. } => unreachable!(), + | MemoryInitOp::Copy { .. } + | MemoryInitOp::AssignUnion { .. } => unreachable!(), + } + } + + pub fn union_field(&self) -> Option { + match self { + MemoryInitOp::CreateUnion { field, .. } => Some(*field), + MemoryInitOp::Check { .. } + | MemoryInitOp::CheckSliceChunk { .. } + | MemoryInitOp::CheckRef { .. } + | MemoryInitOp::Set { .. } + | MemoryInitOp::SetSliceChunk { .. } + | MemoryInitOp::SetRef { .. } + | MemoryInitOp::Unsupported { .. } + | MemoryInitOp::TriviallyUnsafe { .. } + | MemoryInitOp::Copy { .. } + | MemoryInitOp::AssignUnion { .. } => None, } } @@ -158,7 +234,9 @@ impl MemoryInitOp { | MemoryInitOp::CheckRef { .. } | MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => InsertPosition::Before, - MemoryInitOp::Copy { .. } => InsertPosition::After, + MemoryInitOp::Copy { .. } + | MemoryInitOp::AssignUnion { .. } + | MemoryInitOp::CreateUnion { .. } => InsertPosition::After, } } } @@ -184,3 +262,29 @@ impl InitRelevantInstruction { } } } + +/// A helper to generate instrumentation for taking a reference to a given operand. Returns the +/// operand which is a reference and stores all instrumentation in the statements vector passed. +fn mk_ref( + operand: &Operand, + body: &mut MutableBody, + statements: &mut Vec, + source: &mut SourceInstruction, +) -> Operand { + let span = source.span(body.blocks()); + + let ref_local = { + let place = match operand { + Operand::Copy(place) | Operand::Move(place) => place, + Operand::Constant(_) => unreachable!(), + }; + let rvalue = Rvalue::AddressOf(Mutability::Not, place.clone()); + let ret_ty = rvalue.ty(body.locals()).unwrap(); + let result = body.new_local(ret_ty, span, Mutability::Not); + let stmt = Statement { kind: StatementKind::Assign(Place::from(result), rvalue), span }; + statements.push(stmt); + result + }; + + Operand::Copy(Place { local: ref_local, 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 8a162d5944d3..dac130f11545 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 @@ -6,7 +6,7 @@ use stable_mir::{ abi::{FieldsShape, Scalar, TagEncoding, ValueAbi, VariantsShape}, target::{MachineInfo, MachineSize}, - ty::{AdtKind, IndexedVal, RigidTy, Ty, TyKind, UintTy}, + ty::{AdtKind, IndexedVal, RigidTy, Ty, TyKind, UintTy, VariantIdx}, CrateDef, }; @@ -43,10 +43,26 @@ pub enum PointeeLayout { Sized { layout: Layout }, /// Layout of slices, *const/mut str is included in this case and treated as *const/mut [u8]. Slice { element_layout: Layout }, + /// Layout of unions, which are shared storage for multiple fields of potentially different layouts. + Union { field_layouts: Vec }, /// Trait objects have an arbitrary layout. TraitObject, } +impl PointeeLayout { + /// Returns the size of the layout, if available. + pub fn maybe_size(&self) -> Option { + match self { + PointeeLayout::Sized { layout } => Some(layout.len()), + PointeeLayout::Slice { element_layout } => Some(element_layout.len()), + PointeeLayout::Union { field_layouts } => { + Some(field_layouts.iter().map(|field_layout| field_layout.len()).max().unwrap()) + } + PointeeLayout::TraitObject => None, + } + } +} + pub struct PointeeInfo { pointee_ty: Ty, layout: PointeeLayout, @@ -56,6 +72,25 @@ impl PointeeInfo { pub fn from_ty(ty: Ty) -> Result { match ty.kind() { TyKind::RigidTy(rigid_ty) => match rigid_ty { + RigidTy::Adt(adt_def, args) if adt_def.kind() == AdtKind::Union => { + assert!(adt_def.variants().len() == 1); + let fields: Result<_, _> = adt_def + .variant(VariantIdx::to_val(0)) + .unwrap() + .fields() + .into_iter() + .map(|field_def| { + let ty = field_def.ty_with_args(&args); + let size_in_bytes = ty.layout().unwrap().shape().size.bytes(); + data_bytes_for_ty(&MachineInfo::target(), ty, 0) + .map(|data_chunks| generate_byte_mask(size_in_bytes, data_chunks)) + }) + .collect(); + Ok(PointeeInfo { + pointee_ty: ty, + layout: PointeeLayout::Union { field_layouts: fields? }, + }) + } RigidTy::Str => { let slicee_ty = Ty::unsigned_ty(UintTy::U8); let size_in_bytes = slicee_ty.layout().unwrap().shape().size.bytes(); @@ -330,7 +365,7 @@ fn data_bytes_for_ty( | RigidTy::Dynamic(_, _, _) => Err(format!("Unsupported {ty:?}")), } } - FieldsShape::Union(_) => Err(format!("Unsupported {ty:?}")), + FieldsShape::Union(_) => Err(format!("Unions as fields of unions are unsupported {ty:?}")), FieldsShape::Array { .. } => Ok(vec![]), } } @@ -357,12 +392,12 @@ fn tys_layout_cmp_to_size(from_ty: &Ty, to_ty: &Ty, cmp: impl Fn(bool, bool) -> let from_ty_layout = match from_ty_info.layout() { PointeeLayout::Sized { layout } => layout, PointeeLayout::Slice { element_layout } => element_layout, - PointeeLayout::TraitObject => return false, + PointeeLayout::TraitObject | PointeeLayout::Union { .. } => return false, }; let to_ty_layout = match to_ty_info.layout() { PointeeLayout::Sized { layout } => layout, PointeeLayout::Slice { element_layout } => element_layout, - PointeeLayout::TraitObject => return false, + PointeeLayout::TraitObject | PointeeLayout::Union { .. } => return false, }; // Ensure `to_ty_layout` does not have a larger size. if to_ty_layout.len() <= from_ty_layout.len() { diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index dd804b7379f2..9210c43fb163 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -307,6 +307,26 @@ impl IntrinsicGeneratorPass { new_body.insert_assignment(rvalue, &mut source, InsertPosition::Before); let reason: &str = "Kani does not support reasoning about memory initialization of pointers to trait objects."; + new_body.insert_check( + tcx, + &self.check_type, + &mut source, + InsertPosition::Before, + result, + &reason, + ); + } + PointeeLayout::Union { .. } => { + let rvalue = Rvalue::Use(Operand::Constant(ConstOperand { + const_: MirConst::from_bool(false), + span: source.span(new_body.blocks()), + user_ty: None, + })); + let result = + new_body.insert_assignment(rvalue, &mut source, InsertPosition::Before); + let reason: &str = + "Kani does not yet support using initialization predicates on unions."; + new_body.insert_check( tcx, &self.check_type, diff --git a/library/kani/src/mem_init.rs b/library/kani/src/mem_init.rs index 3755fc59a510..a6118c472a92 100644 --- a/library/kani/src/mem_init.rs +++ b/library/kani/src/mem_init.rs @@ -309,7 +309,9 @@ fn set_str_ptr_initialized( } } -/// Copy initialization state of `size_of:: * num_elts` bytes from one pointer to the other. +/// Copy initialization state of `size_of:: * num_elts` bytes from one pointer to the other. Note +/// that in this case `LAYOUT_SIZE == size_of::`. +#[kanitool::disable_checks(pointer)] #[rustc_diagnostic_item = "KaniCopyInitState"] fn copy_init_state(from: *const T, to: *const T, num_elts: usize) { if LAYOUT_SIZE == 0 { @@ -321,3 +323,11 @@ fn copy_init_state(from: *const T, to: *const T, nu MEM_INIT_STATE.copy::(from_ptr as *const u8, to_ptr as *const u8, num_elts); } } + +/// Copy initialization state of `size_of::` bytes from one pointer to the other. Note that in +/// this case `LAYOUT_SIZE == size_of::`. +#[kanitool::disable_checks(pointer)] +#[rustc_diagnostic_item = "KaniCopyInitStateSingle"] +fn copy_init_state_single(from: *const T, to: *const T) { + copy_init_state::(from, to, 1); +} diff --git a/tests/expected/uninit/fixme_unions.rs b/tests/expected/uninit/fixme_unions.rs new file mode 100644 index 000000000000..ec86e5e7e07f --- /dev/null +++ b/tests/expected/uninit/fixme_unions.rs @@ -0,0 +1,61 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z uninit-checks + +//! Tests for handling potentially uninitialized memory access via unions. +//! TODO: add a `.expected` file for this test. + +use std::ptr::addr_of; + +#[repr(C)] +#[derive(Clone, Copy)] +union U { + a: u16, + b: u32, +} + +/// Reading padding data via simple union access if union is passed to another function. +#[kani::proof] +unsafe fn cross_function_union_should_fail() { + unsafe fn helper(u: U) { + let padding = u.b; // Read 4 bytes from `u`. + } + let u = U { a: 0 }; // `u` is initialized for 2 bytes. + helper(u); +} + +/// Reading padding data but a union is behind a pointer. +#[kani::proof] +unsafe fn pointer_union_should_fail() { + let u = U { a: 0 }; // `u` is initialized for 2 bytes. + let u_ptr = addr_of!(u); + let u1 = *u_ptr; + let padding = u1.b; // Read 4 bytes from `u`. +} + +#[repr(C)] +struct S { + u: U, +} + +/// Tests uninitialized access if unions are top-level subfields. +#[kani::proof] +unsafe fn union_as_subfields_should_pass() { + let u = U { a: 0 }; // `u` is initialized for 2 bytes. + let s = S { u }; + let s1 = s; + let u1 = s1.u; // `u1` is initialized for 2 bytes. + let padding = u1.a; // Read 2 bytes from `u`. +} + +union Outer { + u: U, + a: u32, +} + +/// Tests unions composing with other unions. +#[kani::proof] +unsafe fn uber_union_should_pass() { + let u = Outer { u: U { b: 0 } }; // `u` is initialized for 4 bytes. + let non_padding = u.a; // Read 4 bytes from `u`. +} diff --git a/tests/expected/uninit/intrinsics/expected b/tests/expected/uninit/intrinsics/expected index 33392337c30b..b5555785e8af 100644 --- a/tests/expected/uninit/intrinsics/expected +++ b/tests/expected/uninit/intrinsics/expected @@ -1,10 +1,10 @@ -std::ptr::read::>.assertion.1\ +std::ptr::write::>.assertion.1\ - Status: FAILURE\ - - Description: "Kani currently doesn't support checking memory initialization for pointers to `std::mem::MaybeUninit." + - Description: "Interaction between raw pointers and unions is not yet supported." std::ptr::write::>.assertion.1\ - Status: FAILURE\ - - Description: "Kani currently doesn't support checking memory initialization for pointers to `std::mem::MaybeUninit." + - Description: "Interaction between raw pointers and unions is not yet supported."\ check_typed_swap.assertion.1\ - Status: FAILURE\ diff --git a/tests/expected/uninit/unions.expected b/tests/expected/uninit/unions.expected new file mode 100644 index 000000000000..05fdac3a8765 --- /dev/null +++ b/tests/expected/uninit/unions.expected @@ -0,0 +1,17 @@ +union_update_should_fail.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u32`" + +union_complex_subfields_should_fail.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u16`" + +basic_union_should_fail.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u32`" + +Summary: +Verification failed for - union_update_should_fail +Verification failed for - union_complex_subfields_should_fail +Verification failed for - basic_union_should_fail +Complete - 3 successfully verified harnesses, 3 failures, 6 total. diff --git a/tests/expected/uninit/unions.rs b/tests/expected/uninit/unions.rs new file mode 100644 index 000000000000..c623f9fcea94 --- /dev/null +++ b/tests/expected/uninit/unions.rs @@ -0,0 +1,68 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z uninit-checks + +//! Tests for handling potentially uninitialized memory access via unions. + +use std::ptr::addr_of; + +#[repr(C)] +#[derive(Clone, Copy)] +union U { + a: u16, + b: u32, +} + +/// Simple and correct union access. +#[kani::proof] +unsafe fn basic_union_should_pass() { + let u = U { b: 0 }; + let u1 = u; + let non_padding = u1.a; + assert!(non_padding == 0); +} + +/// Reading padding data via simple union access. +#[kani::proof] +unsafe fn basic_union_should_fail() { + let u = U { a: 0 }; + let u1 = u; + let padding = u1.b; +} + +#[repr(C)] +union U1 { + a: (u32, u8), + b: (u32, u16, u8), +} + +/// Tests accessing initialized data via subfields of a union. +#[kani::proof] +unsafe fn union_complex_subfields_should_pass() { + let u = U1 { a: (0, 0) }; + let non_padding = u.b.0; +} + +/// Tests accessing uninit data via subfields of a union. +#[kani::proof] +unsafe fn union_complex_subfields_should_fail() { + let u = U1 { a: (0, 0) }; + let padding = u.b.1; +} + +/// Tests overwriting data inside unions. +#[kani::proof] +unsafe fn union_update_should_pass() { + let mut u = U { a: 0 }; + u.b = 0; + let non_padding = u.b; + assert!(non_padding == 0); +} + +/// Tests overwriting data inside unions. +#[kani::proof] +unsafe fn union_update_should_fail() { + let mut u = U { a: 0 }; + u.a = 0; + let padding = u.b; +} From d5c1b71c3da1d3d1e84235487b5b4acfed41595f Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Tue, 27 Aug 2024 15:51:33 +0200 Subject: [PATCH 19/40] Adjust test patterns so as not to check for trivial properties (#3464) With diffblue/cbmc#8413, CBMC will no longer create property checks for assigns clauses that are trivially true. We had CBMC-Nightly failing since August 17th given the CBMC change. This will bring CBMC-Nightly back to a passing state. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- tests/expected/function-contract/modifies/havoc_pass.expected | 2 +- .../function-contract/modifies/havoc_pass_reordered.expected | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/expected/function-contract/modifies/havoc_pass.expected b/tests/expected/function-contract/modifies/havoc_pass.expected index d5679f71b74c..f7bbb8202c46 100644 --- a/tests/expected/function-contract/modifies/havoc_pass.expected +++ b/tests/expected/function-contract/modifies/havoc_pass.expected @@ -7,7 +7,7 @@ VERIFICATION:- SUCCESSFUL .assigns\ - Status: SUCCESS\ -- Description: "Check that var_4 is assignable"\ +- Description: "Check that *dst is assignable"\ in function copy VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/modifies/havoc_pass_reordered.expected b/tests/expected/function-contract/modifies/havoc_pass_reordered.expected index 2ea601aa26a0..7036111c76bf 100644 --- a/tests/expected/function-contract/modifies/havoc_pass_reordered.expected +++ b/tests/expected/function-contract/modifies/havoc_pass_reordered.expected @@ -7,7 +7,7 @@ VERIFICATION:- SUCCESSFUL copy.assigns\ - Status: SUCCESS\ -- Description: "Check that var_5 is assignable"\ +- Description: "Check that *dst is assignable"\ in function copy VERIFICATION:- SUCCESSFUL From 0adc107628bc225ecd97f4efc092ff4d6918021d Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Tue, 27 Aug 2024 11:33:01 -0400 Subject: [PATCH 20/40] Clarify comment in RFC Template (#3462) Looking at the RFC template file, I thought that the recommendation to leave the software design section empty referred to everything below the comment, rather than just that section. This PR updates the comment. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- rfc/src/template.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfc/src/template.md b/rfc/src/template.md index 2628bed3c54e..e2755e90ea27 100644 --- a/rfc/src/template.md +++ b/rfc/src/template.md @@ -43,6 +43,8 @@ No further explanation is needed. ## Software Design +**We recommend that you leave the Software Design section empty for the first version of your RFC**. + This is the beginning of the technical portion of the RFC. From now on, your main audience is Kani developers, so it's OK to assume readers know Kani architecture. @@ -53,8 +55,6 @@ Please provide a high level description your design. - Any changes to how these components communicate? - What corner cases do you anticipate? -**We recommend you to leave this empty for the first version of your RFC**. - ## Rationale and alternatives This is the section where you discuss the decisions you made. From 7a02955bcf5423daf176a91f2bbfee9f29a54839 Mon Sep 17 00:00:00 2001 From: Adrian Palacios <73246657+adpaco-aws@users.noreply.github.com> Date: Tue, 27 Aug 2024 13:24:40 -0400 Subject: [PATCH 21/40] RFC: Source-based code coverage (#3143) Upgrades the Kani coverage feature with the source-based code coverage implementation used in the Rust compiler. Rendered version available [here](https://github.com/adpaco-aws/rmc/blob/rfc-region-cov/rfc/src/rfcs/0011-source-coverage.md). Related to #2640 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- rfc/src/SUMMARY.md | 1 + rfc/src/rfcs/0008-line-coverage.md | 2 +- rfc/src/rfcs/0011-source-coverage.md | 647 +++++++++++++++++++++++++++ 3 files changed, 649 insertions(+), 1 deletion(-) create mode 100644 rfc/src/rfcs/0011-source-coverage.md diff --git a/rfc/src/SUMMARY.md b/rfc/src/SUMMARY.md index e0b31761ae62..0b38dac9cbb5 100644 --- a/rfc/src/SUMMARY.md +++ b/rfc/src/SUMMARY.md @@ -16,3 +16,4 @@ - [0008-line-coverage](rfcs/0008-line-coverage.md) - [0009-function-contracts](rfcs/0009-function-contracts.md) - [0010-quantifiers](rfcs/0010-quantifiers.md) +- [0011-source-coverage](rfcs/0011-source-coverage.md) diff --git a/rfc/src/rfcs/0008-line-coverage.md b/rfc/src/rfcs/0008-line-coverage.md index d2bedab8bdef..a0f287bee96e 100644 --- a/rfc/src/rfcs/0008-line-coverage.md +++ b/rfc/src/rfcs/0008-line-coverage.md @@ -1,7 +1,7 @@ - **Feature Name:** Line coverage (`line-coverage`) - **Feature Request Issue:** - **RFC PR:** -- **Status:** Unstable +- **Status:** Cancelled - **Version:** 0 - **Proof-of-concept:** (Kani) + (Kani VS Code Extension) diff --git a/rfc/src/rfcs/0011-source-coverage.md b/rfc/src/rfcs/0011-source-coverage.md new file mode 100644 index 000000000000..9ea23c72a9d1 --- /dev/null +++ b/rfc/src/rfcs/0011-source-coverage.md @@ -0,0 +1,647 @@ +- **Feature Name:** Source-based code coverage (`source-coverage`) +- **Feature Request Issue:** +- **RFC PR:** +- **Status:** Under Review +- **Version:** 1 +- **Proof-of-concept:** (Kani) + (`kani-cov`) + +------------------- + +## Summary + +A source-based code coverage feature for Kani built on top of Rust's coverage instrumentation. + +## User Impact + +In our first attempt to add a coverage feature fully managed by Kani, we introduced and made available a line coverage option +(see [RFC: Line coverage](0008-line-coverage.md) for more details). +This option has since then allowed us to gather more data around the expectations for a coverage feature in Kani. + +For example, the line coverage output we produced was not easy to interpret +without knowing some implementation details. +Aside from that, the feature requested in +[#2795](https://github.com/model-checking/kani/issues/2795) alludes to the need +of providing coverage-specific tooling in Kani. +Nevertheless, as captured in +[#2640](https://github.com/model-checking/kani/issues/2640), source-based +coverage results provide the clearest and most precise coverage information. + +In this RFC, we propose an integration with [Rust's source-based code coverage +instrumentation](https://doc.rust-lang.org/rustc/instrument-coverage.html). +This integration would allow us to report source-based code coverage results from Kani. +Also, we propose adding a new user-facing, coverage-focused tool called `kani-cov`. +The tool would allow users to process coverage results generated by Kani and produce +coverage artifacts such as summaries and reports according to their preferences. +In the [next section](#user-experience), we will explain in more detail how we +expect `kani-cov` to assist with coverage-related tasks. + +With these changes, we expect our coverage options to become more flexible, +precise and efficient. These options are expected to replace the previous +options available through the line coverage feature. +In the [last section](#future-possibilities) of this RFC, we will also discuss +the requirements for a potential integration of this coverage feature with the +LLVM toolchain. + +## User Experience + +The proposed experience is partially inspired by that of the most popular +coverage frameworks. +First, let us delve into the LLVM coverage workflow, followed by an explanation +of our proposal. + +### The LLVM code coverage workflow + +The LLVM project is home to one of the most popular code coverage frameworks. +The workflow associated to the LLVM framework is described in the documentation for +[source-based code coverage](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html)[^note-source], +but we briefly describe it here to better relate it with our proposal. + +In short, the LLVM code coverage workflow follows three steps: + 1. **Compiling with coverage enabled.** This causes the compiler to generate an instrumented program. + 2. **Running the instrumented program.** This generates binary-encoded `.profraw` files. + 3. **Using tools to aggregate and export coverage information into other formats.** + +When working in a `cargo` project, step 1 can be done through this command: + +```sh +RUSTFLAGS='-Cinstrument-coverage' cargo build +``` + +The same flag must to be used for step 2: + +```sh +RUSTFLAGS='-Cinstrument-coverage' cargo run +``` + +This should populate the directory with at least one `.profraw` file. +Each `.profraw` file corresponds to a specific source code file in your project. + +At this point, we will have produced the artifacts that we generally require for +the LLVM tools: + 1. **The instrumented binary** which, in addition to the instrumented program, + contains additional information (e.g., the coverage mappings) required to + interpret the profiling results. + 2. **The `.profraw` files** which essentially includes the profiling results + (e.g., counter values) for each function of the corresponding source code file. + +For step 3, the commands will depend on what kind of results we want. +Most likely we will have to merge the `.profraw` files and produce a `.profdata` +file as follows: + +```sh +llvm-profdata merge -sparse *.profraw -o output.profdata +``` + +The resulting `.profdata` file will contain the aggregated coverage results from +the `.profraw` files passed to the `merge` command. + +Then, we can use a command such as + +```sh +llvm-cov show target/debug/binary —instr-profile=output.profdata -show-line-counts-or-regions +``` + +to visualize the code coverage through the terminal as in the image: + +![Source-based code coverage with `llvm-cov`](https://github.com/model-checking/kani/assets/73246657/4f8a973d-8977-4c0b-822d-e73ed6d223aa) + +or the command + +```sh +llvm-cov report target/debug/binary --instr-profile=output.profdata --show-region-summary +``` + +to produce coverage summaries like this: + +``` +Filename Regions Missed Regions Cover Functions Missed Functions Executed Lines Missed Lines Cover Branches Missed Branches Cover +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +/long/long/path/to/my/project/binary/src/main.rs 9 3 66.67% 3 1 66.67% 14 4 71.43% 0 0 - +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +TOTAL 9 3 66.67% 3 1 66.67% 14 4 71.43% 0 0 - +``` + +[^note-source]: The LLVM project refers to their own coverage feature as + *source-based code coverage*. It is not rare to see the term *region + coverage* being used instead to refer to the same thing. That is because + LLVM's source-based code coverage feature can report coverage for code + regions, but other coverage frameworks do not support the concept of code + regions. + +### The Kani coverage workflow + +The two main components of the Kani coverage workflow that we propose are the +following: + 1. The existing `--coverage` flag that drives the coverage workflow in Kani, + emits raw coverage data (as in the `.profraw` files), and produces basic + coverage results by default. + 2. A new subcommand `cov` that allows users to further process raw coverage + information emitted by Kani to produce customized coverage results (i.e., + different to the ones produced by default with the `--coverage` option). + The `cov` subcommand is an alias for the `kani-cov` tool. + +In contrast to the LLVM workflow, where human-readable coverage results can +be produced only after a sequence of LLVM tool commands, we provide some +coverage results by default. +This aligns better with our UX philosophy, and removes the need for a wrapper +around our coverage features like +[`cargo-llvm-cov`](https://github.com/taiki-e/cargo-llvm-cov). +Alternatively, the `cov` subcommand offers the ability of producing more +specific coverage results if needed. +We anticipate the `cov` subcommand being particularly useful in less standard +project setups, giving the users the flexibility required to produce coverage +results tailored to their specific needs. + +In the following, we describe each one of these components in more detail. + +#### The `--coverage` option + +The default coverage workflow will be kicked off through the unstable +`--coverage` option: + +```sh +cargo kani --coverage -Zsource-coverage +``` + +The main difference with respect to the regular verification workflow is that, +at the end of the verification-based coverage run, Kani will generate two coverage +results: + - A coverage summary corresponding to the coverage achieved by the harnesses + included in the verification run. This summary will be printed after the + verification output. + - A coverage report corresponding to the coverage achieved by the harnesses + included in the verification run. The report will be placed in the same target + directory where the raw coverage files are put. The path to the report will + also be printed after the verification output. + +Therefore, a typical `--coverage` run could look like this: + +``` +VERIFICATION:- SUCCESSFUL + +Coverage Results: + +| Filename | Regions | Missed Regions | Cover | Functions | Missed Functions | Cover | +| -------- | ------- | -------------- | ----- | --------- | ---------------- | ----- | +| main.rs | 9 | 3 | 66.67 | 3 | 1 | 33.33 | +| file.rs | 11 | 5 | 45.45 | 2 | 1 | 50.00 | + +Coverage report available in target/kani/x86_64-unknown-linux-gnu/cov/kani_2024-04-26_15-30-00/report/index.html +``` + +#### The `cov` subcommand + +The `cov` subcommand will be used to process raw coverage information generated +by Kani and produce coverage outputs as indicated by the user. +Hence, the `cov` subcommand corresponds to the set of LLVM tools +(`llvm-profdata`, `llvm-cov`, etc.) that are used to produce coverage outputs +through the LLVM coverage workflow. + +In contrast to LLVM, we will have a single subcommand for all Kani +coverage-related needs. The `cov` subcommand will just call the `kani-cov` tool, +which is expected to be shipped along the rest of Kani binaries. + +We suggest that the subcommand initially offers two options: + 1. An option to merge the coverage results from one or more files and coverage + mappings[^note-snapshot] into a single file. + 2. An option to produce coverage outputs from coverage results, including summaries + or coverage reports in human-readable formats (e.g., HTML). + +Let's assume that we have run `cargo kani --coverage -Zsource-coverage` and +generated coverage files in the `my-coverage` folder. Then, we would use `cargo +kani cov` as follows to combine the coverage results[^note-exclude] for all +harnesses: + +```sh +cargo kani cov --merge my-coverage/*.kaniraw -o my-coverage.kanicov +``` + +Let's say the user is first interested in reading a coverage summary through the +terminal. +They can use the `--summary` option for that: + +```sh +cargo kani cov --summary my-coverage/default.kanimap -instr-profile=my-coverage.kanicov +``` + +The command could print a coverage summary like: + +``` +| Filename | Regions | Missed Regions | Cover | Functions | ... +| -------- | ------- | -------------- | ----- | --------- | ... +| main.rs | 9 | 3 | 66.67 | 3 | ... +[...] +``` + +Now, let's say the user wants to produce an HTML report of the coverage results. +They will have to use the `--report` option for that: + +```sh +cargo kani cov --report my-coverage/default.kanimap -format=html -instr-profile=my-coverage.kanicov -o coverage-report +``` + +This time, the command will generate a `coverage-report` folder including a +browsable HTML webpage that highlights the regions covered in the source +according to the coverage results in `my-coverage.kanicov`. + +[^note-export]: The `llvm-cov` tool includes the option + [`gcov`](https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-gcov) to + export into GCC's coverage format [Gcov](https://en.wikipedia.org/wiki/Gcov), + and the option + [`export`](https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-export) + to export into the LCOV format. These may be good options to consider for + `kani-cov` in the future but we should focus on basic formats for now. + +[^note-exclude]: Options to exclude certain coverage results (e.g, from the + standard library) will likely be part of this option. + +[^note-snapshot]: Coverage mappings essentially provide a snapshot of the source + code reports for items that otherwise are unreachable or have been sliced + away during the compilation process. + +#### Integration with the Kani VS Code Extension + +We will update the coverage feature of the +[Kani VS Code Extension](https://github.com/model-checking/kani-vscode-extension) +to follow this new coverage workflow. +In other words, the extension will first run Kani with the `--coverage` option and +use `kani cov` to produce a `.kanicov` file with the coverage results. +The extension will consume the source-based code coverage results and +highlight region coverage in the source code seen from VS Code. + +We could also consider other coverage-related features in order to enhance the +experience through the Kani VS Code Extension. For example, we could +automatically show the percentage of covered regions in the status bar by +additionally extracting a summary of the coverage results. + +Finally, we could also consider an integration with other code coverage tools. +For example, if we wanted to integrate with the VS Code extensions +[Code Coverage](https://marketplace.visualstudio.com/items?itemName=markis.code-coverage) or +[Coverage Gutters](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters), +we would only need to extend `kani-cov` to export coverage results to the LCOV +format or integrate Kani with LLVM tools as discussed in [Integration with LLVM](#integration-with-llvm). + +## Detailed Design + +In this section, we provide more details on: + - The Rust coverage instrumentation and how it can be integrated into + Kani to produce source-based code coverage results. + - The proposed coverage workflow to be run by default in Kani when the + `--coverage` option is used. + +This information is mostly intended as a reference for Kani contributors. +Currently, the Rust coverage instrumentation continues to be developed. Because +of that, Rust toolchain upgrades may result in breaking changes to our own +coverage feature. This section should help developers to understand the general +approach and resolve such issues by themselves. + +### The Rust coverage instrumentation + +The Rust compiler includes two code coverage implementations: + * A source-based coverage implementation which uses LLVM's coverage + instrumentation to generate precise coverage data. This implementation can be + enabled with `-C instrument-coverage`. + * A Gcov-based coverage implementation that derives coverage data based on + DebugInfo. This implementation can be enabled with `-Z profile`. + +The [Instrumentation-based Code Coverage](https://doc.rust-lang.org/rustc/instrument-coverage.html) +chapter from the `rustc` book describes in detail how to enable and use the LLVM +instrumentation-based coverage feature. In contrast, the +[LLVM Source-Based Code Coverage](https://rustc-dev-guide.rust-lang.org/llvm-coverage-instrumentation.html) +chapter from the `rustc` development guide documents how the LLVM +coverage instrumentation is performed in the Rust compiler. + +In this section, we will first summarize some information from the +[LLVM Source-Based Code Coverage](https://rustc-dev-guide.rust-lang.org/llvm-coverage-instrumentation.html) +chapter, limited to details which are relevant to the development of the +source-based coverage feature in Kani. Then, we will explain how Kani taps into +the Rust coverage instrumentation to perform its own coverage instrumentation +and be able to report source-based code coverage results. This will also include +mentions to current issues with this implementation, which we plan to further +discuss in [Future possibilities](#future-possibilities). + +#### Understanding the Rust coverage instrumentation + +The LLVM coverage instrumentation is implemented in the Rust compiler as a +[MIR pass called `InstrumentCoverage`](https://rustc-dev-guide.rust-lang.org/llvm-coverage-instrumentation.html#mir-pass-instrumentcoverage). + +The MIR pass first builds a coverage-specific version of the MIR Control Flow +Graph (CFG) from the MIR. The initial version of this CFG is based on the MIR's +`BasicBlock`s, which then gets refined by combining blocks that can be chained +from a coverage-relevant point of view. The final version of the coverage CFG is +then used to determine where to inject the +[`StatementKind::Coverage`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/enum.StatementKind.html#variant.Coverage) +statements in order to measure coverage for a single region coverage span. + +The injection of `StatementKind::Coverage` statements is the main result we are +interested in for the integration with Kani. Additionally, the instrumentation +will also attach the +[`FunctionCoverageInfo`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/coverage/struct.FunctionCoverageInfo.html) +structure to each function's body.[^note-coverage-info] +This result is also needed at the moment because coverage statements do not +include information on the code region they are supposed to cover. +However, `FunctionCoverageInfo` contains the +[coverage mappings](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/coverage/struct.Mapping.html), +which represent the relation between +[coverage counters](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/coverage/enum.CovTerm.html) +and code regions. + +As explained in [MIR Pass: +`InstrumentCoverage`](https://rustc-dev-guide.rust-lang.org/llvm-coverage-instrumentation.html#mir-pass-instrumentcoverage), +many coverage statements will not be converted into a physical +counter[^note-physical-counter]. Instead, they will be converted into a +*coverage-counter expression* that can be calculated based on other coverage +counters. We highly recommend looking at the example in [MIR Pass: +`InstrumentCoverage`](https://rustc-dev-guide.rust-lang.org/llvm-coverage-instrumentation.html#mir-pass-instrumentcoverage) +to better understand how this works. This optimization is mainly done for +performance reasons because incrementing a physical counter causes a +non-negligible overhead, especially within loops. + +The (`StatementKind::`)`Coverage` statements that are injected by the Rust coverage +instrumentation contain a [`CoverageKind`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/coverage/enum.CoverageKind.html) field indicating the type of coverage counter. The variant +[`CounterIncrement`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/coverage/enum.CoverageKind.html#variant.CounterIncrement) +represents physical counters, while +[`ExpressionUsed`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/coverage/enum.CoverageKind.html#variant.ExpressionUsed) +represents the counter expressions that we just discussed. +Other variants such as `SpanMarker` or `BlockMarker` are not relevant to this +work since they should have been erased after the `InstrumentCoverage` pass. + +[^note-coverage-info]: It is important to note that the StableMIR interface does + not include `FunctionCoverageInfo` in function bodies. Because of that, we + need to pull it from the internal `rustc` function bodies. + +[^note-physical-counter]: By *physical counter*, we refer to a global program + variable that is initialized to zero and incremented by one each time that + the execution passes through. + +#### Integrating the instrumentation into Kani + +Now that we have explained what the Rust coverage instrumentation does at a high +level, we should be ready to discuss how it can be used from Kani. Here, we will +follow an approach where, during the codegen stage, we generate a Kani +reachability check for each code region and, after the verification stage, we +postprocess the information in those checks to generate the coverage +information. So this section will essentially be a retelling of the +implementation in [#3119](https://github.com/model-checking/kani/pull/3119), and +we will discuss variations/extensions of this approach in the +[appropriate](#rationale-and-alternatives) [sections](#future-possibilities). + +Clearly, the first step is adding `-C instrument-coverage` to the `rustc` flags +we use when calling the compiler to codegen. This flag enables the Rust coverage +instrumentation that we discussed earlier, resulting in + 1. the injection of + [`Coverage`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/enum.StatementKind.html#variant.Coverage) + statements in the MIR code, and + 2. the inclusion of [`FunctionCoverageInfo`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/coverage/struct.FunctionCoverageInfo.html) in function bodies. + +The next step is handling the `Coverage` statements from `codegen_statement`. + +Each `Coverage` statement contains opaque coverage +information[^note-opaque-coverage] of the +[`CoverageKind`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/coverage/enum.CoverageKind.html) +type which can be processed to determine the type of coverage counter +(`CounterIncrement` for physical counters, `ExpressionUsed` for counter +expressions) and the ID number of the counter. These two pieces of information allow us to +uniquely identify the counter within a given function. For example, +`CounterIncrement(0)` would generally refer to the first physical counter in the +function. + +Unfortunately, the `CoverageKind` information does not tell us anything about +the code region that the counter covers. However, data about the code region can +be pulled from the coverage mappings included in the `FunctionCoverageInfo` that +is attached to the (internal) function body. Note that the coverage mappings +includes information about all the coverage counters in a function, even for +counters which have been dropped. Matching the `CoverageKind` information with +that of the counters in the coverage mappings allows us to retrieve the code +region for any counter. + +Using all this data, for each coverage statement[^note-expression-integration] we generate a coverage check +that maintains the essence of the coverage checks described in the [RFC for line +coverage](https://model-checking.github.io/kani/rfc/rfcs/0008-line-coverage.html): + +> Coverage checks are a new class of checks similar to [`cover` checks](https://model-checking.github.io/kani/rfc/rfcs/0003-cover-statement.html). +> The main difference is that users cannot directly interact with coverage checks (i.e., they cannot add or remove them manually). +> Coverage checks are encoded as an `assert(false)` statement (to test reachability) with a fixed description. +> In addition, coverage checks are: +> * Hidden from verification results. +> * Postprocessed to produce coverage results. + +Therefore, the last step is to postprocess the results from coverage checks to +produce coverage results. This is not too complicated to do since the checks +already include the counter information (type + ID) and the function name in +the check's description. If the span of the code region is also included +(this is what [#3119](https://github.com/model-checking/kani/pull/3119) is +currently doing), we can directly generate a primitive output like this: + +``` + () + * - + * ... + * - +``` + +For example, for the test case in +[#3119](https://github.com/model-checking/kani/pull/3119) we report this: + +``` +src/main.rs (main) + * 14:1 - 19:2 COVERED + +src/main.rs (test_cov) + * 5:1 - 6:15 COVERED + * 6:19 - 6:28 UNCOVERED + * 7:9 - 7:13 COVERED + * 9:9 - 9:14 UNCOVERED + * 11:1 - 11:2 COVERED +``` + +> **NOTE: This section has been written according to the implementation in +> [#3119](https://github.com/model-checking/kani/pull/3119), which currently +> produces a text-based output like the one shown above. There is ongoing work to +> store the coverage mappings in a separate file (as described in the next +> section), which would save us the need to attach code region data to the +> coverage checks.** + +[^note-opaque-coverage]: The Rust compiler uses the `Opaque` type to prevent +others from interfacing with unstable types (e.g., the `Coverage` type +[here](https://github.com/rust-lang/rust/blob/f7eefec4e03f5ba723fbc04d94dbc1203b7c9bff/compiler/stable_mir/src/mir/body.rs#L389)). +Nonetheless, this can be worked around by serializing its contents and parsing +it back into an internal data type. + +[^note-expression-integration]: We could follow an alternative approach where we +do not instrument each coverage statement, but only those that correspond to +physical counters. Unfortunately, doing so would lead to incorrect coverage +results due to the arithmetic nature of expression-based counters. We elaborate +on this topic in the later parts of this document. + +### The default coverage workflow in Kani + +In this section, we describe the default `--coverage` workflow from a +developer's point of view. This will hopefully help developers understand how +the different coverage components in Kani are connected. For example, we'll +describe the raw coverage information that gets produced throughout the default +`--coverage` workflow and define the basic `cov` commands that it will execute. + +The main difference with respect to the regular verification workflow is that, +at the end of the verification-based coverage run, Kani will generate two types +of files: + - One single file `.kanimap` file for the project. This file will contain the + coverage mappings for the project's source code. + - One `.kaniraw` file for each harness. This file will contain the + verification-based results for the coverage-oriented properties corresponding + to a given harness. + +Note that `.kaniraw` files correspond to `.profraw` files in the LLVM coverage +workflow. Similarly, the `.kanimap` file corresponds to the coverage-related +information that's embedded into the project's binaries in the LLVM coverage +workflow.[^note-kanimap] + +The files will be written into a new timestamped directory associated with the +coverage run. The path to this directory will be printed to standard output in +by default. For example, the [draft implementation](https://github.com/model-checking/kani/pull/3119) +writes the coverage files into the `target/kani//cov/` directory. + +Users aren't expected to read the information in any of these files. +Therefore, there's no need to restrict their format. +The [draft implementation](https://github.com/model-checking/kani/pull/3119) +uses the JSON format but we might consider using a binary format if it doesn't +scale. + +In addition, Kani will produce two types of coverage results: + 1. A coverage summary with the default options. + 2. A terminal-based coverage report with the default options. However, we will + only do this if the program is composed of a single source + file[^note-conditional-report]. + +[^note-kanimap]: Note that the `.kanimap` generation isn't implemented in + [#3119](https://github.com/model-checking/kani/pull/3119). The [draft + implementation of + `kani-cov`](https://github.com/model-checking/kani/pull/3121) simply reads + the source files referred to by the code coverage checks, but it doesn't get + information about code trimmed out by the MIR linker. + +[^note-conditional-report]: In other words, standalone `kani` would always emit +these terminal-based reports, but `cargo kani` would not unless the project +contains a single Rust file (for example, `src/main.rs`). + +## Rationale and alternatives + +### Other coverage implementations + +In a previous version of this feature, we used an ad-hoc coverage implementation. +In addition to being very inefficient[^note-benchmarks], the line-based coverage +results were not trivial to interpret by users. +At the moment, there's only another unstable, GCC-compatible code coverage implementation +based on the Gcov format. The Gcov format is line-based so it's not able +to report region coverage results. +In other words, it's not as advanced nor precise as the source-based implementation. + +[^note-benchmarks]: Actual performance benchmarks to follow in + [#3119](https://github.com/model-checking/kani/pull/3119). + +## Open questions + + - Do we want to instrument dependencies by default? Preliminary benchmarking results show a slowdown of 100% and greater. + More evaluations are required to determine how we handle instrumentation for dependencies, and what options we might want + to provide to users. + - How do we handle features/options for `kani-cov`? In particular, do we need more details in this RFC? + +## Future possibilities + +### Integration with LLVM + +As part of this work, we explored a potential integration with the LLVM +framework. The idea behind such an integration would essentially involve +producing coverage results in formats compatible with the LLVM framework (e.g., +the `.profraw` format). The main advantage of integrating with the LLVM +framework in this way is that we would not need a tool like `kani-cov` to +aggregate coverage results; we could just use LLVM tools such as `llvm-profdata` +and `llvm-cov` to consume them. + +However, at this time we recommend against integrating with LLVM due to these reasons: + 1. Generating the instrumented binary used in the [LLVM coverage + workflow](#the-llvm-code-coverage-workflow) requires a standard `rustc` + compilation with `--cfg kani` in addition to other flags including `-C + instrument-coverage`. This is likely to result in compilation errors since the + standard `rustc` backend cannot produce code for Kani APIs, for example. + 2. Producing the `.profraw` files requires executing the instrumented binary at + least once. This would be an issue for Rust projects which assume a particular + environment for their execution. + 3. There are no stable interfaces to create or modify files in formats + compatible with the LLVM framework. Even though the documentation for the [LLVM + Code Coverage Mapping Format](https://llvm.org/docs/CoverageMappingFormat.html) + is excellent, the easiest way to interact with files on these format is through + LLVM tools (e.g., + [`llvm-cov`](https://github.com/llvm/llvm-project/tree/main/llvm/tools/llvm-cov)) + which bring in many other LLVM dependencies. During our exploration, we + attempted to decode and re-encode files in the `.profraw` to set the counter + data to the values obtained during verification. To this end, we tried tools + like [`llvm-profparser`](https://github.com/xd009642/llvm-profparser/) which + can be used as a replacement for `llvm-profdata` and `llvm-cov` but failed to + parse coverage files emitted by the Rust compiler (this is also related to the + next point). Another crate that we used is + [`coverage-dump`](https://github.com/rust-lang/rust/tree/master/src/tools/coverage-dump), + a recent tool in the Rust compiler used for testing purposes. `coverage-dump` + extracts coverage mappings from LLVM IR assembly files (i.e., human-readable + `*.ll` files) but does not work with the binary-encoded formats. Finally, we + also built some ad-hoc tooling to perform these modifications but it soon + became evident that we would need to develop it further in order to handle any + program. + 4. LLVM releases a new version approximately every six months. This would + likely result in another "toolchain update" problem for Kani in order to + provide compatibility with newer LLVM versions. Moreover, the Rust compiler + supplies their own version of LLVM tools (`rust-profdata`, `rust-cov`, etc.) + which are fully compatible with coverage-related artifacts produced by `rustc`. + + +### Optimization with coverage-counter expressions + +In the [subsection related to the +integration](#integrating-the-instrumentation-into-kani), we noted that we could +follow an alternative approach where we only instrument coverage statements that +correspond to physical counters. In fact, this would be the logical choice since +the replacement of physical counters by expression-based counters would also be +a performance optimization for us. + +However, the expressions used in expression-based counters are built with the +arithmetic operators `Add` (`+`) and `Sub` (`-`). On the other hand, the +coverage checks performed by Kani have a boolean meaning: you either cover a +region or you do not. Thus, there are many cases where these two notions of +coverage counters are incompatible. For example, let's say we have this +function: + +```rust +fn check_value(val: u32) { + if val == VALUE { + do_this(); + } else { + do_that(); + } + do_something_else(); +} +``` + +One way to optimize the counters in this function is to have two physical +counters for the branches of the `if` statement (`c1` and `c2`), and then an +expression-based counter associated to the `do_something_else()` statement +adding those (i.e., `c3 = c1 + c2`). If we have, for example, two executions for +this program, with each one taking a different branch, then the results for the +coverage counters will be `c1 = 1`, `c2 = 1` and `c3 = c1 + c2 = 2`. + +But what does `c3 = 2` mean in the context of a verification-based coverage +result? That is not clear. For instance, in a Kani trace, you could have a +nondeterministic value for `val` which just happens to be `val == VALUE` and not +at the same time. This would result in the same counters (`c1 = 1`, `c2 = 1` and +`c3 = 2`), but the program is being run only once! + +Note that finding a verification-based replacement for the runtime operators in +counter-based expressions is an interesting research topic. If we could +establish a relation between the runtime and verification expressions, then we +could avoid the instrumentation of coverage checks for expression-based +counters. For example, could we replace the `Add` operator (`+`) with an `Or` +operator (`||`)? Intuitively, this makes sense since verification-based coverage +counters are binary. It also seems to work for our example since covering any of +the branches should result in the `do_something_else()` statement being covered +as well, with the counter values now being `c1 = 1`, `c2 = 1` and `c3 = 1`. +However, it is not clear that this would work for all cases, nor it is clear +that we can replace `Sub` with another verification-based operator. From 8c1784946f3adf4b820d238f95e8865a5af229ab Mon Sep 17 00:00:00 2001 From: Adrian Palacios <73246657+adpaco-aws@users.noreply.github.com> Date: Tue, 27 Aug 2024 14:35:00 -0400 Subject: [PATCH 22/40] Adopt Rust's source-based code coverage instrumentation (#3119) This PR replaces the line-based coverage instrumentation we introduced in #2609 with the standard source-based code coverage instrumentation performed by the Rust compiler. As a result, we now insert code coverage checks in the `StatementKind::Coverage(..)` statements produced by the Rust compiler during compilation. These checks include coverage-relevant information[^note-internal] such as the coverage counter/expression they represent [^note-instrument]. Both the coverage metadata (`kanimap`) and coverage results (`kaniraw`) are saved into files after the verification stage. Unfortunately, we currently have a chicken-egg problem with this PR and #3121, where we introduce a tool named `kani-cov` to postprocess coverage results. As explained in #3143, `kani-cov` is expected to be an alias for the `cov` subcommand and provide most of the postprocessing features for coverage-related purposes. But, the tool will likely be introduced after this change. Therefore, we propose to temporarily print a list of the regions in each function with their associated coverage status (i.e., `COVERED` or `UNCOVERED`). ### Source-based code coverage: An example The main advantage of source-based coverage results is their precision with respect to the source code. The [Source-based Code Coverage](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html) documentation explains more details about the LLVM coverage workflow and its different options. For example, let's take this Rust code: ```rust 1 fn _other_function() { 2 println!("Hello, world!"); 3 } 4 5 fn test_cov(val: u32) -> bool { 6 if val < 3 || val == 42 { 7 true 8 } else { 9 false 10 } 11 } 12 13 #[cfg_attr(kani, kani::proof)] 14 fn main() { 15 let test1 = test_cov(1); 16 let test2 = test_cov(2); 17 assert!(test1); 18 assert!(test2); 19 } ``` Compiling and running the program with `rustc` and the `-C instrument-coverage` flag, and using the LLVM tools can get us the following coverage result: ![Image](https://github.com/model-checking/kani/assets/73246657/9070e390-6e0b-4add-828d-d9f9caacad07) In contrast, the `cargo kani --coverage -Zsource-coverage` command currently generates: ``` src/main.rs (main) * 14:1 - 19:2 COVERED src/main.rs (test_cov) * 5:1 - 6:15 COVERED * 6:19 - 6:28 UNCOVERED * 7:9 - 7:13 COVERED * 9:9 - 9:14 UNCOVERED * 11:1 - 11:2 COVERED ``` which is a verification-based coverage result almost equivalent to the runtime coverage results. ### Benchmarking We have evaluated the performance impact of the instrumentation using the `kani-perf.sh` suite (14 benchmarks). For each test, we compare the average time to run standard verification against the average time to run verification with the source-based code coverage feature enabled[^note-line-evaluation]. The evaluation has been performed on an EC2 `m5a.4xlarge` instance running Ubuntu 22.04. The experimental data has been obtained by running the `kani-perf.sh` script 10 times for each version (`only verification` and `verification + coverage`), computing the average and standard deviation. We've split this data into `small` (tests taking 60s or less) and `large` (tests taking more than 60s) and drawn the two graphs below. #### Performance comparison - `small` benchmarks ![performance_comparison_small](https://github.com/user-attachments/assets/679cf412-0193-4b0c-a78c-2d0fb702706f) #### Performance comparison - `large` benchmarks ![performance_comparison_large](https://github.com/user-attachments/assets/4bb5a895-7f57-49e0-86b5-5fea67fad939) #### Comments on performance Looking at the small tests, the performance impact seems negligible in such cases. The difference is more noticeable in the large tests, where the time to run verification and coverage can take 2x or even more. It wouldn't be surprising that, as programs become larger, the complexity of the coverage checking grows exponentially as well. However, since most verification jobs don't take longer than 30min (1800s), it's OK to say that coverage checking represents a 100-200% slowdown in the worst case w.r.t. standard verification. It's also worth noting a few other things: * The standard deviation remains similar in most cases, meaning that the coverage feature doesn't have an impact on their stability. * We haven't tried any SAT solvers other than the ones used by default for each benchmark. It's possible that other solvers perform better/worse with the coverage feature enabled. ### Call-outs * The soundness issue documented in #3441. * The issue with saving coverage mappings for non-reachable functions documented in #3445. * I've modified the test cases in `tests/coverage/` to test this feature. Since this technique is simpler, we don't need that many test cases. However, it's possible I've left some test cases which don't contribute much. Please let me know if you want to add/remove a test case. [^note-internal]: The coverage mappings can't be accessed through the StableMIR interface so we retrieve them through the internal API. [^note-instrument]: The instrumentation replaces certain counters with expressions based on other counters when possible to avoid a part of the runtime overhead. More details can be found [here](https://github.com/rust-lang/rustc-dev-guide/blob/master/src/llvm-coverage-instrumentation.md#mir-pass-instrumentcoverage). Unfortunately, we can't avoid instrumenting expressions at the moment. [^note-line-evaluation]: We have not compared performance against the line-based code coverage feature because it doesn't seem worth it. The line-based coverage feature is guaranteed to include more coverage checks than the source-based one for any function. In addition, source-based results are more precise than line-based ones. So this change represents both a quantitative and qualitative improvement. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- Cargo.lock | 53 ++++++ .../codegen_cprover_gotoc/codegen/assert.rs | 16 +- .../codegen_cprover_gotoc/codegen/block.rs | 33 +--- .../codegen_cprover_gotoc/codegen/function.rs | 71 +++++++ .../codegen/statement.rs | 19 +- kani-driver/Cargo.toml | 1 + kani-driver/src/args/mod.rs | 4 +- kani-driver/src/call_cargo.rs | 2 +- kani-driver/src/call_cbmc.rs | 100 +++++++++- kani-driver/src/call_single_file.rs | 5 + kani-driver/src/cbmc_output_parser.rs | 4 +- kani-driver/src/cbmc_property_renderer.rs | 74 ++------ kani-driver/src/coverage/cov_results.rs | 96 ++++++++++ kani-driver/src/coverage/cov_session.rs | 176 ++++++++++++++++++ kani-driver/src/coverage/mod.rs | 5 + kani-driver/src/harness_runner.rs | 17 +- kani-driver/src/main.rs | 19 ++ kani-driver/src/project.rs | 18 +- kani_metadata/src/unstable.rs | 5 +- tests/coverage/abort/expected | 13 ++ .../coverage/{unreachable => }/abort/main.rs | 0 tests/coverage/assert/expected | 9 + .../coverage/{unreachable => }/assert/test.rs | 0 tests/coverage/assert_eq/expected | 8 + .../{unreachable => }/assert_eq/test.rs | 0 tests/coverage/assert_ne/expected | 9 + .../{unreachable => }/assert_ne/test.rs | 0 tests/coverage/break/expected | 13 ++ .../coverage/{unreachable => }/break/main.rs | 2 +- tests/coverage/compare/expected | 11 ++ .../{unreachable => }/compare/main.rs | 2 +- tests/coverage/contradiction/expected | 9 + .../{unreachable => }/contradiction/main.rs | 0 tests/coverage/debug-assert/expected | 10 + tests/coverage/debug-assert/main.rs | 15 ++ tests/coverage/div-zero/expected | 9 + .../reachable_pass => div-zero}/test.rs | 2 +- tests/coverage/early-return/expected | 12 ++ .../{unreachable => }/early-return/main.rs | 0 tests/coverage/if-statement-multi/expected | 11 ++ tests/coverage/if-statement-multi/test.rs | 26 +++ tests/coverage/if-statement/expected | 14 ++ .../{unreachable => }/if-statement/main.rs | 2 +- .../assert_uncovered_end/expected | 9 + .../known_issues/assert_uncovered_end/test.rs | 14 ++ .../known_issues/assume_assert/expected | 4 + .../known_issues/assume_assert/main.rs | 15 ++ .../known_issues/out-of-bounds/expected | 7 + .../known_issues/out-of-bounds/test.rs | 15 ++ tests/coverage/known_issues/variant/expected | 14 ++ .../variant/main.rs | 5 +- tests/coverage/multiple-harnesses/expected | 37 ++++ .../multiple-harnesses/main.rs | 0 tests/coverage/overflow-failure/expected | 9 + .../test.rs | 4 +- .../coverage/overflow-full-coverage/expected | 9 + .../test.rs | 4 +- .../coverage/reachable/assert-false/expected | 8 - tests/coverage/reachable/assert-false/main.rs | 19 -- .../reachable/assert/reachable_pass/expected | 4 - .../reachable/assert/reachable_pass/test.rs | 10 - .../reachable/bounds/reachable_fail/expected | 4 - .../reachable/bounds/reachable_fail/test.rs | 11 -- .../div-zero/reachable_fail/expected | 4 - .../reachable/div-zero/reachable_fail/test.rs | 11 -- .../div-zero/reachable_pass/expected | 4 - .../overflow/reachable_fail/expected | 5 - .../overflow/reachable_pass/expected | 7 - .../rem-zero/reachable_fail/expected | 4 - .../reachable/rem-zero/reachable_fail/test.rs | 11 -- .../rem-zero/reachable_pass/expected | 4 - .../reachable/rem-zero/reachable_pass/test.rs | 11 -- tests/coverage/unreachable/abort/expected | 7 - tests/coverage/unreachable/assert/expected | 7 - tests/coverage/unreachable/assert_eq/expected | 5 - tests/coverage/unreachable/assert_ne/expected | 6 - .../unreachable/assume_assert/expected | 4 - .../unreachable/assume_assert/main.rs | 8 - tests/coverage/unreachable/bounds/expected | 4 - tests/coverage/unreachable/bounds/test.rs | 12 -- tests/coverage/unreachable/break/expected | 9 - tests/coverage/unreachable/check_id/expected | 16 -- tests/coverage/unreachable/check_id/main.rs | 25 --- tests/coverage/unreachable/compare/expected | 7 - .../unreachable/contradiction/expected | 7 - .../unreachable/debug-assert/expected | 4 - .../coverage/unreachable/debug-assert/main.rs | 10 - tests/coverage/unreachable/divide/expected | 7 - tests/coverage/unreachable/divide/main.rs | 19 -- .../unreachable/early-return/expected | 10 - .../unreachable/if-statement/expected | 10 - .../unreachable/multiple-harnesses/expected | 39 ---- tests/coverage/unreachable/return/expected | 8 - tests/coverage/unreachable/return/main.rs | 17 -- .../unreachable/tutorial_unreachable/expected | 5 - .../unreachable/tutorial_unreachable/main.rs | 11 -- tests/coverage/unreachable/variant/expected | 10 - tests/coverage/unreachable/vectors/expected | 6 - tests/coverage/unreachable/vectors/main.rs | 19 -- .../unreachable/while-loop-break/expected | 11 -- tests/coverage/while-loop-break/expected | 13 ++ .../while-loop-break/main.rs | 0 tests/ui/save-coverage-results/expected | 3 + tests/ui/save-coverage-results/test.rs | 25 +++ tools/compiletest/src/runtest.rs | 2 +- 105 files changed, 950 insertions(+), 554 deletions(-) create mode 100644 kani-driver/src/coverage/cov_results.rs create mode 100644 kani-driver/src/coverage/cov_session.rs create mode 100644 kani-driver/src/coverage/mod.rs create mode 100644 tests/coverage/abort/expected rename tests/coverage/{unreachable => }/abort/main.rs (100%) create mode 100644 tests/coverage/assert/expected rename tests/coverage/{unreachable => }/assert/test.rs (100%) create mode 100644 tests/coverage/assert_eq/expected rename tests/coverage/{unreachable => }/assert_eq/test.rs (100%) create mode 100644 tests/coverage/assert_ne/expected rename tests/coverage/{unreachable => }/assert_ne/test.rs (100%) create mode 100644 tests/coverage/break/expected rename tests/coverage/{unreachable => }/break/main.rs (82%) create mode 100644 tests/coverage/compare/expected rename tests/coverage/{unreachable => }/compare/main.rs (73%) create mode 100644 tests/coverage/contradiction/expected rename tests/coverage/{unreachable => }/contradiction/main.rs (100%) create mode 100644 tests/coverage/debug-assert/expected create mode 100644 tests/coverage/debug-assert/main.rs create mode 100644 tests/coverage/div-zero/expected rename tests/coverage/{reachable/div-zero/reachable_pass => div-zero}/test.rs (64%) create mode 100644 tests/coverage/early-return/expected rename tests/coverage/{unreachable => }/early-return/main.rs (100%) create mode 100644 tests/coverage/if-statement-multi/expected create mode 100644 tests/coverage/if-statement-multi/test.rs create mode 100644 tests/coverage/if-statement/expected rename tests/coverage/{unreachable => }/if-statement/main.rs (78%) create mode 100644 tests/coverage/known_issues/assert_uncovered_end/expected create mode 100644 tests/coverage/known_issues/assert_uncovered_end/test.rs create mode 100644 tests/coverage/known_issues/assume_assert/expected create mode 100644 tests/coverage/known_issues/assume_assert/main.rs create mode 100644 tests/coverage/known_issues/out-of-bounds/expected create mode 100644 tests/coverage/known_issues/out-of-bounds/test.rs create mode 100644 tests/coverage/known_issues/variant/expected rename tests/coverage/{unreachable => known_issues}/variant/main.rs (72%) create mode 100644 tests/coverage/multiple-harnesses/expected rename tests/coverage/{unreachable => }/multiple-harnesses/main.rs (100%) create mode 100644 tests/coverage/overflow-failure/expected rename tests/coverage/{reachable/overflow/reachable_fail => overflow-failure}/test.rs (66%) create mode 100644 tests/coverage/overflow-full-coverage/expected rename tests/coverage/{reachable/overflow/reachable_pass => overflow-full-coverage}/test.rs (62%) delete mode 100644 tests/coverage/reachable/assert-false/expected delete mode 100644 tests/coverage/reachable/assert-false/main.rs delete mode 100644 tests/coverage/reachable/assert/reachable_pass/expected delete mode 100644 tests/coverage/reachable/assert/reachable_pass/test.rs delete mode 100644 tests/coverage/reachable/bounds/reachable_fail/expected delete mode 100644 tests/coverage/reachable/bounds/reachable_fail/test.rs delete mode 100644 tests/coverage/reachable/div-zero/reachable_fail/expected delete mode 100644 tests/coverage/reachable/div-zero/reachable_fail/test.rs delete mode 100644 tests/coverage/reachable/div-zero/reachable_pass/expected delete mode 100644 tests/coverage/reachable/overflow/reachable_fail/expected delete mode 100644 tests/coverage/reachable/overflow/reachable_pass/expected delete mode 100644 tests/coverage/reachable/rem-zero/reachable_fail/expected delete mode 100644 tests/coverage/reachable/rem-zero/reachable_fail/test.rs delete mode 100644 tests/coverage/reachable/rem-zero/reachable_pass/expected delete mode 100644 tests/coverage/reachable/rem-zero/reachable_pass/test.rs delete mode 100644 tests/coverage/unreachable/abort/expected delete mode 100644 tests/coverage/unreachable/assert/expected delete mode 100644 tests/coverage/unreachable/assert_eq/expected delete mode 100644 tests/coverage/unreachable/assert_ne/expected delete mode 100644 tests/coverage/unreachable/assume_assert/expected delete mode 100644 tests/coverage/unreachable/assume_assert/main.rs delete mode 100644 tests/coverage/unreachable/bounds/expected delete mode 100644 tests/coverage/unreachable/bounds/test.rs delete mode 100644 tests/coverage/unreachable/break/expected delete mode 100644 tests/coverage/unreachable/check_id/expected delete mode 100644 tests/coverage/unreachable/check_id/main.rs delete mode 100644 tests/coverage/unreachable/compare/expected delete mode 100644 tests/coverage/unreachable/contradiction/expected delete mode 100644 tests/coverage/unreachable/debug-assert/expected delete mode 100644 tests/coverage/unreachable/debug-assert/main.rs delete mode 100644 tests/coverage/unreachable/divide/expected delete mode 100644 tests/coverage/unreachable/divide/main.rs delete mode 100644 tests/coverage/unreachable/early-return/expected delete mode 100644 tests/coverage/unreachable/if-statement/expected delete mode 100644 tests/coverage/unreachable/multiple-harnesses/expected delete mode 100644 tests/coverage/unreachable/return/expected delete mode 100644 tests/coverage/unreachable/return/main.rs delete mode 100644 tests/coverage/unreachable/tutorial_unreachable/expected delete mode 100644 tests/coverage/unreachable/tutorial_unreachable/main.rs delete mode 100644 tests/coverage/unreachable/variant/expected delete mode 100644 tests/coverage/unreachable/vectors/expected delete mode 100644 tests/coverage/unreachable/vectors/main.rs delete mode 100644 tests/coverage/unreachable/while-loop-break/expected create mode 100644 tests/coverage/while-loop-break/expected rename tests/coverage/{unreachable => }/while-loop-break/main.rs (100%) create mode 100644 tests/ui/save-coverage-results/expected create mode 100644 tests/ui/save-coverage-results/test.rs diff --git a/Cargo.lock b/Cargo.lock index d13ff68b32ff..0b8f681ae6c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -316,6 +316,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "either" version = "1.13.0" @@ -500,6 +509,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", + "time", "toml", "tracing", "tracing-subscriber", @@ -660,6 +670,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" version = "0.1.46" @@ -767,6 +783,12 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -1179,6 +1201,37 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "toml" version = "0.8.19" diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs index f78cf3eba707..4ee81d0c7d3e 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs @@ -21,6 +21,7 @@ use crate::codegen_cprover_gotoc::GotocCtx; use cbmc::goto_program::{Expr, Location, Stmt, Type}; use cbmc::InternedString; +use rustc_middle::mir::coverage::CodeRegion; use stable_mir::mir::{Place, ProjectionElem}; use stable_mir::ty::{Span as SpanStable, TypeAndMut}; use strum_macros::{AsRefStr, EnumString}; @@ -148,18 +149,19 @@ impl<'tcx> GotocCtx<'tcx> { } /// Generate a cover statement for code coverage reports. - pub fn codegen_coverage(&self, span: SpanStable) -> Stmt { + pub fn codegen_coverage( + &self, + counter_data: &str, + span: SpanStable, + code_region: CodeRegion, + ) -> Stmt { let loc = self.codegen_caller_span_stable(span); // Should use Stmt::cover, but currently this doesn't work with CBMC // unless it is run with '--cover cover' (see // https://github.com/diffblue/cbmc/issues/6613). So for now use // `assert(false)`. - self.codegen_assert( - Expr::bool_false(), - PropertyClass::CodeCoverage, - "code coverage for location", - loc, - ) + let msg = format!("{counter_data} - {code_region:?}"); + self.codegen_assert(Expr::bool_false(), PropertyClass::CodeCoverage, &msg, loc) } // The above represent the basic operations we can perform w.r.t. assert/assume/cover diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/block.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/block.rs index 5fe28097a2e0..1b28de887002 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/block.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/block.rs @@ -20,53 +20,24 @@ impl<'tcx> GotocCtx<'tcx> { pub fn codegen_block(&mut self, bb: BasicBlockIdx, bbd: &BasicBlock) { debug!(?bb, "codegen_block"); let label = bb_label(bb); - let check_coverage = self.queries.args().check_coverage; // the first statement should be labelled. if there is no statements, then the // terminator should be labelled. match bbd.statements.len() { 0 => { let term = &bbd.terminator; let tcode = self.codegen_terminator(term); - // When checking coverage, the `coverage` check should be - // labelled instead. - if check_coverage { - let span = term.span; - let cover = self.codegen_coverage(span); - self.current_fn_mut().push_onto_block(cover.with_label(label)); - self.current_fn_mut().push_onto_block(tcode); - } else { - self.current_fn_mut().push_onto_block(tcode.with_label(label)); - } + self.current_fn_mut().push_onto_block(tcode.with_label(label)); } _ => { let stmt = &bbd.statements[0]; let scode = self.codegen_statement(stmt); - // When checking coverage, the `coverage` check should be - // labelled instead. - if check_coverage { - let span = stmt.span; - let cover = self.codegen_coverage(span); - self.current_fn_mut().push_onto_block(cover.with_label(label)); - self.current_fn_mut().push_onto_block(scode); - } else { - self.current_fn_mut().push_onto_block(scode.with_label(label)); - } + self.current_fn_mut().push_onto_block(scode.with_label(label)); for s in &bbd.statements[1..] { - if check_coverage { - let span = s.span; - let cover = self.codegen_coverage(span); - self.current_fn_mut().push_onto_block(cover); - } let stmt = self.codegen_statement(s); self.current_fn_mut().push_onto_block(stmt); } let term = &bbd.terminator; - if check_coverage { - let span = term.span; - let cover = self.codegen_coverage(span); - self.current_fn_mut().push_onto_block(cover); - } let tcode = self.codegen_terminator(term); self.current_fn_mut().push_onto_block(tcode); } diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs index a1afa343a6e7..0793e0c4688f 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs @@ -216,3 +216,74 @@ impl<'tcx> GotocCtx<'tcx> { self.reset_current_fn(); } } + +pub mod rustc_smir { + use crate::stable_mir::CrateDef; + use rustc_middle::mir::coverage::CodeRegion; + use rustc_middle::mir::coverage::CovTerm; + use rustc_middle::mir::coverage::MappingKind::Code; + use rustc_middle::ty::TyCtxt; + use stable_mir::mir::mono::Instance; + use stable_mir::Opaque; + + type CoverageOpaque = stable_mir::Opaque; + + /// Retrieves the `CodeRegion` associated with the data in a + /// `CoverageOpaque` object. + pub fn region_from_coverage_opaque( + tcx: TyCtxt, + coverage_opaque: &CoverageOpaque, + instance: Instance, + ) -> Option { + let cov_term = parse_coverage_opaque(coverage_opaque); + region_from_coverage(tcx, cov_term, instance) + } + + /// Retrieves the `CodeRegion` associated with a `CovTerm` object. + /// + /// Note: This function could be in the internal `rustc` impl for `Coverage`. + pub fn region_from_coverage( + tcx: TyCtxt<'_>, + coverage: CovTerm, + instance: Instance, + ) -> Option { + // We need to pull the coverage info from the internal MIR instance. + let instance_def = rustc_smir::rustc_internal::internal(tcx, instance.def.def_id()); + let body = tcx.instance_mir(rustc_middle::ty::InstanceKind::Item(instance_def)); + + // Some functions, like `std` ones, may not have coverage info attached + // to them because they have been compiled without coverage flags. + if let Some(cov_info) = &body.function_coverage_info { + // Iterate over the coverage mappings and match with the coverage term. + for mapping in &cov_info.mappings { + let Code(term) = mapping.kind else { unreachable!() }; + if term == coverage { + return Some(mapping.code_region.clone()); + } + } + } + None + } + + /// Parse a `CoverageOpaque` item and return the corresponding `CovTerm`: + /// + /// + /// At present, a `CovTerm` can be one of the following: + /// - `CounterIncrement()`: A physical counter. + /// - `ExpressionUsed()`: An expression-based counter. + /// - `Zero`: A counter with a constant zero value. + fn parse_coverage_opaque(coverage_opaque: &Opaque) -> CovTerm { + let coverage_str = coverage_opaque.to_string(); + if let Some(rest) = coverage_str.strip_prefix("CounterIncrement(") { + let (num_str, _rest) = rest.split_once(')').unwrap(); + let num = num_str.parse::().unwrap(); + CovTerm::Counter(num.into()) + } else if let Some(rest) = coverage_str.strip_prefix("ExpressionUsed(") { + let (num_str, _rest) = rest.split_once(')').unwrap(); + let num = num_str.parse::().unwrap(); + CovTerm::Expression(num.into()) + } else { + CovTerm::Zero + } + } +} diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs index b8db1a3d52b6..81407c4dc704 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs @@ -3,6 +3,7 @@ use super::typ::TypeExt; use super::typ::FN_RETURN_VOID_VAR_NAME; use super::{bb_label, PropertyClass}; +use crate::codegen_cprover_gotoc::codegen::function::rustc_smir::region_from_coverage_opaque; use crate::codegen_cprover_gotoc::{GotocCtx, VtableCtx}; use crate::unwrap_or_return_codegen_unimplemented_stmt; use cbmc::goto_program::{Expr, Location, Stmt, Type}; @@ -158,12 +159,28 @@ impl<'tcx> GotocCtx<'tcx> { location, ) } + StatementKind::Coverage(coverage_opaque) => { + let function_name = self.current_fn().readable_name(); + let instance = self.current_fn().instance_stable(); + let counter_data = format!("{coverage_opaque:?} ${function_name}$"); + let maybe_code_region = + region_from_coverage_opaque(self.tcx, &coverage_opaque, instance); + if let Some(code_region) = maybe_code_region { + let coverage_stmt = + self.codegen_coverage(&counter_data, stmt.span, code_region); + // TODO: Avoid single-statement blocks when conversion of + // standalone statements to the irep format is fixed. + // More details in + Stmt::block(vec![coverage_stmt], location) + } else { + Stmt::skip(location) + } + } StatementKind::PlaceMention(_) => todo!(), StatementKind::FakeRead(..) | StatementKind::Retag(_, _) | StatementKind::AscribeUserType { .. } | StatementKind::Nop - | StatementKind::Coverage { .. } | StatementKind::ConstEvalCounter => Stmt::skip(location), } .with_location(location) diff --git a/kani-driver/Cargo.toml b/kani-driver/Cargo.toml index c57ec8e8e2f2..27fef66ffb65 100644 --- a/kani-driver/Cargo.toml +++ b/kani-driver/Cargo.toml @@ -34,6 +34,7 @@ tracing = {version = "0.1", features = ["max_level_trace", "release_max_level_de tracing-subscriber = {version = "0.3.8", features = ["env-filter", "json", "fmt"]} rand = "0.8" which = "6" +time = {version = "0.3.36", features = ["formatting"]} # A good set of suggested dependencies can be found in rustup: # https://github.com/rust-lang/rustup/blob/master/Cargo.toml diff --git a/kani-driver/src/args/mod.rs b/kani-driver/src/args/mod.rs index 2d7593e8050a..c3cfc113af64 100644 --- a/kani-driver/src/args/mod.rs +++ b/kani-driver/src/args/mod.rs @@ -615,12 +615,12 @@ impl ValidateArgs for VerificationArgs { } if self.coverage - && !self.common_args.unstable_features.contains(UnstableFeature::LineCoverage) + && !self.common_args.unstable_features.contains(UnstableFeature::SourceCoverage) { return Err(Error::raw( ErrorKind::MissingRequiredArgument, "The `--coverage` argument is unstable and requires `-Z \ - line-coverage` to be used.", + source-coverage` to be used.", )); } diff --git a/kani-driver/src/call_cargo.rs b/kani-driver/src/call_cargo.rs index 4e8e83b562af..e69062a0cd4f 100644 --- a/kani-driver/src/call_cargo.rs +++ b/kani-driver/src/call_cargo.rs @@ -206,7 +206,7 @@ impl KaniSession { }) } - fn cargo_metadata(&self, build_target: &str) -> Result { + pub fn cargo_metadata(&self, build_target: &str) -> Result { let mut cmd = MetadataCommand::new(); // restrict metadata command to host platform. References: diff --git a/kani-driver/src/call_cbmc.rs b/kani-driver/src/call_cbmc.rs index 387a9723fcdb..bc4424aeb231 100644 --- a/kani-driver/src/call_cbmc.rs +++ b/kani-driver/src/call_cbmc.rs @@ -3,10 +3,15 @@ use anyhow::{bail, Result}; use kani_metadata::{CbmcSolver, HarnessMetadata}; +use regex::Regex; +use rustc_demangle::demangle; +use std::collections::btree_map::Entry; +use std::collections::BTreeMap; use std::ffi::OsString; use std::fmt::Write; use std::path::Path; use std::process::Command; +use std::sync::OnceLock; use std::time::{Duration, Instant}; use crate::args::{OutputFormat, VerificationArgs}; @@ -14,6 +19,8 @@ use crate::cbmc_output_parser::{ extract_results, process_cbmc_output, CheckStatus, Property, VerificationOutput, }; use crate::cbmc_property_renderer::{format_coverage, format_result, kani_cbmc_output_filter}; +use crate::coverage::cov_results::{CoverageCheck, CoverageResults}; +use crate::coverage::cov_results::{CoverageRegion, CoverageTerm}; use crate::session::KaniSession; /// We will use Cadical by default since it performed better than MiniSAT in our analysis. @@ -54,6 +61,8 @@ pub struct VerificationResult { pub runtime: Duration, /// Whether concrete playback generated a test pub generated_concrete_test: bool, + /// The coverage results + pub coverage_results: Option, } impl KaniSession { @@ -273,12 +282,14 @@ impl VerificationResult { if let Some(results) = results { let (status, failed_properties) = verification_outcome_from_properties(&results, should_panic); + let coverage_results = coverage_results_from_properties(&results); VerificationResult { status, failed_properties, results: Ok(results), runtime, generated_concrete_test: false, + coverage_results, } } else { // We never got results from CBMC - something went wrong (e.g. crash) so it's failure @@ -288,6 +299,7 @@ impl VerificationResult { results: Err(output.process_status), runtime, generated_concrete_test: false, + coverage_results: None, } } } @@ -299,6 +311,7 @@ impl VerificationResult { results: Ok(vec![]), runtime: Duration::from_secs(0), generated_concrete_test: false, + coverage_results: None, } } @@ -312,23 +325,26 @@ impl VerificationResult { results: Err(42), runtime: Duration::from_secs(0), generated_concrete_test: false, + coverage_results: None, } } - pub fn render( - &self, - output_format: &OutputFormat, - should_panic: bool, - coverage_mode: bool, - ) -> String { + pub fn render(&self, output_format: &OutputFormat, should_panic: bool) -> String { match &self.results { Ok(results) => { let status = self.status; let failed_properties = self.failed_properties; let show_checks = matches!(output_format, OutputFormat::Regular); - let mut result = if coverage_mode { - format_coverage(results, status, should_panic, failed_properties, show_checks) + let mut result = if let Some(cov_results) = &self.coverage_results { + format_coverage( + results, + cov_results, + status, + should_panic, + failed_properties, + show_checks, + ) } else { format_result(results, status, should_panic, failed_properties, show_checks) }; @@ -404,6 +420,74 @@ fn determine_failed_properties(properties: &[Property]) -> FailedProperties { } } +fn coverage_results_from_properties(properties: &[Property]) -> Option { + let cov_properties: Vec<&Property> = + properties.iter().filter(|p| p.is_code_coverage_property()).collect(); + + if cov_properties.is_empty() { + return None; + } + + // Postprocessing the coverage results involves matching on the descriptions + // of code coverage properties with the `counter_re` regex. These are two + // real examples of such descriptions: + // + // ``` + // CounterIncrement(0) $test_cov$ - src/main.rs:5:1 - 6:15 + // ExpressionUsed(0) $test_cov$ - src/main.rs:6:19 - 6:28 + // ``` + // + // The span is further processed to extract the code region attributes. + // Ideally, we should have coverage mappings (i.e., the relation between + // counters and code regions) available in the coverage metadata: + // . If that were the + // case, we would not need the spans in these descriptions. + let counter_re = { + static COUNTER_RE: OnceLock = OnceLock::new(); + COUNTER_RE.get_or_init(|| { + Regex::new( + r#"^(?CounterIncrement|ExpressionUsed)\((?[0-9]+)\) \$(?[^\$]+)\$ - (?.+)"#, + ) + .unwrap() + }) + }; + + let mut coverage_results: BTreeMap> = BTreeMap::default(); + + for prop in cov_properties { + let mut prop_processed = false; + + if let Some(captures) = counter_re.captures(&prop.description) { + let kind = &captures["kind"]; + let counter_num = &captures["counter_num"]; + let function = demangle(&captures["func_name"]).to_string(); + let status = prop.status; + let span = captures["span"].to_string(); + + let counter_id = counter_num.parse().unwrap(); + let term = match kind { + "CounterIncrement" => CoverageTerm::Counter(counter_id), + "ExpressionUsed" => CoverageTerm::Expression(counter_id), + _ => unreachable!("counter kind could not be recognized: {:?}", kind), + }; + let region = CoverageRegion::from_str(span); + + let cov_check = CoverageCheck::new(function, term, region, status); + let file = cov_check.region.file.clone(); + + if let Entry::Vacant(e) = coverage_results.entry(file.clone()) { + e.insert(vec![cov_check]); + } else { + coverage_results.entry(file).and_modify(|checks| checks.push(cov_check)); + } + prop_processed = true; + } + + assert!(prop_processed, "error: coverage property not processed\n{prop:?}"); + } + + Some(CoverageResults::new(coverage_results)) +} /// Solve Unwind Value from conflicting inputs of unwind values. (--default-unwind, annotation-unwind, --unwind) pub fn resolve_unwind_value( args: &VerificationArgs, diff --git a/kani-driver/src/call_single_file.rs b/kani-driver/src/call_single_file.rs index 4b30fe877507..868d1adce636 100644 --- a/kani-driver/src/call_single_file.rs +++ b/kani-driver/src/call_single_file.rs @@ -155,6 +155,11 @@ impl KaniSession { pub fn kani_rustc_flags(&self, lib_config: LibConfig) -> Vec { let mut flags: Vec<_> = base_rustc_flags(lib_config); // We only use panic abort strategy for verification since we cannot handle unwind logic. + if self.args.coverage { + flags.extend_from_slice( + &["-C", "instrument-coverage", "-Z", "no-profiler-runtime"].map(OsString::from), + ); + } flags.extend_from_slice( &[ "-C", diff --git a/kani-driver/src/cbmc_output_parser.rs b/kani-driver/src/cbmc_output_parser.rs index b3a78e8d03e2..8ef1ee153106 100644 --- a/kani-driver/src/cbmc_output_parser.rs +++ b/kani-driver/src/cbmc_output_parser.rs @@ -27,7 +27,7 @@ use anyhow::Result; use console::style; use pathdiff::diff_paths; use rustc_demangle::demangle; -use serde::{Deserialize, Deserializer}; +use serde::{Deserialize, Deserializer, Serialize}; use std::env; use std::io::{BufRead, BufReader}; @@ -321,7 +321,7 @@ impl std::fmt::Display for TraceData { } } -#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "UPPERCASE")] pub enum CheckStatus { Failure, diff --git a/kani-driver/src/cbmc_property_renderer.rs b/kani-driver/src/cbmc_property_renderer.rs index 4f32028b5866..a0202169863c 100644 --- a/kani-driver/src/cbmc_property_renderer.rs +++ b/kani-driver/src/cbmc_property_renderer.rs @@ -4,12 +4,12 @@ use crate::args::OutputFormat; use crate::call_cbmc::{FailedProperties, VerificationStatus}; use crate::cbmc_output_parser::{CheckStatus, ParserItem, Property, TraceItem}; +use crate::coverage::cov_results::CoverageResults; use console::style; use once_cell::sync::Lazy; use regex::Regex; use rustc_demangle::demangle; -use std::collections::{BTreeMap, HashMap}; -use strum_macros::{AsRefStr, Display}; +use std::collections::HashMap; type CbmcAltDescriptions = HashMap<&'static str, Vec<(&'static str, Option<&'static str>)>>; @@ -150,15 +150,6 @@ static CBMC_ALT_DESCRIPTIONS: Lazy = Lazy::new(|| { map }); -#[derive(PartialEq, Eq, AsRefStr, Clone, Copy, Display)] -#[strum(serialize_all = "UPPERCASE")] -// The status of coverage reported by Kani -enum CoverageStatus { - Full, - Partial, - None, -} - const UNSUPPORTED_CONSTRUCT_DESC: &str = "is not currently supported by Kani"; const UNWINDING_ASSERT_DESC: &str = "unwinding assertion loop"; const UNWINDING_ASSERT_REC_DESC: &str = "recursion unwinding assertion"; @@ -431,72 +422,31 @@ pub fn format_result( result_str } -/// Separate checks into coverage and non-coverage based on property class and format them separately for --coverage. We report both verification and processed coverage -/// results +/// Separate checks into coverage and non-coverage based on property class and +/// format them separately for `--coverage`. Then we report both verification +/// and processed coverage results. +/// +/// Note: The reporting of coverage results should be removed once `kani-cov` is +/// introduced. pub fn format_coverage( properties: &[Property], + cov_results: &CoverageResults, status: VerificationStatus, should_panic: bool, failed_properties: FailedProperties, show_checks: bool, ) -> String { - let (coverage_checks, non_coverage_checks): (Vec, Vec) = + let (_coverage_checks, non_coverage_checks): (Vec, Vec) = properties.iter().cloned().partition(|x| x.property_class() == "code_coverage"); let verification_output = format_result(&non_coverage_checks, status, should_panic, failed_properties, show_checks); - let coverage_output = format_result_coverage(&coverage_checks); - let result = format!("{}\n{}", verification_output, coverage_output); + let cov_results_intro = "Source-based code coverage results:"; + let result = format!("{}\n{}\n\n{}", verification_output, cov_results_intro, cov_results); result } -/// Generate coverage result from all coverage properties (i.e., checks with `code_coverage` property class). -/// Loops through each of the checks with the `code_coverage` property class on a line and gives: -/// - A status `FULL` if all checks pertaining to a line number are `COVERED` -/// - A status `NONE` if all checks related to a line are `UNCOVERED` -/// - Otherwise (i.e., if the line contains both) it reports `PARTIAL`. -/// -/// Used when the user requests coverage information with `--coverage`. -/// Output is tested through the `coverage-based` testing suite, not the regular -/// `expected` suite. -fn format_result_coverage(properties: &[Property]) -> String { - let mut formatted_output = String::new(); - formatted_output.push_str("\nCoverage Results:\n"); - - let mut coverage_results: BTreeMap> = - BTreeMap::default(); - for prop in properties { - let src = prop.source_location.clone(); - let file_entries = coverage_results.entry(src.file.unwrap()).or_default(); - let check_status = if prop.status == CheckStatus::Covered { - CoverageStatus::Full - } else { - CoverageStatus::None - }; - - // Create Map> - file_entries - .entry(src.line.unwrap().parse().unwrap()) - .and_modify(|line_status| { - if *line_status != check_status { - *line_status = CoverageStatus::Partial - } - }) - .or_insert(check_status); - } - - // Create formatted string that is returned to the user as output - for (file, checks) in coverage_results.iter() { - for (line_number, coverage_status) in checks { - formatted_output.push_str(&format!("{}, {}, {}\n", file, line_number, coverage_status)); - } - formatted_output.push('\n'); - } - - formatted_output -} - /// Attempts to build a message for a failed property with as much detailed /// information on the source location as possible. fn build_failure_message(description: String, trace: &Option>) -> String { diff --git a/kani-driver/src/coverage/cov_results.rs b/kani-driver/src/coverage/cov_results.rs new file mode 100644 index 000000000000..845ae7de21bb --- /dev/null +++ b/kani-driver/src/coverage/cov_results.rs @@ -0,0 +1,96 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use crate::cbmc_output_parser::CheckStatus; +use serde::{Deserialize, Serialize}; +use std::{collections::BTreeMap, fmt, fmt::Display}; + +/// The coverage data maps a function name to a set of coverage checks. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CoverageResults { + pub data: BTreeMap>, +} + +impl CoverageResults { + pub fn new(data: BTreeMap>) -> Self { + Self { data } + } +} + +impl fmt::Display for CoverageResults { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for (file, checks) in self.data.iter() { + let mut checks_by_function: BTreeMap> = BTreeMap::new(); + + // Group checks by function + for check in checks { + // Insert the check into the vector corresponding to its function + checks_by_function.entry(check.function.clone()).or_default().push(check.clone()); + } + + for (function, checks) in checks_by_function { + writeln!(f, "{file} ({function})")?; + let mut sorted_checks: Vec = checks.to_vec(); + sorted_checks.sort_by(|a, b| a.region.start.cmp(&b.region.start)); + for check in sorted_checks.iter() { + writeln!(f, " * {} {}", check.region, check.status)?; + } + writeln!(f)?; + } + } + Ok(()) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CoverageCheck { + pub function: String, + term: CoverageTerm, + pub region: CoverageRegion, + status: CheckStatus, +} + +impl CoverageCheck { + pub fn new( + function: String, + term: CoverageTerm, + region: CoverageRegion, + status: CheckStatus, + ) -> Self { + Self { function, term, region, status } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum CoverageTerm { + Counter(u32), + Expression(u32), +} + +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] +pub struct CoverageRegion { + pub file: String, + pub start: (u32, u32), + pub end: (u32, u32), +} + +impl Display for CoverageRegion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{} - {}:{}", self.start.0, self.start.1, self.end.0, self.end.1) + } +} + +impl CoverageRegion { + pub fn from_str(str: String) -> Self { + let blank_splits: Vec<&str> = str.split_whitespace().map(|s| s.trim()).collect(); + assert!(blank_splits[1] == "-"); + let str_splits1: Vec<&str> = blank_splits[0].split([':']).collect(); + let str_splits2: Vec<&str> = blank_splits[2].split([':']).collect(); + assert_eq!(str_splits1.len(), 3, "{str:?}"); + assert_eq!(str_splits2.len(), 2, "{str:?}"); + let file = str_splits1[0].to_string(); + let start = (str_splits1[1].parse().unwrap(), str_splits1[2].parse().unwrap()); + let end = (str_splits2[0].parse().unwrap(), str_splits2[1].parse().unwrap()); + Self { file, start, end } + } +} diff --git a/kani-driver/src/coverage/cov_session.rs b/kani-driver/src/coverage/cov_session.rs new file mode 100644 index 000000000000..df82d982bd72 --- /dev/null +++ b/kani-driver/src/coverage/cov_session.rs @@ -0,0 +1,176 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use std::fs; +use std::fs::File; +use std::io::Write; + +use crate::harness_runner::HarnessResult; +use crate::project::Project; +use crate::KaniSession; +use anyhow::{bail, Result}; + +impl KaniSession { + /// Saves metadata required for coverage-related features. + /// At present, this metadata consists of the following: + /// - The file names of the project's source code. + /// + /// Note: Currently, coverage mappings are not included due to technical + /// limitations. But this is where we should save them. + pub fn save_coverage_metadata(&self, project: &Project, stamp: &String) -> Result<()> { + if self.args.target_dir.is_some() { + self.save_coverage_metadata_cargo(project, stamp) + } else { + self.save_coverage_metadata_standalone(project, stamp) + } + } + + fn save_coverage_metadata_cargo(&self, project: &Project, stamp: &String) -> Result<()> { + let build_target = env!("TARGET"); + let metadata = self.cargo_metadata(build_target)?; + let target_dir = self + .args + .target_dir + .as_ref() + .unwrap_or(&metadata.target_directory.clone().into()) + .clone() + .join("kani"); + + let outdir = target_dir.join(build_target).join(format!("kanicov_{stamp}")); + + // Generally we don't expect this directory to exist, but there's no + // reason to delete it if it does. + if !outdir.exists() { + fs::create_dir(&outdir)?; + } + + // Collect paths to source files in the project + let mut source_targets = Vec::new(); + if let Some(metadata) = &project.cargo_metadata { + for package in &metadata.packages { + for target in &package.targets { + source_targets.push(target.src_path.clone()); + } + } + } else { + bail!("could not find project metadata required for coverage metadata"); + } + + let kanimap_name = format!("kanicov_{stamp}_kanimap"); + let file_name = outdir.join(kanimap_name).with_extension("json"); + let mut kanimap_file = File::create(file_name)?; + + let serialized_data = serde_json::to_string(&source_targets)?; + kanimap_file.write_all(serialized_data.as_bytes())?; + + Ok(()) + } + + fn save_coverage_metadata_standalone(&self, project: &Project, stamp: &String) -> Result<()> { + let input = project.input.clone().unwrap().canonicalize().unwrap(); + let input_dir = input.parent().unwrap().to_path_buf(); + let outdir = input_dir.join(format!("kanicov_{stamp}")); + + // Generally we don't expect this directory to exist, but there's no + // reason to delete it if it does. + if !outdir.exists() { + fs::create_dir(&outdir)?; + } + + // In this case, the source files correspond to the input file + let source_targets = vec![input]; + + let kanimap_name = format!("kanicov_{stamp}_kanimap"); + let file_name = outdir.join(kanimap_name).with_extension("json"); + let mut kanimap_file = File::create(file_name)?; + + let serialized_data = serde_json::to_string(&source_targets)?; + kanimap_file.write_all(serialized_data.as_bytes())?; + + Ok(()) + } + + /// Saves raw coverage check results required for coverage-related features. + pub fn save_coverage_results( + &self, + project: &Project, + results: &Vec, + stamp: &String, + ) -> Result<()> { + if self.args.target_dir.is_some() { + self.save_coverage_results_cargo(results, stamp) + } else { + self.save_coverage_results_standalone(project, results, stamp) + } + } + + pub fn save_coverage_results_cargo( + &self, + results: &Vec, + stamp: &String, + ) -> Result<()> { + let build_target = env!("TARGET"); + let metadata = self.cargo_metadata(build_target)?; + let target_dir = self + .args + .target_dir + .as_ref() + .unwrap_or(&metadata.target_directory.clone().into()) + .clone() + .join("kani"); + + let outdir = target_dir.join(build_target).join(format!("kanicov_{stamp}")); + + // This directory should have been created by `save_coverage_metadata`, + // so now we expect it to exist. + if !outdir.exists() { + bail!("directory associated to coverage run does not exist") + } + + for harness_res in results { + let harness_name = harness_res.harness.mangled_name.clone(); + let kaniraw_name = format!("{harness_name}_kaniraw"); + let file_name = outdir.join(kaniraw_name).with_extension("json"); + let mut cov_file = File::create(file_name)?; + + let cov_results = &harness_res.result.coverage_results.clone().unwrap(); + let serialized_data = serde_json::to_string(&cov_results)?; + cov_file.write_all(serialized_data.as_bytes())?; + } + + println!("[info] Coverage results saved to {}", &outdir.display()); + Ok(()) + } + + pub fn save_coverage_results_standalone( + &self, + project: &Project, + results: &Vec, + stamp: &String, + ) -> Result<()> { + let input = project.input.clone().unwrap().canonicalize().unwrap(); + let input_dir = input.parent().unwrap().to_path_buf(); + let outdir = input_dir.join(format!("kanicov_{stamp}")); + + // This directory should have been created by `save_coverage_metadata`, + // so now we expect it to exist. + if !outdir.exists() { + bail!("directory associated to coverage run does not exist") + } + + for harness_res in results { + let harness_name = harness_res.harness.mangled_name.clone(); + let kaniraw_name = format!("{harness_name}_kaniraw"); + let file_name = outdir.join(kaniraw_name).with_extension("json"); + let mut cov_file = File::create(file_name)?; + + let cov_results = &harness_res.result.coverage_results.clone().unwrap(); + let serialized_data = serde_json::to_string(&cov_results)?; + cov_file.write_all(serialized_data.as_bytes())?; + } + + println!("[info] Coverage results saved to {}", &outdir.display()); + + Ok(()) + } +} diff --git a/kani-driver/src/coverage/mod.rs b/kani-driver/src/coverage/mod.rs new file mode 100644 index 000000000000..2f7072aa82aa --- /dev/null +++ b/kani-driver/src/coverage/mod.rs @@ -0,0 +1,5 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +pub mod cov_results; +pub mod cov_session; diff --git a/kani-driver/src/harness_runner.rs b/kani-driver/src/harness_runner.rs index 992e226e45db..7e432932ab29 100644 --- a/kani-driver/src/harness_runner.rs +++ b/kani-driver/src/harness_runner.rs @@ -124,11 +124,7 @@ impl KaniSession { if !self.args.common_args.quiet && self.args.output_format != OutputFormat::Old { println!( "{}", - result.render( - &self.args.output_format, - harness.attributes.should_panic, - self.args.coverage - ) + result.render(&self.args.output_format, harness.attributes.should_panic) ); } self.gen_and_add_concrete_playback(harness, &mut result)?; @@ -192,6 +188,10 @@ impl KaniSession { } } + if self.args.coverage { + self.show_coverage_summary()?; + } + if failing > 0 { // Failure exit code without additional error message drop(self); @@ -200,4 +200,11 @@ impl KaniSession { Ok(()) } + + /// Show a coverage summary. + /// + /// This is just a placeholder for now. + fn show_coverage_summary(&self) -> Result<()> { + Ok(()) + } } diff --git a/kani-driver/src/main.rs b/kani-driver/src/main.rs index 3bb38ed1294c..d3bd697d2ea4 100644 --- a/kani-driver/src/main.rs +++ b/kani-driver/src/main.rs @@ -5,6 +5,7 @@ use std::ffi::OsString; use std::process::ExitCode; use anyhow::Result; +use time::{format_description, OffsetDateTime}; use args::{check_is_valid, CargoKaniSubcommand}; use args_toml::join_args; @@ -30,6 +31,7 @@ mod call_single_file; mod cbmc_output_parser; mod cbmc_property_renderer; mod concrete_playback; +mod coverage; mod harness_runner; mod metadata; mod project; @@ -129,6 +131,23 @@ fn verify_project(project: Project, session: KaniSession) -> Result<()> { let runner = harness_runner::HarnessRunner { sess: &session, project: &project }; let results = runner.check_all_harnesses(&harnesses)?; + if session.args.coverage { + // We generate a timestamp to save the coverage data in a folder named + // `kanicov_` where `` is the current date based on `format` + // below. The purpose of adding timestamps to the folder name is to make + // coverage results easily identifiable. Using a timestamp makes + // coverage results not only distinguishable, but also easy to relate to + // verification runs. We expect this to be particularly helpful for + // users in a proof debugging session, who are usually interested in the + // most recent results. + let time_now = OffsetDateTime::now_utc(); + let format = format_description::parse("[year]-[month]-[day]_[hour]-[minute]").unwrap(); + let timestamp = time_now.format(&format).unwrap(); + + session.save_coverage_metadata(&project, ×tamp)?; + session.save_coverage_results(&project, &results, ×tamp)?; + } + session.print_final_summary(&results) } diff --git a/kani-driver/src/project.rs b/kani-driver/src/project.rs index 2bc0845cdb46..e0d2d2edd139 100644 --- a/kani-driver/src/project.rs +++ b/kani-driver/src/project.rs @@ -33,6 +33,9 @@ pub struct Project { /// The directory where all outputs should be directed to. This path represents the canonical /// version of outdir. pub outdir: PathBuf, + /// The path to the input file the project was built from. + /// Note that it will only be `Some(...)` if this was built from a standalone project. + pub input: Option, /// The collection of artifacts kept as part of this project. artifacts: Vec, /// Records the cargo metadata from the build, if there was any @@ -82,6 +85,7 @@ impl Project { fn try_new( session: &KaniSession, outdir: PathBuf, + input: Option, metadata: Vec, cargo_metadata: Option, failed_targets: Option>, @@ -115,7 +119,7 @@ impl Project { } } - Ok(Project { outdir, metadata, artifacts, cargo_metadata, failed_targets }) + Ok(Project { outdir, input, metadata, artifacts, cargo_metadata, failed_targets }) } } @@ -178,6 +182,7 @@ pub fn cargo_project(session: &KaniSession, keep_going: bool) -> Result Project::try_new( session, outdir, + None, metadata, Some(outputs.cargo_metadata), outputs.failed_targets, @@ -243,7 +248,14 @@ impl<'a> StandaloneProjectBuilder<'a> { let metadata = from_json(&self.metadata)?; // Create the project with the artifacts built by the compiler. - let result = Project::try_new(self.session, self.outdir, vec![metadata], None, None); + let result = Project::try_new( + self.session, + self.outdir, + Some(self.input), + vec![metadata], + None, + None, + ); if let Ok(project) = &result { self.session.record_temporary_files(&project.artifacts); } @@ -297,5 +309,5 @@ pub(crate) fn std_project(std_path: &Path, session: &KaniSession) -> Result>>()?; - Project::try_new(session, outdir, metadata, None, None) + Project::try_new(session, outdir, None, metadata, None, None) } diff --git a/kani_metadata/src/unstable.rs b/kani_metadata/src/unstable.rs index 120ab0a9e55c..11df998c820f 100644 --- a/kani_metadata/src/unstable.rs +++ b/kani_metadata/src/unstable.rs @@ -78,8 +78,9 @@ pub enum UnstableFeature { ConcretePlayback, /// Enable Kani's unstable async library. AsyncLib, - /// Enable line coverage instrumentation/reports. - LineCoverage, + /// Enable source-based code coverage workflow. + /// See [RFC-0011](https://model-checking.github.io/kani/rfc/rfcs/0011-source-coverage.html) + SourceCoverage, /// Enable function contracts [RFC 9](https://model-checking.github.io/kani/rfc/rfcs/0009-function-contracts.html) FunctionContracts, /// Memory predicate APIs. diff --git a/tests/coverage/abort/expected b/tests/coverage/abort/expected new file mode 100644 index 000000000000..91142ebf94fc --- /dev/null +++ b/tests/coverage/abort/expected @@ -0,0 +1,13 @@ +Source-based code coverage results: + +main.rs (main)\ + * 9:1 - 9:11 COVERED\ + * 10:9 - 10:10 COVERED\ + * 10:14 - 10:18 COVERED\ + * 13:13 - 13:29 COVERED\ + * 14:10 - 15:18 COVERED\ + * 17:13 - 17:29 UNCOVERED\ + * 18:10 - 18:11 COVERED\ + * 20:5 - 20:12 UNCOVERED\ + * 20:20 - 20:41 UNCOVERED\ + * 21:1 - 21:2 UNCOVERED diff --git a/tests/coverage/unreachable/abort/main.rs b/tests/coverage/abort/main.rs similarity index 100% rename from tests/coverage/unreachable/abort/main.rs rename to tests/coverage/abort/main.rs diff --git a/tests/coverage/assert/expected b/tests/coverage/assert/expected new file mode 100644 index 000000000000..46bb664cf6f5 --- /dev/null +++ b/tests/coverage/assert/expected @@ -0,0 +1,9 @@ +Source-based code coverage results: + +test.rs (foo) + * 5:1 - 7:13 COVERED\ + * 9:9 - 10:17 COVERED\ + * 10:18 - 13:10 UNCOVERED\ + * 13:10 - 13:11 UNCOVERED\ + * 14:12 - 17:6 COVERED\ + * 18:1 - 18:2 COVERED diff --git a/tests/coverage/unreachable/assert/test.rs b/tests/coverage/assert/test.rs similarity index 100% rename from tests/coverage/unreachable/assert/test.rs rename to tests/coverage/assert/test.rs diff --git a/tests/coverage/assert_eq/expected b/tests/coverage/assert_eq/expected new file mode 100644 index 000000000000..c2eee7adf803 --- /dev/null +++ b/tests/coverage/assert_eq/expected @@ -0,0 +1,8 @@ +Source-based code coverage results: + +test.rs (main)\ + * 5:1 - 6:29 COVERED\ + * 7:25 - 7:27 COVERED\ + * 7:37 - 7:39 COVERED\ + * 8:15 - 10:6 UNCOVERED\ + * 10:6 - 10:7 COVERED diff --git a/tests/coverage/unreachable/assert_eq/test.rs b/tests/coverage/assert_eq/test.rs similarity index 100% rename from tests/coverage/unreachable/assert_eq/test.rs rename to tests/coverage/assert_eq/test.rs diff --git a/tests/coverage/assert_ne/expected b/tests/coverage/assert_ne/expected new file mode 100644 index 000000000000..c9b727da0f82 --- /dev/null +++ b/tests/coverage/assert_ne/expected @@ -0,0 +1,9 @@ +Source-based code coverage results: + +test.rs (main)\ + * 5:1 - 7:13 COVERED\ + * 8:13 - 10:18 COVERED\ + * 10:19 - 12:10 UNCOVERED\ + * 12:10 - 12:11 COVERED\ + * 13:6 - 13:7 COVERED\ + * 14:1 - 14:2 COVERED diff --git a/tests/coverage/unreachable/assert_ne/test.rs b/tests/coverage/assert_ne/test.rs similarity index 100% rename from tests/coverage/unreachable/assert_ne/test.rs rename to tests/coverage/assert_ne/test.rs diff --git a/tests/coverage/break/expected b/tests/coverage/break/expected new file mode 100644 index 000000000000..739735cdf1a2 --- /dev/null +++ b/tests/coverage/break/expected @@ -0,0 +1,13 @@ +Source-based code coverage results: + +main.rs (find_positive)\ + * 4:1 - 4:47 COVERED\ + * 5:10 - 5:13 COVERED\ + * 5:17 - 5:21 COVERED\ + * 7:20 - 7:29 COVERED\ + * 8:10 - 8:11 COVERED\ + * 11:5 - 11:9 UNCOVERED\ + * 12:1 - 12:2 COVERED + +main.rs (main)\ + * 15:1 - 19:2 COVERED diff --git a/tests/coverage/unreachable/break/main.rs b/tests/coverage/break/main.rs similarity index 82% rename from tests/coverage/unreachable/break/main.rs rename to tests/coverage/break/main.rs index 4e795bd2a6ea..79f422a0c283 100644 --- a/tests/coverage/unreachable/break/main.rs +++ b/tests/coverage/break/main.rs @@ -7,7 +7,7 @@ fn find_positive(nums: &[i32]) -> Option { return Some(num); } } - // This part is unreachable if there is at least one positive number. + // `None` is unreachable because there is at least one positive number. None } diff --git a/tests/coverage/compare/expected b/tests/coverage/compare/expected new file mode 100644 index 000000000000..153dbfa37d80 --- /dev/null +++ b/tests/coverage/compare/expected @@ -0,0 +1,11 @@ +Source-based code coverage results: + +main.rs (compare)\ + * 4:1 - 6:14 COVERED\ + * 6:17 - 6:18 COVERED\ + * 6:28 - 6:29 UNCOVERED + +main.rs (main)\ + * 10:1 - 13:14 COVERED\ + * 13:15 - 15:6 COVERED\ + * 15:6 - 15:7 COVERED diff --git a/tests/coverage/unreachable/compare/main.rs b/tests/coverage/compare/main.rs similarity index 73% rename from tests/coverage/unreachable/compare/main.rs rename to tests/coverage/compare/main.rs index a10fb84e29a8..43318ac0547e 100644 --- a/tests/coverage/unreachable/compare/main.rs +++ b/tests/coverage/compare/main.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT fn compare(x: u16, y: u16) -> u16 { - // The line below should be reported as PARTIAL for having both REACHABLE and UNREACHABLE checks + // The case where `x < y` isn't possible so its region is `UNCOVERED` if x >= y { 1 } else { 0 } } diff --git a/tests/coverage/contradiction/expected b/tests/coverage/contradiction/expected new file mode 100644 index 000000000000..db3676d7da15 --- /dev/null +++ b/tests/coverage/contradiction/expected @@ -0,0 +1,9 @@ +Source-based code coverage results: + +main.rs (contradiction)\ + * 4:1 - 7:13 COVERED\ + * 8:12 - 8:17 COVERED\ + * 8:18 - 10:10 UNCOVERED\ + * 10:10 - 10:11 COVERED\ + * 11:12 - 13:6 COVERED\ + * 14:1 - 14:2 COVERED diff --git a/tests/coverage/unreachable/contradiction/main.rs b/tests/coverage/contradiction/main.rs similarity index 100% rename from tests/coverage/unreachable/contradiction/main.rs rename to tests/coverage/contradiction/main.rs diff --git a/tests/coverage/debug-assert/expected b/tests/coverage/debug-assert/expected new file mode 100644 index 000000000000..fbe57690d347 --- /dev/null +++ b/tests/coverage/debug-assert/expected @@ -0,0 +1,10 @@ +Source-based code coverage results: + +main.rs (main)\ + * 10:1 - 10:11 COVERED\ + * 11:9 - 11:10 COVERED\ + * 11:14 - 11:18 COVERED\ + * 12:30 - 12:71 UNCOVERED\ + * 13:9 - 13:23 UNCOVERED\ + * 13:25 - 13:53 UNCOVERED\ + * 15:1 - 15:2 UNCOVERED diff --git a/tests/coverage/debug-assert/main.rs b/tests/coverage/debug-assert/main.rs new file mode 100644 index 000000000000..a57a45c8c724 --- /dev/null +++ b/tests/coverage/debug-assert/main.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This test checks that the regions after the `debug_assert` macro are +//! `UNCOVERED`. In fact, for this example, the region associated to `"This +//! should fail and stop the execution"` is also `UNCOVERED` because the macro +//! calls span two regions each. + +#[kani::proof] +fn main() { + for i in 0..4 { + debug_assert!(i > 0, "This should fail and stop the execution"); + assert!(i == 0, "This should be unreachable"); + } +} diff --git a/tests/coverage/div-zero/expected b/tests/coverage/div-zero/expected new file mode 100644 index 000000000000..f351005f4f22 --- /dev/null +++ b/tests/coverage/div-zero/expected @@ -0,0 +1,9 @@ +Source-based code coverage results: + +test.rs (div)\ + * 4:1 - 5:14 COVERED\ + * 5:17 - 5:22 COVERED\ + * 5:32 - 5:33 UNCOVERED + +test.rs (main)\ + * 9:1 - 11:2 COVERED diff --git a/tests/coverage/reachable/div-zero/reachable_pass/test.rs b/tests/coverage/div-zero/test.rs similarity index 64% rename from tests/coverage/reachable/div-zero/reachable_pass/test.rs rename to tests/coverage/div-zero/test.rs index 766fb5a89d88..e858b57a12be 100644 --- a/tests/coverage/reachable/div-zero/reachable_pass/test.rs +++ b/tests/coverage/div-zero/test.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT fn div(x: u16, y: u16) -> u16 { - if y != 0 { x / y } else { 0 } // PARTIAL: some cases are `COVERED`, others are not + if y != 0 { x / y } else { 0 } } #[kani::proof] diff --git a/tests/coverage/early-return/expected b/tests/coverage/early-return/expected new file mode 100644 index 000000000000..53cde3abeaf8 --- /dev/null +++ b/tests/coverage/early-return/expected @@ -0,0 +1,12 @@ +Source-based code coverage results: + +main.rs (find_index)\ + * 4:1 - 4:59 COVERED\ + * 5:10 - 5:21 COVERED\ + * 7:20 - 7:31 COVERED\ + * 8:10 - 8:11 COVERED\ + * 10:5 - 10:9 UNCOVERED\ + * 11:1 - 11:2 COVERED + +main.rs (main)\ + * 14:1 - 19:2 COVERED diff --git a/tests/coverage/unreachable/early-return/main.rs b/tests/coverage/early-return/main.rs similarity index 100% rename from tests/coverage/unreachable/early-return/main.rs rename to tests/coverage/early-return/main.rs diff --git a/tests/coverage/if-statement-multi/expected b/tests/coverage/if-statement-multi/expected new file mode 100644 index 000000000000..4e8382d10a6f --- /dev/null +++ b/tests/coverage/if-statement-multi/expected @@ -0,0 +1,11 @@ +Source-based code coverage results: + +test.rs (main)\ + * 21:1 - 26:2 COVERED + +test.rs (test_cov)\ + * 16:1 - 17:15 COVERED\ + * 17:19 - 17:28 UNCOVERED\ + * 17:31 - 17:35 COVERED\ + * 17:45 - 17:50 UNCOVERED\ + * 18:1 - 18:2 COVERED diff --git a/tests/coverage/if-statement-multi/test.rs b/tests/coverage/if-statement-multi/test.rs new file mode 100644 index 000000000000..ac00dec3f451 --- /dev/null +++ b/tests/coverage/if-statement-multi/test.rs @@ -0,0 +1,26 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: --coverage -Zsource-coverage + +//! Checks that we are covering all regions except +//! * the `val == 42` condition +//! * the `false` branch +//! +//! No coverage information is shown for `_other_function` because it's sliced +//! off: + +fn _other_function() { + println!("Hello, world!"); +} + +fn test_cov(val: u32) -> bool { + if val < 3 || val == 42 { true } else { false } +} + +#[cfg_attr(kani, kani::proof)] +fn main() { + let test1 = test_cov(1); + let test2 = test_cov(2); + assert!(test1); + assert!(test2); +} diff --git a/tests/coverage/if-statement/expected b/tests/coverage/if-statement/expected new file mode 100644 index 000000000000..b85b95de9c84 --- /dev/null +++ b/tests/coverage/if-statement/expected @@ -0,0 +1,14 @@ +Source-based code coverage results: + +main.rs (check_number)\ + * 4:1 - 5:15 COVERED\ + * 7:12 - 7:24 COVERED\ + * 7:27 - 7:46 UNCOVERED\ + * 7:56 - 7:74 COVERED\ + * 8:15 - 8:22 UNCOVERED\ + * 9:9 - 9:19 UNCOVERED\ + * 11:9 - 11:15 UNCOVERED\ + * 13:1 - 13:2 COVERED + +main.rs (main)\ + * 16:1 - 20:2 COVERED diff --git a/tests/coverage/unreachable/if-statement/main.rs b/tests/coverage/if-statement/main.rs similarity index 78% rename from tests/coverage/unreachable/if-statement/main.rs rename to tests/coverage/if-statement/main.rs index f497cd76808e..c18d4dd4a5e4 100644 --- a/tests/coverage/unreachable/if-statement/main.rs +++ b/tests/coverage/if-statement/main.rs @@ -3,7 +3,7 @@ fn check_number(num: i32) -> &'static str { if num > 0 { - // The line is partially covered because the if statement is UNREACHABLE while the else statement is reachable + // The next line is partially covered if num % 2 == 0 { "Positive and Even" } else { "Positive and Odd" } } else if num < 0 { "Negative" diff --git a/tests/coverage/known_issues/assert_uncovered_end/expected b/tests/coverage/known_issues/assert_uncovered_end/expected new file mode 100644 index 000000000000..ceba065ce424 --- /dev/null +++ b/tests/coverage/known_issues/assert_uncovered_end/expected @@ -0,0 +1,9 @@ +Source-based code coverage results: + +test.rs (check_assert)\ + * 9:1 - 10:34 COVERED\ + * 11:14 - 13:6 COVERED\ + * 13:6 - 13:7 UNCOVERED + +test.rs (check_assert::{closure#0})\ + * 10:40 - 10:49 COVERED diff --git a/tests/coverage/known_issues/assert_uncovered_end/test.rs b/tests/coverage/known_issues/assert_uncovered_end/test.rs new file mode 100644 index 000000000000..c3da20c8b00b --- /dev/null +++ b/tests/coverage/known_issues/assert_uncovered_end/test.rs @@ -0,0 +1,14 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Checks that `check_assert` is fully covered. At present, the coverage for +//! this test reports an uncovered single-column region at the end of the `if` +//! statement: + +#[kani::proof] +fn check_assert() { + let x: u32 = kani::any_where(|val| *val == 5); + if x > 3 { + assert!(x > 4); + } +} diff --git a/tests/coverage/known_issues/assume_assert/expected b/tests/coverage/known_issues/assume_assert/expected new file mode 100644 index 000000000000..55f3235d7d24 --- /dev/null +++ b/tests/coverage/known_issues/assume_assert/expected @@ -0,0 +1,4 @@ +Source-based code coverage results: + +main.rs (check_assume_assert)\ + * 11:1 - 15:2 COVERED diff --git a/tests/coverage/known_issues/assume_assert/main.rs b/tests/coverage/known_issues/assume_assert/main.rs new file mode 100644 index 000000000000..90f26b2121fa --- /dev/null +++ b/tests/coverage/known_issues/assume_assert/main.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This test should check that the region after `kani::assume(false)` is +//! `UNCOVERED`. However, due to a technical limitation in `rustc`'s coverage +//! instrumentation, only one `COVERED` region is reported for the whole +//! function. More details in +//! . + +#[kani::proof] +fn check_assume_assert() { + let a: u8 = kani::any(); + kani::assume(false); + assert!(a < 5); +} diff --git a/tests/coverage/known_issues/out-of-bounds/expected b/tests/coverage/known_issues/out-of-bounds/expected new file mode 100644 index 000000000000..8ab9e2e15627 --- /dev/null +++ b/tests/coverage/known_issues/out-of-bounds/expected @@ -0,0 +1,7 @@ +Source-based code coverage results: + +test.rs (get)\ + * 8:1 - 10:2 COVERED + +test.rs (main)\ + * 13:1 - 15:2 COVERED diff --git a/tests/coverage/known_issues/out-of-bounds/test.rs b/tests/coverage/known_issues/out-of-bounds/test.rs new file mode 100644 index 000000000000..83242590815b --- /dev/null +++ b/tests/coverage/known_issues/out-of-bounds/test.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This test should check that the return in `get` is `UNCOVERED`. However, the +//! coverage results currently report that the whole function is `COVERED`, +//! likely due to + +fn get(s: &[i16], index: usize) -> i16 { + s[index] +} + +#[kani::proof] +fn main() { + get(&[7, -83, 19], 15); +} diff --git a/tests/coverage/known_issues/variant/expected b/tests/coverage/known_issues/variant/expected new file mode 100644 index 000000000000..13383ed3bab0 --- /dev/null +++ b/tests/coverage/known_issues/variant/expected @@ -0,0 +1,14 @@ +Source-based code coverage results: + +main.rs (main)\ + * 29:1 - 32:2 COVERED + +main.rs (print_direction)\ + * 16:1 - 16:36 COVERED\ + * 18:11 - 18:14 UNCOVERED\ + * 19:26 - 19:47 UNCOVERED\ + * 20:28 - 20:51 UNCOVERED\ + * 21:28 - 21:51 COVERED\ + * 22:34 - 22:63 UNCOVERED\ + * 24:14 - 24:45 UNCOVERED\ + * 26:1 - 26:2 COVERED diff --git a/tests/coverage/unreachable/variant/main.rs b/tests/coverage/known_issues/variant/main.rs similarity index 72% rename from tests/coverage/unreachable/variant/main.rs rename to tests/coverage/known_issues/variant/main.rs index 76a589147bca..c654ca355c45 100644 --- a/tests/coverage/unreachable/variant/main.rs +++ b/tests/coverage/known_issues/variant/main.rs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT //! Checks coverage results in an example with a `match` statement matching on -//! all enum variants. +//! all enum variants. Currently, it does not yield the expected results because +//! it reports the `dir` in the match statement as `UNCOVERED`: +//! enum Direction { Up, @@ -12,6 +14,7 @@ enum Direction { } fn print_direction(dir: Direction) { + // For some reason, `dir`'s span is reported as `UNCOVERED` too match dir { Direction::Up => println!("Going up!"), Direction::Down => println!("Going down!"), diff --git a/tests/coverage/multiple-harnesses/expected b/tests/coverage/multiple-harnesses/expected new file mode 100644 index 000000000000..b5362147fed1 --- /dev/null +++ b/tests/coverage/multiple-harnesses/expected @@ -0,0 +1,37 @@ +Source-based code coverage results: + +main.rs (estimate_size)\ + * 4:1 - 7:15 COVERED\ + * 8:12 - 8:19 COVERED\ + * 9:20 - 9:21 COVERED\ + * 11:20 - 11:21 COVERED\ + * 13:15 - 13:23 COVERED\ + * 14:12 - 14:20 COVERED\ + * 15:20 - 15:21 COVERED\ + * 17:20 - 17:21 COVERED\ + * 20:12 - 20:20 COVERED\ + * 21:20 - 21:21 COVERED\ + * 23:20 - 23:21 COVERED\ + * 26:1 - 26:2 COVERED + +main.rs (fully_covered)\ + * 39:1 - 44:2 COVERED + +Source-based code coverage results: + +main.rs (estimate_size)\ + * 4:1 - 7:15 COVERED\ + * 8:12 - 8:19 COVERED\ + * 9:20 - 9:21 COVERED\ + * 11:20 - 11:21 COVERED\ + * 13:15 - 13:23 COVERED\ + * 14:12 - 14:20 COVERED\ + * 15:20 - 15:21 COVERED\ + * 17:20 - 17:21 COVERED\ + * 20:12 - 20:20 COVERED\ + * 21:20 - 21:21 COVERED\ + * 23:20 - 23:21 UNCOVERED\ + * 26:1 - 26:2 COVERED + +main.rs (mostly_covered)\ + * 30:1 - 35:2 COVERED diff --git a/tests/coverage/unreachable/multiple-harnesses/main.rs b/tests/coverage/multiple-harnesses/main.rs similarity index 100% rename from tests/coverage/unreachable/multiple-harnesses/main.rs rename to tests/coverage/multiple-harnesses/main.rs diff --git a/tests/coverage/overflow-failure/expected b/tests/coverage/overflow-failure/expected new file mode 100644 index 000000000000..db4f29d51336 --- /dev/null +++ b/tests/coverage/overflow-failure/expected @@ -0,0 +1,9 @@ +Source-based code coverage results: + +test.rs (cond_reduce)\ + * 7:1 - 8:18 COVERED\ + * 8:21 - 8:27 COVERED\ + * 8:37 - 8:38 UNCOVERED + +test.rs (main)\ + * 12:1 - 15:2 COVERED diff --git a/tests/coverage/reachable/overflow/reachable_fail/test.rs b/tests/coverage/overflow-failure/test.rs similarity index 66% rename from tests/coverage/reachable/overflow/reachable_fail/test.rs rename to tests/coverage/overflow-failure/test.rs index d435612f2342..cd711f3aeb9e 100644 --- a/tests/coverage/reachable/overflow/reachable_fail/test.rs +++ b/tests/coverage/overflow-failure/test.rs @@ -5,11 +5,11 @@ //! arithmetic overflow failure (caused by the second call to `cond_reduce`). fn cond_reduce(thresh: u32, x: u32) -> u32 { - if x > thresh { x - 50 } else { x } // PARTIAL: some cases are `COVERED`, others are not + if x > thresh { x - 50 } else { x } } #[kani::proof] fn main() { cond_reduce(60, 70); cond_reduce(40, 42); -} // NONE: Caused by the arithmetic overflow failure from the second call to `cond_reduce` +} diff --git a/tests/coverage/overflow-full-coverage/expected b/tests/coverage/overflow-full-coverage/expected new file mode 100644 index 000000000000..4d17761505eb --- /dev/null +++ b/tests/coverage/overflow-full-coverage/expected @@ -0,0 +1,9 @@ +Source-based code coverage results: + +test.rs (main)\ + * 12:1 - 17:2 COVERED + +test.rs (reduce)\ + * 7:1 - 8:16 COVERED\ + * 8:19 - 8:27 COVERED\ + * 8:37 - 8:38 COVERED diff --git a/tests/coverage/reachable/overflow/reachable_pass/test.rs b/tests/coverage/overflow-full-coverage/test.rs similarity index 62% rename from tests/coverage/reachable/overflow/reachable_pass/test.rs rename to tests/coverage/overflow-full-coverage/test.rs index 0b05874efc84..1c3467275b33 100644 --- a/tests/coverage/reachable/overflow/reachable_pass/test.rs +++ b/tests/coverage/overflow-full-coverage/test.rs @@ -1,8 +1,8 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -//! Checks that Kani reports the correct coverage results (`FULL` for all lines) -//! in a case where arithmetic overflow failures are prevented. +//! Checks that Kani reports all regions as `COVERED` as expected in this case +//! where arithmetic overflow failures are prevented. fn reduce(x: u32) -> u32 { if x > 1000 { x - 1000 } else { x } diff --git a/tests/coverage/reachable/assert-false/expected b/tests/coverage/reachable/assert-false/expected deleted file mode 100644 index 97ffbe1d96e4..000000000000 --- a/tests/coverage/reachable/assert-false/expected +++ /dev/null @@ -1,8 +0,0 @@ -coverage/reachable/assert-false/main.rs, 6, FULL -coverage/reachable/assert-false/main.rs, 7, FULL -coverage/reachable/assert-false/main.rs, 11, PARTIAL -coverage/reachable/assert-false/main.rs, 12, PARTIAL -coverage/reachable/assert-false/main.rs, 15, PARTIAL -coverage/reachable/assert-false/main.rs, 16, FULL -coverage/reachable/assert-false/main.rs, 17, PARTIAL -coverage/reachable/assert-false/main.rs, 19, FULL diff --git a/tests/coverage/reachable/assert-false/main.rs b/tests/coverage/reachable/assert-false/main.rs deleted file mode 100644 index 42563b1cd518..000000000000 --- a/tests/coverage/reachable/assert-false/main.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -//! Check that the assert is reported as `PARTIAL` for having both `COVERED` and `UNCOVERED` coverage checks -fn any_bool() -> bool { - kani::any() -} - -#[kani::proof] -fn main() { - if any_bool() { - assert!(false); - } - - if any_bool() { - let s = "Fail with custom runtime message"; - assert!(false, "{}", s); - } -} diff --git a/tests/coverage/reachable/assert/reachable_pass/expected b/tests/coverage/reachable/assert/reachable_pass/expected deleted file mode 100644 index 9d21185b3a83..000000000000 --- a/tests/coverage/reachable/assert/reachable_pass/expected +++ /dev/null @@ -1,4 +0,0 @@ -coverage/reachable/assert/reachable_pass/test.rs, 6, FULL -coverage/reachable/assert/reachable_pass/test.rs, 7, PARTIAL -coverage/reachable/assert/reachable_pass/test.rs, 8, FULL -coverage/reachable/assert/reachable_pass/test.rs, 10, FULL diff --git a/tests/coverage/reachable/assert/reachable_pass/test.rs b/tests/coverage/reachable/assert/reachable_pass/test.rs deleted file mode 100644 index 72acca2b764c..000000000000 --- a/tests/coverage/reachable/assert/reachable_pass/test.rs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -#[kani::proof] -fn main() { - let x: u32 = kani::any_where(|val| *val == 5); - if x > 3 { - assert!(x > 4); // FULL: `x > 4` since `x = 5` - } -} diff --git a/tests/coverage/reachable/bounds/reachable_fail/expected b/tests/coverage/reachable/bounds/reachable_fail/expected deleted file mode 100644 index fedfec8b2a1e..000000000000 --- a/tests/coverage/reachable/bounds/reachable_fail/expected +++ /dev/null @@ -1,4 +0,0 @@ -coverage/reachable/bounds/reachable_fail/test.rs, 5, PARTIAL -coverage/reachable/bounds/reachable_fail/test.rs, 6, NONE -coverage/reachable/bounds/reachable_fail/test.rs, 10, PARTIAL -coverage/reachable/bounds/reachable_fail/test.rs, 11, NONE diff --git a/tests/coverage/reachable/bounds/reachable_fail/test.rs b/tests/coverage/reachable/bounds/reachable_fail/test.rs deleted file mode 100644 index cebd78b2d5d9..000000000000 --- a/tests/coverage/reachable/bounds/reachable_fail/test.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -fn get(s: &[i16], index: usize) -> i16 { - s[index] // PARTIAL: `s[index]` is covered, but `index = 15` induces a failure -} // NONE: `index = 15` caused failure earlier - -#[kani::proof] -fn main() { - get(&[7, -83, 19], 15); -} // NONE: `index = 15` caused failure earlier diff --git a/tests/coverage/reachable/div-zero/reachable_fail/expected b/tests/coverage/reachable/div-zero/reachable_fail/expected deleted file mode 100644 index c1ac77404680..000000000000 --- a/tests/coverage/reachable/div-zero/reachable_fail/expected +++ /dev/null @@ -1,4 +0,0 @@ -coverage/reachable/div-zero/reachable_fail/test.rs, 5, PARTIAL -coverage/reachable/div-zero/reachable_fail/test.rs, 6, NONE -coverage/reachable/div-zero/reachable_fail/test.rs, 10, PARTIAL -coverage/reachable/div-zero/reachable_fail/test.rs, 11, NONE diff --git a/tests/coverage/reachable/div-zero/reachable_fail/test.rs b/tests/coverage/reachable/div-zero/reachable_fail/test.rs deleted file mode 100644 index 5f69005ee712..000000000000 --- a/tests/coverage/reachable/div-zero/reachable_fail/test.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -fn div(x: u16, y: u16) -> u16 { - x / y // PARTIAL: `y = 0` causes failure, but `x / y` is `COVERED` -} - -#[kani::proof] -fn main() { - div(678, 0); -} diff --git a/tests/coverage/reachable/div-zero/reachable_pass/expected b/tests/coverage/reachable/div-zero/reachable_pass/expected deleted file mode 100644 index c7bfb5961c0b..000000000000 --- a/tests/coverage/reachable/div-zero/reachable_pass/expected +++ /dev/null @@ -1,4 +0,0 @@ -coverage/reachable/div-zero/reachable_pass/test.rs, 5, PARTIAL -coverage/reachable/div-zero/reachable_pass/test.rs, 6, FULL -coverage/reachable/div-zero/reachable_pass/test.rs, 10, FULL -coverage/reachable/div-zero/reachable_pass/test.rs, 11, FULL diff --git a/tests/coverage/reachable/overflow/reachable_fail/expected b/tests/coverage/reachable/overflow/reachable_fail/expected deleted file mode 100644 index d45edcc37a63..000000000000 --- a/tests/coverage/reachable/overflow/reachable_fail/expected +++ /dev/null @@ -1,5 +0,0 @@ -coverage/reachable/overflow/reachable_fail/test.rs, 8, PARTIAL -coverage/reachable/overflow/reachable_fail/test.rs, 9, FULL -coverage/reachable/overflow/reachable_fail/test.rs, 13, FULL -coverage/reachable/overflow/reachable_fail/test.rs, 14, PARTIAL -coverage/reachable/overflow/reachable_fail/test.rs, 15, NONE diff --git a/tests/coverage/reachable/overflow/reachable_pass/expected b/tests/coverage/reachable/overflow/reachable_pass/expected deleted file mode 100644 index 5becf2cd23e7..000000000000 --- a/tests/coverage/reachable/overflow/reachable_pass/expected +++ /dev/null @@ -1,7 +0,0 @@ -coverage/reachable/overflow/reachable_pass/test.rs, 8, FULL -coverage/reachable/overflow/reachable_pass/test.rs, 9, FULL -coverage/reachable/overflow/reachable_pass/test.rs, 13, FULL -coverage/reachable/overflow/reachable_pass/test.rs, 14, FULL -coverage/reachable/overflow/reachable_pass/test.rs, 15, FULL -coverage/reachable/overflow/reachable_pass/test.rs, 16, FULL -coverage/reachable/overflow/reachable_pass/test.rs, 17, FULL diff --git a/tests/coverage/reachable/rem-zero/reachable_fail/expected b/tests/coverage/reachable/rem-zero/reachable_fail/expected deleted file mode 100644 index 7852461e4f57..000000000000 --- a/tests/coverage/reachable/rem-zero/reachable_fail/expected +++ /dev/null @@ -1,4 +0,0 @@ -coverage/reachable/rem-zero/reachable_fail/test.rs, 5, PARTIAL -coverage/reachable/rem-zero/reachable_fail/test.rs, 6, NONE -coverage/reachable/rem-zero/reachable_fail/test.rs, 10, PARTIAL -coverage/reachable/rem-zero/reachable_fail/test.rs, 11, NONE diff --git a/tests/coverage/reachable/rem-zero/reachable_fail/test.rs b/tests/coverage/reachable/rem-zero/reachable_fail/test.rs deleted file mode 100644 index 400c7e02340b..000000000000 --- a/tests/coverage/reachable/rem-zero/reachable_fail/test.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -fn rem(x: u16, y: u16) -> u16 { - x % y // PARTIAL: `x % y` is covered but induces a division failure -} // NONE: Caused by division failure earlier - -#[kani::proof] -fn main() { - rem(678, 0); -} // NONE: Caused by division failure earlier diff --git a/tests/coverage/reachable/rem-zero/reachable_pass/expected b/tests/coverage/reachable/rem-zero/reachable_pass/expected deleted file mode 100644 index f3d5934d785d..000000000000 --- a/tests/coverage/reachable/rem-zero/reachable_pass/expected +++ /dev/null @@ -1,4 +0,0 @@ -coverage/reachable/rem-zero/reachable_pass/test.rs, 5, PARTIAL -coverage/reachable/rem-zero/reachable_pass/test.rs, 6, FULL -coverage/reachable/rem-zero/reachable_pass/test.rs, 10, FULL -coverage/reachable/rem-zero/reachable_pass/test.rs, 11, FULL diff --git a/tests/coverage/reachable/rem-zero/reachable_pass/test.rs b/tests/coverage/reachable/rem-zero/reachable_pass/test.rs deleted file mode 100644 index 41ed28f7b903..000000000000 --- a/tests/coverage/reachable/rem-zero/reachable_pass/test.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -fn rem(x: u16, y: u16) -> u16 { - if y != 0 { x % y } else { 0 } -} - -#[kani::proof] -fn main() { - rem(11, 3); -} diff --git a/tests/coverage/unreachable/abort/expected b/tests/coverage/unreachable/abort/expected deleted file mode 100644 index 473b0f5a8d4d..000000000000 --- a/tests/coverage/unreachable/abort/expected +++ /dev/null @@ -1,7 +0,0 @@ -coverage/unreachable/abort/main.rs, 10, PARTIAL -coverage/unreachable/abort/main.rs, 11, FULL -coverage/unreachable/abort/main.rs, 13, FULL -coverage/unreachable/abort/main.rs, 15, FULL -coverage/unreachable/abort/main.rs, 17, NONE -coverage/unreachable/abort/main.rs, 20, NONE -coverage/unreachable/abort/main.rs, 21, NONE diff --git a/tests/coverage/unreachable/assert/expected b/tests/coverage/unreachable/assert/expected deleted file mode 100644 index 9bc6d8faa4f9..000000000000 --- a/tests/coverage/unreachable/assert/expected +++ /dev/null @@ -1,7 +0,0 @@ -coverage/unreachable/assert/test.rs, 6, FULL -coverage/unreachable/assert/test.rs, 7, PARTIAL -coverage/unreachable/assert/test.rs, 9, PARTIAL -coverage/unreachable/assert/test.rs, 10, NONE -coverage/unreachable/assert/test.rs, 12, NONE -coverage/unreachable/assert/test.rs, 16, FULL -coverage/unreachable/assert/test.rs, 18, FULL diff --git a/tests/coverage/unreachable/assert_eq/expected b/tests/coverage/unreachable/assert_eq/expected deleted file mode 100644 index 9b13c3c96ded..000000000000 --- a/tests/coverage/unreachable/assert_eq/expected +++ /dev/null @@ -1,5 +0,0 @@ -coverage/unreachable/assert_eq/test.rs, 6, FULL -coverage/unreachable/assert_eq/test.rs, 7, FULL -coverage/unreachable/assert_eq/test.rs, 8, PARTIAL -coverage/unreachable/assert_eq/test.rs, 9, NONE -coverage/unreachable/assert_eq/test.rs, 11, FULL diff --git a/tests/coverage/unreachable/assert_ne/expected b/tests/coverage/unreachable/assert_ne/expected deleted file mode 100644 index f027f432e280..000000000000 --- a/tests/coverage/unreachable/assert_ne/expected +++ /dev/null @@ -1,6 +0,0 @@ -coverage/unreachable/assert_ne/test.rs, 6, FULL -coverage/unreachable/assert_ne/test.rs, 7, FULL -coverage/unreachable/assert_ne/test.rs, 8, FULL -coverage/unreachable/assert_ne/test.rs, 10, PARTIAL -coverage/unreachable/assert_ne/test.rs, 11, NONE -coverage/unreachable/assert_ne/test.rs, 14, FULL diff --git a/tests/coverage/unreachable/assume_assert/expected b/tests/coverage/unreachable/assume_assert/expected deleted file mode 100644 index 8c1ae8a247d2..000000000000 --- a/tests/coverage/unreachable/assume_assert/expected +++ /dev/null @@ -1,4 +0,0 @@ -coverage/unreachable/assume_assert/main.rs, 5, FULL -coverage/unreachable/assume_assert/main.rs, 6, FULL -coverage/unreachable/assume_assert/main.rs, 7, NONE -coverage/unreachable/assume_assert/main.rs, 8, NONE diff --git a/tests/coverage/unreachable/assume_assert/main.rs b/tests/coverage/unreachable/assume_assert/main.rs deleted file mode 100644 index c4d5d65c6640..000000000000 --- a/tests/coverage/unreachable/assume_assert/main.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -#[kani::proof] -fn check_assume_assert() { - let a: u8 = kani::any(); - kani::assume(false); - assert!(a < 5); -} diff --git a/tests/coverage/unreachable/bounds/expected b/tests/coverage/unreachable/bounds/expected deleted file mode 100644 index 610372000a01..000000000000 --- a/tests/coverage/unreachable/bounds/expected +++ /dev/null @@ -1,4 +0,0 @@ -coverage/unreachable/bounds/test.rs, 5, PARTIAL -coverage/unreachable/bounds/test.rs, 6, FULL -coverage/unreachable/bounds/test.rs, 11, FULL -coverage/unreachable/bounds/test.rs, 12, FULL diff --git a/tests/coverage/unreachable/bounds/test.rs b/tests/coverage/unreachable/bounds/test.rs deleted file mode 100644 index c37c9d0dcad6..000000000000 --- a/tests/coverage/unreachable/bounds/test.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -fn get(s: &[i16], index: usize) -> i16 { - if index < s.len() { s[index] } else { -1 } -} - -#[kani::proof] -fn main() { - //get(&[7, -83, 19], 2); - get(&[5, 206, -46, 321, 8], 8); -} diff --git a/tests/coverage/unreachable/break/expected b/tests/coverage/unreachable/break/expected deleted file mode 100644 index dcb013c50c3d..000000000000 --- a/tests/coverage/unreachable/break/expected +++ /dev/null @@ -1,9 +0,0 @@ -coverage/unreachable/break/main.rs, 5, PARTIAL -coverage/unreachable/break/main.rs, 6, FULL -coverage/unreachable/break/main.rs, 7, FULL -coverage/unreachable/break/main.rs, 11, NONE -coverage/unreachable/break/main.rs, 12, PARTIAL -coverage/unreachable/break/main.rs, 16, FULL -coverage/unreachable/break/main.rs, 17, FULL -coverage/unreachable/break/main.rs, 18, FULL -coverage/unreachable/break/main.rs, 19, FULL diff --git a/tests/coverage/unreachable/check_id/expected b/tests/coverage/unreachable/check_id/expected deleted file mode 100644 index a2d296f0f9a3..000000000000 --- a/tests/coverage/unreachable/check_id/expected +++ /dev/null @@ -1,16 +0,0 @@ -coverage/unreachable/check_id/main.rs, 5, FULL -coverage/unreachable/check_id/main.rs, 6, PARTIAL -coverage/unreachable/check_id/main.rs, 8, NONE -coverage/unreachable/check_id/main.rs, 10, FULL -coverage/unreachable/check_id/main.rs, 14, FULL -coverage/unreachable/check_id/main.rs, 15, FULL -coverage/unreachable/check_id/main.rs, 16, FULL -coverage/unreachable/check_id/main.rs, 17, FULL -coverage/unreachable/check_id/main.rs, 18, FULL -coverage/unreachable/check_id/main.rs, 19, FULL -coverage/unreachable/check_id/main.rs, 20, FULL -coverage/unreachable/check_id/main.rs, 21, FULL -coverage/unreachable/check_id/main.rs, 22, FULL -coverage/unreachable/check_id/main.rs, 23, FULL -coverage/unreachable/check_id/main.rs, 24, PARTIAL -coverage/unreachable/check_id/main.rs, 25, NONE diff --git a/tests/coverage/unreachable/check_id/main.rs b/tests/coverage/unreachable/check_id/main.rs deleted file mode 100644 index 8273a9bcc679..000000000000 --- a/tests/coverage/unreachable/check_id/main.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -fn foo(x: i32) { - assert!(1 + 1 == 2); - if x < 9 { - // unreachable - assert!(2 + 2 == 4); - } -} - -#[kani::proof] -fn main() { - assert!(1 + 1 == 2); - let x = if kani::any() { 33 } else { 57 }; - foo(x); - assert!(1 + 1 == 2); - assert!(1 + 1 == 2); - assert!(1 + 1 == 2); - assert!(1 + 1 == 2); - assert!(1 + 1 == 2); - assert!(1 + 1 == 2); - assert!(1 + 1 == 2); - assert!(3 + 3 == 5); -} diff --git a/tests/coverage/unreachable/compare/expected b/tests/coverage/unreachable/compare/expected deleted file mode 100644 index 6187e232cbe7..000000000000 --- a/tests/coverage/unreachable/compare/expected +++ /dev/null @@ -1,7 +0,0 @@ -coverage/unreachable/compare/main.rs, 6, PARTIAL -coverage/unreachable/compare/main.rs, 7, FULL -coverage/unreachable/compare/main.rs, 11, FULL -coverage/unreachable/compare/main.rs, 12, FULL -coverage/unreachable/compare/main.rs, 13, FULL -coverage/unreachable/compare/main.rs, 14, FULL -coverage/unreachable/compare/main.rs, 16, FULL diff --git a/tests/coverage/unreachable/contradiction/expected b/tests/coverage/unreachable/contradiction/expected deleted file mode 100644 index 4234fc328e1e..000000000000 --- a/tests/coverage/unreachable/contradiction/expected +++ /dev/null @@ -1,7 +0,0 @@ -coverage/unreachable/contradiction/main.rs, 5, FULL -coverage/unreachable/contradiction/main.rs, 6, FULL -coverage/unreachable/contradiction/main.rs, 7, FULL -coverage/unreachable/contradiction/main.rs, 8, PARTIAL -coverage/unreachable/contradiction/main.rs, 9, NONE -coverage/unreachable/contradiction/main.rs, 12, FULL -coverage/unreachable/contradiction/main.rs, 14, FULL diff --git a/tests/coverage/unreachable/debug-assert/expected b/tests/coverage/unreachable/debug-assert/expected deleted file mode 100644 index 25fdfed4c863..000000000000 --- a/tests/coverage/unreachable/debug-assert/expected +++ /dev/null @@ -1,4 +0,0 @@ -coverage/unreachable/debug-assert/main.rs, 6, PARTIAL -coverage/unreachable/debug-assert/main.rs, 7, PARTIAL -coverage/unreachable/debug-assert/main.rs, 8, NONE -coverage/unreachable/debug-assert/main.rs, 10, NONE diff --git a/tests/coverage/unreachable/debug-assert/main.rs b/tests/coverage/unreachable/debug-assert/main.rs deleted file mode 100644 index ab3ab41e47d0..000000000000 --- a/tests/coverage/unreachable/debug-assert/main.rs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -#[kani::proof] -fn main() { - for i in 0..4 { - debug_assert!(i > 0, "This should fail and stop the execution"); - assert!(i == 0, "This should be unreachable"); - } -} diff --git a/tests/coverage/unreachable/divide/expected b/tests/coverage/unreachable/divide/expected deleted file mode 100644 index 63081358941a..000000000000 --- a/tests/coverage/unreachable/divide/expected +++ /dev/null @@ -1,7 +0,0 @@ -coverage/unreachable/divide/main.rs, 6, FULL -coverage/unreachable/divide/main.rs, 7, FULL -coverage/unreachable/divide/main.rs, 9, NONE -coverage/unreachable/divide/main.rs, 11, FULL -coverage/unreachable/divide/main.rs, 15, FULL -coverage/unreachable/divide/main.rs, 16, FULL -coverage/unreachable/divide/main.rs, 17, FULL diff --git a/tests/coverage/unreachable/divide/main.rs b/tests/coverage/unreachable/divide/main.rs deleted file mode 100644 index ba6afab83135..000000000000 --- a/tests/coverage/unreachable/divide/main.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -// Test that checks for UNREACHABLE panics. The panic is reported as NONE for the assumption that the divisor is not zero. -fn divide(a: i32, b: i32) -> i32 { - if b != 0 { - return a / b; - } else { - panic!("Division by zero"); - } -} - -#[kani::proof] -fn main() { - let y: i32 = kani::any(); - kani::assume(y != 0); - let result = divide(10, y); - assert_eq!(result, 5); -} diff --git a/tests/coverage/unreachable/early-return/expected b/tests/coverage/unreachable/early-return/expected deleted file mode 100644 index 466c1775408b..000000000000 --- a/tests/coverage/unreachable/early-return/expected +++ /dev/null @@ -1,10 +0,0 @@ -coverage/unreachable/early-return/main.rs, 5, PARTIAL -coverage/unreachable/early-return/main.rs, 6, FULL -coverage/unreachable/early-return/main.rs, 7, FULL -coverage/unreachable/early-return/main.rs, 10, NONE -coverage/unreachable/early-return/main.rs, 11, PARTIAL -coverage/unreachable/early-return/main.rs, 15, FULL -coverage/unreachable/early-return/main.rs, 16, FULL -coverage/unreachable/early-return/main.rs, 17, FULL -coverage/unreachable/early-return/main.rs, 18, FULL -coverage/unreachable/early-return/main.rs, 19, FULL diff --git a/tests/coverage/unreachable/if-statement/expected b/tests/coverage/unreachable/if-statement/expected deleted file mode 100644 index 8b481863a163..000000000000 --- a/tests/coverage/unreachable/if-statement/expected +++ /dev/null @@ -1,10 +0,0 @@ -coverage/unreachable/if-statement/main.rs, 5, PARTIAL -coverage/unreachable/if-statement/main.rs, 7, PARTIAL -coverage/unreachable/if-statement/main.rs, 8, NONE -coverage/unreachable/if-statement/main.rs, 9, NONE -coverage/unreachable/if-statement/main.rs, 11, NONE -coverage/unreachable/if-statement/main.rs, 13, FULL -coverage/unreachable/if-statement/main.rs, 17, FULL -coverage/unreachable/if-statement/main.rs, 18, FULL -coverage/unreachable/if-statement/main.rs, 19, FULL -coverage/unreachable/if-statement/main.rs, 20, FULL diff --git a/tests/coverage/unreachable/multiple-harnesses/expected b/tests/coverage/unreachable/multiple-harnesses/expected deleted file mode 100644 index 17a52666c08d..000000000000 --- a/tests/coverage/unreachable/multiple-harnesses/expected +++ /dev/null @@ -1,39 +0,0 @@ -Checking harness fully_covered... -coverage/unreachable/multiple-harnesses/main.rs, 5, FULL -coverage/unreachable/multiple-harnesses/main.rs, 7, FULL -coverage/unreachable/multiple-harnesses/main.rs, 8, FULL -coverage/unreachable/multiple-harnesses/main.rs, 9, FULL -coverage/unreachable/multiple-harnesses/main.rs, 11, FULL -coverage/unreachable/multiple-harnesses/main.rs, 13, FULL -coverage/unreachable/multiple-harnesses/main.rs, 14, FULL -coverage/unreachable/multiple-harnesses/main.rs, 15, FULL -coverage/unreachable/multiple-harnesses/main.rs, 17, FULL -coverage/unreachable/multiple-harnesses/main.rs, 20, FULL -coverage/unreachable/multiple-harnesses/main.rs, 21, FULL -coverage/unreachable/multiple-harnesses/main.rs, 23, FULL -coverage/unreachable/multiple-harnesses/main.rs, 26, FULL -coverage/unreachable/multiple-harnesses/main.rs, 40, FULL -coverage/unreachable/multiple-harnesses/main.rs, 41, FULL -coverage/unreachable/multiple-harnesses/main.rs, 42, FULL -coverage/unreachable/multiple-harnesses/main.rs, 43, FULL -coverage/unreachable/multiple-harnesses/main.rs, 44, FULL - -Checking harness mostly_covered... -coverage/unreachable/multiple-harnesses/main.rs, 5, FULL -coverage/unreachable/multiple-harnesses/main.rs, 7, FULL -coverage/unreachable/multiple-harnesses/main.rs, 8, FULL -coverage/unreachable/multiple-harnesses/main.rs, 9, FULL -coverage/unreachable/multiple-harnesses/main.rs, 11, FULL -coverage/unreachable/multiple-harnesses/main.rs, 13, FULL -coverage/unreachable/multiple-harnesses/main.rs, 14, FULL -coverage/unreachable/multiple-harnesses/main.rs, 15, FULL -coverage/unreachable/multiple-harnesses/main.rs, 17, FULL -coverage/unreachable/multiple-harnesses/main.rs, 20, FULL -coverage/unreachable/multiple-harnesses/main.rs, 21, FULL -coverage/unreachable/multiple-harnesses/main.rs, 23, NONE -coverage/unreachable/multiple-harnesses/main.rs, 26, FULL -coverage/unreachable/multiple-harnesses/main.rs, 31, FULL -coverage/unreachable/multiple-harnesses/main.rs, 32, FULL -coverage/unreachable/multiple-harnesses/main.rs, 33, FULL -coverage/unreachable/multiple-harnesses/main.rs, 34, FULL -coverage/unreachable/multiple-harnesses/main.rs, 35, FULL diff --git a/tests/coverage/unreachable/return/expected b/tests/coverage/unreachable/return/expected deleted file mode 100644 index 139f81840aab..000000000000 --- a/tests/coverage/unreachable/return/expected +++ /dev/null @@ -1,8 +0,0 @@ -coverage/unreachable/return/main.rs, 5, FULL -coverage/unreachable/return/main.rs, 6, FULL -coverage/unreachable/return/main.rs, 9, NONE -coverage/unreachable/return/main.rs, 10, PARTIAL -coverage/unreachable/return/main.rs, 14, FULL -coverage/unreachable/return/main.rs, 15, FULL -coverage/unreachable/return/main.rs, 16, FULL -coverage/unreachable/return/main.rs, 17, FULL diff --git a/tests/coverage/unreachable/return/main.rs b/tests/coverage/unreachable/return/main.rs deleted file mode 100644 index ccd76a5b4f8e..000000000000 --- a/tests/coverage/unreachable/return/main.rs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -fn greet(is_guest: bool) -> &'static str { - if is_guest { - return "Welcome, Guest!"; - } - // This part is unreachable if is_guest is true. - "Hello, User!" -} - -#[kani::proof] -fn main() { - let is_guest = true; - let message = greet(is_guest); - assert_eq!(message, "Welcome, Guest!"); -} diff --git a/tests/coverage/unreachable/tutorial_unreachable/expected b/tests/coverage/unreachable/tutorial_unreachable/expected deleted file mode 100644 index 624aa520edc9..000000000000 --- a/tests/coverage/unreachable/tutorial_unreachable/expected +++ /dev/null @@ -1,5 +0,0 @@ -coverage/unreachable/tutorial_unreachable/main.rs, 6, FULL -coverage/unreachable/tutorial_unreachable/main.rs, 7, FULL -coverage/unreachable/tutorial_unreachable/main.rs, 8, PARTIAL -coverage/unreachable/tutorial_unreachable/main.rs, 9, NONE -coverage/unreachable/tutorial_unreachable/main.rs, 11, FULL diff --git a/tests/coverage/unreachable/tutorial_unreachable/main.rs b/tests/coverage/unreachable/tutorial_unreachable/main.rs deleted file mode 100644 index c56e591446cf..000000000000 --- a/tests/coverage/unreachable/tutorial_unreachable/main.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -#[kani::proof] -fn unreachable_example() { - let x = 5; - let y = x + 2; - if x > y { - assert!(x < 8); - } -} diff --git a/tests/coverage/unreachable/variant/expected b/tests/coverage/unreachable/variant/expected deleted file mode 100644 index 8fa3ec8b870f..000000000000 --- a/tests/coverage/unreachable/variant/expected +++ /dev/null @@ -1,10 +0,0 @@ -coverage/unreachable/variant/main.rs, 15, FULL -coverage/unreachable/variant/main.rs, 16, NONE -coverage/unreachable/variant/main.rs, 17, NONE -coverage/unreachable/variant/main.rs, 18, FULL -coverage/unreachable/variant/main.rs, 19, NONE -coverage/unreachable/variant/main.rs, 21, NONE -coverage/unreachable/variant/main.rs, 23, FULL -coverage/unreachable/variant/main.rs, 27, FULL -coverage/unreachable/variant/main.rs, 28, FULL -coverage/unreachable/variant/main.rs, 29, FULL diff --git a/tests/coverage/unreachable/vectors/expected b/tests/coverage/unreachable/vectors/expected deleted file mode 100644 index e47941f17db2..000000000000 --- a/tests/coverage/unreachable/vectors/expected +++ /dev/null @@ -1,6 +0,0 @@ -coverage/unreachable/vectors/main.rs, 8, FULL -coverage/unreachable/vectors/main.rs, 11, FULL -coverage/unreachable/vectors/main.rs, 13, PARTIAL -coverage/unreachable/vectors/main.rs, 15, NONE -coverage/unreachable/vectors/main.rs, 17, FULL -coverage/unreachable/vectors/main.rs, 19, FULL diff --git a/tests/coverage/unreachable/vectors/main.rs b/tests/coverage/unreachable/vectors/main.rs deleted file mode 100644 index a44c4bb47c3d..000000000000 --- a/tests/coverage/unreachable/vectors/main.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -//! Checks coverage results in an example with a guarded out-of-bounds access. - -#[kani::proof] -fn main() { - let numbers = vec![1, 2, 3, 4, 5]; - - // Attempt to access the 10th element of the vector, which is out of bounds. - let tenth_element = numbers.get(9); - - if let Some(value) = tenth_element { - // This part is unreachable since the vector has only 5 elements (indices 0 to 4). - println!("The 10th element is: {}", value); - } else { - println!("The 10th element is out of bounds!"); - } -} diff --git a/tests/coverage/unreachable/while-loop-break/expected b/tests/coverage/unreachable/while-loop-break/expected deleted file mode 100644 index dc66d3e823d3..000000000000 --- a/tests/coverage/unreachable/while-loop-break/expected +++ /dev/null @@ -1,11 +0,0 @@ -coverage/unreachable/while-loop-break/main.rs, 8, FULL -coverage/unreachable/while-loop-break/main.rs, 9, PARTIAL -coverage/unreachable/while-loop-break/main.rs, 10, FULL -coverage/unreachable/while-loop-break/main.rs, 11, FULL -coverage/unreachable/while-loop-break/main.rs, 13, FULL -coverage/unreachable/while-loop-break/main.rs, 15, NONE -coverage/unreachable/while-loop-break/main.rs, 16, PARTIAL -coverage/unreachable/while-loop-break/main.rs, 20, FULL -coverage/unreachable/while-loop-break/main.rs, 21, FULL -coverage/unreachable/while-loop-break/main.rs, 22, FULL -coverage/unreachable/while-loop-break/main.rs, 23, FULL diff --git a/tests/coverage/while-loop-break/expected b/tests/coverage/while-loop-break/expected new file mode 100644 index 000000000000..34afef9ee12c --- /dev/null +++ b/tests/coverage/while-loop-break/expected @@ -0,0 +1,13 @@ +Source-based code coverage results: + +main.rs (find_first_negative)\ + * 7:1 - 8:22 COVERED\ + * 9:11 - 9:29 COVERED\ + * 10:12 - 10:27 COVERED\ + * 11:20 - 11:37 COVERED\ + * 12:10 - 13:19 COVERED\ + * 15:5 - 15:9 UNCOVERED\ + * 16:1 - 16:2 COVERED + +main.rs (main)\ + * 19:1 - 23:2 COVERED diff --git a/tests/coverage/unreachable/while-loop-break/main.rs b/tests/coverage/while-loop-break/main.rs similarity index 100% rename from tests/coverage/unreachable/while-loop-break/main.rs rename to tests/coverage/while-loop-break/main.rs diff --git a/tests/ui/save-coverage-results/expected b/tests/ui/save-coverage-results/expected new file mode 100644 index 000000000000..77c9a0c33dcb --- /dev/null +++ b/tests/ui/save-coverage-results/expected @@ -0,0 +1,3 @@ +Source-based code coverage results: + +[info] Coverage results saved to diff --git a/tests/ui/save-coverage-results/test.rs b/tests/ui/save-coverage-results/test.rs new file mode 100644 index 000000000000..0a422280e51d --- /dev/null +++ b/tests/ui/save-coverage-results/test.rs @@ -0,0 +1,25 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: --coverage -Zsource-coverage + +//! Checks that we print a line which points the user to the path where coverage +//! results have been saved. The line should look like: +//! ``` +//! [info] Coverage results saved to /path/to/outdir/kanicov_YYYY-MM-DD_hh-mm +//! ``` + +fn _other_function() { + println!("Hello, world!"); +} + +fn test_cov(val: u32) -> bool { + if val < 3 || val == 42 { true } else { false } +} + +#[cfg_attr(kani, kani::proof)] +fn main() { + let test1 = test_cov(1); + let test2 = test_cov(2); + assert!(test1); + assert!(test2); +} diff --git a/tools/compiletest/src/runtest.rs b/tools/compiletest/src/runtest.rs index 50f1e3035ac8..c8387c691296 100644 --- a/tools/compiletest/src/runtest.rs +++ b/tools/compiletest/src/runtest.rs @@ -320,7 +320,7 @@ impl<'test> TestCx<'test> { kani.env("RUSTFLAGS", self.props.compile_flags.join(" ")); } kani.arg(&self.testpaths.file).args(&self.props.kani_flags); - kani.arg("--coverage").args(["-Z", "line-coverage"]); + kani.arg("--coverage").args(["-Z", "source-coverage"]); if !self.props.cbmc_flags.is_empty() { kani.arg("--cbmc-args").args(&self.props.cbmc_flags); From 5aad1a9ebc28332756eba58bef314c2b1499ea8f Mon Sep 17 00:00:00 2001 From: Jaisurya Nanduri <91620234+jaisnan@users.noreply.github.com> Date: Wed, 28 Aug 2024 14:47:31 -0400 Subject: [PATCH 23/40] Upgrade toolchain to 08/28 (#3454) Upgrades toolchain to 08/28 Culprit upstream changes: 1. https://github.com/rust-lang/rust/pull/128812 2. https://github.com/rust-lang/rust/pull/128703 3. https://github.com/rust-lang/rust/pull/127679 4. https://github.com/rust-lang/rust-clippy/pull/12993 5. https://github.com/rust-lang/cargo/pull/14370 6. https://github.com/rust-lang/rust/pull/128806 Resolves https://github.com/model-checking/kani/issues/3429 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- Cargo.toml | 3 +++ cprover_bindings/Cargo.toml | 3 +++ kani-compiler/Cargo.toml | 3 +++ kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs | 7 +++++-- kani-compiler/src/kani_middle/coercion.rs | 2 +- .../src/kani_middle/points_to/points_to_analysis.rs | 4 ++-- kani-compiler/src/kani_middle/transform/internal_mir.rs | 7 +++---- kani-compiler/src/session.rs | 3 ++- kani-driver/src/call_cargo.rs | 7 ++++++- kani_metadata/Cargo.toml | 3 +++ library/kani/Cargo.toml | 3 +++ library/kani_core/Cargo.toml | 3 +++ library/kani_macros/Cargo.toml | 3 +++ library/kani_macros/src/lib.rs | 2 +- rust-toolchain.toml | 2 +- tools/compiletest/Cargo.toml | 3 +++ tools/scanner/src/bin/scan.rs | 1 + 17 files changed, 46 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f2301983fcb4..149b8d2c93c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,3 +71,6 @@ exclude = [ "tests/script-based-pre/build-cache-dirty/target/new_dep", "tests/script-based-pre/verify_std_cmd/tmp_dir/target/kani_verify_std", ] + +[workspace.lints.clippy] +too_long_first_doc_paragraph = "allow" diff --git a/cprover_bindings/Cargo.toml b/cprover_bindings/Cargo.toml index c53ffb207fcf..b0e3c578bbcf 100644 --- a/cprover_bindings/Cargo.toml +++ b/cprover_bindings/Cargo.toml @@ -24,3 +24,6 @@ linear-map = {version = "1.2", features = ["serde_impl"]} [dev-dependencies] serde_test = "1" memuse = "0.2.1" + +[lints] +workspace = true diff --git a/kani-compiler/Cargo.toml b/kani-compiler/Cargo.toml index fcb33b8074e4..fc06c393f3eb 100644 --- a/kani-compiler/Cargo.toml +++ b/kani-compiler/Cargo.toml @@ -35,3 +35,6 @@ write_json_symtab = [] [package.metadata.rust-analyzer] # This package uses rustc crates. rustc_private=true + +[lints] +workspace = true diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs index 9baa3c59f4c2..016bf0d3a261 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs @@ -579,7 +579,10 @@ impl<'tcx> GotocCtx<'tcx> { .unwrap(); self.codegen_fndef_type(instance) } - ty::FnPtr(sig) => self.codegen_function_sig(*sig).to_pointer(), + ty::FnPtr(sig_tys, hdr) => { + let sig = sig_tys.with(*hdr); + self.codegen_function_sig(sig).to_pointer() + } ty::Closure(_, subst) => self.codegen_ty_closure(ty, subst), ty::Coroutine(..) => self.codegen_ty_coroutine(ty), ty::Never => self.ensure_struct(NEVER_TYPE_EMPTY_STRUCT_NAME, "!", |_, _| vec![]), @@ -1014,7 +1017,7 @@ impl<'tcx> GotocCtx<'tcx> { // These types were blocking stdlib. Doing the default thing to unblock. // https://github.com/model-checking/kani/issues/214 - ty::FnPtr(_) => self.codegen_ty(pointee_type).to_pointer(), + ty::FnPtr(_, _) => self.codegen_ty(pointee_type).to_pointer(), // These types have no regression tests for them. // For soundness, hold off on generating them till we have test-cases. diff --git a/kani-compiler/src/kani_middle/coercion.rs b/kani-compiler/src/kani_middle/coercion.rs index 822f32631e0c..38760fca300d 100644 --- a/kani-compiler/src/kani_middle/coercion.rs +++ b/kani-compiler/src/kani_middle/coercion.rs @@ -99,7 +99,7 @@ pub fn extract_unsize_casting<'tcx>( coerce_info.dst_ty )); // Find the tail of the coercion that determines the type of metadata to be stored. - let (src_base_ty, dst_base_ty) = tcx.struct_lockstep_tails_erasing_lifetimes( + let (src_base_ty, dst_base_ty) = tcx.struct_lockstep_tails_for_codegen( src_pointee_ty, dst_pointee_ty, ParamEnv::reveal_all(), diff --git a/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs b/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs index eff7dd1fc486..68343ccaad37 100644 --- a/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs +++ b/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs @@ -53,7 +53,7 @@ struct PointsToAnalysis<'a, 'tcx> { tcx: TyCtxt<'tcx>, /// This will be used in the future to resolve function pointer and vtable calls. Currently, we /// can resolve call graph edges just by looking at the terminators and erroring if we can't - /// resolve the callee. + /// resolve the callee. call_graph: &'a CallGraph, /// This graph should contain a subset of the points-to graph reachable from function arguments. /// For the entry function it will be empty (as it supposedly does not have any parameters). @@ -521,7 +521,7 @@ impl<'a, 'tcx> PointsToAnalysis<'a, 'tcx> { | Rvalue::ShallowInitBox(operand, _) | Rvalue::Cast(_, operand, _) | Rvalue::Repeat(operand, ..) => self.successors_for_operand(state, operand), - Rvalue::Ref(_, _, ref_place) | Rvalue::AddressOf(_, ref_place) => { + Rvalue::Ref(_, _, ref_place) | Rvalue::RawPtr(_, ref_place) => { // Here, a reference to a place is created, which leaves the place // unchanged. state.resolve_place(ref_place, self.instance) diff --git a/kani-compiler/src/kani_middle/transform/internal_mir.rs b/kani-compiler/src/kani_middle/transform/internal_mir.rs index 0dcf7d47c13a..ba23fbf2dddf 100644 --- a/kani-compiler/src/kani_middle/transform/internal_mir.rs +++ b/kani-compiler/src/kani_middle/transform/internal_mir.rs @@ -210,10 +210,9 @@ impl RustcInternalMir for Rvalue { fn internal_mir<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { match self { - Rvalue::AddressOf(mutability, place) => rustc_middle::mir::Rvalue::AddressOf( - internal(tcx, mutability), - internal(tcx, place), - ), + Rvalue::AddressOf(mutability, place) => { + rustc_middle::mir::Rvalue::RawPtr(internal(tcx, mutability), internal(tcx, place)) + } Rvalue::Aggregate(aggregate_kind, operands) => rustc_middle::mir::Rvalue::Aggregate( Box::new(aggregate_kind.internal_mir(tcx)), rustc_index::IndexVec::from_raw( diff --git a/kani-compiler/src/session.rs b/kani-compiler/src/session.rs index ecc084e0cc85..f5dd78ff86b0 100644 --- a/kani-compiler/src/session.rs +++ b/kani-compiler/src/session.rs @@ -61,7 +61,8 @@ static JSON_PANIC_HOOK: LazyLock) + Sync + Lrc::new(SourceMap::new(FilePathMapping::empty())), fallback_bundle, false, - HumanReadableErrorType::Default(ColorConfig::Never), + HumanReadableErrorType::Default, + ColorConfig::Never, ); let diagnostic = DiagInner::new(rustc_errors::Level::Bug, msg); emitter.emit_diagnostic(diagnostic); diff --git a/kani-driver/src/call_cargo.rs b/kani-driver/src/call_cargo.rs index e69062a0cd4f..ef289cead4c2 100644 --- a/kani-driver/src/call_cargo.rs +++ b/kani-driver/src/call_cargo.rs @@ -73,6 +73,11 @@ impl KaniSession { cargo_args.push("-v".into()); } + // We need this suffix push because of https://github.com/rust-lang/cargo/pull/14370 + // which removes the library suffix from the build-std command + let mut full_path = std_path.to_path_buf(); + full_path.push("library"); + // Since we are verifying the standard library, we set the reachability to all crates. let mut cmd = setup_cargo_command()?; cmd.args(&cargo_args) @@ -82,7 +87,7 @@ impl KaniSession { // https://doc.rust-lang.org/cargo/reference/environment-variables.html .env("CARGO_ENCODED_RUSTFLAGS", rustc_args.join(OsStr::new("\x1f"))) .env("CARGO_TERM_PROGRESS_WHEN", "never") - .env("__CARGO_TESTS_ONLY_SRC_ROOT", std_path.as_os_str()); + .env("__CARGO_TESTS_ONLY_SRC_ROOT", full_path.as_os_str()); Ok(self .run_build(cmd)? diff --git a/kani_metadata/Cargo.toml b/kani_metadata/Cargo.toml index de91900d6d9c..efa28288d148 100644 --- a/kani_metadata/Cargo.toml +++ b/kani_metadata/Cargo.toml @@ -16,3 +16,6 @@ cbmc = { path = "../cprover_bindings", package = "cprover_bindings" } strum = "0.26" strum_macros = "0.26" clap = { version = "4.4.11", features = ["derive"] } + +[lints] +workspace = true diff --git a/library/kani/Cargo.toml b/library/kani/Cargo.toml index 7d7ced8ee0b7..a3692f95e3f5 100644 --- a/library/kani/Cargo.toml +++ b/library/kani/Cargo.toml @@ -15,3 +15,6 @@ kani_core = { path = "../kani_core" } [features] concrete_playback = [] no_core=["kani_macros/no_core"] + +[lints] +workspace = true diff --git a/library/kani_core/Cargo.toml b/library/kani_core/Cargo.toml index 8928992c3f16..9df828e77c5a 100644 --- a/library/kani_core/Cargo.toml +++ b/library/kani_core/Cargo.toml @@ -14,3 +14,6 @@ kani_macros = { path = "../kani_macros"} [features] no_core=["kani_macros/no_core"] + +[lints] +workspace = true diff --git a/library/kani_macros/Cargo.toml b/library/kani_macros/Cargo.toml index 475e2978df91..574960e5fc0a 100644 --- a/library/kani_macros/Cargo.toml +++ b/library/kani_macros/Cargo.toml @@ -23,3 +23,6 @@ rustc_private = true [features] no_core = [] + +[lints] +workspace = true diff --git a/library/kani_macros/src/lib.rs b/library/kani_macros/src/lib.rs index 6fe0979f08bc..63ed990a4840 100644 --- a/library/kani_macros/src/lib.rs +++ b/library/kani_macros/src/lib.rs @@ -8,7 +8,6 @@ // So we have to enable this on the commandline (see kani-rustc) with: // RUSTFLAGS="-Zcrate-attr=feature(register_tool) -Zcrate-attr=register_tool(kanitool)" #![feature(proc_macro_diagnostic)] - mod derive; // proc_macro::quote is nightly-only, so we'll cobble things together instead @@ -65,6 +64,7 @@ pub fn recursion(attr: TokenStream, item: TokenStream) -> TokenStream { /// Set Loop unwind limit for proof harnesses /// The attribute `#[kani::unwind(arg)]` can only be called alongside `#[kani::proof]`. /// arg - Takes in a integer value (u32) that represents the unwind value for the harness. +#[allow(clippy::too_long_first_doc_paragraph)] #[proc_macro_attribute] pub fn unwind(attr: TokenStream, item: TokenStream) -> TokenStream { attr_impl::unwind(attr, item) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index e3b6229d73c6..3421b9b3adc9 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,5 +2,5 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT [toolchain] -channel = "nightly-2024-08-07" +channel = "nightly-2024-08-28" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] diff --git a/tools/compiletest/Cargo.toml b/tools/compiletest/Cargo.toml index 78a90d9aae38..967bc1715525 100644 --- a/tools/compiletest/Cargo.toml +++ b/tools/compiletest/Cargo.toml @@ -30,3 +30,6 @@ wait-timeout = "0.2.0" [target.'cfg(unix)'.dependencies] libc = "0.2" + +[lints] +workspace = true diff --git a/tools/scanner/src/bin/scan.rs b/tools/scanner/src/bin/scan.rs index 92b5319ec780..197b34d23422 100644 --- a/tools/scanner/src/bin/scan.rs +++ b/tools/scanner/src/bin/scan.rs @@ -14,6 +14,7 @@ //! together with the name of the analysis. //! //! Look at each analysis documentation to see which files an analysis produces. +#![feature(rustc_private)] use scanner::run_all; use std::process::ExitCode; From e28f3db81d9a5c8e5a2dcf9ed344dbeb11dbef06 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Wed, 28 Aug 2024 18:06:19 -0400 Subject: [PATCH 24/40] Extra tests and bug fixes to the delayed UB instrumentation (#3419) This PR is a follow-up to #3374. It introduces the following changes: - Instrument more writes to avoid the case when points-to analysis overapproximates too much. - Add extra tests featuring safe abstractions from standard library. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../kani_middle/points_to/points_to_graph.rs | 78 ++++++++++++------ .../delayed_ub/instrumentation_visitor.rs | 81 ++++++++++++++----- .../transform/check_uninit/delayed_ub/mod.rs | 4 +- .../uninit/delayed-ub-overapprox.expected | 1 + .../expected/uninit/delayed-ub-overapprox.rs | 32 ++++++++ tests/std-checks/std/Cargo.toml | 13 +++ tests/std-checks/std/atomic.expected | 1 + tests/std-checks/std/boxed.expected | 1 + tests/std-checks/std/src/boxed.rs | 40 +++++++++ tests/std-checks/std/src/lib.rs | 9 +++ tests/std-checks/std/src/sync/atomic.rs | 77 ++++++++++++++++++ tests/std-checks/std/src/sync/mod.rs | 4 + 12 files changed, 295 insertions(+), 46 deletions(-) create mode 100644 tests/expected/uninit/delayed-ub-overapprox.expected create mode 100644 tests/expected/uninit/delayed-ub-overapprox.rs create mode 100644 tests/std-checks/std/Cargo.toml create mode 100644 tests/std-checks/std/atomic.expected create mode 100644 tests/std-checks/std/boxed.expected create mode 100644 tests/std-checks/std/src/boxed.rs create mode 100644 tests/std-checks/std/src/lib.rs create mode 100644 tests/std-checks/std/src/sync/atomic.rs create mode 100644 tests/std-checks/std/src/sync/mod.rs diff --git a/kani-compiler/src/kani_middle/points_to/points_to_graph.rs b/kani-compiler/src/kani_middle/points_to/points_to_graph.rs index d2e80f24c737..61804d0ff71a 100644 --- a/kani-compiler/src/kani_middle/points_to/points_to_graph.rs +++ b/kani-compiler/src/kani_middle/points_to/points_to_graph.rs @@ -63,6 +63,27 @@ impl<'tcx> MemLoc<'tcx> { } } +/// Data structure to keep track of both successors and ancestors of the node. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +struct NodeData<'tcx> { + successors: HashSet>, + ancestors: HashSet>, +} + +impl<'tcx> NodeData<'tcx> { + /// Merge two instances of NodeData together, return true if the original one was updated and + /// false otherwise. + fn merge(&mut self, other: Self) -> bool { + let successors_before = self.successors.len(); + let ancestors_before = self.ancestors.len(); + self.successors.extend(other.successors); + self.ancestors.extend(other.ancestors); + let successors_after = self.successors.len(); + let ancestors_after = self.ancestors.len(); + successors_before != successors_after || ancestors_before != ancestors_after + } +} + /// Graph data structure that stores the current results of the point-to analysis. The graph is /// directed, so having an edge between two places means that one is pointing to the other. /// @@ -82,24 +103,39 @@ impl<'tcx> MemLoc<'tcx> { #[derive(Clone, Debug, PartialEq, Eq)] pub struct PointsToGraph<'tcx> { /// A hash map of node --> {nodes} edges. - edges: HashMap, HashSet>>, + nodes: HashMap, NodeData<'tcx>>, } impl<'tcx> PointsToGraph<'tcx> { pub fn empty() -> Self { - Self { edges: HashMap::new() } + Self { nodes: HashMap::new() } } /// Collect all nodes which have incoming edges from `nodes`. pub fn successors(&self, nodes: &HashSet>) -> HashSet> { - nodes.iter().flat_map(|node| self.edges.get(node).cloned().unwrap_or_default()).collect() + nodes + .iter() + .flat_map(|node| self.nodes.get(node).cloned().unwrap_or_default().successors) + .collect() } - /// For each node in `from`, add an edge to each node in `to`. + /// Collect all nodes which have outgoing edges to `nodes`. + pub fn ancestors(&self, nodes: &HashSet>) -> HashSet> { + nodes + .iter() + .flat_map(|node| self.nodes.get(node).cloned().unwrap_or_default().ancestors) + .collect() + } + + /// For each node in `from`, add an edge to each node in `to` (and the reverse for ancestors). pub fn extend(&mut self, from: &HashSet>, to: &HashSet>) { for node in from.iter() { - let node_pointees = self.edges.entry(*node).or_default(); - node_pointees.extend(to.iter()); + let node_pointees = self.nodes.entry(*node).or_default(); + node_pointees.successors.extend(to.iter()); + } + for node in to.iter() { + let node_pointees = self.nodes.entry(*node).or_default(); + node_pointees.ancestors.extend(from.iter()); } } @@ -150,16 +186,16 @@ impl<'tcx> PointsToGraph<'tcx> { /// Dump the graph into a file using the graphviz format for later visualization. pub fn dump(&self, file_path: &str) { let mut nodes: Vec = - self.edges.keys().map(|from| format!("\t\"{:?}\"", from)).collect(); + self.nodes.keys().map(|from| format!("\t\"{:?}\"", from)).collect(); nodes.sort(); let nodes_str = nodes.join("\n"); let mut edges: Vec = self - .edges + .nodes .iter() .flat_map(|(from, to)| { let from = format!("\"{:?}\"", from); - to.iter().map(move |to| { + to.successors.iter().map(move |to| { let to = format!("\"{:?}\"", to); format!("\t{} -> {}", from.clone(), to) }) @@ -178,24 +214,18 @@ impl<'tcx> PointsToGraph<'tcx> { // Working queue. let mut queue = VecDeque::from_iter(targets); // Add all statics, as they can be accessed at any point. - let statics = self.edges.keys().filter(|node| matches!(node, MemLoc::Static(_))); + let statics = self.nodes.keys().filter(|node| matches!(node, MemLoc::Static(_))); queue.extend(statics); // Add all entries. while let Some(next_target) = queue.pop_front() { - result.edges.entry(next_target).or_insert_with(|| { - let outgoing_edges = - self.edges.get(&next_target).cloned().unwrap_or(HashSet::new()); - queue.extend(outgoing_edges.iter()); + result.nodes.entry(next_target).or_insert_with(|| { + let outgoing_edges = self.nodes.get(&next_target).cloned().unwrap_or_default(); + queue.extend(outgoing_edges.successors.iter()); outgoing_edges.clone() }); } result } - - /// Retrieve all places to which a given place is pointing to. - pub fn pointees_of(&self, target: &MemLoc<'tcx>) -> HashSet> { - self.edges.get(&target).unwrap_or(&HashSet::new()).clone() - } } /// Since we are performing the analysis using a dataflow, we need to implement a proper monotonous @@ -206,12 +236,10 @@ impl<'tcx> JoinSemiLattice for PointsToGraph<'tcx> { fn join(&mut self, other: &Self) -> bool { let mut updated = false; // Check every node in the other graph. - for (from, to) in other.edges.iter() { - let existing_to = self.edges.entry(*from).or_default(); - let initial_size = existing_to.len(); - existing_to.extend(to); - let new_size = existing_to.len(); - updated |= initial_size != new_size; + for (node, data) in other.nodes.iter() { + let existing_node = self.nodes.entry(*node).or_default(); + let changed = existing_node.merge(data.clone()); + updated |= changed; } updated } diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/instrumentation_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/instrumentation_visitor.rs index 25059297d3d5..41a2b69d1fdf 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/instrumentation_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/instrumentation_visitor.rs @@ -118,25 +118,70 @@ impl<'a, 'tcx> MirVisitor for InstrumentationVisitor<'a, 'tcx> { } fn visit_place(&mut self, place: &Place, ptx: PlaceContext, location: Location) { - // Match the place by whatever it is pointing to and find an intersection with the targets. - if self - .points_to - .resolve_place_stable(place.clone(), self.current_instance, self.tcx) - .intersection(&self.analysis_targets) - .next() - .is_some() - { - // If we are mutating the place, initialize it. - if ptx.is_mutating() { - self.push_target(MemoryInitOp::SetRef { - operand: Operand::Copy(place.clone()), - value: true, - position: InsertPosition::After, - }); - } else { - // Otherwise, check its initialization. - self.push_target(MemoryInitOp::CheckRef { operand: Operand::Copy(place.clone()) }); + // In order to check whether we should get-instrument the place, see if it resolves to the + // analysis target. + let needs_get = { + self.points_to + .resolve_place_stable(place.clone(), self.current_instance, self.tcx) + .intersection(&self.analysis_targets) + .next() + .is_some() + }; + + // In order to check whether we should set-instrument the place, we need to figure out if + // the place has a common ancestor of the same level with the target. + // + // This is needed because instrumenting the place only if it resolves to the target could give + // false positives in presence of some aliasing relations. + // + // Here is a simple example: + // ``` + // fn foo(val_1: u32, val_2: u32, flag: bool) { + // let reference = if flag { + // &val_1 + // } else { + // &val_2 + // }; + // let _ = *reference; + // } + // ``` + // It yields the following aliasing graph: + // + // `val_1 <-- reference --> val_2` + // + // If `val_1` is a legitimate instrumentation target, we would get-instrument an instruction + // that reads from `*reference`, but that could mean that `val_2` is checked, too. Hence, + // if we don't set-instrument `val_2` we will get a false-positive. + // + // See `tests/expected/uninit/delayed-ub-overapprox.rs` for a more specific example. + let needs_set = { + let mut has_common_ancestor = false; + let mut self_ancestors = + self.points_to.resolve_place_stable(place.clone(), self.current_instance, self.tcx); + let mut target_ancestors = self.analysis_targets.clone(); + + while !self_ancestors.is_empty() || !target_ancestors.is_empty() { + if self_ancestors.intersection(&target_ancestors).next().is_some() { + has_common_ancestor = true; + break; + } + self_ancestors = self.points_to.ancestors(&self_ancestors); + target_ancestors = self.points_to.ancestors(&target_ancestors); } + + has_common_ancestor + }; + + // If we are mutating the place, initialize it. + if ptx.is_mutating() && needs_set { + self.push_target(MemoryInitOp::SetRef { + operand: Operand::Copy(place.clone()), + value: true, + position: InsertPosition::After, + }); + } else if !ptx.is_mutating() && needs_get { + // Otherwise, check its initialization. + self.push_target(MemoryInitOp::CheckRef { operand: Operand::Copy(place.clone()) }); } self.super_place(place, ptx, location) } diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/mod.rs index e179947d2777..c31f72c9b5c3 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/mod.rs @@ -100,9 +100,7 @@ impl GlobalPass for DelayedUbPass { } // Since analysis targets are *pointers*, need to get its successors for instrumentation. - for target in targets.iter() { - analysis_targets.extend(global_points_to_graph.pointees_of(target)); - } + analysis_targets.extend(global_points_to_graph.successors(&targets)); // If we are generating MIR, generate the points-to graph as well. if tcx.sess.opts.output_types.contains_key(&OutputType::Mir) { diff --git a/tests/expected/uninit/delayed-ub-overapprox.expected b/tests/expected/uninit/delayed-ub-overapprox.expected new file mode 100644 index 000000000000..01a90d50b557 --- /dev/null +++ b/tests/expected/uninit/delayed-ub-overapprox.expected @@ -0,0 +1 @@ +Complete - 1 successfully verified harnesses, 0 failures, 1 total. diff --git a/tests/expected/uninit/delayed-ub-overapprox.rs b/tests/expected/uninit/delayed-ub-overapprox.rs new file mode 100644 index 000000000000..68585443fd38 --- /dev/null +++ b/tests/expected/uninit/delayed-ub-overapprox.rs @@ -0,0 +1,32 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z uninit-checks + +//! Make sure that no false positives are generated when points-to analysis overapproximates +//! aliasing. + +#[kani::proof] +fn check_delayed_ub_overapprox() { + unsafe { + let mut value: u128 = 0; + let value_ref = &mut value; + // Perform a call to the helper before mutable pointer cast. This way, a check inserted into + // the helper will pass. + helper(value_ref); + // Cast between two pointers of different padding, which will mark `value` as a possible + // delayed UB analysis target. + let ptr = value_ref as *mut _ as *mut (u8, u32, u64); + *ptr = (4, 4, 4); // Note that since we never read from `value` after overwriting it, no delayed UB occurs. + // Create another `value` and call helper. Note that since helper could potentially + // dereference a delayed-UB pointer, an initialization check will be added to the helper. + // Hence, delayed UB analysis needs to mark the value as properly initialized in shadow + // memory to avoid the spurious failure. + let mut value2: u128 = 0; + helper(&value2); + } +} + +/// A helper that could trigger delayed UB. +fn helper(reference: &u128) -> bool { + *reference == 42 +} diff --git a/tests/std-checks/std/Cargo.toml b/tests/std-checks/std/Cargo.toml new file mode 100644 index 000000000000..6c2b4e0073cb --- /dev/null +++ b/tests/std-checks/std/Cargo.toml @@ -0,0 +1,13 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +[package] +name = "verify-std" +version = "0.1.0" +edition = "2021" +description = "This crate contains contracts and harnesses for std library" + +[package.metadata.kani] +unstable = { function-contracts = true, mem-predicates = true, uninit-checks = true } + +[package.metadata.kani.flags] +output-format = "terse" diff --git a/tests/std-checks/std/atomic.expected b/tests/std-checks/std/atomic.expected new file mode 100644 index 000000000000..158e99910c43 --- /dev/null +++ b/tests/std-checks/std/atomic.expected @@ -0,0 +1 @@ +Complete - 5 successfully verified harnesses, 0 failures, 5 total. diff --git a/tests/std-checks/std/boxed.expected b/tests/std-checks/std/boxed.expected new file mode 100644 index 000000000000..4426ff6c02cd --- /dev/null +++ b/tests/std-checks/std/boxed.expected @@ -0,0 +1 @@ +Complete - 2 successfully verified harnesses, 0 failures, 2 total. diff --git a/tests/std-checks/std/src/boxed.rs b/tests/std-checks/std/src/boxed.rs new file mode 100644 index 000000000000..112b076ae4a3 --- /dev/null +++ b/tests/std-checks/std/src/boxed.rs @@ -0,0 +1,40 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +extern crate kani; + +/// Create wrapper functions to standard library functions that contains their contract. +pub mod contracts { + use kani::{mem::*, requires}; + + /// The actual pre-condition is more complicated: + /// + /// "For non-zero-sized values, ... a value: *mut T that has been allocated with the Global + /// allocator with Layout::for_value(&*value) may be converted into a box using + /// Box::::from_raw(value)." + /// + /// "For zero-sized values, the Box pointer still has to be valid for reads and writes and + /// sufficiently aligned." + #[requires(can_dereference(raw))] + pub unsafe fn from_raw(raw: *mut T) -> Box { + std::boxed::Box::from_raw(raw) + } +} + +#[cfg(kani)] +mod verify { + use super::*; + + #[kani::proof_for_contract(contracts::from_raw)] + pub fn check_from_raw_u32() { + let ptr = unsafe { std::alloc::alloc(std::alloc::Layout::new::()) as *mut u32 }; + unsafe { ptr.write(kani::any()) }; + let _ = unsafe { contracts::from_raw(ptr) }; + } + + #[kani::proof_for_contract(contracts::from_raw)] + pub fn check_from_raw_unit() { + let ptr = kani::any::() as *mut (); + let _ = unsafe { contracts::from_raw(ptr) }; + } +} diff --git a/tests/std-checks/std/src/lib.rs b/tests/std-checks/std/src/lib.rs new file mode 100644 index 000000000000..d78cf75a14ed --- /dev/null +++ b/tests/std-checks/std/src/lib.rs @@ -0,0 +1,9 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Top file that includes all sub-modules mimicking std structure. + +extern crate kani; + +mod boxed; +mod sync; diff --git a/tests/std-checks/std/src/sync/atomic.rs b/tests/std-checks/std/src/sync/atomic.rs new file mode 100644 index 000000000000..85c9a3380775 --- /dev/null +++ b/tests/std-checks/std/src/sync/atomic.rs @@ -0,0 +1,77 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +extern crate kani; + +use std::sync::atomic::{AtomicU16, AtomicU32, AtomicU64, AtomicU8, AtomicUsize}; + +/// Create wrapper functions to standard library functions that contains their contract. +pub mod contracts { + use super::*; + use kani::{mem::*, requires}; + + #[requires(can_dereference(ptr))] + pub unsafe fn from_ptr_u8<'a>(ptr: *mut u8) -> &'a AtomicU8 { + AtomicU8::from_ptr(ptr) + } + + #[requires(can_dereference(ptr))] + pub unsafe fn from_ptr_u16<'a>(ptr: *mut u16) -> &'a AtomicU16 { + AtomicU16::from_ptr(ptr) + } + + #[requires(can_dereference(ptr))] + pub unsafe fn from_ptr_u32<'a>(ptr: *mut u32) -> &'a AtomicU32 { + AtomicU32::from_ptr(ptr) + } + + #[requires(can_dereference(ptr))] + pub unsafe fn from_ptr_u64<'a>(ptr: *mut u64) -> &'a AtomicU64 { + AtomicU64::from_ptr(ptr) + } + + #[requires(can_dereference(ptr))] + pub unsafe fn from_ptr_usize<'a>(ptr: *mut usize) -> &'a AtomicUsize { + AtomicUsize::from_ptr(ptr) + } +} + +#[cfg(kani)] +mod verify { + use super::*; + + #[kani::proof_for_contract(contracts::from_ptr_u8)] + pub fn check_from_ptr_u8() { + let ptr = unsafe { std::alloc::alloc(std::alloc::Layout::new::()) as *mut u8 }; + unsafe { ptr.write(kani::any()) }; + let _ = unsafe { contracts::from_ptr_u8(ptr) }; + } + + #[kani::proof_for_contract(contracts::from_ptr_u16)] + pub fn check_from_ptr_u16() { + let ptr = unsafe { std::alloc::alloc(std::alloc::Layout::new::()) as *mut u16 }; + unsafe { ptr.write(kani::any()) }; + let _ = unsafe { contracts::from_ptr_u16(ptr) }; + } + + #[kani::proof_for_contract(contracts::from_ptr_u32)] + pub fn check_from_ptr_u32() { + let ptr = unsafe { std::alloc::alloc(std::alloc::Layout::new::()) as *mut u32 }; + unsafe { ptr.write(kani::any()) }; + let _ = unsafe { contracts::from_ptr_u32(ptr) }; + } + + #[kani::proof_for_contract(contracts::from_ptr_u64)] + pub fn check_from_ptr_u64() { + let ptr = unsafe { std::alloc::alloc(std::alloc::Layout::new::()) as *mut u64 }; + unsafe { ptr.write(kani::any()) }; + let _ = unsafe { contracts::from_ptr_u64(ptr) }; + } + + #[kani::proof_for_contract(contracts::from_ptr_usize)] + pub fn check_from_ptr_usize() { + let ptr = unsafe { std::alloc::alloc(std::alloc::Layout::new::()) as *mut usize }; + unsafe { ptr.write(kani::any()) }; + let _ = unsafe { contracts::from_ptr_usize(ptr) }; + } +} diff --git a/tests/std-checks/std/src/sync/mod.rs b/tests/std-checks/std/src/sync/mod.rs new file mode 100644 index 000000000000..14b3b086e487 --- /dev/null +++ b/tests/std-checks/std/src/sync/mod.rs @@ -0,0 +1,4 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +mod atomic; From 691f81e7732152479b7065fddf26227a1ee675c1 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Thu, 29 Aug 2024 15:56:26 -0400 Subject: [PATCH 25/40] Upgrade Toolchain to 8/29 (#3468) Upgrade toolchain to 8/29. Culprit upstream changes: https://github.com/rust-lang/rust/pull/129686 Resolves #3466 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../src/codegen_cprover_gotoc/codegen/assert.rs | 6 +++--- .../src/codegen_cprover_gotoc/codegen/function.rs | 12 ++++++------ .../src/codegen_cprover_gotoc/codegen/statement.rs | 6 +++--- rust-toolchain.toml | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs index 4ee81d0c7d3e..72ccb95cf97b 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs @@ -21,7 +21,7 @@ use crate::codegen_cprover_gotoc::GotocCtx; use cbmc::goto_program::{Expr, Location, Stmt, Type}; use cbmc::InternedString; -use rustc_middle::mir::coverage::CodeRegion; +use rustc_middle::mir::coverage::SourceRegion; use stable_mir::mir::{Place, ProjectionElem}; use stable_mir::ty::{Span as SpanStable, TypeAndMut}; use strum_macros::{AsRefStr, EnumString}; @@ -153,14 +153,14 @@ impl<'tcx> GotocCtx<'tcx> { &self, counter_data: &str, span: SpanStable, - code_region: CodeRegion, + source_region: SourceRegion, ) -> Stmt { let loc = self.codegen_caller_span_stable(span); // Should use Stmt::cover, but currently this doesn't work with CBMC // unless it is run with '--cover cover' (see // https://github.com/diffblue/cbmc/issues/6613). So for now use // `assert(false)`. - let msg = format!("{counter_data} - {code_region:?}"); + let msg = format!("{counter_data} - {source_region:?}"); self.codegen_assert(Expr::bool_false(), PropertyClass::CodeCoverage, &msg, loc) } diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs index 0793e0c4688f..34f8363f4948 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs @@ -219,34 +219,34 @@ impl<'tcx> GotocCtx<'tcx> { pub mod rustc_smir { use crate::stable_mir::CrateDef; - use rustc_middle::mir::coverage::CodeRegion; use rustc_middle::mir::coverage::CovTerm; use rustc_middle::mir::coverage::MappingKind::Code; + use rustc_middle::mir::coverage::SourceRegion; use rustc_middle::ty::TyCtxt; use stable_mir::mir::mono::Instance; use stable_mir::Opaque; type CoverageOpaque = stable_mir::Opaque; - /// Retrieves the `CodeRegion` associated with the data in a + /// Retrieves the `SourceRegion` associated with the data in a /// `CoverageOpaque` object. pub fn region_from_coverage_opaque( tcx: TyCtxt, coverage_opaque: &CoverageOpaque, instance: Instance, - ) -> Option { + ) -> Option { let cov_term = parse_coverage_opaque(coverage_opaque); region_from_coverage(tcx, cov_term, instance) } - /// Retrieves the `CodeRegion` associated with a `CovTerm` object. + /// Retrieves the `SourceRegion` associated with a `CovTerm` object. /// /// Note: This function could be in the internal `rustc` impl for `Coverage`. pub fn region_from_coverage( tcx: TyCtxt<'_>, coverage: CovTerm, instance: Instance, - ) -> Option { + ) -> Option { // We need to pull the coverage info from the internal MIR instance. let instance_def = rustc_smir::rustc_internal::internal(tcx, instance.def.def_id()); let body = tcx.instance_mir(rustc_middle::ty::InstanceKind::Item(instance_def)); @@ -258,7 +258,7 @@ pub mod rustc_smir { for mapping in &cov_info.mappings { let Code(term) = mapping.kind else { unreachable!() }; if term == coverage { - return Some(mapping.code_region.clone()); + return Some(mapping.source_region.clone()); } } } diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs index 81407c4dc704..ed0178511126 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs @@ -163,11 +163,11 @@ impl<'tcx> GotocCtx<'tcx> { let function_name = self.current_fn().readable_name(); let instance = self.current_fn().instance_stable(); let counter_data = format!("{coverage_opaque:?} ${function_name}$"); - let maybe_code_region = + let maybe_source_region = region_from_coverage_opaque(self.tcx, &coverage_opaque, instance); - if let Some(code_region) = maybe_code_region { + if let Some(source_region) = maybe_source_region { let coverage_stmt = - self.codegen_coverage(&counter_data, stmt.span, code_region); + self.codegen_coverage(&counter_data, stmt.span, source_region); // TODO: Avoid single-statement blocks when conversion of // standalone statements to the irep format is fixed. // More details in diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 3421b9b3adc9..1494baafeb51 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,5 +2,5 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT [toolchain] -channel = "nightly-2024-08-28" +channel = "nightly-2024-08-29" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 6ac5183eabd704c04b88f80552bedf24f7c8e4e9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 09:43:33 -0400 Subject: [PATCH 26/40] Automatic toolchain upgrade to nightly-2024-08-30 (#3469) Update Rust toolchain from nightly-2024-08-29 to nightly-2024-08-30 without any other source changes. Co-authored-by: celinval <35149715+celinval@users.noreply.github.com> --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 1494baafeb51..503c4611e3a0 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,5 +2,5 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT [toolchain] -channel = "nightly-2024-08-29" +channel = "nightly-2024-08-30" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 01a00b069d3fb81cf3a94ac0fc8e2a18507e8746 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Fri, 30 Aug 2024 15:07:39 -0700 Subject: [PATCH 27/40] Extend name resolution to support qualified paths (Partial Fix) (#3457) This is the first part needed for us to add support to stubbing trait implementations (https://github.com/model-checking/kani/issues/1997) and primitive types (https://github.com/model-checking/kani/issues/2646). After this change, we need to change stubs to accept a `FnResolution` instead of `FnDef`. We also need to find out how to retrieve methods of primitive types. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --------- Co-authored-by: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> Co-authored-by: Felipe R. Monteiro --- Cargo.lock | 2 + kani-compiler/Cargo.toml | 6 +- kani-compiler/src/kani_middle/attributes.rs | 163 ++++++---- kani-compiler/src/kani_middle/mod.rs | 20 +- kani-compiler/src/kani_middle/resolve.rs | 300 ++++++++++++++---- kani-compiler/src/main.rs | 1 + .../{expected => invalid_stub_args.expected} | 0 .../{main.rs => invalid_stub_args.rs} | 2 +- .../unsupported_resolutions.expected | 8 + .../unsupported_resolutions.rs | 42 +++ tests/ui/invalid-attribute/expected | 2 +- 11 files changed, 410 insertions(+), 136 deletions(-) rename tests/ui/function-stubbing-error/{expected => invalid_stub_args.expected} (100%) rename tests/ui/function-stubbing-error/{main.rs => invalid_stub_args.rs} (87%) create mode 100644 tests/ui/function-stubbing-error/unsupported_resolutions.expected create mode 100644 tests/ui/function-stubbing-error/unsupported_resolutions.rs diff --git a/Cargo.lock b/Cargo.lock index 0b8f681ae6c1..0da9ff2e2159 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -476,12 +476,14 @@ dependencies = [ "kani_metadata", "lazy_static", "num", + "quote", "regex", "serde", "serde_json", "shell-words", "strum", "strum_macros", + "syn 2.0.76", "tracing", "tracing-subscriber", "tracing-tree", diff --git a/kani-compiler/Cargo.toml b/kani-compiler/Cargo.toml index fc06c393f3eb..e8e564f9616a 100644 --- a/kani-compiler/Cargo.toml +++ b/kani-compiler/Cargo.toml @@ -13,14 +13,16 @@ cbmc = { path = "../cprover_bindings", package = "cprover_bindings", optional = clap = { version = "4.4.11", features = ["derive", "cargo"] } home = "0.5" itertools = "0.13" -kani_metadata = {path = "../kani_metadata"} +kani_metadata = { path = "../kani_metadata" } lazy_static = "1.4.0" num = { version = "0.4.0", optional = true } +quote = "1.0.36" regex = "1.7.0" serde = { version = "1", optional = true } serde_json = "1" strum = "0.26" strum_macros = "0.26" +syn = { version = "2.0.72", features = ["parsing", "extra-traits"] } shell-words = "1.0.0" tracing = {version = "0.1", features = ["max_level_trace", "release_max_level_debug"]} tracing-subscriber = {version = "0.3.8", features = ["env-filter", "json", "fmt"]} @@ -34,7 +36,7 @@ write_json_symtab = [] [package.metadata.rust-analyzer] # This package uses rustc crates. -rustc_private=true +rustc_private = true [lints] workspace = true diff --git a/kani-compiler/src/kani_middle/attributes.rs b/kani-compiler/src/kani_middle/attributes.rs index f75c0607e1bc..9a3ff7c1d6a6 100644 --- a/kani-compiler/src/kani_middle/attributes.rs +++ b/kani-compiler/src/kani_middle/attributes.rs @@ -5,9 +5,9 @@ use std::collections::BTreeMap; use kani_metadata::{CbmcSolver, HarnessAttributes, HarnessKind, Stub}; +use quote::ToTokens; use rustc_ast::{ attr, AttrArgs, AttrArgsEq, AttrKind, Attribute, ExprKind, LitKind, MetaItem, MetaItemKind, - NestedMetaItem, }; use rustc_errors::ErrorGuaranteed; use rustc_hir::{def::DefKind, def_id::DefId}; @@ -19,10 +19,13 @@ use stable_mir::mir::mono::Instance as InstanceStable; use stable_mir::{CrateDef, DefId as StableDefId}; use std::str::FromStr; use strum_macros::{AsRefStr, EnumString}; +use syn::parse::Parser; +use syn::punctuated::Punctuated; +use syn::{PathSegment, TypePath}; use tracing::{debug, trace}; -use super::resolve::{self, resolve_fn, ResolveError}; +use super::resolve::{resolve_fn, resolve_fn_path, FnResolution, ResolveError}; #[derive(Debug, Clone, Copy, AsRefStr, EnumString, PartialEq, Eq, PartialOrd, Ord)] #[strum(serialize_all = "snake_case")] @@ -555,19 +558,19 @@ impl<'tcx> KaniAttributes<'tcx> { for (name, def_id, span) in self.interpret_stub_verified_attribute() { if KaniAttributes::for_item(self.tcx, def_id).contract_attributes().is_none() { dcx.struct_span_err( - span, + span, + format!( + "Failed to generate verified stub: Function `{}` has no contract.", + self.item_name(), + ), + ) + .with_span_note( + self.tcx.def_span(def_id), format!( - "Failed to generate verified stub: Function `{}` has no contract.", - self.item_name(), + "Try adding a contract to this function or use the unsound `{}` attribute instead.", + KaniAttributeKind::Stub.as_ref(), ), ) - .with_span_note( - self.tcx.def_span(def_id), - format!( - "Try adding a contract to this function or use the unsound `{}` attribute instead.", - KaniAttributeKind::Stub.as_ref(), - ), - ) .emit(); return; } @@ -787,20 +790,48 @@ fn parse_unwind(tcx: TyCtxt, attr: &Attribute) -> Option { fn parse_stubs(tcx: TyCtxt, harness: DefId, attributes: &[&Attribute]) -> Vec { let current_module = tcx.parent_module_from_def_id(harness.expect_local()); - let check_resolve = |attr: &Attribute, name: &str| { - let result = resolve::resolve_fn(tcx, current_module.to_local_def_id(), name); - if let Err(err) = result { - tcx.dcx().span_err(attr.span, format!("failed to resolve `{name}`: {err}")); + let check_resolve = |attr: &Attribute, path: &TypePath| { + let result = resolve_fn_path(tcx, current_module.to_local_def_id(), path); + match result { + Ok(FnResolution::Fn(_)) => { /* no-op */ } + Ok(FnResolution::FnImpl { .. }) => { + tcx.dcx().span_err( + attr.span, + "Kani currently does not support stubbing trait implementations.", + ); + } + Err(err) => { + tcx.dcx().span_err( + attr.span, + format!("failed to resolve `{}`: {err}", pretty_type_path(path)), + ); + } } }; attributes .iter() - .filter_map(|attr| match parse_paths(attr) { - Ok(paths) => match paths.as_slice() { + .filter_map(|attr| { + let paths = parse_paths(attr).unwrap_or_else(|_| { + tcx.dcx().span_err( + attr.span, + format!( + "attribute `kani::{}` takes two path arguments; found argument that is not a path", + KaniAttributeKind::Stub.as_ref()) + ); + vec![] + }); + match paths.as_slice() { [orig, replace] => { check_resolve(attr, orig); check_resolve(attr, replace); - Some(Stub { original: orig.clone(), replacement: replace.clone() }) + Some(Stub { + original: orig.to_token_stream().to_string(), + replacement: replace.to_token_stream().to_string(), + }) + } + [] => { + /* Error was already emitted */ + None } _ => { tcx.dcx().span_err( @@ -812,13 +843,6 @@ fn parse_stubs(tcx: TyCtxt, harness: DefId, attributes: &[&Attribute]) -> Vec { - tcx.dcx().span_err( - error_span, - "attribute `kani::stub` takes two path arguments; found argument that is not a path", - ); - None } }) .collect() @@ -896,35 +920,13 @@ fn parse_integer(attr: &Attribute) -> Option { } /// Extracts a vector with the path arguments of an attribute. -/// Emits an error if it couldn't convert any of the arguments. -fn parse_paths(attr: &Attribute) -> Result, Span> { - let attr_args = attr.meta_item_list(); - attr_args - .unwrap_or_default() - .iter() - .map(|arg| match arg { - NestedMetaItem::Lit(item) => Err(item.span), - NestedMetaItem::MetaItem(item) => parse_path(item).ok_or(item.span), - }) - .collect() -} - -/// Extracts a path from an attribute item, returning `None` if the item is not -/// syntactically a path. -fn parse_path(meta_item: &MetaItem) -> Option { - if meta_item.is_word() { - Some( - meta_item - .path - .segments - .iter() - .map(|seg| seg.ident.as_str()) - .collect::>() - .join("::"), - ) - } else { - None - } +/// +/// Emits an error if it couldn't convert any of the arguments and return an empty vector. +fn parse_paths(attr: &Attribute) -> Result, syn::Error> { + let syn_attr = syn_attr(attr); + let parser = Punctuated::::parse_terminated; + let paths = syn_attr.parse_args_with(parser)?; + Ok(paths.into_iter().collect()) } /// Parse the arguments of the attribute into a (key, value) map. @@ -989,3 +991,54 @@ pub fn matches_diagnostic(tcx: TyCtxt, def: T, attr_name: &str) -> } false } + +/// Parse an attribute using `syn`. +/// +/// This provides a user-friendly interface to manipulate than the internal compiler AST. +fn syn_attr(attr: &Attribute) -> syn::Attribute { + let attr_str = rustc_ast_pretty::pprust::attribute_to_string(attr); + let parser = syn::Attribute::parse_outer; + parser.parse_str(&attr_str).unwrap().pop().unwrap() +} + +/// Return a more user-friendly string for path by trying to remove unneeded whitespace. +/// +/// `quote!()` and `TokenString::to_string()` introduce unnecessary space around separators. +/// This happens because these methods end up using TokenStream display, which has no +/// guarantees on the format printed. +/// +/// +/// E.g.: The path `<[char; 10]>::foo` printed with token stream becomes `< [ char ; 10 ] > :: foo`. +/// while this function turns this into `<[char ; 10]>::foo`. +/// +/// Thus, this can still be improved to handle the `qself.ty`. +/// +/// We also don't handle path segments, but users shouldn't pass generic arguments to our +/// attributes. +fn pretty_type_path(path: &TypePath) -> String { + fn segments_str<'a, I>(segments: I) -> String + where + I: IntoIterator, + { + // We don't bother with path arguments for now since users shouldn't provide them. + segments + .into_iter() + .map(|segment| segment.to_token_stream().to_string()) + .intersperse("::".to_string()) + .collect() + } + let leading = if path.path.leading_colon.is_some() { "::" } else { "" }; + if let Some(qself) = &path.qself { + let pos = qself.position; + let qself_str = qself.ty.to_token_stream().to_string(); + if pos == 0 { + format!("<{qself_str}>::{}", segments_str(&path.path.segments)) + } else { + let before = segments_str(path.path.segments.iter().take(pos)); + let after = segments_str(path.path.segments.iter().skip(pos)); + format!("<{qself_str} as {before}>::{after}") + } + } else { + format!("{leading}{}", segments_str(&path.path.segments)) + } +} diff --git a/kani-compiler/src/kani_middle/mod.rs b/kani-compiler/src/kani_middle/mod.rs index a5d077d9c16e..f281e5de5e88 100644 --- a/kani-compiler/src/kani_middle/mod.rs +++ b/kani-compiler/src/kani_middle/mod.rs @@ -6,7 +6,7 @@ use std::collections::HashSet; use crate::kani_queries::QueryDb; -use rustc_hir::{def::DefKind, def_id::LOCAL_CRATE}; +use rustc_hir::{def::DefKind, def_id::DefId as InternalDefId, def_id::LOCAL_CRATE}; use rustc_middle::span_bug; use rustc_middle::ty::layout::{ FnAbiError, FnAbiOf, FnAbiOfHelpers, FnAbiRequest, HasParamEnv, HasTyCtxt, LayoutError, @@ -225,10 +225,16 @@ fn find_fn_def(tcx: TyCtxt, diagnostic: &str) -> Option { .all_diagnostic_items(()) .name_to_id .get(&rustc_span::symbol::Symbol::intern(diagnostic))?; - let TyKind::RigidTy(RigidTy::FnDef(def, _)) = - rustc_internal::stable(tcx.type_of(attr_id)).value.kind() - else { - return None; - }; - Some(def) + stable_fn_def(tcx, *attr_id) +} + +/// Try to convert an internal `DefId` to a `FnDef`. +pub fn stable_fn_def(tcx: TyCtxt, def_id: InternalDefId) -> Option { + if let TyKind::RigidTy(RigidTy::FnDef(def, _)) = + rustc_internal::stable(tcx.type_of(def_id)).value.kind() + { + Some(def) + } else { + None + } } diff --git a/kani-compiler/src/kani_middle/resolve.rs b/kani-compiler/src/kani_middle/resolve.rs index ca4e8e749b75..88b1af86efe9 100644 --- a/kani-compiler/src/kani_middle/resolve.rs +++ b/kani-compiler/src/kani_middle/resolve.rs @@ -9,45 +9,110 @@ //! //! Note that glob use statements can form loops. The paths can also walk through the loop. -use rustc_smir::rustc_internal; -use std::collections::HashSet; -use std::fmt; -use std::iter::Peekable; - +use crate::kani_middle::stable_fn_def; +use quote::ToTokens; use rustc_errors::ErrorGuaranteed; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId, CRATE_DEF_INDEX, LOCAL_CRATE}; use rustc_hir::{ItemKind, UseKind}; use rustc_middle::ty::TyCtxt; -use stable_mir::ty::{FnDef, RigidTy, TyKind}; +use rustc_smir::rustc_internal; +use stable_mir::ty::{FnDef, RigidTy, Ty, TyKind}; use stable_mir::CrateDef; +use std::collections::HashSet; +use std::fmt; +use std::iter::Peekable; +use std::str::FromStr; +use strum_macros::{EnumString, IntoStaticStr}; +use syn::{Ident, PathSegment, Type, TypePath}; use tracing::debug; +#[derive(Copy, Clone, Debug, Eq, PartialEq, IntoStaticStr, EnumString)] +#[strum(serialize_all = "lowercase")] +enum PrimitiveIdent { + Bool, + Char, + F16, + F32, + F64, + F128, + I8, + I16, + I32, + I64, + I128, + Isize, + Str, + U8, + U16, + U32, + U64, + U128, + Usize, +} + +macro_rules! validate_kind { + ($tcx:ident, $id:ident, $expected:literal, $kind:pat) => {{ + let def_kind = $tcx.def_kind($id); + if matches!(def_kind, $kind) { + Ok($id) + } else { + Err(ResolveError::UnexpectedType { $tcx, item: $id, expected: $expected }) + } + }}; +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum FnResolution { + Fn(FnDef), + FnImpl { def: FnDef, ty: Ty }, +} + +/// Resolve a path to a function / method. +/// +/// The path can either be a simple path or a qualified path. +pub fn resolve_fn_path<'tcx>( + tcx: TyCtxt<'tcx>, + current_module: LocalDefId, + path: &TypePath, +) -> Result> { + match (&path.qself, &path.path.leading_colon) { + (Some(qself), Some(_)) => { + // Qualified path that does not define a trait. + resolve_ty(tcx, current_module, &qself.ty)?; + Err(ResolveError::UnsupportedPath { kind: "qualified bare function paths" }) + } + (Some(qself), None) => { + let ty = resolve_ty(tcx, current_module, &qself.ty)?; + let def_id = resolve_path(tcx, current_module, &path.path)?; + validate_kind!(tcx, def_id, "function / method", DefKind::Fn | DefKind::AssocFn)?; + Ok(FnResolution::FnImpl { def: stable_fn_def(tcx, def_id).unwrap(), ty }) + } + (None, _) => { + // Simple path + let def_id = resolve_path(tcx, current_module, &path.path)?; + validate_kind!(tcx, def_id, "function / method", DefKind::Fn | DefKind::AssocFn)?; + Ok(FnResolution::Fn(stable_fn_def(tcx, def_id).unwrap())) + } + } +} + /// Attempts to resolve a simple path (in the form of a string) to a function / method `DefId`. /// -/// TODO: Extend this implementation to handle qualified paths and simple paths -/// corresponding to trait methods. -/// +/// Use `[resolve_fn_path]` if you want to handle qualified paths and simple paths pub fn resolve_fn<'tcx>( tcx: TyCtxt<'tcx>, current_module: LocalDefId, path_str: &str, ) -> Result> { - let result = resolve_path(tcx, current_module, path_str); - match result { - Ok(def_id) => { - let def_kind = tcx.def_kind(def_id); - if matches!(def_kind, DefKind::AssocFn | DefKind::Fn) { - Ok(def_id) - } else { - Err(ResolveError::UnexpectedType { - tcx, - item: def_id, - expected: "function / method", - }) - } - } - err => err, + let path = syn::parse_str(path_str).map_err(|err| ResolveError::InvalidPath { + msg: format!("Expected a path, but found `{path_str}`. {err}"), + })?; + let result = resolve_fn_path(tcx, current_module, &path)?; + if let FnResolution::Fn(def) = result { + Ok(rustc_internal::internal(tcx, def.def_id())) + } else { + Err(ResolveError::UnsupportedPath { kind: "qualified paths" }) } } @@ -78,25 +143,93 @@ pub fn expect_resolve_fn( } } +/// Attempts to resolve a type. +pub fn resolve_ty<'tcx>( + tcx: TyCtxt<'tcx>, + current_module: LocalDefId, + typ: &syn::Type, +) -> Result> { + debug!(?typ, ?current_module, "resolve_ty"); + let unsupported = |kind: &'static str| Err(ResolveError::UnsupportedPath { kind }); + let invalid = |kind: &'static str| { + Err(ResolveError::InvalidPath { + msg: format!("Expected a type, but found {kind} `{}`", typ.to_token_stream()), + }) + }; + #[warn(non_exhaustive_omitted_patterns)] + match typ { + Type::Path(path) if path.qself.is_none() => { + let def_id = resolve_path(tcx, current_module, &path.path)?; + validate_kind!(tcx, def_id, "type", DefKind::Struct | DefKind::Union | DefKind::Enum)?; + Ok(rustc_internal::stable(tcx.type_of(def_id)).value) + } + Type::Path(_) => unsupported("qualified paths"), + Type::Array(_) + | Type::BareFn(_) + | Type::Macro(_) + | Type::Never(_) + | Type::Paren(_) + | Type::Ptr(_) + | Type::Reference(_) + | Type::Slice(_) + | Type::Tuple(_) => unsupported("path including primitive types"), + Type::Verbatim(_) => unsupported("unknown paths"), + Type::Group(_) => invalid("group paths"), + Type::ImplTrait(_) => invalid("trait impl paths"), + Type::Infer(_) => invalid("inferred paths"), + Type::TraitObject(_) => invalid("trait object paths"), + _ => { + unreachable!() + } + } +} + +/// Checks if a Path segment represents a primitive +fn is_primitive(ident: &Ident) -> bool { + let token = ident.to_string(); + let Ok(typ) = syn::parse_str(&token) else { return false }; + #[warn(non_exhaustive_omitted_patterns)] + match typ { + Type::Array(_) + | Type::Ptr(_) + | Type::Reference(_) + | Type::Slice(_) + | Type::Never(_) + | Type::Tuple(_) => true, + Type::Path(_) => PrimitiveIdent::from_str(&token).is_ok(), + Type::BareFn(_) + | Type::Group(_) + | Type::ImplTrait(_) + | Type::Infer(_) + | Type::Macro(_) + | Type::Paren(_) + | Type::TraitObject(_) + | Type::Verbatim(_) => false, + _ => { + unreachable!() + } + } +} + /// Attempts to resolve a simple path (in the form of a string) to a `DefId`. /// The current module is provided as an argument in order to resolve relative /// paths. -/// -/// Note: This function was written to be generic, however, it has only been tested for functions. -pub(crate) fn resolve_path<'tcx>( +fn resolve_path<'tcx>( tcx: TyCtxt<'tcx>, current_module: LocalDefId, - path_str: &str, + path: &syn::Path, ) -> Result> { + debug!(?path, "resolve_path"); let _span = tracing::span!(tracing::Level::DEBUG, "path_resolution").entered(); - let path = resolve_prefix(tcx, current_module, path_str)?; - path.segments.into_iter().try_fold(path.base, |base, name| { - debug!(?base, ?name, "resolve_path"); + let path = resolve_prefix(tcx, current_module, path)?; + path.segments.into_iter().try_fold(path.base, |base, segment| { + let name = segment.ident.to_string(); let def_kind = tcx.def_kind(base); let next_item = match def_kind { DefKind::ForeignMod | DefKind::Mod => resolve_in_module(tcx, base, &name), DefKind::Struct | DefKind::Enum | DefKind::Union => resolve_in_type(tcx, base, &name), + DefKind::Trait => resolve_in_trait(tcx, base, &name), kind => { debug!(?base, ?kind, "resolve_path: unexpected item"); Err(ResolveError::UnexpectedType { tcx, item: base, expected: "module" }) @@ -119,6 +252,8 @@ pub enum ResolveError<'tcx> { MissingItem { tcx: TyCtxt<'tcx>, base: DefId, unresolved: String }, /// Error triggered when the identifier points to an item with unexpected type. UnexpectedType { tcx: TyCtxt<'tcx>, item: DefId, expected: &'static str }, + /// Error triggered when the identifier is not currently supported. + UnsupportedPath { kind: &'static str }, } impl<'tcx> fmt::Debug for ResolveError<'tcx> { @@ -156,12 +291,15 @@ impl<'tcx> fmt::Display for ResolveError<'tcx> { let def_desc = description(*tcx, *base); write!(f, "unable to find `{unresolved}` inside {def_desc}") } + ResolveError::UnsupportedPath { kind } => { + write!(f, "Kani currently cannot resolve {kind}") + } } } } /// The segments of a path. -type Segments = Vec; +type Segments = Vec; /// A path consisting of a starting point and a bunch of segments. If `base` /// matches `Base::LocalModule { id: _, may_be_external_path : true }`, then @@ -174,8 +312,6 @@ struct Path { /// Identifier for the top module of the crate. const CRATE: &str = "crate"; -/// rustc represents initial `::` as `{{root}}`. -const ROOT: &str = "{{root}}"; /// Identifier for the current module. const SELF: &str = "self"; /// Identifier for the parent of the current module. @@ -186,56 +322,56 @@ const SUPER: &str = "super"; fn resolve_prefix<'tcx>( tcx: TyCtxt<'tcx>, current_module: LocalDefId, - name: &str, + path: &syn::Path, ) -> Result> { - debug!(?name, ?current_module, "resolve_prefix"); + debug!(?path, ?current_module, "resolve_prefix"); // Split the string into segments separated by `::`. Trim the whitespace // since path strings generated from macros sometimes add spaces around // `::`. - let mut segments = name.split("::").map(|s| s.trim().to_string()).peekable(); - assert!(segments.peek().is_some(), "expected identifier, found `{name}`"); + let mut segments = path.segments.iter(); // Resolve qualifiers `crate`, initial `::`, and `self`. The qualifier // `self` may be followed be `super` (handled below). - let first = segments.peek().unwrap().as_str(); - match first { - ROOT => { + match (path.leading_colon, segments.next()) { + (Some(_), Some(segment)) => { // Skip root and get the external crate from the name that follows `::`. - let next = segments.nth(1); - if let Some(next_name) = next { - let result = resolve_external(tcx, &next_name); - if let Some(def_id) = result { - Ok(Path { base: def_id, segments: segments.collect() }) - } else { - Err(ResolveError::MissingItem { - tcx, - base: current_module.to_def_id(), - unresolved: next_name, - }) - } + let next_name = segment.ident.to_string(); + let result = resolve_external(tcx, &next_name); + if let Some(def_id) = result { + Ok(Path { base: def_id, segments: segments.cloned().collect() }) } else { - Err(ResolveError::InvalidPath { msg: "expected identifier after `::`".to_string() }) + Err(ResolveError::MissingItem { + tcx, + base: current_module.to_def_id(), + unresolved: next_name, + }) } } - CRATE => { - segments.next(); + (Some(_), None) => { + Err(ResolveError::InvalidPath { msg: "expected identifier after `::`".to_string() }) + } + (None, Some(segment)) if segment.ident == CRATE => { // Find the module at the root of the crate. let current_module_hir_id = tcx.local_def_id_to_hir_id(current_module); let crate_root = match tcx.hir().parent_iter(current_module_hir_id).last() { None => current_module, Some((hir_id, _)) => hir_id.owner.def_id, }; - Ok(Path { base: crate_root.to_def_id(), segments: segments.collect() }) + Ok(Path { base: crate_root.to_def_id(), segments: segments.cloned().collect() }) } - SELF => { - segments.next(); - resolve_super(tcx, current_module, segments) + (None, Some(segment)) if segment.ident == SELF => { + resolve_super(tcx, current_module, segments.peekable()) } - SUPER => resolve_super(tcx, current_module, segments), - _ => { + (None, Some(segment)) if segment.ident == SUPER => { + resolve_super(tcx, current_module, path.segments.iter().peekable()) + } + (None, Some(segment)) if is_primitive(&segment.ident) => { + Err(ResolveError::UnsupportedPath { kind: "path including primitive types" }) + } + (None, Some(segment)) => { // No special key word was used. Try local first otherwise try external name. - let next_name = segments.next().unwrap(); + let next_name = segment.ident.to_string(); let def_id = resolve_in_module(tcx, current_module.to_def_id(), &next_name).or_else(|err| { if matches!(err, ResolveError::MissingItem { .. }) { @@ -245,25 +381,28 @@ fn resolve_prefix<'tcx>( Err(err) } })?; - Ok(Path { base: def_id, segments: segments.collect() }) + Ok(Path { base: def_id, segments: segments.cloned().collect() }) + } + _ => { + unreachable!("Empty path: `{path:?}`") } } } /// Pop up the module stack until we account for all the `super` prefixes. /// This method will error out if it tries to backtrace from the root crate. -fn resolve_super<'tcx, I>( +fn resolve_super<'tcx, 'a, I>( tcx: TyCtxt, current_module: LocalDefId, mut segments: Peekable, ) -> Result> where - I: Iterator, + I: Iterator, { let current_module_hir_id = tcx.local_def_id_to_hir_id(current_module); let mut parents = tcx.hir().parent_iter(current_module_hir_id); let mut base_module = current_module; - while segments.next_if(|segment| segment == SUPER).is_some() { + while segments.next_if(|segment| segment.ident == SUPER).is_some() { if let Some((parent, _)) = parents.next() { debug!("parent: {parent:?}"); base_module = parent.owner.def_id; @@ -272,7 +411,7 @@ where } } debug!("base: {base_module:?}"); - Ok(Path { base: base_module.to_def_id(), segments: segments.collect() }) + Ok(Path { base: base_module.to_def_id(), segments: segments.cloned().collect() }) } /// Resolves an external crate name. @@ -422,8 +561,7 @@ fn resolve_in_glob_use(tcx: TyCtxt, res: &Res, name: &str) -> RelativeResolution } } -/// Resolves a method in a type. It currently does not resolve trait methods -/// (see ). +/// Resolves a function in a type. fn resolve_in_type<'tcx>( tcx: TyCtxt<'tcx>, type_id: DefId, @@ -445,3 +583,25 @@ fn resolve_in_type<'tcx>( }) .ok_or_else(missing_item_err) } + +/// Resolves a function in a trait. +fn resolve_in_trait<'tcx>( + tcx: TyCtxt<'tcx>, + trait_id: DefId, + name: &str, +) -> Result> { + debug!(?name, ?trait_id, "resolve_in_trait"); + let missing_item_err = + || ResolveError::MissingItem { tcx, base: trait_id, unresolved: name.to_string() }; + let trait_def = tcx.trait_def(trait_id); + // Try the inherent `impl` blocks (i.e., non-trait `impl`s). + tcx.associated_item_def_ids(trait_def.def_id) + .iter() + .copied() + .find(|item| { + let item_path = tcx.def_path_str(*item); + let last = item_path.split("::").last().unwrap(); + last == name + }) + .ok_or_else(missing_item_err) +} diff --git a/kani-compiler/src/main.rs b/kani-compiler/src/main.rs index e47483fb4fa5..3a7816c1b084 100644 --- a/kani-compiler/src/main.rs +++ b/kani-compiler/src/main.rs @@ -15,6 +15,7 @@ #![feature(let_chains)] #![feature(f128)] #![feature(f16)] +#![feature(non_exhaustive_omitted_patterns_lint)] extern crate rustc_abi; extern crate rustc_ast; extern crate rustc_ast_pretty; diff --git a/tests/ui/function-stubbing-error/expected b/tests/ui/function-stubbing-error/invalid_stub_args.expected similarity index 100% rename from tests/ui/function-stubbing-error/expected rename to tests/ui/function-stubbing-error/invalid_stub_args.expected diff --git a/tests/ui/function-stubbing-error/main.rs b/tests/ui/function-stubbing-error/invalid_stub_args.rs similarity index 87% rename from tests/ui/function-stubbing-error/main.rs rename to tests/ui/function-stubbing-error/invalid_stub_args.rs index c433e352e740..baed360b2dda 100644 --- a/tests/ui/function-stubbing-error/main.rs +++ b/tests/ui/function-stubbing-error/invalid_stub_args.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --harness main -Z stubbing +// kani-flags: -Z stubbing // //! This tests whether we detect syntactically misformed `kani::stub` annotations. diff --git a/tests/ui/function-stubbing-error/unsupported_resolutions.expected b/tests/ui/function-stubbing-error/unsupported_resolutions.expected new file mode 100644 index 000000000000..1b4c00d22f4f --- /dev/null +++ b/tests/ui/function-stubbing-error/unsupported_resolutions.expected @@ -0,0 +1,8 @@ +error: failed to resolve `<[char ; 10]>::foo`: Kani currently cannot resolve path including primitive types +error: failed to resolve `<& [u32]>::foo`: Kani currently cannot resolve path including primitive types +error: failed to resolve `<& [u32] as Foo>::foo`: Kani currently cannot resolve path including primitive types +error: failed to resolve `<(i32 , i32) as Foo>::foo`: Kani currently cannot resolve path including primitive types +error: failed to resolve `u8::foo`: Kani currently cannot resolve path including primitive types +error: failed to resolve `::bar`: unable to find `bar` inside trait `Foo` +error: Kani currently does not support stubbing trait implementations +error: failed to resolve `::foo`: Kani currently cannot resolve qualified bare function paths diff --git a/tests/ui/function-stubbing-error/unsupported_resolutions.rs b/tests/ui/function-stubbing-error/unsupported_resolutions.rs new file mode 100644 index 000000000000..2fc74a0fd44f --- /dev/null +++ b/tests/ui/function-stubbing-error/unsupported_resolutions.rs @@ -0,0 +1,42 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// kani-flags: -Z stubbing +// +//! This tests that we emit a nice error message for unsupported paths. + +/// Dummy structure +pub struct Bar; + +/// Dummy trait +pub trait Foo { + fn foo() -> bool { + false + } +} + +impl Foo for Bar {} + +impl Foo for u8 {} + +impl Foo for &[T] {} + +impl Foo for [char; 10] {} + +impl Foo for (i32, i32) {} + +/// Dummy stub +pub fn stub_foo() -> bool { + true +} + +#[kani::proof] +#[kani::stub(::foo, stub_foo)] +#[kani::stub(::foo, stub_foo)] +#[kani::stub(::bar, stub_foo)] +#[kani::stub(u8::foo, stub_foo)] +#[kani::stub(<(i32, i32) as Foo>::foo, stub_foo)] +#[kani::stub(<&[u32] as Foo>::foo, stub_foo)] +#[kani::stub(<&[u32]>::foo, stub_foo)] +#[kani::stub(<[char; 10]>::foo, stub_foo)] +fn unsupported_args() {} diff --git a/tests/ui/invalid-attribute/expected b/tests/ui/invalid-attribute/expected index 75be29c13e83..a5623dba5b6e 100644 --- a/tests/ui/invalid-attribute/expected +++ b/tests/ui/invalid-attribute/expected @@ -5,7 +5,7 @@ attrs.rs | ^^^^^^^^^^^^^^^^^^^^^^^^^^\ | -error: attribute `kani::stub` takes two path arguments; found 0\ +error: attribute `kani::stub` takes two path arguments; found argument that is not a path\ attrs.rs |\ | #[kani::stub(invalid=opt)]\ From 2960f80be858d51c86dd3f641624d55ddfdf65d2 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Fri, 30 Aug 2024 21:08:06 -0400 Subject: [PATCH 28/40] Partially integrate uninit memory checks into `verify_std` (#3470) This PR partially integrates uninitialized memory checks into the `verify_std` pipeline, which makes it possible to enable them for the Rust Standard Library verification. Changes: - Move `mem_init.rs` library code into `kani_core`. - Add a conditional compilation flag to disable uninitialized memory checks whenever some functionality is not yet supported. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- library/kani/src/lib.rs | 1 - library/kani/src/mem_init.rs | 333 ----------------- library/kani_core/src/lib.rs | 9 + library/kani_core/src/mem_init.rs | 347 ++++++++++++++++++ .../verify_std_cmd/verify_core.rs | 4 + .../verify_std_cmd/verify_std.expected | 13 + .../verify_std_cmd/verify_std.sh | 4 + 7 files changed, 377 insertions(+), 334 deletions(-) delete mode 100644 library/kani/src/mem_init.rs create mode 100644 library/kani_core/src/mem_init.rs diff --git a/library/kani/src/lib.rs b/library/kani/src/lib.rs index 59a89622a52d..c3e9ae5497cc 100644 --- a/library/kani/src/lib.rs +++ b/library/kani/src/lib.rs @@ -32,7 +32,6 @@ pub mod shadow; pub mod slice; pub mod vec; -mod mem_init; mod models; #[cfg(feature = "concrete_playback")] diff --git a/library/kani/src/mem_init.rs b/library/kani/src/mem_init.rs deleted file mode 100644 index a6118c472a92..000000000000 --- a/library/kani/src/mem_init.rs +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -//! This module provides instrumentation for tracking 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. Padding bytes are always -//! considered uninitialized when read as data bytes. Each type has a type layout to specify which -//! bytes are considered to be data and which -- padding. This is determined at compile time and -//! statically injected into the program (see `Layout`). -//! -//! 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. -//! -//! Note that for each harness, tracked object and tracked offset are chosen non-deterministically, -//! so calls to `is_xxx_initialized` should be only used in assertion contexts. - -// Definitions in this module are not meant to be visible to the end user, only the compiler. -#![allow(dead_code)] - -/// 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; LAYOUT_SIZE]; - -/// Currently tracked non-deterministically chosen memory initialization state. -struct MemoryInitializationState { - pub tracked_object_id: usize, - pub tracked_offset: usize, - pub value: bool, -} - -impl MemoryInitializationState { - /// This is a dummy initialization function -- the values will be eventually overwritten by a - /// call to `initialize_memory_initialization_state`. - pub const fn new() -> Self { - Self { tracked_object_id: 0, tracked_offset: 0, value: false } - } - - /// Return currently tracked memory initialization state if `ptr` points to the currently - /// tracked object and the tracked offset lies within `LAYOUT_SIZE` bytes of `ptr`. Return - /// `true` otherwise. - /// - /// Such definition is necessary since both tracked object and tracked offset are chosen - /// non-deterministically. - #[kanitool::disable_checks(pointer)] - pub fn get( - &mut self, - ptr: *const u8, - layout: Layout, - ) -> bool { - let obj = crate::mem::pointer_object(ptr); - let offset = crate::mem::pointer_offset(ptr); - if self.tracked_object_id == obj - && self.tracked_offset >= offset - && self.tracked_offset < offset + LAYOUT_SIZE - { - !layout[self.tracked_offset - offset] || self.value - } else { - true - } - } - - /// Set currently tracked memory initialization state if `ptr` points to the currently tracked - /// object and the tracked offset lies within `LAYOUT_SIZE` bytes of `ptr`. Do nothing - /// otherwise. - /// - /// Such definition is necessary since both tracked object and tracked offset are chosen - /// non-deterministically. - #[kanitool::disable_checks(pointer)] - pub fn set( - &mut self, - ptr: *const u8, - layout: Layout, - value: bool, - ) { - let obj = crate::mem::pointer_object(ptr); - let offset = crate::mem::pointer_offset(ptr); - if self.tracked_object_id == obj - && self.tracked_offset >= offset - && self.tracked_offset < offset + LAYOUT_SIZE - { - self.value = layout[self.tracked_offset - offset] && value; - } - } - - /// Copy memory initialization state by non-deterministically switching the tracked object and - /// adjusting the tracked offset. - pub fn copy( - &mut self, - from_ptr: *const u8, - to_ptr: *const u8, - num_elts: usize, - ) { - let from_obj = crate::mem::pointer_object(from_ptr); - let from_offset = crate::mem::pointer_offset(from_ptr); - - let to_obj = crate::mem::pointer_object(to_ptr); - let to_offset = crate::mem::pointer_offset(to_ptr); - - if self.tracked_object_id == from_obj - && self.tracked_offset >= from_offset - && self.tracked_offset < from_offset + num_elts * LAYOUT_SIZE - { - let should_reset: bool = crate::any(); - if should_reset { - self.tracked_object_id = to_obj; - self.tracked_offset += to_offset - from_offset; - // Note that this preserves the value. - } - } - } - - /// Return currently tracked memory initialization state if `ptr` points to the currently - /// tracked object and the tracked offset lies within `LAYOUT_SIZE * num_elts` bytes of `ptr`. - /// Return `true` otherwise. - /// - /// Such definition is necessary since both tracked object and tracked offset are chosen - /// non-deterministically. - #[kanitool::disable_checks(pointer)] - pub fn get_slice( - &mut self, - ptr: *const u8, - layout: Layout, - num_elts: usize, - ) -> bool { - let obj = crate::mem::pointer_object(ptr); - let offset = crate::mem::pointer_offset(ptr); - if self.tracked_object_id == obj - && self.tracked_offset >= offset - && self.tracked_offset < offset + num_elts * LAYOUT_SIZE - { - !layout[(self.tracked_offset - offset) % LAYOUT_SIZE] || self.value - } else { - true - } - } - - /// Set currently tracked memory initialization state if `ptr` points to the currently tracked - /// object and the tracked offset lies within `LAYOUT_SIZE * num_elts` bytes of `ptr`. Do - /// nothing otherwise. - /// - /// Such definition is necessary since both tracked object and tracked offset are chosen - /// non-deterministically. - #[kanitool::disable_checks(pointer)] - pub fn set_slice( - &mut self, - ptr: *const u8, - layout: Layout, - num_elts: usize, - value: bool, - ) { - let obj = crate::mem::pointer_object(ptr); - let offset = crate::mem::pointer_offset(ptr); - if self.tracked_object_id == obj - && self.tracked_offset >= offset - && self.tracked_offset < offset + num_elts * LAYOUT_SIZE - { - self.value = layout[(self.tracked_offset - offset) % LAYOUT_SIZE] && value; - } - } -} - -/// Global object for tracking memory initialization state. -#[rustc_diagnostic_item = "KaniMemoryInitializationState"] -static mut MEM_INIT_STATE: MemoryInitializationState = MemoryInitializationState::new(); - -/// Set tracked object and tracked offset to a non-deterministic value. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniInitializeMemoryInitializationState"] -fn initialize_memory_initialization_state() { - unsafe { - MEM_INIT_STATE.tracked_object_id = crate::any(); - MEM_INIT_STATE.tracked_offset = crate::any(); - MEM_INIT_STATE.value = false; - } -} - -/// Get initialization state of `num_elts` items laid out according to the `layout` starting at address `ptr`. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniIsPtrInitialized"] -fn is_ptr_initialized( - ptr: *const T, - layout: Layout, -) -> bool { - if LAYOUT_SIZE == 0 { - return true; - } - let (ptr, _) = ptr.to_raw_parts(); - unsafe { MEM_INIT_STATE.get(ptr as *const u8, layout) } -} - -/// Set initialization state to `value` for `num_elts` items laid out according to the `layout` starting at address `ptr`. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniSetPtrInitialized"] -fn set_ptr_initialized( - ptr: *const T, - layout: Layout, - value: bool, -) { - if LAYOUT_SIZE == 0 { - return; - } - let (ptr, _) = ptr.to_raw_parts(); - unsafe { - MEM_INIT_STATE.set(ptr as *const u8, layout, value); - } -} - -/// Get initialization state of `num_elts` items laid out according to the `layout` starting at address `ptr`. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniIsSliceChunkPtrInitialized"] -fn is_slice_chunk_ptr_initialized( - ptr: *const T, - layout: Layout, - num_elts: usize, -) -> bool { - if LAYOUT_SIZE == 0 { - return true; - } - let (ptr, _) = ptr.to_raw_parts(); - unsafe { MEM_INIT_STATE.get_slice(ptr as *const u8, layout, num_elts) } -} - -/// Set initialization state to `value` for `num_elts` items laid out according to the `layout` starting at address `ptr`. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniSetSliceChunkPtrInitialized"] -fn set_slice_chunk_ptr_initialized( - ptr: *const T, - layout: Layout, - num_elts: usize, - value: bool, -) { - if LAYOUT_SIZE == 0 { - return; - } - let (ptr, _) = ptr.to_raw_parts(); - unsafe { - MEM_INIT_STATE.set_slice(ptr as *const u8, layout, num_elts, value); - } -} - -/// Get initialization state of the slice, items of which are laid out according to the `layout` starting at address `ptr`. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniIsSlicePtrInitialized"] -fn is_slice_ptr_initialized( - ptr: *const [T], - layout: Layout, -) -> bool { - if LAYOUT_SIZE == 0 { - return true; - } - let (ptr, num_elts) = ptr.to_raw_parts(); - unsafe { MEM_INIT_STATE.get_slice(ptr as *const u8, layout, num_elts) } -} - -/// Set initialization state of the slice, items of which are laid out according to the `layout` starting at address `ptr` to `value`. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniSetSlicePtrInitialized"] -fn set_slice_ptr_initialized( - ptr: *const [T], - layout: Layout, - value: bool, -) { - if LAYOUT_SIZE == 0 { - return; - } - let (ptr, num_elts) = ptr.to_raw_parts(); - unsafe { - MEM_INIT_STATE.set_slice(ptr as *const u8, layout, num_elts, value); - } -} - -/// Get initialization state of the string slice, items of which are laid out according to the `layout` starting at address `ptr`. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniIsStrPtrInitialized"] -fn is_str_ptr_initialized( - ptr: *const str, - layout: Layout, -) -> bool { - if LAYOUT_SIZE == 0 { - return true; - } - let (ptr, num_elts) = ptr.to_raw_parts(); - unsafe { MEM_INIT_STATE.get_slice(ptr as *const u8, layout, num_elts) } -} - -/// Set initialization state of the string slice, items of which are laid out according to the `layout` starting at address `ptr` to `value`. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniSetStrPtrInitialized"] -fn set_str_ptr_initialized( - ptr: *const str, - layout: Layout, - value: bool, -) { - if LAYOUT_SIZE == 0 { - return; - } - let (ptr, num_elts) = ptr.to_raw_parts(); - unsafe { - MEM_INIT_STATE.set_slice(ptr as *const u8, layout, num_elts, value); - } -} - -/// Copy initialization state of `size_of:: * num_elts` bytes from one pointer to the other. Note -/// that in this case `LAYOUT_SIZE == size_of::`. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniCopyInitState"] -fn copy_init_state(from: *const T, to: *const T, num_elts: usize) { - if LAYOUT_SIZE == 0 { - return; - } - let (from_ptr, _) = from.to_raw_parts(); - let (to_ptr, _) = to.to_raw_parts(); - unsafe { - MEM_INIT_STATE.copy::(from_ptr as *const u8, to_ptr as *const u8, num_elts); - } -} - -/// Copy initialization state of `size_of::` bytes from one pointer to the other. Note that in -/// this case `LAYOUT_SIZE == size_of::`. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniCopyInitStateSingle"] -fn copy_init_state_single(from: *const T, to: *const T) { - copy_init_state::(from, to, 1); -} diff --git a/library/kani_core/src/lib.rs b/library/kani_core/src/lib.rs index 6cbe98d30df2..5df8f2228c62 100644 --- a/library/kani_core/src/lib.rs +++ b/library/kani_core/src/lib.rs @@ -22,6 +22,7 @@ mod arbitrary; mod mem; +mod mem_init; pub use kani_macros::*; @@ -45,6 +46,10 @@ macro_rules! kani_lib { pub mod mem { kani_core::kani_mem!(core); } + + mod mem_init { + kani_core::kani_mem_init!(core); + } } }; @@ -56,6 +61,10 @@ macro_rules! kani_lib { pub mod mem { kani_core::kani_mem!(std); } + + mod mem_init { + kani_core::kani_mem_init!(std); + } }; } diff --git a/library/kani_core/src/mem_init.rs b/library/kani_core/src/mem_init.rs new file mode 100644 index 000000000000..cec23c14263b --- /dev/null +++ b/library/kani_core/src/mem_init.rs @@ -0,0 +1,347 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This module provides instrumentation for tracking 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. Padding bytes are always +//! considered uninitialized when read as data bytes. Each type has a type layout to specify which +//! bytes are considered to be data and which -- padding. This is determined at compile time and +//! statically injected into the program (see `Layout`). +//! +//! 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. +//! +//! Note that for each harness, tracked object and tracked offset are chosen non-deterministically, +//! so calls to `is_xxx_initialized` should be only used in assertion contexts. + +// Definitions in this module are not meant to be visible to the end user, only the compiler. +#![allow(dead_code)] + +#[macro_export] +#[allow(clippy::crate_in_macro_def)] +macro_rules! kani_mem_init { + ($core:path) => { + /// 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; LAYOUT_SIZE]; + + /// Currently tracked non-deterministically chosen memory initialization state. + struct MemoryInitializationState { + pub tracked_object_id: usize, + pub tracked_offset: usize, + pub value: bool, + } + + impl MemoryInitializationState { + /// This is a dummy initialization function -- the values will be eventually overwritten by a + /// call to `initialize_memory_initialization_state`. + pub const fn new() -> Self { + Self { tracked_object_id: 0, tracked_offset: 0, value: false } + } + + /// Return currently tracked memory initialization state if `ptr` points to the currently + /// tracked object and the tracked offset lies within `LAYOUT_SIZE` bytes of `ptr`. Return + /// `true` otherwise. + /// + /// Such definition is necessary since both tracked object and tracked offset are chosen + /// non-deterministically. + #[kanitool::disable_checks(pointer)] + pub fn get( + &mut self, + ptr: *const u8, + layout: Layout, + ) -> bool { + let obj = super::mem::pointer_object(ptr); + let offset = super::mem::pointer_offset(ptr); + if self.tracked_object_id == obj + && self.tracked_offset >= offset + && self.tracked_offset < offset + LAYOUT_SIZE + { + !layout[self.tracked_offset - offset] || self.value + } else { + true + } + } + + /// Set currently tracked memory initialization state if `ptr` points to the currently tracked + /// object and the tracked offset lies within `LAYOUT_SIZE` bytes of `ptr`. Do nothing + /// otherwise. + /// + /// Such definition is necessary since both tracked object and tracked offset are chosen + /// non-deterministically. + #[kanitool::disable_checks(pointer)] + pub fn set( + &mut self, + ptr: *const u8, + layout: Layout, + value: bool, + ) { + let obj = super::mem::pointer_object(ptr); + let offset = super::mem::pointer_offset(ptr); + if self.tracked_object_id == obj + && self.tracked_offset >= offset + && self.tracked_offset < offset + LAYOUT_SIZE + { + self.value = layout[self.tracked_offset - offset] && value; + } + } + + /// Copy memory initialization state by non-deterministically switching the tracked object and + /// adjusting the tracked offset. + pub fn copy( + &mut self, + from_ptr: *const u8, + to_ptr: *const u8, + num_elts: usize, + ) { + let from_obj = super::mem::pointer_object(from_ptr); + let from_offset = super::mem::pointer_offset(from_ptr); + + let to_obj = super::mem::pointer_object(to_ptr); + let to_offset = super::mem::pointer_offset(to_ptr); + + if self.tracked_object_id == from_obj + && self.tracked_offset >= from_offset + && self.tracked_offset < from_offset + num_elts * LAYOUT_SIZE + { + let should_reset: bool = super::any(); + if should_reset { + self.tracked_object_id = to_obj; + self.tracked_offset += to_offset - from_offset; + // Note that this preserves the value. + } + } + } + + /// Return currently tracked memory initialization state if `ptr` points to the currently + /// tracked object and the tracked offset lies within `LAYOUT_SIZE * num_elts` bytes of `ptr`. + /// Return `true` otherwise. + /// + /// Such definition is necessary since both tracked object and tracked offset are chosen + /// non-deterministically. + #[kanitool::disable_checks(pointer)] + pub fn get_slice( + &mut self, + ptr: *const u8, + layout: Layout, + num_elts: usize, + ) -> bool { + let obj = super::mem::pointer_object(ptr); + let offset = super::mem::pointer_offset(ptr); + if self.tracked_object_id == obj + && self.tracked_offset >= offset + && self.tracked_offset < offset + num_elts * LAYOUT_SIZE + { + !layout[(self.tracked_offset - offset) % LAYOUT_SIZE] || self.value + } else { + true + } + } + + /// Set currently tracked memory initialization state if `ptr` points to the currently tracked + /// object and the tracked offset lies within `LAYOUT_SIZE * num_elts` bytes of `ptr`. Do + /// nothing otherwise. + /// + /// Such definition is necessary since both tracked object and tracked offset are chosen + /// non-deterministically. + #[kanitool::disable_checks(pointer)] + pub fn set_slice( + &mut self, + ptr: *const u8, + layout: Layout, + num_elts: usize, + value: bool, + ) { + let obj = super::mem::pointer_object(ptr); + let offset = super::mem::pointer_offset(ptr); + if self.tracked_object_id == obj + && self.tracked_offset >= offset + && self.tracked_offset < offset + num_elts * LAYOUT_SIZE + { + self.value = layout[(self.tracked_offset - offset) % LAYOUT_SIZE] && value; + } + } + } + + /// Global object for tracking memory initialization state. + #[rustc_diagnostic_item = "KaniMemoryInitializationState"] + static mut MEM_INIT_STATE: MemoryInitializationState = MemoryInitializationState::new(); + + /// Set tracked object and tracked offset to a non-deterministic value. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniInitializeMemoryInitializationState"] + fn initialize_memory_initialization_state() { + unsafe { + MEM_INIT_STATE.tracked_object_id = super::any(); + MEM_INIT_STATE.tracked_offset = super::any(); + MEM_INIT_STATE.value = false; + } + } + + /// Get initialization state of `num_elts` items laid out according to the `layout` starting at address `ptr`. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniIsPtrInitialized"] + fn is_ptr_initialized( + ptr: *const T, + layout: Layout, + ) -> bool { + if LAYOUT_SIZE == 0 { + return true; + } + let (ptr, _) = ptr.to_raw_parts(); + unsafe { MEM_INIT_STATE.get(ptr as *const u8, layout) } + } + + /// Set initialization state to `value` for `num_elts` items laid out according to the `layout` starting at address `ptr`. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniSetPtrInitialized"] + fn set_ptr_initialized( + ptr: *const T, + layout: Layout, + value: bool, + ) { + if LAYOUT_SIZE == 0 { + return; + } + let (ptr, _) = ptr.to_raw_parts(); + unsafe { + MEM_INIT_STATE.set(ptr as *const u8, layout, value); + } + } + + /// Get initialization state of `num_elts` items laid out according to the `layout` starting at address `ptr`. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniIsSliceChunkPtrInitialized"] + fn is_slice_chunk_ptr_initialized( + ptr: *const T, + layout: Layout, + num_elts: usize, + ) -> bool { + if LAYOUT_SIZE == 0 { + return true; + } + let (ptr, _) = ptr.to_raw_parts(); + unsafe { MEM_INIT_STATE.get_slice(ptr as *const u8, layout, num_elts) } + } + + /// Set initialization state to `value` for `num_elts` items laid out according to the `layout` starting at address `ptr`. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniSetSliceChunkPtrInitialized"] + fn set_slice_chunk_ptr_initialized( + ptr: *const T, + layout: Layout, + num_elts: usize, + value: bool, + ) { + if LAYOUT_SIZE == 0 { + return; + } + let (ptr, _) = ptr.to_raw_parts(); + unsafe { + MEM_INIT_STATE.set_slice(ptr as *const u8, layout, num_elts, value); + } + } + + /// Get initialization state of the slice, items of which are laid out according to the `layout` starting at address `ptr`. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniIsSlicePtrInitialized"] + fn is_slice_ptr_initialized( + ptr: *const [T], + layout: Layout, + ) -> bool { + if LAYOUT_SIZE == 0 { + return true; + } + let (ptr, num_elts) = ptr.to_raw_parts(); + unsafe { MEM_INIT_STATE.get_slice(ptr as *const u8, layout, num_elts) } + } + + /// Set initialization state of the slice, items of which are laid out according to the `layout` starting at address `ptr` to `value`. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniSetSlicePtrInitialized"] + fn set_slice_ptr_initialized( + ptr: *const [T], + layout: Layout, + value: bool, + ) { + if LAYOUT_SIZE == 0 { + return; + } + let (ptr, num_elts) = ptr.to_raw_parts(); + unsafe { + MEM_INIT_STATE.set_slice(ptr as *const u8, layout, num_elts, value); + } + } + + /// Get initialization state of the string slice, items of which are laid out according to the `layout` starting at address `ptr`. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniIsStrPtrInitialized"] + fn is_str_ptr_initialized( + ptr: *const str, + layout: Layout, + ) -> bool { + if LAYOUT_SIZE == 0 { + return true; + } + let (ptr, num_elts) = ptr.to_raw_parts(); + unsafe { MEM_INIT_STATE.get_slice(ptr as *const u8, layout, num_elts) } + } + + /// Set initialization state of the string slice, items of which are laid out according to the `layout` starting at address `ptr` to `value`. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniSetStrPtrInitialized"] + fn set_str_ptr_initialized( + ptr: *const str, + layout: Layout, + value: bool, + ) { + if LAYOUT_SIZE == 0 { + return; + } + let (ptr, num_elts) = ptr.to_raw_parts(); + unsafe { + MEM_INIT_STATE.set_slice(ptr as *const u8, layout, num_elts, value); + } + } + + /// Copy initialization state of `size_of:: * num_elts` bytes from one pointer to the other. Note + /// that in this case `LAYOUT_SIZE == size_of::`. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniCopyInitState"] + fn copy_init_state( + from: *const T, + to: *const T, + num_elts: usize, + ) { + if LAYOUT_SIZE == 0 { + return; + } + let (from_ptr, _) = from.to_raw_parts(); + let (to_ptr, _) = to.to_raw_parts(); + unsafe { + MEM_INIT_STATE.copy::( + from_ptr as *const u8, + to_ptr as *const u8, + num_elts, + ); + } + } + + /// Copy initialization state of `size_of::` bytes from one pointer to the other. Note that in + /// this case `LAYOUT_SIZE == size_of::`. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniCopyInitStateSingle"] + fn copy_init_state_single(from: *const T, to: *const T) { + copy_init_state::(from, to, 1); + } + }; +} diff --git a/tests/script-based-pre/verify_std_cmd/verify_core.rs b/tests/script-based-pre/verify_std_cmd/verify_core.rs index 28cf113a9210..9bdabb32dde3 100644 --- a/tests/script-based-pre/verify_std_cmd/verify_core.rs +++ b/tests/script-based-pre/verify_std_cmd/verify_core.rs @@ -29,6 +29,7 @@ pub mod verify { } #[kani::proof_for_contract(dummy_read)] + #[cfg(not(uninit_checks))] fn check_dummy_read() { let val: char = kani::any(); assert_eq!(unsafe { dummy_read(&val) }, val); @@ -37,16 +38,19 @@ pub mod verify { /// Ensure we can verify constant functions. #[kani::requires(kani::mem::can_dereference(ptr))] #[rustc_diagnostic_item = "dummy_read"] + #[cfg(not(uninit_checks))] const unsafe fn dummy_read(ptr: *const T) -> T { *ptr } + #[cfg(not(uninit_checks))] #[kani::proof_for_contract(swap_tuple)] fn check_swap_tuple() { let initial: (char, NonZeroU8) = kani::any(); let _swapped = swap_tuple(initial); } + #[cfg(not(uninit_checks))] #[kani::ensures(| result | result.0 == second && result.1 == first)] fn swap_tuple((first, second): (char, NonZeroU8)) -> (NonZeroU8, char) { (second, first) diff --git a/tests/script-based-pre/verify_std_cmd/verify_std.expected b/tests/script-based-pre/verify_std_cmd/verify_std.expected index 22e8fb2e6375..16705ffcfefa 100644 --- a/tests/script-based-pre/verify_std_cmd/verify_std.expected +++ b/tests/script-based-pre/verify_std_cmd/verify_std.expected @@ -19,3 +19,16 @@ Checking harness num::verify::check_non_zero... VERIFICATION:- SUCCESSFUL Complete - 6 successfully verified harnesses, 0 failures, 6 total. + +[TEST] Run kani verify-std -Z uninit-checks + +Checking harness verify::check_add_one... +VERIFICATION:- SUCCESSFUL + +Checking harness verify::dummy_proof... +VERIFICATION:- SUCCESSFUL + +Checking harness verify::harness... +VERIFICATION:- SUCCESSFUL + +Complete - 3 successfully verified harnesses, 0 failures, 3 total. diff --git a/tests/script-based-pre/verify_std_cmd/verify_std.sh b/tests/script-based-pre/verify_std_cmd/verify_std.sh index 6a95c667b71b..f06e8a8f9921 100755 --- a/tests/script-based-pre/verify_std_cmd/verify_std.sh +++ b/tests/script-based-pre/verify_std_cmd/verify_std.sh @@ -25,6 +25,7 @@ CORE_CODE=$(cat verify_core.rs) STD_CODE=' #[cfg(kani)] +#[cfg(not(uninit_checks))] mod verify { use core::kani; #[kani::proof] @@ -53,5 +54,8 @@ echo "[TEST] Run kani verify-std" export RUST_BACKTRACE=1 kani verify-std -Z unstable-options "${TMP_DIR}/library" --target-dir "${TMP_DIR}/target" -Z function-contracts -Z stubbing -Z mem-predicates +echo "[TEST] Run kani verify-std -Z uninit-checks" +RUSTFLAGS="--cfg=uninit_checks" kani verify-std -Z unstable-options "${TMP_DIR}/library" --target-dir "${TMP_DIR}/target" -Z function-contracts -Z stubbing -Z mem-predicates -Z uninit-checks + # Cleanup rm -r ${TMP_DIR} From 5449c3cabb36713a050e8c3978bea0d72695b0db Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Sun, 1 Sep 2024 11:50:20 -0400 Subject: [PATCH 29/40] Update Toolchain to 9/1 (#3478) Updates toolchain to 9/1. This couldn't run automatically because of #3476. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 503c4611e3a0..ca038ea7a812 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,5 +2,5 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT [toolchain] -channel = "nightly-2024-08-30" +channel = "nightly-2024-09-01" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 6bc48a96cfa9b9a5777189381580e72c6bd676f2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 06:40:59 -0700 Subject: [PATCH 30/40] Automatic cargo update to 2024-09-02 (#3480) Dependency upgrade resulting from `cargo update`. Co-authored-by: tautschnig <1144736+tautschnig@users.noreply.github.com> --- Cargo.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0da9ff2e2159..c180c66dde90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -176,7 +176,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -428,9 +428,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown", @@ -483,7 +483,7 @@ dependencies = [ "shell-words", "strum", "strum_macros", - "syn 2.0.76", + "syn 2.0.77", "tracing", "tracing-subscriber", "tracing-tree", @@ -541,7 +541,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -953,9 +953,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" dependencies = [ "bitflags", "errno", @@ -1029,7 +1029,7 @@ checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1136,7 +1136,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1151,9 +1151,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.76" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -1190,7 +1190,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1287,7 +1287,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1574,5 +1574,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] From cc47dd126afab6fd3aa7cdbe1d8e4678c0ced475 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 11:13:26 -0700 Subject: [PATCH 31/40] Bump tests/perf/s2n-quic from `8f7c04b` to `1ff3a9c` (#3481) Bumps [tests/perf/s2n-quic](https://github.com/aws/s2n-quic) from `8f7c04b` to `1ff3a9c`.
Commits
  • 1ff3a9c chore: import 8/29 version (#2311)
  • b6ca57b test(s2n-quic-core): revert to bigger length for Kani test (#2312)
  • 24089b4 chore: release 1.45.0 (#2310)
  • a74e9cb fix(s2n-quic-transport): don't let cwnd drop below initial cwnd when MTU decr...
  • 9af5d19 docs(s2n-quic): fix link to compliance report in CI doc
  • acd3fe4 build(deps): update ring requirement from 0.16 to 0.17 (#2287)
  • 2869c19 build(deps): update lru requirement from 0.10 to 0.12 (#2286)
  • b3a10a1 fix(s2n-quic): ConfirmComplete with handshake de-duplication (#2307)
  • b4ba62d fix(s2n-quic-transport): wait until handshake is confirmed to start MTU probi...
  • See full diff in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/perf/s2n-quic | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/perf/s2n-quic b/tests/perf/s2n-quic index 8f7c04be292f..1ff3a9c8adee 160000 --- a/tests/perf/s2n-quic +++ b/tests/perf/s2n-quic @@ -1 +1 @@ -Subproject commit 8f7c04be292f6419b38e37bd7ff67f15833735d7 +Subproject commit 1ff3a9c8adee30346537698c7b2a12279facd2d3 From ec5bfc6a3dac88c4110b5e0e727b3bbd82215ab8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 21:21:30 +0200 Subject: [PATCH 32/40] Automatic toolchain upgrade to nightly-2024-09-02 (#3479) Update Rust toolchain from nightly-2024-09-01 to nightly-2024-09-02 without any other source changes. Co-authored-by: celinval <35149715+celinval@users.noreply.github.com> --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index ca038ea7a812..ae9b702c4c05 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,5 +2,5 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT [toolchain] -channel = "nightly-2024-09-01" +channel = "nightly-2024-09-02" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 2c939abcd956247676a5f727f5433e7a10a66cc1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 14:00:18 +0200 Subject: [PATCH 33/40] Automatic toolchain upgrade to nightly-2024-09-03 (#3482) Update Rust toolchain from nightly-2024-09-02 to nightly-2024-09-03 without any other source changes. Co-authored-by: celinval <35149715+celinval@users.noreply.github.com> --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index ae9b702c4c05..a9bc7f1dd533 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,5 +2,5 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT [toolchain] -channel = "nightly-2024-09-02" +channel = "nightly-2024-09-03" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From eb4d5a6e7523bc42e865c1f410569d3de0576e77 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Tue, 3 Sep 2024 10:10:24 -0400 Subject: [PATCH 34/40] RFC for List Subcommand (#3463) RFC for the `kani list` subcommand. I saw the comment [in the template](https://github.com/model-checking/kani/blob/main/rfc/src/template.md#software-design) to leave the Software Design section empty, but I was looking at the raw MD file and made the mistake of thinking it referred to all sections below the comment. In other words, I thought when it said "We recommend you to leave this empty for the first version of your RFC" it meant that everything below the comment should be empty (so Rationale, Open Questions, and Future Work) and not Software Design. I realized my mistake when I was making this PR, but I figured I may as well leave it since I already wrote it. I [opened a PR](https://github.com/model-checking/kani/pull/3462) to update the comment. Resolves [#2573](https://github.com/model-checking/kani/issues/2573), [#1612](#https://github.com/model-checking/kani/issues/1612). By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- rfc/src/SUMMARY.md | 1 + rfc/src/rfcs/0012-list.md | 175 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 rfc/src/rfcs/0012-list.md diff --git a/rfc/src/SUMMARY.md b/rfc/src/SUMMARY.md index 0b38dac9cbb5..c5a7161d0d45 100644 --- a/rfc/src/SUMMARY.md +++ b/rfc/src/SUMMARY.md @@ -17,3 +17,4 @@ - [0009-function-contracts](rfcs/0009-function-contracts.md) - [0010-quantifiers](rfcs/0010-quantifiers.md) - [0011-source-coverage](rfcs/0011-source-coverage.md) +- [0012-list](rfcs/0012-list.md) diff --git a/rfc/src/rfcs/0012-list.md b/rfc/src/rfcs/0012-list.md new file mode 100644 index 000000000000..fea4a65be7f3 --- /dev/null +++ b/rfc/src/rfcs/0012-list.md @@ -0,0 +1,175 @@ +- **Feature Name:** List Subcommand +- **Feature Request Issue:** [#2573](https://github.com/model-checking/kani/issues/2573), [#1612](https://github.com/model-checking/kani/issues/1612) +- **RFC PR:** #3463 +- **Status:** Under Review +- **Version:** 0 + +------------------- + +## Summary + +Add a subcommand `list` that, for each crate under verification, lists the information relevant to its verification. + +## User Impact + +Currently, there is no automated way for a user to gather metadata about Kani's integration with their project. If, for example, a user wants a list of harnesses for their project, they must search for all the relevant contract attributes (currently `#[proof]` or `#[proof_for_contract]`) themselves. If done manually, this process is tedious, especially for large projects. Even with a shell script, it is error-prone--if, for example, we introduce a new type of proof harness, users would have to account for it when searching their project. + +Internally, this feature will be useful for tracking our customers' use of Kani and our progress with standard library verification. Externally, users can leverage this feature to get a high-level view of which areas of their projects have harnesses (and, by extension, which areas are still in need of verification). + +This feature will not cause any regressions for exisiting users. + +## User Experience + +Users run a `list` subcommand, which prints metadata about the harnesses and contracts in each crate under verification. The subcommand will take the option `--message-format=[human|json]`, which changes the output format. The default is `human`, which prints to the terminal. The `json` option creates and writes to a JSON file instead. + +This subcommand will not fail. In the case that it does not find any harnesses or contracts, it will print a message informing the user of that fact. + +### Human Format + +The default format, `human`, will print the harnesses and contracts in a project, along with the total counts of each. + +For example: + +``` +Kani Version: 0.5.4 + +Standard Harnesses: +- example::verify::check_new +- example::verify::check_modify + +Contract Harnesses: +- example::verify::check_foo_u32 +- example::verify::check_foo_u64 +- example::verify::check_func +- example::verify::check_bar + +Contracts: +|--------------------------|-----------------------------------------------| +| Function | Contract Harnesses | +|--------------------------|-----------------------------------------------| +| example::impl::func | example::verify::check_func | +|--------------------------|-----------------------------------------------| +| example::impl::bar | example::verify::check_bar | +|--------------------------|-----------------------------------------------| +| example::impl::foo | example::verify::check_foo_u32, | +| | example::verify::check_foo_u64 | +|--------------------------|-----------------------------------------------| +| example::prep::parse | NONE | +|--------------------------|-----------------------------------------------| + +Totals: +- Standard Harnesses: 2 +- Contract Harnesses: 4 +- Functions with Contracts: 4 +- Contracts: 10 +``` + +A "Standard Harness" is a `#[proof]` harness, while a "Contract Harness" is a `#[proof_for_contract]` harness. + +### JSON Format + +As the name implies, the goal of the `human` output is to be friendly for human readers. If the user wants an output format that's more easily parsed by a script, they can use the `json` option. + +The JSON format will contain the same information as the human format, with the addition of file paths and file version. The file version will start at zero and increment whenever we make an update to the format. This way, any users relying on this format for their scripts can realize that changes have occurred and update their logic accordingly. + +For example: + +```json +{ + kani-version: 0.5.4, + file-version: 0, + standard-harnesses: [ + { + file: /Users/johnsmith/example/kani_standard_proofs.rs + harnesses: [ + example::verify::check_modify, + example::verify::check_new + ] + }, + ], + contract-harnesses: [ + { + file: /Users/johnsmith/example/kani_contract_proofs.rs + harnesses: [ + example::verify::check_bar, + example::verify::check_foo_u32, + example::verify::check_foo_u64, + example::verify::check_func + ] + }, + ], + contracts: [ + { + function: example::impl::func + file: /Users/johnsmith/example/impl.rs + harnesses: [example::verify::check_func] + }, + { + function: example::impl::bar + file: /Users/johnsmith/example/impl.rs + harnesses: [example::verify::check_bar] + }, + { + function: example::impl::foo + file: /Users/johnsmith/example/impl.rs + harnesses: [ + example::verify::check_foo_u32, + example::verify::check_foo_u64 + ] + }, + { + function: example::prep::parse + file: /Users/johnsmith/example/prep.rs + harnesses: [] + } + ], + totals: { + standard-harnesses: 2, + contract-harnesses: 4, + functions-with-contracts: 4, + contracts: 10, + } +} +``` + +## Software Design + +We will add a new subcommand to `kani-driver`. + +*We will update this section once the UX is finalized*. + +## Rationale and alternatives + +Users of Kani may have many questions about their project--not only where their contracts and harnesses are, but also where their stubs are, what kinds of contracts they have, etc. Rather than try to answer every question a user might have, which would make the output quite verbose, we focus on these four: + +1. Where are the harnesses? +2. Where are the contracts? +3. Which contracts are verified, and by which harnesses? +4. How many harnesses and contracts are there? + +We believe these questions are the most important for our use cases of tracking verification progress for customers and the standard library. The UX is designed to answer these questions clearly and concisely. + +We could have a more verbose or granular output, e.g., printing the metadata on a per-crate or per-module level, or including stubs or other attributes. Such a design would have the benefit of providing more information, with the disadvantage of being more complex to implement and more information for the user to process. + +If we do not implement this feature, users will have to obtain this metadata through manual searching, or by writing a script to do it themselves. This feature will improve our internal productivity by automating the process. + +## Open questions + +1. Do we want to include more contracts information? We could print more granular information about contracts, e.g., the text of the contracts, the number of `requires`, `ensures`, or `modifies` contracts, or the locations of the contracts. +2. More generally, we could introduce additional options that collect information about other Kani attributes (e.g., stubs). The default would be to leave them out, but this way a user could get more verbose output if they so choose. +3. Do we want to add a filtering option? For instance, `--harnesses ` and `--contracts `, where `pattern` corresponds to a Rust-style path. For example, `kani list --harnesses "my_crate::my_module::*"` would include all harnesses with that path prefix, while `kani list --contracts "my_crate::my_module::*"` would include all functions under contract with that path prefix. + +## Out of scope / Future Improvements + +It would be nice to differentiate between regular Kani harnesses and Bolero harnesses. Bolero harnesses invoke Kani using conditional compilation, e.g.: + +```rust +#[cfg_attr(kani, kani::proof)] +fn check() { + bolero::check!()... +} +``` + +See [this blog post](https://model-checking.github.io/kani-verifier-blog/2022/10/27/using-kani-with-the-bolero-property-testing-framework.html) for more information. + +There's no easy way for us to know whether a harness comes from Bolero, since Bolero takes care of rewriting the test to use Kani syntax and invoking the Kani engine. By the time the harness gets to Kani, there's no way for us to tell it apart from a regular harness. Fixing this would require some changes to our Bolero integration. \ No newline at end of file From 6cf4ea46924f17c148cc590fe99b40c6a5282a52 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Tue, 3 Sep 2024 13:12:22 -0400 Subject: [PATCH 35/40] Add tests for fixed issues. (#3484) #2239 and #3022 are resolved in Kani v0.5.4. Add tests for them. Resolves #2239 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- tests/expected/issue-2239/issue_2239.expected | 5 ++++ tests/expected/issue-2239/issue_2239.rs | 15 ++++++++++++ tests/expected/issue-3022/issue_3022.expected | 5 ++++ tests/expected/issue-3022/issue_3022.rs | 24 +++++++++++++++++++ 4 files changed, 49 insertions(+) create mode 100644 tests/expected/issue-2239/issue_2239.expected create mode 100644 tests/expected/issue-2239/issue_2239.rs create mode 100644 tests/expected/issue-3022/issue_3022.expected create mode 100644 tests/expected/issue-3022/issue_3022.rs diff --git a/tests/expected/issue-2239/issue_2239.expected b/tests/expected/issue-2239/issue_2239.expected new file mode 100644 index 000000000000..8bdab0df1862 --- /dev/null +++ b/tests/expected/issue-2239/issue_2239.expected @@ -0,0 +1,5 @@ +test_trivial_bounds.unreachable.1\ +- Status: FAILURE\ +- Description: "unreachable code" + +VERIFICATION:- FAILED \ No newline at end of file diff --git a/tests/expected/issue-2239/issue_2239.rs b/tests/expected/issue-2239/issue_2239.rs new file mode 100644 index 000000000000..36c41dec604d --- /dev/null +++ b/tests/expected/issue-2239/issue_2239.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#![feature(trivial_bounds)] +#![allow(unused, trivial_bounds)] + +#[kani::proof] +fn test_trivial_bounds() +where + i32: Iterator, +{ + for _ in 2i32 {} +} + +fn main() {} diff --git a/tests/expected/issue-3022/issue_3022.expected b/tests/expected/issue-3022/issue_3022.expected new file mode 100644 index 000000000000..9600182a5209 --- /dev/null +++ b/tests/expected/issue-3022/issue_3022.expected @@ -0,0 +1,5 @@ +main.assertion\ +- Status: SUCCESS\ +- Description: "assertion failed: inner == func2.inner" + +VERIFICATION:- SUCCESSFUL \ No newline at end of file diff --git a/tests/expected/issue-3022/issue_3022.rs b/tests/expected/issue-3022/issue_3022.rs new file mode 100644 index 000000000000..6ef6f944395f --- /dev/null +++ b/tests/expected/issue-3022/issue_3022.rs @@ -0,0 +1,24 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +type BuiltIn = for<'a> fn(&str); + +struct Function { + inner: BuiltIn, +} + +impl Function { + fn new(subr: BuiltIn) -> Self { + Self { inner: subr } + } +} + +fn dummy(_: &str) {} + +#[kani::proof] +fn main() { + let func1 = Function::new(dummy); + let func2 = Function::new(dummy); + let inner: fn(&'static _) -> _ = func1.inner; + assert!(inner == func2.inner); +} From edded56800e355867c439137e3de83b39b9252ad Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Tue, 3 Sep 2024 21:46:12 -0400 Subject: [PATCH 36/40] Update Kani Book (#3474) Add the following to the book: - **Reference section on contracts**. The point of this section is not to be an exhaustive explanation--we have good documentation on contracts already, and we don't want to maintain it in two places. The goal of the section is to give readers an intuition for the main idea and then direct them to more detailed resources. - **Add a reference to the blog**--it's a good resource to learn about Kani and it would be nice to have an easier way to find it. Inspired by [this post](https://users.rust-lang.org/t/can-somebody-explain-how-kani-verifier-works/113918). - **Move the crates documentation earlier.** This partly addresses [#3029](https://github.com/model-checking/kani/issues/3029) by at least making our crates documentation more visible in the book. (I don't have this PR as resolving that issue, since we would need to actually publish the docs on `crates.io` to do that.) By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- docs/src/SUMMARY.md | 5 +- docs/src/getting-started.md | 1 + docs/src/reference/experimental/contracts.md | 66 ++++++++++++++++++++ docs/src/reference/experimental/coverage.md | 2 +- 4 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 docs/src/reference/experimental/contracts.md diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 784ec075d183..d534cf1f7f22 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -7,6 +7,8 @@ - [Using Kani](./usage.md) - [Verification results](./verification-results.md) +- [Crates Documentation](./crates/index.md) + - [Tutorial](./kani-tutorial.md) - [First steps](./tutorial-first-steps.md) - [Failures that Kani can spot](./tutorial-kinds-of-failure.md) @@ -18,6 +20,7 @@ - [Experimental features](./reference/experimental/experimental-features.md) - [Coverage](./reference/experimental/coverage.md) - [Stubbing](./reference/experimental/stubbing.md) + - [Contracts](./reference/experimental/contracts.md) - [Concrete Playback](./reference/experimental/concrete-playback.md) - [Application](./application.md) - [Comparison with other tools](./tool-comparison.md) @@ -45,8 +48,6 @@ - [Unstable features](./rust-feature-support/unstable.md) - [Overrides](./overrides.md) -- [Crates Documentation](./crates/index.md) - --- - [FAQ](./faq.md) diff --git a/docs/src/getting-started.md b/docs/src/getting-started.md index 5de7ae431850..1acfd039ce97 100644 --- a/docs/src/getting-started.md +++ b/docs/src/getting-started.md @@ -15,6 +15,7 @@ Proof harnesses are similar to test harnesses, especially property-based test ha Kani is currently under active development. Releases are published [here](https://github.com/model-checking/kani/releases). Major changes to Kani are documented in the [RFC Book](https://model-checking.github.io/kani/rfc). +We also publish updates on Kani use cases and features on our [blog](https://model-checking.github.io/kani-verifier-blog/). There is support for a fair amount of Rust language features, but not all (e.g., concurrency). Please see [Limitations](./limitations.md) for a detailed list of supported features. diff --git a/docs/src/reference/experimental/contracts.md b/docs/src/reference/experimental/contracts.md new file mode 100644 index 000000000000..fd4e8169bc34 --- /dev/null +++ b/docs/src/reference/experimental/contracts.md @@ -0,0 +1,66 @@ +# Contracts + +Consider the following example: + +```rust +fn gcd(mut max: u8, mut min: u8) -> u8 { + if min > max { + std::mem::swap(&mut max, &mut min); + } + + let rest = max % min; + if rest == 0 { min } else { gcd(min, rest) } +} +``` +Let's assume we want to verify some code that calls `gcd`. +In the [worst case](https://en.wikipedia.org/wiki/Euclidean_algorithm#Worst-case), the number of steps (recursions) in `gcd` approaches 1.5 times the number of bits needed to represent the input numbers. +So, for two large 64-bit numbers, a single call to `gcd` can take almost 96 iterations. +It would be very expensive for Kani to unroll each of these iterations and then perform symbolic execution. + +Instead, we can write *contracts* with guarantees about `gcd`'s behavior. +Once Kani verifies that `gcd`'s contracts are correct, it can replace each invocation of `gcd` with its contracts, which reduces verification time for `gcd`'s callers. +For example, perhaps we want to ensure that the returned `result` does indeed divide both `max` and `min`. +In that case, we could write contracts like these: + +```rust +#[kani::requires(min != 0 && max != 0)] +#[kani::ensures(|result| *result != 0 && max % *result == 0 && min % *result == 0)] +#[kani::recursion] +fn gcd(mut max: u8, mut min: u8) -> u8 { ... } +``` + +Since `gcd` performs `max % min` (and perhaps swaps those values), passing zero as an argument could cause a division by zero. +The `requires` contract tells Kani to restrict the range of nondeterministic inputs to nonzero ones so that we don't run into this error. +The `ensures` contract is what actually checks that the result is a correct divisor for the inputs. +(The `recursion` attribute is required when using contracts on recursive functions). + +Then, we would write a harness to *verify* those contracts, like so: + +```rust +#[kani::proof_for_contract(gcd)] +fn check_gcd() { + let max: u8 = kani::any(); + let min: u8 = kani::any(); + gcd(max, min); +} +``` + +and verify it by running `kani -Z function-contracts`. + +Once Kani verifies the contracts, we can use Kani's [stubbing feature](stubbing.md) to replace all invocations to `gcd` with its contracts, for instance: + +```rust +// Assume foo() invokes gcd(). +// By using stub_verified, we tell Kani to replace +// invocations of gcd() with its verified contracts. +#[kani::proof] +#[kani::stub_verified(gcd)] +fn check_foo() { + let x: u8 = kani::any(); + foo(x); +} +``` +By leveraging the stubbing feature, we can replace the (expensive) `gcd` call with a *verified abstraction* of its behavior, greatly reducing verification time for `foo`. + +There is far more to learn about contracts. +We highly recommend reading our [blog post about contracts](https://model-checking.github.io/kani-verifier-blog/2024/01/29/function-contracts.html) (from which this `gcd` example is taken). We also recommend looking at the `contracts` module in our [documentation](../../crates/index.md). diff --git a/docs/src/reference/experimental/coverage.md b/docs/src/reference/experimental/coverage.md index fb73d5f7c05b..5e536b3992b6 100644 --- a/docs/src/reference/experimental/coverage.md +++ b/docs/src/reference/experimental/coverage.md @@ -1,4 +1,4 @@ -## Coverage +# Coverage Recall our `estimate_size` example from [First steps](../../tutorial-first-steps.md), where we wrote a proof harness constraining the range of inputs to integers less than 4096: From 93a29af23c569c51ddc56d1a2d22bec1d21e01c2 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Tue, 3 Sep 2024 23:00:36 -0400 Subject: [PATCH 37/40] Cross-function union instrumentation (#3465) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR introduces support for memory initialization checks for unions passed across the function boundary. Whenever a union is passed as an argument, we need to make sure that its initialization state is preserved. Unlike pointers, unions do not have a stable memory address which could identify them in shadow memory. Hence, we need to pass extra information across function boundary since unions are passed “by value”. We introduce a global variable to store the previous address of unions passed as function arguments, which allows us to effectively tie the initialization state of unions passed between functions. This struct is written to by the caller and read from by the callee. For more information about planned functionality, see https://github.com/model-checking/kani/issues/3300 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../kani_middle/transform/check_uninit/mod.rs | 68 ++++++++- .../check_uninit/ptr_uninit/uninit_visitor.rs | 132 +++++++++++++----- .../check_uninit/relevant_instruction.rs | 99 ++++++------- .../transform/check_uninit/ty_layout.rs | 59 ++++++-- .../kani_middle/transform/kani_intrinsics.rs | 8 +- library/kani_core/src/mem_init.rs | 81 ++++++++++- tests/expected/uninit/fixme_unions.rs | 58 +++++++- tests/expected/uninit/unions.expected | 22 ++- tests/expected/uninit/unions.rs | 109 +++++++++++++++ 9 files changed, 516 insertions(+), 120 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 0130c00d1a71..726dc1965770 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -45,6 +45,9 @@ const KANI_IS_STR_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniIsStrPtrInitialized"; const KANI_SET_STR_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniSetStrPtrInitialized"; const KANI_COPY_INIT_STATE_DIAGNOSTIC: &str = "KaniCopyInitState"; const KANI_COPY_INIT_STATE_SINGLE_DIAGNOSTIC: &str = "KaniCopyInitStateSingle"; +const KANI_LOAD_ARGUMENT_DIAGNOSTIC: &str = "KaniLoadArgument"; +const KANI_STORE_ARGUMENT_DIAGNOSTIC: &str = "KaniStoreArgument"; +const KANI_RESET_ARGUMENT_BUFFER_DIAGNOSTIC: &str = "KaniResetArgumentBuffer"; // Function bodies of those functions will not be instrumented as not to cause infinite recursion. const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = &[ @@ -58,6 +61,9 @@ const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = &[ KANI_SET_STR_PTR_INITIALIZED_DIAGNOSTIC, KANI_COPY_INIT_STATE_DIAGNOSTIC, KANI_COPY_INIT_STATE_SINGLE_DIAGNOSTIC, + KANI_LOAD_ARGUMENT_DIAGNOSTIC, + KANI_STORE_ARGUMENT_DIAGNOSTIC, + KANI_RESET_ARGUMENT_BUFFER_DIAGNOSTIC, ]; /// Instruments the code with checks for uninitialized memory, agnostic to the source of targets. @@ -159,9 +165,9 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { // Calculate pointee layout for byte-by-byte memory initialization checks. match PointeeInfo::from_ty(pointee_ty) { Ok(type_info) => type_info, - Err(_) => { + Err(reason) => { let reason = format!( - "Kani currently doesn't support checking memory initialization for pointers to `{pointee_ty}.", + "Kani currently doesn't support checking memory initialization for pointers to `{pointee_ty}. {reason}", ); self.inject_assert_false(self.tcx, body, source, operation.position(), &reason); return; @@ -185,6 +191,9 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { MemoryInitOp::AssignUnion { .. } => { self.build_assign_union(body, source, operation, pointee_info) } + MemoryInitOp::StoreArgument { .. } | MemoryInitOp::LoadArgument { .. } => { + self.build_argument_operation(body, source, operation, pointee_info) + } MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => { unreachable!() } @@ -532,6 +541,61 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { ); } + /// Instrument the code to pass information about arguments containing unions. Whenever a + /// function is called and some of the arguments contain unions, we store the information. And + /// when we enter the callee, we load the information. + fn build_argument_operation( + &mut self, + body: &mut MutableBody, + source: &mut SourceInstruction, + operation: MemoryInitOp, + pointee_info: PointeeInfo, + ) { + let ret_place = Place { + local: body.new_local(Ty::new_tuple(&[]), source.span(body.blocks()), Mutability::Not), + projection: vec![], + }; + let mut statements = vec![]; + let layout_size = pointee_info.layout().maybe_size().unwrap(); + let diagnostic = match operation { + MemoryInitOp::LoadArgument { .. } => KANI_LOAD_ARGUMENT_DIAGNOSTIC, + MemoryInitOp::StoreArgument { .. } => KANI_STORE_ARGUMENT_DIAGNOSTIC, + _ => unreachable!(), + }; + let argument_operation_instance = resolve_mem_init_fn( + get_mem_init_fn_def(self.tcx, diagnostic, &mut self.mem_init_fn_cache), + layout_size, + *pointee_info.ty(), + ); + let operand = operation.mk_operand(body, &mut statements, source); + let argument_no = operation.expect_argument_no(); + let terminator = Terminator { + kind: TerminatorKind::Call { + func: Operand::Copy(Place::from(body.new_local( + argument_operation_instance.ty(), + source.span(body.blocks()), + Mutability::Not, + ))), + args: vec![ + operand, + Operand::Constant(ConstOperand { + span: source.span(body.blocks()), + user_ty: None, + const_: MirConst::try_from_uint(argument_no as u128, UintTy::Usize) + .unwrap(), + }), + ], + destination: ret_place.clone(), + target: Some(0), // this will be overriden in add_bb + unwind: UnwindAction::Terminate, + }, + span: source.span(body.blocks()), + }; + + // Construct the basic block and insert it into the body. + body.insert_bb(BasicBlock { statements, terminator }, source, operation.position()); + } + /// Copy memory initialization state from one union variable to another. fn build_assign_union( &mut self, diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs index 207005dafb27..bd4356017b6b 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs @@ -9,8 +9,8 @@ use crate::{ body::{InsertPosition, MutableBody, SourceInstruction}, check_uninit::{ relevant_instruction::{InitRelevantInstruction, MemoryInitOp}, - ty_layout::tys_layout_compatible_to_size, - TargetFinder, + ty_layout::{tys_layout_compatible_to_size, LayoutComputationError}, + PointeeInfo, TargetFinder, }, }, }; @@ -38,11 +38,38 @@ impl TargetFinder for CheckUninitVisitor { fn find_all(mut self, body: &MutableBody) -> Vec { self.locals = body.locals().to_vec(); for (bb_idx, bb) in body.blocks().iter().enumerate() { - self.current_target = InitRelevantInstruction { - source: SourceInstruction::Statement { idx: 0, bb: bb_idx }, - before_instruction: vec![], - after_instruction: vec![], + // Set the first target to start iterating from. + self.current_target = if !bb.statements.is_empty() { + InitRelevantInstruction { + source: SourceInstruction::Statement { idx: 0, bb: bb_idx }, + before_instruction: vec![], + after_instruction: vec![], + } + } else { + InitRelevantInstruction { + source: SourceInstruction::Terminator { bb: bb_idx }, + before_instruction: vec![], + after_instruction: vec![], + } }; + if bb_idx == 0 { + let union_args: Vec<_> = body + .locals() + .iter() + .enumerate() + .skip(1) + .take(body.arg_count()) + .filter(|(_, local)| local.ty.kind().is_union()) + .collect(); + if !union_args.is_empty() { + for (idx, _) in union_args { + self.push_target(MemoryInitOp::LoadArgument { + operand: Operand::Copy(Place { local: idx, projection: vec![] }), + argument_no: idx, + }) + } + } + } self.visit_basic_block(bb); } self.targets @@ -84,6 +111,22 @@ impl MirVisitor for CheckUninitVisitor { StatementKind::Assign(place, rvalue) => { // First check rvalue. self.visit_rvalue(rvalue, location); + + // Preemptively check if we do not support computing the layout of a type because of + // inner union fields. This allows to inject `assert!(false)` early. + if let Err(reason @ LayoutComputationError::UnionAsField(_)) = + PointeeInfo::from_ty(place.ty(&self.locals).unwrap()) + { + self.push_target(MemoryInitOp::Unsupported { + reason: format!( + "Checking memory initialization of type {} is not supported. {}", + place.ty(&self.locals).unwrap(), + reason + ), + }); + return; + } + // 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 @@ -127,9 +170,10 @@ impl MirVisitor for CheckUninitVisitor { // if a union as a subfield is detected, `assert!(false)` will be injected from // the type layout code. let is_inside_union = { - let mut contains_union = false; let mut place_to_add_projections = Place { local: place.local, projection: vec![] }; + let mut contains_union = + place_to_add_projections.ty(&self.locals).unwrap().kind().is_union(); for projection_elem in place.projection.iter() { if place_to_add_projections.ty(&self.locals).unwrap().kind().is_union() { contains_union = true; @@ -143,35 +187,37 @@ impl MirVisitor for CheckUninitVisitor { // Need to copy some information about union initialization, since lvalue is // either a union or a field inside a union. if is_inside_union { - if let Rvalue::Use(operand) = rvalue { - // This is a union-to-union assignment, so we need to copy the - // initialization state. - if place.ty(&self.locals).unwrap().kind().is_union() { - self.push_target(MemoryInitOp::AssignUnion { - lvalue: place.clone(), - rvalue: operand.clone(), - }); - } else { - // This is assignment to a field of a union. - self.push_target(MemoryInitOp::SetRef { - operand: Operand::Copy(place.clone()), - value: true, - position: InsertPosition::After, - }); + match rvalue { + Rvalue::Use(operand) => { + // This is a union-to-union assignment, so we need to copy the + // initialization state. + if place.ty(&self.locals).unwrap().kind().is_union() { + self.push_target(MemoryInitOp::AssignUnion { + lvalue: place.clone(), + rvalue: operand.clone(), + }); + } else { + // This is assignment to a field of a union. + self.push_target(MemoryInitOp::SetRef { + operand: Operand::Copy(place.clone()), + value: true, + position: InsertPosition::After, + }); + } } - } - } - - // Create a union from scratch as an aggregate. We handle it here because we - // need to know which field is getting assigned. - if let Rvalue::Aggregate(AggregateKind::Adt(adt_def, _, _, _, union_field), _) = - rvalue - { - if adt_def.kind() == AdtKind::Union { - self.push_target(MemoryInitOp::CreateUnion { - operand: Operand::Copy(place.clone()), - field: union_field.unwrap(), // Safe to unwrap because we know this is a union. - }); + Rvalue::Aggregate(AggregateKind::Adt(adt_def, _, _, _, union_field), _) => { + // Create a union from scratch as an aggregate. We handle it here because we + // need to know which field is getting assigned. + if adt_def.kind() == AdtKind::Union { + self.push_target(MemoryInitOp::CreateUnion { + operand: Operand::Copy(place.clone()), + field: union_field.unwrap(), // Safe to unwrap because we know this is a union. + }); + } + } + // TODO: add support for Rvalue::Cast, etc. + _ => self + .push_target(MemoryInitOp::Unsupported { reason: "Performing a union assignment with a non-supported construct as an Rvalue".to_string() }), } } } @@ -218,7 +264,7 @@ impl MirVisitor for CheckUninitVisitor { before_instruction: vec![], }; } else { - unreachable!() + // The only instruction in this basic block is the terminator, which was already set. } // Leave it as an exhaustive match to be notified when a new kind is added. match &term.kind { @@ -340,6 +386,20 @@ impl MirVisitor for CheckUninitVisitor { } _ => {} } + } else { + let union_args: Vec<_> = args + .iter() + .enumerate() + .filter(|(_, arg)| arg.ty(&self.locals).unwrap().kind().is_union()) + .collect(); + if !union_args.is_empty() { + for (idx, operand) in union_args { + self.push_target(MemoryInitOp::StoreArgument { + operand: operand.clone(), + argument_no: idx + 1, // since arguments are 1-indexed + }) + } + } } } _ => {} diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs b/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs index 6120f2d3d4c4..9ad23071df4a 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs @@ -16,65 +16,38 @@ 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 `sizeof(operand)` bytes. - Check { - operand: Operand, - }, + Check { operand: Operand }, /// Set memory initialization state of data bytes in a memory region starting from the pointer /// `operand` and of length `sizeof(operand)` bytes. - Set { - operand: Operand, - value: bool, - position: InsertPosition, - }, + Set { operand: Operand, value: bool, position: InsertPosition }, /// Check memory initialization of data bytes in a memory region starting from the pointer /// `operand` and of length `count * sizeof(operand)` bytes. - CheckSliceChunk { - operand: Operand, - count: Operand, - }, + CheckSliceChunk { 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. - SetSliceChunk { - operand: Operand, - count: Operand, - value: bool, - position: InsertPosition, - }, + SetSliceChunk { operand: Operand, count: Operand, value: bool, position: InsertPosition }, /// Set memory initialization of data bytes in a memory region starting from the reference to /// `operand` and of length `sizeof(operand)` bytes. - CheckRef { - operand: Operand, - }, + CheckRef { operand: Operand }, /// Set memory initialization of data bytes in a memory region starting from the reference to /// `operand` and of length `sizeof(operand)` bytes. - SetRef { - operand: Operand, - value: bool, - position: InsertPosition, - }, + SetRef { operand: Operand, value: bool, position: InsertPosition }, /// Unsupported memory initialization operation. - Unsupported { - reason: String, - }, + Unsupported { reason: String }, /// Operation that trivially accesses uninitialized memory, results in injecting `assert!(false)`. - TriviallyUnsafe { - reason: String, - }, - /// Operation that copies memory initialization state over to another operand. - Copy { - from: Operand, - to: Operand, - count: Operand, - }, - - AssignUnion { - lvalue: Place, - rvalue: Operand, - }, - CreateUnion { - operand: Operand, - field: FieldIdx, - }, + TriviallyUnsafe { reason: String }, + /// Copy memory initialization state over to another operand. + Copy { from: Operand, to: Operand, count: Operand }, + /// Copy memory initialization state over from one union variable to another. + AssignUnion { lvalue: Place, rvalue: Operand }, + /// Create a union from scratch with a given field index and store it in the provided operand. + CreateUnion { operand: Operand, field: FieldIdx }, + /// Load argument containing a union from the argument buffer together if the argument number + /// provided matches. + LoadArgument { operand: Operand, argument_no: usize }, + /// Store argument containing a union into the argument buffer together with the argument number + /// provided. + StoreArgument { operand: Operand, argument_no: usize }, } impl MemoryInitOp { @@ -94,7 +67,9 @@ impl MemoryInitOp { | MemoryInitOp::SetSliceChunk { operand, .. } => operand.clone(), MemoryInitOp::CheckRef { operand, .. } | MemoryInitOp::SetRef { operand, .. } - | MemoryInitOp::CreateUnion { operand, .. } => { + | MemoryInitOp::CreateUnion { operand, .. } + | MemoryInitOp::LoadArgument { operand, .. } + | MemoryInitOp::StoreArgument { operand, .. } => { mk_ref(operand, body, statements, source) } MemoryInitOp::Copy { .. } @@ -141,7 +116,9 @@ impl MemoryInitOp { | MemoryInitOp::SetSliceChunk { operand, .. } => operand.ty(body.locals()).unwrap(), MemoryInitOp::SetRef { operand, .. } | MemoryInitOp::CheckRef { operand, .. } - | MemoryInitOp::CreateUnion { operand, .. } => { + | MemoryInitOp::CreateUnion { operand, .. } + | MemoryInitOp::LoadArgument { operand, .. } + | MemoryInitOp::StoreArgument { operand, .. } => { let place = match operand { Operand::Copy(place) | Operand::Move(place) => place, Operand::Constant(_) => unreachable!(), @@ -188,7 +165,9 @@ impl MemoryInitOp { | MemoryInitOp::CreateUnion { .. } | MemoryInitOp::AssignUnion { .. } | MemoryInitOp::Unsupported { .. } - | MemoryInitOp::TriviallyUnsafe { .. } => unreachable!(), + | MemoryInitOp::TriviallyUnsafe { .. } + | MemoryInitOp::StoreArgument { .. } + | MemoryInitOp::LoadArgument { .. } => unreachable!(), } } @@ -204,7 +183,9 @@ impl MemoryInitOp { | MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } | MemoryInitOp::Copy { .. } - | MemoryInitOp::AssignUnion { .. } => unreachable!(), + | MemoryInitOp::AssignUnion { .. } + | MemoryInitOp::StoreArgument { .. } + | MemoryInitOp::LoadArgument { .. } => unreachable!(), } } @@ -220,7 +201,9 @@ impl MemoryInitOp { | MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } | MemoryInitOp::Copy { .. } - | MemoryInitOp::AssignUnion { .. } => None, + | MemoryInitOp::AssignUnion { .. } + | MemoryInitOp::StoreArgument { .. } + | MemoryInitOp::LoadArgument { .. } => None, } } @@ -233,12 +216,22 @@ impl MemoryInitOp { | MemoryInitOp::CheckSliceChunk { .. } | MemoryInitOp::CheckRef { .. } | MemoryInitOp::Unsupported { .. } - | MemoryInitOp::TriviallyUnsafe { .. } => InsertPosition::Before, + | MemoryInitOp::TriviallyUnsafe { .. } + | MemoryInitOp::StoreArgument { .. } + | MemoryInitOp::LoadArgument { .. } => InsertPosition::Before, MemoryInitOp::Copy { .. } | MemoryInitOp::AssignUnion { .. } | MemoryInitOp::CreateUnion { .. } => InsertPosition::After, } } + + pub fn expect_argument_no(&self) -> usize { + match self { + MemoryInitOp::LoadArgument { argument_no, .. } + | MemoryInitOp::StoreArgument { argument_no, .. } => *argument_no, + _ => unreachable!(), + } + } } /// Represents an instruction in the source code together with all memory initialization checks/sets 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 dac130f11545..8a9e3ac094d5 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,11 +3,12 @@ // //! Utility functions that help calculate type layout. +use std::fmt::Display; + use stable_mir::{ abi::{FieldsShape, Scalar, TagEncoding, ValueAbi, VariantsShape}, target::{MachineInfo, MachineSize}, ty::{AdtKind, IndexedVal, RigidTy, Ty, TyKind, UintTy, VariantIdx}, - CrateDef, }; /// Represents a chunk of data bytes in a data structure. @@ -68,8 +69,41 @@ pub struct PointeeInfo { layout: PointeeLayout, } +/// Different layout computation errors that could arise from the currently unsupported constructs. +pub enum LayoutComputationError { + UnknownUnsizedLayout(Ty), + EnumWithNicheEncoding(Ty), + EnumWithMultiplePaddingVariants(Ty), + UnsupportedType(Ty), + UnionAsField(Ty), +} + +impl Display for LayoutComputationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LayoutComputationError::UnknownUnsizedLayout(ty) => { + write!(f, "Cannot determine layout for an unsized type {ty}") + } + LayoutComputationError::EnumWithNicheEncoding(ty) => { + write!(f, "Cannot determine layout for an Enum with niche encoding of type {ty}") + } + LayoutComputationError::EnumWithMultiplePaddingVariants(ty) => write!( + f, + "Cannot determine layout for an Enum of type {ty}, as it has multiple variants that have different padding." + ), + LayoutComputationError::UnsupportedType(ty) => { + write!(f, "Cannot determine layout for an unsupported type {ty}.") + } + LayoutComputationError::UnionAsField(ty) => write!( + f, + "Cannot determine layout for a type that contains union of type {ty} as a field." + ), + } + } +} + impl PointeeInfo { - pub fn from_ty(ty: Ty) -> Result { + pub fn from_ty(ty: Ty) -> Result { match ty.kind() { TyKind::RigidTy(rigid_ty) => match rigid_ty { RigidTy::Adt(adt_def, args) if adt_def.kind() == AdtKind::Union => { @@ -120,7 +154,7 @@ impl PointeeInfo { }; Ok(PointeeInfo { pointee_ty: ty, layout }) } else { - Err(format!("Cannot determine type layout for type `{ty}`")) + Err(LayoutComputationError::UnknownUnsizedLayout(ty)) } } }, @@ -144,7 +178,7 @@ fn data_bytes_for_ty( machine_info: &MachineInfo, ty: Ty, current_offset: usize, -) -> Result, String> { +) -> Result, LayoutComputationError> { let layout = ty.layout().unwrap().shape(); match layout.fields { @@ -201,9 +235,7 @@ fn data_bytes_for_ty( VariantsShape::Multiple { tag_encoding: TagEncoding::Niche { .. }, .. - } => { - Err(format!("Unsupported Enum `{}` check", def.trimmed_name()))? - } + } => Err(LayoutComputationError::EnumWithNicheEncoding(ty)), VariantsShape::Multiple { variants, tag, .. } => { // Retrieve data bytes for the tag. let tag_size = match tag { @@ -273,10 +305,11 @@ fn data_bytes_for_ty( } else { // Struct has multiple padding variants, Kani cannot // differentiate between them. - Err(format!( - "Unsupported Enum `{}` check", - def.trimmed_name() - )) + Err( + LayoutComputationError::EnumWithMultiplePaddingVariants( + ty, + ), + ) } } } @@ -362,10 +395,10 @@ fn data_bytes_for_ty( | RigidTy::Coroutine(_, _, _) | RigidTy::CoroutineWitness(_, _) | RigidTy::Foreign(_) - | RigidTy::Dynamic(_, _, _) => Err(format!("Unsupported {ty:?}")), + | RigidTy::Dynamic(_, _, _) => Err(LayoutComputationError::UnsupportedType(ty)), } } - FieldsShape::Union(_) => Err(format!("Unions as fields of unions are unsupported {ty:?}")), + FieldsShape::Union(_) => Err(LayoutComputationError::UnionAsField(ty)), FieldsShape::Array { .. } => Ok(vec![]), } } diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index 9210c43fb163..5a026db525a7 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -195,9 +195,9 @@ impl IntrinsicGeneratorPass { let arg_ty = new_body.locals()[1].ty; // Sanity check: since CBMC memory object primitives only accept pointers, need to // ensure the correct type. - let TyKind::RigidTy(RigidTy::RawPtr(target_ty, _)) = arg_ty.kind() else { unreachable!() }; + let TyKind::RigidTy(RigidTy::RawPtr(pointee_ty, _)) = arg_ty.kind() else { unreachable!() }; // Calculate pointee layout for byte-by-byte memory initialization checks. - let pointee_info = PointeeInfo::from_ty(target_ty); + let pointee_info = PointeeInfo::from_ty(pointee_ty); match pointee_info { Ok(pointee_info) => { match pointee_info.layout() { @@ -338,7 +338,7 @@ impl IntrinsicGeneratorPass { } }; } - Err(msg) => { + Err(reason) => { // We failed to retrieve the type layout. let rvalue = Rvalue::Use(Operand::Constant(ConstOperand { const_: MirConst::from_bool(false), @@ -348,7 +348,7 @@ impl IntrinsicGeneratorPass { let result = new_body.insert_assignment(rvalue, &mut source, InsertPosition::Before); let reason = format!( - "Kani currently doesn't support checking memory initialization of `{target_ty}`. {msg}" + "Kani currently doesn't support checking memory initialization for pointers to `{pointee_ty}. {reason}", ); new_body.insert_check( tcx, diff --git a/library/kani_core/src/mem_init.rs b/library/kani_core/src/mem_init.rs index cec23c14263b..6475c62e31ae 100644 --- a/library/kani_core/src/mem_init.rs +++ b/library/kani_core/src/mem_init.rs @@ -22,6 +22,13 @@ #[allow(clippy::crate_in_macro_def)] macro_rules! kani_mem_init { ($core:path) => { + /// Global object for tracking memory initialization state. + #[rustc_diagnostic_item = "KaniMemoryInitializationState"] + static mut MEM_INIT_STATE: MemoryInitializationState = MemoryInitializationState::new(); + + /// Global object for tracking union initialization state across function boundaries. + static mut ARGUMENT_BUFFER: Option = None; + /// Bytewise mask, representing which bytes of a type are data and which are padding. /// For example, for a type like this: /// ``` @@ -97,6 +104,7 @@ macro_rules! kani_mem_init { /// Copy memory initialization state by non-deterministically switching the tracked object and /// adjusting the tracked offset. + #[kanitool::disable_checks(pointer)] pub fn copy( &mut self, from_ptr: *const u8, @@ -119,6 +127,24 @@ macro_rules! kani_mem_init { self.tracked_offset += to_offset - from_offset; // Note that this preserves the value. } + } else { + self.bless::(to_ptr, 1); + } + } + + /// Set currently tracked memory initialization state to `true` if `ptr` points to the + /// currently tracked object and the tracked offset lies within `LAYOUT_SIZE * num_elts` + /// bytes of `ptr`. + #[kanitool::disable_checks(pointer)] + pub fn bless(&mut self, ptr: *const u8, num_elts: usize) { + let obj = super::mem::pointer_object(ptr); + let offset = super::mem::pointer_offset(ptr); + + if self.tracked_object_id == obj + && self.tracked_offset >= offset + && self.tracked_offset < offset + num_elts * LAYOUT_SIZE + { + self.value = true; } } @@ -172,10 +198,6 @@ macro_rules! kani_mem_init { } } - /// Global object for tracking memory initialization state. - #[rustc_diagnostic_item = "KaniMemoryInitializationState"] - static mut MEM_INIT_STATE: MemoryInitializationState = MemoryInitializationState::new(); - /// Set tracked object and tracked offset to a non-deterministic value. #[kanitool::disable_checks(pointer)] #[rustc_diagnostic_item = "KaniInitializeMemoryInitializationState"] @@ -343,5 +365,56 @@ macro_rules! kani_mem_init { fn copy_init_state_single(from: *const T, to: *const T) { copy_init_state::(from, to, 1); } + + /// Information about currently tracked argument, used for passing union initialization + /// state across function boundaries. This struct is written to by the caller and read from + /// by the callee. + #[derive(Clone, Copy)] + struct ArgumentBuffer { + selected_argument: usize, + saved_address: *const (), + layout_size: usize, + } + + /// Non-deterministically store information about currently tracked argument in the argument + /// buffer. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniStoreArgument"] + fn store_argument(from: *const T, selected_argument: usize) { + let (from_ptr, _) = from.to_raw_parts(); + let should_store: bool = super::any(); + if should_store { + unsafe { + ARGUMENT_BUFFER = Some(ArgumentBuffer { + selected_argument, + saved_address: from_ptr, + layout_size: LAYOUT_SIZE, + }) + } + } + } + + /// Load information from the argument buffer (if the argument position matches) via copying + /// the memory initialization information from an address in the caller to an address in the + /// callee. Otherwise, mark that the argument as initialized, as it will be checked by + /// another non-deterministic branch. Reset the argument buffer after loading from it. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniLoadArgument"] + fn load_argument(to: *const T, selected_argument: usize) { + let (to_ptr, _) = to.to_raw_parts(); + if let Some(buffer) = unsafe { ARGUMENT_BUFFER } { + if buffer.selected_argument == selected_argument { + assert!(buffer.layout_size == LAYOUT_SIZE); + copy_init_state_single::(buffer.saved_address, to_ptr); + unsafe { + ARGUMENT_BUFFER = None; + } + return; + } + } + unsafe { + MEM_INIT_STATE.bless::(to_ptr as *const u8, 1); + } + } }; } diff --git a/tests/expected/uninit/fixme_unions.rs b/tests/expected/uninit/fixme_unions.rs index ec86e5e7e07f..b0e7d600187d 100644 --- a/tests/expected/uninit/fixme_unions.rs +++ b/tests/expected/uninit/fixme_unions.rs @@ -14,14 +14,13 @@ union U { b: u32, } -/// Reading padding data via simple union access if union is passed to another function. +/// Reading non-padding data but a union is behind a pointer. #[kani::proof] -unsafe fn cross_function_union_should_fail() { - unsafe fn helper(u: U) { - let padding = u.b; // Read 4 bytes from `u`. - } +unsafe fn pointer_union_should_pass() { let u = U { a: 0 }; // `u` is initialized for 2 bytes. - helper(u); + let u_ptr = addr_of!(u); + let u1 = *u_ptr; + let padding = u1.a; // Read 2 bytes from `u`. } /// Reading padding data but a union is behind a pointer. @@ -48,14 +47,59 @@ unsafe fn union_as_subfields_should_pass() { let padding = u1.a; // Read 2 bytes from `u`. } +/// Tests initialized access if unions are top-level subfields. +#[kani::proof] +unsafe fn union_as_subfields_should_fail() { + let u = U { a: 0 }; // `u` is initialized for 2 bytes. + let s = S { u }; + let s1 = s; + let u1 = s1.u; // `u1` is initialized for 2 bytes. + let padding = u1.b; // Read 4 bytes from `u`. +} + union Outer { u: U, a: u32, } -/// Tests unions composing with other unions. +/// Tests unions composing with other unions and reading non-padding data. #[kani::proof] unsafe fn uber_union_should_pass() { let u = Outer { u: U { b: 0 } }; // `u` is initialized for 4 bytes. let non_padding = u.a; // Read 4 bytes from `u`. } + +/// Tests unions composing with other unions and reading padding data. +#[kani::proof] +unsafe fn uber_union_should_fail() { + let u = Outer { u: U { a: 0 } }; // `u` is initialized for 2 bytes. + let padding = u.a; // Read 4 bytes from `u`. +} + +/// Attempting to read initialized data via transmuting a union. +#[kani::proof] +unsafe fn transmute_union_should_pass() { + let u = U { b: 0 }; // `u` is initialized for 4 bytes. + let non_padding: u32 = std::mem::transmute(u); // Transmute `u` into a value of 4 bytes. +} + +/// Attempting to read uninitialized data via transmuting a union. +#[kani::proof] +unsafe fn transmute_union_should_fail() { + let u = U { a: 0 }; // `u` is initialized for 2 bytes. + let padding: u32 = std::mem::transmute(u); // Transmute `u` into a value of 4 bytes. +} + +/// Attempting to transmute into union and read initialized data. +#[kani::proof] +unsafe fn transmute_into_union_should_pass() { + let u: U = std::mem::transmute(0u32); // `u` is initialized for 4 bytes. + let non_padding = u.b; // Read 4 bytes from `u`. +} + +/// Attempting to transmute into union and read uninitialized data. +#[kani::proof] +unsafe fn transmute_into_union_should_fail() { + let u: U = std::mem::transmute_copy(&0u16); // `u` is initialized for 2 bytes. + let padding = u.b; // Read 4 bytes from `u`. +} diff --git a/tests/expected/uninit/unions.expected b/tests/expected/uninit/unions.expected index 05fdac3a8765..ca7f777c4065 100644 --- a/tests/expected/uninit/unions.expected +++ b/tests/expected/uninit/unions.expected @@ -10,8 +10,28 @@ basic_union_should_fail.assertion.1\ - Status: FAILURE\ - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u32`" +cross_function_union_should_fail::helper.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u32`" + +cross_function_multi_union_should_fail::helper.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u32`" + +multi_cross_function_union_should_fail::sub_helper.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u32`" + +basic_multifield_union_should_fail.assertion.7\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`" + Summary: +Verification failed for - cross_function_multi_union_should_fail +Verification failed for - multi_cross_function_union_should_fail +Verification failed for - cross_function_union_should_fail Verification failed for - union_update_should_fail Verification failed for - union_complex_subfields_should_fail +Verification failed for - basic_multifield_union_should_fail Verification failed for - basic_union_should_fail -Complete - 3 successfully verified harnesses, 3 failures, 6 total. +Complete - 7 successfully verified harnesses, 7 failures, 14 total. diff --git a/tests/expected/uninit/unions.rs b/tests/expected/uninit/unions.rs index c623f9fcea94..979120fb566c 100644 --- a/tests/expected/uninit/unions.rs +++ b/tests/expected/uninit/unions.rs @@ -30,6 +30,42 @@ unsafe fn basic_union_should_fail() { let padding = u1.b; } +#[repr(C)] +#[derive(Clone, Copy)] +union MultiU { + d: u128, + a: u16, + c: u64, + b: u32, +} + +/// Simple and correct multifield union access. +#[kani::proof] +unsafe fn basic_multifield_union_should_pass() { + let u = MultiU { c: 0 }; + let mut u1 = u; + let non_padding_a = u1.a; + assert!(non_padding_a == 0); + let non_padding_b = u1.b; + assert!(non_padding_b == 0); + let non_padding_c = u1.c; + assert!(non_padding_c == 0); +} + +/// Reading padding data via simple multifield union access. +#[kani::proof] +unsafe fn basic_multifield_union_should_fail() { + let u = MultiU { c: 0 }; + let mut u1 = u; + let non_padding_a = u1.a; + assert!(non_padding_a == 0); + let non_padding_b = u1.b; + assert!(non_padding_b == 0); + let non_padding_c = u1.c; + assert!(non_padding_c == 0); + let padding = u1.d; // Accessing uninitialized data. +} + #[repr(C)] union U1 { a: (u32, u8), @@ -66,3 +102,76 @@ unsafe fn union_update_should_fail() { u.a = 0; let padding = u.b; } + +/// Reading padding data via simple union access if union is passed to another function. +#[kani::proof] +unsafe fn cross_function_union_should_fail() { + unsafe fn helper(u: U) { + let padding = u.b; // Read 4 bytes from `u`. + } + let u = U { a: 0 }; // `u` is initialized for 2 bytes. + helper(u); +} + +/// Reading non-padding data via simple union access if union is passed to another function. +#[kani::proof] +unsafe fn cross_function_union_should_pass() { + unsafe fn helper(u: U) { + let non_padding = u.a; // Read 2 bytes from `u`. + } + let u = U { a: 0 }; // `u` is initialized for 2 bytes. + helper(u); +} + +/// Reading padding data via simple union access if union is passed to another function multiple +/// times. +#[kani::proof] +unsafe fn multi_cross_function_union_should_fail() { + unsafe fn helper(u: U) { + sub_helper(u); + } + unsafe fn sub_helper(u: U) { + let padding = u.b; // Read 4 bytes from `u`. + } + let u = U { a: 0 }; // `u` is initialized for 2 bytes. + helper(u); +} + +/// Reading non-padding data via simple union access if union is passed to another function multiple +/// times. +#[kani::proof] +unsafe fn multi_cross_function_union_should_pass() { + unsafe fn helper(u: U) { + sub_helper(u); + } + unsafe fn sub_helper(u: U) { + let non_padding = u.a; // Read 2 bytes from `u`. + } + let u = U { a: 0 }; // `u` is initialized for 2 bytes. + helper(u); +} + +/// Reading padding data via simple union access if multiple unions are passed to another function. +#[kani::proof] +unsafe fn cross_function_multi_union_should_fail() { + unsafe fn helper(u1: U, u2: U) { + let padding = u1.b; // Read 4 bytes from `u1`. + let non_padding = u2.b; // Read 4 bytes from `u2`. + } + let u1 = U { a: 0 }; // `u1` is initialized for 2 bytes. + let u2 = U { b: 0 }; // `u2` is initialized for 4 bytes. + helper(u1, u2); +} + +/// Reading non-padding data via simple union access if multiple unions are passed to another +/// function. +#[kani::proof] +unsafe fn cross_function_multi_union_should_pass() { + unsafe fn helper(u1: U, u2: U) { + let padding = u1.b; // Read 4 bytes from `u1`. + let non_padding = u2.b; // Read 4 bytes from `u2`. + } + let u1 = U { b: 0 }; // `u1` is initialized for 4 bytes. + let u2 = U { b: 0 }; // `u2` is initialized for 4 bytes. + helper(u1, u2); +} From 4a9a70c1a2119c47004adcb1e9f4bd9ebcbe8eec Mon Sep 17 00:00:00 2001 From: rahulku Date: Wed, 4 Sep 2024 05:18:07 -0700 Subject: [PATCH 38/40] vstte paper (#3473) By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --------- Co-authored-by: Michael Tautschnig Co-authored-by: Michael Tautschnig --- papers/vstte2024/README.md | 1 + papers/vstte2024/paper.bib | 135 ++++++++++++++++++++++++++ papers/vstte2024/paper.tex | 172 ++++++++++++++++++++++++++++++++++ scripts/ci/copyright_check.py | 12 +-- 4 files changed, 314 insertions(+), 6 deletions(-) create mode 100644 papers/vstte2024/README.md create mode 100644 papers/vstte2024/paper.bib create mode 100644 papers/vstte2024/paper.tex diff --git a/papers/vstte2024/README.md b/papers/vstte2024/README.md new file mode 100644 index 000000000000..568b501138b5 --- /dev/null +++ b/papers/vstte2024/README.md @@ -0,0 +1 @@ +This contains the contents for a short paper at VSTTE2024. In order to build this, please download the LLNCS style file available at https://resource-cms.springernature.com/springer-cms/rest/v1/content/19238648/data/v8, unpack the resulting llncs.zip, and use standard LaTeX tools to build the paper. \ No newline at end of file diff --git a/papers/vstte2024/paper.bib b/papers/vstte2024/paper.bib new file mode 100644 index 000000000000..dafdbc98cbb2 --- /dev/null +++ b/papers/vstte2024/paper.bib @@ -0,0 +1,135 @@ +% Copyright Kani Contributors +% SPDX-License-Identifier: Apache-2.0 OR MIT + +\begin{paper}{8} +@inproceedings{verus-sys, + author = {Lattuada, Andrea and Hance, Travis and Bosamiya, Jay and Brun, Matthias and Cho, Chanhee and LeBlanc, Hayley and Srinivasan, Pranav and Achermann, Reto and Chajed, Tej and Hawblitzel, Chris and Howell, Jon and Lorch, Jay and Padon, Oded and Parno, Bryan}, + booktitle = {Proceedings of the ACM Symposium on Operating Systems Principles (SOSP)}, + code = {https://github.com/verus-lang/verus}, + month = {November}, + title = {Verus: A Practical Foundation for Systems Verification}, + year = {2024} +} + +@inproceedings{denis2022creusot, + title={Creusot: a foundry for the deductive verification of {R}ust programs}, + author={Denis, Xavier and Jourdan, Jacques-Henri and March{'e}, Claude}, + booktitle={International Conference on Formal Engineering Methods}, + pages={90--105}, + year={2022}, + organization={Springer} +} + +@inproceedings{astrauskas2022prusti, + title={The {P}rusti project: Formal verification for {R}ust}, + author={Astrauskas, Vytautas and B{\'\i}l{\`y}, Aurel and Fiala, Jon{\'a}{\v{s}} and Grannan, Zachary and Matheja, Christoph and M{\"u}ller, Peter and Poli, Federico and Summers, Alexander J}, + booktitle={NASA Formal Methods Symposium}, + pages={88--108}, + year={2022}, + organization={Springer} +} + +@inproceedings{vanhattum2022verifying, + title={Verifying dynamic trait objects in {R}ust}, + author={VanHattum, Alexa and Schwartz-Narbonne, Daniel and Chong, Nathan and Sampson, Adrian}, + booktitle={Proceedings of the 44th International Conference on Software Engineering: Software Engineering in Practice}, + pages={321--330}, + year={2022} +} + +@manual{superPower, + title = {Unsafe Rust}, + url = {https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html}, + author = {Rust Documentation} +} + +@inproceedings{li2021mirchecker, + title={MirChecker: detecting bugs in {R}ust programs via static analysis}, + author={Li, Zhuohua and Wang, Jincheng and Sun, Mingshen and Lui, John CS}, + booktitle={Proceedings of the 2021 ACM SIGSAC conference on computer and communications security}, + pages={2183--2196}, + year={2021} +} + +@manual{stringChallenge, + title = {Memory Safety of String}, + year = {2024}, + author = {Zyad Hassan}, + url = {https://model-checking.github.io/verify-rust-std/challenges/0010-string.html} +} + +@manual{anderson20medium, + title = {The Rust Compilation Model Calamity}, + year = {2020}, + url = {https://pingcap.medium.com/the-rust-compilation-model-calamity-1a8ce781cf6cb}, + author = {Brian Anderson} +} + +@article{matsakis2014rust, + title={The rust language}, + author={Matsakis, Nicholas D and Klock, Felix S}, + journal={ACM SIGAda Ada Letters}, + volume={34}, + number={3}, + pages={103--104}, + year={2014}, + publisher={ACM New York, NY, USA} +} + +@manual{challenge, + title = {Memory Safety of String}, + url = {https://github.com/model-checking/verify-rust-std/issues/61}, + author = {Zyad Hassan}, + year = {2024} +} + +@manual{solution, + title = {char and ascii{\_}char contracts}, + url = {https://github.com/model-checking/verify-rust-std/pull/48}, + year = {2024}, + author = {Carolyn Zech} +} + +@manual{rustAndroid, + title = {The Impact of Rust on Security Development}, + year = {2024}, + url = {https://www.riscure.com/the-impact-of-rust-on-security-development}, + author = {Christiaan Biesterbosch and Valeria Vatolina} +} +@manual{liam2022android, + title = {How Google is using Rust to reduce memory safety vulnerabilities in Android}, + year = {2020}, + url = {https://www.zdnet.com/article/google-after-using-rust-we-slashed-android-memory-safety-vulnerabilities/}, + author = {Liam Tung} +} + +@article{jung2017rustbelt, + title={RustBelt: Securing the foundations of the Rust programming language}, + author={Jung, Ralf and Jourdan, Jacques-Henri and Krebbers, Robbert and Dreyer, Derek}, + journal={Proceedings of the ACM on Programming Languages}, + volume={2}, + number={POPL}, + pages={1--34}, + year={2017}, + publisher={ACM New York, NY, USA} +} + +@article{ayoun2024hybrid, + title={A hybrid approach to semi-automated Rust verification}, + author={Ayoun, Sacha-{\'E}lie and Denis, Xavier and Maksimovi{\'c}, Petar and Gardner, Philippa}, + journal={arXiv preprint arXiv:2403.15122}, + year={2024} +} + +@article{ho2022aeneas, + title={Aeneas: Rust verification by functional translation}, + author={Ho, Son and Protzenko, Jonathan}, + journal={Proceedings of the ACM on Programming Languages}, + volume={6}, + number={ICFP}, + pages={711--741}, + year={2022}, + publisher={ACM New York, NY, USA} +} + +\end{thebibliography} \ No newline at end of file diff --git a/papers/vstte2024/paper.tex b/papers/vstte2024/paper.tex new file mode 100644 index 000000000000..a523bbf1de5b --- /dev/null +++ b/papers/vstte2024/paper.tex @@ -0,0 +1,172 @@ +% Copyright Kani Contributors +% SPDX-License-Identifier: Apache-2.0 OR MIT + +\documentclass[runningheads]{llncs} +% +\usepackage[T1]{fontenc} +\usepackage{graphicx} +\usepackage{amsmath,amsfonts} +\usepackage{hyperref} +\usepackage{listings} +\usepackage{xcolor} +\usepackage{color} +\usepackage{listings} +\definecolor{GrayCodeBlock}{RGB}{241,241,241} +\definecolor{BlackText}{RGB}{110,107,94} +\definecolor{RedTypename}{RGB}{182,86,17} +\definecolor{GreenString}{RGB}{96,172,57} +\definecolor{PurpleKeyword}{RGB}{184,84,212} +\definecolor{GrayComment}{RGB}{170,170,170} +\definecolor{GoldDocumentation}{RGB}{180,165,45} +\lstdefinelanguage{rust} +{ + columns=fullflexible, + keepspaces=true, + frame=single, + framesep=0pt, + framerule=0pt, + framexleftmargin=4pt, + framexrightmargin=4pt, + framextopmargin=5pt, + framexbottommargin=3pt, + xleftmargin=4pt, + xrightmargin=4pt, + backgroundcolor=\color{GrayCodeBlock}, + basicstyle=\ttfamily\color{BlackText}, + keywords={ + true,false, + unsafe,async,await,move, + use,pub,crate,super,self,mod, + struct,enum,fn,const,static,let,mut,ref,type,impl,dyn,trait,where,as, + break,continue,if,else,while,for,loop,match,return,yield,in + }, + keywordstyle=\color{PurpleKeyword}, + ndkeywords={ + bool,u8,u16,u32,u64,u128,i8,i16,i32,i64,i128,char,str, + Self,Option,Some,None,Result,Ok,Err,String,Box,Vec,Rc,Arc,Cell,RefCell,HashMap,BTreeMap, + macro_rules + }, + ndkeywordstyle=\color{RedTypename}, + comment=[l][\color{GrayComment}\slshape]{//}, + morecomment=[s][\color{GrayComment}\slshape]{/*}{*/}, + morecomment=[l][\color{GoldDocumentation}\slshape]{///}, + morecomment=[s][\color{GoldDocumentation}\slshape]{/*!}{*/}, + morecomment=[l][\color{GoldDocumentation}\slshape]{//!}, + morecomment=[s][\color{RedTypename}]{\#![}{]}, + morecomment=[s][\color{RedTypename}]{\#[}{]}, + stringstyle=\color{GreenString}, + string=[b]" +} + +\begin{document} +% +\title{Verifying the Rust Standard Library} +%\titlerunning{Verifying the Rust} + +\author{ +Rahul Kumar \and +Celina Val \and +Felipe Monteiro \and +Michael Tautschnig \and +Zyad Hassan \and +Qinheping Hu \and +Adrian Palacios \and +Remi Delmas \and +Jaisurya Nanduri \and +Felix Klock \and +Justus Adam \and +Carolyn Zech \and +Artem Agvanian +} +% +\authorrunning{R. Kumar et al.} + +\institute{Amazon Web Services, USA\\ \url{https://aws.amazon.com/} +} + +\maketitle + +\begin{abstract} +The Rust programming language is growing fast and seeing increased adoption due to performance and speed-of-development benefits. It provides strong compile-time guarantees along with blazing performance and an active community of support. The Rust language has experienced steady growth in the last few years with a total developer size of close to 3M developers. Several large projects such as Servo, TiKV, and the Rust compiler itself are in the millions of lines of code. Although Rust provides strong safety guarantees for \texttt{safe} code, the story with \texttt{unsafe} code is incomplete. In this short paper, we motivate the case for verifying the Rust standard library and how we are approaching this endeavor. We describe our effort to verify the Rust standard library via a crowd-sourced verification effort, wherein verifying the Rust standard library is specified as a set of challenges open to all. + +\keywords{Rust \and standard library \and verification \and formal methods \and safe +\and unsafe \and memory safety \and correctness \and challenge} +\end{abstract} + +\section{Rust} + +Rust~\cite{matsakis2014rust} is a modern programming language designed to enable developers to efficiently create high performance reliable systems. Rust delivers high performance because it does not use a garbage collector. Combined with a powerful type system that enforces ownership of memory wherein memory can be shared or mutable, but never both. This helps avoid data-races and memory errors, thereby reducing the trade-off between high-level safety guarantees and low-level controls -- a highly desired property of programming languages. Unlike C/C++, the Rust language aims to minimize undefined behavior statically by employing a strong type system and an \textit{extensible} ownership model for memory. + +The extensible model of ownership relies on the simple (yet difficult) principle of enforcing that an object can be accessed by multiple aliases/references only for read purposes. To write to an object, there can only be one reference to it at any given time. Such a principle in practice eliminates significant amounts of memory-related errors~\cite{rustAndroid}. In spite of the great benefits in practice, this principle tends to be restrictive for a certain subset of implementations that are too low-level or require very specific types of synchronization. As a result, the Rust language introduced the \texttt{unsafe} keyword. When used, the compiler may not be able to prove the memory safety rules that are enforced on \texttt{safe} code blocks. Alias tracking is not performed for raw pointers which can only be used in \texttt{unsafe} code blocks, which enables developers to perform actions that would be rejected by the compiler in \texttt{safe} code blocks. This is also referred to as \textit{superpowers}~\cite{superPower} of \texttt{unsafe} code blocks. Examples of these superpowers include dereferencing a raw pointer, calling an unsafe function or method, and accessing fields of unions etc. A clear side-effect of this choice is that most if not all memory related errors in the code are due to the \texttt{unsafe} code blocks introduced by the developer. + +Rust developers use \textit{encapsulation} as a common design pattern to mask unsafe code blocks. The safe abstractions allow \texttt{unsafe} code blocks to be limited in number and not leak into all parts of the codebase. The Rust standard library itself has widespread use of \texttt{unsafe} code blocks, with almost 5.5K \texttt{unsafe} functions and 4.8K \texttt{unsafe} code blocks. In the last 3 years, 40 soundness issues have been filed in the Rust standard library along with 17 reported CVEs, even with the extensive testing and usage of the library. The onus of proving the safety and correctness of these \texttt{unsafe} code blocks is on the developers. Some such efforts have been made, but there is still a lot of ground to cover~\cite{jung2017rustbelt}. + +Verifying the Rust standard library is important and rewarding along multiple dimensions such as improving Rust, creating better verification tools, and enabling a safer ecosystem. Given the size and scope of this exercise, we believe doing this in isolation would be expensive and counter-productive. Ergo, we believe that motivating the community and creating a unified crowd-sourced effort is the desirable method, which we hope to catalyze via our proposed effort. + + +\section{Rust Verification Landscape} + +A common misconception Rust developers have is that they are producing \texttt{safe} memory-safe code by simply using Rust as their development language. To counter this, there have been significant efforts to create tools and techniques that enable verification of Rust code. Here we list (alphabetically) some tools: + +\begin{itemize} + + \item \textbf{Creusot}~\cite{denis2022creusot} is a Rust verifier that also employs deductive-style verification for \texttt{safe} Rust code. Creusot also introduces \textbf{Pearlite} - a specification language for specifying function and loop contracts. + + \item \textbf{Gillian-Rust}~\cite{ayoun2024hybrid} is a separation logic based hybrid verification tool for Rust programs with support for \texttt{unsafe} code. Gillian-Rust is also linked to Creusot, but does in certain cases require manual intervention. + + \item \textbf{Kani}~\cite{vanhattum2022verifying} uses bounded model checking to verify generic memory safety properties and user specified assertions. Kani supports both \texttt{unsafe} and \texttt{safe} code, but cannot guarantee unbounded verification in all cases. + + \item \textbf{Prusti}~\cite{astrauskas2022prusti} employs deductive verification to prove functional correctness of \texttt{safe} Rust code. Specifically, it targets certain type of \textit{panics} and allows users to specify properties of interest. + + \item \textbf{Verus}~\cite{verus-sys} is an SMT-based tool used to verify Rust code and can support \texttt{unsafe} in certain situations such as the use of raw pointers and unsafe cells. + + \item There are several other tools which are in the related space, but we do not list them here explicitly. +\end{itemize} + +\section{Verifying the Rust Standard Library} + +We are proposing the creation of a crowd-sourced verification effort, wherein verifying the Rust standard library is specified as a set of challenges. Each challenge describes the goal and the success criteria. Currently, we are focusing on doing verification for memory-safety. The challenges are open to anyone. This effort aims to be \textit{tool agnostic} to facilitate the introduction of verification solutions into the Rust mainline and making verification an integral part of the Rust ecosystem. Towards this, we have been working with the Rust language team to introduce function and loop contracts into the Rust mainline and have created a fork of the Rust standard library repository \url{https://github.com/model-checking/verify-rust-std/} wherein all solutions to challenges and verification artifacts are stored. Challenges can come in various flavors: 1/ specifying contracts for a part of the Rust standard library, 2/ specify and verify a part of the Rust standard library, and 3/ introduce new tools/techniques to verify parts of the Rust standard library. The repository provides templates for introducing new challenges, new tools, and instructions on how to submit solutions to challenges. To date, we have over 20 students, academics, and researchers engaging. + +As part of this effort, we are also creating challenges. For example, we have created a challenge to verify the String library in the standard library~\cite{stringChallenge}. In this challenge, the goal is to verify the memory safety of \texttt{std::string::String} and prove the absence of undefined behavior (UB). Even though the majority of \texttt{String} methods are safe, many of them are safe abstractions over unsafe code. For instance, the insert method is implemented as follows : +\begin{lstlisting}[language=rust, caption=Unsafe usage in String, frame=single, numbers=left] + pub fn insert(&mut self, idx: usize, ch: char) { + assert!(self.is_char_boundary(idx)); + let mut bits = [0; 4]; + let bits = ch.encode_utf8(&mut bits).as_bytes(); + + unsafe { + self.insert_bytes(idx, bits); + } + } +\end{lstlisting} + +The goal also specifies the \textit{success criteria} that must be met for the solution to be reviewed and merged into the CI pipeline. +\begin{lstlisting}[caption=Success criteria for the String challenge.,frame=single] +Verify the memory safety of all public functions that are +safe abstractions over unsafe code: + unbounded: from_utf16le, from_utf16le_lossy, + from_utf16be, from_utf16be_lossy, + remove_matches, insert_str, + split_off, replace_range, retain + others: pop, remove, insert, drain, leak, + into_boxed_str +Ones marked as unbounded must be verified for any +string/slice length. +\end{lstlisting} + +Example of a solution for a challenge can be found in~\cite{solution}. This particular solution introduces new contracts for \texttt{char} and \texttt{ascii\_char}. The contracts are also verified using Kani. + +\noindent \textbf{Our call to action} to you is to come and be a part of this effort and contribute by solving challenges, introducing new challenges, introducing new tools, or helping review and refine the current processes! + +\begin{credits} +\subsubsection{\ackname} We would like to thank all the academic partners that have helped us shape challenges, started contributing to challenges, and provide invaluable advice throughout the process of jump starting this initiative. We also would like to thank Niko Matsakis, Byron Cook, and Kurt Kufeld for their support and leadership. +\end{credits} + +% +% Bibliography +% +\bibliographystyle{splncs04} +\bibliography{paper} + + +\end{document} diff --git a/scripts/ci/copyright_check.py b/scripts/ci/copyright_check.py index 3b07fb56da58..64fd92d06712 100755 --- a/scripts/ci/copyright_check.py +++ b/scripts/ci/copyright_check.py @@ -8,15 +8,15 @@ from itertools import chain -COMMENT_OR_EMPTY_PATTERN = '^(//.*$|#.*$|\\s*$)' +COMMENT_OR_EMPTY_PATTERN = '^(//.*$|#.*$|%.*$|\\s*$)' -STANDARD_HEADER_PATTERN_1 = '(//|#) Copyright Kani Contributors' -STANDARD_HEADER_PATTERN_2 = '(//|#) SPDX-License-Identifier: Apache-2.0 OR MIT' +STANDARD_HEADER_PATTERN_1 = '(//|#|%) Copyright Kani Contributors' +STANDARD_HEADER_PATTERN_2 = '(//|#|%) SPDX-License-Identifier: Apache-2.0 OR MIT' -MODIFIED_HEADER_PATTERN_1 = '(//|#) SPDX-License-Identifier: Apache-2.0 OR MIT' +MODIFIED_HEADER_PATTERN_1 = '(//|#|%) SPDX-License-Identifier: Apache-2.0 OR MIT' MODIFIED_HEADER_PATTERN_2 = COMMENT_OR_EMPTY_PATTERN -MODIFIED_HEADER_PATTERN_3 = '(//|#) Modifications Copyright Kani Contributors' -MODIFIED_HEADER_PATTERN_4 = '(//|#) See GitHub history for details.' +MODIFIED_HEADER_PATTERN_3 = '(//|#|%) Modifications Copyright Kani Contributors' +MODIFIED_HEADER_PATTERN_4 = '(//|#|%) See GitHub history for details.' class CheckResult(Enum): FAIL = 1 From 33e3c361d8b0ba404e51e7a119edabad41b1e86b Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Wed, 4 Sep 2024 14:44:42 -0400 Subject: [PATCH 39/40] Fix contract expansion for `old` (#3491) Fixes the macro expansion for contracts to properly place history expressions. ## Problem Before this PR, instantiations of "remembers variables" (i.e., the variables that save the value before the function executes) were always put *above* any statements from previous macro expansions. For example, for this code (from #3359): ```rust #[kani::requires(val < i32::MAX)] #[kani::ensures(|result| *result == old(val + 1))] pub fn next(mut val: i32) -> i32 { val + 1 } ``` Kani would first expand the `requires` attribute and insert `kani::assume(val < i32::MAX)`. The expansion of `ensures` would then put the remembers variables first, generating this: ``` let remember_kani_internal_1e725538cd5566b8 = val + 1; kani::assume(val < i32::MAX); ``` which causes an integer overflow because we don't restrict the value of `val` before adding 1. Instead, we want: ``` kani::assume(val < i32::MAX); let remember_kani_internal_1e725538cd5566b8 = val + 1; ``` ## Solution The solution is to insert the remembers variables immediately after preconditions--that way, they respect the preconditions but are still declared before the function under contract executes. When we're expanding an `ensures` clause, we iterate through each of the already-generated statements, find the position where the preconditions end, then insert the remembers variables there. For instance: ``` kani::assume(x < 100); kani::assume(y < 10); kani::assume(x + y < 105); <-- remembers variables go here --> let _wrapper_arg = ... ``` --- Resolves #3359 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../src/sysroot/contracts/check.rs | 11 ++-- .../src/sysroot/contracts/helpers.rs | 53 ++++++++++++++++++- .../kani_macros/src/sysroot/contracts/mod.rs | 7 +++ .../src/sysroot/contracts/replace.rs | 8 ++- .../ensures_before_requires.expected | 5 ++ .../ensures_before_requires.rs | 18 +++++++ .../respects-preconditions/modifies.expected | 5 ++ .../respects-preconditions/modifies.rs | 29 ++++++++++ .../requires_before_ensures.expected | 5 ++ .../requires_before_ensures.rs | 17 ++++++ 10 files changed, 152 insertions(+), 6 deletions(-) create mode 100644 tests/expected/function-contract/history/respects-preconditions/ensures_before_requires.expected create mode 100644 tests/expected/function-contract/history/respects-preconditions/ensures_before_requires.rs create mode 100644 tests/expected/function-contract/history/respects-preconditions/modifies.expected create mode 100644 tests/expected/function-contract/history/respects-preconditions/modifies.rs create mode 100644 tests/expected/function-contract/history/respects-preconditions/requires_before_ensures.expected create mode 100644 tests/expected/function-contract/history/respects-preconditions/requires_before_ensures.rs diff --git a/library/kani_macros/src/sysroot/contracts/check.rs b/library/kani_macros/src/sysroot/contracts/check.rs index 69f280ad9335..314a5e74bc98 100644 --- a/library/kani_macros/src/sysroot/contracts/check.rs +++ b/library/kani_macros/src/sysroot/contracts/check.rs @@ -9,8 +9,8 @@ use std::mem; use syn::{parse_quote, Block, Expr, FnArg, Local, LocalInit, Pat, PatIdent, ReturnType, Stmt}; use super::{ - helpers::*, shared::build_ensures, ContractConditionsData, ContractConditionsHandler, - INTERNAL_RESULT_IDENT, + helpers::*, shared::build_ensures, ClosureType, ContractConditionsData, + ContractConditionsHandler, INTERNAL_RESULT_IDENT, }; const WRAPPER_ARG: &str = "_wrapper_arg"; @@ -38,9 +38,14 @@ impl<'a> ContractConditionsHandler<'a> { ); let return_expr = body_stmts.pop(); + + let (assumes, rest_of_body) = + split_for_remembers(&body_stmts[..], ClosureType::Check); + quote!({ + #(#assumes)* #remembers - #(#body_stmts)* + #(#rest_of_body)* #exec_postconditions #return_expr }) diff --git a/library/kani_macros/src/sysroot/contracts/helpers.rs b/library/kani_macros/src/sysroot/contracts/helpers.rs index 410c2d971c44..8db6218e2693 100644 --- a/library/kani_macros/src/sysroot/contracts/helpers.rs +++ b/library/kani_macros/src/sysroot/contracts/helpers.rs @@ -4,10 +4,14 @@ //! Functions that operate third party data structures with no logic that is //! specific to Kani and contracts. +use crate::attr_impl::contracts::ClosureType; use proc_macro2::{Ident, Span}; use std::borrow::Cow; use syn::spanned::Spanned; -use syn::{parse_quote, Attribute, Expr, ExprBlock, Local, LocalInit, PatIdent, Stmt}; +use syn::{ + parse_quote, Attribute, Expr, ExprBlock, ExprCall, ExprPath, Local, LocalInit, PatIdent, Path, + Stmt, +}; /// If an explicit return type was provided it is returned, otherwise `()`. pub fn return_type_to_type(return_type: &syn::ReturnType) -> Cow { @@ -169,6 +173,53 @@ pub fn chunks_by<'a, T, C: Default + Extend>( }) } +/// Splits `stmts` into (preconditions, rest). +/// For example, ClosureType::Check assumes preconditions, so given this sequence of statements: +/// ```ignore +/// kani::assume(.. precondition_1); +/// kani::assume(.. precondition_2); +/// let _wrapper_arg = (ptr as * const _,); +/// ... +/// ``` +/// This function would return the two kani::assume statements in the former slice +/// and the remaining statements in the latter. +/// The flow for ClosureType::Replace is the same, except preconditions are asserted rather than assumed. +/// +/// The caller can use the returned tuple to insert remembers statements after `preconditions` and before `rest`. +/// Inserting the remembers statements after `preconditions` ensures that they are bound by the preconditions. +/// To understand why this is important, take the following example: +/// ```ignore +/// #[kani::requires(x < u32::MAX)] +/// #[kani::ensures(|result| old(x + 1) == *result)] +/// fn add_one(x: u32) -> u32 {...} +/// ``` +/// If the `old(x + 1)` statement didn't respect the precondition's upper bound on `x`, Kani would encounter an integer overflow. +/// +/// Inserting the remembers statements before `rest` ensures that they are declared before the original function executes, +/// so that they will store historical, pre-computation values as intended. +pub fn split_for_remembers(stmts: &[Stmt], closure_type: ClosureType) -> (&[Stmt], &[Stmt]) { + let mut pos = 0; + + let check_str = match closure_type { + ClosureType::Check => "assume", + ClosureType::Replace => "assert", + }; + + for stmt in stmts { + if let Stmt::Expr(Expr::Call(ExprCall { func, .. }), _) = stmt { + if let Expr::Path(ExprPath { path: Path { segments, .. }, .. }) = func.as_ref() { + let first_two_idents = + segments.iter().take(2).map(|sgmt| sgmt.ident.to_string()).collect::>(); + + if first_two_idents == vec!["kani", check_str] { + pos += 1; + } + } + } + } + stmts.split_at(pos) +} + macro_rules! assert_spanned_err { ($condition:expr, $span_source:expr, $msg:expr, $($args:expr),+) => { if !$condition { diff --git a/library/kani_macros/src/sysroot/contracts/mod.rs b/library/kani_macros/src/sysroot/contracts/mod.rs index db5f30131405..281960bf041b 100644 --- a/library/kani_macros/src/sysroot/contracts/mod.rs +++ b/library/kani_macros/src/sysroot/contracts/mod.rs @@ -514,6 +514,13 @@ enum ContractConditionsData { }, } +/// Which function are we currently generating? +#[derive(Copy, Clone, Eq, PartialEq)] +enum ClosureType { + Check, + Replace, +} + impl<'a> ContractConditionsHandler<'a> { /// Handle the contract state and return the generated code fn dispatch_on(mut self, state: ContractFunctionState) -> TokenStream2 { diff --git a/library/kani_macros/src/sysroot/contracts/replace.rs b/library/kani_macros/src/sysroot/contracts/replace.rs index 02ac4a772348..71913e630622 100644 --- a/library/kani_macros/src/sysroot/contracts/replace.rs +++ b/library/kani_macros/src/sysroot/contracts/replace.rs @@ -11,7 +11,7 @@ use syn::Stmt; use super::{ helpers::*, shared::{build_ensures, try_as_result_assign}, - ContractConditionsData, ContractConditionsHandler, INTERNAL_RESULT_IDENT, + ClosureType, ContractConditionsData, ContractConditionsHandler, INTERNAL_RESULT_IDENT, }; impl<'a> ContractConditionsHandler<'a> { @@ -84,9 +84,13 @@ impl<'a> ContractConditionsHandler<'a> { ContractConditionsData::Ensures { attr } => { let (remembers, ensures_clause) = build_ensures(attr); let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site()); + + let (asserts, rest_of_before) = split_for_remembers(before, ClosureType::Replace); + quote!({ + #(#asserts)* #remembers - #(#before)* + #(#rest_of_before)* #(#after)* kani::assume(#ensures_clause); #result diff --git a/tests/expected/function-contract/history/respects-preconditions/ensures_before_requires.expected b/tests/expected/function-contract/history/respects-preconditions/ensures_before_requires.expected new file mode 100644 index 000000000000..a9e6a1a6a601 --- /dev/null +++ b/tests/expected/function-contract/history/respects-preconditions/ensures_before_requires.expected @@ -0,0 +1,5 @@ +next\ + - Status: SUCCESS\ + - Description: "attempt to add with overflow" + +VERIFICATION:- SUCCESSFUL \ No newline at end of file diff --git a/tests/expected/function-contract/history/respects-preconditions/ensures_before_requires.rs b/tests/expected/function-contract/history/respects-preconditions/ensures_before_requires.rs new file mode 100644 index 000000000000..380da8898e71 --- /dev/null +++ b/tests/expected/function-contract/history/respects-preconditions/ensures_before_requires.rs @@ -0,0 +1,18 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +// Demonstrate that when the ensures contract is before the requires contract, +// the history expression respects the upper bound on x, so x + 1 doesn't overflow +// This example is taken from https://github.com/model-checking/kani/issues/3359 + +#[kani::ensures(|result| *result == old(val + 1))] +#[kani::requires(val < i32::MAX)] +pub fn next(val: i32) -> i32 { + val + 1 +} + +#[kani::proof_for_contract(next)] +pub fn check_next() { + let _ = next(kani::any()); +} diff --git a/tests/expected/function-contract/history/respects-preconditions/modifies.expected b/tests/expected/function-contract/history/respects-preconditions/modifies.expected new file mode 100644 index 000000000000..40dd76d9ce4d --- /dev/null +++ b/tests/expected/function-contract/history/respects-preconditions/modifies.expected @@ -0,0 +1,5 @@ +modify\ + - Status: SUCCESS\ + - Description: "attempt to add with overflow" + +VERIFICATION:- SUCCESSFUL \ No newline at end of file diff --git a/tests/expected/function-contract/history/respects-preconditions/modifies.rs b/tests/expected/function-contract/history/respects-preconditions/modifies.rs new file mode 100644 index 000000000000..b20b8396e3d6 --- /dev/null +++ b/tests/expected/function-contract/history/respects-preconditions/modifies.rs @@ -0,0 +1,29 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +// Demonstrate the the history expression respects preconditions +// with multiple interleaved preconditions, modifies contracts, and history expressions + +#[derive(kani::Arbitrary)] +struct Point { + x: X, + y: Y, +} + +#[kani::requires(ptr.x < 100)] +#[kani::ensures(|result| old(ptr.x + 1) == ptr.x)] +#[kani::modifies(&mut ptr.x)] +#[kani::ensures(|result| old(ptr.y - 1) == ptr.y)] +#[kani::modifies(&mut ptr.y)] +#[kani::requires(ptr.y > 0)] +fn modify(ptr: &mut Point) { + ptr.x += 1; + ptr.y -= 1; +} + +#[kani::proof_for_contract(modify)] +fn main() { + let mut p: Point = kani::any(); + modify(&mut p); +} diff --git a/tests/expected/function-contract/history/respects-preconditions/requires_before_ensures.expected b/tests/expected/function-contract/history/respects-preconditions/requires_before_ensures.expected new file mode 100644 index 000000000000..a9e6a1a6a601 --- /dev/null +++ b/tests/expected/function-contract/history/respects-preconditions/requires_before_ensures.expected @@ -0,0 +1,5 @@ +next\ + - Status: SUCCESS\ + - Description: "attempt to add with overflow" + +VERIFICATION:- SUCCESSFUL \ No newline at end of file diff --git a/tests/expected/function-contract/history/respects-preconditions/requires_before_ensures.rs b/tests/expected/function-contract/history/respects-preconditions/requires_before_ensures.rs new file mode 100644 index 000000000000..4f8f6871f8f9 --- /dev/null +++ b/tests/expected/function-contract/history/respects-preconditions/requires_before_ensures.rs @@ -0,0 +1,17 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +// Demonstrate that when the requires contract is before the ensures contract, the history expression respects the upper bound on x, so x + 1 doesn't overflow +// This example is taken from https://github.com/model-checking/kani/issues/3359 + +#[kani::requires(val < i32::MAX)] +#[kani::ensures(|result| *result == old(val + 1))] +pub fn next(val: i32) -> i32 { + val + 1 +} + +#[kani::proof_for_contract(next)] +pub fn check_next() { + let _ = next(kani::any()); +} From 603f9bf6c86aa3956acd0f686ea7ac094362360e Mon Sep 17 00:00:00 2001 From: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> Date: Wed, 4 Sep 2024 13:00:01 -0700 Subject: [PATCH 40/40] Bump Kani version to 0.55.0 (#3486) These are the auto-generated release notes: ## What's Changed * Update CBMC build instructions for Amazon Linux 2 by @tautschnig in https://github.com/model-checking/kani/pull/3431 * Handle intrinsics systematically by @artemagvanian in https://github.com/model-checking/kani/pull/3422 * Bump tests/perf/s2n-quic from `445f73b` to `ab9723a` by @dependabot in https://github.com/model-checking/kani/pull/3434 * Automatic cargo update to 2024-08-12 by @github-actions in https://github.com/model-checking/kani/pull/3433 * Actually apply CBMC patch by @tautschnig in https://github.com/model-checking/kani/pull/3436 * Update features/verify-rust-std branch by @feliperodri in https://github.com/model-checking/kani/pull/3435 * Add test related to issue 3432 by @celinval in https://github.com/model-checking/kani/pull/3439 * Implement memory initialization state copy functionality by @artemagvanian in https://github.com/model-checking/kani/pull/3350 * Bump tests/perf/s2n-quic from `ab9723a` to `80b93a7` by @dependabot in https://github.com/model-checking/kani/pull/3453 * Make points-to analysis handle all intrinsics explicitly by @artemagvanian in https://github.com/model-checking/kani/pull/3452 * Automatic cargo update to 2024-08-19 by @github-actions in https://github.com/model-checking/kani/pull/3450 * Add loop scanner to tool-scanner by @qinheping in https://github.com/model-checking/kani/pull/3443 * Avoid corner-cases by grouping instrumentation into basic blocks and using backward iteration by @artemagvanian in https://github.com/model-checking/kani/pull/3438 * Re-enabled hierarchical logs in the compiler by @celinval in https://github.com/model-checking/kani/pull/3449 * Fix ICE due to mishandling of Aggregate rvalue for raw pointers to `str` by @celinval in https://github.com/model-checking/kani/pull/3448 * Automatic cargo update to 2024-08-26 by @github-actions in https://github.com/model-checking/kani/pull/3459 * Bump tests/perf/s2n-quic from `80b93a7` to `8f7c04b` by @dependabot in https://github.com/model-checking/kani/pull/3460 * Update deny action by @zhassan-aws in https://github.com/model-checking/kani/pull/3461 * Basic support for memory initialization checks for unions by @artemagvanian in https://github.com/model-checking/kani/pull/3444 * Adjust test patterns so as not to check for trivial properties by @tautschnig in https://github.com/model-checking/kani/pull/3464 * Clarify comment in RFC Template by @carolynzech in https://github.com/model-checking/kani/pull/3462 * RFC: Source-based code coverage by @adpaco-aws in https://github.com/model-checking/kani/pull/3143 * Adopt Rust's source-based code coverage instrumentation by @adpaco-aws in https://github.com/model-checking/kani/pull/3119 * Upgrade toolchain to 08/28 by @jaisnan in https://github.com/model-checking/kani/pull/3454 * Extra tests and bug fixes to the delayed UB instrumentation by @artemagvanian in https://github.com/model-checking/kani/pull/3419 * Upgrade Toolchain to 8/29 by @carolynzech in https://github.com/model-checking/kani/pull/3468 * Automatic toolchain upgrade to nightly-2024-08-30 by @github-actions in https://github.com/model-checking/kani/pull/3469 * Extend name resolution to support qualified paths (Partial Fix) by @celinval in https://github.com/model-checking/kani/pull/3457 * Partially integrate uninit memory checks into `verify_std` by @artemagvanian in https://github.com/model-checking/kani/pull/3470 * Update Toolchain to 9/1 by @carolynzech in https://github.com/model-checking/kani/pull/3478 * Automatic cargo update to 2024-09-02 by @github-actions in https://github.com/model-checking/kani/pull/3480 * Bump tests/perf/s2n-quic from `8f7c04b` to `1ff3a9c` by @dependabot in https://github.com/model-checking/kani/pull/3481 * Automatic toolchain upgrade to nightly-2024-09-02 by @github-actions in https://github.com/model-checking/kani/pull/3479 * Automatic toolchain upgrade to nightly-2024-09-03 by @github-actions in https://github.com/model-checking/kani/pull/3482 * RFC for List Subcommand by @carolynzech in https://github.com/model-checking/kani/pull/3463 * Add tests for fixed issues. by @carolynzech in https://github.com/model-checking/kani/pull/3484 **Full Changelog**: https://github.com/model-checking/kani/compare/kani-0.54.0...kani-0.55.0 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- CHANGELOG.md | 22 ++++++++++++++++++++++ Cargo.lock | 20 ++++++++++---------- Cargo.toml | 2 +- cprover_bindings/Cargo.toml | 2 +- kani-compiler/Cargo.toml | 2 +- kani-driver/Cargo.toml | 2 +- kani_metadata/Cargo.toml | 2 +- library/kani/Cargo.toml | 2 +- library/kani_core/Cargo.toml | 2 +- library/kani_macros/Cargo.toml | 2 +- library/std/Cargo.toml | 2 +- tools/build-kani/Cargo.toml | 2 +- 12 files changed, 42 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d36def1a45f1..e35af4c8fa24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,28 @@ This file contains notable changes (e.g. breaking changes, major changes, etc.) This file was introduced starting Kani 0.23.0, so it only contains changes from version 0.23.0 onwards. +## [0.55.0] + +### Major/Breaking Changes +* Coverage reporting in Kani is now source-based instead of line-based. +Consequently, the unstable `-Zline-coverage` flag has been replaced with a `-Zsource-coverage` one. +Check the [Source-Coverage RFC](https://model-checking.github.io/kani/rfc/rfcs/0011-source-coverage.html) for more details. +* Several improvements were made to the memory initialization checks. The current state is summarized in https://github.com/model-checking/kani/issues/3300. We welcome your feedback! + +### What's Changed +* Update CBMC build instructions for Amazon Linux 2 by @tautschnig in https://github.com/model-checking/kani/pull/3431 +* Implement memory initialization state copy functionality by @artemagvanian in https://github.com/model-checking/kani/pull/3350 +* Make points-to analysis handle all intrinsics explicitly by @artemagvanian in https://github.com/model-checking/kani/pull/3452 +* Avoid corner-cases by grouping instrumentation into basic blocks and using backward iteration by @artemagvanian in https://github.com/model-checking/kani/pull/3438 +* Fix ICE due to mishandling of Aggregate rvalue for raw pointers to `str` by @celinval in https://github.com/model-checking/kani/pull/3448 +* Basic support for memory initialization checks for unions by @artemagvanian in https://github.com/model-checking/kani/pull/3444 +* Adopt Rust's source-based code coverage instrumentation by @adpaco-aws in https://github.com/model-checking/kani/pull/3119 +* Extra tests and bug fixes to the delayed UB instrumentation by @artemagvanian in https://github.com/model-checking/kani/pull/3419 +* Partially integrate uninit memory checks into `verify_std` by @artemagvanian in https://github.com/model-checking/kani/pull/3470 +* Rust toolchain upgraded to `nightly-2024-09-03` by @jaisnan @carolynzech + +**Full Changelog**: https://github.com/model-checking/kani/compare/kani-0.54.0...kani-0.55.0 + ## [0.54.0] ### Major Changes diff --git a/Cargo.lock b/Cargo.lock index c180c66dde90..1c371b85fba9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,7 +93,7 @@ checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "build-kani" -version = "0.54.0" +version = "0.55.0" dependencies = [ "anyhow", "cargo_metadata", @@ -235,7 +235,7 @@ dependencies = [ [[package]] name = "cprover_bindings" -version = "0.54.0" +version = "0.55.0" dependencies = [ "lazy_static", "linear-map", @@ -459,7 +459,7 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "kani" -version = "0.54.0" +version = "0.55.0" dependencies = [ "kani_core", "kani_macros", @@ -467,7 +467,7 @@ dependencies = [ [[package]] name = "kani-compiler" -version = "0.54.0" +version = "0.55.0" dependencies = [ "clap", "cprover_bindings", @@ -491,7 +491,7 @@ dependencies = [ [[package]] name = "kani-driver" -version = "0.54.0" +version = "0.55.0" dependencies = [ "anyhow", "cargo_metadata", @@ -520,7 +520,7 @@ dependencies = [ [[package]] name = "kani-verifier" -version = "0.54.0" +version = "0.55.0" dependencies = [ "anyhow", "home", @@ -529,14 +529,14 @@ dependencies = [ [[package]] name = "kani_core" -version = "0.54.0" +version = "0.55.0" dependencies = [ "kani_macros", ] [[package]] name = "kani_macros" -version = "0.54.0" +version = "0.55.0" dependencies = [ "proc-macro-error", "proc-macro2", @@ -546,7 +546,7 @@ dependencies = [ [[package]] name = "kani_metadata" -version = "0.54.0" +version = "0.55.0" dependencies = [ "clap", "cprover_bindings", @@ -1098,7 +1098,7 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "std" -version = "0.54.0" +version = "0.55.0" dependencies = [ "kani", ] diff --git a/Cargo.toml b/Cargo.toml index 149b8d2c93c8..ee9848b578dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani-verifier" -version = "0.54.0" +version = "0.55.0" edition = "2021" description = "A bit-precise model checker for Rust." readme = "README.md" diff --git a/cprover_bindings/Cargo.toml b/cprover_bindings/Cargo.toml index b0e3c578bbcf..008c81aef2ad 100644 --- a/cprover_bindings/Cargo.toml +++ b/cprover_bindings/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "cprover_bindings" -version = "0.54.0" +version = "0.55.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/kani-compiler/Cargo.toml b/kani-compiler/Cargo.toml index e8e564f9616a..9ca8d10f5275 100644 --- a/kani-compiler/Cargo.toml +++ b/kani-compiler/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani-compiler" -version = "0.54.0" +version = "0.55.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/kani-driver/Cargo.toml b/kani-driver/Cargo.toml index 27fef66ffb65..7485d2279ad6 100644 --- a/kani-driver/Cargo.toml +++ b/kani-driver/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani-driver" -version = "0.54.0" +version = "0.55.0" edition = "2021" description = "Build a project with Kani and run all proof harnesses" license = "MIT OR Apache-2.0" diff --git a/kani_metadata/Cargo.toml b/kani_metadata/Cargo.toml index efa28288d148..18eadc4095ed 100644 --- a/kani_metadata/Cargo.toml +++ b/kani_metadata/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani_metadata" -version = "0.54.0" +version = "0.55.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/kani/Cargo.toml b/library/kani/Cargo.toml index a3692f95e3f5..fa50783516f4 100644 --- a/library/kani/Cargo.toml +++ b/library/kani/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani" -version = "0.54.0" +version = "0.55.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/kani_core/Cargo.toml b/library/kani_core/Cargo.toml index 9df828e77c5a..447cd0b3f298 100644 --- a/library/kani_core/Cargo.toml +++ b/library/kani_core/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani_core" -version = "0.54.0" +version = "0.55.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/kani_macros/Cargo.toml b/library/kani_macros/Cargo.toml index 574960e5fc0a..a6b20a68bc39 100644 --- a/library/kani_macros/Cargo.toml +++ b/library/kani_macros/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani_macros" -version = "0.54.0" +version = "0.55.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index 9f0d09b0d7bb..12c923e9b655 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -5,7 +5,7 @@ # Note: this package is intentionally named std to make sure the names of # standard library symbols are preserved name = "std" -version = "0.54.0" +version = "0.55.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/tools/build-kani/Cargo.toml b/tools/build-kani/Cargo.toml index cd2985e4ad68..41095f1d7c3c 100644 --- a/tools/build-kani/Cargo.toml +++ b/tools/build-kani/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "build-kani" -version = "0.54.0" +version = "0.55.0" edition = "2021" description = "Builds Kani, Sysroot and release bundle." license = "MIT OR Apache-2.0"