Skip to content

Commit 0eb1fe8

Browse files
Reject proof harnesses with arguments (rust-lang#2132)
- Change `#[kani::proof]` expansion so it doesn't include `#[no_mangle]` but includes `[allow(dead_code)]`. (rust-lang#661 and rust-lang#689). - Add a check for harnesses with arguments and merge the checks into one function (rust-lang#1919). Co-authored-by: Zyad Hassan <[email protected]>
1 parent 7f232fe commit 0eb1fe8

File tree

9 files changed

+128
-47
lines changed

9 files changed

+128
-47
lines changed

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

+31-30
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use rustc_ast::Attribute;
1313
use rustc_hir::def::DefKind;
1414
use rustc_hir::def_id::DefId;
1515
use rustc_middle::mir::{HasLocalDecls, Local};
16+
use rustc_middle::ty::layout::FnAbiOf;
1617
use rustc_middle::ty::{self, Instance};
1718
use std::collections::BTreeMap;
1819
use std::convert::TryInto;
@@ -232,46 +233,46 @@ impl<'tcx> GotocCtx<'tcx> {
232233
self.reset_current_fn();
233234
}
234235

235-
pub fn is_proof_harness(&self, def_id: DefId) -> bool {
236-
let all_attributes = self.tcx.get_attrs_unchecked(def_id);
237-
let (proof_attributes, _) = partition_kanitool_attributes(all_attributes);
238-
if !proof_attributes.is_empty() {
239-
let span = proof_attributes.first().unwrap().span;
240-
if self.tcx.def_kind(def_id) != DefKind::Fn {
241-
self.tcx
242-
.sess
243-
.span_err(span, "The kani::proof attribute can only be applied to functions.");
244-
} else if self.tcx.generics_of(def_id).requires_monomorphization(self.tcx) {
236+
/// Check that if an item is tagged with a proof_attribute, it is a valid harness.
237+
fn check_proof_attribute(&self, def_id: DefId, proof_attributes: Vec<&Attribute>) {
238+
assert!(!proof_attributes.is_empty());
239+
let span = proof_attributes.first().unwrap().span;
240+
if proof_attributes.len() > 1 {
241+
self.tcx.sess.span_warn(proof_attributes[0].span, "Duplicate attribute");
242+
}
243+
244+
if self.tcx.def_kind(def_id) != DefKind::Fn {
245+
self.tcx
246+
.sess
247+
.span_err(span, "The kani::proof attribute can only be applied to functions.");
248+
} else if self.tcx.generics_of(def_id).requires_monomorphization(self.tcx) {
249+
self.tcx
250+
.sess
251+
.span_err(span, "The proof attribute cannot be applied to generic functions.");
252+
} else {
253+
let instance = Instance::mono(self.tcx, def_id);
254+
if !self.fn_abi_of_instance(instance, ty::List::empty()).args.is_empty() {
245255
self.tcx
246256
.sess
247-
.span_err(span, "The proof attribute cannot be applied to generic functions.");
257+
.span_err(span, "Functions used as harnesses can not have any arguments.");
248258
}
249-
self.tcx.sess.abort_if_errors();
250-
true
251-
} else {
252-
false
253259
}
254260
}
255261

256-
// Check that all attributes assigned to an item is valid.
262+
pub fn is_proof_harness(&self, def_id: DefId) -> bool {
263+
let all_attributes = self.tcx.get_attrs_unchecked(def_id);
264+
let (proof_attributes, _) = partition_kanitool_attributes(all_attributes);
265+
!proof_attributes.is_empty()
266+
}
267+
268+
/// Check that all attributes assigned to an item is valid.
269+
/// Errors will be added to the session. Invoke self.tcx.sess.abort_if_errors() to terminate
270+
/// the session in case of an error.
257271
pub fn check_attributes(&self, def_id: DefId) {
258272
let all_attributes = self.tcx.get_attrs_unchecked(def_id);
259273
let (proof_attributes, other_attributes) = partition_kanitool_attributes(all_attributes);
260274
if !proof_attributes.is_empty() {
261-
let span = proof_attributes.first().unwrap().span;
262-
if self.tcx.def_kind(def_id) != DefKind::Fn {
263-
self.tcx
264-
.sess
265-
.span_err(span, "The kani::proof attribute can only be applied to functions.");
266-
} else if self.tcx.generics_of(def_id).requires_monomorphization(self.tcx) {
267-
self.tcx
268-
.sess
269-
.span_err(span, "The proof attribute cannot be applied to generic functions.");
270-
} else if proof_attributes.len() > 1 {
271-
self.tcx
272-
.sess
273-
.span_warn(proof_attributes[0].span, "Only one '#[kani::proof]' allowed");
274-
}
275+
self.check_proof_attribute(def_id, proof_attributes);
275276
} else if !other_attributes.is_empty() {
276277
self.tcx.sess.span_err(
277278
other_attributes[0].1.span,

kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs

+39-2
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,15 @@ use rustc_data_structures::rustc_erase_owner;
2929
use rustc_data_structures::sync::MetadataRef;
3030
use rustc_middle::mir::interpret::Allocation;
3131
use rustc_middle::span_bug;
32-
use rustc_middle::ty::layout::{HasParamEnv, HasTyCtxt, LayoutError, LayoutOfHelpers, TyAndLayout};
32+
use rustc_middle::ty::layout::{
33+
FnAbiError, FnAbiOfHelpers, FnAbiRequest, HasParamEnv, HasTyCtxt, LayoutError, LayoutOfHelpers,
34+
TyAndLayout,
35+
};
3336
use rustc_middle::ty::{self, Instance, Ty, TyCtxt};
3437
use rustc_session::cstore::MetadataLoader;
3538
use rustc_session::Session;
36-
use rustc_span::source_map::Span;
39+
use rustc_span::source_map::{respan, Span};
40+
use rustc_target::abi::call::FnAbi;
3741
use rustc_target::abi::Endian;
3842
use rustc_target::abi::{HasDataLayout, TargetDataLayout};
3943
use rustc_target::spec::Target;
@@ -380,6 +384,39 @@ impl<'tcx> HasDataLayout for GotocCtx<'tcx> {
380384
self.tcx.data_layout()
381385
}
382386
}
387+
388+
/// Implement error handling for extracting function ABI information.
389+
impl<'tcx> FnAbiOfHelpers<'tcx> for GotocCtx<'tcx> {
390+
type FnAbiOfResult = &'tcx FnAbi<'tcx, Ty<'tcx>>;
391+
392+
#[inline]
393+
fn handle_fn_abi_err(
394+
&self,
395+
err: FnAbiError<'tcx>,
396+
span: Span,
397+
fn_abi_request: FnAbiRequest<'tcx>,
398+
) -> ! {
399+
if let FnAbiError::Layout(LayoutError::SizeOverflow(_)) = err {
400+
self.tcx.sess.emit_fatal(respan(span, err))
401+
} else {
402+
match fn_abi_request {
403+
FnAbiRequest::OfFnPtr { sig, extra_args } => {
404+
span_bug!(
405+
span,
406+
"Error: {err}\n while running `fn_abi_of_fn_ptr. ({sig}, {extra_args:?})`",
407+
);
408+
}
409+
FnAbiRequest::OfInstance { instance, extra_args } => {
410+
span_bug!(
411+
span,
412+
"Error: {err}\n while running `fn_abi_of_instance. ({instance}, {extra_args:?})`",
413+
);
414+
}
415+
}
416+
}
417+
}
418+
}
419+
383420
pub struct GotocMetadataLoader();
384421
impl MetadataLoader for GotocMetadataLoader {
385422
fn get_rlib_metadata(&self, _: &Target, _filename: &Path) -> Result<MetadataRef, String> {

library/kani_macros/src/lib.rs

+1-5
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,12 @@ pub fn proof(attr: TokenStream, item: TokenStream) -> TokenStream {
4444
let fn_item = parse_macro_input!(item as ItemFn);
4545
let attrs = fn_item.attrs;
4646
let vis = fn_item.vis;
47-
let fn_name = fn_item.sig.ident.clone();
48-
let test_name = quote!(#fn_name);
4947
let sig = fn_item.sig;
5048
let body = fn_item.block;
5149

5250
let kani_attributes = quote!(
51+
#[allow(dead_code)]
5352
#[kanitool::proof]
54-
#[export_name = concat!(module_path!(), "::", stringify!(#test_name))]
55-
// no_mangle is a temporary hack to make the function "public" so it gets codegen'd
56-
#[no_mangle]
5753
);
5854

5955
assert!(attr.is_empty(), "#[kani::proof] does not take any arguments for now");

tests/expected/reach/assert/unreachable/test.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

44
#[kani::proof]
5-
fn foo(x: i32) {
5+
fn foo() {
6+
let x: i32 = kani::any();
67
if x > 5 {
78
// fails
89
assert!(x < 4);

tests/kani/Intrinsics/PtrGuaranteedCmp/cmp.rs

+13-7
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,20 @@
66
#![feature(core_intrinsics)]
77
use std::intrinsics::ptr_guaranteed_cmp;
88

9-
#[kani::proof]
10-
fn test_ptr_eq(ptr1: *const u8, ptr2: *const u8) {
11-
kani::assume(ptr1 == ptr2);
12-
assert_eq!(ptr_guaranteed_cmp(ptr1, ptr2), 1);
9+
fn ptr_eq(ptr1: *const u8, ptr2: *const u8) -> bool {
10+
ptr_guaranteed_cmp(ptr1, ptr2) == 1
11+
}
12+
13+
fn ptr_ne(ptr1: *const u8, ptr2: *const u8) -> bool {
14+
ptr_guaranteed_cmp(ptr1, ptr2) == 0
1315
}
1416

1517
#[kani::proof]
16-
fn test_ptr_ne(ptr1: *const u8, ptr2: *const u8) {
17-
kani::assume(ptr1 != ptr2);
18-
assert_eq!(ptr_guaranteed_cmp(ptr1, ptr2), 0);
18+
fn check_ptr_guaranteed_cmp() {
19+
let v1: u8 = kani::any();
20+
let v2: u8 = kani::any();
21+
assert!(ptr_eq(&v1, &v1));
22+
assert!(ptr_eq(&v2, &v2));
23+
assert!(ptr_ne(&v2, &v1));
24+
assert!(ptr_ne(&v1, &v2));
1925
}

tests/ui/invalid-harnesses/expected

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
warning: Duplicate attribute\
2+
invalid.rs:\
3+
|\
4+
| #[kani::proof]\
5+
| ^^^^^^^^^^^^^^
6+
7+
error: Functions used as harnesses can not have any arguments.\
8+
invalid.rs:\
9+
|\
10+
| #[kani::proof]
11+
| ^^^^^^^^^^^^^^
12+
13+
error: The proof attribute cannot be applied to generic functions.\
14+
invalid.rs:\
15+
|\
16+
| #[kani::proof]\
17+
| ^^^^^^^^^^^^^^

tests/ui/invalid-harnesses/invalid.rs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// This test is to check Kani's error handling of invalid usages of the `proof` harness.
5+
// We also ensure that all errors and warnings are printed in one compilation.
6+
7+
#[kani::proof]
8+
#[kani::proof]
9+
fn multiple_proof_annotations() {}
10+
11+
#[kani::proof]
12+
fn proof_with_arg(arg: bool) {
13+
assert!(arg);
14+
}
15+
16+
#[kani::proof]
17+
fn generic_harness<T: Default>() {
18+
let _ = T::default();
19+
}

tests/ui/logging/warning/expected

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
warning: Only one '#[kani::proof]' allowed
1+
warning: Duplicate attribute
+5-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
warning: Only one '#[kani::proof]' allowed
1+
warning: Duplicate attribute\
2+
main.rs:\
3+
|\
4+
| #[kani::proof]\
5+
| ^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)