Auto merge of #2363 - emarteca:int-function-args-returns, r=oli-obk

Adding support for external C functions that have integer (or empty) args and/or returns

Starts addressing `@https://github.com/rust-lang/miri/issues/11`

### Implementation
Adding support for calling external C functions that have any number of integer arguments (types of integers: `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`) and an integer return type (or `void`).
As suggested in `@https://github.com/rust-lang/miri/issues/11,` the [`libffi` crate](https://docs.rs/libffi/latest/libffi/index.html) is used to dispatch the calls to external C functions.

#### Modifications
Main modifications are to:
* [helper.rs](https://github.com/emarteca/miri/blob/int-function-args-returns/src/helpers.rs) : adding a function `call_and_add_external_c_fct_to_context` to read the code pointer to the external C function, dispatch the call, and save the return in MIRI internal memory. Handles all conversions between MIRI and C values (using some macros, also defined in this file).
* [foreign_items.rs](https://github.com/emarteca/miri/blob/int-function-args-returns/src/shims/foreign_items.rs) : handles the calling of `call_and_add_external_c_fct_to_context` in [helper.rs](https://github.com/emarteca/miri/blob/int-function-args-returns/src/helpers.rs) when a foreign item is encountered. Also adds some structs to model C representations of arguments, and the signature of the external C call.

### Testing
Adds tests for the following external functions which are now supported:
* [int tests](https://github.com/emarteca/miri/blob/int-function-args-returns/tests/pass/external_C/int_c_tests.rs):
     - adds 2 to a provided int (no type of int specified, so autocasts)
     - takes the sum of its 12 arguments (tests stack spill)
     - adds 3 to a 16 bit int
     - adds an `i16` to an `i64`
     - returns -10 as an unsigned int
* [void tests](https://github.com/emarteca/miri/blob/int-function-args-returns/tests/pass/external_C/print_from_c.rs)
     - void function that prints from C

### Code review
The code in this PR was reviewed by `@maurer` on [another fork](https://github.com/maurer/miri/pull/1) -- thanks!
This commit is contained in:
bors 2022-08-26 08:51:11 +00:00
commit 64185014af
19 changed files with 565 additions and 4 deletions

8
.gitignore vendored
View File

@ -2,10 +2,18 @@ target
/doc
tex/*/out
*.dot
*.out
*.rs.bk
.vscode
*.mm_profdata
perf.data
perf.data.old
flamegraph.svg
<<<<<<< HEAD
=======
<<<<<<< HEAD
tests/extern-so/libtestlib.so
=======
>>>>>>> master
>>>>>>> 58ba05a0 (C FFI support for functions with int args and returns)
.auto-*

38
Cargo.lock generated
View File

@ -2,6 +2,12 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "abort_on_panic"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955f37ac58af2416bac687c8ab66a4ccba282229bd7422a28d2281a5e66a6116"
[[package]]
name = "addr2line"
version = "0.17.0"
@ -327,6 +333,36 @@ version = "0.2.112"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
[[package]]
name = "libffi"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e08093a2ddeee94bd0c830a53d895ff91f1f3bb0f9b3c8c6b00739cdf76bc1d"
dependencies = [
"abort_on_panic",
"libc",
"libffi-sys",
]
[[package]]
name = "libffi-sys"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab4106b7f09d7b87d021334d5618fac1dfcfb824d4c5fe111ff0074dfd242e15"
dependencies = [
"cc",
]
[[package]]
name = "libloading"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "lock_api"
version = "0.4.5"
@ -401,6 +437,8 @@ dependencies = [
"getrandom",
"lazy_static",
"libc",
"libffi",
"libloading",
"log",
"measureme",
"rand",

View File

@ -20,6 +20,8 @@ doctest = false # and no doc tests
[dependencies]
getrandom = { version = "0.2", features = ["std"] }
env_logger = "0.9"
libffi = "3.0.0"
libloading = "0.7"
log = "0.4"
shell-escape = "0.1.4"
rand = "0.8"

View File

@ -346,6 +346,17 @@ to Miri failing to detect cases of undefined behavior in a program.
this flag is **unsound**.
* `-Zmiri-disable-weak-memory-emulation` disables the emulation of some C++11 weak
memory effects.
* `-Zmiri-extern-so-file=<path to a shared object file>` is an experimental flag for providing support
for FFI calls. Functions not provided by that file are still executed via the usual Miri shims.
**WARNING**: If an invalid/incorrect `.so` file is specified, this can cause undefined behaviour in Miri itself!
And of course, Miri cannot do any checks on the actions taken by the external code.
Note that Miri has its own handling of file descriptors, so if you want to replace *some* functions
working on file descriptors, you will have to replace *all* of them, or the two kinds of
file descriptors will be mixed up.
This is **work in progress**; currently, only integer arguments and return values are
supported (and no, pointer/integer casts to work around this limitation will not work;
they will fail horribly).
Follow [the discussion on supporting other types](https://github.com/rust-lang/miri/issues/2365).
* `-Zmiri-measureme=<name>` enables `measureme` profiling for the interpreted program.
This can be used to find which parts of your program are executing slowly under Miri.
The profile is written out to a file with the prefix `<name>`, and can be processed

6
build.rs Normal file
View File

@ -0,0 +1,6 @@
fn main() {
// Re-export the TARGET environment variable so it can
// be accessed by miri.
let target = std::env::var("TARGET").unwrap();
println!("cargo:rustc-env=TARGET={:?}", target);
}

1
miri
View File

@ -108,6 +108,7 @@ esac
## Prepare the environment
# Determine some toolchain properties
# export the target so its available in miri
TARGET=$(rustc +$TOOLCHAIN --version --verbose | grep "^host:" | cut -d ' ' -f 2)
SYSROOT=$(rustc +$TOOLCHAIN --print sysroot)
LIBDIR=$SYSROOT/lib/rustlib/$TARGET/lib

View File

@ -530,6 +530,19 @@ fn main() {
"full" => BacktraceStyle::Full,
_ => show_error!("-Zmiri-backtrace may only be 0, 1, or full"),
};
} else if let Some(param) = arg.strip_prefix("-Zmiri-extern-so-file=") {
let filename = param.to_string();
if std::path::Path::new(&filename).exists() {
if let Some(other_filename) = miri_config.external_so_file {
panic!(
"-Zmiri-extern-so-file external SO file is already set to {}",
other_filename.display()
);
}
miri_config.external_so_file = Some(filename.into());
} else {
panic!("-Zmiri-extern-so-file path {} does not exist", filename);
}
} else {
// Forward to rustc.
rustc_args.push(arg);

View File

@ -3,6 +3,7 @@
use std::ffi::{OsStr, OsString};
use std::iter;
use std::panic::{self, AssertUnwindSafe};
use std::path::PathBuf;
use std::thread;
use log::info;
@ -128,6 +129,9 @@ pub struct MiriConfig {
pub report_progress: Option<u32>,
/// Whether Stacked Borrows retagging should recurse into fields of datatypes.
pub retag_fields: bool,
/// The location of a shared object file to load when calling external functions
/// FIXME! consider allowing users to specify paths to multiple SO files, or to a directory
pub external_so_file: Option<PathBuf>,
}
impl Default for MiriConfig {
@ -159,6 +163,7 @@ fn default() -> MiriConfig {
preemption_rate: 0.01, // 1%
report_progress: None,
retag_fields: false,
external_so_file: None,
}
}
}

View File

@ -358,10 +358,14 @@ pub struct Evaluator<'mir, 'tcx> {
pub(crate) report_progress: Option<u32>,
/// The number of blocks that passed since the last progress report.
pub(crate) since_progress_report: u32,
/// Handle of the optional shared object file for external functions.
pub external_so_lib: Option<(libloading::Library, std::path::PathBuf)>,
}
impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
pub(crate) fn new(config: &MiriConfig, layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>) -> Self {
let target_triple = &layout_cx.tcx.sess.opts.target_triple.to_string();
let local_crates = helpers::get_local_crates(layout_cx.tcx);
let layouts =
PrimitiveLayouts::new(layout_cx).expect("Couldn't get layouts of primitive types");
@ -412,6 +416,24 @@ pub(crate) fn new(config: &MiriConfig, layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>)
preemption_rate: config.preemption_rate,
report_progress: config.report_progress,
since_progress_report: 0,
external_so_lib: config.external_so_file.as_ref().map(|lib_file_path| {
// Check if host target == the session target.
if option_env!("TARGET") == Some(target_triple) {
panic!(
"calling external C functions in linked .so file requires target and host to be the same"
);
}
// Note: it is the user's responsibility to provide a correct SO file.
// WATCH OUT: If an invalid/incorrect SO file is specified, this can cause
// undefined behaviour in Miri itself!
(
unsafe {
libloading::Library::new(lib_file_path)
.expect("Failed to read specified shared object file")
},
lib_file_path.clone(),
)
}),
}
}

291
src/shims/ffi_support.rs Normal file
View File

@ -0,0 +1,291 @@
use libffi::{high::call as ffi, low::CodePtr};
use std::ops::Deref;
use rustc_middle::ty::{self as ty, IntTy, Ty, UintTy};
use rustc_span::Symbol;
use rustc_target::abi::HasDataLayout;
use crate::*;
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
/// Extract the scalar value from the result of reading a scalar from the machine,
/// and convert it to a `CArg`.
fn scalar_to_carg(
k: ScalarMaybeUninit<Provenance>,
arg_type: Ty<'tcx>,
cx: &impl HasDataLayout,
) -> InterpResult<'tcx, CArg> {
match arg_type.kind() {
// If the primitive provided can be converted to a type matching the type pattern
// then create a `CArg` of this primitive value with the corresponding `CArg` constructor.
// the ints
ty::Int(IntTy::I8) => {
return Ok(CArg::Int8(k.to_i8()?));
}
ty::Int(IntTy::I16) => {
return Ok(CArg::Int16(k.to_i16()?));
}
ty::Int(IntTy::I32) => {
return Ok(CArg::Int32(k.to_i32()?));
}
ty::Int(IntTy::I64) => {
return Ok(CArg::Int64(k.to_i64()?));
}
ty::Int(IntTy::Isize) => {
// This will fail if host != target, but then the entire FFI thing probably won't work well
// in that situation.
return Ok(CArg::ISize(k.to_machine_isize(cx)?.try_into().unwrap()));
}
// the uints
ty::Uint(UintTy::U8) => {
return Ok(CArg::UInt8(k.to_u8()?));
}
ty::Uint(UintTy::U16) => {
return Ok(CArg::UInt16(k.to_u16()?));
}
ty::Uint(UintTy::U32) => {
return Ok(CArg::UInt32(k.to_u32()?));
}
ty::Uint(UintTy::U64) => {
return Ok(CArg::UInt64(k.to_u64()?));
}
ty::Uint(UintTy::Usize) => {
// This will fail if host != target, but then the entire FFI thing probably won't work well
// in that situation.
return Ok(CArg::USize(k.to_machine_usize(cx)?.try_into().unwrap()));
}
_ => {}
}
// If no primitives were returned then we have an unsupported type.
throw_unsup_format!(
"unsupported scalar argument type to external C function: {:?}",
arg_type
);
}
/// Call external C function and
/// store output, depending on return type in the function signature.
fn call_external_c_and_store_return<'a>(
&mut self,
link_name: Symbol,
dest: &PlaceTy<'tcx, Provenance>,
ptr: CodePtr,
libffi_args: Vec<libffi::high::Arg<'a>>,
) -> InterpResult<'tcx, ()> {
let this = self.eval_context_mut();
// Unsafe because of the call to external C code.
// Because this is calling a C function it is not necessarily sound,
// but there is no way around this and we've checked as much as we can.
unsafe {
// If the return type of a function is a primitive integer type,
// then call the function (`ptr`) with arguments `libffi_args`, store the return value as the specified
// primitive integer type, and then write this value out to the miri memory as an integer.
match dest.layout.ty.kind() {
// ints
ty::Int(IntTy::I8) => {
let x = ffi::call::<i8>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Int(IntTy::I16) => {
let x = ffi::call::<i16>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Int(IntTy::I32) => {
let x = ffi::call::<i32>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Int(IntTy::I64) => {
let x = ffi::call::<i64>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Int(IntTy::Isize) => {
let x = ffi::call::<isize>(ptr, libffi_args.as_slice());
// `isize` doesn't `impl Into<i128>`, so convert manually.
// Convert to `i64` since this covers both 32- and 64-bit machines.
this.write_int(i64::try_from(x).unwrap(), dest)?;
return Ok(());
}
// uints
ty::Uint(UintTy::U8) => {
let x = ffi::call::<u8>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Uint(UintTy::U16) => {
let x = ffi::call::<u16>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Uint(UintTy::U32) => {
let x = ffi::call::<u32>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Uint(UintTy::U64) => {
let x = ffi::call::<u64>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Uint(UintTy::Usize) => {
let x = ffi::call::<usize>(ptr, libffi_args.as_slice());
// `usize` doesn't `impl Into<i128>`, so convert manually.
// Convert to `u64` since this covers both 32- and 64-bit machines.
this.write_int(u64::try_from(x).unwrap(), dest)?;
return Ok(());
}
// Functions with no declared return type (i.e., the default return)
// have the output_type `Tuple([])`.
ty::Tuple(t_list) =>
if t_list.len() == 0 {
ffi::call::<()>(ptr, libffi_args.as_slice());
return Ok(());
},
_ => {}
}
// FIXME ellen! deal with all the other return types
throw_unsup_format!("unsupported return type to external C function: {:?}", link_name);
}
}
/// Get the pointer to the function of the specified name in the shared object file,
/// if it exists. The function must be in the shared object file specified: we do *not*
/// return pointers to functions in dependencies of the library.
fn get_func_ptr_explicitly_from_lib(&mut self, link_name: Symbol) -> Option<CodePtr> {
let this = self.eval_context_mut();
// Try getting the function from the shared library.
// On windows `_lib_path` will be unused, hence the name starting with `_`.
let (lib, _lib_path) = this.machine.external_so_lib.as_ref().unwrap();
let func: libloading::Symbol<'_, unsafe extern "C" fn()> = unsafe {
match lib.get(link_name.as_str().as_bytes()) {
Ok(x) => x,
Err(_) => {
return None;
}
}
};
// FIXME: this is a hack!
// The `libloading` crate will automatically load system libraries like `libc`.
// On linux `libloading` is based on `dlsym`: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#202
// and `dlsym`(https://linux.die.net/man/3/dlsym) looks through the dependency tree of the
// library if it can't find the symbol in the library itself.
// So, in order to check if the function was actually found in the specified
// `machine.external_so_lib` we need to check its `dli_fname` and compare it to
// the specified SO file path.
// This code is a reimplementation of the mechanism for getting `dli_fname` in `libloading`,
// from: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#411
// using the `libc` crate where this interface is public.
// No `libc::dladdr` on windows.
#[cfg(unix)]
let mut info = std::mem::MaybeUninit::<libc::Dl_info>::uninit();
#[cfg(unix)]
unsafe {
if libc::dladdr(*func.deref() as *const _, info.as_mut_ptr()) != 0 {
if std::ffi::CStr::from_ptr(info.assume_init().dli_fname).to_str().unwrap()
!= _lib_path.to_str().unwrap()
{
return None;
}
}
}
// Return a pointer to the function.
Some(CodePtr(*func.deref() as *mut _))
}
/// Call specified external C function, with supplied arguments.
/// Need to convert all the arguments from their hir representations to
/// a form compatible with C (through `libffi` call).
/// Then, convert return from the C call into a corresponding form that
/// can be stored in Miri internal memory.
fn call_external_c_fct(
&mut self,
link_name: Symbol,
dest: &PlaceTy<'tcx, Provenance>,
args: &[OpTy<'tcx, Provenance>],
) -> InterpResult<'tcx, bool> {
// Get the pointer to the function in the shared object file if it exists.
let code_ptr = match self.get_func_ptr_explicitly_from_lib(link_name) {
Some(ptr) => ptr,
None => {
// Shared object file does not export this function -- try the shims next.
return Ok(false);
}
};
let this = self.eval_context_mut();
// Get the function arguments, and convert them to `libffi`-compatible form.
let mut libffi_args = Vec::<CArg>::with_capacity(args.len());
for cur_arg in args.iter() {
libffi_args.push(Self::scalar_to_carg(
this.read_scalar(cur_arg)?,
cur_arg.layout.ty,
this,
)?);
}
// Convert them to `libffi::high::Arg` type.
let libffi_args = libffi_args
.iter()
.map(|cur_arg| cur_arg.arg_downcast())
.collect::<Vec<libffi::high::Arg<'_>>>();
// Call the function and store output, depending on return type in the function signature.
self.call_external_c_and_store_return(link_name, dest, code_ptr, libffi_args)?;
Ok(true)
}
}
#[derive(Debug, Clone)]
/// Enum of supported arguments to external C functions.
// We introduce this enum instead of just calling `ffi::arg` and storing a list
// of `libffi::high::Arg` directly, because the `libffi::high::Arg` just wraps a reference
// to the value it represents: https://docs.rs/libffi/latest/libffi/high/call/struct.Arg.html
// and we need to store a copy of the value, and pass a reference to this copy to C instead.
pub enum CArg {
/// 8-bit signed integer.
Int8(i8),
/// 16-bit signed integer.
Int16(i16),
/// 32-bit signed integer.
Int32(i32),
/// 64-bit signed integer.
Int64(i64),
/// isize.
ISize(isize),
/// 8-bit unsigned integer.
UInt8(u8),
/// 16-bit unsigned integer.
UInt16(u16),
/// 32-bit unsigned integer.
UInt32(u32),
/// 64-bit unsigned integer.
UInt64(u64),
/// usize.
USize(usize),
}
impl<'a> CArg {
/// Convert a `CArg` to a `libffi` argument type.
fn arg_downcast(&'a self) -> libffi::high::Arg<'a> {
match self {
CArg::Int8(i) => ffi::arg(i),
CArg::Int16(i) => ffi::arg(i),
CArg::Int32(i) => ffi::arg(i),
CArg::Int64(i) => ffi::arg(i),
CArg::ISize(i) => ffi::arg(i),
CArg::UInt8(i) => ffi::arg(i),
CArg::UInt16(i) => ffi::arg(i),
CArg::UInt32(i) => ffi::arg(i),
CArg::UInt64(i) => ffi::arg(i),
CArg::USize(i) => ffi::arg(i),
}
}
}

View File

@ -23,6 +23,7 @@
use super::backtrace::EvalContextExt as _;
use crate::helpers::{convert::Truncate, target_os_is_unix};
use crate::shims::ffi_support::EvalContextExt as _;
use crate::*;
/// Returned by `emulate_foreign_item_by_name`.
@ -31,7 +32,7 @@ pub enum EmulateByNameResult<'mir, 'tcx> {
NeedsJumping,
/// Jumping has already been taken care of.
AlreadyJumped,
/// A MIR body has been found for the function
/// A MIR body has been found for the function.
MirBody(&'mir mir::Body<'tcx>, ty::Instance<'tcx>),
/// The item is not supported.
NotSupported,
@ -369,6 +370,17 @@ fn emulate_foreign_item_by_name(
) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
let this = self.eval_context_mut();
// First deal with any external C functions in linked .so file
// (if any SO file is specified, and if the host target == the session target)
if this.machine.external_so_lib.as_ref().is_some() {
// An Ok(false) here means that the function being called was not exported
// by the specified SO file; we should continue and check if it corresponds to
// a provided shim.
if this.call_external_c_fct(link_name, dest, args)? {
return Ok(EmulateByNameResult::NeedsJumping);
}
}
// When adding a new shim, you should follow the following pattern:
// ```
// "shim_name" => {
@ -779,9 +791,8 @@ fn emulate_foreign_item_by_name(
target => throw_unsup_format!("the target `{}` is not supported", target),
}
};
// We only fall through to here if we did *not* hit the `_` arm above,
// i.e., if we actually emulated the function.
// i.e., if we actually emulated the function with one of the shims.
Ok(EmulateByNameResult::NeedsJumping)
}

View File

@ -1,6 +1,7 @@
#![warn(clippy::integer_arithmetic)]
mod backtrace;
pub mod ffi_support;
pub mod foreign_items;
pub mod intrinsics;
pub mod unix;

View File

@ -1,13 +1,42 @@
use colored::*;
use regex::Regex;
use std::path::{Path, PathBuf};
use std::{env, ffi::OsString};
use std::{env, ffi::OsString, process::Command};
use ui_test::{color_eyre::Result, Config, DependencyBuilder, Mode, OutputConflictHandling};
fn miri_path() -> PathBuf {
PathBuf::from(option_env!("MIRI").unwrap_or(env!("CARGO_BIN_EXE_miri")))
}
// Build the shared object file for testing external C function calls.
fn build_so_for_c_ffi_tests() -> PathBuf {
let cc = option_env!("CC").unwrap_or("cc");
// Target directory that we can write to.
let so_target_dir = Path::new(&env::var_os("CARGO_TARGET_DIR").unwrap()).join("miri-extern-so");
// Create the directory if it does not already exist.
std::fs::create_dir_all(&so_target_dir)
.expect("Failed to create directory for shared object file");
let so_file_path = so_target_dir.join("libtestlib.so");
let cc_output = Command::new(cc)
.args([
"-shared",
"-o",
so_file_path.to_str().unwrap(),
"tests/extern-so/test.c",
// Only add the functions specified in libcode.version to the shared object file.
// This is to avoid automatically adding `malloc`, etc.
// Source: https://anadoxin.org/blog/control-over-symbol-exports-in-gcc.html/
"-fPIC",
"-Wl,--version-script=tests/extern-so/libcode.version",
])
.output()
.expect("failed to generate shared object file for testing external C function calls");
if !cc_output.status.success() {
panic!("error in generating shared object file for testing external C function calls");
}
so_file_path
}
fn run_tests(
mode: Mode,
path: &str,
@ -40,6 +69,16 @@ fn run_tests(
flags.push(target.into());
}
// If we're on linux, and we're testing the extern-so functionality,
// then build the shared object file for testing external C function calls
// and push the relevant compiler flag.
if cfg!(target_os = "linux") && path.starts_with("tests/extern-so/") {
let so_file_path = build_so_for_c_ffi_tests();
let mut flag = std::ffi::OsString::from("-Zmiri-extern-so-file=");
flag.push(so_file_path.into_os_string());
flags.push(flag);
}
let skip_ui_checks = env::var_os("MIRI_SKIP_UI_CHECKS").is_some();
let output_conflict_handling = match (env::var_os("MIRI_BLESS").is_some(), skip_ui_checks) {
@ -176,6 +215,10 @@ fn main() -> Result<()> {
ui(Mode::Pass, "tests/pass-dep", WithDependencies)?;
ui(Mode::Panic, "tests/panic", WithDependencies)?;
ui(Mode::Fail, "tests/fail", WithDependencies)?;
if cfg!(target_os = "linux") {
ui(Mode::Pass, "tests/extern-so/pass", WithoutDependencies)?;
ui(Mode::Fail, "tests/extern-so/fail", WithDependencies)?;
}
Ok(())
}

View File

@ -0,0 +1,12 @@
//@only-target-linux
//@only-on-host
extern "C" {
fn foo();
}
fn main() {
unsafe {
foo(); //~ ERROR: unsupported operation: can't call foreign function: foo
}
}

View File

@ -0,0 +1,14 @@
error: unsupported operation: can't call foreign function: foo
--> $DIR/function_not_in_so.rs:LL:CC
|
LL | foo();
| ^^^^^ can't call foreign function: foo
|
= help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
= note: backtrace:
= note: inside `main` at $DIR/function_not_in_so.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -0,0 +1,9 @@
CODEABI_1.0 {
global: *add_one_int*;
*printer*;
*test_stack_spill*;
*get_unsigned_int*;
*add_int16*;
*add_short_to_long*;
local: *;
};

View File

@ -0,0 +1,46 @@
//@only-target-linux
//@only-on-host
extern "C" {
fn add_one_int(x: i32) -> i32;
fn add_int16(x: i16) -> i16;
fn test_stack_spill(
a: i32,
b: i32,
c: i32,
d: i32,
e: i32,
f: i32,
g: i32,
h: i32,
i: i32,
j: i32,
k: i32,
l: i32,
) -> i32;
fn add_short_to_long(x: i16, y: i64) -> i64;
fn get_unsigned_int() -> u32;
fn printer();
}
fn main() {
unsafe {
// test function that adds 2 to a provided int
assert_eq!(add_one_int(1), 3);
// test function that takes the sum of its 12 arguments
assert_eq!(test_stack_spill(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), 78);
// test function that adds 3 to a 16 bit int
assert_eq!(add_int16(-1i16), 2i16);
// test function that adds an i16 to an i64
assert_eq!(add_short_to_long(-1i16, 123456789123i64), 123456789122i64);
// test function that returns -10 as an unsigned int
assert_eq!(get_unsigned_int(), (-10i32) as u32);
// test void function that prints from C
printer();
}
}

View File

@ -0,0 +1 @@
printing from C

27
tests/extern-so/test.c Normal file
View File

@ -0,0 +1,27 @@
#include <stdio.h>
int add_one_int(int x) {
return 2 + x;
}
void printer() {
printf("printing from C\n");
}
// function with many arguments, to test functionality when some args are stored
// on the stack
int test_stack_spill(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l) {
return a+b+c+d+e+f+g+h+i+j+k+l;
}
unsigned int get_unsigned_int() {
return -10;
}
short add_int16(short x) {
return x + 3;
}
long add_short_to_long(short x, long y) {
return x + y;
}