Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 77 additions & 3 deletions c2rust-rust-tools/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use itertools::Itertools;
use log::warn;
use std::collections::HashSet;
use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::io;
use std::io::Write;
use std::path::Path;
use std::process::Command;
use std::str;
use std::str::FromStr;

use crate::RustEdition::Edition2021;
Expand Down Expand Up @@ -146,6 +150,7 @@ pub struct Rustc<'a> {
edition: RustEdition,
crate_name: Option<&'a str>,
expect_error: bool,
imported_crates: &'a [&'a str],
}

pub fn rustc(rs_path: &Path) -> Rustc {
Expand All @@ -154,6 +159,7 @@ pub fn rustc(rs_path: &Path) -> Rustc {
edition: Default::default(),
crate_name: None,
expect_error: false,
imported_crates: Default::default(),
}
}

Expand All @@ -176,20 +182,39 @@ impl<'a> Rustc<'a> {
}
}

pub fn expect_unresolved_imports(self, imported_crates: &'a [&'a str]) -> Self {
Self {
imported_crates,
..self
}
}

pub fn run(self) {
let Self {
rs_path,
edition,
crate_name,
expect_error,
imported_crates,
} = self;
let crate_name =
crate_name.unwrap_or_else(|| rs_path.file_stem().unwrap().to_str().unwrap());
run_rustc(rs_path, edition, crate_name, expect_error);
run_rustc(rs_path, edition, crate_name, expect_error, &imported_crates);
}
}

