diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index 8879c175da2f9..ee49d314887c7 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -947,20 +947,36 @@ impl CStore { } fn inject_panic_runtime(&mut self, tcx: TyCtxt<'_>, krate: &ast::Crate) { + let is_std = attr::contains_name(&krate.attrs, sym::needs_panic_runtime); // If we're only compiling an rlib, then there's no need to select a // panic runtime, so we just skip this section entirely. let only_rlib = tcx.crate_types().iter().all(|ct| *ct == CrateType::Rlib); - if only_rlib { + if only_rlib && !is_std { info!("panic runtime injection skipped, only generating rlib"); return; } + let desired_strategy = tcx.sess.panic_strategy(); + let name = match desired_strategy { + PanicStrategy::Unwind => sym::panic_unwind, + PanicStrategy::Abort => sym::panic_abort, + PanicStrategy::ImmediateAbort => { + // Immediate-aborting panics don't use a runtime. + return; + } + }; + // If we need a panic runtime, we try to find an existing one here. At // the same time we perform some general validation of the DAG we've got // going such as ensuring everything has a compatible panic strategy. - let mut needs_panic_runtime = attr::contains_name(&krate.attrs, sym::needs_panic_runtime); - for (_cnum, data) in self.iter_crate_data() { + let mut found_panic_runtime = None; + let mut needs_panic_runtime = is_std; + for (cnum, data) in self.iter_crate_data() { needs_panic_runtime |= data.needs_panic_runtime(); + + if data.is_panic_runtime() && data.name() == name { + found_panic_runtime = Some(cnum) + } } // If we just don't need a panic runtime at all, then we're done here @@ -969,6 +985,21 @@ impl CStore { return; } + // The panic runtime may already be resolved as a `std` dependency via `resolve_crate_deps`. + // + // For `build-std=always`, we avoid injecting it again as a direct dependency, because + // Cargo relies on loading panic runtimes via the `-Ldependency` search paths. + // We know that the panic runtime injected during the `std` build is the correct one + // since Cargo passes the same `-Cpanic=` option to all crates. + // + // For prebuilt `std` it doesn't matter whether the runtime is injected directly or indirectly. + if let Some(found_panic_runtime) = found_panic_runtime { + self.injected_panic_runtime = Some(found_panic_runtime); + return; + } + + info!("panic runtime not found -- loading {}", name); + // By this point we know that we need a panic runtime. Here we just load // an appropriate default runtime for our panic strategy. // @@ -978,17 +1009,7 @@ impl CStore { // Also note that we have yet to perform validation of the crate graph // in terms of everyone has a compatible panic runtime format, that's // performed later as part of the `dependency_format` module. - let desired_strategy = tcx.sess.panic_strategy(); - let name = match desired_strategy { - PanicStrategy::Unwind => sym::panic_unwind, - PanicStrategy::Abort => sym::panic_abort, - PanicStrategy::ImmediateAbort => { - // Immediate-aborting panics don't use a runtime. - return; - } - }; - info!("panic runtime not found -- loading {}", name); - + // // This has to be conditional as both panic_unwind and panic_abort may be present in the // crate graph at the same time. One of them will later be activated in dependency_formats. let Some(cnum) = self.resolve_crate( @@ -1002,12 +1023,14 @@ impl CStore { }; let cdata = self.get_crate_data(cnum); - // Sanity check the loaded crate to ensure it is indeed a panic runtime - // and the panic strategy is indeed what we thought it was. + // Sanity check the loaded crate to ensure it is indeed a panic runtime. if !cdata.is_panic_runtime() { tcx.dcx().emit_err(diagnostics::CrateNotPanicRuntime { crate_name: name }); } - if cdata.required_panic_strategy() != Some(desired_strategy) { + // Check the `panic_abort` was compiled with `-Cpanic=abort`. + if desired_strategy == PanicStrategy::Abort + && cdata.required_panic_strategy() != Some(PanicStrategy::Abort) + { tcx.dcx().emit_err(diagnostics::NoPanicStrategy { crate_name: name, strategy: desired_strategy, diff --git a/tests/run-make-cargo/panic-strategies/rmake.rs b/tests/run-make-cargo/panic-strategies/rmake.rs new file mode 100644 index 0000000000000..b53476655d03e --- /dev/null +++ b/tests/run-make-cargo/panic-strategies/rmake.rs @@ -0,0 +1,64 @@ +// This test ensures we are able to compile -Zbuild-std=std with multiple panic strategies. +// +//@ needs-target-std + +use run_make_support::tempfile::TempDir; +use run_make_support::{cargo, rfs}; + +fn main() { + // This is a regression test to ensure that rustc doesn't load `panic_abort` + // from the sysroot. See rust-lang/cargo#7359 + test("abort"); + + // The `panic_abort` crate must be compiled with the `-Cpanic=abort` option + // and the compiler has a check to enforce this. However `build-std` + // does not yet respect the `std` profile, it may lead to a mismatch: + // - Cargo profile sets `panic = "unwind"` -> `panic_abort` is not activated (linked). + // - But `panic_abort` is still compiled with `-Cpanic=unwind`. + // + // This test ensures that the check is not triggered in such a situation. + // + // FIXME(build-std): ideally, `panic_abort` should always be compiled with + // `-Cpanic=abort`, even when unused. + test("unwind"); + + test("immediate-abort"); +} + +fn test(panic: &'static str) { + let dir = TempDir::new().unwrap(); + + let manifest = manifest(panic); + rfs::write(dir.path().join("Cargo.toml"), &manifest); + rfs::write(dir.path().join("main.rs"), "fn main() {}"); + + let mut args = vec!["build", "--release", "-Zbuild-std=std"]; + if panic == "immediate-abort" { + args.push("-Zpanic-immediate-abort"); + } + cargo() + .current_dir(dir.path()) + .args(&args) + .env("RUSTC_BOOTSTRAP", "1") + // Visual Studio 2022 requires that the LIB env var be set so it can + // find the Windows SDK. + .env("LIB", std::env::var("LIB").unwrap_or_default()) + .run(); +} + +fn manifest(panic: &'static str) -> String { + format!( + r#"[package] +name = "foo" +version = "0.1.0" +edition = "2024" + +[[bin]] +name = "foo" +path = "main.rs" + +[profile.release] +panic = "{panic}" +"# + ) +} diff --git a/tests/run-make/locate-panic-runtime/core.rs b/tests/run-make/locate-panic-runtime/core.rs new file mode 100644 index 0000000000000..ed65cbecc0560 --- /dev/null +++ b/tests/run-make/locate-panic-runtime/core.rs @@ -0,0 +1,21 @@ +// We are core. +#![feature(lang_items, no_core)] +#![allow(internal_features)] +#![no_std] +#![no_core] +#![crate_type = "rlib"] + +#[lang = "panic_info"] +pub struct PanicInfo {} + +#[lang = "copy"] +pub trait Copy: Sized {} + +#[lang = "pointee_sized"] +pub trait PointeeSized {} + +#[lang = "meta_sized"] +pub trait MetaSized: PointeeSized {} + +#[lang = "sized"] +pub trait Sized: MetaSized {} diff --git a/tests/run-make/locate-panic-runtime/lib.rs b/tests/run-make/locate-panic-runtime/lib.rs new file mode 100644 index 0000000000000..e85363beaf408 --- /dev/null +++ b/tests/run-make/locate-panic-runtime/lib.rs @@ -0,0 +1,11 @@ +#![feature(no_core)] +#![no_std] +#![no_core] +#![crate_type = "dylib"] + +extern crate std; + +#[panic_handler] +fn panic(_: &std::PanicInfo) -> ! { + loop {} +} diff --git a/tests/run-make/locate-panic-runtime/panic_abort.rs b/tests/run-make/locate-panic-runtime/panic_abort.rs new file mode 100644 index 0000000000000..f681b3386853a --- /dev/null +++ b/tests/run-make/locate-panic-runtime/panic_abort.rs @@ -0,0 +1,9 @@ +// We are panic runtime. +#![feature(panic_runtime, no_core)] +#![allow(internal_features)] +#![no_std] +#![no_core] +#![panic_runtime] +#![crate_type = "rlib"] + +extern crate core; diff --git a/tests/run-make/locate-panic-runtime/rmake.rs b/tests/run-make/locate-panic-runtime/rmake.rs new file mode 100644 index 0000000000000..3a53e4051b6cf --- /dev/null +++ b/tests/run-make/locate-panic-runtime/rmake.rs @@ -0,0 +1,66 @@ +// This test makes sure that the injected panic runtime can be loaded from +// `-L dependency=` paths as per RFC 3874 (build-std=always). +// +// Note: We have two possible panic runtime crates: the built one and the one from +// the sysroot. Sysroot lookup is disabled via the `--sysroot=` override to verify +// that we can load the correct one. +// +// `--emit=llvm-ir` is used to avoid running the linker. + +use run_make_support::{path, rfs, rust_lib_name, rustc}; + +fn main() { + rfs::create_dir("panic_abort"); + + // Compile `core`. + rustc().input("core.rs").panic("abort").sysroot("./no_exists").run(); + + // Compile `panic_abort` into a separate directory to prevent it from being + // found via `-L .` + rustc() + .input("panic_abort.rs") + .panic("abort") + .out_dir("panic_abort") + .sysroot("./no_exists") + .run(); + + // Compile `std`. + rustc() + .input("std.rs") + .extern_("panic_abort", &path("panic_abort").join(rust_lib_name("panic_abort"))) + .panic("abort") + .sysroot("./no_exists") + .run(); + + // Compile the final artifact. The panic runtime cannot be located without the + // `-Ldependency=` option. + rustc() + .input("lib.rs") + .arg("-Cpanic=abort") + .sysroot("./no_exists") + .emit("llvm-ir") + .run_fail() + .assert_stderr_contains("can't find crate for `panic_abort`"); + + // Compile the final artifact. The panic runtime cannot be located via + // `-Lcrate=` paths (This means that the panic runtime is not direct + // dependency). + rustc() + .input("lib.rs") + .arg("-Cpanic=abort") + .sysroot("./no_exists") + .library_search_path(format!("crate={}", path("panic_abort").display())) + .emit("llvm-ir") + .run_fail() + .assert_stderr_contains("can't find crate for `panic_abort`"); + + // Compile the final artifact. The panic runtime can be located via + // `-Ldependency=` paths. + rustc() + .input("lib.rs") + .arg("-Cpanic=abort") + .sysroot("./no_exists") + .library_search_path(format!("dependency={}", path("panic_abort").display())) + .emit("llvm-ir") + .run(); +} diff --git a/tests/run-make/locate-panic-runtime/std.rs b/tests/run-make/locate-panic-runtime/std.rs new file mode 100644 index 0000000000000..63eb75eca50a2 --- /dev/null +++ b/tests/run-make/locate-panic-runtime/std.rs @@ -0,0 +1,11 @@ +// We are std. +#![feature(needs_panic_runtime, no_core)] +#![allow(internal_features)] +#![no_std] +#![no_core] +// Tell rustc to inject panic runtime. +#![needs_panic_runtime] +#![crate_type = "rlib"] + +extern crate core; +pub use core::*;