4587: Add "missing unsafe" diagnostics r=Nashenas88 a=Nashenas88

Addresses #190 

Co-authored-by: Paul Daniel Faria <Nashenas88@users.noreply.github.com>
This commit is contained in:
bors[bot] 2020-06-27 16:03:29 +00:00 committed by GitHub
commit 45fc8d5c84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 328 additions and 13 deletions

1
.gitignore vendored
View file

@ -7,6 +7,5 @@ crates/*/target
*.log *.log
*.iml *.iml
.vscode/settings.json .vscode/settings.json
*.html
generated_assists.adoc generated_assists.adoc
generated_features.adoc generated_features.adoc

4
Cargo.lock generated
View file

@ -30,7 +30,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [ dependencies = [
"winapi 0.3.8", "winapi 0.3.9",
] ]
[[package]] [[package]]
@ -1791,7 +1791,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
dependencies = [ dependencies = [
"libc", "libc",
"winapi 0.3.8", "winapi 0.3.9",
] ]
[[package]] [[package]]

View file

@ -26,8 +26,10 @@ use hir_ty::{
autoderef, autoderef,
display::{HirDisplayError, HirFormatter}, display::{HirDisplayError, HirFormatter},
expr::ExprValidator, expr::ExprValidator,
method_resolution, ApplicationTy, Canonical, GenericPredicate, InEnvironment, Substs, method_resolution,
TraitEnvironment, Ty, TyDefId, TypeCtor, unsafe_validation::UnsafeValidator,
ApplicationTy, Canonical, GenericPredicate, InEnvironment, Substs, TraitEnvironment, Ty,
TyDefId, TypeCtor,
}; };
use ra_db::{CrateId, CrateName, Edition, FileId}; use ra_db::{CrateId, CrateName, Edition, FileId};
use ra_prof::profile; use ra_prof::profile;
@ -677,7 +679,9 @@ impl Function {
let _p = profile("Function::diagnostics"); let _p = profile("Function::diagnostics");
let infer = db.infer(self.id.into()); let infer = db.infer(self.id.into());
infer.add_diagnostics(db, self.id, sink); infer.add_diagnostics(db, self.id, sink);
let mut validator = ExprValidator::new(self.id, infer, sink); let mut validator = ExprValidator::new(self.id, infer.clone(), sink);
validator.validate_body(db);
let mut validator = UnsafeValidator::new(self.id, infer, sink);
validator.validate_body(db); validator.validate_body(db);
} }
} }

View file

@ -176,6 +176,7 @@ impl ExprCollector<'_> {
if !self.expander.is_cfg_enabled(&expr) { if !self.expander.is_cfg_enabled(&expr) {
return self.missing_expr(); return self.missing_expr();
} }
match expr { match expr {
ast::Expr::IfExpr(e) => { ast::Expr::IfExpr(e) => {
let then_branch = self.collect_block_opt(e.then_branch()); let then_branch = self.collect_block_opt(e.then_branch());
@ -218,8 +219,12 @@ impl ExprCollector<'_> {
let body = self.collect_block_opt(e.block_expr()); let body = self.collect_block_opt(e.block_expr());
self.alloc_expr(Expr::TryBlock { body }, syntax_ptr) self.alloc_expr(Expr::TryBlock { body }, syntax_ptr)
} }
ast::Effect::Unsafe(_) => {
let body = self.collect_block_opt(e.block_expr());
self.alloc_expr(Expr::Unsafe { body }, syntax_ptr)
}
// FIXME: we need to record these effects somewhere... // FIXME: we need to record these effects somewhere...
ast::Effect::Async(_) | ast::Effect::Label(_) | ast::Effect::Unsafe(_) => { ast::Effect::Async(_) | ast::Effect::Label(_) => {
self.collect_block_opt(e.block_expr()) self.collect_block_opt(e.block_expr())
} }
}, },
@ -445,7 +450,6 @@ impl ExprCollector<'_> {
Mutability::from_mutable(e.mut_token().is_some()) Mutability::from_mutable(e.mut_token().is_some())
}; };
let rawness = Rawness::from_raw(raw_tok); let rawness = Rawness::from_raw(raw_tok);
self.alloc_expr(Expr::Ref { expr, rawness, mutability }, syntax_ptr) self.alloc_expr(Expr::Ref { expr, rawness, mutability }, syntax_ptr)
} }
ast::Expr::PrefixExpr(e) => { ast::Expr::PrefixExpr(e) => {

View file

@ -150,6 +150,9 @@ pub enum Expr {
Tuple { Tuple {
exprs: Vec<ExprId>, exprs: Vec<ExprId>,
}, },
Unsafe {
body: ExprId,
},
Array(Array), Array(Array),
Literal(Literal), Literal(Literal),
} }
@ -247,7 +250,7 @@ impl Expr {
f(*expr); f(*expr);
} }
} }
Expr::TryBlock { body } => f(*body), Expr::TryBlock { body } | Expr::Unsafe { body } => f(*body),
Expr::Loop { body, .. } => f(*body), Expr::Loop { body, .. } => f(*body),
Expr::While { condition, body, .. } => { Expr::While { condition, body, .. } => {
f(*condition); f(*condition);

View file

@ -169,3 +169,31 @@ impl AstDiagnostic for BreakOutsideOfLoop {
ast::Expr::cast(node).unwrap() ast::Expr::cast(node).unwrap()
} }
} }
#[derive(Debug)]
pub struct MissingUnsafe {
pub file: HirFileId,
pub expr: AstPtr<ast::Expr>,
}
impl Diagnostic for MissingUnsafe {
fn message(&self) -> String {
format!("This operation is unsafe and requires an unsafe function or block")
}
fn source(&self) -> InFile<SyntaxNodePtr> {
InFile { file_id: self.file, value: self.expr.clone().into() }
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
}
impl AstDiagnostic for MissingUnsafe {
type AST = ast::Expr;
fn ast(&self, db: &impl AstDatabase) -> Self::AST {
let root = db.parse_or_expand(self.source().file_id).unwrap();
let node = self.source().value.to_node(&root);
ast::Expr::cast(node).unwrap()
}
}

View file

@ -142,6 +142,7 @@ impl<'a> InferenceContext<'a> {
// FIXME: Breakable block inference // FIXME: Breakable block inference
self.infer_block(statements, *tail, expected) self.infer_block(statements, *tail, expected)
} }
Expr::Unsafe { body } => self.infer_expr(*body, expected),
Expr::TryBlock { body } => { Expr::TryBlock { body } => {
let _inner = self.infer_expr(*body, expected); let _inner = self.infer_expr(*body, expected);
// FIXME should be std::result::Result<{inner}, _> // FIXME should be std::result::Result<{inner}, _>

View file

@ -37,6 +37,7 @@ pub(crate) mod utils;
pub mod db; pub mod db;
pub mod diagnostics; pub mod diagnostics;
pub mod expr; pub mod expr;
pub mod unsafe_validation;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;

View file

@ -11,7 +11,10 @@ use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, SourceDataba
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use stdx::format_to; use stdx::format_to;
use crate::{db::HirDatabase, diagnostics::Diagnostic, expr::ExprValidator}; use crate::{
db::HirDatabase, diagnostics::Diagnostic, expr::ExprValidator,
unsafe_validation::UnsafeValidator,
};
#[salsa::database( #[salsa::database(
ra_db::SourceDatabaseExtStorage, ra_db::SourceDatabaseExtStorage,
@ -119,7 +122,9 @@ impl TestDB {
let infer = self.infer(f.into()); let infer = self.infer(f.into());
let mut sink = DiagnosticSink::new(&mut cb); let mut sink = DiagnosticSink::new(&mut cb);
infer.add_diagnostics(self, f, &mut sink); infer.add_diagnostics(self, f, &mut sink);
let mut validator = ExprValidator::new(f, infer, &mut sink); let mut validator = ExprValidator::new(f, infer.clone(), &mut sink);
validator.validate_body(self);
let mut validator = UnsafeValidator::new(f, infer, &mut sink);
validator.validate_body(self); validator.validate_body(self);
} }
} }

View file

@ -538,6 +538,155 @@ fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() {
assert_snapshot!(diagnostics, @""); assert_snapshot!(diagnostics, @"");
} }
#[test]
fn missing_unsafe_diagnostic_with_raw_ptr() {
let diagnostics = TestDB::with_files(
r"
//- /lib.rs
fn missing_unsafe() {
let x = &5 as *const usize;
let y = *x;
}
",
)
.diagnostics()
.0;
assert_snapshot!(diagnostics, @r#""*x": This operation is unsafe and requires an unsafe function or block"#);
}
#[test]
fn missing_unsafe_diagnostic_with_unsafe_call() {
let diagnostics = TestDB::with_files(
r"
//- /lib.rs
unsafe fn unsafe_fn() {
let x = &5 as *const usize;
let y = *x;
}
fn missing_unsafe() {
unsafe_fn();
}
",
)
.diagnostics()
.0;
assert_snapshot!(diagnostics, @r#""unsafe_fn()": This operation is unsafe and requires an unsafe function or block"#);
}
#[test]
fn missing_unsafe_diagnostic_with_unsafe_method_call() {
let diagnostics = TestDB::with_files(
r"
struct HasUnsafe;
impl HasUnsafe {
unsafe fn unsafe_fn(&self) {
let x = &5 as *const usize;
let y = *x;
}
}
fn missing_unsafe() {
HasUnsafe.unsafe_fn();
}
",
)
.diagnostics()
.0;
assert_snapshot!(diagnostics, @r#""HasUnsafe.unsafe_fn()": This operation is unsafe and requires an unsafe function or block"#);
}
#[test]
fn no_missing_unsafe_diagnostic_with_raw_ptr_in_unsafe_block() {
let diagnostics = TestDB::with_files(
r"
fn nothing_to_see_move_along() {
let x = &5 as *const usize;
unsafe {
let y = *x;
}
}
",
)
.diagnostics()
.0;
assert_snapshot!(diagnostics, @"");
}
#[test]
fn missing_unsafe_diagnostic_with_raw_ptr_outside_unsafe_block() {
let diagnostics = TestDB::with_files(
r"
fn nothing_to_see_move_along() {
let x = &5 as *const usize;
unsafe {
let y = *x;
}
let z = *x;
}
",
)
.diagnostics()
.0;
assert_snapshot!(diagnostics, @r#""*x": This operation is unsafe and requires an unsafe function or block"#);
}
#[test]
fn no_missing_unsafe_diagnostic_with_unsafe_call_in_unsafe_block() {
let diagnostics = TestDB::with_files(
r"
unsafe fn unsafe_fn() {
let x = &5 as *const usize;
let y = *x;
}
fn nothing_to_see_move_along() {
unsafe {
unsafe_fn();
}
}
",
)
.diagnostics()
.0;
assert_snapshot!(diagnostics, @"");
}
#[test]
fn no_missing_unsafe_diagnostic_with_unsafe_method_call_in_unsafe_block() {
let diagnostics = TestDB::with_files(
r"
struct HasUnsafe;
impl HasUnsafe {
unsafe fn unsafe_fn() {
let x = &5 as *const usize;
let y = *x;
}
}
fn nothing_to_see_move_along() {
unsafe {
HasUnsafe.unsafe_fn();
}
}
",
)
.diagnostics()
.0;
assert_snapshot!(diagnostics, @"");
}
#[test] #[test]
fn break_outside_of_loop() { fn break_outside_of_loop() {
let diagnostics = TestDB::with_files( let diagnostics = TestDB::with_files(

View file

@ -1880,6 +1880,7 @@ fn main() {
@r###" @r###"
10..130 '{ ...2 }; }': () 10..130 '{ ...2 }; }': ()
20..21 'x': i32 20..21 'x': i32
24..37 'unsafe { 92 }': i32
31..37 '{ 92 }': i32 31..37 '{ 92 }': i32
33..35 '92': i32 33..35 '92': i32
47..48 'y': {unknown} 47..48 'y': {unknown}

View file

@ -0,0 +1,120 @@
//! Provides validations for unsafe code. Currently checks if unsafe functions are missing
//! unsafe blocks.
use std::sync::Arc;
use hir_def::{
body::Body,
expr::{Expr, ExprId, UnaryOp},
DefWithBodyId, FunctionId,
};
use hir_expand::diagnostics::DiagnosticSink;
use crate::{
db::HirDatabase, diagnostics::MissingUnsafe, lower::CallableDef, ApplicationTy,
InferenceResult, Ty, TypeCtor,
};
pub struct UnsafeValidator<'a, 'b: 'a> {
func: FunctionId,
infer: Arc<InferenceResult>,
sink: &'a mut DiagnosticSink<'b>,
}
impl<'a, 'b> UnsafeValidator<'a, 'b> {
pub fn new(
func: FunctionId,
infer: Arc<InferenceResult>,
sink: &'a mut DiagnosticSink<'b>,
) -> UnsafeValidator<'a, 'b> {
UnsafeValidator { func, infer, sink }
}
pub fn validate_body(&mut self, db: &dyn HirDatabase) {
let def = self.func.into();
let unsafe_expressions = unsafe_expressions(db, self.infer.as_ref(), def);
let func_data = db.function_data(self.func);
if func_data.is_unsafe
|| unsafe_expressions
.iter()
.filter(|unsafe_expr| !unsafe_expr.inside_unsafe_block)
.count()
== 0
{
return;
}
let (_, body_source) = db.body_with_source_map(def);
for unsafe_expr in unsafe_expressions {
if !unsafe_expr.inside_unsafe_block {
if let Ok(in_file) = body_source.as_ref().expr_syntax(unsafe_expr.expr) {
self.sink.push(MissingUnsafe { file: in_file.file_id, expr: in_file.value })
}
}
}
}
}
pub struct UnsafeExpr {
pub expr: ExprId,
pub inside_unsafe_block: bool,
}
pub fn unsafe_expressions(
db: &dyn HirDatabase,
infer: &InferenceResult,
def: DefWithBodyId,
) -> Vec<UnsafeExpr> {
let mut unsafe_exprs = vec![];
let body = db.body(def);
walk_unsafe(&mut unsafe_exprs, db, infer, &body, body.body_expr, false);
unsafe_exprs
}
fn walk_unsafe(
unsafe_exprs: &mut Vec<UnsafeExpr>,
db: &dyn HirDatabase,
infer: &InferenceResult,
body: &Body,
current: ExprId,
inside_unsafe_block: bool,
) {
let expr = &body.exprs[current];
match expr {
Expr::Call { callee, .. } => {
let ty = &infer[*callee];
if let &Ty::Apply(ApplicationTy {
ctor: TypeCtor::FnDef(CallableDef::FunctionId(func)),
..
}) = ty
{
if db.function_data(func).is_unsafe {
unsafe_exprs.push(UnsafeExpr { expr: current, inside_unsafe_block });
}
}
}
Expr::MethodCall { .. } => {
if infer
.method_resolution(current)
.map(|func| db.function_data(func).is_unsafe)
.unwrap_or(false)
{
unsafe_exprs.push(UnsafeExpr { expr: current, inside_unsafe_block });
}
}
Expr::UnaryOp { expr, op: UnaryOp::Deref } => {
if let Ty::Apply(ApplicationTy { ctor: TypeCtor::RawPtr(..), .. }) = &infer[*expr] {
unsafe_exprs.push(UnsafeExpr { expr: current, inside_unsafe_block });
}
}
Expr::Unsafe { body: child } => {
return walk_unsafe(unsafe_exprs, db, infer, body, *child, true);
}
_ => {}
}
expr.walk_child_exprs(|child| {
walk_unsafe(unsafe_exprs, db, infer, body, child, inside_unsafe_block);
});
}

View file

@ -25,7 +25,6 @@ pub enum HighlightTag {
EnumVariant, EnumVariant,
EscapeSequence, EscapeSequence,
Field, Field,
FormatSpecifier,
Function, Function,
Generic, Generic,
Keyword, Keyword,
@ -33,7 +32,6 @@ pub enum HighlightTag {
Macro, Macro,
Module, Module,
NumericLiteral, NumericLiteral,
Operator,
SelfKeyword, SelfKeyword,
SelfType, SelfType,
Static, Static,
@ -45,6 +43,8 @@ pub enum HighlightTag {
Union, Union,
Local, Local,
UnresolvedReference, UnresolvedReference,
FormatSpecifier,
Operator,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]