From d7de704a9306756645405982027acf808a274011 Mon Sep 17 00:00:00 2001 From: Khyber Sen Date: Tue, 31 Oct 2023 15:41:55 -0700 Subject: [PATCH 1/6] `c2rust-{build-paths,instrument}`: Fix #708 by using `#[cfg(unix)]` instead of `if cfg!(unix)`. --- c2rust-build-paths/src/lib.rs | 7 +++++-- dynamic_instrumentation/src/main.rs | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/c2rust-build-paths/src/lib.rs b/c2rust-build-paths/src/lib.rs index 16828e610d..1f5bb1aff4 100644 --- a/c2rust-build-paths/src/lib.rs +++ b/c2rust-build-paths/src/lib.rs @@ -31,11 +31,14 @@ impl SysRoot { .split(|c| c.is_ascii_whitespace()) .next() .unwrap_or_default(); - let path = if cfg!(unix) { + #[cfg(unix)] + let path = { use std::os::unix::ffi::OsStrExt; OsStr::from_bytes(path) - } else { + }; + #[cfg(not(unix))] + let path = { // Windows is hard, so just require UTF-8 let path = str::from_utf8(path).expect("`rustc --print sysroot` is not UTF-8"); OsStr::new(path) diff --git a/dynamic_instrumentation/src/main.rs b/dynamic_instrumentation/src/main.rs index 328cc8e0da..46a2231287 100644 --- a/dynamic_instrumentation/src/main.rs +++ b/dynamic_instrumentation/src/main.rs @@ -127,11 +127,14 @@ fn resolve_sysroot() -> anyhow::Result { .split(|c| c.is_ascii_whitespace()) .next() .unwrap_or_default(); - let path = if cfg!(unix) { + #[cfg(unix)] + let path = { use std::os::unix::ffi::OsStrExt; OsStr::from_bytes(path) - } else { + }; + #[cfg(not(unix))] + let path = { // Windows is hard, so just require UTF-8 let path = std::str::from_utf8(path).context("`rustc --print sysroot` is not UTF-8")?; OsStr::new(path) From 93a3f50d6d523548bbb803376f6a64940d41f148 Mon Sep 17 00:00:00 2001 From: Khyber Sen Date: Tue, 31 Oct 2023 17:32:03 -0700 Subject: [PATCH 2/6] `c2rust-analyze`: Add dependencies needed for `cargo` wrapper. It was tricky to get exactly the right ones with the right MSRV. --- Cargo.lock | 7 +++++-- c2rust-analyze/Cargo.toml | 6 +++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 537547a3e2..7b036bdbc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,9 +77,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "arc-swap" @@ -204,6 +204,7 @@ dependencies = [ name = "c2rust-analyze" version = "0.18.0" dependencies = [ + "anyhow", "assert_matches", "backtrace", "bincode", @@ -212,6 +213,7 @@ dependencies = [ "c2rust-pdg", "clap 4.2.7", "env_logger", + "fs-err", "indexmap", "itertools", "libc", @@ -221,6 +223,7 @@ dependencies = [ "rustc-hash", "serde", "shlex", + "toml_edit", ] [[package]] diff --git a/c2rust-analyze/Cargo.toml b/c2rust-analyze/Cargo.toml index 0c0c63efff..720578e8b5 100644 --- a/c2rust-analyze/Cargo.toml +++ b/c2rust-analyze/Cargo.toml @@ -15,7 +15,7 @@ categories.workspace = true polonius-engine = "0.13.0" rustc-hash = "1.1.0" bitflags = "1.3.2" -c2rust-pdg = { path = "../pdg"} +c2rust-pdg = { path = "../pdg" } bincode = "1.0" serde = "1.0" assert_matches = "1.5.0" @@ -25,6 +25,10 @@ log = "0.4.17" backtrace = "0.3.67" itertools = "0.10" libc = "0.2.147" +clap = { version = "4.2.7", features = ["derive"] } +fs-err = "2.9.0" +anyhow = "1.0.75" +toml_edit = "0.19.8" [build-dependencies] c2rust-build-paths = { path = "../c2rust-build-paths", version = "0.18.0" } From cc7499db6d26bad7f51bfcd4c89872f46554e355 Mon Sep 17 00:00:00 2001 From: Khyber Sen Date: Wed, 1 Nov 2023 02:42:00 -0700 Subject: [PATCH 3/6] `c2rust-instrument`: Fix a few typos in error messages and docs. --- dynamic_instrumentation/src/main.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dynamic_instrumentation/src/main.rs b/dynamic_instrumentation/src/main.rs index 46a2231287..23d71e4e53 100644 --- a/dynamic_instrumentation/src/main.rs +++ b/dynamic_instrumentation/src/main.rs @@ -203,8 +203,9 @@ const METADATA_VAR: &str = "C2RUST_INSTRUMENT_METADATA_PATH"; /// Read a [`PathBuf`] from the [`mod@env`]ironment that should've been set by the [`cargo_wrapper`]. fn env_path_from_wrapper(var: &str) -> anyhow::Result { - let path = env::var_os(var) - .ok_or_else(|| anyhow!("the `cargo` wrapper should've `${var}` for the `rustc` wrapper"))?; + let path = env::var_os(var).ok_or_else(|| { + anyhow!("the `cargo` wrapper should've set `${{${var}}}` for the `rustc` wrapper") + })?; Ok(path.into()) } @@ -253,7 +254,7 @@ fn bin_crate_name() -> Option { /// it doesn't specify `--target`. /// /// This would work more robustly if we were also instrumenting dependencies, -/// as our currently solution would no longer work, but we aren't. +/// as our current solution would no longer work, but we aren't. /// /// On the other hand, the `--target` solution has a drawback /// in that there are many ways to specify the target: From 0a412427a6963a96d88435c85304fa93ee51e016 Mon Sep 17 00:00:00 2001 From: Khyber Sen Date: Thu, 2 Nov 2023 23:32:12 -0700 Subject: [PATCH 4/6] `c2rust-instrument`: Clarify docs. --- dynamic_instrumentation/src/main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dynamic_instrumentation/src/main.rs b/dynamic_instrumentation/src/main.rs index 23d71e4e53..0e9c5574b6 100644 --- a/dynamic_instrumentation/src/main.rs +++ b/dynamic_instrumentation/src/main.rs @@ -57,7 +57,7 @@ struct Args { #[clap(long)] set_runtime: bool, - /// Set `$RUSTFLAGS` for the instrumented `cargo`. + /// Set `$RUSTFLAGS` for the wrapped `cargo`. /// /// This allows setting `$RUSTFLAGS` for the inner `cargo` when `c2rust-instrument` is invoked via `cargo run`, for example. /// If `$RUSTFLAGS` is already set, these `--rustflags` are appended with a space. @@ -85,8 +85,8 @@ struct InterceptedCargoArgs { #[clap(long, value_parser)] manifest_path: Option, - /// Need this so `--` is allowed. - /// Not actually used. + /// Need this so `--` is allowed. Not actually used, + /// as we're just intercepting a few args and passing the rest through. extra_args: Vec, } @@ -105,7 +105,7 @@ fn exit_with_status(status: ExitStatus) { /// Note that the sysroot contains the toolchain and host target name, /// but this has no effect on cross-compiling. /// Every toolchain's `rustc` is able to itself cross-compile. -/// I'm not sure why the host target needs to be in the sysroot directory name, but it is. +/// It's unclear why the host target needs to be in the sysroot directory name, but it is. /// /// Also note that this sysroot lookup should be done at runtime, /// not at compile-time in the `build.rs`, From d6209fd46bf2f17056e1afa87b41d4664c1d3039 Mon Sep 17 00:00:00 2001 From: Khyber Sen Date: Wed, 1 Nov 2023 02:49:16 -0700 Subject: [PATCH 5/6] `c2rust-analyze`: Add a `cargo` wrapper so it can be invoked like `cargo` on a whole crate. This copies the approach from `c2rust-instrument`. The main difference are: * We don't care about the instrumentation and metadata `c2rust-instrument` need. * We still allow `c2rust-analyze` to be called as a `rustc_wrapper` directly. More specifically, the `cargo` wrapper is supposed to set `RUST_SYSROOT`. `c2rust-instrument` requires this, while `c2rust-analyze` will re-calculate it if it wasn't. This allows us to keep using the `rustc` wrapper in tests, as the tests are all set up as single files meant to be compiled with `rustc` directly. We can change this, but that'll come later. For full crates we test like `lighttpd-minimal`, we'll definitely switch to the `cargo` wrapper, at the very least for a proof of concept. --- c2rust-analyze/src/main.rs | 345 ++++++++++++++++++++++++++++- c2rust-analyze/tests/common/mod.rs | 7 + 2 files changed, 345 insertions(+), 7 deletions(-) diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index cd15ff91d1..9e2dbae20c 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -34,18 +34,349 @@ mod util; use crate::log::init_logger; use analyze::AnalysisCallbacks; +use anyhow::anyhow; +use anyhow::ensure; +use anyhow::Context; +use clap::Parser; use rustc_driver::RunCompiler; +use rustc_driver::TimePassesCallbacks; +use rustc_session::config::CrateType; +use std::borrow::Borrow; use std::env; +use std::ffi::OsStr; +use std::ffi::OsString; +use std::iter; +use std::path::Path; +use std::path::PathBuf; +use std::process; +use std::process::Command; +use std::process::ExitStatus; -fn main() -> rustc_interface::interface::Result<()> { - init_logger(); +/// Statically analyze and try to lift to safe Rust. +#[derive(Debug, Parser)] +#[clap(author, version, about, long_about = None)] +struct Args { + /// Set `$RUSTFLAGS` for the wrapped `cargo`. + /// + /// This allows setting `$RUSTFLAGS` for the inner `cargo` when `c2rust-analyze` is invoked via `cargo run`, for example. + /// If `$RUSTFLAGS` is already set, these `--rustflags` are appended with a space. + // + // The reason this exists is twofold: + // + // 1. It would be convenient if `cargo` itself had such a `--rustflags` argument, + // so at least we can recreate it here ourselves. + // + // 2. If this binary is invoked by something like `cargo run --bin c2rust-analyze`, + // then it's impossible to set `$RUSTFLAGS` only for the inner `cargo` that you want to add them to + // without also adding them to the outer `cargo run` `cargo`. + // `--rustflags` lets you do that easily. + #[clap(long)] + rustflags: Option, + + /// `cargo` args. + cargo_args: Vec, +} + +/// `cargo` args that we intercept. +#[derive(Debug, Parser)] +#[clap(ignore_errors = true)] +struct InterceptedCargoArgs { + #[clap(long, value_parser)] + manifest_path: Option, + + /// Need this so `--` is allowed. Not actually used, + /// as we're just intercepting a few args and passing the rest through. + extra_args: Vec, +} + +fn exit_with_status(status: ExitStatus) { + process::exit(status.code().unwrap_or(1)) +} + +/// Resolve the current `rustc` sysroot using `rustc --print sysroot`. +/// +/// Normally, `rustc` looks up the sysroot by the location of its own binary. +/// This works because the `rustc` on `$PATH` is actually `rustup`, +/// and `rustup` invokes the real `rustc`, which is in a location relative to the sysroot. +/// As we invoke `rustc_driver` directly here, we are `rustc`, +/// and thus we have to explicitly specify the sysroot that the real `rustc` would normally use. +/// +/// Note that the sysroot contains the toolchain and host target name, +/// but this has no effect on cross-compiling. +/// Every toolchain's `rustc` is able to itself cross-compile. +/// It's unclear why the host target needs to be in the sysroot directory name, but it is. +/// +/// Also note that this sysroot lookup should be done at runtime, +/// not at compile-time in the `build.rs`, +/// as the toolchain locations could be different +/// from where this binary was compiled and where it is running +/// (it could be on a different machine with a different `$RUSTUP_HOME`). +/// +/// TODO(kkysen) deduplicate this with `c2rust_build_paths::SysRoot::resolve` +fn resolve_sysroot() -> anyhow::Result { + let rustc = env::var_os("RUSTC").unwrap_or_else(|| "rustc".into()); + let output = Command::new(rustc) + .args(&["--print", "sysroot"]) + .output() + .context("could not invoke `rustc` to find rust sysroot")?; + // trim, but `str::trim` doesn't exist on `[u8]` + let path = output + .stdout + .as_slice() + .split(|c| c.is_ascii_whitespace()) + .next() + .unwrap_or_default(); + #[cfg(unix)] + let path = { + use std::os::unix::ffi::OsStrExt; + + OsStr::from_bytes(path) + }; + #[cfg(not(unix))] + let path = { + // Windows is hard, so just require UTF-8 + let path = std::str::from_utf8(path).context("`rustc --print sysroot` is not UTF-8")?; + OsStr::new(path) + }; + let path = Path::new(path).to_owned(); + // `rustc` reports a million errors if the sysroot is wrong, so try to check first. + ensure!( + path.is_dir(), + "invalid sysroot (not a dir): {}", + path.display() + ); + Ok(path) +} + +struct Cargo { + path: PathBuf, +} + +impl Cargo { + pub fn new() -> Self { + let path = env::var_os("CARGO") + .unwrap_or_else(|| "cargo".into()) + .into(); + Self { path } + } + + pub fn command(&self) -> Command { + Command::new(&self.path) + } + + pub fn run(&self, f: impl FnOnce(&mut Command) -> anyhow::Result<()>) -> anyhow::Result<()> { + let mut cmd = self.command(); + f(&mut cmd)?; + let status = cmd.status()?; + if !status.success() { + eprintln!("error ({status}) running: {cmd:?}"); + exit_with_status(status); + } + Ok(()) + } +} + +const RUSTC_WRAPPER_VAR: &str = "RUSTC_WRAPPER"; +const RUST_SYSROOT_VAR: &str = "RUST_SYSROOT"; + +/// Read a [`PathBuf`] from the [`mod@env`]ironment that should've been set by the [`cargo_wrapper`]. +fn env_path_from_wrapper(var: &str) -> anyhow::Result { + let path = env::var_os(var).ok_or_else(|| { + anyhow!("the `cargo` wrapper should've set `${{${var}}}` for the `rustc` wrapper") + })?; + Ok(path.into()) +} + +/// Check if the current [`rustc_wrapper`] invocation is for the primary `cargo` package, +/// as determined by `$CARGO_PRIMARY_PACKAGE`. +fn is_primary_package() -> bool { + env::var("CARGO_PRIMARY_PACKAGE").is_ok() +} + +/// Check if the current [`rustc_wrapper`] invocation is a binary crate, +/// i.e., if `--crate-type bin` was specified. +/// +/// This uses the [`rustc_driver`] and [`rustc_session`] APIs +/// to check this exactly as `rustc` would. +fn is_bin_crate(at_args: &[String]) -> anyhow::Result { + let args = rustc_driver::args::arg_expand_all(at_args); + let matches = rustc_driver::handle_options(&args) + .ok_or_else(|| anyhow!("failed to parse `rustc` args"))?; + let session_options = rustc_session::config::build_session_options(&matches); + let is_bin = session_options.crate_types.contains(&CrateType::Executable); + Ok(is_bin) +} - let dont_catch = env::var_os("C2RUST_ANALYZE_TEST_DONT_CATCH_PANIC").is_some(); - if !dont_catch { - panic_detail::set_hook(); +/// Read the name of the current binary crate being compiled, if it is a binary crate ([`is_bin_crate`]). +/// +/// Note that despite setting `--crate-type bin` and [`is_bin_crate`] being true, +/// there is no name set for build scripts. +/// That's how we can detect them. +fn bin_crate_name() -> Option { + env::var_os("CARGO_BIN_NAME").map(PathBuf::from) +} + +/// Detect if the current [`rustc_wrapper`] is for compiling a build script. +/// +/// We check if it is a build script by checking if it's a `--crate-type bin` +/// and yet has no `$CARGO_BIN_NAME`, which is set for normal binary crates. +/// +/// Another solution (that `miri` uses) is to always specify the `--target`, +/// even if it's the host target. Then `cargo` thinks it's cross-compiling, +/// and always forwards `--target` to `rustc` for native compilations, +/// but for host compilations like build scripts and proc-macros, +/// it doesn't specify `--target`. +/// +/// This would work more robustly if we were also analyzing dependencies, +/// as our current solution would no longer work, but we aren't. +/// +/// On the other hand, the `--target` solution has a drawback +/// in that there are many ways to specify the target: +/// * `--target` +/// * `$CARGO_BUILD_TARGET` +/// * `targets` in `rust-toolchain.toml` +/// * `targets` in `.cargo/config.toml` +/// * and maybe some other places as well +/// All the resolution is in `cargo`, but we have to decide +/// if we're going to supply `--target $HOST` before `cargo` runs, +/// so we have to check all of those places ourselves to make sure +/// that we're not overriding the true cross-compilation the user wants. +/// +/// Compared to the current solution, this seems harder, +/// so we're sticking with the current solution for now as long as it works. +fn is_build_script(at_args: &[String]) -> anyhow::Result { + Ok(bin_crate_name().is_none() && is_bin_crate(at_args)?) +} + +/// Run as a `rustc` wrapper (a la `$RUSTC_WRAPPER`/[`RUSTC_WRAPPER_VAR`]). +fn rustc_wrapper() -> anyhow::Result<()> { + let mut at_args = env::args().skip(1).collect::>(); + // We also want to avoid proc-macro crates, + // but those must be separate crates, so we should be okay. + let is_primary_compilation = is_primary_package() && !is_build_script(&at_args)?; + + let sysroot = env_path_from_wrapper(RUST_SYSROOT_VAR).or_else(|_| resolve_sysroot())?; + let sysroot = sysroot + .as_path() + .to_str() + .ok_or_else(|| anyhow!("sysroot path is not UTF-8: {}", sysroot.display()))?; + at_args.extend(["--sysroot".into(), sysroot.into()]); + let result = if is_primary_compilation { + let dont_catch = env::var_os("C2RUST_ANALYZE_TEST_DONT_CATCH_PANIC").is_some(); + if !dont_catch { + panic_detail::set_hook(); + } + + RunCompiler::new(&at_args, &mut AnalysisCallbacks).run() + } else { + // Always use the dynamically linked `librustc_driver-{hash}.so`, + // as it is guaranteed to be the same version as the instrumented version. + // Furthermore, we can't accidentally load the wrong `librustc_driver-{hash}.so`, + // as it contains its hash. + // This also avoids an extra `rustc` (and potentially `rustup` `rustc`) invocation. + RunCompiler::new(&at_args, &mut TimePassesCallbacks::default()).run() + }; + // `ErrorReported` means the error has already been reported to the user, + // so we just have to fail/exit with a failing exit code. + // There is no `impl Error for ErrorReported`. + result.map_err(|_| anyhow!("`rustc` failed"))?; + Ok(()) +} + +/// Set `$RUST_TOOLCHAIN` to the toolchain channel specified in `rust-toolchain.toml`. +/// This ensures that we use a toolchain compatible with the `rustc` private crates that we linked to. +fn set_rust_toolchain() -> anyhow::Result<()> { + let toml = include_str!("../rust-toolchain.toml"); + // Couldn't find an `include_toml!` macro to do this at compile time. + let doc = toml.parse::()?; + let channel = doc["toolchain"]["channel"].as_str(); + if let Some(toolchain) = channel { + env::set_var("RUSTUP_TOOLCHAIN", toolchain); } + Ok(()) +} + +trait OsStringJoin { + fn join(&mut self, sep: &OsStr) -> OsString; +} + +impl OsStringJoin for I +where + I: Iterator, + T: Borrow, +{ + fn join(&mut self, sep: &OsStr) -> OsString { + match self.next() { + None => OsString::new(), + Some(first_elt) => { + // estimate lower bound of capacity needed + let (lower, _) = self.size_hint(); + let mut result = OsString::with_capacity(sep.len() * lower); + result.push(first_elt.borrow()); + self.for_each(|elt| { + result.push(sep); + result.push(elt.borrow()); + }); + result + } + } + } +} - let args = env::args().collect::>(); +/// Run as a `cargo` wrapper/plugin, the default invocation. +fn cargo_wrapper(rustc_wrapper: &Path) -> anyhow::Result<()> { + let Args { + rustflags, + cargo_args, + } = Args::parse(); - RunCompiler::new(&args, &mut AnalysisCallbacks).run() + let args_for_cargo = + iter::once(OsStr::new("cargo")).chain(cargo_args.iter().map(OsString::as_os_str)); + let InterceptedCargoArgs { + manifest_path, + extra_args: _, + } = InterceptedCargoArgs::parse_from(args_for_cargo); + + let manifest_path = manifest_path.as_deref(); + let _manifest_dir = manifest_path.and_then(|path| path.parent()); + + set_rust_toolchain()?; + + // Resolve the sysroot once in the [`cargo_wrapper`] + // so that we don't need all of the [`rustc_wrapper`]s to have to do it. + let sysroot = resolve_sysroot()?; + + let cargo = Cargo::new(); + + cargo.run(|cmd| { + let rustflags = [ + env::var_os("RUSTFLAGS"), + Some("-A warnings".into()), + rustflags, + ] + .into_iter() + .flatten() + .join(OsStr::new(" ")); + + cmd.args(cargo_args) + .env(RUSTC_WRAPPER_VAR, rustc_wrapper) + .env(RUST_SYSROOT_VAR, &sysroot) + .env("RUSTFLAGS", &rustflags); + Ok(()) + })?; + + Ok(()) +} + +fn main() -> anyhow::Result<()> { + init_logger(); + + let own_exe = env::current_exe()?; + + let wrapping_rustc = env::var_os(RUSTC_WRAPPER_VAR).as_deref() == Some(own_exe.as_os_str()); + if wrapping_rustc { + rustc_wrapper() + } else { + cargo_wrapper(&own_exe) + } } diff --git a/c2rust-analyze/tests/common/mod.rs b/c2rust-analyze/tests/common/mod.rs index dbc801ee92..40ee08ba17 100644 --- a/c2rust-analyze/tests/common/mod.rs +++ b/c2rust-analyze/tests/common/mod.rs @@ -162,6 +162,13 @@ impl Analyze { let output_stderr = File::try_clone(&output_stdout).unwrap(); let mut cmd = Command::new(&self.path); + + // Simulate an invocation by [`cargo_wrapper`]. + cmd.env("RUSTC_WRAPPER", &self.path); // Run as `rustc`, not `cargo`. + cmd.env("CARGO_PRIMARY_PACKAGE", ""); // `cargo` sets this. + cmd.env("CARGO_BIN_NAME", rs_path.file_name().unwrap()); // `cargo` sets this. + cmd.arg("rustc"); // `cargo` passes this to `$RUSTC_WRAPPER`. + if !args.catch_panics { cmd.env("C2RUST_ANALYZE_TEST_DONT_CATCH_PANIC", "1"); } From f071ea7ee91efe3b6d07b82a8cee5e4a5dc69b38 Mon Sep 17 00:00:00 2001 From: Khyber Sen Date: Wed, 1 Nov 2023 02:53:41 -0700 Subject: [PATCH 6/6] `c2rust-analyze/tests`: Convert the `lighttpd_minimal` test to the use the `cargo` wrapper. This is done mostly as a proof of concept for now. Ideally we'd want to add it as a method on `Analyze`/`FileCheck` and handle error reporting better. Now that `lighttpd-minimal` is run with `cargo`, we don't need the `extern crate libc;` anymore. --- .gitignore | 1 + analysis/tests/lighttpd-minimal/src/main.rs | 2 -- c2rust-analyze/tests/analyze.rs | 31 +++++++++++++++++++-- c2rust-analyze/tests/common/mod.rs | 4 +++ 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 26642bbd37..18c446d98c 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ marks.*.json # Outputs of `c2rust-analyze` inspect/ *.analysis.txt +analysis.txt diff --git a/analysis/tests/lighttpd-minimal/src/main.rs b/analysis/tests/lighttpd-minimal/src/main.rs index 74ed4d85d7..04d9d936ac 100644 --- a/analysis/tests/lighttpd-minimal/src/main.rs +++ b/analysis/tests/lighttpd-minimal/src/main.rs @@ -6,8 +6,6 @@ #![allow(unused_variables)] #![feature(extern_types)] -extern crate libc; - use libc::*; use std::mem; diff --git a/c2rust-analyze/tests/analyze.rs b/c2rust-analyze/tests/analyze.rs index 12d9bb4ca3..3cbb0a9566 100644 --- a/c2rust-analyze/tests/analyze.rs +++ b/c2rust-analyze/tests/analyze.rs @@ -1,6 +1,13 @@ pub mod common; -use crate::common::{check_for_missing_tests_for, test_dir_for, Analyze, CrateOptions, CrateType}; +use crate::common::check_for_missing_tests_for; +use crate::common::test_dir_for; +use crate::common::Analyze; +use crate::common::CrateOptions; +use crate::common::CrateType; +use fs_err::File; +use std::path::Path; +use std::process::Command; #[test] fn check_for_missing_tests() { @@ -38,7 +45,27 @@ define_tests! { #[test] fn lighttpd_minimal() { - Analyze::resolve().run("../analysis/tests/lighttpd-minimal/src/main.rs"); + let analyze = Analyze::resolve(); + let mut cmd = Command::new(analyze.path()); + + cmd.arg("--"); + + cmd.arg("check"); + + let dir = Path::new("../analysis/tests/lighttpd-minimal"); + let manifest_path = dir.join("Cargo.toml"); + cmd.arg("--manifest-path").arg(manifest_path); + + let output_path = dir.join("analysis.txt"); + let output_stdout = File::create(&output_path).unwrap(); + let output_stderr = File::try_clone(&output_stdout).unwrap(); + cmd.stdout(output_stdout.into_parts().0) + .stderr(output_stderr.into_parts().0); + + let status = cmd.status().unwrap(); + assert!(status.success()); + + // TODO(kkysen) Handle error reporting better like [`Analyze::run`]. } #[test] diff --git a/c2rust-analyze/tests/common/mod.rs b/c2rust-analyze/tests/common/mod.rs index 40ee08ba17..f63ea1d71c 100644 --- a/c2rust-analyze/tests/common/mod.rs +++ b/c2rust-analyze/tests/common/mod.rs @@ -139,6 +139,10 @@ impl Analyze { Self { path } } + pub fn path(&self) -> &Path { + &self.path + } + fn run_with_( &self, rs_path: &Path,