Skip to content

Commit

Permalink
Fix #[rustc_deprecated_safe_2024]
Browse files Browse the repository at this point in the history
It should be considered by the edition of the caller, not the callee.

Technically we still don't do it correctly - we need the span of the method name (if it comes from a macro), but we don't keep it and this is good enough for now.
  • Loading branch information
ChayimFriedman2 committed Jan 27, 2025
1 parent ad0aea4 commit 9a0504b
Show file tree
Hide file tree
Showing 16 changed files with 274 additions and 100 deletions.
6 changes: 5 additions & 1 deletion src/tools/rust-analyzer/crates/hir-def/src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@ impl FunctionData {
.map(Box::new);
let rustc_allow_incoherent_impl = attrs.by_key(&sym::rustc_allow_incoherent_impl).exists();
if flags.contains(FnFlags::HAS_UNSAFE_KW)
&& !crate_graph[krate].edition.at_least_2024()
&& attrs.by_key(&sym::rustc_deprecated_safe_2024).exists()
{
flags.remove(FnFlags::HAS_UNSAFE_KW);
flags.insert(FnFlags::DEPRECATED_SAFE_2024);
}

if attrs.by_key(&sym::target_feature).exists() {
Expand Down Expand Up @@ -152,6 +152,10 @@ impl FunctionData {
self.flags.contains(FnFlags::HAS_UNSAFE_KW)
}

pub fn is_deprecated_safe_2024(&self) -> bool {
self.flags.contains(FnFlags::DEPRECATED_SAFE_2024)
}

pub fn is_safe(&self) -> bool {
self.flags.contains(FnFlags::HAS_SAFE_KW)
}
Expand Down
26 changes: 18 additions & 8 deletions src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2238,17 +2238,27 @@ impl ExprCollector<'_> {
let unsafe_arg_new = self.alloc_expr_desugared(Expr::Path(unsafe_arg_new));
let unsafe_arg_new =
self.alloc_expr_desugared(Expr::Call { callee: unsafe_arg_new, args: Box::default() });
let unsafe_arg_new = self.alloc_expr_desugared(Expr::Unsafe {
let mut unsafe_arg_new = self.alloc_expr_desugared(Expr::Unsafe {
id: None,
// We collect the unused expressions here so that we still infer them instead of
// dropping them out of the expression tree
statements: fmt
.orphans
.into_iter()
.map(|expr| Statement::Expr { expr, has_semi: true })
.collect(),
statements: Box::new([]),
tail: Some(unsafe_arg_new),
});
if !fmt.orphans.is_empty() {
unsafe_arg_new = self.alloc_expr_desugared(Expr::Block {
id: None,
// We collect the unused expressions here so that we still infer them instead of
// dropping them out of the expression tree. We cannot store them in the `Unsafe`
// block because then unsafe blocks within them will get a false "unused unsafe"
// diagnostic (rustc has a notion of builtin unsafe blocks, but we don't).
statements: fmt
.orphans
.into_iter()
.map(|expr| Statement::Expr { expr, has_semi: true })
.collect(),
tail: Some(unsafe_arg_new),
label: None,
});
}

let idx = self.alloc_expr(
Expr::Call {
Expand Down
1 change: 1 addition & 0 deletions src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,7 @@ bitflags::bitflags! {
/// only very few functions with it. So we only encode its existence here, and lookup
/// it if needed.
const HAS_TARGET_FEATURE = 1 << 8;
const DEPRECATED_SAFE_2024 = 1 << 9;
}
}

Expand Down
117 changes: 81 additions & 36 deletions src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,26 @@ use hir_def::{
path::Path,
resolver::{HasResolver, ResolveValueResult, Resolver, ValueNs},
type_ref::Rawness,
AdtId, DefWithBodyId, FieldId, VariantId,
AdtId, DefWithBodyId, FieldId, FunctionId, VariantId,
};
use span::Edition;

use crate::{
db::HirDatabase, utils::is_fn_unsafe_to_call, InferenceResult, Interner, TargetFeatures, TyExt,
TyKind,
};

/// 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<(ExprOrPatId, UnsafetyReason)>, bool) {
#[derive(Debug, Default)]
pub struct MissingUnsafeResult {
pub unsafe_exprs: Vec<(ExprOrPatId, UnsafetyReason)>,
/// If `fn_is_unsafe` is false, `unsafe_exprs` are hard errors. If true, they're `unsafe_op_in_unsafe_fn`.
pub fn_is_unsafe: bool,
pub deprecated_safe_calls: Vec<ExprId>,
}

pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> MissingUnsafeResult {
let _p = tracing::info_span!("missing_unsafe").entered();

let mut res = Vec::new();
let is_unsafe = match def {
DefWithBodyId::FunctionId(it) => db.function_data(it).is_unsafe(),
DefWithBodyId::StaticId(_)
Expand All @@ -37,11 +39,19 @@ pub fn missing_unsafe(
| DefWithBodyId::FieldId(_) => false,
};

let mut res = MissingUnsafeResult { fn_is_unsafe: is_unsafe, ..MissingUnsafeResult::default() };
let body = db.body(def);
let infer = db.infer(def);
let mut callback = |node, inside_unsafe_block, reason| {
if inside_unsafe_block == InsideUnsafeBlock::No {
res.push((node, reason));
let mut callback = |diag| match diag {
UnsafeDiagnostic::UnsafeOperation { node, inside_unsafe_block, reason } => {
if inside_unsafe_block == InsideUnsafeBlock::No {
res.unsafe_exprs.push((node, reason));
}
}
UnsafeDiagnostic::DeprecatedSafe2024 { node, inside_unsafe_block } => {
if inside_unsafe_block == InsideUnsafeBlock::No {
res.deprecated_safe_calls.push(node)
}
}
};
let mut visitor = UnsafeVisitor::new(db, &infer, &body, def, &mut callback);
Expand All @@ -56,7 +66,7 @@ pub fn missing_unsafe(
}
}

(res, is_unsafe)
res
}

#[derive(Debug, Clone, Copy)]
Expand All @@ -75,15 +85,31 @@ pub enum InsideUnsafeBlock {
Yes,
}

#[derive(Debug)]
enum UnsafeDiagnostic {
UnsafeOperation {
node: ExprOrPatId,
inside_unsafe_block: InsideUnsafeBlock,
reason: UnsafetyReason,
},
/// A lint.
DeprecatedSafe2024 { node: ExprId, inside_unsafe_block: InsideUnsafeBlock },
}

pub fn unsafe_expressions(
db: &dyn HirDatabase,
infer: &InferenceResult,
def: DefWithBodyId,
body: &Body,
current: ExprId,
unsafe_expr_cb: &mut dyn FnMut(ExprOrPatId, InsideUnsafeBlock, UnsafetyReason),
callback: &mut dyn FnMut(InsideUnsafeBlock),
) {
let mut visitor = UnsafeVisitor::new(db, infer, body, def, unsafe_expr_cb);
let mut visitor_callback = |diag| {
if let UnsafeDiagnostic::UnsafeOperation { inside_unsafe_block, .. } = diag {
callback(inside_unsafe_block);
}
};
let mut visitor = UnsafeVisitor::new(db, infer, body, def, &mut visitor_callback);
_ = visitor.resolver.update_to_inner_scope(db.upcast(), def, current);
visitor.walk_expr(current);
}
Expand All @@ -97,8 +123,10 @@ struct UnsafeVisitor<'a> {
inside_unsafe_block: InsideUnsafeBlock,
inside_assignment: bool,
inside_union_destructure: bool,
unsafe_expr_cb: &'a mut dyn FnMut(ExprOrPatId, InsideUnsafeBlock, UnsafetyReason),
callback: &'a mut dyn FnMut(UnsafeDiagnostic),
def_target_features: TargetFeatures,
// FIXME: This needs to be the edition of the span of each call.
edition: Edition,
}

impl<'a> UnsafeVisitor<'a> {
Expand All @@ -107,13 +135,14 @@ impl<'a> UnsafeVisitor<'a> {
infer: &'a InferenceResult,
body: &'a Body,
def: DefWithBodyId,
unsafe_expr_cb: &'a mut dyn FnMut(ExprOrPatId, InsideUnsafeBlock, UnsafetyReason),
unsafe_expr_cb: &'a mut dyn FnMut(UnsafeDiagnostic),
) -> Self {
let resolver = def.resolver(db.upcast());
let def_target_features = match def {
DefWithBodyId::FunctionId(func) => TargetFeatures::from_attrs(&db.attrs(func.into())),
_ => TargetFeatures::default(),
};
let edition = db.crate_graph()[resolver.module().krate()].edition;
Self {
db,
infer,
Expand All @@ -123,13 +152,34 @@ impl<'a> UnsafeVisitor<'a> {
inside_unsafe_block: InsideUnsafeBlock::No,
inside_assignment: false,
inside_union_destructure: false,
unsafe_expr_cb,
callback: unsafe_expr_cb,
def_target_features,
edition,
}
}

fn call_cb(&mut self, node: ExprOrPatId, reason: UnsafetyReason) {
(self.unsafe_expr_cb)(node, self.inside_unsafe_block, reason);
fn on_unsafe_op(&mut self, node: ExprOrPatId, reason: UnsafetyReason) {
(self.callback)(UnsafeDiagnostic::UnsafeOperation {
node,
inside_unsafe_block: self.inside_unsafe_block,
reason,
});
}

fn check_call(&mut self, node: ExprId, func: FunctionId) {
let unsafety = is_fn_unsafe_to_call(self.db, func, &self.def_target_features, self.edition);
match unsafety {
crate::utils::Unsafety::Safe => {}
crate::utils::Unsafety::Unsafe => {
self.on_unsafe_op(node.into(), UnsafetyReason::UnsafeFnCall)
}
crate::utils::Unsafety::DeprecatedSafe2024 => {
(self.callback)(UnsafeDiagnostic::DeprecatedSafe2024 {
node,
inside_unsafe_block: self.inside_unsafe_block,
})
}
}
}

fn walk_pats_top(&mut self, pats: impl Iterator<Item = PatId>, parent_expr: ExprId) {
Expand All @@ -154,7 +204,9 @@ impl<'a> UnsafeVisitor<'a> {
| Pat::Ref { .. }
| Pat::Box { .. }
| Pat::Expr(..)
| Pat::ConstBlock(..) => self.call_cb(current.into(), UnsafetyReason::UnionField),
| Pat::ConstBlock(..) => {
self.on_unsafe_op(current.into(), UnsafetyReason::UnionField)
}
// `Or` only wraps other patterns, and `Missing`/`Wild` do not constitute a read.
Pat::Missing | Pat::Wild | Pat::Or(_) => {}
}
Expand Down Expand Up @@ -189,9 +241,7 @@ impl<'a> UnsafeVisitor<'a> {
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.def_target_features) {
self.call_cb(current.into(), UnsafetyReason::UnsafeFnCall);
}
self.check_call(current, func);
}
}
Expr::Path(path) => {
Expand All @@ -217,18 +267,13 @@ impl<'a> UnsafeVisitor<'a> {
}
}
Expr::MethodCall { .. } => {
if self
.infer
.method_resolution(current)
.map(|(func, _)| is_fn_unsafe_to_call(self.db, func, &self.def_target_features))
.unwrap_or(false)
{
self.call_cb(current.into(), UnsafetyReason::UnsafeFnCall);
if let Some((func, _)) = self.infer.method_resolution(current) {
self.check_call(current, func);
}
}
Expr::UnaryOp { expr, op: UnaryOp::Deref } => {
if let TyKind::Raw(..) = &self.infer[*expr].kind(Interner) {
self.call_cb(current.into(), UnsafetyReason::RawPtrDeref);
self.on_unsafe_op(current.into(), UnsafetyReason::RawPtrDeref);
}
}
Expr::Unsafe { .. } => {
Expand All @@ -243,7 +288,7 @@ impl<'a> UnsafeVisitor<'a> {
self.walk_pats_top(std::iter::once(target), current);
self.inside_assignment = old_inside_assignment;
}
Expr::InlineAsm(_) => self.call_cb(current.into(), UnsafetyReason::InlineAsm),
Expr::InlineAsm(_) => self.on_unsafe_op(current.into(), UnsafetyReason::InlineAsm),
// rustc allows union assignment to propagate through field accesses and casts.
Expr::Cast { .. } => self.inside_assignment = inside_assignment,
Expr::Field { .. } => {
Expand All @@ -252,7 +297,7 @@ impl<'a> UnsafeVisitor<'a> {
if let Some(Either::Left(FieldId { parent: VariantId::UnionId(_), .. })) =
self.infer.field_resolution(current)
{
self.call_cb(current.into(), UnsafetyReason::UnionField);
self.on_unsafe_op(current.into(), UnsafetyReason::UnionField);
}
}
}
Expand Down Expand Up @@ -287,9 +332,9 @@ impl<'a> UnsafeVisitor<'a> {
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);
self.on_unsafe_op(node, UnsafetyReason::MutableStatic);
} else if static_data.is_extern && !static_data.has_safe_kw {
self.call_cb(node, UnsafetyReason::ExternStatic);
self.on_unsafe_op(node, UnsafetyReason::ExternStatic);
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/tools/rust-analyzer/crates/hir-ty/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ pub use mapping::{
};
pub use method_resolution::check_orphan_rules;
pub use traits::TraitEnvironment;
pub use utils::{all_super_traits, direct_super_traits, is_fn_unsafe_to_call, TargetFeatures};
pub use utils::{
all_super_traits, direct_super_traits, is_fn_unsafe_to_call, TargetFeatures, Unsafety,
};
pub use variance::Variance;

pub use chalk_ir::{
Expand Down
38 changes: 32 additions & 6 deletions src/tools/rust-analyzer/crates/hir-ty/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use intern::{sym, Symbol};
use rustc_abi::TargetDataLayout;
use rustc_hash::FxHashSet;
use smallvec::{smallvec, SmallVec};
use span::Edition;
use stdx::never;

use crate::{
Expand Down Expand Up @@ -292,21 +293,38 @@ impl TargetFeatures {
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Unsafety {
Safe,
Unsafe,
/// A lint.
DeprecatedSafe2024,
}

pub fn is_fn_unsafe_to_call(
db: &dyn HirDatabase,
func: FunctionId,
caller_target_features: &TargetFeatures,
) -> bool {
call_edition: Edition,
) -> Unsafety {
let data = db.function_data(func);
if data.is_unsafe() {
return true;
return Unsafety::Unsafe;
}

if data.has_target_feature() {
// RFC 2396 <https://rust-lang.github.io/rfcs/2396-target-feature-1.1.html>.
let callee_target_features = TargetFeatures::from_attrs(&db.attrs(func.into()));
if !caller_target_features.enabled.is_superset(&callee_target_features.enabled) {
return true;
return Unsafety::Unsafe;
}
}

if data.is_deprecated_safe_2024() {
if call_edition.at_least_2024() {
return Unsafety::Unsafe;
} else {
return Unsafety::DeprecatedSafe2024;
}
}

Expand All @@ -319,14 +337,22 @@ pub fn is_fn_unsafe_to_call(
if is_intrinsic_block {
// legacy intrinsics
// extern "rust-intrinsic" intrinsics are unsafe unless they have the rustc_safe_intrinsic attribute
!db.attrs(func.into()).by_key(&sym::rustc_safe_intrinsic).exists()
if db.attrs(func.into()).by_key(&sym::rustc_safe_intrinsic).exists() {
Unsafety::Safe
} else {
Unsafety::Unsafe
}
} else {
// Function in an `extern` block are always unsafe to call, except when
// it is marked as `safe`.
!data.is_safe()
if data.is_safe() {
Unsafety::Safe
} else {
Unsafety::Unsafe
}
}
}
_ => false,
_ => Unsafety::Safe,
}
}

Expand Down
Loading

0 comments on commit 9a0504b

Please sign in to comment.