Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-ty/src/diagnostics/unsafe_check.rs')
-rw-r--r--crates/hir-ty/src/diagnostics/unsafe_check.rs134
1 files changed, 96 insertions, 38 deletions
diff --git a/crates/hir-ty/src/diagnostics/unsafe_check.rs b/crates/hir-ty/src/diagnostics/unsafe_check.rs
index 6bba83fac9..ac849b0762 100644
--- a/crates/hir-ty/src/diagnostics/unsafe_check.rs
+++ b/crates/hir-ty/src/diagnostics/unsafe_check.rs
@@ -5,28 +5,31 @@ use std::mem;
use either::Either;
use hir_def::{
- body::Body,
+ expr_store::Body,
hir::{Expr, ExprId, ExprOrPatId, Pat, PatId, Statement, UnaryOp},
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, TyExt, TyKind,
+ 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(_)
@@ -35,11 +38,19 @@ pub fn missing_unsafe(
| DefWithBodyId::InTypeConstId(_) => 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);
@@ -54,7 +65,7 @@ pub fn missing_unsafe(
}
}
- (res, is_unsafe)
+ res
}
#[derive(Debug, Clone, Copy)]
@@ -73,15 +84,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);
}
@@ -95,7 +122,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> {
@@ -104,9 +134,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,
@@ -116,12 +151,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) {
@@ -146,7 +203,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(_) => {}
}
@@ -180,9 +239,13 @@ impl<'a> UnsafeVisitor<'a> {
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);
+ let callee = &self.infer[callee];
+ if let Some(func) = callee.as_fn_def(self.db) {
+ self.check_call(current, func);
+ }
+ if let TyKind::Function(fn_ptr) = callee.kind(Interner) {
+ if fn_ptr.sig.safety == chalk_ir::Safety::Unsafe {
+ self.on_unsafe_op(current.into(), UnsafetyReason::UnsafeFnCall);
}
}
}
@@ -209,18 +272,13 @@ impl<'a> UnsafeVisitor<'a> {
}
}
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);
+ 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 { .. } => {
@@ -235,7 +293,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 { .. } => {
@@ -244,7 +302,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);
}
}
}
@@ -279,9 +337,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);
}
}
}