diff --git a/.gitignore b/.gitignore index 185ff4f756..c1d3d33576 100644 --- a/.gitignore +++ b/.gitignore @@ -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-* diff --git a/Cargo.lock b/Cargo.lock index b0601ba3d4..084b7b27a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 39bc9185db..d6d005ac36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index c7a3200dbd..8e96338a86 100644 --- a/README.md +++ b/README.md @@ -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=` 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=` 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 ``, and can be processed diff --git a/build.rs b/build.rs new file mode 100644 index 0000000000..c8121aa973 --- /dev/null +++ b/build.rs @@ -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); +} diff --git a/miri b/miri index 84bd748170..19f1a987ac 100755 --- a/miri +++ b/miri @@ -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 diff --git a/src/bin/miri.rs b/src/bin/miri.rs index ca0787b229..fa6a307038 100644 --- a/src/bin/miri.rs +++ b/src/bin/miri.rs @@ -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); diff --git a/src/eval.rs b/src/eval.rs index 12e895852d..511163a2e7 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -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, /// 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, } impl Default for MiriConfig { @@ -159,6 +163,7 @@ impl Default for MiriConfig { preemption_rate: 0.01, // 1% report_progress: None, retag_fields: false, + external_so_file: None, } } } diff --git a/src/machine.rs b/src/machine.rs index 7357731f35..841c1343fa 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -358,10 +358,14 @@ pub struct Evaluator<'mir, 'tcx> { pub(crate) report_progress: Option, /// 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 @@ impl<'mir, 'tcx> Evaluator<'mir, '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(), + ) + }), } } diff --git a/src/shims/ffi_support.rs b/src/shims/ffi_support.rs new file mode 100644 index 0000000000..f1ae1e7d3f --- /dev/null +++ b/src/shims/ffi_support.rs @@ -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, + 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>, + ) -> 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::(ptr, libffi_args.as_slice()); + this.write_int(x, dest)?; + return Ok(()); + } + ty::Int(IntTy::I16) => { + let x = ffi::call::(ptr, libffi_args.as_slice()); + this.write_int(x, dest)?; + return Ok(()); + } + ty::Int(IntTy::I32) => { + let x = ffi::call::(ptr, libffi_args.as_slice()); + this.write_int(x, dest)?; + return Ok(()); + } + ty::Int(IntTy::I64) => { + let x = ffi::call::(ptr, libffi_args.as_slice()); + this.write_int(x, dest)?; + return Ok(()); + } + ty::Int(IntTy::Isize) => { + let x = ffi::call::(ptr, libffi_args.as_slice()); + // `isize` doesn't `impl Into`, 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::(ptr, libffi_args.as_slice()); + this.write_int(x, dest)?; + return Ok(()); + } + ty::Uint(UintTy::U16) => { + let x = ffi::call::(ptr, libffi_args.as_slice()); + this.write_int(x, dest)?; + return Ok(()); + } + ty::Uint(UintTy::U32) => { + let x = ffi::call::(ptr, libffi_args.as_slice()); + this.write_int(x, dest)?; + return Ok(()); + } + ty::Uint(UintTy::U64) => { + let x = ffi::call::(ptr, libffi_args.as_slice()); + this.write_int(x, dest)?; + return Ok(()); + } + ty::Uint(UintTy::Usize) => { + let x = ffi::call::(ptr, libffi_args.as_slice()); + // `usize` doesn't `impl Into`, 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 { + 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::::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::::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::>>(); + + // 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), + } + } +} diff --git a/src/shims/foreign_items.rs b/src/shims/foreign_items.rs index 960dbfe6ed..b8862b3ba4 100644 --- a/src/shims/foreign_items.rs +++ b/src/shims/foreign_items.rs @@ -23,6 +23,7 @@ use rustc_target::{ 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 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx ) -> 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 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx 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) } diff --git a/src/shims/mod.rs b/src/shims/mod.rs index 6182955924..ee2145db7e 100644 --- a/src/shims/mod.rs +++ b/src/shims/mod.rs @@ -1,6 +1,7 @@ #![warn(clippy::integer_arithmetic)] mod backtrace; +pub mod ffi_support; pub mod foreign_items; pub mod intrinsics; pub mod unix; diff --git a/tests/compiletest.rs b/tests/compiletest.rs index b38edc9e0f..fe0d9be28c 100644 --- a/tests/compiletest.rs +++ b/tests/compiletest.rs @@ -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(()) } diff --git a/tests/extern-so/fail/function_not_in_so.rs b/tests/extern-so/fail/function_not_in_so.rs new file mode 100644 index 0000000000..3aaeb632ca --- /dev/null +++ b/tests/extern-so/fail/function_not_in_so.rs @@ -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 + } +} diff --git a/tests/extern-so/fail/function_not_in_so.stderr b/tests/extern-so/fail/function_not_in_so.stderr new file mode 100644 index 0000000000..8ff9ca74bc --- /dev/null +++ b/tests/extern-so/fail/function_not_in_so.stderr @@ -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 + diff --git a/tests/extern-so/libcode.version b/tests/extern-so/libcode.version new file mode 100644 index 0000000000..0f04b9aaeb --- /dev/null +++ b/tests/extern-so/libcode.version @@ -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: *; +}; diff --git a/tests/extern-so/pass/call_extern_c_fcts.rs b/tests/extern-so/pass/call_extern_c_fcts.rs new file mode 100644 index 0000000000..1e1d0b11e9 --- /dev/null +++ b/tests/extern-so/pass/call_extern_c_fcts.rs @@ -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(); + } +} diff --git a/tests/extern-so/pass/call_extern_c_fcts.stdout b/tests/extern-so/pass/call_extern_c_fcts.stdout new file mode 100644 index 0000000000..7ba13d2d7b --- /dev/null +++ b/tests/extern-so/pass/call_extern_c_fcts.stdout @@ -0,0 +1 @@ +printing from C diff --git a/tests/extern-so/test.c b/tests/extern-so/test.c new file mode 100644 index 0000000000..68714f1743 --- /dev/null +++ b/tests/extern-so/test.c @@ -0,0 +1,27 @@ +#include + +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; +}