fn run_rustc(rs_path: &Path, edition: RustEdition, crate_name: &str, expect_error: bool) {
fn run_rustc(
rs_path: &Path,
edition: RustEdition,
crate_name: &str,
expect_error: bool,
imported_crates: &[&str],
) {
let rs = fs_err::read_to_string(rs_path).unwrap();
for imported_crate in imported_crates {
assert!(rs.contains(&format!("::{imported_crate}")));
}

// There's no good way to not create an output with `rustc`,
// so just create an `.rlib` and then delete it immediately.
let rlib_path = rs_path.with_file_name(format!("lib{crate_name}.rlib"));
Expand All @@ -206,10 +231,59 @@ fn run_rustc(rs_path: &Path, edition: RustEdition, crate_name: &str, expect_erro
"-o",
]);
cmd.args([&rlib_path, rs_path]);
let status = cmd.status().unwrap();
let output = cmd.output().unwrap();
let status = output.status;
if status.success() {
fs_err::remove_file(&rlib_path).unwrap();
}
io::stdout().write_all(&output.stdout).unwrap();
io::stderr().write_all(&output.stderr).unwrap();

// Using rustc itself to build snapshots that reference crates (like libc) is difficult because we don't know
// the appropriate --extern libc=/path/to/liblibc-XXXXXXXXXXXXXXXX.rlib to pass.
// Skip for now, as we've already compared the literal text,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment ("skip for now") doesn't make sense anymore as compilation is no longer being skipped.

// and we're checking the error messages here.
let stderr = str::from_utf8(&output.stderr).unwrap();
let mut error_lines = stderr
.split('\n')
// .split(|&b| b == b'\n')
.filter(|line| line.starts_with("error[E"))
.collect::<HashSet<_>>();
dbg!(&error_lines);
for imported_crate in imported_crates {
// For `::{imported_crate}::*`.
let absolute_path = match edition {
Edition2021 => format!("error[E0433]: failed to resolve: could not find `{imported_crate}` in the list of imported crates"),
Edition2024 => format!("error[E0433]: cannot find `{imported_crate}` in the crate root"),
};
Comment on lines +256 to +258
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that we should parse the error code from the message and filter on that plus the mentioned library name. Hard-coding the whole error message seems very brittle.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do I parse the library name, though, without hardcoding the error message? I agree it could be brittle, but it'd have a simple error and then we update the error message needed, so it's a simple fix if it's ever changed, too.

Copy link
Copy Markdown
Contributor

@fw-immunant fw-immunant Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the library name we do probably have to choose between something brittle (like hard-coding the error message) and something loose (like a simple string-contains check), but for the error code we should be able to parse in a somewhat robust way. E.g., in JSON output format it's given in its own "code" field. Even without JSON output, the "error[ENNNN]" prefix should be relatively stable.

// For `use ::{imported_crate}*`.
let absolute_use_path = format!("error[E0432]: unresolved import `{imported_crate}`");
// For `{imported_crate}::*`.
let relative_path = match edition {
Edition2021 => format!("error[E0433]: failed to resolve: use of undeclared crate or module `{imported_crate}`"),
Edition2024 => format!("error[E0433]: cannot find module or crate `{imported_crate}` in this scope"),
};

let absolute_path = absolute_path.as_str();
let absolute_use_path = absolute_use_path.as_str();
let relative_path = relative_path.as_str();
dbg!(absolute_path);
dbg!(absolute_use_path);
dbg!(relative_path);

// Pre-compute to avoid `||` short-circuiting.
let absolute_path = error_lines.remove(absolute_path);
let absolute_use_path = error_lines.remove(absolute_use_path);
let relative_path = error_lines.remove(relative_path);

assert!(absolute_path || absolute_use_path || relative_path);
}
if !imported_crates.is_empty() {
dbg!(&error_lines);
assert!(error_lines.is_empty());
return;
}

if expect_error {
assert!(
!status.success(),
Expand Down
38 changes: 24 additions & 14 deletions c2rust-transpile/tests/snapshots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ fn transpile_snapshot(
c_path: &Path,
edition: RustEdition,
expect_compile_error: bool,
imported_crates: &[&str],
) {
let cfg = config(edition);
compile_and_transpile_file(c_path, cfg);
Expand Down Expand Up @@ -133,21 +134,11 @@ fn transpile_snapshot(

insta::assert_snapshot!(snapshot_name, &rs, &debug_expr);

// Using rustc itself to build snapshots that reference libc is difficult because we don't know
// the appropriate --extern libc=/path/to/liblibc-XXXXXXXXXXXXXXXX.rlib to pass. Skip for now,
// as we've already compared the literal text.
if rs.contains("libc::") {
eprintln!(
"warning: skipping compiling {} with rustc since it depends on libc",
rs_path.display()
);
return;
}

rustc(&rs_path)
.edition(edition)
.crate_name(crate_name)
.expect_error(expect_compile_error)
.expect_unresolved_imports(imported_crates)
.run();
}

Expand All @@ -158,6 +149,7 @@ struct TranspileTest<'a> {
os_specific: bool,
expect_compile_error_edition_2021: bool,
expect_compile_error_edition_2024: bool,
imported_crates: Vec<&'a str>,
}

fn transpile(c_file_name: &str) -> TranspileTest {
Expand All @@ -167,6 +159,7 @@ fn transpile(c_file_name: &str) -> TranspileTest {
os_specific: false,
expect_compile_error_edition_2021: false,
expect_compile_error_edition_2024: false,
imported_crates: Default::default(),
}
}

Expand Down Expand Up @@ -211,13 +204,19 @@ impl<'a> TranspileTest<'a> {
.expect_compile_error_edition_2024(expect_error)
}

pub fn expect_unresolved_import(mut self, imported_crate: &'a str) -> Self {
self.imported_crates.push(imported_crate);
self
}

pub fn run(self) {
let Self {
c_file_name,
arch_specific,
os_specific,
expect_compile_error_edition_2021,
expect_compile_error_edition_2024,
imported_crates,
} = self;

let specific_dir_prefix = [arch_specific.then_some("arch"), os_specific.then_some("os")]
Expand Down Expand Up @@ -268,12 +267,14 @@ impl<'a> TranspileTest<'a> {
&c_path,
Edition2021,
expect_compile_error_edition_2021,
&imported_crates,
);
transpile_snapshot(
&platform,
&c_path,
Edition2024,
expect_compile_error_edition_2024,
&imported_crates,
);
}
}
Expand Down Expand Up @@ -451,7 +452,10 @@ fn test_rotate_os_specific() {

#[test]
fn test_sigign() {
transpile("sigign.c").os_specific(true).run();
transpile("sigign.c")
.os_specific(true)
.expect_unresolved_import("libc")
.run();
}

#[test]
Expand All @@ -461,12 +465,18 @@ fn test_typedefidx() {

#[test]
fn test_types() {
transpile("types.c").os_specific(true).run();
transpile("types.c")
.os_specific(true)
.expect_unresolved_import("libc")
.run();
}

#[test]
fn test_wide_strings() {
transpile("wide_strings.c").os_specific(true).run();
transpile("wide_strings.c")
.os_specific(true)
.expect_unresolved_import("libc")
.run();
}

// arch-os-specific
Expand Down
Loading