forked from rust-lang/rust
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Limit FFI calls by default to explicitly supported ones (rust-lang#2428)
C-FFI is fairly unstable right now and the lack of error handling causes a poor experience when things go wrong. In this PR, we limit its usage to only support CBMC built-in functions that are explicitly included in Kani and alloc functions from kani_lib.c. User's should still be enable to go back to previous behavior by enabling unstable C-FFI support Co-authored-by: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com>
- Loading branch information
1 parent
8e6267d
commit 6e91d13
Showing
16 changed files
with
311 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
161 changes: 161 additions & 0 deletions
161
kani-compiler/src/codegen_cprover_gotoc/codegen/foreign_function.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
// Copyright Kani Contributors | ||
// SPDX-License-Identifier: Apache-2.0 OR MIT | ||
//! This module implements foreign function handling. | ||
//! | ||
//! Kani currently only support CBMC built-in functions that are declared in the `cprover_bindings` | ||
//! crate, and allocation functions defined in `kani_lib.c`. | ||
//! | ||
//! All other functions will be replaced by an unimplemented check, due to current issues with | ||
//! linking and usability unless unstable C-FFI support is enabled. | ||
use std::collections::HashSet; | ||
|
||
use crate::codegen_cprover_gotoc::codegen::PropertyClass; | ||
use crate::codegen_cprover_gotoc::GotocCtx; | ||
use crate::kani_middle; | ||
use cbmc::goto_program::{Expr, Location, Stmt, Symbol, Type}; | ||
use cbmc::{InternString, InternedString}; | ||
use kani_queries::UserInput; | ||
use lazy_static::lazy_static; | ||
use rustc_middle::ty::Instance; | ||
use rustc_target::abi::call::Conv; | ||
use tracing::{debug, trace}; | ||
|
||
lazy_static! { | ||
/// The list of Rust allocation functions that are declared in the `core::alloc` module | ||
/// but defined by each backend. | ||
/// For our `goto-program` backend, these functions are defined inside `kani_lib.c`. | ||
/// For now, we blindly trust that the definitions in `kani_lib.c` are kept in sync with the | ||
/// declarations from the standard library, provided here: | ||
/// <https://stdrs.dev/nightly/x86_64-unknown-linux-gnu/alloc/alloc/index.html> | ||
static ref RUST_ALLOC_FNS: HashSet<InternedString> = { | ||
HashSet::from([ | ||
"__rust_alloc".into(), | ||
"__rust_alloc_zeroed".into(), | ||
"__rust_dealloc".into(), | ||
"__rust_realloc".into(), | ||
]) | ||
}; | ||
} | ||
|
||
impl<'tcx> GotocCtx<'tcx> { | ||
/// Generate the symbol and symbol table entry for foreign items. | ||
/// | ||
/// CBMC built-in functions that are supported by Kani are always added to the symbol table, and | ||
/// this function will return them. | ||
/// | ||
/// For other foreign items, we declare a shim and add to the list of foreign shims to be | ||
/// handled later. | ||
pub fn codegen_foreign_fn(&mut self, instance: Instance<'tcx>) -> &Symbol { | ||
debug!(?instance, "codegen_foreign_function"); | ||
let fn_name = self.symbol_name(instance).intern(); | ||
if self.symbol_table.contains(fn_name) { | ||
// Symbol has been added (either a built-in CBMC function or a Rust allocation function). | ||
self.symbol_table.lookup(fn_name).unwrap() | ||
} else if RUST_ALLOC_FNS.contains(&fn_name) | ||
|| (self.is_cffi_enabled() && kani_middle::fn_abi(self.tcx, instance).conv == Conv::C) | ||
{ | ||
// Add a Rust alloc lib function as is declared by core. | ||
// When C-FFI feature is enabled, we just trust the rust declaration. | ||
// TODO: Add proper casting and clashing definitions check. | ||
// https://github.com/model-checking/kani/issues/1350 | ||
// https://github.com/model-checking/kani/issues/2426 | ||
self.ensure(fn_name, |gcx, _| { | ||
let typ = gcx.codegen_ffi_type(instance); | ||
Symbol::function( | ||
fn_name, | ||
typ, | ||
None, | ||
gcx.readable_instance_name(instance), | ||
Location::none(), | ||
) | ||
.with_is_extern(true) | ||
}) | ||
} else { | ||
let shim_name = format!("{fn_name}_ffi_shim"); | ||
trace!(?shim_name, "codegen_foreign_function"); | ||
self.ensure(&shim_name, |gcx, _| { | ||
// Generate a shim with an unsupported C-FFI error message. | ||
let typ = gcx.codegen_ffi_type(instance); | ||
Symbol::function( | ||
&shim_name, | ||
typ, | ||
Some(gcx.codegen_ffi_shim(shim_name.as_str().into(), instance)), | ||
gcx.readable_instance_name(instance), | ||
Location::none(), | ||
) | ||
}) | ||
} | ||
} | ||
|
||
/// Checks whether C-FFI has been enabled or not. | ||
/// When enabled, we blindly encode the function type as is. | ||
fn is_cffi_enabled(&self) -> bool { | ||
self.queries.get_unstable_features().contains(&"c-ffi".to_string()) | ||
} | ||
|
||
/// Generate code for a foreign function shim. | ||
fn codegen_ffi_shim(&mut self, shim_name: InternedString, instance: Instance<'tcx>) -> Stmt { | ||
debug!(?shim_name, ?instance, sym=?self.symbol_table.lookup(shim_name), "generate_foreign_shim"); | ||
|
||
let loc = self.codegen_span(&self.tcx.def_span(instance.def_id())); | ||
let unsupported_check = self.codegen_ffi_unsupported(instance, loc); | ||
Stmt::block(vec![unsupported_check], loc) | ||
} | ||
|
||
/// Generate type for the given foreign instance. | ||
fn codegen_ffi_type(&mut self, instance: Instance<'tcx>) -> Type { | ||
let fn_name = self.symbol_name(instance); | ||
let fn_abi = kani_middle::fn_abi(self.tcx, instance); | ||
let loc = self.codegen_span(&self.tcx.def_span(instance.def_id())); | ||
let params = fn_abi | ||
.args | ||
.iter() | ||
.enumerate() | ||
.filter_map(|(idx, arg)| { | ||
(!arg.is_ignore()).then(|| { | ||
let arg_name = format!("{fn_name}::param_{idx}"); | ||
let base_name = format!("param_{idx}"); | ||
let arg_type = self.codegen_ty(arg.layout.ty); | ||
let sym = Symbol::variable(&arg_name, &base_name, arg_type.clone(), loc) | ||
.with_is_parameter(true); | ||
self.symbol_table.insert(sym); | ||
arg_type.as_parameter(Some(arg_name.into()), Some(base_name.into())) | ||
}) | ||
}) | ||
.collect(); | ||
let ret_type = self.codegen_ty(fn_abi.ret.layout.ty); | ||
|
||
if fn_abi.c_variadic { | ||
Type::variadic_code(params, ret_type) | ||
} else { | ||
Type::code(params, ret_type) | ||
} | ||
} | ||
|
||
/// Kani does not currently support FFI functions except for built-in CBMC functions. | ||
/// | ||
/// This will behave like `codegen_unimplemented_stmt` but print a message that includes | ||
/// the name of the function not supported and the calling convention. | ||
pub fn codegen_ffi_unsupported(&mut self, instance: Instance<'tcx>, loc: Location) -> Stmt { | ||
let fn_name = &self.symbol_name(instance); | ||
debug!(?fn_name, ?loc, "codegen_ffi_unsupported"); | ||
|
||
// Save this occurrence so we can emit a warning in the compilation report. | ||
let entry = self.unsupported_constructs.entry("foreign function".into()).or_default(); | ||
entry.push(loc); | ||
|
||
let call_conv = kani_middle::fn_abi(self.tcx, instance).conv; | ||
let msg = format!("call to foreign \"{call_conv:?}\" function `{fn_name}`"); | ||
let url = if call_conv == Conv::C { | ||
"https://github.com/model-checking/kani/issues/2423" | ||
} else { | ||
"https://github.com/model-checking/kani/issues/new/choose" | ||
}; | ||
self.codegen_assert_assume( | ||
Expr::bool_false(), | ||
PropertyClass::UnsupportedConstruct, | ||
&GotocCtx::unsupported_msg(&msg, Some(url)), | ||
loc, | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
|
||
mod assert; | ||
mod block; | ||
mod foreign_function; | ||
mod function; | ||
mod intrinsic; | ||
mod operand; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# Copyright Kani Contributors | ||
# SPDX-License-Identifier: Apache-2.0 OR MIT | ||
|
||
[package] | ||
name = "libc_checks" | ||
version = "0.1.0" | ||
edition = "2021" | ||
description = "Test integration with libc" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
libc = "0.2" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
warning: Found the following unsupported constructs:\ | ||
- foreign function | ||
Failed Checks: call to foreign "C" function `pthread_key_create` is not currently supported by Kani. Please post your example at https://github.com/model-checking/kani/issues/2423 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
// Copyright Kani Contributors | ||
// SPDX-License-Identifier: Apache-2.0 OR MIT | ||
|
||
pub mod pthread_key_create; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// Copyright Kani Contributors | ||
// SPDX-License-Identifier: Apache-2.0 OR MIT | ||
// | ||
//! Check that Kani doesn't crash if it invokes calls to pthread_key_create. | ||
//! | ||
//! The declaration of `pthread_key_create` in the libc crate differs in type from the C one. | ||
//! The rust libc crate uses `Option<unsafe extern "C" fn(*mut u8)>` to represent the optional | ||
//! destructor, while the C one takes a function pointer. | ||
//! | ||
//! See <https://github.com/model-checking/kani/issues/1781> for more details. | ||
//! | ||
//! Until we add full support to C-FFI, functions that are not explicitly declared in the Kani | ||
//! compiler setup will be codegen as unsupported. | ||
//! | ||
//! This test ensures that a harness only fails during verification if the call is reachable. | ||
use libc; | ||
|
||
#[kani::proof] | ||
pub fn check_create() { | ||
let mut key = 0; | ||
let _res = unsafe { libc::pthread_key_create(&mut key, None) }; | ||
} |
Oops, something went wrong.