internal: move missing unsafe diagnostic to hir

This commit is contained in:
Aleksey Kladov 2021-06-12 17:39:46 +03:00
parent f8009666be
commit 0413d51317
6 changed files with 145 additions and 166 deletions

View File

@ -301,3 +301,27 @@ impl Diagnostic for BreakOutsideOfLoop {
self self
} }
} }
// Diagnostic: missing-unsafe
//
// This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block.
#[derive(Debug)]
pub struct MissingUnsafe {
pub file: HirFileId,
pub expr: AstPtr<ast::Expr>,
}
impl Diagnostic for MissingUnsafe {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("missing-unsafe")
}
fn message(&self) -> String {
format!("This operation is unsafe and requires an unsafe function or block")
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile { file_id: self.file, value: self.expr.clone().into() }
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
}

View File

@ -36,14 +36,14 @@ use std::{iter, sync::Arc};
use arrayvec::ArrayVec; use arrayvec::ArrayVec;
use base_db::{CrateDisplayName, CrateId, Edition, FileId}; use base_db::{CrateDisplayName, CrateId, Edition, FileId};
use diagnostics::{ use diagnostics::{
BreakOutsideOfLoop, InactiveCode, MacroError, NoSuchField, UnimplementedBuiltinMacro, BreakOutsideOfLoop, InactiveCode, MacroError, MissingUnsafe, NoSuchField,
UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule, UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall,
UnresolvedProcMacro, UnresolvedModule, UnresolvedProcMacro,
}; };
use either::Either; use either::Either;
use hir_def::{ use hir_def::{
adt::{ReprKind, VariantData}, adt::{ReprKind, VariantData},
body::BodyDiagnostic, body::{BodyDiagnostic, SyntheticSyntax},
expr::{BindingAnnotation, LabelId, Pat, PatId}, expr::{BindingAnnotation, LabelId, Pat, PatId},
item_tree::ItemTreeNode, item_tree::ItemTreeNode,
lang_item::LangItemTarget, lang_item::LangItemTarget,
@ -1060,6 +1060,18 @@ impl Function {
} }
} }
for expr in hir_ty::diagnostics::missing_unsafe(db, self.id.into()) {
match source_map.as_ref().expr_syntax(expr) {
Ok(in_file) => {
sink.push(MissingUnsafe { file: in_file.file_id, expr: in_file.value })
}
Err(SyntheticSyntax) => {
// FIXME: The `expr` was desugared, report or assert that
// this dosen't happen.
}
}
}
hir_ty::diagnostics::validate_module_item(db, krate, self.id.into(), sink); hir_ty::diagnostics::validate_module_item(db, krate, self.id.into(), sink);
hir_ty::diagnostics::validate_body(db, self.id.into(), sink); hir_ty::diagnostics::validate_body(db, self.id.into(), sink);
} }

View File

@ -17,7 +17,10 @@ use crate::{
diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink}, diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink},
}; };
pub use crate::diagnostics::expr::{record_literal_missing_fields, record_pattern_missing_fields}; pub use crate::diagnostics::{
expr::{record_literal_missing_fields, record_pattern_missing_fields},
unsafe_check::missing_unsafe,
};
pub fn validate_module_item( pub fn validate_module_item(
db: &dyn HirDatabase, db: &dyn HirDatabase,
@ -35,8 +38,6 @@ pub fn validate_body(db: &dyn HirDatabase, owner: DefWithBodyId, sink: &mut Diag
let infer = db.infer(owner); let infer = db.infer(owner);
let mut validator = expr::ExprValidator::new(owner, infer.clone(), sink); let mut validator = expr::ExprValidator::new(owner, infer.clone(), sink);
validator.validate_body(db); validator.validate_body(db);
let mut validator = unsafe_check::UnsafeValidator::new(owner, infer, sink);
validator.validate_body(db);
} }
// Diagnostic: missing-structure-fields // Diagnostic: missing-structure-fields
@ -219,30 +220,6 @@ impl Diagnostic for RemoveThisSemicolon {
} }
} }
// Diagnostic: missing-unsafe
//
// This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block.
#[derive(Debug)]
pub struct MissingUnsafe {
pub file: HirFileId,
pub expr: AstPtr<ast::Expr>,
}
impl Diagnostic for MissingUnsafe {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("missing-unsafe")
}
fn message(&self) -> String {
format!("This operation is unsafe and requires an unsafe function or block")
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile { file_id: self.file, value: self.expr.clone().into() }
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
}
// Diagnostic: mismatched-arg-count // Diagnostic: mismatched-arg-count
// //
// This diagnostic is triggered if a function is invoked with an incorrect amount of arguments. // This diagnostic is triggered if a function is invoked with an incorrect amount of arguments.

