From a4b70f24fcd4972a54ee9ba4dfe3974d4eeb511c Mon Sep 17 00:00:00 2001 From: Joseph Perez Date: Fri, 7 Nov 2025 15:20:36 +0100 Subject: [PATCH 1/3] lint(unsafe_code): exclude unsafe declarations from lint coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unsafe declarations of traits, functions, methods, or extern blocks cannot cause undefined behavior by themselves — only unsafe blocks or implementations of unsafe traits can. The `unsafe_code` lint previously applied to these declarations, preventing, for example, declarations of `extern "C"` blocks. See rust-lang/rust#108926. This change removes all such unsafe declarations from `unsafe_code` coverage. However, to maintain soundness, unsafe functions *with bodies* are only allowed when `unsafe_op_in_unsafe_fn` is set to `forbid`. This ensures unsafe operations inside such functions require an `unsafe` block. Declarations of unsafe functions *without bodies* (e.g., in traits) are always allowed. Closes #108926 --- compiler/rustc_lint/messages.ftl | 7 ++--- compiler/rustc_lint/src/builtin.rs | 31 ++++++++++++------- compiler/rustc_lint/src/lints.rs | 6 ---- tests/ui/lint/lint-unsafe-code.rs | 5 ++- tests/ui/lint/lint-unsafe-code.stderr | 14 +-------- .../forbid-unsafe-op-in-unsafe-fn.rs | 25 +++++++++++++++ .../forbid-unsafe-op-in-unsafe-fn.stderr | 14 +++++++++ .../lint/unsafe_code/unsafe-extern-blocks.rs | 8 ++--- .../unsafe_code/unsafe-extern-blocks.stderr | 17 ---------- 9 files changed, 66 insertions(+), 61 deletions(-) create mode 100644 tests/ui/lint/unsafe_code/forbid-unsafe-op-in-unsafe-fn.rs create mode 100644 tests/ui/lint/unsafe_code/forbid-unsafe-op-in-unsafe-fn.stderr delete mode 100644 tests/ui/lint/unsafe_code/unsafe-extern-blocks.stderr diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 8aa90c070acd3..0887d6ec63596 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -64,7 +64,7 @@ lint_builtin_const_no_mangle = const items should never be `#[no_mangle]` .suggestion = try a static value lint_builtin_decl_unsafe_fn = declaration of an `unsafe` function -lint_builtin_decl_unsafe_method = declaration of an `unsafe` method + .note = declaration of `unsafe` function is allowed when lint `unsafe_op_in_unsafe_fn` is set to `forbid` level lint_builtin_deref_nullptr = dereferencing a null pointer .label = this code causes undefined behavior when executed @@ -91,6 +91,7 @@ lint_builtin_global_asm = usage of `core::arch::global_asm` lint_builtin_global_macro_unsafety = using this macro is unsafe even though it does not need an `unsafe` block lint_builtin_impl_unsafe_method = implementation of an `unsafe` method + .note = implementation of `unsafe` method is allowed when lint `unsafe_op_in_unsafe_fn` is set to `forbid` level lint_builtin_incomplete_features = the feature `{$name}` is incomplete and may not be safe to use and/or cause compiler crashes .note = see issue #{$n} for more information @@ -165,12 +166,8 @@ lint_builtin_unreachable_pub = unreachable `pub` {$what} lint_builtin_unsafe_block = usage of an `unsafe` block -lint_builtin_unsafe_extern_block = usage of an `unsafe extern` block - lint_builtin_unsafe_impl = implementation of an `unsafe` trait -lint_builtin_unsafe_trait = declaration of an `unsafe` trait - lint_builtin_unstable_features = use of an unstable feature lint_builtin_unused_doc_comment = unused doc comment diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 83de7d3892314..d98b5a70efc4c 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -216,6 +216,8 @@ declare_lint! { /// #[no_mangle] /// #[link_section = ".example_section"] /// pub static VAR1: u32 = 1; + /// + /// unsafe fn unsafe_func() { } /// ``` /// /// {{produces}} @@ -226,6 +228,20 @@ declare_lint! { /// constructs (including, but not limited to `no_mangle`, `link_section` /// and `export_name` attributes) wrong usage of which causes undefined /// behavior. + /// + /// However, the declaration of an unsafe trait cannot cause any undefined + /// behavior — only its implementations can — that's why it is not covered + /// by this lint, despite the `unsafe` prefix. + /// + /// Along the same line, unsafe declaration of function (or method) cannot + /// cause any undefined behavior. Nevertheless, unsafe functions can contain + /// unsafe operations without `unsafe` block, as checked by the + /// [`unsafe_op_in_unsafe_fn`] lint. As a consequence, unless + /// `unsafe_op_in_unsafe_fn` is set to `forbid`, unsafe function declarations + /// *with a body* are covered by this lint. Declarations of unsafe functions + /// *without body* (e.g., in traits) are always allowed. + /// + /// [`unsafe_op_in_unsafe_fn`]: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#unsafe-op-in-unsafe-fn UNSAFE_CODE, Allow, "usage of `unsafe` code and other potentially unsound constructs", @@ -263,10 +279,6 @@ impl EarlyLintPass for UnsafeCode { fn check_item(&mut self, cx: &EarlyContext<'_>, it: &ast::Item) { match it.kind { - ast::ItemKind::Trait(box ast::Trait { safety: ast::Safety::Unsafe(_), .. }) => { - self.report_unsafe(cx, it.span, BuiltinUnsafe::UnsafeTrait); - } - ast::ItemKind::Impl(ast::Impl { of_trait: Some(box ast::TraitImplHeader { safety: ast::Safety::Unsafe(_), .. }), .. @@ -306,12 +318,6 @@ impl EarlyLintPass for UnsafeCode { self.report_unsafe(cx, it.span, BuiltinUnsafe::GlobalAsm); } - ast::ItemKind::ForeignMod(ForeignMod { safety, .. }) => { - if let Safety::Unsafe(_) = safety { - self.report_unsafe(cx, it.span, BuiltinUnsafe::UnsafeExternBlock); - } - } - ast::ItemKind::MacroDef(..) => { if let Some(hir::Attribute::Parsed(AttributeKind::AllowInternalUnsafe(span))) = AttributeParser::parse_limited( @@ -343,6 +349,9 @@ impl EarlyLintPass for UnsafeCode { } fn check_fn(&mut self, cx: &EarlyContext<'_>, fk: FnKind<'_>, span: Span, _: ast::NodeId) { + if cx.get_lint_level(UNSAFE_OP_IN_UNSAFE_FN).level == Level::Forbid { + return; + } if let FnKind::Fn( ctxt, _, @@ -356,7 +365,7 @@ impl EarlyLintPass for UnsafeCode { let decorator = match ctxt { FnCtxt::Foreign => return, FnCtxt::Free => BuiltinUnsafe::DeclUnsafeFn, - FnCtxt::Assoc(_) if body.is_none() => BuiltinUnsafe::DeclUnsafeMethod, + FnCtxt::Assoc(_) if body.is_none() => return, FnCtxt::Assoc(_) => BuiltinUnsafe::ImplUnsafeMethod, }; self.report_unsafe(cx, span, decorator); diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index c55f2b9dd6f24..7b56f82b0df05 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -126,10 +126,6 @@ pub(crate) enum BuiltinUnsafe { AllowInternalUnsafe, #[diag(lint_builtin_unsafe_block)] UnsafeBlock, - #[diag(lint_builtin_unsafe_extern_block)] - UnsafeExternBlock, - #[diag(lint_builtin_unsafe_trait)] - UnsafeTrait, #[diag(lint_builtin_unsafe_impl)] UnsafeImpl, #[diag(lint_builtin_no_mangle_fn)] @@ -158,8 +154,6 @@ pub(crate) enum BuiltinUnsafe { ExportNameMethod, #[diag(lint_builtin_decl_unsafe_fn)] DeclUnsafeFn, - #[diag(lint_builtin_decl_unsafe_method)] - DeclUnsafeMethod, #[diag(lint_builtin_impl_unsafe_method)] ImplUnsafeMethod, #[diag(lint_builtin_global_asm)] diff --git a/tests/ui/lint/lint-unsafe-code.rs b/tests/ui/lint/lint-unsafe-code.rs index b72e4c3a9e7fb..0ca102a059177 100644 --- a/tests/ui/lint/lint-unsafe-code.rs +++ b/tests/ui/lint/lint-unsafe-code.rs @@ -62,11 +62,11 @@ impl AssocFnTrait for AssocFnBar { } unsafe fn baz() {} //~ ERROR: declaration of an `unsafe` function -unsafe trait Foo {} //~ ERROR: declaration of an `unsafe` trait +unsafe trait Foo {} unsafe impl Foo for Bar {} //~ ERROR: implementation of an `unsafe` trait trait Baz { - unsafe fn baz(&self); //~ ERROR: declaration of an `unsafe` method + unsafe fn baz(&self); unsafe fn provided(&self) {} //~ ERROR: implementation of an `unsafe` method unsafe fn provided_override(&self) {} //~ ERROR: implementation of an `unsafe` method } @@ -95,7 +95,6 @@ impl Baz for Bar3 { unsafe fn provided_override(&self) {} //~ ERROR: implementation of an `unsafe` method } -#[allow(unsafe_code)] unsafe trait B { fn dummy(&self) {} } diff --git a/tests/ui/lint/lint-unsafe-code.stderr b/tests/ui/lint/lint-unsafe-code.stderr index 037f0a8323a75..19cf6751375b2 100644 --- a/tests/ui/lint/lint-unsafe-code.stderr +++ b/tests/ui/lint/lint-unsafe-code.stderr @@ -89,24 +89,12 @@ error: declaration of an `unsafe` function LL | unsafe fn baz() {} | ^^^^^^^^^^^^^^^^^^ -error: declaration of an `unsafe` trait - --> $DIR/lint-unsafe-code.rs:65:1 - | -LL | unsafe trait Foo {} - | ^^^^^^^^^^^^^^^^^^^ - error: implementation of an `unsafe` trait --> $DIR/lint-unsafe-code.rs:66:1 | LL | unsafe impl Foo for Bar {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: declaration of an `unsafe` method - --> $DIR/lint-unsafe-code.rs:69:5 - | -LL | unsafe fn baz(&self); - | ^^^^^^^^^^^^^^^^^^^^^ - error: implementation of an `unsafe` method --> $DIR/lint-unsafe-code.rs:70:5 | @@ -220,5 +208,5 @@ LL | unsafe_in_macro!() | = note: this error originates in the macro `unsafe_in_macro` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 28 previous errors +error: aborting due to 26 previous errors diff --git a/tests/ui/lint/unsafe_code/forbid-unsafe-op-in-unsafe-fn.rs b/tests/ui/lint/unsafe_code/forbid-unsafe-op-in-unsafe-fn.rs new file mode 100644 index 0000000000000..903f9a2a1d233 --- /dev/null +++ b/tests/ui/lint/unsafe_code/forbid-unsafe-op-in-unsafe-fn.rs @@ -0,0 +1,25 @@ +#![forbid(unsafe_op_in_unsafe_fn)] +#![allow(unused_unsafe)] +#![allow(dead_code)] +#![deny(unsafe_code)] + +struct Bar; + +unsafe fn baz() {} + +trait Baz { + unsafe fn baz(&self); + unsafe fn provided(&self) {} + unsafe fn provided_override(&self) {} +} + +impl Baz for Bar { + unsafe fn baz(&self) {} + unsafe fn provided_override(&self) {} +} + +unsafe fn unsafe_op_in_unsafe_fn() { + unsafe {} //~ ERROR: usage of an `unsafe` block +} + +fn main() {} diff --git a/tests/ui/lint/unsafe_code/forbid-unsafe-op-in-unsafe-fn.stderr b/tests/ui/lint/unsafe_code/forbid-unsafe-op-in-unsafe-fn.stderr new file mode 100644 index 0000000000000..927395649c223 --- /dev/null +++ b/tests/ui/lint/unsafe_code/forbid-unsafe-op-in-unsafe-fn.stderr @@ -0,0 +1,14 @@ +error: usage of an `unsafe` block + --> $DIR/forbid-unsafe-op-in-unsafe-fn.rs:22:5 + | +LL | unsafe {} + | ^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/forbid-unsafe-op-in-unsafe-fn.rs:4:9 + | +LL | #![deny(unsafe_code)] + | ^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/lint/unsafe_code/unsafe-extern-blocks.rs b/tests/ui/lint/unsafe_code/unsafe-extern-blocks.rs index c264fa1c8daf1..ac4bdb47e510d 100644 --- a/tests/ui/lint/unsafe_code/unsafe-extern-blocks.rs +++ b/tests/ui/lint/unsafe_code/unsafe-extern-blocks.rs @@ -1,13 +1,9 @@ +//@ check-pass + #![deny(unsafe_code)] -#[allow(unsafe_code)] unsafe extern "C" { fn foo(); } -unsafe extern "C" { - //~^ ERROR usage of an `unsafe extern` block [unsafe_code] - fn bar(); -} - fn main() {} diff --git a/tests/ui/lint/unsafe_code/unsafe-extern-blocks.stderr b/tests/ui/lint/unsafe_code/unsafe-extern-blocks.stderr deleted file mode 100644 index 6d3b064da344f..0000000000000 --- a/tests/ui/lint/unsafe_code/unsafe-extern-blocks.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error: usage of an `unsafe extern` block - --> $DIR/unsafe-extern-blocks.rs:8:1 - | -LL | / unsafe extern "C" { -LL | | -LL | | fn bar(); -LL | | } - | |_^ - | -note: the lint level is defined here - --> $DIR/unsafe-extern-blocks.rs:1:9 - | -LL | #![deny(unsafe_code)] - | ^^^^^^^^^^^ - -error: aborting due to 1 previous error - From 11a4c3197473aab6857cf08b46714c34ca9484ca Mon Sep 17 00:00:00 2001 From: Joseph Perez Date: Fri, 7 Nov 2025 16:10:36 +0100 Subject: [PATCH 2/3] Roll unsafe extern block coverage back See https://github.com/rust-lang/rust/pull/148651#issuecomment-3502965679 --- compiler/rustc_lint/messages.ftl | 2 ++ compiler/rustc_lint/src/builtin.rs | 6 ++++++ compiler/rustc_lint/src/lints.rs | 2 ++ library/alloc/src/vec/spec_extend.rs | 10 ++++++++++ .../ui/lint/unsafe_code/unsafe-extern-blocks.rs | 8 ++++++-- .../unsafe_code/unsafe-extern-blocks.stderr | 17 +++++++++++++++++ 6 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 tests/ui/lint/unsafe_code/unsafe-extern-blocks.stderr diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 0887d6ec63596..8b25dbfe4e4fa 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -166,6 +166,8 @@ lint_builtin_unreachable_pub = unreachable `pub` {$what} lint_builtin_unsafe_block = usage of an `unsafe` block +lint_builtin_unsafe_extern_block = usage of an `unsafe extern` block + lint_builtin_unsafe_impl = implementation of an `unsafe` trait lint_builtin_unstable_features = use of an unstable feature diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index d98b5a70efc4c..7638e5983f0fa 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -318,6 +318,12 @@ impl EarlyLintPass for UnsafeCode { self.report_unsafe(cx, it.span, BuiltinUnsafe::GlobalAsm); } + ast::ItemKind::ForeignMod(ForeignMod { safety, .. }) => { + if let Safety::Unsafe(_) = safety { + self.report_unsafe(cx, it.span, BuiltinUnsafe::UnsafeExternBlock); + } + } + ast::ItemKind::MacroDef(..) => { if let Some(hir::Attribute::Parsed(AttributeKind::AllowInternalUnsafe(span))) = AttributeParser::parse_limited( diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 7b56f82b0df05..e82c14aaa6e9d 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -126,6 +126,8 @@ pub(crate) enum BuiltinUnsafe { AllowInternalUnsafe, #[diag(lint_builtin_unsafe_block)] UnsafeBlock, + #[diag(lint_builtin_unsafe_extern_block)] + UnsafeExternBlock, #[diag(lint_builtin_unsafe_impl)] UnsafeImpl, #[diag(lint_builtin_no_mangle_fn)] diff --git a/library/alloc/src/vec/spec_extend.rs b/library/alloc/src/vec/spec_extend.rs index 7085bceef5baa..02dd44d6e9c88 100644 --- a/library/alloc/src/vec/spec_extend.rs +++ b/library/alloc/src/vec/spec_extend.rs @@ -1,4 +1,5 @@ use core::iter::TrustedLen; +use core::option::{self}; use core::slice::{self}; use super::{IntoIter, Vec}; @@ -36,6 +37,15 @@ impl SpecExtend> for Vec { } } +impl SpecExtend> for Vec { + #[track_caller] + fn spec_extend(&mut self, mut iterator: option::IntoIter) { + if let Some(element) = iterator.next() { + self.push(element); + } + } +} + impl<'a, T: 'a, I, A: Allocator> SpecExtend<&'a T, I> for Vec where I: Iterator, diff --git a/tests/ui/lint/unsafe_code/unsafe-extern-blocks.rs b/tests/ui/lint/unsafe_code/unsafe-extern-blocks.rs index ac4bdb47e510d..c264fa1c8daf1 100644 --- a/tests/ui/lint/unsafe_code/unsafe-extern-blocks.rs +++ b/tests/ui/lint/unsafe_code/unsafe-extern-blocks.rs @@ -1,9 +1,13 @@ -//@ check-pass - #![deny(unsafe_code)] +#[allow(unsafe_code)] unsafe extern "C" { fn foo(); } +unsafe extern "C" { + //~^ ERROR usage of an `unsafe extern` block [unsafe_code] + fn bar(); +} + fn main() {} diff --git a/tests/ui/lint/unsafe_code/unsafe-extern-blocks.stderr b/tests/ui/lint/unsafe_code/unsafe-extern-blocks.stderr new file mode 100644 index 0000000000000..6d3b064da344f --- /dev/null +++ b/tests/ui/lint/unsafe_code/unsafe-extern-blocks.stderr @@ -0,0 +1,17 @@ +error: usage of an `unsafe extern` block + --> $DIR/unsafe-extern-blocks.rs:8:1 + | +LL | / unsafe extern "C" { +LL | | +LL | | fn bar(); +LL | | } + | |_^ + | +note: the lint level is defined here + --> $DIR/unsafe-extern-blocks.rs:1:9 + | +LL | #![deny(unsafe_code)] + | ^^^^^^^^^^^ + +error: aborting due to 1 previous error + From e15274b25a747b96d7f861b149bd4a755753e88f Mon Sep 17 00:00:00 2001 From: Joseph Perez Date: Fri, 7 Nov 2025 16:14:49 +0100 Subject: [PATCH 3/3] Fix forgotten test stderr regeneration --- tests/ui/lint/lint-unsafe-code.rs | 14 +++++--------- tests/ui/lint/lint-unsafe-code.stderr | 8 ++++---- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/tests/ui/lint/lint-unsafe-code.rs b/tests/ui/lint/lint-unsafe-code.rs index 0ca102a059177..bf5bed73bebdf 100644 --- a/tests/ui/lint/lint-unsafe-code.rs +++ b/tests/ui/lint/lint-unsafe-code.rs @@ -95,32 +95,28 @@ impl Baz for Bar3 { unsafe fn provided_override(&self) {} //~ ERROR: implementation of an `unsafe` method } -unsafe trait B { - fn dummy(&self) {} -} - -trait C { +trait B { #[allow(unsafe_code)] unsafe fn baz(&self); unsafe fn provided(&self) {} //~ ERROR: implementation of an `unsafe` method } -impl C for Bar { +impl B for Bar { #[allow(unsafe_code)] unsafe fn baz(&self) {} unsafe fn provided(&self) {} //~ ERROR: implementation of an `unsafe` method } -impl C for Bar2 { +impl B for Bar2 { unsafe fn baz(&self) {} //~ ERROR: implementation of an `unsafe` method } -trait D { +trait C { #[allow(unsafe_code)] unsafe fn unsafe_provided(&self) {} } -impl D for Bar {} +impl C for Bar {} fn main() { unsafe {} //~ ERROR: usage of an `unsafe` block diff --git a/tests/ui/lint/lint-unsafe-code.stderr b/tests/ui/lint/lint-unsafe-code.stderr index 19cf6751375b2..17179597d7c23 100644 --- a/tests/ui/lint/lint-unsafe-code.stderr +++ b/tests/ui/lint/lint-unsafe-code.stderr @@ -126,25 +126,25 @@ LL | unsafe fn provided_override(&self) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: implementation of an `unsafe` method - --> $DIR/lint-unsafe-code.rs:106:5 + --> $DIR/lint-unsafe-code.rs:101:5 | LL | unsafe fn provided(&self) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: implementation of an `unsafe` method - --> $DIR/lint-unsafe-code.rs:112:5 + --> $DIR/lint-unsafe-code.rs:107:5 | LL | unsafe fn provided(&self) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: implementation of an `unsafe` method - --> $DIR/lint-unsafe-code.rs:116:5 + --> $DIR/lint-unsafe-code.rs:111:5 | LL | unsafe fn baz(&self) {} | ^^^^^^^^^^^^^^^^^^^^^^^ error: usage of an `unsafe` block - --> $DIR/lint-unsafe-code.rs:127:5 + --> $DIR/lint-unsafe-code.rs:122:5 | LL | unsafe {} | ^^^^^^^^^