diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index 6e606def83625..562b85ccca865 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -5,8 +5,8 @@ use rustc_span::edition::Edition::Edition2024; use super::prelude::*; use crate::attributes::AttributeSafety; use crate::session_diagnostics::{ - NakedFunctionIncompatibleAttribute, NullOnExport, NullOnObjcClass, NullOnObjcSelector, - ObjcClassExpectedStringLiteral, ObjcSelectorExpectedStringLiteral, + EmptyExportName, NakedFunctionIncompatibleAttribute, NullOnExport, NullOnObjcClass, + NullOnObjcSelector, ObjcClassExpectedStringLiteral, ObjcSelectorExpectedStringLiteral, }; use crate::target_checking::Policy::AllowSilent; @@ -129,6 +129,12 @@ impl SingleAttributeParser for ExportNameParser { cx.emit_err(NullOnExport { span: cx.attr_span }); return None; } + if name.is_empty() { + // LLVM will make up a name if the empty string is given, but that name will be + // inconsistent between compilation units, causing linker errors. + cx.emit_err(EmptyExportName { span: cx.attr_span }); + return None; + } Some(AttributeKind::ExportName { name, span: cx.attr_span }) } } diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index d9310906dfeef..3e0138977aadf 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -17,7 +17,7 @@ use crate::session_diagnostics::{ AsNeededCompatibility, BundleNeedsStatic, EmptyLinkName, ExportSymbolsNeedsStatic, ImportNameTypeRaw, ImportNameTypeX86, IncompatibleWasmLink, InvalidLinkModifier, InvalidMachoSection, InvalidMachoSectionReason, LinkFrameworkApple, LinkOrdinalOutOfRange, - LinkRequiresName, MultipleModifiers, NullOnLinkSection, RawDylibNoNul, RawDylibOnlyWindows, + LinkRequiresName, MultipleModifiers, NullOnLinkName, NullOnLinkSection, RawDylibOnlyWindows, WholeArchiveNeedsStatic, }; @@ -42,6 +42,19 @@ impl SingleAttributeParser for LinkNameParser { return None; }; + if name.as_str().contains('\0') { + // `#[link_name = ...]` will be converted to a null-terminated string, + // so it may not contain any null characters. + cx.emit_err(NullOnLinkName { span: nv.value_span }); + return None; + } + if name.is_empty() { + // Otherwise LLVM will just make up a name and the linker will fail + // to find an empty symbol name. + cx.emit_err(EmptyLinkName { span: nv.value_span }); + return None; + } + Some(LinkName { name, span: cx.attr_span }) } } @@ -218,7 +231,7 @@ impl CombineAttributeParser for LinkParser { if wasm_import_module.is_some() { (name, kind) = (wasm_import_module, Some(NativeLibKind::WasmImportModule)); } - let Some((name, name_span)) = name else { + let Some((name, _name_span)) = name else { cx.emit_err(LinkRequiresName { span: cx.attr_span }); return None; }; @@ -230,12 +243,6 @@ impl CombineAttributeParser for LinkParser { } } - if let Some(NativeLibKind::RawDylib { .. }) = kind - && name.as_str().contains('\0') - { - cx.emit_err(RawDylibNoNul { span: name_span }); - } - Some(LinkEntry { span: cx.attr_span, kind: kind.unwrap_or(NativeLibKind::Unspecified), @@ -265,9 +272,13 @@ impl LinkParser { return false; }; + if link_name.as_str().contains('\0') { + cx.emit_err(NullOnLinkName { span: nv.value_span }); + } if link_name.is_empty() { cx.emit_err(EmptyLinkName { span: nv.value_span }); } + *name = Some((link_name, nv.value_span)); true } diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 0a9c96033257d..5b715005f1076 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -396,6 +396,13 @@ pub(crate) struct UnusedMultiple { pub name: Symbol, } +#[derive(Diagnostic)] +#[diag("`export_name` may not be empty")] +pub(crate) struct EmptyExportName { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag("`export_name` may not contain null characters", code = E0648)] pub(crate) struct NullOnExport { @@ -410,6 +417,13 @@ pub(crate) struct NullOnLinkSection { pub span: Span, } +#[derive(Diagnostic)] +#[diag("link name may not contain null characters", code = E0648)] +pub(crate) struct NullOnLinkName { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag("`objc::class!` may not contain null characters")] pub(crate) struct NullOnObjcClass { @@ -984,13 +998,6 @@ pub(crate) struct LinkRequiresName { pub span: Span, } -#[derive(Diagnostic)] -#[diag("link name must not contain NUL characters if link kind is `raw-dylib`")] -pub(crate) struct RawDylibNoNul { - #[primary_span] - pub span: Span, -} - #[derive(Diagnostic)] #[diag("link kind `raw-dylib` is only supported on Windows targets", code = E0455)] pub(crate) struct RawDylibOnlyWindows { diff --git a/compiler/rustc_lint/src/unused/must_use.rs b/compiler/rustc_lint/src/unused/must_use.rs index b2787bf54f9ac..293320d868a66 100644 --- a/compiler/rustc_lint/src/unused/must_use.rs +++ b/compiler/rustc_lint/src/unused/must_use.rs @@ -133,18 +133,11 @@ pub enum MustUsePath { /// Returns `Some(path)` if `ty` should be considered as "`must_use`" in the context of `expr` /// (`expr` is used to get the parent module, which can affect which types are considered uninhabited). -/// -/// If `simplify_uninhabited` is true, this function considers `Result` and -/// `ControlFlow` the same as `T` (we don't set this *yet* in rustc, but expose this -/// so clippy can use this). -// -// FIXME: remove `simplify_uninhabited` once clippy had a release with the new semantics. #[instrument(skip(cx, expr), level = "debug", ret)] pub fn is_ty_must_use<'tcx>( cx: &LateContext<'tcx>, ty: Ty<'tcx>, expr: &hir::Expr<'_>, - simplify_uninhabited: bool, ) -> IsTyMustUse { if ty.is_unit() { return IsTyMustUse::Trivial; @@ -157,50 +150,29 @@ pub fn is_ty_must_use<'tcx>( match *ty.kind() { _ if is_uninhabited(ty) => IsTyMustUse::Trivial, ty::Adt(..) if let Some(boxed) = ty.boxed_ty() => { - is_ty_must_use(cx, boxed, expr, simplify_uninhabited) - .map(|inner| MustUsePath::Boxed(Box::new(inner))) + is_ty_must_use(cx, boxed, expr).map(|inner| MustUsePath::Boxed(Box::new(inner))) } ty::Adt(def, args) if cx.tcx.is_lang_item(def.did(), LangItem::Pin) => { let pinned_ty = args.type_at(0); - is_ty_must_use(cx, pinned_ty, expr, simplify_uninhabited) - .map(|inner| MustUsePath::Pinned(Box::new(inner))) + is_ty_must_use(cx, pinned_ty, expr).map(|inner| MustUsePath::Pinned(Box::new(inner))) } // Consider `Result` (e.g. `Result<(), !>`) equivalent to `T`. ty::Adt(def, args) - if simplify_uninhabited - && cx.tcx.is_diagnostic_item(sym::Result, def.did()) + if cx.tcx.is_diagnostic_item(sym::Result, def.did()) && is_uninhabited(args.type_at(1)) => { let ok_ty = args.type_at(0); - is_ty_must_use(cx, ok_ty, expr, simplify_uninhabited) - .map(|path| MustUsePath::Result(Box::new(path))) + is_ty_must_use(cx, ok_ty, expr).map(|path| MustUsePath::Result(Box::new(path))) } // Consider `ControlFlow` (e.g. `ControlFlow`) equivalent to `T`. ty::Adt(def, args) - if simplify_uninhabited - && cx.tcx.is_diagnostic_item(sym::ControlFlow, def.did()) + if cx.tcx.is_diagnostic_item(sym::ControlFlow, def.did()) && is_uninhabited(args.type_at(0)) => { let continue_ty = args.type_at(1); - is_ty_must_use(cx, continue_ty, expr, simplify_uninhabited) + is_ty_must_use(cx, continue_ty, expr) .map(|path| MustUsePath::ControlFlow(Box::new(path))) } - // Suppress warnings on `Result<(), Uninhabited>` (e.g. `Result<(), !>`). - ty::Adt(def, args) - if cx.tcx.is_diagnostic_item(sym::Result, def.did()) - && args.type_at(0).is_unit() - && is_uninhabited(args.type_at(1)) => - { - IsTyMustUse::Trivial - } - // Suppress warnings on `ControlFlow` (e.g. `ControlFlow`). - ty::Adt(def, args) - if cx.tcx.is_diagnostic_item(sym::ControlFlow, def.did()) - && args.type_at(1).is_unit() - && is_uninhabited(args.type_at(0)) => - { - IsTyMustUse::Trivial - } ty::Adt(def, _) => { is_def_must_use(cx, def.did(), expr.span).map_or(IsTyMustUse::No, IsTyMustUse::Yes) } @@ -258,7 +230,7 @@ pub fn is_ty_must_use<'tcx>( let mut nested_must_use = Vec::new(); tys.iter().zip(elem_exprs).enumerate().for_each(|(i, (ty, expr))| { - let must_use = is_ty_must_use(cx, ty, expr, simplify_uninhabited); + let must_use = is_ty_must_use(cx, ty, expr); all_trivial &= matches!(must_use, IsTyMustUse::Trivial); if let IsTyMustUse::Yes(path) = must_use { @@ -280,8 +252,9 @@ pub fn is_ty_must_use<'tcx>( // If the array is empty we don't lint, to avoid false positives Some(0) | None => IsTyMustUse::No, // If the array is definitely non-empty, we can do `#[must_use]` checking. - Some(len) => is_ty_must_use(cx, ty, expr, simplify_uninhabited) - .map(|inner| MustUsePath::Array(Box::new(inner), len)), + Some(len) => { + is_ty_must_use(cx, ty, expr).map(|inner| MustUsePath::Array(Box::new(inner), len)) + } }, ty::Closure(..) | ty::CoroutineClosure(..) => { IsTyMustUse::Yes(MustUsePath::Closure(expr.span)) @@ -345,7 +318,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { let ty = cx.typeck_results().expr_ty(expr); - let must_use_result = is_ty_must_use(cx, ty, expr, false); + let must_use_result = is_ty_must_use(cx, ty, expr); let type_lint_emitted_or_trivial = match must_use_result { IsTyMustUse::Yes(path) => { emit_must_use_untranslated(cx, &path, "", "", 1, false, expr_is_from_block); diff --git a/library/std/src/process.rs b/library/std/src/process.rs index 69cbc113bb05a..0b8db439a6370 100644 --- a/library/std/src/process.rs +++ b/library/std/src/process.rs @@ -1185,7 +1185,8 @@ impl Command { /// [`Command::env_remove`] can be retrieved with this method. /// /// Note that this output does not include environment variables inherited from the parent - /// process. + /// process. To see the full list of environment variables, including those inherited from the + /// parent process, use [`Command::get_resolved_envs`]. /// /// Each element is a tuple key/value pair `(&OsStr, Option<&OsStr>)`. A [`None`] value /// indicates its key was explicitly removed via [`Command::env_remove`]. The associated key for @@ -1214,6 +1215,42 @@ impl Command { CommandEnvs { iter: self.inner.get_envs() } } + /// Returns an iterator of the environment variables that will be set when the process is spawned. + /// + /// This returns the environment as it would be if the command were executed at the time of calling + /// this method. The returned environment includes: + /// - All inherited environment variables from the parent process (unless [`Command::env_clear`] was called) + /// - All environment variables explicitly set via [`Command::env`] or [`Command::envs`] + /// - Excluding any environment variables removed via [`Command::env_remove`] + /// + /// Note that the returned environment is a snapshot at the time this method is called and will not + /// reflect any subsequent changes to the `Command` or the parent process's environment. Additionally, + /// it will not reflect changes made in a `pre_exec` hook (on Unix platforms). + /// + /// Each element is a tuple `(OsString, OsString)` representing an environment variable key and value. + /// + /// # Examples + /// + /// ``` + /// #![feature(command_resolved_envs)] + /// use std::process::Command; + /// use std::ffi::{OsString, OsStr}; + /// use std::env; + /// use std::collections::HashMap; + /// + /// let mut cmd = Command::new("ls"); + /// cmd.env("TZ", "UTC"); + /// unsafe { env::set_var("EDITOR", "vim"); } + /// + /// let resolved: HashMap = cmd.get_resolved_envs().collect(); + /// assert_eq!(resolved.get(OsStr::new("TZ")), Some(&OsString::from("UTC"))); + /// assert_eq!(resolved.get(OsStr::new("EDITOR")), Some(&OsString::from("vim"))); + /// ``` + #[unstable(feature = "command_resolved_envs", issue = "149070")] + pub fn get_resolved_envs(&self) -> CommandResolvedEnvs { + self.inner.get_resolved_envs() + } + /// Returns the working directory for the child process. /// /// This returns [`None`] if the working directory will not be changed. @@ -1367,6 +1404,9 @@ impl<'a> fmt::Debug for CommandEnvs<'a> { } } +#[unstable(feature = "command_resolved_envs", issue = "149070")] +pub use imp::CommandResolvedEnvs; + /// The output of a finished process. /// /// This is returned in a Result by either the [`output`] method of a diff --git a/library/std/src/sys/process/env.rs b/library/std/src/sys/process/env.rs index e08b476540ef9..15065c6e2c922 100644 --- a/library/std/src/sys/process/env.rs +++ b/library/std/src/sys/process/env.rs @@ -113,3 +113,34 @@ impl<'a> ExactSizeIterator for CommandEnvs<'a> { self.iter.is_empty() } } + +/// An iterator over the fully resolved environment variables. +/// +/// This struct is created by +/// [`Command::get_resolved_envs`][crate::process::Command::get_resolved_envs]. See its +/// documentation for more. +#[derive(Debug)] +#[must_use = "iterators are lazy and do nothing unless consumed"] +#[unstable(feature = "command_resolved_envs", issue = "149070")] +pub struct CommandResolvedEnvs { + inner: crate::collections::btree_map::IntoIter, +} + +impl CommandResolvedEnvs { + pub(crate) fn new(map: BTreeMap) -> Self { + Self { inner: map.into_iter() } + } +} + +#[unstable(feature = "command_resolved_envs", issue = "149070")] +impl Iterator for CommandResolvedEnvs { + type Item = (OsString, OsString); + + fn next(&mut self) -> Option { + self.inner.next().map(|(key, value)| (key.into(), value)) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} diff --git a/library/std/src/sys/process/mod.rs b/library/std/src/sys/process/mod.rs index 46f4ebf6db421..3eb20dae083ca 100644 --- a/library/std/src/sys/process/mod.rs +++ b/library/std/src/sys/process/mod.rs @@ -27,6 +27,8 @@ cfg_select! { mod env; pub use env::CommandEnvs; +#[unstable(feature = "command_resolved_envs", issue = "149070")] +pub use env::CommandResolvedEnvs; #[cfg(target_family = "unix")] pub use imp::getppid; pub use imp::{ diff --git a/library/std/src/sys/process/motor.rs b/library/std/src/sys/process/motor.rs index 133633f7bc67b..080da9be3af92 100644 --- a/library/std/src/sys/process/motor.rs +++ b/library/std/src/sys/process/motor.rs @@ -1,5 +1,5 @@ use super::CommandEnvs; -use super::env::CommandEnv; +use super::env::{CommandEnv, CommandResolvedEnvs}; use crate::ffi::OsStr; pub use crate::ffi::OsString as EnvKey; use crate::num::NonZeroI32; @@ -100,6 +100,10 @@ impl Command { self.env.does_clear() } + pub fn get_resolved_envs(&self) -> CommandResolvedEnvs { + CommandResolvedEnvs::new(self.env.capture()) + } + pub fn get_current_dir(&self) -> Option<&Path> { self.cwd.as_ref().map(Path::new) } diff --git a/library/std/src/sys/process/uefi.rs b/library/std/src/sys/process/uefi.rs index 3ae3aa7f2e309..528bc2be2244a 100644 --- a/library/std/src/sys/process/uefi.rs +++ b/library/std/src/sys/process/uefi.rs @@ -1,6 +1,6 @@ use r_efi::protocols::{simple_text_input, simple_text_output}; -use super::env::{CommandEnv, CommandEnvs}; +use super::env::{CommandEnv, CommandEnvs, CommandResolvedEnvs}; use crate::collections::BTreeMap; pub use crate::ffi::OsString as EnvKey; use crate::ffi::{OsStr, OsString}; @@ -86,6 +86,10 @@ impl Command { self.env.does_clear() } + pub fn get_resolved_envs(&self) -> CommandResolvedEnvs { + CommandResolvedEnvs::new(self.env.capture()) + } + pub fn get_current_dir(&self) -> Option<&Path> { None } diff --git a/library/std/src/sys/process/unix/common.rs b/library/std/src/sys/process/unix/common.rs index 2d83782b7d0b9..8215b196127ac 100644 --- a/library/std/src/sys/process/unix/common.rs +++ b/library/std/src/sys/process/unix/common.rs @@ -15,7 +15,7 @@ use crate::sys::fs::File; #[cfg(not(target_os = "fuchsia"))] use crate::sys::fs::OpenOptions; use crate::sys::pipe::pipe; -use crate::sys::process::env::{CommandEnv, CommandEnvs}; +use crate::sys::process::env::{CommandEnv, CommandEnvs, CommandResolvedEnvs}; use crate::sys::{FromInner, IntoInner, cvt_r}; use crate::{fmt, io, mem}; @@ -267,6 +267,10 @@ impl Command { self.env.does_clear() } + pub fn get_resolved_envs(&self) -> CommandResolvedEnvs { + CommandResolvedEnvs::new(self.env.capture()) + } + pub fn get_current_dir(&self) -> Option<&Path> { self.cwd.as_ref().map(|cs| Path::new(OsStr::from_bytes(cs.as_bytes()))) } diff --git a/library/std/src/sys/process/unsupported.rs b/library/std/src/sys/process/unsupported.rs index 9ed66a559117c..114f0001b7faf 100644 --- a/library/std/src/sys/process/unsupported.rs +++ b/library/std/src/sys/process/unsupported.rs @@ -1,4 +1,4 @@ -use super::env::{CommandEnv, CommandEnvs}; +use super::env::{CommandEnv, CommandEnvs, CommandResolvedEnvs}; pub use crate::ffi::OsString as EnvKey; use crate::ffi::{OsStr, OsString}; use crate::num::NonZero; @@ -89,6 +89,10 @@ impl Command { self.env.does_clear() } + pub fn get_resolved_envs(&self) -> CommandResolvedEnvs { + CommandResolvedEnvs::new(self.env.capture()) + } + pub fn get_current_dir(&self) -> Option<&Path> { self.cwd.as_ref().map(|cs| Path::new(cs)) } diff --git a/library/std/src/sys/process/windows.rs b/library/std/src/sys/process/windows.rs index deb4243d314e2..a747ef048d901 100644 --- a/library/std/src/sys/process/windows.rs +++ b/library/std/src/sys/process/windows.rs @@ -5,7 +5,7 @@ mod tests; use core::ffi::c_void; -use super::env::{CommandEnv, CommandEnvs}; +use super::env::{CommandEnv, CommandEnvs, CommandResolvedEnvs}; use crate::collections::BTreeMap; use crate::env::consts::{EXE_EXTENSION, EXE_SUFFIX}; use crate::ffi::{OsStr, OsString}; @@ -256,6 +256,10 @@ impl Command { self.env.does_clear() } + pub fn get_resolved_envs(&self) -> CommandResolvedEnvs { + CommandResolvedEnvs::new(self.env.capture()) + } + pub fn get_current_dir(&self) -> Option<&Path> { self.cwd.as_ref().map(Path::new) } diff --git a/tests/codegen-llvm/issues/issue-144329-niched-option-check.rs b/tests/codegen-llvm/issues/issue-144329-niched-option-check.rs new file mode 100644 index 0000000000000..4f80608e7d34a --- /dev/null +++ b/tests/codegen-llvm/issues/issue-144329-niched-option-check.rs @@ -0,0 +1,64 @@ +//! Ensure that redundant null checks on `&mut T` from `Option<(_, &mut T)>` are eliminated. + +//@ compile-flags: -Copt-level=3 + +#![crate_type = "lib"] + +type T = [u64; 4]; + +// CHECK-LABEL: @f0( +#[no_mangle] +pub fn f0(stack: &mut Stack, f: fn(&T)) -> bool { + // CHECK-NOT: icmp eq ptr.*null.* + f_impl::<0>(stack, f) +} + +// CHECK-LABEL: @f1( +#[no_mangle] +pub fn f1(stack: &mut Stack, f: fn(&T)) -> bool { + // CHECK-NOT: icmp eq ptr.*null.* + f_impl::<1>(stack, f) +} + +// CHECK-LABEL: @f2( +#[no_mangle] +pub fn f2(stack: &mut Stack, f: fn(&T)) -> bool { + // CHECK-NOT: icmp eq ptr.*null.* + f_impl::<2>(stack, f) +} + +#[inline(always)] +fn f_impl(stack: &mut Stack, f: fn(&T)) -> bool { + let Some((a, b)) = stack.popn_top::() else { + return false; + }; + a.iter().for_each(f); + f(b); + true +} + +pub struct Stack { + data: Vec, +} + +impl Stack { + #[inline] + fn popn_top(&mut self) -> Option<([T; N], &mut T)> { + if self.data.len() < N + 1 { + return None; + } + unsafe { Some((self.popn_unchecked(), self.top_unchecked())) } + } + + unsafe fn popn_unchecked(&mut self) -> [T; N] { + core::array::from_fn(|_| unsafe { self.pop_unchecked() }) + } + + unsafe fn pop_unchecked(&mut self) -> T { + self.data.pop().unwrap_unchecked() + } + + unsafe fn top_unchecked(&mut self) -> &mut T { + self.data.last_mut().unwrap_unchecked() + } +} diff --git a/tests/ui/attributes/invalid-export-name.rs b/tests/ui/attributes/invalid-export-name.rs new file mode 100644 index 0000000000000..1fa33a24ce424 --- /dev/null +++ b/tests/ui/attributes/invalid-export-name.rs @@ -0,0 +1,22 @@ +#![crate_type = "lib"] + +#[export_name = "\0foo"] +//~^ ERROR `export_name` may not contain null characters +fn has_null_byte() {} + +#[export_name = "foo\0"] +//~^ ERROR `export_name` may not contain null characters +fn null_terminated() {} + +#[export_name = "\0"] +//~^ ERROR `export_name` may not contain null characters +fn empty_null() {} + +#[export_name = ""] +//~^ ERROR `export_name` may not be empty +fn empty() {} + +#[export_name = "\ +"] +//~^^ ERROR `export_name` may not be empty +fn empty_newline() {} diff --git a/tests/ui/attributes/invalid-export-name.stderr b/tests/ui/attributes/invalid-export-name.stderr new file mode 100644 index 0000000000000..f8553c823cd8f --- /dev/null +++ b/tests/ui/attributes/invalid-export-name.stderr @@ -0,0 +1,34 @@ +error[E0648]: `export_name` may not contain null characters + --> $DIR/invalid-export-name.rs:3:1 + | +LL | #[export_name = "\0foo"] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0648]: `export_name` may not contain null characters + --> $DIR/invalid-export-name.rs:7:1 + | +LL | #[export_name = "foo\0"] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0648]: `export_name` may not contain null characters + --> $DIR/invalid-export-name.rs:11:1 + | +LL | #[export_name = "\0"] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: `export_name` may not be empty + --> $DIR/invalid-export-name.rs:15:1 + | +LL | #[export_name = ""] + | ^^^^^^^^^^^^^^^^^^^ + +error: `export_name` may not be empty + --> $DIR/invalid-export-name.rs:19:1 + | +LL | / #[export_name = "\ +LL | | "] + | |__^ + +error: aborting due to 5 previous errors + +For more information about this error, try `rustc --explain E0648`. diff --git a/tests/ui/attributes/invalid-link-name.rs b/tests/ui/attributes/invalid-link-name.rs new file mode 100644 index 0000000000000..579100914d3c4 --- /dev/null +++ b/tests/ui/attributes/invalid-link-name.rs @@ -0,0 +1,54 @@ +#![crate_type = "lib"] + +#[link(name = "")] +//~^ ERROR link name must not be empty +unsafe extern "C" { + #[link_name = ""] + //~^ ERROR link name must not be empty + safe fn empty(); +} + +#[link(name = " ")] +unsafe extern "C" { + #[link_name = " "] + safe fn this_is_fine(); +} + +#[export_name = " "] +extern "C" fn bar() -> i32 { + 42 +} + +#[link(name = "\0")] +//~^ ERROR link name may not contain null characters +unsafe extern "C" {} + +#[link(name = "foo\0")] +//~^ ERROR link name may not contain null characters +unsafe extern "C" {} + +#[link(name = "\0foo")] +//~^ ERROR link name may not contain null characters +unsafe extern "C" {} + +#[link(name = "fo\0o")] +//~^ ERROR link name may not contain null characters +unsafe extern "C" {} + +unsafe extern "C" { + #[link_name = "\0"] + //~^ ERROR link name may not contain null characters + safe fn empty_null(); + + #[link_name = "foo\0"] + //~^ ERROR link name may not contain null characters + safe fn trailing_null(); + + #[link_name = "\0foo"] + //~^ ERROR link name may not contain null characters + safe fn leading_null(); + + #[link_name = "fo\0o"] + //~^ ERROR link name may not contain null characters + safe fn middle_null(); +} diff --git a/tests/ui/attributes/invalid-link-name.stderr b/tests/ui/attributes/invalid-link-name.stderr new file mode 100644 index 0000000000000..b31c1d53bd81e --- /dev/null +++ b/tests/ui/attributes/invalid-link-name.stderr @@ -0,0 +1,64 @@ +error[E0454]: link name must not be empty + --> $DIR/invalid-link-name.rs:3:15 + | +LL | #[link(name = "")] + | ^^ empty link name + +error[E0454]: link name must not be empty + --> $DIR/invalid-link-name.rs:6:19 + | +LL | #[link_name = ""] + | ^^ empty link name + +error[E0648]: link name may not contain null characters + --> $DIR/invalid-link-name.rs:22:15 + | +LL | #[link(name = "\0")] + | ^^^^ + +error[E0648]: link name may not contain null characters + --> $DIR/invalid-link-name.rs:26:15 + | +LL | #[link(name = "foo\0")] + | ^^^^^^^ + +error[E0648]: link name may not contain null characters + --> $DIR/invalid-link-name.rs:30:15 + | +LL | #[link(name = "\0foo")] + | ^^^^^^^ + +error[E0648]: link name may not contain null characters + --> $DIR/invalid-link-name.rs:34:15 + | +LL | #[link(name = "fo\0o")] + | ^^^^^^^ + +error[E0648]: link name may not contain null characters + --> $DIR/invalid-link-name.rs:39:19 + | +LL | #[link_name = "\0"] + | ^^^^ + +error[E0648]: link name may not contain null characters + --> $DIR/invalid-link-name.rs:43:19 + | +LL | #[link_name = "foo\0"] + | ^^^^^^^ + +error[E0648]: link name may not contain null characters + --> $DIR/invalid-link-name.rs:47:19 + | +LL | #[link_name = "\0foo"] + | ^^^^^^^ + +error[E0648]: link name may not contain null characters + --> $DIR/invalid-link-name.rs:51:19 + | +LL | #[link_name = "fo\0o"] + | ^^^^^^^ + +error: aborting due to 10 previous errors + +Some errors have detailed explanations: E0454, E0648. +For more information about an error, try `rustc --explain E0454`. diff --git a/tests/ui/linkage-attr/raw-dylib/elf/malformed-link-name.rs b/tests/ui/linkage-attr/raw-dylib/elf/malformed-link-name.rs index 46e3798284b25..02262c2378c83 100644 --- a/tests/ui/linkage-attr/raw-dylib/elf/malformed-link-name.rs +++ b/tests/ui/linkage-attr/raw-dylib/elf/malformed-link-name.rs @@ -11,10 +11,8 @@ unsafe extern "C" { pub safe fn exit_0(status: i32) -> !; //~ ERROR link name must be well-formed if link kind is `raw-dylib` #[link_name = "@GLIBC_2.2.5"] pub safe fn exit_1(status: i32) -> !; //~ ERROR link name must be well-formed if link kind is `raw-dylib` - #[link_name = "ex\0it@GLIBC_2.2.5"] - pub safe fn exit_2(status: i32) -> !; //~ ERROR link name must be well-formed if link kind is `raw-dylib` #[link_name = "exit@@GLIBC_2.2.5"] - pub safe fn exit_3(status: i32) -> !; //~ ERROR link name must be well-formed if link kind is `raw-dylib` + pub safe fn exit_2(status: i32) -> !; //~ ERROR link name must be well-formed if link kind is `raw-dylib` } fn main() {} diff --git a/tests/ui/linkage-attr/raw-dylib/elf/malformed-link-name.stderr b/tests/ui/linkage-attr/raw-dylib/elf/malformed-link-name.stderr index 5a979e7a3b1af..f83c63f06b8ba 100644 --- a/tests/ui/linkage-attr/raw-dylib/elf/malformed-link-name.stderr +++ b/tests/ui/linkage-attr/raw-dylib/elf/malformed-link-name.stderr @@ -16,11 +16,5 @@ error: link name must be well-formed if link kind is `raw-dylib` LL | pub safe fn exit_2(status: i32) -> !; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: link name must be well-formed if link kind is `raw-dylib` - --> $DIR/malformed-link-name.rs:17:5 - | -LL | pub safe fn exit_3(status: i32) -> !; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: aborting due to 4 previous errors +error: aborting due to 3 previous errors diff --git a/tests/ui/lint/unused/must_use-result-unit-uninhabited.rs b/tests/ui/lint/unused/must_use-result-unit-uninhabited.rs index 8f63e4a7f8323..d1b47374a11b8 100644 --- a/tests/ui/lint/unused/must_use-result-unit-uninhabited.rs +++ b/tests/ui/lint/unused/must_use-result-unit-uninhabited.rs @@ -7,6 +7,11 @@ use core::ops::{ControlFlow, ControlFlow::Continue}; use dep::{MyUninhabited, MyUninhabitedNonexhaustive}; +#[must_use] +struct MustUse; + +struct Struct; + fn result_unit_unit() -> Result<(), ()> { Ok(()) } @@ -19,6 +24,14 @@ fn result_unit_never() -> Result<(), !> { Ok(()) } +fn result_struct_never() -> Result { + Ok(Struct) +} + +fn result_must_use_never() -> Result { + Ok(MustUse) +} + fn result_unit_myuninhabited() -> Result<(), MyUninhabited> { Ok(()) } @@ -80,6 +93,8 @@ fn main() { result_unit_unit(); //~ ERROR: unused `Result` that must be used result_unit_infallible(); result_unit_never(); + result_must_use_never(); //~ ERROR: unused `MustUse` in a `Result` with an uninhabited error that must be used + result_struct_never(); result_unit_myuninhabited(); result_unit_myuninhabited_nonexhaustive(); //~ ERROR: unused `Result` that must be used result_unit_assoctype(S1); diff --git a/tests/ui/lint/unused/must_use-result-unit-uninhabited.stderr b/tests/ui/lint/unused/must_use-result-unit-uninhabited.stderr index 31d6f6bcf2bc7..b4c62c7690b49 100644 --- a/tests/ui/lint/unused/must_use-result-unit-uninhabited.stderr +++ b/tests/ui/lint/unused/must_use-result-unit-uninhabited.stderr @@ -1,5 +1,5 @@ error: unused `Result` that must be used - --> $DIR/must_use-result-unit-uninhabited.rs:80:5 + --> $DIR/must_use-result-unit-uninhabited.rs:93:5 | LL | result_unit_unit(); | ^^^^^^^^^^^^^^^^^^ @@ -15,8 +15,14 @@ help: use `let _ = ...` to ignore the resulting value LL | let _ = result_unit_unit(); | +++++++ +error: unused `MustUse` in a `Result` with an uninhabited error that must be used + --> $DIR/must_use-result-unit-uninhabited.rs:96:5 + | +LL | result_must_use_never(); + | ^^^^^^^^^^^^^^^^^^^^^^^ + error: unused `Result` that must be used - --> $DIR/must_use-result-unit-uninhabited.rs:84:5 + --> $DIR/must_use-result-unit-uninhabited.rs:99:5 | LL | result_unit_myuninhabited_nonexhaustive(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -28,7 +34,7 @@ LL | let _ = result_unit_myuninhabited_nonexhaustive(); | +++++++ error: unused `Result` that must be used - --> $DIR/must_use-result-unit-uninhabited.rs:86:5 + --> $DIR/must_use-result-unit-uninhabited.rs:101:5 | LL | result_unit_assoctype(S2); | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -40,7 +46,7 @@ LL | let _ = result_unit_assoctype(S2); | +++++++ error: unused `Result` that must be used - --> $DIR/must_use-result-unit-uninhabited.rs:88:5 + --> $DIR/must_use-result-unit-uninhabited.rs:103:5 | LL | S2.method_use_assoc_type(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -52,7 +58,7 @@ LL | let _ = S2.method_use_assoc_type(); | +++++++ error: unused `ControlFlow` that must be used - --> $DIR/must_use-result-unit-uninhabited.rs:90:5 + --> $DIR/must_use-result-unit-uninhabited.rs:105:5 | LL | controlflow_unit(); | ^^^^^^^^^^^^^^^^^^ @@ -63,7 +69,7 @@ LL | let _ = controlflow_unit(); | +++++++ error: unused `Result` that must be used - --> $DIR/must_use-result-unit-uninhabited.rs:99:9 + --> $DIR/must_use-result-unit-uninhabited.rs:114:9 | LL | self.generate(); | ^^^^^^^^^^^^^^^ @@ -74,5 +80,5 @@ help: use `let _ = ...` to ignore the resulting value LL | let _ = self.generate(); | +++++++ -error: aborting due to 6 previous errors +error: aborting due to 7 previous errors