View File

@ -1,8 +1,6 @@
//! Provides validations for unsafe code. Currently checks if unsafe functions are missing //! Provides validations for unsafe code. Currently checks if unsafe functions are missing
//! unsafe blocks. //! unsafe blocks.
use std::sync::Arc;
use hir_def::{ use hir_def::{
body::Body, body::Body,
expr::{Expr, ExprId, UnaryOp}, expr::{Expr, ExprId, UnaryOp},
@ -10,52 +8,25 @@ use hir_def::{
DefWithBodyId, DefWithBodyId,
}; };
use crate::{ use crate::{db::HirDatabase, InferenceResult, Interner, TyExt, TyKind};
db::HirDatabase, diagnostics::MissingUnsafe, diagnostics_sink::DiagnosticSink, InferenceResult,
Interner, TyExt, TyKind,
};
pub(super) struct UnsafeValidator<'a, 'b: 'a> { pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> Vec<ExprId> {
owner: DefWithBodyId, let infer = db.infer(def);
infer: Arc<InferenceResult>,
sink: &'a mut DiagnosticSink<'b>,
}
impl<'a, 'b> UnsafeValidator<'a, 'b> { // let unsafe_expressions = ;
pub(super) fn new( let is_unsafe = match def {
owner: DefWithBodyId,
infer: Arc<InferenceResult>,
sink: &'a mut DiagnosticSink<'b>,
) -> UnsafeValidator<'a, 'b> {
UnsafeValidator { owner, infer, sink }
}
pub(super) fn validate_body(&mut self, db: &dyn HirDatabase) {
let def = self.owner;
let unsafe_expressions = unsafe_expressions(db, self.infer.as_ref(), def);
let is_unsafe = match self.owner {
DefWithBodyId::FunctionId(it) => db.function_data(it).is_unsafe(), DefWithBodyId::FunctionId(it) => db.function_data(it).is_unsafe(),
DefWithBodyId::StaticId(_) | DefWithBodyId::ConstId(_) => false, DefWithBodyId::StaticId(_) | DefWithBodyId::ConstId(_) => false,
}; };
if is_unsafe if is_unsafe {
|| unsafe_expressions return Vec::new();
.iter()
.filter(|unsafe_expr| !unsafe_expr.inside_unsafe_block)
.count()
== 0
{
return;
} }
let (_, body_source) = db.body_with_source_map(def); unsafe_expressions(db, &infer, def)
for unsafe_expr in unsafe_expressions { .into_iter()
if !unsafe_expr.inside_unsafe_block { .filter(|it| !it.inside_unsafe_block)
if let Ok(in_file) = body_source.as_ref().expr_syntax(unsafe_expr.expr) { .map(|it| it.expr)
self.sink.push(MissingUnsafe { file: in_file.file_id, expr: in_file.value }) .collect()
}
}
}
}
} }
pub(crate) struct UnsafeExpr { pub(crate) struct UnsafeExpr {
@ -126,92 +97,3 @@ fn walk_unsafe(
walk_unsafe(unsafe_exprs, db, infer, def, body, child, inside_unsafe_block); walk_unsafe(unsafe_exprs, db, infer, def, body, child, inside_unsafe_block);
}); });
} }
#[cfg(test)]
mod tests {
use crate::diagnostics::tests::check_diagnostics;
#[test]
fn missing_unsafe_diagnostic_with_raw_ptr() {
check_diagnostics(
r#"
fn main() {
let x = &5 as *const usize;
unsafe { let y = *x; }
let z = *x;
} //^^ This operation is unsafe and requires an unsafe function or block
"#,
)
}
#[test]
fn missing_unsafe_diagnostic_with_unsafe_call() {
check_diagnostics(
r#"
struct HasUnsafe;
impl HasUnsafe {
unsafe fn unsafe_fn(&self) {
let x = &5 as *const usize;
let y = *x;
}
}
unsafe fn unsafe_fn() {
let x = &5 as *const usize;
let y = *x;
}
fn main() {
unsafe_fn();
//^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
HasUnsafe.unsafe_fn();
//^^^^^^^^^^^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
unsafe {
unsafe_fn();
HasUnsafe.unsafe_fn();
}
}
"#,
);
}
#[test]
fn missing_unsafe_diagnostic_with_static_mut() {
check_diagnostics(
r#"
struct Ty {
a: u8,
}
static mut STATIC_MUT: Ty = Ty { a: 0 };
fn main() {
let x = STATIC_MUT.a;
//^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
unsafe {
let x = STATIC_MUT.a;
}
}
"#,
);
}
#[test]
fn no_missing_unsafe_diagnostic_with_safe_intrinsic() {
check_diagnostics(
r#"
extern "rust-intrinsic" {
pub fn bitreverse(x: u32) -> u32; // Safe intrinsic
pub fn floorf32(x: f32) -> f32; // Unsafe intrinsic
}
fn main() {
let _ = bitreverse(12);
let _ = floorf32(12.0);
//^^^^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
}
"#,
);
}
}

