diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-06-27 17:03:29 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-06-27 17:03:29 +0100 |
commit | 45fc8d5c84d40da7a5dbf9b1a23ec389f54d1a81 (patch) | |
tree | ebe09304a24180b010b21a6aea3a727433403565 /crates/ra_hir_ty/src | |
parent | 9d1e2c4d9dc6c7f5fbaee5d9907d135f618d7ac6 (diff) | |
parent | 9777d2cb2dcea7b5a3b289708fea21b4bf787f0f (diff) |
Merge #4587
4587: Add "missing unsafe" diagnostics r=Nashenas88 a=Nashenas88
Addresses #190
Co-authored-by: Paul Daniel Faria <[email protected]>
Diffstat (limited to 'crates/ra_hir_ty/src')
-rw-r--r-- | crates/ra_hir_ty/src/diagnostics.rs | 28 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/infer/expr.rs | 1 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/test_db.rs | 9 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/tests.rs | 149 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/tests/simple.rs | 1 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/unsafe_validation.rs | 120 |
7 files changed, 307 insertions, 2 deletions
diff --git a/crates/ra_hir_ty/src/diagnostics.rs b/crates/ra_hir_ty/src/diagnostics.rs index ebd9cb08f..a59efb347 100644 --- a/crates/ra_hir_ty/src/diagnostics.rs +++ b/crates/ra_hir_ty/src/diagnostics.rs | |||
@@ -169,3 +169,31 @@ impl AstDiagnostic for BreakOutsideOfLoop { | |||
169 | ast::Expr::cast(node).unwrap() | 169 | ast::Expr::cast(node).unwrap() |
170 | } | 170 | } |
171 | } | 171 | } |
172 | |||
173 | #[derive(Debug)] | ||
174 | pub struct MissingUnsafe { | ||
175 | pub file: HirFileId, | ||
176 | pub expr: AstPtr<ast::Expr>, | ||
177 | } | ||
178 | |||
179 | impl Diagnostic for MissingUnsafe { | ||
180 | fn message(&self) -> String { | ||
181 | format!("This operation is unsafe and requires an unsafe function or block") | ||
182 | } | ||
183 | fn source(&self) -> InFile<SyntaxNodePtr> { | ||
184 | InFile { file_id: self.file, value: self.expr.clone().into() } | ||
185 | } | ||
186 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
187 | self | ||
188 | } | ||
189 | } | ||
190 | |||
191 | impl AstDiagnostic for MissingUnsafe { | ||
192 | type AST = ast::Expr; | ||
193 | |||
194 | fn ast(&self, db: &impl AstDatabase) -> Self::AST { | ||
195 | let root = db.parse_or_expand(self.source().file_id).unwrap(); | ||
196 | let node = self.source().value.to_node(&root); | ||
197 | ast::Expr::cast(node).unwrap() | ||
198 | } | ||
199 | } | ||
diff --git a/crates/ra_hir_ty/src/infer/expr.rs b/crates/ra_hir_ty/src/infer/expr.rs index a9565a58d..61af5f064 100644 --- a/crates/ra_hir_ty/src/infer/expr.rs +++ b/crates/ra_hir_ty/src/infer/expr.rs | |||
@@ -142,6 +142,7 @@ impl<'a> InferenceContext<'a> { | |||
142 | // FIXME: Breakable block inference | 142 | // FIXME: Breakable block inference |
143 | self.infer_block(statements, *tail, expected) | 143 | self.infer_block(statements, *tail, expected) |
144 | } | 144 | } |
145 | Expr::Unsafe { body } => self.infer_expr(*body, expected), | ||
145 | Expr::TryBlock { body } => { | 146 | Expr::TryBlock { body } => { |
146 | let _inner = self.infer_expr(*body, expected); | 147 | let _inner = self.infer_expr(*body, expected); |
147 | // FIXME should be std::result::Result<{inner}, _> | 148 | // FIXME should be std::result::Result<{inner}, _> |
diff --git a/crates/ra_hir_ty/src/lib.rs b/crates/ra_hir_ty/src/lib.rs index f22232324..414158139 100644 --- a/crates/ra_hir_ty/src/lib.rs +++ b/crates/ra_hir_ty/src/lib.rs | |||
@@ -37,6 +37,7 @@ pub(crate) mod utils; | |||
37 | pub mod db; | 37 | pub mod db; |
38 | pub mod diagnostics; | 38 | pub mod diagnostics; |
39 | pub mod expr; | 39 | pub mod expr; |
40 | pub mod unsafe_validation; | ||
40 | 41 | ||
41 | #[cfg(test)] | 42 | #[cfg(test)] |
42 | mod tests; | 43 | mod tests; |
diff --git a/crates/ra_hir_ty/src/test_db.rs b/crates/ra_hir_ty/src/test_db.rs index ad04e3e0f..9c2c6959d 100644 --- a/crates/ra_hir_ty/src/test_db.rs +++ b/crates/ra_hir_ty/src/test_db.rs | |||
@@ -11,7 +11,10 @@ use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, SourceDataba | |||
11 | use rustc_hash::FxHashSet; | 11 | use rustc_hash::FxHashSet; |
12 | use stdx::format_to; | 12 | use stdx::format_to; |
13 | 13 | ||
14 | use crate::{db::HirDatabase, diagnostics::Diagnostic, expr::ExprValidator}; | 14 | use crate::{ |
15 | db::HirDatabase, diagnostics::Diagnostic, expr::ExprValidator, | ||
16 | unsafe_validation::UnsafeValidator, | ||
17 | }; | ||
15 | 18 | ||
16 | #[salsa::database( | 19 | #[salsa::database( |
17 | ra_db::SourceDatabaseExtStorage, | 20 | ra_db::SourceDatabaseExtStorage, |
@@ -119,7 +122,9 @@ impl TestDB { | |||
119 | let infer = self.infer(f.into()); | 122 | let infer = self.infer(f.into()); |
120 | let mut sink = DiagnosticSink::new(&mut cb); | 123 | let mut sink = DiagnosticSink::new(&mut cb); |
121 | infer.add_diagnostics(self, f, &mut sink); | 124 | infer.add_diagnostics(self, f, &mut sink); |
122 | let mut validator = ExprValidator::new(f, infer, &mut sink); | 125 | let mut validator = ExprValidator::new(f, infer.clone(), &mut sink); |
126 | validator.validate_body(self); | ||
127 | let mut validator = UnsafeValidator::new(f, infer, &mut sink); | ||
123 | validator.validate_body(self); | 128 | validator.validate_body(self); |
124 | } | 129 | } |
125 | } | 130 | } |
diff --git a/crates/ra_hir_ty/src/tests.rs b/crates/ra_hir_ty/src/tests.rs index 85ff26a36..2a85ce85d 100644 --- a/crates/ra_hir_ty/src/tests.rs +++ b/crates/ra_hir_ty/src/tests.rs | |||
@@ -539,6 +539,155 @@ fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() { | |||
539 | } | 539 | } |
540 | 540 | ||
541 | #[test] | 541 | #[test] |
542 | fn missing_unsafe_diagnostic_with_raw_ptr() { | ||
543 | let diagnostics = TestDB::with_files( | ||
544 | r" | ||
545 | //- /lib.rs | ||
546 | fn missing_unsafe() { | ||
547 | let x = &5 as *const usize; | ||
548 | let y = *x; | ||
549 | } | ||
550 | ", | ||
551 | ) | ||
552 | .diagnostics() | ||
553 | .0; | ||
554 | |||
555 | assert_snapshot!(diagnostics, @r#""*x": This operation is unsafe and requires an unsafe function or block"#); | ||
556 | } | ||
557 | |||
558 | #[test] | ||
559 | fn missing_unsafe_diagnostic_with_unsafe_call() { | ||
560 | let diagnostics = TestDB::with_files( | ||
561 | r" | ||
562 | //- /lib.rs | ||
563 | unsafe fn unsafe_fn() { | ||
564 | let x = &5 as *const usize; | ||
565 | let y = *x; | ||
566 | } | ||
567 | |||
568 | fn missing_unsafe() { | ||
569 | unsafe_fn(); | ||
570 | } | ||
571 | ", | ||
572 | ) | ||
573 | .diagnostics() | ||
574 | .0; | ||
575 | |||
576 | assert_snapshot!(diagnostics, @r#""unsafe_fn()": This operation is unsafe and requires an unsafe function or block"#); | ||
577 | } | ||
578 | |||
579 | #[test] | ||
580 | fn missing_unsafe_diagnostic_with_unsafe_method_call() { | ||
581 | let diagnostics = TestDB::with_files( | ||
582 | r" | ||
583 | struct HasUnsafe; | ||
584 | |||
585 | impl HasUnsafe { | ||
586 | unsafe fn unsafe_fn(&self) { | ||
587 | let x = &5 as *const usize; | ||
588 | let y = *x; | ||
589 | } | ||
590 | } | ||
591 | |||
592 | fn missing_unsafe() { | ||
593 | HasUnsafe.unsafe_fn(); | ||
594 | } | ||
595 | |||
596 | ", | ||
597 | ) | ||
598 | .diagnostics() | ||
599 | .0; | ||
600 | |||
601 | assert_snapshot!(diagnostics, @r#""HasUnsafe.unsafe_fn()": This operation is unsafe and requires an unsafe function or block"#); | ||
602 | } | ||
603 | |||
604 | #[test] | ||
605 | fn no_missing_unsafe_diagnostic_with_raw_ptr_in_unsafe_block() { | ||
606 | let diagnostics = TestDB::with_files( | ||
607 | r" | ||
608 | fn nothing_to_see_move_along() { | ||
609 | let x = &5 as *const usize; | ||
610 | unsafe { | ||
611 | let y = *x; | ||
612 | } | ||
613 | } | ||
614 | ", | ||
615 | ) | ||
616 | .diagnostics() | ||
617 | .0; | ||
618 | |||
619 | assert_snapshot!(diagnostics, @""); | ||
620 | } | ||
621 | |||
622 | #[test] | ||
623 | fn missing_unsafe_diagnostic_with_raw_ptr_outside_unsafe_block() { | ||
624 | let diagnostics = TestDB::with_files( | ||
625 | r" | ||
626 | fn nothing_to_see_move_along() { | ||
627 | let x = &5 as *const usize; | ||
628 | unsafe { | ||
629 | let y = *x; | ||
630 | } | ||
631 | let z = *x; | ||
632 | } | ||
633 | ", | ||
634 | ) | ||
635 | .diagnostics() | ||
636 | .0; | ||
637 | |||
638 | assert_snapshot!(diagnostics, @r#""*x": This operation is unsafe and requires an unsafe function or block"#); | ||
639 | } | ||
640 | |||
641 | #[test] | ||
642 | fn no_missing_unsafe_diagnostic_with_unsafe_call_in_unsafe_block() { | ||
643 | let diagnostics = TestDB::with_files( | ||
644 | r" | ||
645 | unsafe fn unsafe_fn() { | ||
646 | let x = &5 as *const usize; | ||
647 | let y = *x; | ||
648 | } | ||
649 | |||
650 | fn nothing_to_see_move_along() { | ||
651 | unsafe { | ||
652 | unsafe_fn(); | ||
653 | } | ||
654 | } | ||
655 | ", | ||
656 | ) | ||
657 | .diagnostics() | ||
658 | .0; | ||
659 | |||
660 | assert_snapshot!(diagnostics, @""); | ||
661 | } | ||
662 | |||
663 | #[test] | ||
664 | fn no_missing_unsafe_diagnostic_with_unsafe_method_call_in_unsafe_block() { | ||
665 | let diagnostics = TestDB::with_files( | ||
666 | r" | ||
667 | struct HasUnsafe; | ||
668 | |||
669 | impl HasUnsafe { | ||
670 | unsafe fn unsafe_fn() { | ||
671 | let x = &5 as *const usize; | ||
672 | let y = *x; | ||
673 | } | ||
674 | } | ||
675 | |||
676 | fn nothing_to_see_move_along() { | ||
677 | unsafe { | ||
678 | HasUnsafe.unsafe_fn(); | ||
679 | } | ||
680 | } | ||
681 | |||
682 | ", | ||
683 | ) | ||
684 | .diagnostics() | ||
685 | .0; | ||
686 | |||
687 | assert_snapshot!(diagnostics, @""); | ||
688 | } | ||
689 | |||
690 | #[test] | ||
542 | fn break_outside_of_loop() { | 691 | fn break_outside_of_loop() { |
543 | let diagnostics = TestDB::with_files( | 692 | let diagnostics = TestDB::with_files( |
544 | r" | 693 | r" |
diff --git a/crates/ra_hir_ty/src/tests/simple.rs b/crates/ra_hir_ty/src/tests/simple.rs index 7d8197f8b..cd919466f 100644 --- a/crates/ra_hir_ty/src/tests/simple.rs +++ b/crates/ra_hir_ty/src/tests/simple.rs | |||
@@ -1880,6 +1880,7 @@ fn main() { | |||
1880 | @r###" | 1880 | @r###" |
1881 | 10..130 '{ ...2 }; }': () | 1881 | 10..130 '{ ...2 }; }': () |
1882 | 20..21 'x': i32 | 1882 | 20..21 'x': i32 |
1883 | 24..37 'unsafe { 92 }': i32 | ||
1883 | 31..37 '{ 92 }': i32 | 1884 | 31..37 '{ 92 }': i32 |
1884 | 33..35 '92': i32 | 1885 | 33..35 '92': i32 |
1885 | 47..48 'y': {unknown} | 1886 | 47..48 'y': {unknown} |
diff --git a/crates/ra_hir_ty/src/unsafe_validation.rs b/crates/ra_hir_ty/src/unsafe_validation.rs new file mode 100644 index 000000000..c512c4f8e --- /dev/null +++ b/crates/ra_hir_ty/src/unsafe_validation.rs | |||
@@ -0,0 +1,120 @@ | |||
1 | //! Provides validations for unsafe code. Currently checks if unsafe functions are missing | ||
2 | //! unsafe blocks. | ||
3 | |||
4 | use std::sync::Arc; | ||
5 | |||
6 | use hir_def::{ | ||
7 | body::Body, | ||
8 | expr::{Expr, ExprId, UnaryOp}, | ||
9 | DefWithBodyId, FunctionId, | ||
10 | }; | ||
11 | use hir_expand::diagnostics::DiagnosticSink; | ||
12 | |||
13 | use crate::{ | ||
14 | db::HirDatabase, diagnostics::MissingUnsafe, lower::CallableDef, ApplicationTy, | ||
15 | InferenceResult, Ty, TypeCtor, | ||
16 | }; | ||
17 | |||
18 | pub struct UnsafeValidator<'a, 'b: 'a> { | ||
19 | func: FunctionId, | ||
20 | infer: Arc<InferenceResult>, | ||
21 | sink: &'a mut DiagnosticSink<'b>, | ||
22 | } | ||
23 | |||
24 | impl<'a, 'b> UnsafeValidator<'a, 'b> { | ||
25 | pub fn new( | ||
26 | func: FunctionId, | ||
27 | infer: Arc<InferenceResult>, | ||
28 | sink: &'a mut DiagnosticSink<'b>, | ||
29 | ) -> UnsafeValidator<'a, 'b> { | ||
30 | UnsafeValidator { func, infer, sink } | ||
31 | } | ||
32 | |||
33 | pub fn validate_body(&mut self, db: &dyn HirDatabase) { | ||
34 | let def = self.func.into(); | ||
35 | let unsafe_expressions = unsafe_expressions(db, self.infer.as_ref(), def); | ||
36 | let func_data = db.function_data(self.func); | ||
37 | if func_data.is_unsafe | ||
38 | || unsafe_expressions | ||
39 | .iter() | ||
40 | .filter(|unsafe_expr| !unsafe_expr.inside_unsafe_block) | ||
41 | .count() | ||
42 | == 0 | ||
43 | { | ||
44 | return; | ||
45 | } | ||
46 | |||
47 | let (_, body_source) = db.body_with_source_map(def); | ||
48 | for unsafe_expr in unsafe_expressions { | ||
49 | if !unsafe_expr.inside_unsafe_block { | ||
50 | if let Ok(in_file) = body_source.as_ref().expr_syntax(unsafe_expr.expr) { | ||
51 | self.sink.push(MissingUnsafe { file: in_file.file_id, expr: in_file.value }) | ||
52 | } | ||
53 | } | ||
54 | } | ||
55 | } | ||
56 | } | ||
57 | |||
58 | pub struct UnsafeExpr { | ||
59 | pub expr: ExprId, | ||
60 | pub inside_unsafe_block: bool, | ||
61 | } | ||
62 | |||
63 | pub fn unsafe_expressions( | ||
64 | db: &dyn HirDatabase, | ||
65 | infer: &InferenceResult, | ||
66 | def: DefWithBodyId, | ||
67 | ) -> Vec<UnsafeExpr> { | ||
68 | let mut unsafe_exprs = vec![]; | ||
69 | let body = db.body(def); | ||
70 | walk_unsafe(&mut unsafe_exprs, db, infer, &body, body.body_expr, false); | ||
71 | |||
72 | unsafe_exprs | ||
73 | } | ||
74 | |||
75 | fn walk_unsafe( | ||
76 | unsafe_exprs: &mut Vec<UnsafeExpr>, | ||
77 | db: &dyn HirDatabase, | ||
78 | infer: &InferenceResult, | ||
79 | body: &Body, | ||
80 | current: ExprId, | ||
81 | inside_unsafe_block: bool, | ||
82 | ) { | ||
83 | let expr = &body.exprs[current]; | ||
84 | match expr { | ||
85 | Expr::Call { callee, .. } => { | ||
86 | let ty = &infer[*callee]; | ||
87 | if let &Ty::Apply(ApplicationTy { | ||
88 | ctor: TypeCtor::FnDef(CallableDef::FunctionId(func)), | ||
89 | .. | ||
90 | }) = ty | ||
91 | { | ||
92 | if db.function_data(func).is_unsafe { | ||
93 | unsafe_exprs.push(UnsafeExpr { expr: current, inside_unsafe_block }); | ||
94 | } | ||
95 | } | ||
96 | } | ||
97 | Expr::MethodCall { .. } => { | ||
98 | if infer | ||
99 | .method_resolution(current) | ||
100 | .map(|func| db.function_data(func).is_unsafe) | ||
101 | .unwrap_or(false) | ||
102 | { | ||
103 | unsafe_exprs.push(UnsafeExpr { expr: current, inside_unsafe_block }); | ||
104 | } | ||
105 | } | ||
106 | Expr::UnaryOp { expr, op: UnaryOp::Deref } => { | ||
107 | if let Ty::Apply(ApplicationTy { ctor: TypeCtor::RawPtr(..), .. }) = &infer[*expr] { | ||
108 | unsafe_exprs.push(UnsafeExpr { expr: current, inside_unsafe_block }); | ||
109 | } | ||
110 | } | ||
111 | Expr::Unsafe { body: child } => { | ||
112 | return walk_unsafe(unsafe_exprs, db, infer, body, *child, true); | ||
113 | } | ||
114 | _ => {} | ||
115 | } | ||
116 | |||
117 | expr.walk_child_exprs(|child| { | ||
118 | walk_unsafe(unsafe_exprs, db, infer, body, child, inside_unsafe_block); | ||
119 | }); | ||
120 | } | ||