From f0be14565fd331e9c96123dda33bf1105f35201c Mon Sep 17 00:00:00 2001 From: jyn Date: Sat, 29 Apr 2023 06:30:21 -0500 Subject: [PATCH 01/19] drive-by cleanup of rustdoc comment --- src/librustdoc/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index c15afca22611b..70030e293344e 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -156,13 +156,13 @@ pub fn main() { rustc_driver::install_ice_hook(); - // When using CI artifacts (with `download_stage1 = true`), tracing is unconditionally built + // When using CI artifacts with `download-rustc`, tracing is unconditionally built // with `--features=static_max_level_info`, which disables almost all rustdoc logging. To avoid // this, compile our own version of `tracing` that logs all levels. // NOTE: this compiles both versions of tracing unconditionally, because // - The compile time hit is not that bad, especially compared to rustdoc's incremental times, and - // - Otherwise, there's no warning that logging is being ignored when `download_stage1 = true`. - // NOTE: The reason this doesn't show double logging when `download_stage1 = false` and + // - Otherwise, there's no warning that logging is being ignored when `download-rustc` is enabled + // NOTE: The reason this doesn't show double logging when `download-rustc = false` and // `debug_logging = true` is because all rustc logging goes to its version of tracing (the one // in the sysroot), and all of rustdoc's logging goes to its version (the one in Cargo.toml). init_logging(); From 6edb4cae6517fe37f4f9de79d9122955b9b522f7 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Mon, 1 May 2023 18:05:05 +1000 Subject: [PATCH 02/19] Don't print backtrace on ICEs in `issue-86800.rs`. Because it then just has to be filtered out. This change makes this test more like these other tests: - tests/ui/treat-err-as-bug/err.rs - tests/ui/treat-err-as-bug/delay_span_bug.rs - tests/ui/mir/validate/storage-live.rs - tests/ui/associated-inherent-types/bugs/ice-substitution.rs - tests/ui/layout/valid_range_oob.rs --- tests/ui/impl-trait/issues/issue-86800.rs | 10 ++++------ tests/ui/impl-trait/issues/issue-86800.stderr | 14 +------------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/tests/ui/impl-trait/issues/issue-86800.rs b/tests/ui/impl-trait/issues/issue-86800.rs index 351243c6727da..ec4fda322d04f 100644 --- a/tests/ui/impl-trait/issues/issue-86800.rs +++ b/tests/ui/impl-trait/issues/issue-86800.rs @@ -1,14 +1,12 @@ #![feature(type_alias_impl_trait)] // edition:2021 -// unset-rustc-env:RUST_BACKTRACE // compile-flags:-Z treat-err-as-bug=1 -// error-pattern:stack backtrace: +// error-pattern: aborting due to `-Z treat-err-as-bug=1` // failure-status:101 -// normalize-stderr-test "note: .*" -> "" -// normalize-stderr-test "thread 'rustc' .*" -> "" -// normalize-stderr-test " +[0-9]+:.*\n" -> "" -// normalize-stderr-test " +at .*\n" -> "" +// normalize-stderr-test ".*note: .*\n\n" -> "" +// normalize-stderr-test "thread 'rustc' panicked.*\n" -> "" +// rustc-env:RUST_BACKTRACE=0 use std::future::Future; diff --git a/tests/ui/impl-trait/issues/issue-86800.stderr b/tests/ui/impl-trait/issues/issue-86800.stderr index f3a773837785e..facab390d1524 100644 --- a/tests/ui/impl-trait/issues/issue-86800.stderr +++ b/tests/ui/impl-trait/issues/issue-86800.stderr @@ -1,24 +1,12 @@ error: unconstrained opaque type - --> $DIR/issue-86800.rs:33:34 + --> $DIR/issue-86800.rs:31:34 | LL | type TransactionFuture<'__, O> = impl '__ + Future>; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = - - -stack backtrace: - error: the compiler unexpectedly panicked. this is a bug. - - - - - - query stack during panic: #0 [type_of] computing type of `TransactionFuture::{opaque#0}` #1 [check_mod_item_types] checking item types in top-level module -#2 [analysis] running analysis passes on this crate end of query stack From 5f45c69c594a6cabbcb4919ef46a5e67cd9c0097 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 2 May 2023 09:54:35 +1000 Subject: [PATCH 03/19] Improve filtering in `default-backtrace-ice.rs`. This test is supposed to ensure that full backtraces are used for ICEs. But it doesn't actually do that -- the filtering done cannot distinguish between a full backtrace versus a short backtrace. So this commit changes the filtering to preserve the existence of `__rust_{begin,end}_short_backtrace` markers, which only appear in full backtraces. This change means the test now tests what it is supposed to test. Also, the existing filtering included a rule that excluded any line starting with two spaces. This was too strong because it filtered out some parts of the error message. (This was not a showstopper). It was also not strong enough because it didn't work with three digit stack frame numbers, which just started seeing after upgrading my Ubuntu distro to 23.04 machine (this *was* a showstopper). So the commit replaces that rule with two more precise rules, one for lines with stack frame numbers, and one for "at ..." lines. --- tests/ui/panics/default-backtrace-ice.rs | 10 +++++++++- tests/ui/panics/default-backtrace-ice.stderr | 5 +++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/ui/panics/default-backtrace-ice.rs b/tests/ui/panics/default-backtrace-ice.rs index fd86a3f9dfaff..483645ab90fa5 100644 --- a/tests/ui/panics/default-backtrace-ice.rs +++ b/tests/ui/panics/default-backtrace-ice.rs @@ -4,6 +4,14 @@ // failure-status:101 // normalize-stderr-test "note: .*" -> "" // normalize-stderr-test "thread 'rustc' .*" -> "" -// normalize-stderr-test " .*\n" -> "" +// normalize-stderr-test " +\d+:.*__rust_begin_short_backtrace.*" -> "(begin_short_backtrace)" +// normalize-stderr-test " +\d+:.*__rust_end_short_backtrace.*" -> "(end_short_backtrace)" +// normalize-stderr-test " +\d+:.*\n" -> "" +// normalize-stderr-test " +at .*\n" -> "" +// +// This test makes sure that full backtraces are used for ICEs when +// RUST_BACKTRACE is not set. It does this by checking for the presence of +// `__rust_{begin,end}_short_backtrace` markers, which only appear in full +// backtraces. The rest of the backtrace is filtered out. fn main() { missing_ident; } diff --git a/tests/ui/panics/default-backtrace-ice.stderr b/tests/ui/panics/default-backtrace-ice.stderr index 4bd4780e25f6b..28fb91763d439 100644 --- a/tests/ui/panics/default-backtrace-ice.stderr +++ b/tests/ui/panics/default-backtrace-ice.stderr @@ -1,8 +1,13 @@ error[E0425]: cannot find value `missing_ident` in this scope + --> $DIR/default-backtrace-ice.rs:17:13 + | LL | fn main() { missing_ident; } + | ^^^^^^^^^^^^^ not found in this scope stack backtrace: +(end_short_backtrace) +(begin_short_backtrace) error: the compiler unexpectedly panicked. this is a bug. From 2469afef1a9697e671f9e90971c342fa3e0006e6 Mon Sep 17 00:00:00 2001 From: jyn Date: Sat, 29 Apr 2023 06:29:07 -0500 Subject: [PATCH 04/19] Make the BUG_REPORT_URL configurable by tools This greatly simplifies how hard it is to set a custom bug report url; previously tools had to copy the entire hook implementation. - Switch clippy to the new hook This also adds a `extra_info` callback so clippy can include its own version number, which differs from rustc's. - Call `install_ice_hook` in rustfmt --- compiler/rustc_driver_impl/src/lib.rs | 103 ++++++++++++--------- src/librustdoc/lib.rs | 6 +- src/tools/clippy/src/driver.rs | 70 ++------------ src/tools/miri/src/bin/miri.rs | 8 +- src/tools/rustfmt/src/bin/main.rs | 9 ++ tests/rustdoc-ui/ice-bug-report-url.rs | 14 +++ tests/rustdoc-ui/ice-bug-report-url.stderr | 16 ++++ 7 files changed, 115 insertions(+), 111 deletions(-) create mode 100644 tests/rustdoc-ui/ice-bug-report-url.rs create mode 100644 tests/rustdoc-ui/ice-bug-report-url.stderr diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 5fac485de6417..92153e91a277d 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -25,7 +25,7 @@ use rustc_data_structures::profiling::{ use rustc_data_structures::sync::SeqCst; use rustc_errors::registry::{InvalidErrorCode, Registry}; use rustc_errors::{ - DiagnosticMessage, ErrorGuaranteed, PResult, SubdiagnosticMessage, TerminalUrl, + DiagnosticMessage, ErrorGuaranteed, Handler, PResult, SubdiagnosticMessage, TerminalUrl, }; use rustc_feature::find_gated_cfg; use rustc_fluent_macro::fluent_messages; @@ -55,7 +55,7 @@ use std::panic::{self, catch_unwind}; use std::path::PathBuf; use std::process::{self, Command, Stdio}; use std::str; -use std::sync::LazyLock; +use std::sync::OnceLock; use std::time::Instant; // This import blocks the use of panicking `print` and `println` in all the code @@ -119,7 +119,7 @@ pub const EXIT_SUCCESS: i32 = 0; /// Exit status code used for compilation failures and invalid flags. pub const EXIT_FAILURE: i32 = 1; -const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust/issues/new\ +pub const DEFAULT_BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust/issues/new\ ?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md"; const ICE_REPORT_COMPILER_FLAGS: &[&str] = &["-Z", "-C", "--crate-type"]; @@ -1195,35 +1195,58 @@ pub fn catch_with_exit_code(f: impl FnOnce() -> interface::Result<()>) -> i32 { } } -static DEFAULT_HOOK: LazyLock) + Sync + Send + 'static>> = - LazyLock::new(|| { - let hook = panic::take_hook(); - panic::set_hook(Box::new(|info| { - // If the error was caused by a broken pipe then this is not a bug. - // Write the error and return immediately. See #98700. - #[cfg(windows)] - if let Some(msg) = info.payload().downcast_ref::() { - if msg.starts_with("failed printing to stdout: ") && msg.ends_with("(os error 232)") - { - early_error_no_abort(ErrorOutputType::default(), &msg); - return; - } - }; +/// Stores the default panic hook, from before [`install_ice_hook`] was called. +static DEFAULT_HOOK: OnceLock) + Sync + Send + 'static>> = + OnceLock::new(); + +/// Installs a panic hook that will print the ICE message on unexpected panics. +/// +/// The hook is intended to be useable even by external tools. You can pass a custom +/// `bug_report_url`, or report arbitrary info in `extra_info`. Note that `extra_info` is called in +/// a context where *the thread is currently panicking*, so it must not panic or the process will +/// abort. +/// +/// If you have no extra info to report, pass the empty closure `|_| ()` as the argument to +/// extra_info. +/// +/// A custom rustc driver can skip calling this to set up a custom ICE hook. +pub fn install_ice_hook(bug_report_url: &'static str, extra_info: fn(&Handler)) { + // If the user has not explicitly overridden "RUST_BACKTRACE", then produce + // full backtraces. When a compiler ICE happens, we want to gather + // as much information as possible to present in the issue opened + // by the user. Compiler developers and other rustc users can + // opt in to less-verbose backtraces by manually setting "RUST_BACKTRACE" + // (e.g. `RUST_BACKTRACE=1`) + if std::env::var("RUST_BACKTRACE").is_err() { + std::env::set_var("RUST_BACKTRACE", "full"); + } - // Invoke the default handler, which prints the actual panic message and optionally a backtrace - // Don't do this for delayed bugs, which already emit their own more useful backtrace. - if !info.payload().is::() { - (*DEFAULT_HOOK)(info); + let default_hook = DEFAULT_HOOK.get_or_init(panic::take_hook); - // Separate the output with an empty line - eprintln!(); + panic::set_hook(Box::new(move |info| { + // If the error was caused by a broken pipe then this is not a bug. + // Write the error and return immediately. See #98700. + #[cfg(windows)] + if let Some(msg) = info.payload().downcast_ref::() { + if msg.starts_with("failed printing to stdout: ") && msg.ends_with("(os error 232)") { + early_error_no_abort(ErrorOutputType::default(), &msg); + return; } + }; - // Print the ICE message - report_ice(info, BUG_REPORT_URL); - })); - hook - }); + // Invoke the default handler, which prints the actual panic message and optionally a backtrace + // Don't do this for delayed bugs, which already emit their own more useful backtrace. + if !info.payload().is::() { + (*default_hook)(info); + + // Separate the output with an empty line + eprintln!(); + } + + // Print the ICE message + report_ice(info, bug_report_url, extra_info); + })); +} /// Prints the ICE message, including query stack, but without backtrace. /// @@ -1231,7 +1254,7 @@ static DEFAULT_HOOK: LazyLock) + Sync + Send + /// /// When `install_ice_hook` is called, this function will be called as the panic /// hook. -pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) { +pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str, extra_info: fn(&Handler)) { let fallback_bundle = rustc_errors::fallback_fluent_bundle(crate::DEFAULT_LOCALE_RESOURCES.to_vec(), false); let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr( @@ -1276,6 +1299,10 @@ pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) { interface::try_print_query_stack(&handler, num_frames); + // We don't trust this callback not to panic itself, so run it at the end after we're sure we've + // printed all the relevant info. + extra_info(&handler); + #[cfg(windows)] if env::var("RUSTC_BREAK_ON_ICE").is_ok() { // Trigger a debugger if we crashed during bootstrap @@ -1283,22 +1310,6 @@ pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) { } } -/// Installs a panic hook that will print the ICE message on unexpected panics. -/// -/// A custom rustc driver can skip calling this to set up a custom ICE hook. -pub fn install_ice_hook() { - // If the user has not explicitly overridden "RUST_BACKTRACE", then produce - // full backtraces. When a compiler ICE happens, we want to gather - // as much information as possible to present in the issue opened - // by the user. Compiler developers and other rustc users can - // opt in to less-verbose backtraces by manually setting "RUST_BACKTRACE" - // (e.g. `RUST_BACKTRACE=1`) - if std::env::var("RUST_BACKTRACE").is_err() { - std::env::set_var("RUST_BACKTRACE", "full"); - } - LazyLock::force(&DEFAULT_HOOK); -} - /// This allows tools to enable rust logging without having to magically match rustc's /// tracing crate version. pub fn init_rustc_env_logger() { @@ -1369,7 +1380,7 @@ pub fn main() -> ! { init_rustc_env_logger(); signal_handler::install(); let mut callbacks = TimePassesCallbacks::default(); - install_ice_hook(); + install_ice_hook(DEFAULT_BUG_REPORT_URL, |_| ()); let exit_code = catch_with_exit_code(|| { let args = env::args_os() .enumerate() diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 70030e293344e..2746debbfab61 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -154,7 +154,11 @@ pub fn main() { } } - rustc_driver::install_ice_hook(); + rustc_driver::install_ice_hook( + "https://github.com/rust-lang/rust/issues/new\ + ?labels=C-bug%2C+I-ICE%2C+T-rustdoc&template=ice.md", + |_| (), + ); // When using CI artifacts with `download-rustc`, tracing is unconditionally built // with `--features=static_max_level_info`, which disables almost all rustdoc logging. To avoid diff --git a/src/tools/clippy/src/driver.rs b/src/tools/clippy/src/driver.rs index 205905d509135..59bf447a7cd07 100644 --- a/src/tools/clippy/src/driver.rs +++ b/src/tools/clippy/src/driver.rs @@ -11,7 +11,6 @@ // FIXME: switch to something more ergonomic here, once available. // (Currently there is no way to opt into sysroot crates without `extern crate`.) extern crate rustc_driver; -extern crate rustc_errors; extern crate rustc_interface; extern crate rustc_session; extern crate rustc_span; @@ -20,13 +19,10 @@ use rustc_interface::interface; use rustc_session::parse::ParseSess; use rustc_span::symbol::Symbol; -use std::borrow::Cow; use std::env; use std::ops::Deref; -use std::panic; use std::path::Path; use std::process::exit; -use std::sync::LazyLock; /// If a command-line option matches `find_arg`, then apply the predicate `pred` on its value. If /// true, then return it. The parameter is assumed to be either `--arg=value` or `--arg value`. @@ -198,66 +194,18 @@ You can use tool lints to allow or deny lints from your code, eg.: const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust-clippy/issues/new"; -type PanicCallback = dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static; -static ICE_HOOK: LazyLock> = LazyLock::new(|| { - let hook = panic::take_hook(); - panic::set_hook(Box::new(|info| report_clippy_ice(info, BUG_REPORT_URL))); - hook -}); - -fn report_clippy_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) { - // Invoke our ICE handler, which prints the actual panic message and optionally a backtrace - (*ICE_HOOK)(info); - - // Separate the output with an empty line - eprintln!(); - - let fallback_bundle = rustc_errors::fallback_fluent_bundle(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), false); - let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr( - rustc_errors::ColorConfig::Auto, - None, - None, - fallback_bundle, - false, - false, - None, - false, - false, - rustc_errors::TerminalUrl::No, - )); - let handler = rustc_errors::Handler::with_emitter(true, None, emitter); - - // a .span_bug or .bug call has already printed what - // it wants to print. - if !info.payload().is::() { - let mut d = rustc_errors::Diagnostic::new(rustc_errors::Level::Bug, "unexpected panic"); - handler.emit_diagnostic(&mut d); - } - - let version_info = rustc_tools_util::get_version_info!(); - - let xs: Vec> = vec![ - "the compiler unexpectedly panicked. this is a bug.".into(), - format!("we would appreciate a bug report: {bug_report_url}").into(), - format!("Clippy version: {version_info}").into(), - ]; - - for note in &xs { - handler.note_without_error(note.as_ref()); - } - - // If backtraces are enabled, also print the query stack - let backtrace = env::var_os("RUST_BACKTRACE").map_or(false, |x| &x != "0"); - - let num_frames = if backtrace { None } else { Some(2) }; - - interface::try_print_query_stack(&handler, num_frames); -} - #[allow(clippy::too_many_lines)] pub fn main() { rustc_driver::init_rustc_env_logger(); - LazyLock::force(&ICE_HOOK); + + rustc_driver::install_ice_hook(BUG_REPORT_URL, |handler| { + // FIXME: this macro calls unwrap internally but is called in a panicking context! It's not + // as simple as moving the call from the hook to main, because `install_ice_hook` doesn't + // accept a generic closure. + let version_info = rustc_tools_util::get_version_info!(); + handler.note_without_error(format!("Clippy version: {version_info}")); + }); + exit(rustc_driver::catch_with_exit_code(move || { let mut orig_args: Vec = env::args().collect(); let has_sysroot_arg = arg_value(&orig_args, "--sysroot", |_| true).is_some(); diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 3aa71bb7e3c87..2c0074951d6e5 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -286,11 +286,10 @@ fn main() { // (`install_ice_hook` might change `RUST_BACKTRACE`.) let env_snapshot = env::vars_os().collect::>(); - // Earliest rustc setup. - rustc_driver::install_ice_hook(); - // If the environment asks us to actually be rustc, then do that. if let Some(crate_kind) = env::var_os("MIRI_BE_RUSTC") { + // Earliest rustc setup. + rustc_driver::install_ice_hook(rustc_driver::DEFAULT_BUG_REPORT_URL, |_| ()); rustc_driver::init_rustc_env_logger(); let target_crate = if crate_kind == "target" { @@ -309,6 +308,9 @@ fn main() { ) } + // Add an ICE bug report hook. + rustc_driver::install_ice_hook("https://github.com/rust-lang/miri/issues/new", |_| ()); + // Init loggers the Miri way. init_early_loggers(); diff --git a/src/tools/rustfmt/src/bin/main.rs b/src/tools/rustfmt/src/bin/main.rs index be64559e87745..47846424b06e4 100644 --- a/src/tools/rustfmt/src/bin/main.rs +++ b/src/tools/rustfmt/src/bin/main.rs @@ -1,3 +1,5 @@ +#![feature(rustc_private)] + use anyhow::{format_err, Result}; use io::Error as IoError; @@ -19,7 +21,14 @@ use crate::rustfmt::{ FormatReportFormatterBuilder, Input, Session, Verbosity, }; +const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rustfmt/issues/new?labels=bug"; + +// N.B. these crates are loaded from the sysroot, so they need extern crate. +extern crate rustc_driver; + fn main() { + rustc_driver::install_ice_hook(BUG_REPORT_URL, |_| ()); + env_logger::Builder::from_env("RUSTFMT_LOG").init(); let opts = make_opts(); diff --git a/tests/rustdoc-ui/ice-bug-report-url.rs b/tests/rustdoc-ui/ice-bug-report-url.rs new file mode 100644 index 0000000000000..cc066447d313b --- /dev/null +++ b/tests/rustdoc-ui/ice-bug-report-url.rs @@ -0,0 +1,14 @@ +// compile-flags: -Ztreat-err-as-bug +// failure-status: 101 +// error-pattern: aborting due to +// error-pattern: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-rustdoc&template=ice.md + +// normalize-stderr-test "note: compiler flags.*\n\n" -> "" +// normalize-stderr-test "note: rustc.*running on.*" -> "note: rustc {version} running on {platform}" +// normalize-stderr-test "thread.*panicked at .*, compiler.*" -> "thread panicked at 'aborting due to `-Z treat-err-as-bug`'" +// normalize-stderr-test "\s*\d{1,}: .*\n" -> "" +// normalize-stderr-test "\s at .*\n" -> "" +// normalize-stderr-test ".*note: Some details are omitted.*\n" -> "" + +fn wrong() +//~^ ERROR expected one of diff --git a/tests/rustdoc-ui/ice-bug-report-url.stderr b/tests/rustdoc-ui/ice-bug-report-url.stderr new file mode 100644 index 0000000000000..cfb73a9b9193f --- /dev/null +++ b/tests/rustdoc-ui/ice-bug-report-url.stderr @@ -0,0 +1,16 @@ +error: expected one of `->`, `where`, or `{`, found `` + --> $DIR/ice-bug-report-url.rs:13:10 + | +LL | fn wrong() + | ^ expected one of `->`, `where`, or `{` + +thread panicked at 'aborting due to `-Z treat-err-as-bug`' +stack backtrace: +error: the compiler unexpectedly panicked. this is a bug. + +note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-rustdoc&template=ice.md + +note: rustc {version} running on {platform} + +query stack during panic: +end of query stack From 8ff3903643b530c9029e8f2c6c6956fda8f21d77 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Sun, 5 Mar 2023 15:03:22 +0000 Subject: [PATCH 05/19] initial step towards implementing C string literals --- compiler/rustc_ast/src/ast.rs | 3 + compiler/rustc_ast/src/token.rs | 7 + compiler/rustc_ast/src/util/literal.rs | 55 ++++- compiler/rustc_ast_pretty/src/pprust/state.rs | 2 + compiler/rustc_builtin_macros/src/concat.rs | 4 + .../rustc_builtin_macros/src/concat_bytes.rs | 4 + .../rustc_expand/src/proc_macro_server.rs | 4 + compiler/rustc_hir/src/lang_items.rs | 1 + .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 5 + compiler/rustc_lexer/src/lib.rs | 30 +++ compiler/rustc_lexer/src/unescape.rs | 199 +++++++++++------- compiler/rustc_parse/src/lexer/mod.rs | 64 ++++++ library/core/src/ffi/c_str.rs | 1 + src/librustdoc/html/highlight.rs | 4 +- .../src/matches/match_same_arms.rs | 1 + .../clippy/clippy_lints/src/utils/author.rs | 5 + src/tools/clippy/clippy_utils/src/consts.rs | 1 + 17 files changed, 310 insertions(+), 80 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index ea04ba4f66e46..fb22e4640647d 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1814,6 +1814,8 @@ pub enum LitKind { /// A byte string (`b"foo"`). Not stored as a symbol because it might be /// non-utf8, and symbols only allow utf8 strings. ByteStr(Lrc<[u8]>, StrStyle), + /// A C String (`c"foo"`). + CStr(Lrc<[u8]>, StrStyle), /// A byte char (`b'f'`). Byte(u8), /// A character literal (`'a'`). @@ -1868,6 +1870,7 @@ impl LitKind { // unsuffixed variants LitKind::Str(..) | LitKind::ByteStr(..) + | LitKind::CStr(..) | LitKind::Byte(..) | LitKind::Char(..) | LitKind::Int(_, LitIntType::Unsuffixed) diff --git a/compiler/rustc_ast/src/token.rs b/compiler/rustc_ast/src/token.rs index f947ae4d05732..42b843482a32b 100644 --- a/compiler/rustc_ast/src/token.rs +++ b/compiler/rustc_ast/src/token.rs @@ -74,6 +74,8 @@ pub enum LitKind { StrRaw(u8), // raw string delimited by `n` hash symbols ByteStr, ByteStrRaw(u8), // raw byte string delimited by `n` hash symbols + CStr, + CStrRaw(u8), Err, } @@ -141,6 +143,10 @@ impl fmt::Display for Lit { delim = "#".repeat(n as usize), string = symbol )?, + CStr => write!(f, "c\"{symbol}\"")?, + CStrRaw(n) => { + write!(f, "cr{delim}\"{symbol}\"{delim}", delim = "#".repeat(n as usize))? + } Integer | Float | Bool | Err => write!(f, "{symbol}")?, } @@ -170,6 +176,7 @@ impl LitKind { Float => "float", Str | StrRaw(..) => "string", ByteStr | ByteStrRaw(..) => "byte string", + CStr | CStrRaw(..) => "C string", Err => "error", } } diff --git a/compiler/rustc_ast/src/util/literal.rs b/compiler/rustc_ast/src/util/literal.rs index 74b842ac96eac..8534011e18921 100644 --- a/compiler/rustc_ast/src/util/literal.rs +++ b/compiler/rustc_ast/src/util/literal.rs @@ -2,7 +2,10 @@ use crate::ast::{self, LitKind, MetaItemLit, StrStyle}; use crate::token::{self, Token}; -use rustc_lexer::unescape::{byte_from_char, unescape_byte, unescape_char, unescape_literal, Mode}; +use rustc_lexer::unescape::{ + byte_from_char, unescape_byte, unescape_c_string, unescape_char, unescape_literal, CStrUnit, + Mode, +}; use rustc_span::symbol::{kw, sym, Symbol}; use rustc_span::Span; use std::{ascii, fmt, str}; @@ -158,6 +161,52 @@ impl LitKind { LitKind::ByteStr(bytes.into(), StrStyle::Raw(n)) } + token::CStr => { + let s = symbol.as_str(); + let mut buf = Vec::with_capacity(s.len()); + let mut error = Ok(()); + unescape_c_string(s, Mode::CStr, &mut |span, c| match c { + Ok(CStrUnit::Byte(0) | CStrUnit::Char('\0')) => { + error = Err(LitError::NulInCStr(span)); + } + Ok(CStrUnit::Byte(b)) => buf.push(b), + Ok(CStrUnit::Char(c)) if c.len_utf8() == 1 => buf.push(c as u8), + Ok(CStrUnit::Char(c)) => { + buf.extend_from_slice(c.encode_utf8(&mut [0; 4]).as_bytes()) + } + Err(err) => { + if err.is_fatal() { + error = Err(LitError::LexerError); + } + } + }); + error?; + buf.push(b'\0'); + LitKind::CStr(buf.into(), StrStyle::Cooked) + } + token::CStrRaw(n) => { + let s = symbol.as_str(); + let mut buf = Vec::with_capacity(s.len()); + let mut error = Ok(()); + unescape_c_string(s, Mode::RawCStr, &mut |span, c| match c { + Ok(CStrUnit::Byte(0) | CStrUnit::Char('\0')) => { + error = Err(LitError::NulInCStr(span)); + } + Ok(CStrUnit::Byte(b)) => buf.push(b), + Ok(CStrUnit::Char(c)) if c.len_utf8() == 1 => buf.push(c as u8), + Ok(CStrUnit::Char(c)) => { + buf.extend_from_slice(c.encode_utf8(&mut [0; 4]).as_bytes()) + } + Err(err) => { + if err.is_fatal() { + error = Err(LitError::LexerError); + } + } + }); + error?; + buf.push(b'\0'); + LitKind::CStr(buf.into(), StrStyle::Raw(n)) + } token::Err => LitKind::Err, }) } @@ -191,6 +240,8 @@ impl fmt::Display for LitKind { string = symbol )?; } + // TODO need to reescape + LitKind::CStr(..) => todo!(), LitKind::Int(n, ty) => { write!(f, "{n}")?; match ty { @@ -237,6 +288,8 @@ impl MetaItemLit { LitKind::Str(_, ast::StrStyle::Raw(n)) => token::StrRaw(n), LitKind::ByteStr(_, ast::StrStyle::Cooked) => token::ByteStr, LitKind::ByteStr(_, ast::StrStyle::Raw(n)) => token::ByteStrRaw(n), + LitKind::CStr(_, ast::StrStyle::Cooked) => token::CStr, + LitKind::CStr(_, ast::StrStyle::Raw(n)) => token::CStrRaw(n), LitKind::Byte(_) => token::Byte, LitKind::Char(_) => token::Char, LitKind::Int(..) => token::Integer, diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index 849336c8669a1..535ac89e751d5 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -210,6 +210,8 @@ pub fn literal_to_string(lit: token::Lit) -> String { token::ByteStrRaw(n) => { format!("br{delim}\"{string}\"{delim}", delim = "#".repeat(n as usize), string = symbol) } + // TODO + token::CStr | token::CStrRaw(_) => todo!(), token::Integer | token::Float | token::Bool | token::Err => symbol.to_string(), }; diff --git a/compiler/rustc_builtin_macros/src/concat.rs b/compiler/rustc_builtin_macros/src/concat.rs index b92964d03e9f9..50e88ae2eeede 100644 --- a/compiler/rustc_builtin_macros/src/concat.rs +++ b/compiler/rustc_builtin_macros/src/concat.rs @@ -32,6 +32,10 @@ pub fn expand_concat( Ok(ast::LitKind::Bool(b)) => { accumulator.push_str(&b.to_string()); } + Ok(ast::LitKind::CStr(..)) => { + cx.span_err(e.span, "cannot concatenate a C string literal"); + has_errors = true; + } Ok(ast::LitKind::Byte(..) | ast::LitKind::ByteStr(..)) => { cx.emit_err(errors::ConcatBytestr { span: e.span }); has_errors = true; diff --git a/compiler/rustc_builtin_macros/src/concat_bytes.rs b/compiler/rustc_builtin_macros/src/concat_bytes.rs index ba639c0a9fe3c..ae674995e4294 100644 --- a/compiler/rustc_builtin_macros/src/concat_bytes.rs +++ b/compiler/rustc_builtin_macros/src/concat_bytes.rs @@ -18,6 +18,10 @@ fn invalid_type_err( }; let snippet = cx.sess.source_map().span_to_snippet(span).ok(); match ast::LitKind::from_token_lit(token_lit) { + Ok(ast::LitKind::CStr(_, _)) => { + // TODO + cx.span_err(span, "cannot concatenate C string litearls"); + } Ok(ast::LitKind::Char(_)) => { let sugg = snippet.map(|snippet| ConcatBytesInvalidSuggestion::CharLit { span, snippet }); diff --git a/compiler/rustc_expand/src/proc_macro_server.rs b/compiler/rustc_expand/src/proc_macro_server.rs index 1e7d07bc22d52..04bdea273ebd5 100644 --- a/compiler/rustc_expand/src/proc_macro_server.rs +++ b/compiler/rustc_expand/src/proc_macro_server.rs @@ -61,6 +61,8 @@ impl FromInternal for LitKind { token::StrRaw(n) => LitKind::StrRaw(n), token::ByteStr => LitKind::ByteStr, token::ByteStrRaw(n) => LitKind::ByteStrRaw(n), + // TODO + token::CStr | token::CStrRaw(_) => todo!(), token::Err => LitKind::Err, token::Bool => unreachable!(), } @@ -436,6 +438,8 @@ impl server::FreeFunctions for Rustc<'_, '_> { | token::LitKind::StrRaw(_) | token::LitKind::ByteStr | token::LitKind::ByteStrRaw(_) + | token::LitKind::CStr + | token::LitKind::CStrRaw(_) | token::LitKind::Err => return Err(()), token::LitKind::Integer | token::LitKind::Float => {} } diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index e1c030d3e198a..7ddafc9083a15 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -332,6 +332,7 @@ language_item_table! { RangeTo, sym::RangeTo, range_to_struct, Target::Struct, GenericRequirement::None; String, sym::String, string, Target::Struct, GenericRequirement::None; + CStr, sym::CStr, c_str, Target::Struct, GenericRequirement::None; } pub enum GenericRequirement { diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index f42c825d9e8b1..374266638d160 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -1300,6 +1300,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { opt_ty.unwrap_or_else(|| self.next_float_var()) } ast::LitKind::Bool(_) => tcx.types.bool, + ast::LitKind::CStr(_, _) => tcx.mk_imm_ref( + tcx.lifetimes.re_static, + tcx.type_of(tcx.require_lang_item(hir::LangItem::CStr, Some(lit.span))) + .skip_binder(), + ), ast::LitKind::Err => tcx.ty_error_misc(), } } diff --git a/compiler/rustc_lexer/src/lib.rs b/compiler/rustc_lexer/src/lib.rs index b3f4b5cd5e5a0..95cb1f93e3906 100644 --- a/compiler/rustc_lexer/src/lib.rs +++ b/compiler/rustc_lexer/src/lib.rs @@ -186,12 +186,16 @@ pub enum LiteralKind { Str { terminated: bool }, /// "b"abc"", "b"abc" ByteStr { terminated: bool }, + /// `c"abc"`, `c"abc` + CStr { terminated: bool }, /// "r"abc"", "r#"abc"#", "r####"ab"###"c"####", "r#"a". `None` indicates /// an invalid literal. RawStr { n_hashes: Option }, /// "br"abc"", "br#"abc"#", "br####"ab"###"c"####", "br#"a". `None` /// indicates an invalid literal. RawByteStr { n_hashes: Option }, + /// `cr"abc"`, "cr#"abc"#", `cr#"a`. `None` is invalid. + RawCStr { n_hashes: Option }, } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -391,6 +395,32 @@ impl Cursor<'_> { _ => self.ident_or_unknown_prefix(), }, + // TODO deduplicate this code + // c-string literal, raw c-string literal or identifier. + 'c' => match (self.first(), self.second()) { + ('"', _) => { + self.bump(); + let terminated = self.double_quoted_string(); + let suffix_start = self.pos_within_token(); + if terminated { + self.eat_literal_suffix(); + } + let kind = CStr { terminated }; + Literal { kind, suffix_start } + } + ('r', '"') | ('r', '#') => { + self.bump(); + let res = self.raw_double_quoted_string(2); + let suffix_start = self.pos_within_token(); + if res.is_ok() { + self.eat_literal_suffix(); + } + let kind = RawCStr { n_hashes: res.ok() }; + Literal { kind, suffix_start } + } + _ => self.ident_or_unknown_prefix(), + }, + // Identifier (this should be checked after other variant that can // start as identifier). c if is_id_start(c) => self.ident_or_unknown_prefix(), diff --git a/compiler/rustc_lexer/src/unescape.rs b/compiler/rustc_lexer/src/unescape.rs index bb4d91247b81d..4b707c9ec9619 100644 --- a/compiler/rustc_lexer/src/unescape.rs +++ b/compiler/rustc_lexer/src/unescape.rs @@ -90,6 +90,39 @@ where Mode::RawStr | Mode::RawByteStr => { unescape_raw_str_or_raw_byte_str(src, mode == Mode::RawByteStr, callback) } + Mode::CStr | Mode::RawCStr => unreachable!(), + } +} + +pub enum CStrUnit { + Byte(u8), + Char(char), +} + +impl From for CStrUnit { + fn from(value: u8) -> Self { + CStrUnit::Byte(value) + } +} + +impl From for CStrUnit { + fn from(value: char) -> Self { + CStrUnit::Char(value) + } +} + +pub fn unescape_c_string(src: &str, mode: Mode, callback: &mut F) +where + F: FnMut(Range, Result), +{ + if mode == Mode::RawCStr { + unescape_raw_str_or_raw_byte_str( + src, + mode.characters_should_be_ascii(), + &mut |r, result| callback(r, result.map(CStrUnit::Char)), + ); + } else { + unescape_str_common(src, mode, callback); } } @@ -114,19 +147,26 @@ pub enum Mode { ByteStr, RawStr, RawByteStr, + CStr, + RawCStr, } impl Mode { pub fn in_double_quotes(self) -> bool { match self { - Mode::Str | Mode::ByteStr | Mode::RawStr | Mode::RawByteStr => true, + Mode::Str + | Mode::ByteStr + | Mode::RawStr + | Mode::RawByteStr + | Mode::CStr + | Mode::RawCStr => true, Mode::Char | Mode::Byte => false, } } pub fn is_byte(self) -> bool { match self { - Mode::Byte | Mode::ByteStr | Mode::RawByteStr => true, + Mode::Byte | Mode::ByteStr | Mode::RawByteStr | Mode::CStr | Mode::RawCStr => true, Mode::Char | Mode::Str | Mode::RawStr => false, } } @@ -163,62 +203,63 @@ fn scan_escape(chars: &mut Chars<'_>, is_byte: bool) -> Result { - // We've parsed '\u', now we have to parse '{..}'. + 'u' => scan_unicode(chars, is_byte)?, + _ => return Err(EscapeError::InvalidEscape), + }; + Ok(res) +} - if chars.next() != Some('{') { - return Err(EscapeError::NoBraceInUnicodeEscape); - } +fn scan_unicode(chars: &mut Chars<'_>, is_byte: bool) -> Result { + // We've parsed '\u', now we have to parse '{..}'. - // First character must be a hexadecimal digit. - let mut n_digits = 1; - let mut value: u32 = match chars.next().ok_or(EscapeError::UnclosedUnicodeEscape)? { - '_' => return Err(EscapeError::LeadingUnderscoreUnicodeEscape), - '}' => return Err(EscapeError::EmptyUnicodeEscape), - c => c.to_digit(16).ok_or(EscapeError::InvalidCharInUnicodeEscape)?, - }; - - // First character is valid, now parse the rest of the number - // and closing brace. - loop { - match chars.next() { - None => return Err(EscapeError::UnclosedUnicodeEscape), - Some('_') => continue, - Some('}') => { - if n_digits > 6 { - return Err(EscapeError::OverlongUnicodeEscape); - } - - // Incorrect syntax has higher priority for error reporting - // than unallowed value for a literal. - if is_byte { - return Err(EscapeError::UnicodeEscapeInByte); - } - - break std::char::from_u32(value).ok_or_else(|| { - if value > 0x10FFFF { - EscapeError::OutOfRangeUnicodeEscape - } else { - EscapeError::LoneSurrogateUnicodeEscape - } - })?; - } - Some(c) => { - let digit: u32 = - c.to_digit(16).ok_or(EscapeError::InvalidCharInUnicodeEscape)?; - n_digits += 1; - if n_digits > 6 { - // Stop updating value since we're sure that it's incorrect already. - continue; - } - value = value * 16 + digit; + if chars.next() != Some('{') { + return Err(EscapeError::NoBraceInUnicodeEscape); + } + + // First character must be a hexadecimal digit. + let mut n_digits = 1; + let mut value: u32 = match chars.next().ok_or(EscapeError::UnclosedUnicodeEscape)? { + '_' => return Err(EscapeError::LeadingUnderscoreUnicodeEscape), + '}' => return Err(EscapeError::EmptyUnicodeEscape), + c => c.to_digit(16).ok_or(EscapeError::InvalidCharInUnicodeEscape)?, + }; + + // First character is valid, now parse the rest of the number + // and closing brace. + loop { + match chars.next() { + None => return Err(EscapeError::UnclosedUnicodeEscape), + Some('_') => continue, + Some('}') => { + if n_digits > 6 { + return Err(EscapeError::OverlongUnicodeEscape); + } + + // Incorrect syntax has higher priority for error reporting + // than unallowed value for a literal. + if is_byte { + return Err(EscapeError::UnicodeEscapeInByte); + } + + break std::char::from_u32(value).ok_or_else(|| { + if value > 0x10FFFF { + EscapeError::OutOfRangeUnicodeEscape + } else { + EscapeError::LoneSurrogateUnicodeEscape } - }; + }); } - } - _ => return Err(EscapeError::InvalidEscape), - }; - Ok(res) + Some(c) => { + let digit: u32 = c.to_digit(16).ok_or(EscapeError::InvalidCharInUnicodeEscape)?; + n_digits += 1; + if n_digits > 6 { + // Stop updating value since we're sure that it's incorrect already. + continue; + } + value = value * 16 + digit; + } + }; + } } #[inline] @@ -266,7 +307,9 @@ where // if unescaped '\' character is followed by '\n'. // For details see [Rust language reference] // (https://doc.rust-lang.org/reference/tokens.html#string-literals). - skip_ascii_whitespace(&mut chars, start, callback); + skip_ascii_whitespace(&mut chars, start, &mut |range, err| { + callback(range, Err(err)) + }); continue; } _ => scan_escape(&mut chars, is_byte), @@ -281,32 +324,32 @@ where let end = src.len() - chars.as_str().len(); callback(start..end, res); } +} - fn skip_ascii_whitespace(chars: &mut Chars<'_>, start: usize, callback: &mut F) - where - F: FnMut(Range, Result), - { - let tail = chars.as_str(); - let first_non_space = tail - .bytes() - .position(|b| b != b' ' && b != b'\t' && b != b'\n' && b != b'\r') - .unwrap_or(tail.len()); - if tail[1..first_non_space].contains('\n') { - // The +1 accounts for the escaping slash. - let end = start + first_non_space + 1; - callback(start..end, Err(EscapeError::MultipleSkippedLinesWarning)); - } - let tail = &tail[first_non_space..]; - if let Some(c) = tail.chars().nth(0) { - if c.is_whitespace() { - // For error reporting, we would like the span to contain the character that was not - // skipped. The +1 is necessary to account for the leading \ that started the escape. - let end = start + first_non_space + c.len_utf8() + 1; - callback(start..end, Err(EscapeError::UnskippedWhitespaceWarning)); - } +fn skip_ascii_whitespace(chars: &mut Chars<'_>, start: usize, callback: &mut F) +where + F: FnMut(Range, EscapeError), +{ + let tail = chars.as_str(); + let first_non_space = tail + .bytes() + .position(|b| b != b' ' && b != b'\t' && b != b'\n' && b != b'\r') + .unwrap_or(tail.len()); + if tail[1..first_non_space].contains('\n') { + // The +1 accounts for the escaping slash. + let end = start + first_non_space + 1; + callback(start..end, EscapeError::MultipleSkippedLinesWarning); + } + let tail = &tail[first_non_space..]; + if let Some(c) = tail.chars().nth(0) { + if c.is_whitespace() { + // For error reporting, we would like the span to contain the character that was not + // skipped. The +1 is necessary to account for the leading \ that started the escape. + let end = start + first_non_space + c.len_utf8() + 1; + callback(start..end, EscapeError::UnskippedWhitespaceWarning); } - *chars = tail.chars(); } + *chars = tail.chars(); } /// Takes a contents of a string literal (without quotes) and produces a diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs index a4a75fcb96995..b2050780309f8 100644 --- a/compiler/rustc_parse/src/lexer/mod.rs +++ b/compiler/rustc_parse/src/lexer/mod.rs @@ -415,6 +415,16 @@ impl<'a> StringReader<'a> { } self.cook_quoted(token::ByteStr, Mode::ByteStr, start, end, 2, 1) // b" " } + rustc_lexer::LiteralKind::CStr { terminated } => { + if !terminated { + self.sess.span_diagnostic.span_fatal_with_code( + self.mk_sp(start + BytePos(1), end), + "unterminated C string", + error_code!(E0767), + ) + } + self.cook_c_string(token::CStr, Mode::CStr, start, end, 2, 1) // c" " + } rustc_lexer::LiteralKind::RawStr { n_hashes } => { if let Some(n_hashes) = n_hashes { let n = u32::from(n_hashes); @@ -433,6 +443,15 @@ impl<'a> StringReader<'a> { self.report_raw_str_error(start, 2); } } + rustc_lexer::LiteralKind::RawCStr { n_hashes } => { + if let Some(n_hashes) = n_hashes { + let n = u32::from(n_hashes); + let kind = token::CStrRaw(n_hashes); + self.cook_c_string(kind, Mode::RawCStr, start, end, 3 + n, 1 + n) // cr##" "## + } else { + self.report_raw_str_error(start, 2); + } + } rustc_lexer::LiteralKind::Int { base, empty_int } => { if empty_int { let span = self.mk_sp(start, end); @@ -692,6 +711,51 @@ impl<'a> StringReader<'a> { (token::Err, self.symbol_from_to(start, end)) } } + + fn cook_c_string( + &self, + kind: token::LitKind, + mode: Mode, + start: BytePos, + end: BytePos, + prefix_len: u32, + postfix_len: u32, + ) -> (token::LitKind, Symbol) { + let mut has_fatal_err = false; + let content_start = start + BytePos(prefix_len); + let content_end = end - BytePos(postfix_len); + let lit_content = self.str_from_to(content_start, content_end); + unescape::unescape_c_string(lit_content, mode, &mut |range, result| { + // Here we only check for errors. The actual unescaping is done later. + if let Err(err) = result { + let span_with_quotes = self.mk_sp(start, end); + let (start, end) = (range.start as u32, range.end as u32); + let lo = content_start + BytePos(start); + let hi = lo + BytePos(end - start); + let span = self.mk_sp(lo, hi); + if err.is_fatal() { + has_fatal_err = true; + } + emit_unescape_error( + &self.sess.span_diagnostic, + lit_content, + span_with_quotes, + span, + mode, + range, + err, + ); + } + }); + + // We normally exclude the quotes for the symbol, but for errors we + // include it because it results in clearer error messages. + if !has_fatal_err { + (kind, Symbol::intern(lit_content)) + } else { + (token::Err, self.symbol_from_to(start, end)) + } + } } pub fn nfc_normalize(string: &str) -> Symbol { diff --git a/library/core/src/ffi/c_str.rs b/library/core/src/ffi/c_str.rs index bd2b2c36c4315..2ac679b6bc347 100644 --- a/library/core/src/ffi/c_str.rs +++ b/library/core/src/ffi/c_str.rs @@ -82,6 +82,7 @@ use crate::str; #[cfg_attr(not(test), rustc_diagnostic_item = "CStr")] #[stable(feature = "core_c_str", since = "1.64.0")] #[rustc_has_incoherent_inherent_impls] +#[cfg_attr(not(bootstrap), lang = "CStr")] // FIXME: // `fn from` in `impl From<&CStr> for Box` current implementation relies // on `CStr` being layout-compatible with `[u8]`. diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 946c85a205f5a..c94968b4817cb 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -811,7 +811,9 @@ impl<'src> Classifier<'src> { | LiteralKind::Str { .. } | LiteralKind::ByteStr { .. } | LiteralKind::RawStr { .. } - | LiteralKind::RawByteStr { .. } => Class::String, + | LiteralKind::RawByteStr { .. } + | LiteralKind::CStr { .. } + | LiteralKind::RawCStr { .. } => Class::String, // Number literals. LiteralKind::Float { .. } | LiteralKind::Int { .. } => Class::Number, }, diff --git a/src/tools/clippy/clippy_lints/src/matches/match_same_arms.rs b/src/tools/clippy/clippy_lints/src/matches/match_same_arms.rs index 158e6caa4de54..a48f4c77f857f 100644 --- a/src/tools/clippy/clippy_lints/src/matches/match_same_arms.rs +++ b/src/tools/clippy/clippy_lints/src/matches/match_same_arms.rs @@ -284,6 +284,7 @@ impl<'a> NormalizedPat<'a> { LitKind::Str(sym, _) => Self::LitStr(sym), LitKind::ByteStr(ref bytes, _) => Self::LitBytes(bytes), LitKind::Byte(val) => Self::LitInt(val.into()), + LitKind::CStr(ref bytes, _) => Self::LitBytes(bytes), LitKind::Char(val) => Self::LitInt(val.into()), LitKind::Int(val, _) => Self::LitInt(val), LitKind::Bool(val) => Self::LitBool(val), diff --git a/src/tools/clippy/clippy_lints/src/utils/author.rs b/src/tools/clippy/clippy_lints/src/utils/author.rs index 01927b6b5f10d..f75dff46624e4 100644 --- a/src/tools/clippy/clippy_lints/src/utils/author.rs +++ b/src/tools/clippy/clippy_lints/src/utils/author.rs @@ -304,6 +304,11 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { kind!("ByteStr(ref {vec})"); chain!(self, "let [{:?}] = **{vec}", vec.value); }, + LitKind::CStr(ref vec, _) => { + bind!(self, vec); + kind!("CStr(ref {vec})"); + chain!(self, "let [{:?}] = **{vec}", vec.value); + } LitKind::Str(s, _) => { bind!(self, s); kind!("Str({s}, _)"); diff --git a/src/tools/clippy/clippy_utils/src/consts.rs b/src/tools/clippy/clippy_utils/src/consts.rs index 99bfc4b5717c8..7c7ec6d334d9b 100644 --- a/src/tools/clippy/clippy_utils/src/consts.rs +++ b/src/tools/clippy/clippy_utils/src/consts.rs @@ -211,6 +211,7 @@ pub fn lit_to_mir_constant(lit: &LitKind, ty: Option>) -> Constant { LitKind::Str(ref is, _) => Constant::Str(is.to_string()), LitKind::Byte(b) => Constant::Int(u128::from(b)), LitKind::ByteStr(ref s, _) => Constant::Binary(Lrc::clone(s)), + LitKind::CStr(ref s, _) => Constant::Binary(Lrc::clone(s)), LitKind::Char(c) => Constant::Char(c), LitKind::Int(n, _) => Constant::Int(n), LitKind::Float(ref is, LitFloatType::Suffixed(fty)) => match fty { From 76d1f93896fb642cd27cbe8ef481b66e974dbdf9 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Mon, 6 Mar 2023 07:10:23 +0000 Subject: [PATCH 06/19] update and add a few tests --- compiler/rustc_ast/src/ast.rs | 2 +- compiler/rustc_ast/src/util/literal.rs | 4 ++-- compiler/rustc_ast_passes/src/feature_gate.rs | 1 + compiler/rustc_feature/src/active.rs | 2 ++ .../src/build/expr/as_constant.rs | 6 +++++ compiler/rustc_span/src/symbol.rs | 1 + .../rfcs/rfc-3348-c-string-literals/basic.rs | 7 ++++++ .../rfcs/rfc-3348-c-string-literals/gate.rs | 7 ++++++ .../rfc-3348-c-string-literals/gate.stderr | 21 ++++++++++++++++++ .../rfc-3348-c-string-literals/no-nuls.rs | Bin 0 -> 322 bytes .../rfc-3348-c-string-literals/no-nuls.stderr | Bin 0 -> 670 bytes 11 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 tests/ui/rfcs/rfc-3348-c-string-literals/basic.rs create mode 100644 tests/ui/rfcs/rfc-3348-c-string-literals/gate.rs create mode 100644 tests/ui/rfcs/rfc-3348-c-string-literals/gate.stderr create mode 100644 tests/ui/rfcs/rfc-3348-c-string-literals/no-nuls.rs create mode 100644 tests/ui/rfcs/rfc-3348-c-string-literals/no-nuls.stderr diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index fb22e4640647d..fb90d309fcde7 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1814,7 +1814,7 @@ pub enum LitKind { /// A byte string (`b"foo"`). Not stored as a symbol because it might be /// non-utf8, and symbols only allow utf8 strings. ByteStr(Lrc<[u8]>, StrStyle), - /// A C String (`c"foo"`). + /// A C String (`c"foo"`). Guaranteed only have `\0` in the end. CStr(Lrc<[u8]>, StrStyle), /// A byte char (`b'f'`). Byte(u8), diff --git a/compiler/rustc_ast/src/util/literal.rs b/compiler/rustc_ast/src/util/literal.rs index 8534011e18921..5fa0ea354550a 100644 --- a/compiler/rustc_ast/src/util/literal.rs +++ b/compiler/rustc_ast/src/util/literal.rs @@ -181,7 +181,7 @@ impl LitKind { } }); error?; - buf.push(b'\0'); + buf.push(0); LitKind::CStr(buf.into(), StrStyle::Cooked) } token::CStrRaw(n) => { @@ -204,7 +204,7 @@ impl LitKind { } }); error?; - buf.push(b'\0'); + buf.push(0); LitKind::CStr(buf.into(), StrStyle::Raw(n)) } token::Err => LitKind::Err, diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 17bcd24ee39fd..9defc6603e837 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -1,3 +1,4 @@ +use ast::token; use rustc_ast as ast; use rustc_ast::visit::{self, AssocCtxt, FnCtxt, FnKind, Visitor}; use rustc_ast::{attr, AssocConstraint, AssocConstraintKind, NodeId}; diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs index 6201e5b619b87..45f462a63ee8c 100644 --- a/compiler/rustc_feature/src/active.rs +++ b/compiler/rustc_feature/src/active.rs @@ -313,6 +313,8 @@ declare_features! ( (active, async_fn_in_trait, "1.66.0", Some(91611), None), /// Treat `extern "C"` function as nounwind. (active, c_unwind, "1.52.0", Some(74990), None), + /// Allows `c"foo"` literals. + (active, c_str_literals, "CURRENT_RUSTC_VERSION", Some(105723), None), /// Allows using C-variadics. (active, c_variadic, "1.34.0", Some(44930), None), /// Allows the use of `#[cfg(sanitize = "option")]`; set when -Zsanitizer is used. diff --git a/compiler/rustc_mir_build/src/build/expr/as_constant.rs b/compiler/rustc_mir_build/src/build/expr/as_constant.rs index fbcfd43372433..59549435233c5 100644 --- a/compiler/rustc_mir_build/src/build/expr/as_constant.rs +++ b/compiler/rustc_mir_build/src/build/expr/as_constant.rs @@ -146,6 +146,12 @@ pub(crate) fn lit_to_mir_constant<'tcx>( let id = tcx.allocate_bytes(data); ConstValue::Scalar(Scalar::from_pointer(id.into(), &tcx)) } + (ast::LitKind::CStr(data, _), ty::Ref(_, inner_ty, _)) if matches!(inner_ty.kind(), ty::Adt(def, _) if Some(def.did()) == tcx.lang_items().c_str()) => + { + let allocation = Allocation::from_bytes_byte_aligned_immutable(data as &[u8]); + let allocation = tcx.mk_const_alloc(allocation); + ConstValue::Slice { data: allocation, start: 0, end: data.len() } + } (ast::LitKind::Byte(n), ty::Uint(ty::UintTy::U8)) => { ConstValue::Scalar(Scalar::from_uint(*n, Size::from_bytes(1))) } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 7969b848fd956..f83aa504b90e6 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -441,6 +441,7 @@ symbols! { bridge, bswap, c_str, + c_str_literals, c_unwind, c_variadic, call, diff --git a/tests/ui/rfcs/rfc-3348-c-string-literals/basic.rs b/tests/ui/rfcs/rfc-3348-c-string-literals/basic.rs new file mode 100644 index 0000000000000..e4b07ab8108e0 --- /dev/null +++ b/tests/ui/rfcs/rfc-3348-c-string-literals/basic.rs @@ -0,0 +1,7 @@ +// run-pass + +#![feature(c_str_literals)] + +fn main() { + assert_eq!(b"test\0", c"test".to_bytes_with_nul()); +} diff --git a/tests/ui/rfcs/rfc-3348-c-string-literals/gate.rs b/tests/ui/rfcs/rfc-3348-c-string-literals/gate.rs new file mode 100644 index 0000000000000..674b0c5e23351 --- /dev/null +++ b/tests/ui/rfcs/rfc-3348-c-string-literals/gate.rs @@ -0,0 +1,7 @@ +fn main() { + c"foo"; + //~^ ERROR: `c".."` literals are experimental + + m!(c"test"); + //~^ ERROR: `c".."` literals are experimental +} diff --git a/tests/ui/rfcs/rfc-3348-c-string-literals/gate.stderr b/tests/ui/rfcs/rfc-3348-c-string-literals/gate.stderr new file mode 100644 index 0000000000000..bc0c537aada83 --- /dev/null +++ b/tests/ui/rfcs/rfc-3348-c-string-literals/gate.stderr @@ -0,0 +1,21 @@ +error[E0658]: `c".."` literals are experimental + --> $DIR/gate.rs:8:5 + | +LL | c"foo"; + | ^^^^^^ + | + = note: see issue #105723 for more information + = help: add `#![feature(c_str_literals)]` to the crate attributes to enable + +error[E0658]: `c".."` literals are experimental + --> $DIR/gate.rs:11:8 + | +LL | m!(c"test"); + | ^^^^^^^ + | + = note: see issue #105723 for more information + = help: add `#![feature(c_str_literals)]` to the crate attributes to enable + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/rfcs/rfc-3348-c-string-literals/no-nuls.rs b/tests/ui/rfcs/rfc-3348-c-string-literals/no-nuls.rs new file mode 100644 index 0000000000000000000000000000000000000000..f6c86a1ba8762358537827376cbffc5c3f4508e9 GIT binary patch literal 322 zcmY#Zj802UEGaEY)kuynE-8x7$t+1NO3W$NjOF4=%Tvfr%*@l!RH)`s0D@$t7y~71 zFjHT@E>6KUD9As^N+GW_Cr2SUBe5tk8K_qwGf%-;0cccaUOE>{TWPg{K`mKY8OYKV QQ(-`sON*39w6B&60N$!x!TeXXdftt2!GI~Yrix-cM3w;1ma$Enh(Pc#!UVj(ggLC5kOomYT5DR{C8&R_#& z`K>OKf1}P$Lfcgd?nWL;VFqm$4^_YPMWO$ VFr1x))mY+2^4@4?5MIMD_6Dmm#F_vA literal 0 HcmV?d00001 From a49570fd20a16c0a41b1dfdaf121ef69f60acd7e Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Mon, 6 Mar 2023 07:42:04 +0000 Subject: [PATCH 07/19] fix TODO comments --- compiler/rustc_ast/src/util/literal.rs | 10 +- compiler/rustc_ast_pretty/src/pprust/state.rs | 6 +- .../rustc_builtin_macros/src/concat_bytes.rs | 5 +- .../rustc_expand/src/proc_macro_server.rs | 6 +- compiler/rustc_feature/src/active.rs | 4 +- compiler/rustc_lexer/src/lib.rs | 108 +++++++++--------- library/proc_macro/src/bridge/mod.rs | 4 + .../rfcs/rfc-3348-c-string-literals/gate.rs | 6 + 8 files changed, 82 insertions(+), 67 deletions(-) diff --git a/compiler/rustc_ast/src/util/literal.rs b/compiler/rustc_ast/src/util/literal.rs index 5fa0ea354550a..cd3b163e3ac97 100644 --- a/compiler/rustc_ast/src/util/literal.rs +++ b/compiler/rustc_ast/src/util/literal.rs @@ -240,8 +240,14 @@ impl fmt::Display for LitKind { string = symbol )?; } - // TODO need to reescape - LitKind::CStr(..) => todo!(), + LitKind::CStr(ref bytes, StrStyle::Cooked) => { + write!(f, "c\"{}\"", escape_byte_str_symbol(bytes))? + } + LitKind::CStr(ref bytes, StrStyle::Raw(n)) => { + // This can only be valid UTF-8. + let symbol = str::from_utf8(bytes).unwrap(); + write!(f, "cr{delim}\"{symbol}\"{delim}", delim = "#".repeat(n as usize),)?; + } LitKind::Int(n, ty) => { write!(f, "{n}")?; match ty { diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index 535ac89e751d5..61b7863c686cf 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -210,8 +210,10 @@ pub fn literal_to_string(lit: token::Lit) -> String { token::ByteStrRaw(n) => { format!("br{delim}\"{string}\"{delim}", delim = "#".repeat(n as usize), string = symbol) } - // TODO - token::CStr | token::CStrRaw(_) => todo!(), + token::CStr => format!("c\"{symbol}\""), + token::CStrRaw(n) => { + format!("cr{delim}\"{symbol}\"{delim}", delim = "#".repeat(n as usize)) + } token::Integer | token::Float | token::Bool | token::Err => symbol.to_string(), }; diff --git a/compiler/rustc_builtin_macros/src/concat_bytes.rs b/compiler/rustc_builtin_macros/src/concat_bytes.rs index ae674995e4294..5ef35af0a059a 100644 --- a/compiler/rustc_builtin_macros/src/concat_bytes.rs +++ b/compiler/rustc_builtin_macros/src/concat_bytes.rs @@ -19,8 +19,9 @@ fn invalid_type_err( let snippet = cx.sess.source_map().span_to_snippet(span).ok(); match ast::LitKind::from_token_lit(token_lit) { Ok(ast::LitKind::CStr(_, _)) => { - // TODO - cx.span_err(span, "cannot concatenate C string litearls"); + // FIXME(c_str_literals): should concatenation of C string literals + // include the null bytes in the end? + cx.span_err(span, "cannot concatenate C string literals"); } Ok(ast::LitKind::Char(_)) => { let sugg = diff --git a/compiler/rustc_expand/src/proc_macro_server.rs b/compiler/rustc_expand/src/proc_macro_server.rs index 04bdea273ebd5..891e84a2f3071 100644 --- a/compiler/rustc_expand/src/proc_macro_server.rs +++ b/compiler/rustc_expand/src/proc_macro_server.rs @@ -61,8 +61,8 @@ impl FromInternal for LitKind { token::StrRaw(n) => LitKind::StrRaw(n), token::ByteStr => LitKind::ByteStr, token::ByteStrRaw(n) => LitKind::ByteStrRaw(n), - // TODO - token::CStr | token::CStrRaw(_) => todo!(), + token::CStr => LitKind::CStr, + token::CStrRaw(n) => LitKind::CStrRaw(n), token::Err => LitKind::Err, token::Bool => unreachable!(), } @@ -80,6 +80,8 @@ impl ToInternal for LitKind { LitKind::StrRaw(n) => token::StrRaw(n), LitKind::ByteStr => token::ByteStr, LitKind::ByteStrRaw(n) => token::ByteStrRaw(n), + LitKind::CStr => token::CStr, + LitKind::CStrRaw(n) => token::CStrRaw(n), LitKind::Err => token::Err, } } diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs index 45f462a63ee8c..4e5eebd285bbb 100644 --- a/compiler/rustc_feature/src/active.rs +++ b/compiler/rustc_feature/src/active.rs @@ -311,10 +311,10 @@ declare_features! ( (active, async_closure, "1.37.0", Some(62290), None), /// Allows async functions to be declared, implemented, and used in traits. (active, async_fn_in_trait, "1.66.0", Some(91611), None), - /// Treat `extern "C"` function as nounwind. - (active, c_unwind, "1.52.0", Some(74990), None), /// Allows `c"foo"` literals. (active, c_str_literals, "CURRENT_RUSTC_VERSION", Some(105723), None), + /// Treat `extern "C"` function as nounwind. + (active, c_unwind, "1.52.0", Some(74990), None), /// Allows using C-variadics. (active, c_variadic, "1.34.0", Some(44930), None), /// Allows the use of `#[cfg(sanitize = "option")]`; set when -Zsanitizer is used. diff --git a/compiler/rustc_lexer/src/lib.rs b/compiler/rustc_lexer/src/lib.rs index 95cb1f93e3906..ce8c9ebe7ce77 100644 --- a/compiler/rustc_lexer/src/lib.rs +++ b/compiler/rustc_lexer/src/lib.rs @@ -361,65 +361,18 @@ impl Cursor<'_> { }, // Byte literal, byte string literal, raw byte string literal or identifier. - 'b' => match (self.first(), self.second()) { - ('\'', _) => { - self.bump(); - let terminated = self.single_quoted_string(); - let suffix_start = self.pos_within_token(); - if terminated { - self.eat_literal_suffix(); - } - let kind = Byte { terminated }; - Literal { kind, suffix_start } - } - ('"', _) => { - self.bump(); - let terminated = self.double_quoted_string(); - let suffix_start = self.pos_within_token(); - if terminated { - self.eat_literal_suffix(); - } - let kind = ByteStr { terminated }; - Literal { kind, suffix_start } - } - ('r', '"') | ('r', '#') => { - self.bump(); - let res = self.raw_double_quoted_string(2); - let suffix_start = self.pos_within_token(); - if res.is_ok() { - self.eat_literal_suffix(); - } - let kind = RawByteStr { n_hashes: res.ok() }; - Literal { kind, suffix_start } - } - _ => self.ident_or_unknown_prefix(), - }, + 'b' => self.c_or_byte_string( + |terminated| ByteStr { terminated }, + |n_hashes| RawByteStr { n_hashes }, + Some(|terminated| Byte { terminated }), + ), - // TODO deduplicate this code // c-string literal, raw c-string literal or identifier. - 'c' => match (self.first(), self.second()) { - ('"', _) => { - self.bump(); - let terminated = self.double_quoted_string(); - let suffix_start = self.pos_within_token(); - if terminated { - self.eat_literal_suffix(); - } - let kind = CStr { terminated }; - Literal { kind, suffix_start } - } - ('r', '"') | ('r', '#') => { - self.bump(); - let res = self.raw_double_quoted_string(2); - let suffix_start = self.pos_within_token(); - if res.is_ok() { - self.eat_literal_suffix(); - } - let kind = RawCStr { n_hashes: res.ok() }; - Literal { kind, suffix_start } - } - _ => self.ident_or_unknown_prefix(), - }, + 'c' => self.c_or_byte_string( + |terminated| CStr { terminated }, + |n_hashes| RawCStr { n_hashes }, + None, + ), // Identifier (this should be checked after other variant that can // start as identifier). @@ -583,6 +536,47 @@ impl Cursor<'_> { } } + fn c_or_byte_string( + &mut self, + mk_kind: impl FnOnce(bool) -> LiteralKind, + mk_kind_raw: impl FnOnce(Option) -> LiteralKind, + single_quoted: Option LiteralKind>, + ) -> TokenKind { + match (self.first(), self.second(), single_quoted) { + ('\'', _, Some(mk_kind)) => { + self.bump(); + let terminated = self.single_quoted_string(); + let suffix_start = self.pos_within_token(); + if terminated { + self.eat_literal_suffix(); + } + let kind = mk_kind(terminated); + Literal { kind, suffix_start } + } + ('"', _, _) => { + self.bump(); + let terminated = self.double_quoted_string(); + let suffix_start = self.pos_within_token(); + if terminated { + self.eat_literal_suffix(); + } + let kind = mk_kind(terminated); + Literal { kind, suffix_start } + } + ('r', '"', _) | ('r', '#', _) => { + self.bump(); + let res = self.raw_double_quoted_string(2); + let suffix_start = self.pos_within_token(); + if res.is_ok() { + self.eat_literal_suffix(); + } + let kind = mk_kind_raw(res.ok()); + Literal { kind, suffix_start } + } + _ => self.ident_or_unknown_prefix(), + } + } + fn number(&mut self, first_digit: char) -> LiteralKind { debug_assert!('0' <= self.prev() && self.prev() <= '9'); let mut base = Base::Decimal; diff --git a/library/proc_macro/src/bridge/mod.rs b/library/proc_macro/src/bridge/mod.rs index 54b11c543f162..caecda1bc63fd 100644 --- a/library/proc_macro/src/bridge/mod.rs +++ b/library/proc_macro/src/bridge/mod.rs @@ -337,6 +337,8 @@ pub enum LitKind { StrRaw(u8), ByteStr, ByteStrRaw(u8), + CStr, + CStrRaw(u8), Err, } @@ -350,6 +352,8 @@ rpc_encode_decode!( StrRaw(n), ByteStr, ByteStrRaw(n), + CStr, + CStrRaw(n), Err, } ); diff --git a/tests/ui/rfcs/rfc-3348-c-string-literals/gate.rs b/tests/ui/rfcs/rfc-3348-c-string-literals/gate.rs index 674b0c5e23351..b27da26ed23bb 100644 --- a/tests/ui/rfcs/rfc-3348-c-string-literals/gate.rs +++ b/tests/ui/rfcs/rfc-3348-c-string-literals/gate.rs @@ -1,3 +1,9 @@ +// gate-test-c_str_literals + +macro_rules! m { + ($t:tt) => {} +} + fn main() { c"foo"; //~^ ERROR: `c".."` literals are experimental From d5e7206ca674661a13d7bbe03284b81031e1ac33 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Mon, 6 Mar 2023 14:09:28 +0000 Subject: [PATCH 08/19] rm diag item, use lang item --- library/core/src/ffi/c_str.rs | 1 - src/tools/clippy/clippy_lints/src/strlen_on_c_strings.rs | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/library/core/src/ffi/c_str.rs b/library/core/src/ffi/c_str.rs index 2ac679b6bc347..07b11814f965f 100644 --- a/library/core/src/ffi/c_str.rs +++ b/library/core/src/ffi/c_str.rs @@ -79,7 +79,6 @@ use crate::str; /// /// [str]: prim@str "str" #[derive(Hash)] -#[cfg_attr(not(test), rustc_diagnostic_item = "CStr")] #[stable(feature = "core_c_str", since = "1.64.0")] #[rustc_has_incoherent_inherent_impls] #[cfg_attr(not(bootstrap), lang = "CStr")] diff --git a/src/tools/clippy/clippy_lints/src/strlen_on_c_strings.rs b/src/tools/clippy/clippy_lints/src/strlen_on_c_strings.rs index 03324c66e8efc..2f2e84fa35a12 100644 --- a/src/tools/clippy/clippy_lints/src/strlen_on_c_strings.rs +++ b/src/tools/clippy/clippy_lints/src/strlen_on_c_strings.rs @@ -1,11 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_context; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; use clippy_utils::visitors::is_expr_unsafe; use clippy_utils::{get_parent_node, match_libc_symbol}; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, Node, UnsafeSource}; +use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, LangItem, Node, UnsafeSource}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::sym; @@ -67,7 +67,7 @@ impl<'tcx> LateLintPass<'tcx> for StrlenOnCStrings { let val_name = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0; let method_name = if is_type_diagnostic_item(cx, ty, sym::cstring_type) { "as_bytes" - } else if is_type_diagnostic_item(cx, ty, sym::CStr) { + } else if is_type_lang_item(cx, ty, LangItem::CStr) { "to_bytes" } else { return; From 4c01d494b8233c930868be33cf4880b4267ede82 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Mon, 6 Mar 2023 14:14:55 +0000 Subject: [PATCH 09/19] refactor unescape --- compiler/rustc_lexer/src/unescape.rs | 91 +++++++++++++------ .../src/lexer/unescape_error_reporting.rs | 22 +++-- 2 files changed, 75 insertions(+), 38 deletions(-) diff --git a/compiler/rustc_lexer/src/unescape.rs b/compiler/rustc_lexer/src/unescape.rs index 4b707c9ec9619..c9ad54d8d9806 100644 --- a/compiler/rustc_lexer/src/unescape.rs +++ b/compiler/rustc_lexer/src/unescape.rs @@ -86,7 +86,8 @@ where let res = unescape_char_or_byte(&mut chars, mode == Mode::Byte); callback(0..(src.len() - chars.as_str().len()), res); } - Mode::Str | Mode::ByteStr => unescape_str_or_byte_str(src, mode == Mode::ByteStr, callback), + Mode::Str | Mode::ByteStr => unescape_str_common(src, mode, callback), + Mode::RawStr | Mode::RawByteStr => { unescape_raw_str_or_raw_byte_str(src, mode == Mode::RawByteStr, callback) } @@ -94,6 +95,7 @@ where } } +/// A unit within CStr. Must not be a nul character. pub enum CStrUnit { Byte(u8), Char(char), @@ -164,24 +166,52 @@ impl Mode { } } - pub fn is_byte(self) -> bool { + /// Non-byte literals should have `\xXX` escapes that are within the ASCII range. + pub fn ascii_escapes_should_be_ascii(self) -> bool { + match self { + Mode::Char | Mode::Str | Mode::RawStr => true, + Mode::Byte | Mode::ByteStr | Mode::RawByteStr | Mode::CStr | Mode::RawCStr => false, + } + } + + /// Whether characters within the literal must be within the ASCII range + pub fn characters_should_be_ascii(self) -> bool { + match self { + Mode::Byte | Mode::ByteStr | Mode::RawByteStr => true, + Mode::Char | Mode::Str | Mode::RawStr | Mode::CStr | Mode::RawCStr => false, + } + } + + /// Byte literals do not allow unicode escape. + pub fn is_unicode_escape_disallowed(self) -> bool { match self { - Mode::Byte | Mode::ByteStr | Mode::RawByteStr | Mode::CStr | Mode::RawCStr => true, - Mode::Char | Mode::Str | Mode::RawStr => false, + Mode::Byte | Mode::ByteStr | Mode::RawByteStr => true, + Mode::Char | Mode::Str | Mode::RawStr | Mode::CStr | Mode::RawCStr => false, + } + } + + pub fn prefix_noraw(self) -> &'static str { + match self { + Mode::Byte | Mode::ByteStr | Mode::RawByteStr => "b", + Mode::CStr | Mode::RawCStr => "c", + Mode::Char | Mode::Str | Mode::RawStr => "", } } } -fn scan_escape(chars: &mut Chars<'_>, is_byte: bool) -> Result { +fn scan_escape + From>( + chars: &mut Chars<'_>, + mode: Mode, +) -> Result { // Previous character was '\\', unescape what follows. let res = match chars.next().ok_or(EscapeError::LoneSlash)? { - '"' => '"', - 'n' => '\n', - 'r' => '\r', - 't' => '\t', - '\\' => '\\', - '\'' => '\'', - '0' => '\0', + '"' => b'"', + 'n' => b'\n', + 'r' => b'\r', + 't' => b'\t', + '\\' => b'\\', + '\'' => b'\'', + '0' => b'\0', 'x' => { // Parse hexadecimal character code. @@ -194,22 +224,23 @@ fn scan_escape(chars: &mut Chars<'_>, is_byte: bool) -> Result scan_unicode(chars, is_byte)?, + 'u' => return scan_unicode(chars, mode.is_unicode_escape_disallowed()).map(Into::into), _ => return Err(EscapeError::InvalidEscape), }; - Ok(res) + Ok(res.into()) } -fn scan_unicode(chars: &mut Chars<'_>, is_byte: bool) -> Result { +fn scan_unicode( + chars: &mut Chars<'_>, + is_unicode_escape_disallowed: bool, +) -> Result { // We've parsed '\u', now we have to parse '{..}'. if chars.next() != Some('{') { @@ -237,7 +268,7 @@ fn scan_unicode(chars: &mut Chars<'_>, is_byte: bool) -> Result, is_byte: bool) -> Result Result { - if is_byte && !c.is_ascii() { +fn ascii_check(c: char, characters_should_be_ascii: bool) -> Result { + if characters_should_be_ascii && !c.is_ascii() { // Byte literal can't be a non-ascii character. Err(EscapeError::NonAsciiCharInByte) } else { @@ -275,7 +306,7 @@ fn ascii_check(c: char, is_byte: bool) -> Result { fn unescape_char_or_byte(chars: &mut Chars<'_>, is_byte: bool) -> Result { let c = chars.next().ok_or(EscapeError::ZeroChars)?; let res = match c { - '\\' => scan_escape(chars, is_byte), + '\\' => scan_escape(chars, if is_byte { Mode::Byte } else { Mode::Char }), '\n' | '\t' | '\'' => Err(EscapeError::EscapeOnlyChar), '\r' => Err(EscapeError::BareCarriageReturn), _ => ascii_check(c, is_byte), @@ -288,9 +319,9 @@ fn unescape_char_or_byte(chars: &mut Chars<'_>, is_byte: bool) -> Result(src: &str, is_byte: bool, callback: &mut F) +fn unescape_str_common + From>(src: &str, mode: Mode, callback: &mut F) where - F: FnMut(Range, Result), + F: FnMut(Range, Result), { let mut chars = src.chars(); @@ -312,17 +343,17 @@ where }); continue; } - _ => scan_escape(&mut chars, is_byte), + _ => scan_escape::(&mut chars, mode), } } - '\n' => Ok('\n'), - '\t' => Ok('\t'), + '\n' => Ok(b'\n'.into()), + '\t' => Ok(b'\t'.into()), '"' => Err(EscapeError::EscapeOnlyChar), '\r' => Err(EscapeError::BareCarriageReturn), - _ => ascii_check(c, is_byte), + _ => ascii_check(c, mode.characters_should_be_ascii()).map(Into::into), }; let end = src.len() - chars.as_str().len(); - callback(start..end, res); + callback(start..end, res.map(Into::into)); } } diff --git a/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs b/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs index 0d12ec6081d83..2e4c798ab2259 100644 --- a/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs +++ b/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs @@ -78,8 +78,7 @@ pub(crate) fn emit_unescape_error( } }; let sugg = sugg.unwrap_or_else(|| { - let is_byte = mode.is_byte(); - let prefix = if is_byte { "b" } else { "" }; + let prefix = mode.prefix_noraw(); let mut escaped = String::with_capacity(lit.len()); let mut chrs = lit.chars().peekable(); while let Some(first) = chrs.next() { @@ -97,7 +96,11 @@ pub(crate) fn emit_unescape_error( }; } let sugg = format!("{prefix}\"{escaped}\""); - MoreThanOneCharSugg::Quotes { span: span_with_quotes, is_byte, sugg } + MoreThanOneCharSugg::Quotes { + span: span_with_quotes, + is_byte: mode == Mode::Byte, + sugg, + } }); handler.emit_err(UnescapeError::MoreThanOneChar { span: span_with_quotes, @@ -112,7 +115,7 @@ pub(crate) fn emit_unescape_error( char_span, escaped_sugg: c.escape_default().to_string(), escaped_msg: escaped_char(c), - byte: mode.is_byte(), + byte: mode == Mode::Byte, }); } EscapeError::BareCarriageReturn => { @@ -126,12 +129,15 @@ pub(crate) fn emit_unescape_error( EscapeError::InvalidEscape => { let (c, span) = last_char(); - let label = - if mode.is_byte() { "unknown byte escape" } else { "unknown character escape" }; + let label = if mode == Mode::Byte || mode == Mode::ByteStr { + "unknown byte escape" + } else { + "unknown character escape" + }; let ec = escaped_char(c); let mut diag = handler.struct_span_err(span, &format!("{}: `{}`", label, ec)); diag.span_label(span, label); - if c == '{' || c == '}' && !mode.is_byte() { + if c == '{' || c == '}' && matches!(mode, Mode::Str | Mode::RawStr) { diag.help( "if used in a formatting string, curly braces are escaped with `{{` and `}}`", ); @@ -141,7 +147,7 @@ pub(crate) fn emit_unescape_error( version control settings", ); } else { - if !mode.is_byte() { + if mode == Mode::Str || mode == Mode::Char { diag.span_suggestion( span_with_quotes, "if you meant to write a literal backslash (perhaps escaping in a regular expression), consider a raw string literal", From 78e3455d375feb5d100a43110f78b405a8ff05f1 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Mon, 6 Mar 2023 14:15:02 +0000 Subject: [PATCH 10/19] address comments --- compiler/rustc_ast/src/ast.rs | 2 +- compiler/rustc_lexer/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index fb90d309fcde7..8555ba3e7cee4 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1814,7 +1814,7 @@ pub enum LitKind { /// A byte string (`b"foo"`). Not stored as a symbol because it might be /// non-utf8, and symbols only allow utf8 strings. ByteStr(Lrc<[u8]>, StrStyle), - /// A C String (`c"foo"`). Guaranteed only have `\0` in the end. + /// A C String (`c"foo"`). Guaranteed to only have `\0` at the end. CStr(Lrc<[u8]>, StrStyle), /// A byte char (`b'f'`). Byte(u8), diff --git a/compiler/rustc_lexer/src/lib.rs b/compiler/rustc_lexer/src/lib.rs index ce8c9ebe7ce77..c07dc19a0ac3a 100644 --- a/compiler/rustc_lexer/src/lib.rs +++ b/compiler/rustc_lexer/src/lib.rs @@ -194,7 +194,7 @@ pub enum LiteralKind { /// "br"abc"", "br#"abc"#", "br####"ab"###"c"####", "br#"a". `None` /// indicates an invalid literal. RawByteStr { n_hashes: Option }, - /// `cr"abc"`, "cr#"abc"#", `cr#"a`. `None` is invalid. + /// `cr"abc"`, "cr#"abc"#", `cr#"a`. `None` indicates an invalid literal. RawCStr { n_hashes: Option }, } From bf3ca5979e47774802e95623c11e71fb303e5ff3 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Tue, 7 Mar 2023 05:09:19 +0000 Subject: [PATCH 11/19] try gating early, add non-ascii test --- compiler/rustc_ast_passes/src/feature_gate.rs | 1 - compiler/rustc_parse/src/parser/expr.rs | 1 + tests/ui/rfcs/rfc-3348-c-string-literals/non-ascii.rs | 10 ++++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 tests/ui/rfcs/rfc-3348-c-string-literals/non-ascii.rs diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 9defc6603e837..17bcd24ee39fd 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -1,4 +1,3 @@ -use ast::token; use rustc_ast as ast; use rustc_ast::visit::{self, AssocCtxt, FnCtxt, FnKind, Visitor}; use rustc_ast::{attr, AssocConstraint, AssocConstraintKind, NodeId}; diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index bff9de5c652f9..c89b4ca8d6f0f 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -1922,6 +1922,7 @@ impl<'a> Parser<'a> { let recovered = self.recover_after_dot(); let token = recovered.as_ref().unwrap_or(&self.token); let span = token.span; + token::Lit::from_token(token).map(|token_lit| { self.bump(); (token_lit, span) diff --git a/tests/ui/rfcs/rfc-3348-c-string-literals/non-ascii.rs b/tests/ui/rfcs/rfc-3348-c-string-literals/non-ascii.rs new file mode 100644 index 0000000000000..82e8e2090d7db --- /dev/null +++ b/tests/ui/rfcs/rfc-3348-c-string-literals/non-ascii.rs @@ -0,0 +1,10 @@ +// run-pass + +#![feature(c_str_literals)] + +fn main() { + assert_eq!( + c"\xEF\x80🦀\u{1F980}".to_bytes_with_nul(), + &[0xEF, 0x80, 0xF0, 0x9F, 0xA6, 0x80, 0xF0, 0x9F, 0xA6, 0x80, 0x00], + ); +} From abb181dfd9b9df22908ab08d7cfb46509295e2e6 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Fri, 10 Mar 2023 14:18:58 +0000 Subject: [PATCH 12/19] make it semantic error --- compiler/rustc_ast/src/util/literal.rs | 2 ++ compiler/rustc_ast_passes/src/feature_gate.rs | 1 + compiler/rustc_parse/src/lexer/mod.rs | 3 +++ compiler/rustc_session/messages.ftl | 2 ++ compiler/rustc_session/src/errors.rs | 15 ++++++++++++++- .../rfcs/rfc-3348-c-string-literals/no-nuls.rs | Bin 322 -> 570 bytes .../rfc-3348-c-string-literals/no-nuls.stderr | Bin 670 -> 674 bytes 7 files changed, 22 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_ast/src/util/literal.rs b/compiler/rustc_ast/src/util/literal.rs index cd3b163e3ac97..15a54fe13d0b7 100644 --- a/compiler/rustc_ast/src/util/literal.rs +++ b/compiler/rustc_ast/src/util/literal.rs @@ -8,6 +8,7 @@ use rustc_lexer::unescape::{ }; use rustc_span::symbol::{kw, sym, Symbol}; use rustc_span::Span; +use std::ops::Range; use std::{ascii, fmt, str}; // Escapes a string, represented as a symbol. Reuses the original symbol, @@ -38,6 +39,7 @@ pub enum LitError { InvalidFloatSuffix, NonDecimalFloat(u32), IntTooLarge(u32), + NulInCStr(Range), } impl LitKind { diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 17bcd24ee39fd..c4578ec4af1ef 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -572,6 +572,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session) { } }; } + gate_all!(c_str_literals, "`c\"..\"` literals are experimental"); gate_all!( if_let_guard, "`if let` guards are experimental", diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs index b2050780309f8..050f18986154a 100644 --- a/compiler/rustc_parse/src/lexer/mod.rs +++ b/compiler/rustc_parse/src/lexer/mod.rs @@ -204,6 +204,9 @@ impl<'a> StringReader<'a> { rustc_lexer::TokenKind::Literal { kind, suffix_start } => { let suffix_start = start + BytePos(suffix_start); let (kind, symbol) = self.cook_lexer_literal(start, suffix_start, kind); + if let token::LitKind::CStr | token::LitKind::CStrRaw(_) = kind { + self.sess.gated_spans.gate(sym::c_str_literals, self.mk_sp(start, self.pos)); + } let suffix = if suffix_start < self.pos { let string = self.str_from(suffix_start); if string == "_" { diff --git a/compiler/rustc_session/messages.ftl b/compiler/rustc_session/messages.ftl index ff53f22d43f93..2420857e739ed 100644 --- a/compiler/rustc_session/messages.ftl +++ b/compiler/rustc_session/messages.ftl @@ -93,3 +93,5 @@ session_invalid_int_literal_width = invalid width `{$width}` for integer literal .help = valid widths are 8, 16, 32, 64 and 128 session_optimization_fuel_exhausted = optimization-fuel-exhausted: {$msg} + +session_nul_in_c_str = null characters in C string literals are not supported diff --git a/compiler/rustc_session/src/errors.rs b/compiler/rustc_session/src/errors.rs index bd32adbbdbb54..22af74eb1d9e0 100644 --- a/compiler/rustc_session/src/errors.rs +++ b/compiler/rustc_session/src/errors.rs @@ -6,7 +6,7 @@ use rustc_ast::token; use rustc_ast::util::literal::LitError; use rustc_errors::{error_code, DiagnosticMessage, EmissionGuarantee, IntoDiagnostic, MultiSpan}; use rustc_macros::Diagnostic; -use rustc_span::{Span, Symbol}; +use rustc_span::{BytePos, Span, Symbol}; use rustc_target::spec::{SplitDebuginfo, StackProtector, TargetTriple}; #[derive(Diagnostic)] @@ -307,6 +307,13 @@ pub(crate) struct BinaryFloatLiteralNotSupported { pub span: Span, } +#[derive(Diagnostic)] +#[diag(session_nul_in_c_str)] +pub(crate) struct NulInCStr { + #[primary_span] + pub span: Span, +} + pub fn report_lit_error(sess: &ParseSess, err: LitError, lit: token::Lit, span: Span) { // Checks if `s` looks like i32 or u1234 etc. fn looks_like_width_suffix(first_chars: &[char], s: &str) -> bool { @@ -385,6 +392,12 @@ pub fn report_lit_error(sess: &ParseSess, err: LitError, lit: token::Lit, span: }; sess.emit_err(IntLiteralTooLarge { span, limit }); } + LitError::NulInCStr(range) => { + let lo = BytePos(span.lo().0 + range.start as u32 + 2); + let hi = BytePos(span.lo().0 + range.end as u32 + 2); + let span = span.with_lo(lo).with_hi(hi); + sess.emit_err(NulInCStr { span }); + } } } diff --git a/tests/ui/rfcs/rfc-3348-c-string-literals/no-nuls.rs b/tests/ui/rfcs/rfc-3348-c-string-literals/no-nuls.rs index f6c86a1ba8762358537827376cbffc5c3f4508e9..e66519f294cd08f751f03b725967b5cd60dcee1d 100644 GIT binary patch literal 570 zcmbV}F>Avx5QRPKSDYOWQkQs6Xd#4jDiqw&PK^+9YLJy6ouS3JzdaYZZGy>C-z1*& z?w-C|#6_(oc209ud32R&P&;Y7*fUmJXk}x$fSv)BO3Ex*hvRbj{SLc4f6Z55J7Yk7 zNGBC}Jv0`!K)o7!V86bu&$3~jH=1WFKeKsZUGm?F17odJ?pV3bXdi=aPx$r2jHf(B zTp@}F0gq464{Jv67lo=1{CV^8wjoT$jeCafw@y%Qo3Q zPSCPEM;jBMN(!DFx@K1cWyzSFfW04Mhd1Y+KEI4HjfNE;5i#rWCql1B(8Y@nzCYRR fyP*I|{vI!G(MUg&)j`5rY|)T>DjOU+cR0qrx?#zt literal 670 zcmcJNI}5@v5XYVKDgG4KYQ#q+f{QLfC+AYAHE5wEa!Ew6es@F0SVV;0bT96AFG-|H zK)JR>eXXdftt2!GI~Yrix-cM3w;1ma$Enh(Pc#!UVj(ggLC5kOomYT5DR{C8&R_#& z`K>OKf1}P$Lfcgd?nWL;VFqm$4^_YPMWO$ VFr1x))mY+2^4@4?5MIMD_6Dmm#F_vA From 6d905a8cc14783bc577dc0534d2516d16ef3e43b Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Fri, 10 Mar 2023 14:29:26 +0000 Subject: [PATCH 13/19] fix tidy --- .../rfcs/rfc-3348-c-string-literals/no-nuls.rs | Bin 570 -> 565 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/ui/rfcs/rfc-3348-c-string-literals/no-nuls.rs b/tests/ui/rfcs/rfc-3348-c-string-literals/no-nuls.rs index e66519f294cd08f751f03b725967b5cd60dcee1d..7bc6097f124aabfded4a9e9791611cfe4fbf9f78 100644 GIT binary patch delta 7 OcmdnRvXy0nDH8w-N&;U1 delta 13 ScmdnWvWsPdDHAIf2mk;YE&`SS From d30c6681751b10a14265e09e5f74f39d2a32e641 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Fri, 28 Apr 2023 15:28:52 +0000 Subject: [PATCH 14/19] make cook generic --- compiler/rustc_parse/src/lexer/mod.rs | 64 +++++++++++---------------- 1 file changed, 27 insertions(+), 37 deletions(-) diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs index 050f18986154a..9d0037b8a8083 100644 --- a/compiler/rustc_parse/src/lexer/mod.rs +++ b/compiler/rustc_parse/src/lexer/mod.rs @@ -1,3 +1,5 @@ +use std::ops::Range; + use crate::errors; use crate::lexer::unicode_chars::UNICODE_ARRAY; use crate::make_unclosed_delims_error; @@ -6,7 +8,7 @@ use rustc_ast::token::{self, CommentKind, Delimiter, Token, TokenKind}; use rustc_ast::tokenstream::TokenStream; use rustc_ast::util::unicode::contains_text_flow_control_chars; use rustc_errors::{error_code, Applicability, Diagnostic, DiagnosticBuilder, StashKey}; -use rustc_lexer::unescape::{self, Mode}; +use rustc_lexer::unescape::{self, EscapeError, Mode}; use rustc_lexer::Cursor; use rustc_lexer::{Base, DocStyle, RawStrError}; use rustc_session::lint::builtin::{ @@ -670,7 +672,7 @@ impl<'a> StringReader<'a> { self.sess.emit_fatal(errors::TooManyHashes { span: self.mk_sp(start, self.pos), num }); } - fn cook_quoted( + fn cook_common( &self, kind: token::LitKind, mode: Mode, @@ -678,12 +680,13 @@ impl<'a> StringReader<'a> { end: BytePos, prefix_len: u32, postfix_len: u32, + unescape: fn(&str, Mode, &mut dyn FnMut(Range, Result<(), EscapeError>)), ) -> (token::LitKind, Symbol) { let mut has_fatal_err = false; let content_start = start + BytePos(prefix_len); let content_end = end - BytePos(postfix_len); let lit_content = self.str_from_to(content_start, content_end); - unescape::unescape_literal(lit_content, mode, &mut |range, result| { + unescape(lit_content, mode, &mut |range, result| { // Here we only check for errors. The actual unescaping is done later. if let Err(err) = result { let span_with_quotes = self.mk_sp(start, end); @@ -715,7 +718,7 @@ impl<'a> StringReader<'a> { } } - fn cook_c_string( + fn cook_quoted( &self, kind: token::LitKind, mode: Mode, @@ -724,40 +727,27 @@ impl<'a> StringReader<'a> { prefix_len: u32, postfix_len: u32, ) -> (token::LitKind, Symbol) { - let mut has_fatal_err = false; - let content_start = start + BytePos(prefix_len); - let content_end = end - BytePos(postfix_len); - let lit_content = self.str_from_to(content_start, content_end); - unescape::unescape_c_string(lit_content, mode, &mut |range, result| { - // Here we only check for errors. The actual unescaping is done later. - if let Err(err) = result { - let span_with_quotes = self.mk_sp(start, end); - let (start, end) = (range.start as u32, range.end as u32); - let lo = content_start + BytePos(start); - let hi = lo + BytePos(end - start); - let span = self.mk_sp(lo, hi); - if err.is_fatal() { - has_fatal_err = true; - } - emit_unescape_error( - &self.sess.span_diagnostic, - lit_content, - span_with_quotes, - span, - mode, - range, - err, - ); - } - }); + self.cook_common(kind, mode, start, end, prefix_len, postfix_len, |src, mode, callback| { + unescape::unescape_literal(src, mode, &mut |span, result| { + callback(span, result.map(drop)) + }) + }) + } - // We normally exclude the quotes for the symbol, but for errors we - // include it because it results in clearer error messages. - if !has_fatal_err { - (kind, Symbol::intern(lit_content)) - } else { - (token::Err, self.symbol_from_to(start, end)) - } + fn cook_c_string( + &self, + kind: token::LitKind, + mode: Mode, + start: BytePos, + end: BytePos, + prefix_len: u32, + postfix_len: u32, + ) -> (token::LitKind, Symbol) { + self.cook_common(kind, mode, start, end, prefix_len, postfix_len, |src, mode, callback| { + unescape::unescape_c_string(src, mode, &mut |span, result| { + callback(span, result.map(drop)) + }) + }) } } From d5bc581f5db6ba5198ccec45d938422778f56bff Mon Sep 17 00:00:00 2001 From: clubby789 Date: Sun, 30 Apr 2023 02:20:53 +0100 Subject: [PATCH 15/19] Migrate `mir_transform` to translatable diagnostics --- Cargo.lock | 3 + compiler/rustc_driver_impl/Cargo.toml | 8 +- compiler/rustc_driver_impl/src/lib.rs | 1 + .../rustc_errors/src/diagnostic_builder.rs | 8 + compiler/rustc_mir_transform/Cargo.toml | 2 + compiler/rustc_mir_transform/messages.ftl | 66 +++++ .../src/check_const_item_mutation.rs | 65 ++--- .../src/check_packed_ref.rs | 23 +- .../rustc_mir_transform/src/check_unsafety.rs | 68 ++--- .../src/const_prop_lint.rs | 76 +++--- compiler/rustc_mir_transform/src/errors.rs | 245 ++++++++++++++++++ .../src/ffi_unwind_calls.rs | 16 +- .../src/function_item_references.rs | 33 +-- compiler/rustc_mir_transform/src/generator.rs | 38 +-- compiler/rustc_mir_transform/src/lib.rs | 8 + .../src/lower_intrinsics.rs | 10 +- 16 files changed, 470 insertions(+), 200 deletions(-) create mode 100644 compiler/rustc_mir_transform/messages.ftl create mode 100644 compiler/rustc_mir_transform/src/errors.rs diff --git a/Cargo.lock b/Cargo.lock index f6ee8157b40ef..b6253f539bb6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3351,6 +3351,7 @@ dependencies = [ "rustc_middle", "rustc_mir_build", "rustc_mir_dataflow", + "rustc_mir_transform", "rustc_monomorphize", "rustc_parse", "rustc_passes", @@ -3859,8 +3860,10 @@ dependencies = [ "rustc_const_eval", "rustc_data_structures", "rustc_errors", + "rustc_fluent_macro", "rustc_hir", "rustc_index", + "rustc_macros", "rustc_middle", "rustc_mir_dataflow", "rustc_serialize", diff --git a/compiler/rustc_driver_impl/Cargo.toml b/compiler/rustc_driver_impl/Cargo.toml index cc4c5a0cacddf..d7d97fcc3e7b7 100644 --- a/compiler/rustc_driver_impl/Cargo.toml +++ b/compiler/rustc_driver_impl/Cargo.toml @@ -51,6 +51,7 @@ rustc_interface = { path = "../rustc_interface" } rustc_ast = { path = "../rustc_ast" } rustc_span = { path = "../rustc_span" } rustc_hir_analysis = { path = "../rustc_hir_analysis" } +rustc_mir_transform = { path = "../rustc_mir_transform" } [target.'cfg(unix)'.dependencies] libc = "0.2" @@ -64,5 +65,8 @@ features = [ [features] llvm = ['rustc_interface/llvm'] max_level_info = ['rustc_log/max_level_info'] -rustc_use_parallel_compiler = ['rustc_data_structures/rustc_use_parallel_compiler', 'rustc_interface/rustc_use_parallel_compiler', - 'rustc_middle/rustc_use_parallel_compiler'] +rustc_use_parallel_compiler = [ + 'rustc_data_structures/rustc_use_parallel_compiler', + 'rustc_interface/rustc_use_parallel_compiler', + 'rustc_middle/rustc_use_parallel_compiler' +] diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 5fac485de6417..c629c55264a12 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -99,6 +99,7 @@ pub static DEFAULT_LOCALE_RESOURCES: &[&str] = &[ rustc_middle::DEFAULT_LOCALE_RESOURCE, rustc_mir_build::DEFAULT_LOCALE_RESOURCE, rustc_mir_dataflow::DEFAULT_LOCALE_RESOURCE, + rustc_mir_transform::DEFAULT_LOCALE_RESOURCE, rustc_monomorphize::DEFAULT_LOCALE_RESOURCE, rustc_parse::DEFAULT_LOCALE_RESOURCE, rustc_passes::DEFAULT_LOCALE_RESOURCE, diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs index 3064d2bedbe12..c8e57aa39af7c 100644 --- a/compiler/rustc_errors/src/diagnostic_builder.rs +++ b/compiler/rustc_errors/src/diagnostic_builder.rs @@ -570,6 +570,14 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> { Some((diagnostic, handler)) } + /// Retrieves the [`Handler`] if available + pub fn handler(&self) -> Option<&Handler> { + match self.inner.state { + DiagnosticBuilderState::Emittable(handler) => Some(handler), + DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation => None, + } + } + /// Buffers the diagnostic for later emission, /// unless handler has disabled such buffering. pub fn buffer(self, buffered_diagnostics: &mut Vec) { diff --git a/compiler/rustc_mir_transform/Cargo.toml b/compiler/rustc_mir_transform/Cargo.toml index 962536669e0db..eca5f98a2c01c 100644 --- a/compiler/rustc_mir_transform/Cargo.toml +++ b/compiler/rustc_mir_transform/Cargo.toml @@ -24,6 +24,8 @@ rustc_session = { path = "../rustc_session" } rustc_target = { path = "../rustc_target" } rustc_trait_selection = { path = "../rustc_trait_selection" } rustc_span = { path = "../rustc_span" } +rustc_fluent_macro = { path = "../rustc_fluent_macro" } +rustc_macros = { path = "../rustc_macros" } [dev-dependencies] coverage_test_macros = { path = "src/coverage/test_macros" } diff --git a/compiler/rustc_mir_transform/messages.ftl b/compiler/rustc_mir_transform/messages.ftl new file mode 100644 index 0000000000000..8c85cb5f76d86 --- /dev/null +++ b/compiler/rustc_mir_transform/messages.ftl @@ -0,0 +1,66 @@ +mir_transform_const_modify = attempting to modify a `const` item + .note = each usage of a `const` item creates a new temporary; the original `const` item will not be modified + +mir_transform_const_mut_borrow = taking a mutable reference to a `const` item + .note = each usage of a `const` item creates a new temporary + .note2 = the mutable reference will refer to this temporary, not the original `const` item + .note3 = mutable reference created due to call to this method + +mir_transform_const_defined_here = `const` item defined here + +mir_transform_unaligned_packed_ref = reference to packed field is unaligned + .note = packed structs are only aligned by one byte, and many modern architectures penalize unaligned field accesses + .note_ub = creating a misaligned reference is undefined behavior (even if that reference is never dereferenced) + .help = copy the field contents to a local variable, or replace the reference with a raw pointer and use `read_unaligned`/`write_unaligned` (loads and stores via `*p` must be properly aligned even when using raw pointers) + +mir_transform_unused_unsafe = unnecessary `unsafe` block + .label = because it's nested under this `unsafe` block + +mir_transform_requires_unsafe = {$details} is unsafe and requires unsafe {$op_in_unsafe_fn_allowed -> + [true] function or block + *[false] block + } + .not_inherited = items do not inherit unsafety from separate enclosing items + +mir_transform_call_to_unsafe_label = call to unsafe function +mir_transform_call_to_unsafe_note = consult the function's documentation for information on how to avoid undefined behavior +mir_transform_use_of_asm_label = use of inline assembly +mir_transform_use_of_asm_note = inline assembly is entirely unchecked and can cause undefined behavior +mir_transform_initializing_valid_range_label = initializing type with `rustc_layout_scalar_valid_range` attr +mir_transform_initializing_valid_range_note = initializing a layout restricted type's field with a value outside the valid range is undefined behavior +mir_transform_const_ptr2int_label = cast of pointer to int +mir_transform_const_ptr2int_note = casting pointers to integers in constants +mir_transform_use_of_static_mut_label = use of mutable static +mir_transform_use_of_static_mut_note = mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior +mir_transform_use_of_extern_static_label = use of extern static +mir_transform_use_of_extern_static_note = extern statics are not controlled by the Rust type system: invalid data, aliasing violations or data races will cause undefined behavior +mir_transform_deref_ptr_label = dereference of raw pointer +mir_transform_deref_ptr_note = raw pointers may be null, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior +mir_transform_union_access_label = access to union field +mir_transform_union_access_note = the field may not be properly initialized: using uninitialized data will cause undefined behavior +mir_transform_mutation_layout_constrained_label = mutation of layout constrained field +mir_transform_mutation_layout_constrained_note = mutating layout constrained fields cannot statically be checked for valid values +mir_transform_mutation_layout_constrained_borrow_label = borrow of layout constrained field with interior mutability +mir_transform_mutation_layout_constrained_borrow_note = references to fields of layout constrained fields lose the constraints. Coupled with interior mutability, the field can be changed to invalid values +mir_transform_target_feature_call_label = call to function with `#[target_feature]` +mir_transform_target_feature_call_note = can only be called if the required target features are available + +mir_transform_unsafe_op_in_unsafe_fn = {$details} is unsafe and requires unsafe block (error E0133) + +mir_transform_arithmetic_overflow = this arithmetic operation will overflow +mir_transform_operation_will_panic = this operation will panic at runtime + +mir_transform_ffi_unwind_call = call to {$foreign -> + [true] foreign function + *[false] function pointer + } with FFI-unwind ABI + +mir_transform_fn_item_ref = taking a reference to a function item does not give a function pointer + .suggestion = cast `{$ident}` to obtain a function pointer + +mir_transform_must_not_suspend = {$pre}`{$def_path}`{$post} held across a suspend point, but should not be + .label = the value is held across this suspend point + .note = {$reason} + .help = consider using a block (`{"{ ... }"}`) to shrink the value's scope, ending before the suspend point + +mir_transform_simd_shuffle_last_const = last argument of `simd_shuffle` is required to be a `const` item diff --git a/compiler/rustc_mir_transform/src/check_const_item_mutation.rs b/compiler/rustc_mir_transform/src/check_const_item_mutation.rs index 57b24c9c552a5..b79150737d612 100644 --- a/compiler/rustc_mir_transform/src/check_const_item_mutation.rs +++ b/compiler/rustc_mir_transform/src/check_const_item_mutation.rs @@ -1,11 +1,12 @@ -use rustc_errors::{DiagnosticBuilder, DiagnosticMessage}; +use rustc_hir::HirId; use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::*; use rustc_middle::ty::TyCtxt; use rustc_session::lint::builtin::CONST_ITEM_MUTATION; use rustc_span::def_id::DefId; +use rustc_span::Span; -use crate::MirLint; +use crate::{errors, MirLint}; pub struct CheckConstItemMutation; @@ -58,16 +59,14 @@ impl<'tcx> ConstMutationChecker<'_, 'tcx> { } } - fn lint_const_item_usage( + /// If we should lint on this usage, return the [`HirId`], source [`Span`] + /// and [`Span`] of the const item to use in the lint. + fn should_lint_const_item_usage( &self, place: &Place<'tcx>, const_item: DefId, location: Location, - msg: impl Into, - decorate: impl for<'a, 'b> FnOnce( - &'a mut DiagnosticBuilder<'b, ()>, - ) -> &'a mut DiagnosticBuilder<'b, ()>, - ) { + ) -> Option<(HirId, Span, Span)> { // Don't lint on borrowing/assigning when a dereference is involved. // If we 'leave' the temporary via a dereference, we must // be modifying something else @@ -83,16 +82,9 @@ impl<'tcx> ConstMutationChecker<'_, 'tcx> { .assert_crate_local() .lint_root; - self.tcx.struct_span_lint_hir( - CONST_ITEM_MUTATION, - lint_root, - source_info.span, - msg, - |lint| { - decorate(lint) - .span_note(self.tcx.def_span(const_item), "`const` item defined here") - }, - ); + Some((lint_root, source_info.span, self.tcx.def_span(const_item))) + } else { + None } } } @@ -104,10 +96,14 @@ impl<'tcx> Visitor<'tcx> for ConstMutationChecker<'_, 'tcx> { // Assigning directly to a constant (e.g. `FOO = true;`) is a hard error, // so emitting a lint would be redundant. if !lhs.projection.is_empty() { - if let Some(def_id) = self.is_const_item_without_destructor(lhs.local) { - self.lint_const_item_usage(&lhs, def_id, loc, "attempting to modify a `const` item",|lint| { - lint.note("each usage of a `const` item creates a new temporary; the original `const` item will not be modified") - }) + if let Some(def_id) = self.is_const_item_without_destructor(lhs.local) + && let Some((lint_root, span, item)) = self.should_lint_const_item_usage(&lhs, def_id, loc) { + self.tcx.emit_spanned_lint( + CONST_ITEM_MUTATION, + lint_root, + span, + errors::ConstMutate::Modify { konst: item } + ); } } // We are looking for MIR of the form: @@ -143,17 +139,22 @@ impl<'tcx> Visitor<'tcx> for ConstMutationChecker<'_, 'tcx> { }); let lint_loc = if method_did.is_some() { self.body.terminator_loc(loc.block) } else { loc }; - self.lint_const_item_usage(place, def_id, lint_loc, "taking a mutable reference to a `const` item", |lint| { - lint - .note("each usage of a `const` item creates a new temporary") - .note("the mutable reference will refer to this temporary, not the original `const` item"); - - if let Some((method_did, _substs)) = method_did { - lint.span_note(self.tcx.def_span(method_did), "mutable reference created due to call to this method"); - } - lint - }); + let method_call = if let Some((method_did, _)) = method_did { + Some(self.tcx.def_span(method_did)) + } else { + None + }; + if let Some((lint_root, span, item)) = + self.should_lint_const_item_usage(place, def_id, lint_loc) + { + self.tcx.emit_spanned_lint( + CONST_ITEM_MUTATION, + lint_root, + span, + errors::ConstMutate::MutBorrow { method_call, konst: item }, + ); + } } } self.super_rvalue(rvalue, loc); diff --git a/compiler/rustc_mir_transform/src/check_packed_ref.rs b/compiler/rustc_mir_transform/src/check_packed_ref.rs index b9bc89fcf8fa4..2e6cf603d5968 100644 --- a/compiler/rustc_mir_transform/src/check_packed_ref.rs +++ b/compiler/rustc_mir_transform/src/check_packed_ref.rs @@ -1,10 +1,9 @@ -use rustc_errors::struct_span_err; use rustc_middle::mir::visit::{PlaceContext, Visitor}; use rustc_middle::mir::*; use rustc_middle::ty::{self, TyCtxt}; -use crate::util; use crate::MirLint; +use crate::{errors, util}; pub struct CheckPackedRef; @@ -49,25 +48,7 @@ impl<'tcx> Visitor<'tcx> for PackedRefChecker<'_, 'tcx> { // shouldn't do. span_bug!(self.source_info.span, "builtin derive created an unaligned reference"); } else { - struct_span_err!( - self.tcx.sess, - self.source_info.span, - E0793, - "reference to packed field is unaligned" - ) - .note( - "packed structs are only aligned by one byte, and many modern architectures \ - penalize unaligned field accesses" - ) - .note( - "creating a misaligned reference is undefined behavior (even if that \ - reference is never dereferenced)", - ).help( - "copy the field contents to a local variable, or replace the \ - reference with a raw pointer and use `read_unaligned`/`write_unaligned` \ - (loads and stores via `*p` must be properly aligned even when using raw pointers)" - ) - .emit(); + self.tcx.sess.emit_err(errors::UnalignedPackedRef { span: self.source_info.span }); } } } diff --git a/compiler/rustc_mir_transform/src/check_unsafety.rs b/compiler/rustc_mir_transform/src/check_unsafety.rs index ce6d865a7dcef..bdb4f20da1059 100644 --- a/compiler/rustc_mir_transform/src/check_unsafety.rs +++ b/compiler/rustc_mir_transform/src/check_unsafety.rs @@ -1,5 +1,4 @@ use rustc_data_structures::unord::{UnordItems, UnordSet}; -use rustc_errors::struct_span_err; use rustc_hir as hir; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LocalDefId}; @@ -15,6 +14,8 @@ use rustc_session::lint::Level; use std::ops::Bound; +use crate::errors; + pub struct UnsafetyChecker<'a, 'tcx> { body: &'a Body<'tcx>, body_did: LocalDefId, @@ -509,21 +510,12 @@ fn unsafety_check_result(tcx: TyCtxt<'_>, def: LocalDefId) -> &UnsafetyCheckResu fn report_unused_unsafe(tcx: TyCtxt<'_>, kind: UnusedUnsafe, id: HirId) { let span = tcx.sess.source_map().guess_head_span(tcx.hir().span(id)); - let msg = "unnecessary `unsafe` block"; - tcx.struct_span_lint_hir(UNUSED_UNSAFE, id, span, msg, |lint| { - lint.span_label(span, msg); - match kind { - UnusedUnsafe::Unused => {} - UnusedUnsafe::InUnsafeBlock(id) => { - lint.span_label( - tcx.sess.source_map().guess_head_span(tcx.hir().span(id)), - "because it's nested under this `unsafe` block", - ); - } - } - - lint - }); + let nested_parent = if let UnusedUnsafe::InUnsafeBlock(id) = kind { + Some(tcx.sess.source_map().guess_head_span(tcx.hir().span(id))) + } else { + None + }; + tcx.emit_spanned_lint(UNUSED_UNSAFE, id, span, errors::UnusedUnsafe { span, nested_parent }); } pub fn check_unsafety(tcx: TyCtxt<'_>, def_id: LocalDefId) { @@ -537,26 +529,11 @@ pub fn check_unsafety(tcx: TyCtxt<'_>, def_id: LocalDefId) { let UnsafetyCheckResult { violations, unused_unsafes, .. } = tcx.unsafety_check_result(def_id); for &UnsafetyViolation { source_info, lint_root, kind, details } in violations.iter() { - let (description, note) = details.description_and_note(); + let details = errors::RequiresUnsafeDetail { violation: details, span: source_info.span }; match kind { UnsafetyViolationKind::General => { - // once - let unsafe_fn_msg = if unsafe_op_in_unsafe_fn_allowed(tcx, lint_root) { - " function or" - } else { - "" - }; - - let mut err = struct_span_err!( - tcx.sess, - source_info.span, - E0133, - "{} is unsafe and requires unsafe{} block", - description, - unsafe_fn_msg, - ); - err.span_label(source_info.span, description).note(note); + let op_in_unsafe_fn_allowed = unsafe_op_in_unsafe_fn_allowed(tcx, lint_root); let note_non_inherited = tcx.hir().parent_iter(lint_root).find(|(id, node)| { if let Node::Expr(block) = node && let ExprKind::Block(block, _) = block.kind @@ -572,22 +549,23 @@ pub fn check_unsafety(tcx: TyCtxt<'_>, def_id: LocalDefId) { false } }); - if let Some((id, _)) = note_non_inherited { - let span = tcx.hir().span(id); - err.span_label( - tcx.sess.source_map().guess_head_span(span), - "items do not inherit unsafety from separate enclosing items", - ); - } - - err.emit(); + let enclosing = if let Some((id, _)) = note_non_inherited { + Some(tcx.sess.source_map().guess_head_span(tcx.hir().span(id))) + } else { + None + }; + tcx.sess.emit_err(errors::RequiresUnsafe { + span: source_info.span, + enclosing, + details, + op_in_unsafe_fn_allowed, + }); } - UnsafetyViolationKind::UnsafeFn => tcx.struct_span_lint_hir( + UnsafetyViolationKind::UnsafeFn => tcx.emit_spanned_lint( UNSAFE_OP_IN_UNSAFE_FN, lint_root, source_info.span, - format!("{} is unsafe and requires unsafe block (error E0133)", description,), - |lint| lint.span_label(source_info.span, description).note(note), + errors::UnsafeOpInUnsafeFn { details }, ), } } diff --git a/compiler/rustc_mir_transform/src/const_prop_lint.rs b/compiler/rustc_mir_transform/src/const_prop_lint.rs index a4049d08d7b82..adb09c509d287 100644 --- a/compiler/rustc_mir_transform/src/const_prop_lint.rs +++ b/compiler/rustc_mir_transform/src/const_prop_lint.rs @@ -1,6 +1,8 @@ //! Propagates constants for early reporting of statically known //! assertion failures +use std::fmt::Debug; + use either::Left; use rustc_const_eval::interpret::Immediate; @@ -17,7 +19,6 @@ use rustc_middle::ty::InternalSubsts; use rustc_middle::ty::{ self, ConstInt, Instance, ParamEnv, ScalarInt, Ty, TyCtxt, TypeVisitableExt, }; -use rustc_session::lint; use rustc_span::Span; use rustc_target::abi::{HasDataLayout, Size, TargetDataLayout}; use rustc_trait_selection::traits; @@ -25,6 +26,7 @@ use rustc_trait_selection::traits; use crate::const_prop::CanConstProp; use crate::const_prop::ConstPropMachine; use crate::const_prop::ConstPropMode; +use crate::errors::AssertLint; use crate::MirLint; /// The maximum number of bytes that we'll allocate space for a local or the return value. @@ -311,18 +313,9 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { } } - fn report_assert_as_lint( - &self, - lint: &'static lint::Lint, - location: Location, - message: &'static str, - panic: AssertKind, - ) { - let source_info = self.body().source_info(location); + fn report_assert_as_lint(&self, source_info: &SourceInfo, lint: AssertLint) { if let Some(lint_root) = self.lint_root(*source_info) { - self.tcx.struct_span_lint_hir(lint, lint_root, source_info.span, message, |lint| { - lint.span_label(source_info.span, format!("{:?}", panic)) - }); + self.tcx.emit_spanned_lint(lint.lint(), lint_root, source_info.span, lint); } } @@ -335,11 +328,13 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { // `AssertKind` only has an `OverflowNeg` variant, so make sure that is // appropriate to use. assert_eq!(op, UnOp::Neg, "Neg is the only UnOp that can overflow"); + let source_info = self.body().source_info(location); self.report_assert_as_lint( - lint::builtin::ARITHMETIC_OVERFLOW, - location, - "this arithmetic operation will overflow", - AssertKind::OverflowNeg(val.to_const_int()), + source_info, + AssertLint::ArithmeticOverflow( + source_info.span, + AssertKind::OverflowNeg(val.to_const_int()), + ), ); return None; } @@ -370,23 +365,23 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { let r_bits = r.to_scalar().to_bits(right_size).ok(); if r_bits.map_or(false, |b| b >= left_size.bits() as u128) { debug!("check_binary_op: reporting assert for {:?}", location); + let source_info = self.body().source_info(location); + let panic = AssertKind::Overflow( + op, + match l { + Some(l) => l.to_const_int(), + // Invent a dummy value, the diagnostic ignores it anyway + None => ConstInt::new( + ScalarInt::try_from_uint(1_u8, left_size).unwrap(), + left_ty.is_signed(), + left_ty.is_ptr_sized_integral(), + ), + }, + r.to_const_int(), + ); self.report_assert_as_lint( - lint::builtin::ARITHMETIC_OVERFLOW, - location, - "this arithmetic operation will overflow", - AssertKind::Overflow( - op, - match l { - Some(l) => l.to_const_int(), - // Invent a dummy value, the diagnostic ignores it anyway - None => ConstInt::new( - ScalarInt::try_from_uint(1_u8, left_size).unwrap(), - left_ty.is_signed(), - left_ty.is_ptr_sized_integral(), - ), - }, - r.to_const_int(), - ), + source_info, + AssertLint::ArithmeticOverflow(source_info.span, panic), ); return None; } @@ -398,11 +393,13 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { let (_res, overflow, _ty) = this.ecx.overflowing_binary_op(op, &l, &r)?; Ok(overflow) })? { + let source_info = self.body().source_info(location); self.report_assert_as_lint( - lint::builtin::ARITHMETIC_OVERFLOW, - location, - "this arithmetic operation will overflow", - AssertKind::Overflow(op, l.to_const_int(), r.to_const_int()), + source_info, + AssertLint::ArithmeticOverflow( + source_info.span, + AssertKind::Overflow(op, l.to_const_int(), r.to_const_int()), + ), ); return None; } @@ -543,11 +540,10 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { // Need proper const propagator for these. _ => return None, }; + let source_info = self.body().source_info(location); self.report_assert_as_lint( - lint::builtin::UNCONDITIONAL_PANIC, - location, - "this operation will panic at runtime", - msg, + source_info, + AssertLint::UnconditionalPanic(source_info.span, msg), ); } diff --git a/compiler/rustc_mir_transform/src/errors.rs b/compiler/rustc_mir_transform/src/errors.rs new file mode 100644 index 0000000000000..602e40d513104 --- /dev/null +++ b/compiler/rustc_mir_transform/src/errors.rs @@ -0,0 +1,245 @@ +use rustc_errors::{ + DecorateLint, DiagnosticBuilder, DiagnosticMessage, EmissionGuarantee, Handler, IntoDiagnostic, +}; +use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic}; +use rustc_middle::mir::{AssertKind, UnsafetyViolationDetails}; +use rustc_session::lint::{self, Lint}; +use rustc_span::Span; + +#[derive(LintDiagnostic)] +pub(crate) enum ConstMutate { + #[diag(mir_transform_const_modify)] + #[note] + Modify { + #[note(mir_transform_const_defined_here)] + konst: Span, + }, + #[diag(mir_transform_const_mut_borrow)] + #[note] + #[note(mir_transform_note2)] + MutBorrow { + #[note(mir_transform_note3)] + method_call: Option, + #[note(mir_transform_const_defined_here)] + konst: Span, + }, +} + +#[derive(Diagnostic)] +#[diag(mir_transform_unaligned_packed_ref, code = "E0793")] +#[note] +#[note(mir_transform_note_ub)] +#[help] +pub(crate) struct UnalignedPackedRef { + #[primary_span] + pub span: Span, +} + +#[derive(LintDiagnostic)] +#[diag(mir_transform_unused_unsafe)] +pub(crate) struct UnusedUnsafe { + #[label(mir_transform_unused_unsafe)] + pub span: Span, + #[label] + pub nested_parent: Option, +} + +pub(crate) struct RequiresUnsafe { + pub span: Span, + pub details: RequiresUnsafeDetail, + pub enclosing: Option, + pub op_in_unsafe_fn_allowed: bool, +} + +// The primary message for this diagnostic should be '{$label} is unsafe and...', +// so we need to eagerly translate the label here, which isn't supported by the derive API +// We could also exhaustively list out the primary messages for all unsafe violations, +// but this would result in a lot of duplication. +impl<'sess, G: EmissionGuarantee> IntoDiagnostic<'sess, G> for RequiresUnsafe { + #[track_caller] + fn into_diagnostic(self, handler: &'sess Handler) -> DiagnosticBuilder<'sess, G> { + let mut diag = + handler.struct_diagnostic(crate::fluent_generated::mir_transform_requires_unsafe); + diag.code(rustc_errors::DiagnosticId::Error("E0133".to_string())); + diag.set_span(self.span); + diag.span_label(self.span, self.details.label()); + diag.note(self.details.note()); + let desc = handler.eagerly_translate_to_string(self.details.label(), [].into_iter()); + diag.set_arg("details", desc); + diag.set_arg("op_in_unsafe_fn_allowed", self.op_in_unsafe_fn_allowed); + if let Some(sp) = self.enclosing { + diag.span_label(sp, crate::fluent_generated::mir_transform_not_inherited); + } + diag + } +} + +#[derive(Copy, Clone)] +pub(crate) struct RequiresUnsafeDetail { + pub span: Span, + pub violation: UnsafetyViolationDetails, +} + +impl RequiresUnsafeDetail { + fn note(self) -> DiagnosticMessage { + use UnsafetyViolationDetails::*; + match self.violation { + CallToUnsafeFunction => crate::fluent_generated::mir_transform_call_to_unsafe_note, + UseOfInlineAssembly => crate::fluent_generated::mir_transform_use_of_asm_note, + InitializingTypeWith => { + crate::fluent_generated::mir_transform_initializing_valid_range_note + } + CastOfPointerToInt => crate::fluent_generated::mir_transform_const_ptr2int_note, + UseOfMutableStatic => crate::fluent_generated::mir_transform_use_of_static_mut_note, + UseOfExternStatic => crate::fluent_generated::mir_transform_use_of_extern_static_note, + DerefOfRawPointer => crate::fluent_generated::mir_transform_deref_ptr_note, + AccessToUnionField => crate::fluent_generated::mir_transform_union_access_note, + MutationOfLayoutConstrainedField => { + crate::fluent_generated::mir_transform_mutation_layout_constrained_note + } + BorrowOfLayoutConstrainedField => { + crate::fluent_generated::mir_transform_mutation_layout_constrained_borrow_note + } + CallToFunctionWith => crate::fluent_generated::mir_transform_target_feature_call_note, + } + } + + fn label(self) -> DiagnosticMessage { + use UnsafetyViolationDetails::*; + match self.violation { + CallToUnsafeFunction => crate::fluent_generated::mir_transform_call_to_unsafe_label, + UseOfInlineAssembly => crate::fluent_generated::mir_transform_use_of_asm_label, + InitializingTypeWith => { + crate::fluent_generated::mir_transform_initializing_valid_range_label + } + CastOfPointerToInt => crate::fluent_generated::mir_transform_const_ptr2int_label, + UseOfMutableStatic => crate::fluent_generated::mir_transform_use_of_static_mut_label, + UseOfExternStatic => crate::fluent_generated::mir_transform_use_of_extern_static_label, + DerefOfRawPointer => crate::fluent_generated::mir_transform_deref_ptr_label, + AccessToUnionField => crate::fluent_generated::mir_transform_union_access_label, + MutationOfLayoutConstrainedField => { + crate::fluent_generated::mir_transform_mutation_layout_constrained_label + } + BorrowOfLayoutConstrainedField => { + crate::fluent_generated::mir_transform_mutation_layout_constrained_borrow_label + } + CallToFunctionWith => crate::fluent_generated::mir_transform_target_feature_call_label, + } + } +} + +pub(crate) struct UnsafeOpInUnsafeFn { + pub details: RequiresUnsafeDetail, +} + +impl<'a> DecorateLint<'a, ()> for UnsafeOpInUnsafeFn { + #[track_caller] + fn decorate_lint<'b>( + self, + diag: &'b mut DiagnosticBuilder<'a, ()>, + ) -> &'b mut DiagnosticBuilder<'a, ()> { + let desc = diag + .handler() + .expect("lint should not yet be emitted") + .eagerly_translate_to_string(self.details.label(), [].into_iter()); + diag.set_arg("details", desc); + diag.span_label(self.details.span, self.details.label()); + diag.note(self.details.note()); + diag + } + + fn msg(&self) -> DiagnosticMessage { + crate::fluent_generated::mir_transform_unsafe_op_in_unsafe_fn + } +} + +pub(crate) enum AssertLint

{ + ArithmeticOverflow(Span, AssertKind

), + UnconditionalPanic(Span, AssertKind

), +} + +impl<'a, P: std::fmt::Debug> DecorateLint<'a, ()> for AssertLint

{ + fn decorate_lint<'b>( + self, + diag: &'b mut DiagnosticBuilder<'a, ()>, + ) -> &'b mut DiagnosticBuilder<'a, ()> { + diag.span_label(self.span(), format!("{:?}", self.panic())); + diag + } + + fn msg(&self) -> DiagnosticMessage { + match self { + AssertLint::ArithmeticOverflow(..) => { + crate::fluent_generated::mir_transform_arithmetic_overflow + } + AssertLint::UnconditionalPanic(..) => { + crate::fluent_generated::mir_transform_operation_will_panic + } + } + } +} + +impl

AssertLint

{ + pub fn lint(&self) -> &'static Lint { + match self { + AssertLint::ArithmeticOverflow(..) => lint::builtin::ARITHMETIC_OVERFLOW, + AssertLint::UnconditionalPanic(..) => lint::builtin::UNCONDITIONAL_PANIC, + } + } + pub fn span(&self) -> Span { + match self { + AssertLint::ArithmeticOverflow(sp, _) | AssertLint::UnconditionalPanic(sp, _) => *sp, + } + } + pub fn panic(&self) -> &AssertKind

{ + match self { + AssertLint::ArithmeticOverflow(_, p) | AssertLint::UnconditionalPanic(_, p) => p, + } + } +} + +#[derive(LintDiagnostic)] +#[diag(mir_transform_ffi_unwind_call)] +pub(crate) struct FfiUnwindCall { + #[label(mir_transform_ffi_unwind_call)] + pub span: Span, + pub foreign: bool, +} + +#[derive(LintDiagnostic)] +#[diag(mir_transform_fn_item_ref)] +pub(crate) struct FnItemRef { + #[suggestion(code = "{sugg}", applicability = "unspecified")] + pub span: Span, + pub sugg: String, + pub ident: String, +} + +#[derive(LintDiagnostic)] +#[diag(mir_transform_must_not_suspend)] +pub(crate) struct MustNotSupend<'a> { + #[label] + pub yield_sp: Span, + #[subdiagnostic] + pub reason: Option, + #[help] + pub src_sp: Span, + pub pre: &'a str, + pub def_path: String, + pub post: &'a str, +} + +#[derive(Subdiagnostic)] +#[note(mir_transform_note)] +pub(crate) struct MustNotSuspendReason { + #[primary_span] + pub span: Span, + pub reason: String, +} + +#[derive(Diagnostic)] +#[diag(mir_transform_simd_shuffle_last_const)] +pub(crate) struct SimdShuffleLastConst { + #[primary_span] + pub span: Span, +} diff --git a/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs b/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs index db68adc8bc979..ac1de989a7204 100644 --- a/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs +++ b/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs @@ -8,6 +8,8 @@ use rustc_session::lint::builtin::FFI_UNWIND_CALLS; use rustc_target::spec::abi::Abi; use rustc_target::spec::PanicStrategy; +use crate::errors; + fn abi_can_unwind(abi: Abi) -> bool { use Abi::*; match abi { @@ -107,13 +109,13 @@ fn has_ffi_unwind_calls(tcx: TyCtxt<'_>, local_def_id: LocalDefId) -> bool { .lint_root; let span = terminator.source_info.span; - let msg = match fn_def_id { - Some(_) => "call to foreign function with FFI-unwind ABI", - None => "call to function pointer with FFI-unwind ABI", - }; - tcx.struct_span_lint_hir(FFI_UNWIND_CALLS, lint_root, span, msg, |lint| { - lint.span_label(span, msg) - }); + let foreign = fn_def_id.is_some(); + tcx.emit_spanned_lint( + FFI_UNWIND_CALLS, + lint_root, + span, + errors::FfiUnwindCall { span, foreign }, + ); tainted = true; } diff --git a/compiler/rustc_mir_transform/src/function_item_references.rs b/compiler/rustc_mir_transform/src/function_item_references.rs index f26c6de9648c0..5989dbebf2db9 100644 --- a/compiler/rustc_mir_transform/src/function_item_references.rs +++ b/compiler/rustc_mir_transform/src/function_item_references.rs @@ -1,5 +1,4 @@ use itertools::Itertools; -use rustc_errors::Applicability; use rustc_hir::def_id::DefId; use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::*; @@ -8,7 +7,7 @@ use rustc_session::lint::builtin::FUNCTION_ITEM_REFERENCES; use rustc_span::{symbol::sym, Span}; use rustc_target::spec::abi::Abi; -use crate::MirLint; +use crate::{errors, MirLint}; pub struct FunctionItemReferences; @@ -174,27 +173,21 @@ impl<'tcx> FunctionItemRefChecker<'_, 'tcx> { let num_args = fn_sig.inputs().map_bound(|inputs| inputs.len()).skip_binder(); let variadic = if fn_sig.c_variadic() { ", ..." } else { "" }; let ret = if fn_sig.output().skip_binder().is_unit() { "" } else { " -> _" }; - self.tcx.struct_span_lint_hir( + let sugg = format!( + "{} as {}{}fn({}{}){}", + if params.is_empty() { ident.clone() } else { format!("{}::<{}>", ident, params) }, + unsafety, + abi, + vec!["_"; num_args].join(", "), + variadic, + ret, + ); + + self.tcx.emit_spanned_lint( FUNCTION_ITEM_REFERENCES, lint_root, span, - "taking a reference to a function item does not give a function pointer", - |lint| { - lint.span_suggestion( - span, - format!("cast `{}` to obtain a function pointer", ident), - format!( - "{} as {}{}fn({}{}){}", - if params.is_empty() { ident } else { format!("{}::<{}>", ident, params) }, - unsafety, - abi, - vec!["_"; num_args].join(", "), - variadic, - ret, - ), - Applicability::Unspecified, - ) - }, + errors::FnItemRef { span, sugg, ident }, ); } } diff --git a/compiler/rustc_mir_transform/src/generator.rs b/compiler/rustc_mir_transform/src/generator.rs index e44dd084b2de5..719a4f349cbf7 100644 --- a/compiler/rustc_mir_transform/src/generator.rs +++ b/compiler/rustc_mir_transform/src/generator.rs @@ -51,6 +51,7 @@ //! Otherwise it drops all the values in scope at the last suspension point. use crate::deref_separator::deref_finder; +use crate::errors; use crate::simplify; use crate::MirPass; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; @@ -1892,36 +1893,21 @@ fn check_must_not_suspend_def( data: SuspendCheckData<'_>, ) -> bool { if let Some(attr) = tcx.get_attr(def_id, sym::must_not_suspend) { - let msg = rustc_errors::DelayDm(|| { - format!( - "{}`{}`{} held across a suspend point, but should not be", - data.descr_pre, - tcx.def_path_str(def_id), - data.descr_post, - ) + let reason = attr.value_str().map(|s| errors::MustNotSuspendReason { + span: data.source_span, + reason: s.as_str().to_string(), }); - tcx.struct_span_lint_hir( + tcx.emit_spanned_lint( rustc_session::lint::builtin::MUST_NOT_SUSPEND, hir_id, data.source_span, - msg, - |lint| { - // add span pointing to the offending yield/await - lint.span_label(data.yield_span, "the value is held across this suspend point"); - - // Add optional reason note - if let Some(note) = attr.value_str() { - // FIXME(guswynn): consider formatting this better - lint.span_note(data.source_span, note.as_str()); - } - - // Add some quick suggestions on what to do - // FIXME: can `drop` work as a suggestion here as well? - lint.span_help( - data.source_span, - "consider using a block (`{ ... }`) \ - to shrink the value's scope, ending before the suspend point", - ) + errors::MustNotSupend { + yield_sp: data.yield_span, + reason, + src_sp: data.source_span, + pre: data.descr_pre, + def_path: tcx.def_path_str(def_id), + post: data.descr_post, }, ); diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index 25d7db0ee605a..bcaebad543b25 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -1,4 +1,6 @@ #![allow(rustc::potential_query_instability)] +#![deny(rustc::untranslatable_diagnostic)] +#![deny(rustc::diagnostic_outside_of_impl)] #![feature(box_patterns)] #![feature(drain_filter)] #![feature(let_chains)] @@ -69,6 +71,7 @@ pub mod dump_mir; mod early_otherwise_branch; mod elaborate_box_derefs; mod elaborate_drops; +mod errors; mod ffi_unwind_calls; mod function_item_references; mod generator; @@ -105,6 +108,11 @@ use rustc_const_eval::transform::promote_consts; use rustc_const_eval::transform::validate; use rustc_mir_dataflow::rustc_peek; +use rustc_errors::{DiagnosticMessage, SubdiagnosticMessage}; +use rustc_fluent_macro::fluent_messages; + +fluent_messages! { "../messages.ftl" } + pub fn provide(providers: &mut Providers) { check_unsafety::provide(providers); coverage::query::provide(providers); diff --git a/compiler/rustc_mir_transform/src/lower_intrinsics.rs b/compiler/rustc_mir_transform/src/lower_intrinsics.rs index 62b727674c5d1..e95f60718ef9f 100644 --- a/compiler/rustc_mir_transform/src/lower_intrinsics.rs +++ b/compiler/rustc_mir_transform/src/lower_intrinsics.rs @@ -1,6 +1,6 @@ //! Lowers intrinsic calls -use crate::MirPass; +use crate::{errors, MirPass}; use rustc_middle::mir::*; use rustc_middle::ty::subst::SubstsRef; use rustc_middle::ty::{self, Ty, TyCtxt}; @@ -287,11 +287,7 @@ fn resolve_rust_intrinsic<'tcx>( } fn validate_simd_shuffle<'tcx>(tcx: TyCtxt<'tcx>, args: &[Operand<'tcx>], span: Span) { - match &args[2] { - Operand::Constant(_) => {} // all good - _ => { - let msg = "last argument of `simd_shuffle` is required to be a `const` item"; - tcx.sess.span_err(span, msg); - } + if !matches!(args[2], Operand::Constant(_)) { + tcx.sess.emit_err(errors::SimdShuffleLastConst { span }); } } From 6bb1f792db5536bcb4adbc7bcefa10cb5a827aed Mon Sep 17 00:00:00 2001 From: lcnr Date: Wed, 3 May 2023 15:47:36 +0200 Subject: [PATCH 16/19] cleanup nll generalizer --- .../rustc_infer/src/infer/nll_relate/mod.rs | 75 ++----------------- 1 file changed, 7 insertions(+), 68 deletions(-) diff --git a/compiler/rustc_infer/src/infer/nll_relate/mod.rs b/compiler/rustc_infer/src/infer/nll_relate/mod.rs index 9f7b26b87f459..88a0a81e276d2 100644 --- a/compiler/rustc_infer/src/infer/nll_relate/mod.rs +++ b/compiler/rustc_infer/src/infer/nll_relate/mod.rs @@ -30,11 +30,10 @@ use rustc_middle::traits::ObligationCause; use rustc_middle::ty::error::TypeError; use rustc_middle::ty::fold::FnMutDelegate; use rustc_middle::ty::relate::{self, Relate, RelateResult, TypeRelation}; -use rustc_middle::ty::visit::{TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor}; +use rustc_middle::ty::visit::TypeVisitableExt; use rustc_middle::ty::{self, InferConst, Ty, TyCtxt}; use rustc_span::{Span, Symbol}; use std::fmt::Debug; -use std::ops::ControlFlow; use super::combine::ObligationEmittingRelation; @@ -115,11 +114,6 @@ pub trait TypeRelatingDelegate<'tcx> { fn forbid_inference_vars() -> bool; } -#[derive(Clone, Debug, Default)] -struct BoundRegionScope<'tcx> { - map: FxHashMap>, -} - #[derive(Copy, Clone)] struct UniversallyQuantified(bool); @@ -230,10 +224,13 @@ where ) -> RelateResult<'tcx, T> { let universe = self.infcx.probe_ty_var(for_vid).unwrap_err(); + if value.has_escaping_bound_vars() { + bug!("trying to instantiate {for_vid:?} with escaping bound vars: {value:?}"); + } + let mut generalizer = TypeGeneralizer { infcx: self.infcx, delegate: &mut self.delegate, - first_free_index: ty::INNERMOST, ambient_variance: self.ambient_variance, for_vid_sub_root: self.infcx.inner.borrow_mut().type_variables().sub_root_var(for_vid), universe, @@ -488,13 +485,7 @@ where } if a == b { - // Subtle: if a or b has a bound variable that we are lazily - // substituting, then even if a == b, it could be that the values we - // will substitute for those bound variables are *not* the same, and - // hence returning `Ok(a)` is incorrect. - if !a.has_escaping_bound_vars() && !b.has_escaping_bound_vars() { - return Ok(a); - } + return Ok(a); } match (a.kind(), b.kind()) { @@ -726,47 +717,6 @@ where } } -/// When we encounter a binder like `for<..> fn(..)`, we actually have -/// to walk the `fn` value to find all the values bound by the `for` -/// (these are not explicitly present in the ty representation right -/// now). This visitor handles that: it descends the type, tracking -/// binder depth, and finds late-bound regions targeting the -/// `for<..`>. For each of those, it creates an entry in -/// `bound_region_scope`. -struct ScopeInstantiator<'me, 'tcx> { - next_region: &'me mut dyn FnMut(ty::BoundRegion) -> ty::Region<'tcx>, - // The debruijn index of the scope we are instantiating. - target_index: ty::DebruijnIndex, - bound_region_scope: &'me mut BoundRegionScope<'tcx>, -} - -impl<'me, 'tcx> TypeVisitor> for ScopeInstantiator<'me, 'tcx> { - fn visit_binder>>( - &mut self, - t: &ty::Binder<'tcx, T>, - ) -> ControlFlow { - self.target_index.shift_in(1); - t.super_visit_with(self); - self.target_index.shift_out(1); - - ControlFlow::Continue(()) - } - - fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow { - let ScopeInstantiator { bound_region_scope, next_region, .. } = self; - - match *r { - ty::ReLateBound(debruijn, br) if debruijn == self.target_index => { - bound_region_scope.map.entry(br).or_insert_with(|| next_region(br)); - } - - _ => {} - } - - ControlFlow::Continue(()) - } -} - /// The "type generalizer" is used when handling inference variables. /// /// The basic strategy for handling a constraint like `?A <: B` is to @@ -780,11 +730,6 @@ impl<'me, 'tcx> TypeVisitor> for ScopeInstantiator<'me, 'tcx> { /// value of `A`. Finally, we relate `&'0 u32 <: &'x u32`, which /// establishes `'0: 'x` as a constraint. /// -/// As a side-effect of this generalization procedure, we also replace -/// all the bound regions that we have traversed with concrete values, -/// so that the resulting generalized type is independent from the -/// scopes. -/// /// [blog post]: https://is.gd/0hKvIr struct TypeGeneralizer<'me, 'tcx, D> where @@ -798,8 +743,6 @@ where /// some other type. What will be the variance at this point? ambient_variance: ty::Variance, - first_free_index: ty::DebruijnIndex, - /// The vid of the type variable that is in the process of being /// instantiated. If we find this within the value we are folding, /// that means we would have created a cyclic value. @@ -939,7 +882,7 @@ where ) -> RelateResult<'tcx, ty::Region<'tcx>> { debug!("TypeGeneralizer::regions(a={:?})", a); - if let ty::ReLateBound(debruijn, _) = *a && debruijn < self.first_free_index { + if let ty::ReLateBound(..) = *a { return Ok(a); } @@ -958,7 +901,6 @@ where // FIXME(#54105) -- if the ambient variance is bivariant, // though, we may however need to check well-formedness or // risk a problem like #41677 again. - let replacement_region_vid = self.delegate.generalize_existential(self.universe); Ok(replacement_region_vid) @@ -1002,10 +944,7 @@ where T: Relate<'tcx>, { debug!("TypeGeneralizer::binders(a={:?})", a); - - self.first_free_index.shift_in(1); let result = self.relate(a.skip_binder(), a.skip_binder())?; - self.first_free_index.shift_out(1); Ok(a.rebind(result)) } } From 0c5fe37786425edf53db60f20fe68f86dcb0b481 Mon Sep 17 00:00:00 2001 From: lcnr Date: Wed, 3 May 2023 20:58:49 +0200 Subject: [PATCH 17/19] remove `inside_canonicalization_ctxt` flag we never reach the code checking for this flag while the flag is enabled, so it does not change the behavior of the code. --- compiler/rustc_borrowck/src/lib.rs | 8 ++--- .../src/type_check/relate_tys.rs | 13 +++++--- compiler/rustc_infer/src/infer/at.rs | 3 -- .../src/infer/canonical/canonicalizer.rs | 2 -- compiler/rustc_infer/src/infer/mod.rs | 32 ------------------- 5 files changed, 13 insertions(+), 45 deletions(-) diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index c4c54620e04e9..315303b25fe4e 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -498,11 +498,11 @@ impl<'cx, 'tcx> BorrowckInferCtxt<'cx, 'tcx> { let next_region = self.infcx.next_region_var(origin); let vid = next_region.as_var(); - if cfg!(debug_assertions) && !self.inside_canonicalization_ctxt() { + if cfg!(debug_assertions) { debug!("inserting vid {:?} with origin {:?} into var_to_origin", vid, origin); let ctxt = get_ctxt_fn(); let mut var_to_origin = self.reg_var_to_origin.borrow_mut(); - var_to_origin.insert(vid, ctxt); + assert_eq!(var_to_origin.insert(vid, ctxt), None); } next_region @@ -520,11 +520,11 @@ impl<'cx, 'tcx> BorrowckInferCtxt<'cx, 'tcx> { let next_region = self.infcx.next_nll_region_var(origin); let vid = next_region.as_var(); - if cfg!(debug_assertions) && !self.inside_canonicalization_ctxt() { + if cfg!(debug_assertions) { debug!("inserting vid {:?} with origin {:?} into var_to_origin", vid, origin); let ctxt = get_ctxt_fn(); let mut var_to_origin = self.reg_var_to_origin.borrow_mut(); - var_to_origin.insert(vid, ctxt); + assert_eq!(var_to_origin.insert(vid, ctxt), None); } next_region diff --git a/compiler/rustc_borrowck/src/type_check/relate_tys.rs b/compiler/rustc_borrowck/src/type_check/relate_tys.rs index 7e6d17ec3437e..7158c62b548af 100644 --- a/compiler/rustc_borrowck/src/type_check/relate_tys.rs +++ b/compiler/rustc_borrowck/src/type_check/relate_tys.rs @@ -131,9 +131,13 @@ impl<'tcx> TypeRelatingDelegate<'tcx> for NllTypeRelatingDelegate<'_, '_, 'tcx> ty::BoundRegionKind::BrEnv => BoundRegionInfo::Name(sym::env), }; - if cfg!(debug_assertions) && !self.type_checker.infcx.inside_canonicalization_ctxt() { + if cfg!(debug_assertions) { let mut var_to_origin = self.type_checker.infcx.reg_var_to_origin.borrow_mut(); - var_to_origin.insert(reg.as_var(), RegionCtxt::Placeholder(reg_info)); + let new = RegionCtxt::Placeholder(reg_info); + let prev = var_to_origin.insert(reg.as_var(), new); + if let Some(prev) = prev { + assert_eq!(new, prev); + } } reg @@ -146,9 +150,10 @@ impl<'tcx> TypeRelatingDelegate<'tcx> for NllTypeRelatingDelegate<'_, '_, 'tcx> universe, ); - if cfg!(debug_assertions) && !self.type_checker.infcx.inside_canonicalization_ctxt() { + if cfg!(debug_assertions) { let mut var_to_origin = self.type_checker.infcx.reg_var_to_origin.borrow_mut(); - var_to_origin.insert(reg.as_var(), RegionCtxt::Existential(None)); + let prev = var_to_origin.insert(reg.as_var(), RegionCtxt::Existential(None)); + assert_eq!(prev, None); } reg diff --git a/compiler/rustc_infer/src/infer/at.rs b/compiler/rustc_infer/src/infer/at.rs index d240d8e491faf..0c8854e962abb 100644 --- a/compiler/rustc_infer/src/infer/at.rs +++ b/compiler/rustc_infer/src/infer/at.rs @@ -30,8 +30,6 @@ use super::*; use rustc_middle::ty::relate::{Relate, TypeRelation}; use rustc_middle::ty::{Const, ImplSubject}; -use std::cell::Cell; - /// Whether we should define opaque types or just treat them opaquely. /// /// Currently only used to prevent predicate matching from matching anything @@ -84,7 +82,6 @@ impl<'tcx> InferCtxt<'tcx> { in_snapshot: self.in_snapshot.clone(), universe: self.universe.clone(), intercrate: self.intercrate, - inside_canonicalization_ctxt: Cell::new(self.inside_canonicalization_ctxt()), } } } diff --git a/compiler/rustc_infer/src/infer/canonical/canonicalizer.rs b/compiler/rustc_infer/src/infer/canonical/canonicalizer.rs index d798202a64441..427d05c8b4da7 100644 --- a/compiler/rustc_infer/src/infer/canonical/canonicalizer.rs +++ b/compiler/rustc_infer/src/infer/canonical/canonicalizer.rs @@ -561,8 +561,6 @@ impl<'cx, 'tcx> Canonicalizer<'cx, 'tcx> { where V: TypeFoldable>, { - let _inside_canonical_ctxt_guard = infcx.set_canonicalization_ctxt(); - let needs_canonical_flags = if canonicalize_region_mode.any() { TypeFlags::HAS_INFER | TypeFlags::HAS_FREE_REGIONS | // `HAS_RE_PLACEHOLDER` implies `HAS_FREE_REGIONS` diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index 1cfdb791cd6ae..a89b993159902 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -39,7 +39,6 @@ use rustc_span::Span; use std::cell::{Cell, RefCell}; use std::fmt; -use std::ops::Drop; use self::combine::CombineFields; use self::error_reporting::TypeErrCtxt; @@ -342,11 +341,6 @@ pub struct InferCtxt<'tcx> { /// there is no type that the user could *actually name* that /// would satisfy it. This avoids crippling inference, basically. pub intercrate: bool, - - /// Flag that is set when we enter canonicalization. Used for debugging to ensure - /// that we only collect region information for `BorrowckInferCtxt::reg_var_to_origin` - /// inside non-canonicalization contexts. - inside_canonicalization_ctxt: Cell, } /// See the `error_reporting` module for more details. @@ -638,7 +632,6 @@ impl<'tcx> InferCtxtBuilder<'tcx> { skip_leak_check: Cell::new(false), universe: Cell::new(ty::UniverseIndex::ROOT), intercrate, - inside_canonicalization_ctxt: Cell::new(false), } } } @@ -1636,31 +1629,6 @@ impl<'tcx> InferCtxt<'tcx> { } } } - - pub fn inside_canonicalization_ctxt(&self) -> bool { - self.inside_canonicalization_ctxt.get() - } - - pub fn set_canonicalization_ctxt(&self) -> CanonicalizationCtxtGuard<'_, 'tcx> { - let prev_ctxt = self.inside_canonicalization_ctxt(); - self.inside_canonicalization_ctxt.set(true); - CanonicalizationCtxtGuard { prev_ctxt, infcx: self } - } - - fn set_canonicalization_ctxt_to(&self, ctxt: bool) { - self.inside_canonicalization_ctxt.set(ctxt); - } -} - -pub struct CanonicalizationCtxtGuard<'cx, 'tcx> { - prev_ctxt: bool, - infcx: &'cx InferCtxt<'tcx>, -} - -impl<'cx, 'tcx> Drop for CanonicalizationCtxtGuard<'cx, 'tcx> { - fn drop(&mut self) { - self.infcx.set_canonicalization_ctxt_to(self.prev_ctxt) - } } impl<'tcx> TypeErrCtxt<'_, 'tcx> { From 1e45f0885d766c3d5b1279cd9fb9e78f41cd4ab2 Mon Sep 17 00:00:00 2001 From: BlackHoleFox Date: Mon, 5 Dec 2022 23:15:16 -0600 Subject: [PATCH 18/19] Add deployment-target --print flag for Apple targets --- compiler/rustc_driver_impl/src/lib.rs | 16 +++++++ compiler/rustc_session/src/config.rs | 4 +- compiler/rustc_target/src/spec/apple_base.rs | 42 ++++++++++++++----- compiler/rustc_target/src/spec/mod.rs | 1 + src/doc/rustc/src/command-line-arguments.md | 6 +++ src/tools/tidy/src/ui_tests.rs | 2 +- .../valid-print-requests.stderr | 2 +- tests/ui/deployment-target/invalid-target.rs | 4 ++ .../deployment-target/invalid-target.stderr | 2 + tests/ui/deployment-target/macos-target.rs | 6 +++ .../ui/deployment-target/macos-target.stdout | 1 + 11 files changed, 72 insertions(+), 14 deletions(-) create mode 100644 tests/ui/deployment-target/invalid-target.rs create mode 100644 tests/ui/deployment-target/invalid-target.stderr create mode 100644 tests/ui/deployment-target/macos-target.rs create mode 100644 tests/ui/deployment-target/macos-target.stdout diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 5fac485de6417..b9fb944b0c698 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -745,6 +745,22 @@ fn print_crate_info( } } } + DeploymentTarget => { + use rustc_target::spec::current_apple_deployment_target; + + if sess.target.is_like_osx { + safe_println!( + "deployment_target={}", + current_apple_deployment_target(&sess.target) + .expect("unknown Apple target OS") + ) + } else { + early_error( + ErrorOutputType::default(), + "only Apple targets currently support deployment version info", + ) + } + } } } Compilation::Stop diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index cfdba1120ec38..a398c70c9a06f 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -587,6 +587,7 @@ pub enum PrintRequest { StackProtectorStrategies, LinkArgs, SplitDebuginfo, + DeploymentTarget, } #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] @@ -1452,7 +1453,7 @@ pub fn rustc_short_optgroups() -> Vec { "[crate-name|file-names|sysroot|target-libdir|cfg|calling-conventions|\ target-list|target-cpus|target-features|relocation-models|code-models|\ tls-models|target-spec-json|all-target-specs-json|native-static-libs|\ - stack-protector-strategies|link-args]", + stack-protector-strategies|link-args|deployment-target]", ), opt::flagmulti_s("g", "", "Equivalent to -C debuginfo=2"), opt::flagmulti_s("O", "", "Equivalent to -C opt-level=2"), @@ -1902,6 +1903,7 @@ fn collect_print_requests( ("all-target-specs-json", PrintRequest::AllTargetSpecs), ("link-args", PrintRequest::LinkArgs), ("split-debuginfo", PrintRequest::SplitDebuginfo), + ("deployment-target", PrintRequest::DeploymentTarget), ]; prints.extend(matches.opt_strs("print").into_iter().map(|req| { diff --git a/compiler/rustc_target/src/spec/apple_base.rs b/compiler/rustc_target/src/spec/apple_base.rs index 4c7f64b20785b..ff2246318288e 100644 --- a/compiler/rustc_target/src/spec/apple_base.rs +++ b/compiler/rustc_target/src/spec/apple_base.rs @@ -1,7 +1,7 @@ use std::{borrow::Cow, env}; use crate::spec::{cvs, Cc, DebuginfoKind, FramePointer, LinkArgs}; -use crate::spec::{LinkerFlavor, Lld, SplitDebuginfo, StaticCow, TargetOptions}; +use crate::spec::{LinkerFlavor, Lld, SplitDebuginfo, StaticCow, Target, TargetOptions}; #[cfg(test)] #[path = "apple/tests.rs"] @@ -179,12 +179,28 @@ pub fn opts(os: &'static str, arch: Arch) -> TargetOptions { } } -fn deployment_target(var_name: &str) -> Option<(u32, u32)> { - let deployment_target = env::var(var_name).ok(); - deployment_target - .as_ref() - .and_then(|s| s.split_once('.')) - .and_then(|(a, b)| a.parse::().and_then(|a| b.parse::().map(|b| (a, b))).ok()) +pub fn deployment_target(target: &Target) -> Option { + let (major, minor) = match &*target.os { + "macos" => { + // This does not need to be specific. It just needs to handle x86 vs M1. + let arch = if target.arch == "x86" || target.arch == "x86_64" { X86_64 } else { Arm64 }; + macos_deployment_target(arch) + } + "ios" => ios_deployment_target(), + "watchos" => watchos_deployment_target(), + "tvos" => tvos_deployment_target(), + _ => return None, + }; + + Some(format!("{major}.{minor}")) +} + +fn from_set_deployment_target(var_name: &str) -> Option<(u32, u32)> { + let deployment_target = env::var(var_name).ok()?; + let (unparsed_major, unparsed_minor) = deployment_target.split_once('.')?; + let (major, minor) = (unparsed_major.parse().ok()?, unparsed_minor.parse().ok()?); + + Some((major, minor)) } fn macos_default_deployment_target(arch: Arch) -> (u32, u32) { @@ -198,7 +214,8 @@ fn macos_default_deployment_target(arch: Arch) -> (u32, u32) { } fn macos_deployment_target(arch: Arch) -> (u32, u32) { - deployment_target("MACOSX_DEPLOYMENT_TARGET") + // If you are looking for the default deployment target, prefer `rustc --print deployment-target`. + from_set_deployment_target("MACOSX_DEPLOYMENT_TARGET") .unwrap_or_else(|| macos_default_deployment_target(arch)) } @@ -247,7 +264,8 @@ fn link_env_remove(arch: Arch, os: &'static str) -> StaticCow<[StaticCow]> } fn ios_deployment_target() -> (u32, u32) { - deployment_target("IPHONEOS_DEPLOYMENT_TARGET").unwrap_or((7, 0)) + // If you are looking for the default deployment target, prefer `rustc --print deployment-target`. + from_set_deployment_target("IPHONEOS_DEPLOYMENT_TARGET").unwrap_or((7, 0)) } pub fn ios_llvm_target(arch: Arch) -> String { @@ -272,7 +290,8 @@ pub fn ios_sim_llvm_target(arch: Arch) -> String { } fn tvos_deployment_target() -> (u32, u32) { - deployment_target("TVOS_DEPLOYMENT_TARGET").unwrap_or((7, 0)) + // If you are looking for the default deployment target, prefer `rustc --print deployment-target`. + from_set_deployment_target("TVOS_DEPLOYMENT_TARGET").unwrap_or((7, 0)) } fn tvos_lld_platform_version() -> String { @@ -281,7 +300,8 @@ fn tvos_lld_platform_version() -> String { } fn watchos_deployment_target() -> (u32, u32) { - deployment_target("WATCHOS_DEPLOYMENT_TARGET").unwrap_or((5, 0)) + // If you are looking for the default deployment target, prefer `rustc --print deployment-target`. + from_set_deployment_target("WATCHOS_DEPLOYMENT_TARGET").unwrap_or((5, 0)) } fn watchos_lld_platform_version() -> String { diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 10d38c34919c5..ba4b89c9ea10b 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -60,6 +60,7 @@ pub mod crt_objects; mod aix_base; mod android_base; mod apple_base; +pub use apple_base::deployment_target as current_apple_deployment_target; mod avr_gnu_base; mod bpf_base; mod dragonfly_base; diff --git a/src/doc/rustc/src/command-line-arguments.md b/src/doc/rustc/src/command-line-arguments.md index d4d26654ed114..3be4382b0a3aa 100644 --- a/src/doc/rustc/src/command-line-arguments.md +++ b/src/doc/rustc/src/command-line-arguments.md @@ -248,8 +248,14 @@ The valid types of print values are: exact format of this debugging output is not a stable guarantee, other than that it will include the linker executable and the text of each command-line argument passed to the linker. +- `deployment-target` - The currently selected [deployment target] (or minimum OS version) + for the selected Apple platform target. This value can be used or passed along to other + components alongside a Rust build that need this information, such as C compilers. + This returns rustc's minimum supported deployment target if no `*_DEPLOYMENT_TARGET` variable + is present in the environment, or otherwise returns the variable's parsed value. [conditional compilation]: ../reference/conditional-compilation.html +[deployment target]: https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/cross_development/Configuring/configuring.html ## `-g`: include debug information diff --git a/src/tools/tidy/src/ui_tests.rs b/src/tools/tidy/src/ui_tests.rs index a2f7b8ba7be30..e4f328ec0ddc8 100644 --- a/src/tools/tidy/src/ui_tests.rs +++ b/src/tools/tidy/src/ui_tests.rs @@ -10,7 +10,7 @@ use std::path::{Path, PathBuf}; const ENTRY_LIMIT: usize = 900; // FIXME: The following limits should be reduced eventually. const ISSUES_ENTRY_LIMIT: usize = 1953; -const ROOT_ENTRY_LIMIT: usize = 894; +const ROOT_ENTRY_LIMIT: usize = 895; fn check_entries(tests_path: &Path, bad: &mut bool) { let mut directories: HashMap = HashMap::new(); diff --git a/tests/run-make/valid-print-requests/valid-print-requests.stderr b/tests/run-make/valid-print-requests/valid-print-requests.stderr index bea6ce067f6e8..4f57550c29a7b 100644 --- a/tests/run-make/valid-print-requests/valid-print-requests.stderr +++ b/tests/run-make/valid-print-requests/valid-print-requests.stderr @@ -1,2 +1,2 @@ -error: unknown print request `uwu`. Valid print requests are: `crate-name`, `file-names`, `sysroot`, `target-libdir`, `cfg`, `calling-conventions`, `target-list`, `target-cpus`, `target-features`, `relocation-models`, `code-models`, `tls-models`, `native-static-libs`, `stack-protector-strategies`, `target-spec-json`, `all-target-specs-json`, `link-args`, `split-debuginfo` +error: unknown print request `uwu`. Valid print requests are: `crate-name`, `file-names`, `sysroot`, `target-libdir`, `cfg`, `calling-conventions`, `target-list`, `target-cpus`, `target-features`, `relocation-models`, `code-models`, `tls-models`, `native-static-libs`, `stack-protector-strategies`, `target-spec-json`, `all-target-specs-json`, `link-args`, `split-debuginfo`, `deployment-target` diff --git a/tests/ui/deployment-target/invalid-target.rs b/tests/ui/deployment-target/invalid-target.rs new file mode 100644 index 0000000000000..336624320a32c --- /dev/null +++ b/tests/ui/deployment-target/invalid-target.rs @@ -0,0 +1,4 @@ +// compile-flags: --target x86_64-unknown-linux-gnu --print deployment-target +// needs-llvm-components: x86 + +fn main() {} diff --git a/tests/ui/deployment-target/invalid-target.stderr b/tests/ui/deployment-target/invalid-target.stderr new file mode 100644 index 0000000000000..eb4ac131c40cd --- /dev/null +++ b/tests/ui/deployment-target/invalid-target.stderr @@ -0,0 +1,2 @@ +error: only Apple targets currently support deployment version info + diff --git a/tests/ui/deployment-target/macos-target.rs b/tests/ui/deployment-target/macos-target.rs new file mode 100644 index 0000000000000..8fd4009138143 --- /dev/null +++ b/tests/ui/deployment-target/macos-target.rs @@ -0,0 +1,6 @@ +// only-macos +// compile-flags: --print deployment-target +// normalize-stdout-test: "\d+\.0" -> "$$CURRENT_MAJOR_VERSION.0" +// check-pass + +fn main() {} diff --git a/tests/ui/deployment-target/macos-target.stdout b/tests/ui/deployment-target/macos-target.stdout new file mode 100644 index 0000000000000..42005b873acb7 --- /dev/null +++ b/tests/ui/deployment-target/macos-target.stdout @@ -0,0 +1 @@ +deployment_target=$CURRENT_MAJOR_VERSION.0 From 025c60310a9eec1c866ec085ed52d7f981a39349 Mon Sep 17 00:00:00 2001 From: Krasimir Georgiev Date: Thu, 4 May 2023 10:42:22 +0000 Subject: [PATCH 19/19] bootstrap: add llvm-project/runtimes to the sources This is needed to build libunwind for LLVM 16: https://discourse.llvm.org/t/runtimes-removed-support-for-llvm-enable-projects-in-libcxx-libcxxabi-and-libunwind/65707 Doesn't work: with this, running `python3 x.py dist` produces a tar archive that still doesn't contain the runtimes subdirectory? --- src/bootstrap/dist.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs index 3b35ca1d15dc8..4e3964dac98bc 100644 --- a/src/bootstrap/dist.rs +++ b/src/bootstrap/dist.rs @@ -822,6 +822,8 @@ fn copy_src_dirs( "llvm-project\\compiler-rt", "llvm-project/cmake", "llvm-project\\cmake", + "llvm-project/runtimes", + "llvm-project\\runtimes", ]; if spath.contains("llvm-project") && !spath.ends_with("llvm-project")