View File

@ -36,8 +36,8 @@ use syntax::SmolStr;
use super::{DomainGoal, InEnvironment, ProjectionTy, TraitEnvironment, TraitRef, Ty}; use super::{DomainGoal, InEnvironment, ProjectionTy, TraitEnvironment, TraitRef, Ty};
use crate::{ use crate::{
db::HirDatabase, fold_tys, lower::ImplTraitLoweringMode, db::HirDatabase, fold_tys, lower::ImplTraitLoweringMode, to_assoc_type_id, AliasEq, AliasTy,
to_assoc_type_id, AliasEq, AliasTy, Goal, Interner, Substitution, TyBuilder, TyExt, TyKind, Goal, Interner, Substitution, TyBuilder, TyExt, TyKind,
}; };
// This lint has a false positive here. See the link below for details. // This lint has a false positive here. See the link below for details.

View File

@ -855,6 +855,90 @@ impl Foo {
Foo { bar: 0 } Foo { bar: 0 }
} }
} }
"#,
);
}
#[test]
fn missing_unsafe_diagnostic_with_raw_ptr() {
check_diagnostics(
r#"
fn main() {
let x = &5 as *const usize;
unsafe { let y = *x; }
let z = *x;
} //^^ This operation is unsafe and requires an unsafe function or block
"#,
)
}
#[test]
fn missing_unsafe_diagnostic_with_unsafe_call() {
check_diagnostics(
r#"
struct HasUnsafe;
impl HasUnsafe {
unsafe fn unsafe_fn(&self) {
let x = &5 as *const usize;
let y = *x;
}
}
unsafe fn unsafe_fn() {
let x = &5 as *const usize;
let y = *x;
}
fn main() {
unsafe_fn();
//^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
HasUnsafe.unsafe_fn();
//^^^^^^^^^^^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
unsafe {
unsafe_fn();
HasUnsafe.unsafe_fn();
}
}
"#,
);
}
#[test]
fn missing_unsafe_diagnostic_with_static_mut() {
check_diagnostics(
r#"
struct Ty {
a: u8,
}
static mut STATIC_MUT: Ty = Ty { a: 0 };
fn main() {
let x = STATIC_MUT.a;
//^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
unsafe {
let x = STATIC_MUT.a;
}
}
"#,
);
}
#[test]
fn no_missing_unsafe_diagnostic_with_safe_intrinsic() {
check_diagnostics(
r#"
extern "rust-intrinsic" {
pub fn bitreverse(x: u32) -> u32; // Safe intrinsic
pub fn floorf32(x: f32) -> f32; // Unsafe intrinsic
}
fn main() {
let _ = bitreverse(12);
let _ = floorf32(12.0);
//^^^^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
}
"#, "#,
); );
} }