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.rs104
1 files changed, 104 insertions, 0 deletions
diff --git a/crates/hir-ty/src/diagnostics/unsafe_check.rs b/crates/hir-ty/src/diagnostics/unsafe_check.rs
new file mode 100644
index 0000000000..161b19a739
--- /dev/null
+++ b/crates/hir-ty/src/diagnostics/unsafe_check.rs
@@ -0,0 +1,104 @@
+//! Provides validations for unsafe code. Currently checks if unsafe functions are missing
+//! unsafe blocks.
+
+use hir_def::{
+ body::Body,
+ expr::{Expr, ExprId, UnaryOp},
+ resolver::{resolver_for_expr, ResolveValueResult, ValueNs},
+ DefWithBodyId,
+};
+
+use crate::{
+ db::HirDatabase, utils::is_fn_unsafe_to_call, InferenceResult, Interner, TyExt, TyKind,
+};
+
+pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> Vec<ExprId> {
+ let infer = db.infer(def);
+ let mut res = Vec::new();
+
+ let is_unsafe = match def {
+ DefWithBodyId::FunctionId(it) => db.function_data(it).has_unsafe_kw(),
+ DefWithBodyId::StaticId(_) | DefWithBodyId::ConstId(_) => false,
+ };
+ if is_unsafe {
+ return res;
+ }
+
+ let body = db.body(def);
+ unsafe_expressions(db, &infer, def, &body, body.body_expr, &mut |expr| {
+ if !expr.inside_unsafe_block {
+ res.push(expr.expr);
+ }
+ });
+
+ res
+}
+
+pub struct UnsafeExpr {
+ pub expr: ExprId,
+ pub inside_unsafe_block: bool,
+}
+
+// FIXME: Move this out, its not a diagnostic only thing anymore, and handle unsafe pattern accesses as well
+pub fn unsafe_expressions(
+ db: &dyn HirDatabase,
+ infer: &InferenceResult,
+ def: DefWithBodyId,
+ body: &Body,
+ current: ExprId,
+ unsafe_expr_cb: &mut dyn FnMut(UnsafeExpr),
+) {
+ walk_unsafe(db, infer, def, body, current, false, unsafe_expr_cb)
+}
+
+fn walk_unsafe(
+ db: &dyn HirDatabase,
+ infer: &InferenceResult,
+ def: DefWithBodyId,
+ body: &Body,
+ current: ExprId,
+ inside_unsafe_block: bool,
+ unsafe_expr_cb: &mut dyn FnMut(UnsafeExpr),
+) {
+ 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 { expr: current, inside_unsafe_block });
+ }
+ }
+ }
+ Expr::Path(path) => {
+ let resolver = resolver_for_expr(db.upcast(), def, current);
+ let value_or_partial = resolver.resolve_path_in_value_ns(db.upcast(), path.mod_path());
+ if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id))) = value_or_partial {
+ if db.static_data(id).mutable {
+ unsafe_expr_cb(UnsafeExpr { expr: current, inside_unsafe_block });
+ }
+ }
+ }
+ Expr::MethodCall { .. } => {
+ if infer
+ .method_resolution(current)
+ .map(|(func, _)| is_fn_unsafe_to_call(db, func))
+ .unwrap_or(false)
+ {
+ unsafe_expr_cb(UnsafeExpr { expr: current, inside_unsafe_block });
+ }
+ }
+ Expr::UnaryOp { expr, op: UnaryOp::Deref } => {
+ if let TyKind::Raw(..) = &infer[*expr].kind(Interner) {
+ unsafe_expr_cb(UnsafeExpr { expr: current, inside_unsafe_block });
+ }
+ }
+ Expr::Unsafe { body: child } => {
+ return walk_unsafe(db, infer, def, body, *child, true, unsafe_expr_cb);
+ }
+ _ => {}
+ }
+
+ expr.walk_child_exprs(|child| {
+ walk_unsafe(db, infer, def, body, child, inside_unsafe_block, unsafe_expr_cb);
+ });
+}