From b784a1ca79d9f683cd96369637993068de678a3a Mon Sep 17 00:00:00 2001 From: cezarbbb Date: Wed, 29 Apr 2026 20:21:02 +0800 Subject: [PATCH] When archive format is wrong produce an error instead of ICE --- .../rustc_codegen_ssa/src/back/archive.rs | 80 ++++++++++++++++++- compiler/rustc_codegen_ssa/src/errors.rs | 12 +++ .../run-make/archive-corrupt-error/corrupt.a | 3 + tests/run-make/archive-corrupt-error/lib.rs | 3 + tests/run-make/archive-corrupt-error/rmake.rs | 21 +++++ tests/run-make/archive-format-error/lib.rs | 3 + tests/run-make/archive-format-error/native.c | 1 + tests/run-make/archive-format-error/rmake.rs | 22 +++++ 8 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 tests/run-make/archive-corrupt-error/corrupt.a create mode 100644 tests/run-make/archive-corrupt-error/lib.rs create mode 100644 tests/run-make/archive-corrupt-error/rmake.rs create mode 100644 tests/run-make/archive-format-error/lib.rs create mode 100644 tests/run-make/archive-format-error/native.c create mode 100644 tests/run-make/archive-format-error/rmake.rs diff --git a/compiler/rustc_codegen_ssa/src/back/archive.rs b/compiler/rustc_codegen_ssa/src/back/archive.rs index 3f12e857391b2..728049a841ef8 100644 --- a/compiler/rustc_codegen_ssa/src/back/archive.rs +++ b/compiler/rustc_codegen_ssa/src/back/archive.rs @@ -9,7 +9,7 @@ use ar_archive_writer::{ ArchiveKind, COFFShortExport, MachineTypes, NewArchiveMember, write_archive_to_stream, }; pub use ar_archive_writer::{DEFAULT_OBJECT_READER, ObjectReader}; -use object::read::archive::ArchiveFile; +use object::read::archive::{ArchiveFile, ArchiveKind as ObjectArchiveKind}; use object::read::macho::FatArch; use rustc_data_structures::fx::FxIndexSet; use rustc_data_structures::memmap::Mmap; @@ -320,6 +320,50 @@ pub trait ArchiveBuilder { fn build(self: Box, output: &Path) -> bool; } +fn target_archive_format_to_object_kind(format: &str) -> Option { + match format { + "gnu" => Some(ObjectArchiveKind::Gnu), + "bsd" => Some(ObjectArchiveKind::Bsd), + "darwin" => Some(ObjectArchiveKind::Bsd64), + "coff" => Some(ObjectArchiveKind::Coff), + "aix_big" => Some(ObjectArchiveKind::AixBig), + _ => None, + } +} + +fn archive_kinds_compatible(actual: ObjectArchiveKind, expected: ObjectArchiveKind) -> bool { + if actual == expected { + return true; + } + matches!( + (actual, expected), + // An archive without long filenames or symbol table is detected as Unknown; + // this is compatible with any target format. + (ObjectArchiveKind::Unknown, _) + // 64-bit symbol table variants are compatible with their 32-bit counterparts + | (ObjectArchiveKind::Gnu64, ObjectArchiveKind::Gnu) + | (ObjectArchiveKind::Gnu, ObjectArchiveKind::Gnu64) + | (ObjectArchiveKind::Bsd64, ObjectArchiveKind::Bsd) + | (ObjectArchiveKind::Bsd, ObjectArchiveKind::Bsd64) + // GNU and COFF archives share the same magic and member header format; + // only the symbol table layout differs. + | (ObjectArchiveKind::Gnu, ObjectArchiveKind::Coff) + | (ObjectArchiveKind::Coff, ObjectArchiveKind::Gnu) + | (ObjectArchiveKind::Gnu64, ObjectArchiveKind::Coff) + ) +} + +fn archive_kind_display_name(kind: ObjectArchiveKind) -> String { + match kind { + ObjectArchiveKind::Gnu | ObjectArchiveKind::Gnu64 => "GNU".to_string(), + ObjectArchiveKind::Bsd => "BSD".to_string(), + ObjectArchiveKind::Bsd64 => "Darwin".to_string(), + ObjectArchiveKind::Coff => "COFF".to_string(), + ObjectArchiveKind::AixBig => "AIX big".to_string(), + _ => format!("{kind:?}"), + } +} + pub struct ArArchiveBuilderBuilder; impl ArchiveBuilderBuilder for ArArchiveBuilderBuilder { @@ -420,6 +464,19 @@ impl<'a> ArchiveBuilder for ArArchiveBuilder<'a> { .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; let archive_index = self.src_archives.len(); + if let Some(expected_kind) = + target_archive_format_to_object_kind(&self.sess.target.archive_format) + { + let actual_kind = archive.kind(); + if !archive_kinds_compatible(actual_kind, expected_kind) { + self.sess.dcx().emit_warn(crate::errors::IncompatibleArchiveFormat { + path: archive_path.clone(), + actual: archive_kind_display_name(actual_kind), + expected: archive_kind_display_name(expected_kind), + }); + } + } + for entry in archive.members() { let entry = entry.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; let file_name = String::from_utf8(entry.name().to_vec()) @@ -482,9 +539,24 @@ impl<'a> ArArchiveBuilder<'a> { match entry { ArchiveEntry::FromArchive { archive_index, file_range } => { let src_archive = &self.src_archives[archive_index]; - - let data = &src_archive.1 - [file_range.0 as usize..file_range.0 as usize + file_range.1 as usize]; + let archive_data = &src_archive.1; + let start = file_range.0 as usize; + let end = start + file_range.1 as usize; + let Some(data) = archive_data.get(start..end) else { + return Err(io_error_context( + "invalid archive member", + io::Error::new( + io::ErrorKind::InvalidData, + format!( + "archive member at offset {start} with size {} \ + exceeds archive size {} in `{}`", + file_range.1, + archive_data.len(), + src_archive.0.display(), + ), + ), + )); + }; Box::new(data) as Box> } diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs index 82a6525adcc99..789108cbb29a7 100644 --- a/compiler/rustc_codegen_ssa/src/errors.rs +++ b/compiler/rustc_codegen_ssa/src/errors.rs @@ -705,6 +705,18 @@ pub(crate) struct UnknownArchiveKind<'a> { pub kind: &'a str, } +#[derive(Diagnostic)] +#[diag("archive `{$path}` was built as {$actual} format, but the target expects {$expected}")] +#[help( + "this often occurs when using BSD-format archive tools on a Linux target; \ + rebuild the archive with the correct format for the target platform" +)] +pub(crate) struct IncompatibleArchiveFormat { + pub path: PathBuf, + pub actual: String, + pub expected: String, +} + #[derive(Diagnostic)] #[diag("linking static libraries is not supported for BPF")] pub(crate) struct BpfStaticlibNotSupported; diff --git a/tests/run-make/archive-corrupt-error/corrupt.a b/tests/run-make/archive-corrupt-error/corrupt.a new file mode 100644 index 0000000000000..1e6232305b839 --- /dev/null +++ b/tests/run-make/archive-corrupt-error/corrupt.a @@ -0,0 +1,3 @@ +! +corrupt.o/ 0 0 0 100644 10000 ` +small_data diff --git a/tests/run-make/archive-corrupt-error/lib.rs b/tests/run-make/archive-corrupt-error/lib.rs new file mode 100644 index 0000000000000..9412884815b74 --- /dev/null +++ b/tests/run-make/archive-corrupt-error/lib.rs @@ -0,0 +1,3 @@ +extern "C" { + fn foo() -> i32; +} diff --git a/tests/run-make/archive-corrupt-error/rmake.rs b/tests/run-make/archive-corrupt-error/rmake.rs new file mode 100644 index 0000000000000..d18659ca638a4 --- /dev/null +++ b/tests/run-make/archive-corrupt-error/rmake.rs @@ -0,0 +1,21 @@ +// Regression test for https://github.com/rust-lang/rust/issues/148217 +// A corrupt archive with member offset exceeding file boundary should produce +// an error, not an ICE. + +//@ ignore-cross-compile + +use run_make_support::{path, rfs, rustc, static_lib_name}; + +fn main() { + rfs::create_dir("archive"); + rfs::copy("corrupt.a", path("archive").join(static_lib_name("corrupt"))); + rustc() + .input("lib.rs") + .crate_type("rlib") + .library_search_path("archive") + .arg("-lstatic=corrupt") + .run_fail() + .assert_stderr_not_contains("panicked") + .assert_stderr_not_contains("unexpectedly panicked") + .assert_stderr_contains("invalid archive member"); +} diff --git a/tests/run-make/archive-format-error/lib.rs b/tests/run-make/archive-format-error/lib.rs new file mode 100644 index 0000000000000..9412884815b74 --- /dev/null +++ b/tests/run-make/archive-format-error/lib.rs @@ -0,0 +1,3 @@ +extern "C" { + fn foo() -> i32; +} diff --git a/tests/run-make/archive-format-error/native.c b/tests/run-make/archive-format-error/native.c new file mode 100644 index 0000000000000..bf7759e11ea9d --- /dev/null +++ b/tests/run-make/archive-format-error/native.c @@ -0,0 +1 @@ +int foo() { return 42; } diff --git a/tests/run-make/archive-format-error/rmake.rs b/tests/run-make/archive-format-error/rmake.rs new file mode 100644 index 0000000000000..1d217f05b8d36 --- /dev/null +++ b/tests/run-make/archive-format-error/rmake.rs @@ -0,0 +1,22 @@ +// Regression test for https://github.com/rust-lang/rust/issues/148217 +// BSD format archive on a Linux target should emit a format mismatch warning. + +//@ ignore-cross-compile +//@ only-linux + +use run_make_support::{cc, llvm_ar, path, rfs, rustc, static_lib_name}; + +fn main() { + rfs::create_dir("archive"); + + cc().arg("-c").input("native.c").output("archive/native.o").run(); + let bsd_archive = path("archive").join(static_lib_name("native_bsd")); + llvm_ar().arg("rcus").arg("--format=bsd").output_input(&bsd_archive, "archive/native.o").run(); + rustc() + .input("lib.rs") + .crate_type("rlib") + .library_search_path("archive") + .arg("-lstatic=native_bsd") + .run() + .assert_stderr_contains("was built as BSD format, but the target expects GNU"); +}