use hir::db::ExpandDatabase; use hir::{UnsafeLint, UnsafetyReason}; use ide_db::text_edit::TextEdit; use ide_db::{assists::Assist, source_change::SourceChange}; use syntax::{AstNode, match_ast}; use syntax::{SyntaxNode, ast}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, fix}; // Diagnostic: missing-unsafe // // This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block. pub(crate) fn missing_unsafe(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Diagnostic { let code = match d.lint { UnsafeLint::HardError => DiagnosticCode::RustcHardError("E0133"), UnsafeLint::UnsafeOpInUnsafeFn => DiagnosticCode::RustcLint("unsafe_op_in_unsafe_fn"), UnsafeLint::DeprecatedSafe2024 => DiagnosticCode::RustcLint("deprecated_safe_2024"), }; let operation = display_unsafety_reason(d.reason); Diagnostic::new_with_syntax_node_ptr( ctx, code, format!("{operation} is unsafe and requires an unsafe function or block"), d.node.map(|it| it.into()), ) .stable() .with_fixes(fixes(ctx, d)) } fn display_unsafety_reason(reason: UnsafetyReason) -> &'static str { match reason { UnsafetyReason::UnionField => "access to union field", UnsafetyReason::UnsafeFnCall => "call to unsafe function", UnsafetyReason::InlineAsm => "use of inline assembly", UnsafetyReason::RawPtrDeref => "dereference of raw pointer", UnsafetyReason::MutableStatic => "use of mutable static", UnsafetyReason::ExternStatic => "use of extern static", } } fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Option> { // The fixit will not work correctly for macro expansions, so we don't offer it in that case. if d.node.file_id.is_macro() { return None; } let root = ctx.sema.db.parse_or_expand(d.node.file_id); let node = d.node.value.to_node(&root); let expr = node.syntax().ancestors().find_map(ast::Expr::cast)?; let node_to_add_unsafe_block = pick_best_node_to_add_unsafe_block(&expr)?; let mut replacement = format!("unsafe {{ {} }}", node_to_add_unsafe_block.text()); if let Some(expr) = ast::Expr::cast(node_to_add_unsafe_block.clone()) && needs_parentheses(&expr) { replacement = format!("({replacement})"); } let edit = TextEdit::replace(node_to_add_unsafe_block.text_range(), replacement); let source_change = SourceChange::from_text_edit( d.node.file_id.original_file(ctx.sema.db).file_id(ctx.sema.db), edit, ); Some(vec![fix("add_unsafe", "Add unsafe block", source_change, expr.syntax().text_range())]) } // Pick the first ancestor expression of the unsafe `expr` that is not a // receiver of a method call, a field access, the left-hand side of an // assignment, or a reference. As all of those cases would incur a forced move // if wrapped which might not be wanted. That is: // - `unsafe_expr.foo` -> `unsafe { unsafe_expr.foo }` // - `unsafe_expr.foo.bar` -> `unsafe { unsafe_expr.foo.bar }` // - `unsafe_expr.foo()` -> `unsafe { unsafe_expr.foo() }` // - `unsafe_expr.foo.bar()` -> `unsafe { unsafe_expr.foo.bar() }` // - `unsafe_expr += 1` -> `unsafe { unsafe_expr += 1 }` // - `&unsafe_expr` -> `unsafe { &unsafe_expr }` // - `&&unsafe_expr` -> `unsafe { &&unsafe_expr }` fn pick_best_node_to_add_unsafe_block(unsafe_expr: &ast::Expr) -> Option { // The `unsafe_expr` might be: // - `ast::CallExpr`: call an unsafe function // - `ast::MethodCallExpr`: call an unsafe method // - `ast::PrefixExpr`: dereference a raw pointer // - `ast::PathExpr`: access a static mut variable for (node, parent) in unsafe_expr.syntax().ancestors().zip(unsafe_expr.syntax().ancestors().skip(1)) { match_ast! { match parent { // If the `parent` is a `MethodCallExpr`, that means the `node` // is the receiver of the method call, because only the receiver // can be a direct child of a method call. The method name // itself is not an expression but a `NameRef`, and an argument // is a direct child of an `ArgList`. ast::MethodCallExpr(_) => continue, ast::FieldExpr(_) => continue, ast::RefExpr(_) => continue, ast::BinExpr(it) => { // Check if the `node` is the left-hand side of an // assignment, if so, we don't want to wrap it in an unsafe // block, e.g. `unsafe_expr += 1` let is_left_hand_side_of_assignment = { if let Some(ast::BinaryOp::Assignment { .. }) = it.op_kind() { it.lhs().map(|lhs| lhs.syntax().text_range().contains_range(node.text_range())).unwrap_or(false) } else { false } }; if !is_left_hand_side_of_assignment { return Some(node); } }, _ => { return Some(node); } } } } None } fn needs_parentheses(expr: &ast::Expr) -> bool { let node = expr.syntax(); node.ancestors() .skip(1) .take_while(|it| it.text_range().start() == node.text_range().start()) .map_while(ast::Expr::cast) .last() .and_then(|it| Some(it.syntax().parent()?.kind())) .is_some_and(|kind| ast::ExprStmt::can_cast(kind) || ast::StmtList::can_cast(kind)) } #[cfg(test)] mod tests { use crate::tests::{check_diagnostics, check_fix, check_no_fix}; #[test] fn missing_unsafe_diagnostic_with_raw_ptr() { check_diagnostics( r#" //- minicore: sized fn main() { let x = &5_usize as *const usize; unsafe { let _y = *x; } let _z = *x; } //^^💡 error: dereference of raw pointer is unsafe and requires an unsafe function or block "#, ) } #[test] fn missing_unsafe_diagnostic_with_unsafe_call() { check_diagnostics( r#" //- minicore: sized struct HasUnsafe; impl HasUnsafe { unsafe fn unsafe_fn(&self) { let x = &5_usize as *const usize; let _y = unsafe {*x}; } } unsafe fn unsafe_fn() { let x = &5_usize as *const usize; let _y = unsafe {*x}; } fn main() { unsafe_fn(); //^^^^^^^^^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block HasUnsafe.unsafe_fn(); //^^^^^^^^^^^^^^^^^^^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block unsafe { unsafe_fn(); HasUnsafe.unsafe_fn(); } } "#, ); } #[test] fn missing_unsafe_diagnostic_with_static_mut() { check_diagnostics( r#" //- minicore: copy struct Ty { a: u8, } static mut STATIC_MUT: Ty = Ty { a: 0 }; fn main() { let _x = STATIC_MUT.a; //^^^^^^^^^^💡 error: use of mutable static is unsafe and requires an unsafe function or block unsafe { let _x = STATIC_MUT.a; } } "#, ); } #[test] fn missing_unsafe_diagnostic_with_extern_static() { check_diagnostics( r#" //- minicore: copy extern "C" { static EXTERN: i32; static mut EXTERN_MUT: i32; } fn main() { let _x = EXTERN; //^^^^^^💡 error: use of extern static is unsafe and requires an unsafe function or block let _x = EXTERN_MUT; //^^^^^^^^^^💡 error: use of mutable static is unsafe and requires an unsafe function or block unsafe { let _x = EXTERN; let _x = EXTERN_MUT; } } "#, ); } #[test] fn no_unsafe_diagnostic_with_addr_of_static() { check_diagnostics( r#" //- minicore: copy, addr_of use core::ptr::{addr_of, addr_of_mut}; extern "C" { static EXTERN: i32; static mut EXTERN_MUT: i32; } static mut STATIC_MUT: i32 = 0; fn main() { let _x = addr_of!(EXTERN); let _x = addr_of!(EXTERN_MUT); let _x = addr_of!(STATIC_MUT); let _x = addr_of_mut!(EXTERN_MUT); let _x = addr_of_mut!(STATIC_MUT); } "#, ); } #[test] fn no_missing_unsafe_diagnostic_with_safe_intrinsic() { check_diagnostics( r#" #[rustc_intrinsic] pub fn bitreverse(x: u32) -> u32; // Safe intrinsic #[rustc_intrinsic] pub unsafe fn floorf32(x: f32) -> f32; // Unsafe intrinsic fn main() { let _ = bitreverse(12); let _ = floorf32(12.0); //^^^^^^^^^^^^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block } "#, ); } #[test] fn no_missing_unsafe_diagnostic_with_legacy_safe_intrinsic() { check_diagnostics( r#" extern "rust-intrinsic" { #[rustc_safe_intrinsic] pub fn bitreverse(x: u32) -> u32; // Safe intrinsic pub fn floorf32(x: f32) -> f32; // Unsafe intrinsic } fn main() { let _ = bitreverse(12); let _ = floorf32(12.0); //^^^^^^^^^^^^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block } "#, ); } #[test] fn no_missing_unsafe_diagnostic_with_deprecated_safe_2024() { check_diagnostics( r#" #[rustc_deprecated_safe_2024] fn set_var() {} #[rustc_deprecated_safe_2024(audit_that = "something")] fn set_var2() {} fn main() { set_var(); set_var2(); } "#, ); } #[test] fn add_unsafe_block_when_dereferencing_a_raw_pointer() { check_fix( r#" //- minicore: sized fn main() { let x = &5_usize as *const usize; let _z = *x$0; } "#, r#" fn main() { let x = &5_usize as *const usize; let _z = unsafe { *x }; } "#, ); } #[test] fn add_unsafe_block_when_calling_unsafe_function() { check_fix( r#" //- minicore: sized unsafe fn func() { let x = &5_usize as *const usize; let z = *x; } fn main() { func$0(); } "#, r#" unsafe fn func() { let x = &5_usize as *const usize; let z = *x; } fn main() { unsafe { func() }; } "#, ) } #[test] fn add_unsafe_block_when_calling_unsafe_method() { check_fix( r#" //- minicore: sized struct S(usize); impl S { unsafe fn func(&self) { let x = &self.0 as *const usize; let _z = unsafe { *x }; } } fn main() { let s = S(5); s.func$0(); } "#, r#" struct S(usize); impl S { unsafe fn func(&self) { let x = &self.0 as *const usize; let _z = unsafe { *x }; } } fn main() { let s = S(5); unsafe { s.func() }; } "#, ) } #[test] fn add_unsafe_block_when_accessing_mutable_static() { check_fix( r#" //- minicore: copy struct Ty { a: u8, } static mut STATIC_MUT: Ty = Ty { a: 0 }; fn main() { let _x = STATIC_MUT$0.a; } "#, r#" struct Ty { a: u8, } static mut STATIC_MUT: Ty = Ty { a: 0 }; fn main() { let _x = unsafe { STATIC_MUT.a }; } "#, ) } #[test] fn add_unsafe_block_when_calling_unsafe_intrinsic() { check_fix( r#" extern "rust-intrinsic" { pub fn floorf32(x: f32) -> f32; } fn main() { let _ = floorf32$0(12.0); } "#, r#" extern "rust-intrinsic" { pub fn floorf32(x: f32) -> f32; } fn main() { let _ = unsafe { floorf32(12.0) }; } "#, ) } #[test] fn unsafe_expr_as_a_receiver_of_a_method_call() { check_fix( r#" unsafe fn foo() -> String { "string".to_string() } fn main() { foo$0().len(); } "#, r#" unsafe fn foo() -> String { "string".to_string() } fn main() { unsafe { foo().len() }; } "#, ) } #[test] fn raw_deref_on_union_field() { check_diagnostics( r#" fn main() { union U { a: u8 } let x = U { a: 3 }; let a = &raw mut x.a; union U1 { a: u8 } let x = U1 { a: 3 }; let a = x.a; // ^^^ 💡 error: access to union field is unsafe and requires an unsafe function or block let b = &raw const x.a; let tmp = Vec::from([1, 2, 3]); let c = &raw const tmp[x.a]; // ^^^ 💡 error: access to union field is unsafe and requires an unsafe function or block union URef { p: &'static mut i32, } fn deref_union_field(u: URef) { // Not an assignment but an access to the union field! *(u.p) = 13; // ^^^ 💡 error: access to union field is unsafe and requires an unsafe function or block } } "#, ) } #[test] fn unsafe_expr_as_an_argument_of_a_method_call() { check_fix( r#" static mut STATIC_MUT: u8 = 0; fn main() { let mut v = vec![]; v.push(STATIC_MUT$0); } "#, r#" static mut STATIC_MUT: u8 = 0; fn main() { let mut v = vec![]; v.push(unsafe { STATIC_MUT }); } "#, ) } #[test] fn unsafe_expr_as_left_hand_side_of_assignment() { check_fix( r#" static mut STATIC_MUT: u8 = 0; fn main() { STATIC_MUT$0 = 1; } "#, r#" static mut STATIC_MUT: u8 = 0; fn main() { unsafe { STATIC_MUT = 1 }; } "#, ) } #[test] fn unsafe_expr_as_right_hand_side_of_assignment() { check_fix( r#" //- minicore: copy static mut STATIC_MUT: u8 = 0; fn main() { let _x; _x = STATIC_MUT$0; } "#, r#" static mut STATIC_MUT: u8 = 0; fn main() { let _x; _x = unsafe { STATIC_MUT }; } "#, ) } #[test] fn unsafe_expr_in_binary_plus() { check_fix( r#" //- minicore: copy static mut STATIC_MUT: u8 = 0; fn main() { let _x = STATIC_MUT$0 + 1; } "#, r#" static mut STATIC_MUT: u8 = 0; fn main() { let _x = unsafe { STATIC_MUT } + 1; } "#, ) } #[test] fn needs_parentheses_for_unambiguous() { check_fix( r#" //- minicore: copy static mut STATIC_MUT: u8 = 0; fn foo() -> u8 { STATIC_MUT$0 * 2 } "#, r#" static mut STATIC_MUT: u8 = 0; fn foo() -> u8 { (unsafe { STATIC_MUT }) * 2 } "#, ) } #[test] fn ref_to_unsafe_expr() { check_fix( r#" static mut STATIC_MUT: u8 = 0; fn main() { let _x = &STATIC_MUT$0; } "#, r#" static mut STATIC_MUT: u8 = 0; fn main() { let _x = unsafe { &STATIC_MUT }; } "#, ) } #[test] fn ref_ref_to_unsafe_expr() { check_fix( r#" static mut STATIC_MUT: u8 = 0; fn main() { let _x = &&STATIC_MUT$0; } "#, r#" static mut STATIC_MUT: u8 = 0; fn main() { let _x = unsafe { &&STATIC_MUT }; } "#, ) } #[test] fn unsafe_expr_in_macro_call() { check_no_fix( r#" unsafe fn foo() -> u8 { 0 } fn main() { let x = format!("foo: {}", foo$0()); } "#, ) } #[test] fn rustc_deprecated_safe_2024() { check_diagnostics( r#" //- /ed2021.rs crate:ed2021 edition:2021 #[rustc_deprecated_safe_2024] unsafe fn deprecated_safe() -> u8 { 0 } //- /ed2024.rs crate:ed2024 edition:2024 #[rustc_deprecated_safe_2024] unsafe fn deprecated_safe() -> u8 { 0 } //- /dep1.rs crate:dep1 deps:ed2021,ed2024 edition:2021 fn main() { ed2021::deprecated_safe(); ed2024::deprecated_safe(); } //- /dep2.rs crate:dep2 deps:ed2021,ed2024 edition:2024 fn main() { ed2021::deprecated_safe(); // ^^^^^^^^^^^^^^^^^^^^^^^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block ed2024::deprecated_safe(); // ^^^^^^^^^^^^^^^^^^^^^^^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block } //- /dep3.rs crate:dep3 deps:ed2021,ed2024 edition:2021 #![warn(deprecated_safe)] fn main() { ed2021::deprecated_safe(); // ^^^^^^^^^^^^^^^^^^^^^^^^^💡 warn: call to unsafe function is unsafe and requires an unsafe function or block ed2024::deprecated_safe(); // ^^^^^^^^^^^^^^^^^^^^^^^^^💡 warn: call to unsafe function is unsafe and requires an unsafe function or block } "#, ) } #[test] fn orphan_unsafe_format_args() { // Checks that we don't place orphan arguments for formatting under an unsafe block. check_diagnostics( r#" //- minicore: fmt_before_1_89_0 fn foo() { let p = 0xDEADBEEF as *const i32; format_args!("", *p); // ^^ error: dereference of raw pointer is unsafe and requires an unsafe function or block } "#, ); check_diagnostics( r#" //- minicore: fmt fn foo() { let p = 0xDEADBEEF as *const i32; format_args!("", *p); // ^^ error: dereference of raw pointer is unsafe and requires an unsafe function or block } "#, ); } #[test] fn unsafe_op_in_unsafe_fn_allowed_by_default_in_edition_2021() { check_diagnostics( r#" //- /lib.rs crate:foo edition:2021 unsafe fn foo(p: *mut i32) { *p = 123; } "#, ); check_diagnostics( r#" //- /lib.rs crate:foo edition:2021 #![deny(warnings)] unsafe fn foo(p: *mut i32) { *p = 123; } "#, ); } #[test] fn unsafe_op_in_unsafe_fn_warn_by_default_in_edition_2024() { check_diagnostics( r#" //- /lib.rs crate:foo edition:2024 unsafe fn foo(p: *mut i32) { *p = 123; //^^💡 warn: dereference of raw pointer is unsafe and requires an unsafe function or block } "#, ); check_diagnostics( r#" //- /lib.rs crate:foo edition:2024 #![deny(warnings)] unsafe fn foo(p: *mut i32) { *p = 123; //^^💡 error: dereference of raw pointer is unsafe and requires an unsafe function or block } "#, ); } #[test] fn unsafe_op_in_unsafe_fn() { check_diagnostics( r#" #![warn(unsafe_op_in_unsafe_fn)] unsafe fn foo(p: *mut i32) { *p = 123; //^^💡 warn: dereference of raw pointer is unsafe and requires an unsafe function or block } "#, ) } #[test] fn no_unsafe_diagnostic_with_safe_kw() { check_diagnostics( r#" unsafe extern { pub safe fn f(); pub unsafe fn g(); pub fn h(); pub safe static S1: i32; pub unsafe static S2: i32; pub static S3: i32; } fn main() { f(); g(); //^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block h(); //^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block let _ = S1; let _ = S2; //^^💡 error: use of extern static is unsafe and requires an unsafe function or block let _ = S3; //^^💡 error: use of extern static is unsafe and requires an unsafe function or block } "#, ); } #[test] fn no_unsafe_diagnostic_when_destructuring_union_with_wildcard() { check_diagnostics( r#" union Union { field: i32 } fn foo(v: &Union) { let Union { field: _ } = v; let Union { field: _ | _ } = v; Union { field: _ } = *v; } "#, ); } #[test] fn union_destructuring() { check_diagnostics( r#" union Union { field: u8 } fn foo(v @ Union { field: _field }: &Union) { // ^^^^^^ error: access to union field is unsafe and requires an unsafe function or block let Union { mut field } = v; // ^^^^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block let Union { field: 0..=255 } = v; // ^^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block let Union { field: 0 // ^💡 error: access to union field is unsafe and requires an unsafe function or block | 1..=255 } = v; // ^^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block Union { field } = *v; // ^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block match v { Union { field: _field } => {} // ^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block } if let Union { field: _field } = v {} // ^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block (|&Union { field }| { _ = field; })(v); // ^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block } "#, ); } #[test] fn union_field_access() { check_diagnostics( r#" union Union { field: u8 } fn foo(v: &Union) { v.field; // ^^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block } "#, ); } #[test] fn inline_asm() { check_diagnostics( r#" //- minicore: asm fn foo() { core::arch::asm!(""); // ^^^^ error: use of inline assembly is unsafe and requires an unsafe function or block } "#, ); } #[test] fn unsafe_op_in_unsafe_fn_dismissed_in_signature() { check_diagnostics( r#" #![warn(unsafe_op_in_unsafe_fn)] union Union { field: u32 } unsafe fn foo(Union { field: _field }: Union) {} "#, ) } #[test] fn union_assignment_allowed() { check_diagnostics( r#" union Union { field: u32 } fn foo(mut v: Union) { v.field = 123; (v.field,) = (123,); *&mut v.field = 123; // ^^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block } struct Struct { field: u32 } union Union2 { field: Struct } fn bar(mut v: Union2) { v.field.field = 123; } "#, ) } #[test] fn raw_ref_reborrow_is_safe() { check_diagnostics( r#" fn main() { let ptr: *mut i32; let _addr = &raw const *ptr; let local = 1; let ptr = &local as *const i32; let _addr = &raw const *ptr; } "#, ) } #[test] fn target_feature() { check_diagnostics( r#" #[target_feature(enable = "avx")] fn foo() {} #[target_feature(enable = "avx2")] fn bar() { foo(); } fn baz() { foo(); // ^^^^^ 💡 error: call to unsafe function is unsafe and requires an unsafe function or block } "#, ); } #[test] fn unsafe_fn_ptr_call() { check_diagnostics( r#" fn f(it: unsafe fn()){ it(); // ^^^^ 💡 error: call to unsafe function is unsafe and requires an unsafe function or block } "#, ); } #[test] fn unsafe_call_in_const_expr() { check_diagnostics( r#" unsafe fn f() {} fn main() { const { f(); }; // ^^^ 💡 error: call to unsafe function is unsafe and requires an unsafe function or block } "#, ); } #[test] fn asm_label() { check_diagnostics( r#" //- minicore: asm fn foo() { unsafe { core::arch::asm!( "jmp {}", label { let p = 0xDEADBEAF as *mut u8; *p = 3; // ^^ error: dereference of raw pointer is unsafe and requires an unsafe function or block }, ); } } "#, ); } #[test] fn regression_19823() { check_diagnostics( r#" pub trait FooTrait { unsafe fn method1(); unsafe fn method2(); } unsafe fn some_unsafe_fn() {} macro_rules! impl_foo { () => { unsafe fn method1() { some_unsafe_fn(); } unsafe fn method2() { some_unsafe_fn(); } }; } pub struct S1; #[allow(unsafe_op_in_unsafe_fn)] impl FooTrait for S1 { unsafe fn method1() { some_unsafe_fn(); } unsafe fn method2() { some_unsafe_fn(); } } pub struct S2; #[allow(unsafe_op_in_unsafe_fn)] impl FooTrait for S2 { impl_foo!(); } "#, ); } #[test] fn no_false_positive_on_format_args_since_1_89_0() { check_diagnostics( r#" //- minicore: fmt fn test() { let foo = 10; let bar = true; let _x = format_args!("{} {0} {} {last}", foo, bar, last = "!"); } "#, ); } #[test] fn naked_asm_is_safe() { check_diagnostics( r#" #[rustc_builtin_macro] macro_rules! naked_asm { () => {} } #[unsafe(naked)] extern "C" fn naked() { naked_asm!(""); } "#, ); } #[test] fn target_feature_safe_on_wasm() { check_diagnostics( r#" //- target_arch: wasm32 #[target_feature(enable = "simd128")] fn requires_target_feature() {} fn main() { requires_target_feature(); } "#, ); } #[test] fn multiple_target_feature_enable() { check_diagnostics( r#" #[target_feature(enable = "avx2,fma")] fn foo() {} #[target_feature(enable = "avx2", enable = "fma")] fn bar() { foo(); } "#, ); } }