From 327b8c9f9374e73e4a2d05f87e91f34557f19ce9 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 4 Dec 2024 08:05:23 +0200 Subject: [PATCH] Extend reported unsafe operations We add union fields access (in both expressions and patterns) and inline assembly. That completes the unsafe check (there are some other unsafe things but they are unstable), and so also opens the door to reporting unused unsafe without annoying people about their not-unused unsafe blocks. --- crates/hir-def/src/body.rs | 139 +++++++- crates/hir-ty/src/diagnostics.rs | 2 +- crates/hir-ty/src/diagnostics/unsafe_check.rs | 319 +++++++++++++----- crates/hir/src/diagnostics.rs | 7 +- crates/hir/src/lib.rs | 9 +- crates/hir/src/source_analyzer.rs | 6 +- .../src/handlers/missing_unsafe.rs | 154 +++++++-- 7 files changed, 511 insertions(+), 125 deletions(-) diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs index 5a386f6cf8..d4a1120908 100644 --- a/crates/hir-def/src/body.rs +++ b/crates/hir-def/src/body.rs @@ -408,7 +408,8 @@ impl Body { f(else_branch); } } - Expr::Let { expr, .. } => { + Expr::Let { expr, pat } => { + self.walk_exprs_in_pat(*pat, &mut f); f(*expr); } Expr::Block { statements, tail, .. } @@ -442,6 +443,137 @@ impl Body { f(*receiver); args.iter().copied().for_each(f); } + Expr::Match { expr, arms } => { + f(*expr); + arms.iter().for_each(|arm| { + f(arm.expr); + self.walk_exprs_in_pat(arm.pat, &mut f); + }); + } + Expr::Break { expr, .. } + | Expr::Return { expr } + | Expr::Yield { expr } + | Expr::Yeet { expr } => { + if let &Some(expr) = expr { + f(expr); + } + } + Expr::Become { expr } => f(*expr), + Expr::RecordLit { fields, spread, .. } => { + for field in fields.iter() { + f(field.expr); + } + if let &Some(expr) = spread { + f(expr); + } + } + Expr::Closure { body, .. } => { + f(*body); + } + Expr::BinaryOp { lhs, rhs, .. } => { + f(*lhs); + f(*rhs); + } + Expr::Range { lhs, rhs, .. } => { + if let &Some(lhs) = rhs { + f(lhs); + } + if let &Some(rhs) = lhs { + f(rhs); + } + } + Expr::Index { base, index, .. } => { + f(*base); + f(*index); + } + Expr::Field { expr, .. } + | Expr::Await { expr } + | Expr::Cast { expr, .. } + | Expr::Ref { expr, .. } + | Expr::UnaryOp { expr, .. } + | Expr::Box { expr } => { + f(*expr); + } + Expr::Tuple { exprs, .. } => exprs.iter().copied().for_each(f), + Expr::Array(a) => match a { + Array::ElementList { elements, .. } => elements.iter().copied().for_each(f), + Array::Repeat { initializer, repeat } => { + f(*initializer); + f(*repeat) + } + }, + &Expr::Assignment { target, value } => { + self.walk_exprs_in_pat(target, &mut f); + f(value); + } + } + } + + pub fn walk_child_exprs_without_pats(&self, expr_id: ExprId, mut f: impl FnMut(ExprId)) { + let expr = &self[expr_id]; + match expr { + Expr::Continue { .. } + | Expr::Const(_) + | Expr::Missing + | Expr::Path(_) + | Expr::OffsetOf(_) + | Expr::Literal(_) + | Expr::Underscore => {} + Expr::InlineAsm(it) => it.operands.iter().for_each(|(_, op)| match op { + AsmOperand::In { expr, .. } + | AsmOperand::Out { expr: Some(expr), .. } + | AsmOperand::InOut { expr, .. } => f(*expr), + AsmOperand::SplitInOut { in_expr, out_expr, .. } => { + f(*in_expr); + if let Some(out_expr) = out_expr { + f(*out_expr); + } + } + AsmOperand::Out { expr: None, .. } + | AsmOperand::Const(_) + | AsmOperand::Label(_) + | AsmOperand::Sym(_) => (), + }), + Expr::If { condition, then_branch, else_branch } => { + f(*condition); + f(*then_branch); + if let &Some(else_branch) = else_branch { + f(else_branch); + } + } + Expr::Let { expr, .. } => { + f(*expr); + } + Expr::Block { statements, tail, .. } + | Expr::Unsafe { statements, tail, .. } + | Expr::Async { statements, tail, .. } => { + for stmt in statements.iter() { + match stmt { + Statement::Let { initializer, else_branch, .. } => { + if let &Some(expr) = initializer { + f(expr); + } + if let &Some(expr) = else_branch { + f(expr); + } + } + Statement::Expr { expr: expression, .. } => f(*expression), + Statement::Item(_) => (), + } + } + if let &Some(expr) = tail { + f(expr); + } + } + Expr::Loop { body, .. } => f(*body), + Expr::Call { callee, args, .. } => { + f(*callee); + args.iter().copied().for_each(f); + } + Expr::MethodCall { receiver, args, .. } => { + f(*receiver); + args.iter().copied().for_each(f); + } Expr::Match { expr, arms } => { f(*expr); arms.iter().map(|arm| arm.expr).for_each(f); @@ -498,10 +630,7 @@ impl Body { f(*repeat) } }, - &Expr::Assignment { target, value } => { - self.walk_exprs_in_pat(target, &mut f); - f(value); - } + &Expr::Assignment { target: _, value } => f(value), } } diff --git a/crates/hir-ty/src/diagnostics.rs b/crates/hir-ty/src/diagnostics.rs index af4d2c9fc0..30c02a2936 100644 --- a/crates/hir-ty/src/diagnostics.rs +++ b/crates/hir-ty/src/diagnostics.rs @@ -9,5 +9,5 @@ pub use crate::diagnostics::{ expr::{ record_literal_missing_fields, record_pattern_missing_fields, BodyValidationDiagnostic, }, - unsafe_check::{missing_unsafe, unsafe_expressions, UnsafeExpr}, + unsafe_check::{missing_unsafe, unsafe_expressions, InsideUnsafeBlock, UnsafetyReason}, }; diff --git a/crates/hir-ty/src/diagnostics/unsafe_check.rs b/crates/hir-ty/src/diagnostics/unsafe_check.rs index c7f7fb7ad3..193aaa52c2 100644 --- a/crates/hir-ty/src/diagnostics/unsafe_check.rs +++ b/crates/hir-ty/src/diagnostics/unsafe_check.rs @@ -1,12 +1,16 @@ //! Provides validations for unsafe code. Currently checks if unsafe functions are missing //! unsafe blocks. +use std::mem; + +use either::Either; use hir_def::{ body::Body, - hir::{Expr, ExprId, ExprOrPatId, Pat, UnaryOp}, - resolver::{resolver_for_expr, ResolveValueResult, Resolver, ValueNs}, + hir::{Expr, ExprId, ExprOrPatId, Pat, PatId, Statement, UnaryOp}, + path::Path, + resolver::{HasResolver, ResolveValueResult, Resolver, ValueNs}, type_ref::Rawness, - DefWithBodyId, + AdtId, DefWithBodyId, FieldId, VariantId, }; use crate::{ @@ -16,7 +20,10 @@ use crate::{ /// Returns `(unsafe_exprs, fn_is_unsafe)`. /// /// If `fn_is_unsafe` is false, `unsafe_exprs` are hard errors. If true, they're `unsafe_op_in_unsafe_fn`. -pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> (Vec, bool) { +pub fn missing_unsafe( + db: &dyn HirDatabase, + def: DefWithBodyId, +) -> (Vec<(ExprOrPatId, UnsafetyReason)>, bool) { let _p = tracing::info_span!("missing_unsafe").entered(); let mut res = Vec::new(); @@ -30,111 +37,243 @@ pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> (Vec { + db: &'a dyn HirDatabase, + infer: &'a InferenceResult, + body: &'a Body, + resolver: Resolver, def: DefWithBodyId, - current: ExprId, - inside_unsafe_block: bool, - unsafe_expr_cb: &mut dyn FnMut(UnsafeExpr), -) { - let mut mark_unsafe_path = |path, node| { - let g = resolver.update_to_inner_scope(db.upcast(), def, current); - let hygiene = body.expr_or_pat_path_hygiene(node); - let value_or_partial = resolver.resolve_path_in_value_ns(db.upcast(), path, hygiene); - if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id), _)) = value_or_partial { - let static_data = db.static_data(id); - if static_data.mutable || (static_data.is_extern && !static_data.has_safe_kw) { - unsafe_expr_cb(UnsafeExpr { node, inside_unsafe_block }); - } - } - resolver.reset_to_guard(g); - }; + inside_unsafe_block: InsideUnsafeBlock, + inside_assignment: bool, + inside_union_destructure: bool, + unsafe_expr_cb: &'a mut dyn FnMut(ExprOrPatId, InsideUnsafeBlock, UnsafetyReason), +} - let expr = &body.exprs[current]; - match expr { - &Expr::Call { callee, .. } => { - if let Some(func) = infer[callee].as_fn_def(db) { - if is_fn_unsafe_to_call(db, func) { - unsafe_expr_cb(UnsafeExpr { node: current.into(), inside_unsafe_block }); - } - } +impl<'a> UnsafeVisitor<'a> { + fn new( + db: &'a dyn HirDatabase, + infer: &'a InferenceResult, + body: &'a Body, + def: DefWithBodyId, + unsafe_expr_cb: &'a mut dyn FnMut(ExprOrPatId, InsideUnsafeBlock, UnsafetyReason), + ) -> Self { + let resolver = def.resolver(db.upcast()); + Self { + db, + infer, + body, + resolver, + def, + inside_unsafe_block: InsideUnsafeBlock::No, + inside_assignment: false, + inside_union_destructure: false, + unsafe_expr_cb, } - Expr::Path(path) => mark_unsafe_path(path, current.into()), - Expr::Ref { expr, rawness: Rawness::RawPtr, mutability: _ } => { - if let Expr::Path(_) = body.exprs[*expr] { - // Do not report unsafe for `addr_of[_mut]!(EXTERN_OR_MUT_STATIC)`, - // see https://github.com/rust-lang/rust/pull/125834. - return; - } - } - Expr::MethodCall { .. } => { - if infer - .method_resolution(current) - .map(|(func, _)| is_fn_unsafe_to_call(db, func)) - .unwrap_or(false) - { - unsafe_expr_cb(UnsafeExpr { node: current.into(), inside_unsafe_block }); - } - } - Expr::UnaryOp { expr, op: UnaryOp::Deref } => { - if let TyKind::Raw(..) = &infer[*expr].kind(Interner) { - unsafe_expr_cb(UnsafeExpr { node: current.into(), inside_unsafe_block }); - } - } - Expr::Unsafe { .. } => { - return body.walk_child_exprs(current, |child| { - walk_unsafe(db, infer, body, resolver, def, child, true, unsafe_expr_cb); - }); - } - &Expr::Assignment { target, value: _ } => { - body.walk_pats(target, &mut |pat| { - if let Pat::Path(path) = &body[pat] { - mark_unsafe_path(path, pat.into()); - } - }); - } - _ => {} } - body.walk_child_exprs(current, |child| { - walk_unsafe(db, infer, body, resolver, def, child, inside_unsafe_block, unsafe_expr_cb); - }); + fn call_cb(&mut self, node: ExprOrPatId, reason: UnsafetyReason) { + (self.unsafe_expr_cb)(node, self.inside_unsafe_block, reason); + } + + fn walk_pats_top(&mut self, pats: impl Iterator, parent_expr: ExprId) { + let guard = self.resolver.update_to_inner_scope(self.db.upcast(), self.def, parent_expr); + pats.for_each(|pat| self.walk_pat(pat)); + self.resolver.reset_to_guard(guard); + } + + fn walk_pat(&mut self, current: PatId) { + let pat = &self.body.pats[current]; + + if self.inside_union_destructure { + match pat { + Pat::Tuple { .. } + | Pat::Record { .. } + | Pat::Range { .. } + | Pat::Slice { .. } + | Pat::Path(..) + | Pat::Lit(..) + | Pat::Bind { .. } + | Pat::TupleStruct { .. } + | Pat::Ref { .. } + | Pat::Box { .. } + | Pat::Expr(..) + | Pat::ConstBlock(..) => self.call_cb(current.into(), UnsafetyReason::UnionField), + // `Or` only wraps other patterns, and `Missing`/`Wild` do not constitute a read. + Pat::Missing | Pat::Wild | Pat::Or(_) => {} + } + } + + match pat { + Pat::Record { .. } => { + if let Some((AdtId::UnionId(_), _)) = self.infer[current].as_adt() { + let old_inside_union_destructure = + mem::replace(&mut self.inside_union_destructure, true); + self.body.walk_pats_shallow(current, |pat| self.walk_pat(pat)); + self.inside_union_destructure = old_inside_union_destructure; + return; + } + } + Pat::Path(path) => self.mark_unsafe_path(current.into(), path), + &Pat::ConstBlock(expr) => { + let old_inside_assignment = mem::replace(&mut self.inside_assignment, false); + self.walk_expr(expr); + self.inside_assignment = old_inside_assignment; + } + &Pat::Expr(expr) => self.walk_expr(expr), + _ => {} + } + + self.body.walk_pats_shallow(current, |pat| self.walk_pat(pat)); + } + + fn walk_expr(&mut self, current: ExprId) { + let expr = &self.body.exprs[current]; + let inside_assignment = mem::replace(&mut self.inside_assignment, false); + match expr { + &Expr::Call { callee, .. } => { + if let Some(func) = self.infer[callee].as_fn_def(self.db) { + if is_fn_unsafe_to_call(self.db, func) { + self.call_cb(current.into(), UnsafetyReason::UnsafeFnCall); + } + } + } + Expr::Path(path) => { + let guard = + self.resolver.update_to_inner_scope(self.db.upcast(), self.def, current); + self.mark_unsafe_path(current.into(), path); + self.resolver.reset_to_guard(guard); + } + Expr::Ref { expr, rawness: Rawness::RawPtr, mutability: _ } => { + if let Expr::Path(_) = self.body.exprs[*expr] { + // Do not report unsafe for `addr_of[_mut]!(EXTERN_OR_MUT_STATIC)`, + // see https://github.com/rust-lang/rust/pull/125834. + return; + } + } + Expr::MethodCall { .. } => { + if self + .infer + .method_resolution(current) + .map(|(func, _)| is_fn_unsafe_to_call(self.db, func)) + .unwrap_or(false) + { + self.call_cb(current.into(), UnsafetyReason::UnsafeFnCall); + } + } + Expr::UnaryOp { expr, op: UnaryOp::Deref } => { + if let TyKind::Raw(..) = &self.infer[*expr].kind(Interner) { + self.call_cb(current.into(), UnsafetyReason::RawPtrDeref); + } + } + Expr::Unsafe { .. } => { + let old_inside_unsafe_block = + mem::replace(&mut self.inside_unsafe_block, InsideUnsafeBlock::Yes); + self.body.walk_child_exprs_without_pats(current, |child| self.walk_expr(child)); + self.inside_unsafe_block = old_inside_unsafe_block; + return; + } + &Expr::Assignment { target, value: _ } => { + let old_inside_assignment = mem::replace(&mut self.inside_assignment, true); + self.walk_pats_top(std::iter::once(target), current); + self.inside_assignment = old_inside_assignment; + } + Expr::InlineAsm(_) => self.call_cb(current.into(), UnsafetyReason::InlineAsm), + // rustc allows union assignment to propagate through field accesses and casts. + Expr::Cast { .. } => self.inside_assignment = inside_assignment, + Expr::Field { .. } => { + self.inside_assignment = inside_assignment; + if !inside_assignment { + if let Some(Either::Left(FieldId { parent: VariantId::UnionId(_), .. })) = + self.infer.field_resolution(current) + { + self.call_cb(current.into(), UnsafetyReason::UnionField); + } + } + } + Expr::Block { statements, .. } | Expr::Async { statements, .. } => { + self.walk_pats_top( + statements.iter().filter_map(|statement| match statement { + &Statement::Let { pat, .. } => Some(pat), + _ => None, + }), + current, + ); + } + Expr::Match { arms, .. } => { + self.walk_pats_top(arms.iter().map(|arm| arm.pat), current); + } + &Expr::Let { pat, .. } => { + self.walk_pats_top(std::iter::once(pat), current); + } + Expr::Closure { args, .. } => { + self.walk_pats_top(args.iter().copied(), current); + } + _ => {} + } + + self.body.walk_child_exprs_without_pats(current, |child| self.walk_expr(child)); + } + + fn mark_unsafe_path(&mut self, node: ExprOrPatId, path: &Path) { + let hygiene = self.body.expr_or_pat_path_hygiene(node); + let value_or_partial = + self.resolver.resolve_path_in_value_ns(self.db.upcast(), path, hygiene); + if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id), _)) = value_or_partial { + let static_data = self.db.static_data(id); + if static_data.mutable { + self.call_cb(node, UnsafetyReason::MutableStatic); + } else if static_data.is_extern && !static_data.has_safe_kw { + self.call_cb(node, UnsafetyReason::ExternStatic); + } + } + } } diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 8297acde85..9ca021027d 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -5,7 +5,9 @@ //! be expressed in terms of hir types themselves. pub use hir_ty::diagnostics::{CaseType, IncorrectCase}; use hir_ty::{ - db::HirDatabase, diagnostics::BodyValidationDiagnostic, CastError, InferenceDiagnostic, + db::HirDatabase, + diagnostics::{BodyValidationDiagnostic, UnsafetyReason}, + CastError, InferenceDiagnostic, }; use cfg::{CfgExpr, CfgOptions}; @@ -258,9 +260,10 @@ pub struct PrivateField { #[derive(Debug)] pub struct MissingUnsafe { - pub expr: InFile>>, + pub node: InFile>>, /// If true, the diagnostics is an `unsafe_op_in_unsafe_fn` lint instead of a hard error. pub only_lint: bool, + pub reason: UnsafetyReason, } #[derive(Debug)] diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index c9498b3aea..0b2ba56b1f 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -147,6 +147,7 @@ pub use { }, hir_ty::{ consteval::ConstEvalError, + diagnostics::UnsafetyReason, display::{ClosureStyle, HirDisplay, HirDisplayError, HirWrite}, dyn_compatibility::{DynCompatibilityViolation, MethodViolationCode}, layout::LayoutError, @@ -1890,10 +1891,10 @@ impl DefWithBody { ); } - let (unafe_exprs, only_lint) = hir_ty::diagnostics::missing_unsafe(db, self.into()); - for expr in unafe_exprs { - match source_map.expr_or_pat_syntax(expr) { - Ok(expr) => acc.push(MissingUnsafe { expr, only_lint }.into()), + let (unsafe_exprs, only_lint) = hir_ty::diagnostics::missing_unsafe(db, self.into()); + for (node, reason) in unsafe_exprs { + match source_map.expr_or_pat_syntax(node) { + Ok(node) => acc.push(MissingUnsafe { node, only_lint, reason }.into()), Err(SyntheticSyntax) => { // FIXME: Here and elsewhere in this file, the `expr` was // desugared, report or assert that this doesn't happen. diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index c16454cff6..56ed81f053 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -36,7 +36,7 @@ use hir_expand::{ use hir_ty::{ diagnostics::{ record_literal_missing_fields, record_pattern_missing_fields, unsafe_expressions, - UnsafeExpr, + InsideUnsafeBlock, }, lang_items::lang_items_for_bin_op, method_resolution, Adjustment, InferenceResult, Interner, Substitution, Ty, TyExt, TyKind, @@ -939,8 +939,8 @@ impl SourceAnalyzer { *def, body, expr_id, - &mut |UnsafeExpr { inside_unsafe_block, .. }| { - is_unsafe |= !inside_unsafe_block + &mut |_, inside_unsafe_block, _| { + is_unsafe |= inside_unsafe_block == InsideUnsafeBlock::No }, ) }; diff --git a/crates/ide-diagnostics/src/handlers/missing_unsafe.rs b/crates/ide-diagnostics/src/handlers/missing_unsafe.rs index a630d3c7c3..2bfdda3565 100644 --- a/crates/ide-diagnostics/src/handlers/missing_unsafe.rs +++ b/crates/ide-diagnostics/src/handlers/missing_unsafe.rs @@ -1,5 +1,5 @@ use hir::db::ExpandDatabase; -use hir::HirFileIdExt; +use hir::{HirFileIdExt, UnsafetyReason}; use ide_db::text_edit::TextEdit; use ide_db::{assists::Assist, source_change::SourceChange}; use syntax::{ast, SyntaxNode}; @@ -16,23 +16,35 @@ pub(crate) fn missing_unsafe(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsaf } else { DiagnosticCode::RustcHardError("E0133") }; + let operation = display_unsafety_reason(d.reason); Diagnostic::new_with_syntax_node_ptr( ctx, code, - "this operation is unsafe and requires an unsafe function or block", - d.expr.map(|it| it.into()), + format!("{operation} is unsafe and requires an unsafe function or block"), + d.node.map(|it| it.into()), ) .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.expr.file_id.is_macro() { + if d.node.file_id.is_macro() { return None; } - let root = ctx.sema.db.parse_or_expand(d.expr.file_id); - let node = d.expr.value.to_node(&root); + 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)?; @@ -40,7 +52,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Option u8 { fn main() { ed2021::safe(); ed2024::not_safe(); - //^^^^^^^^^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block + //^^^^^^^^^^^^^^^^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block } "#, ) @@ -591,7 +603,7 @@ unsafe fn foo(p: *mut i32) { #![warn(unsafe_op_in_unsafe_fn)] unsafe fn foo(p: *mut i32) { *p = 123; - //^^💡 warn: this operation is unsafe and requires an unsafe function or block + //^^💡 warn: dereference of raw pointer is unsafe and requires an unsafe function or block } "#, ) @@ -618,17 +630,119 @@ unsafe extern { fn main() { f(); g(); - //^^^💡 error: this operation is unsafe and requires an unsafe function or block + //^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block h(); - //^^^💡 error: this operation is unsafe and requires an unsafe function or block + //^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block let _ = S1; let _ = S2; - //^^💡 error: this operation is unsafe and requires an unsafe function or block + //^^💡 error: use of extern static is unsafe and requires an unsafe function or block let _ = S3; - //^^💡 error: this operation is unsafe and requires an unsafe function or block + //^^💡 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; +} + + "#, + ) + } }