]>
Commit | Line | Data |
---|---|---|
dfeec247 XL |
1 | use rustc_data_structures::fx::FxHashSet; |
2 | use rustc_errors::struct_span_err; | |
3 | use rustc_hir as hir; | |
f9f354fc XL |
4 | use rustc_hir::def_id::{DefId, LocalDefId}; |
5 | use rustc_hir::hir_id::HirId; | |
dfeec247 XL |
6 | use rustc_hir::intravisit; |
7 | use rustc_hir::Node; | |
ba9703b0 XL |
8 | use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor}; |
9 | use rustc_middle::mir::*; | |
ba9703b0 XL |
10 | use rustc_middle::ty::query::Providers; |
11 | use rustc_middle::ty::{self, TyCtxt}; | |
cdc7bbd5 | 12 | use rustc_session::lint::builtin::{UNSAFE_OP_IN_UNSAFE_FN, UNUSED_UNSAFE}; |
f9f354fc | 13 | use rustc_session::lint::Level; |
ea8adc8c | 14 | |
0731742a XL |
15 | use std::ops::Bound; |
16 | ||
dc9dc135 XL |
17 | pub struct UnsafetyChecker<'a, 'tcx> { |
18 | body: &'a Body<'tcx>, | |
f9f354fc | 19 | body_did: LocalDefId, |
ea8adc8c XL |
20 | violations: Vec<UnsafetyViolation>, |
21 | source_info: SourceInfo, | |
dc9dc135 | 22 | tcx: TyCtxt<'tcx>, |
ea8adc8c | 23 | param_env: ty::ParamEnv<'tcx>, |
9fa01778 | 24 | /// Mark an `unsafe` block as used, so we don't lint it. |
532ac7d7 XL |
25 | used_unsafe: FxHashSet<hir::HirId>, |
26 | inherited_blocks: Vec<(hir::HirId, bool)>, | |
ea8adc8c XL |
27 | } |
28 | ||
dc9dc135 | 29 | impl<'a, 'tcx> UnsafetyChecker<'a, 'tcx> { |
b7449926 | 30 | fn new( |
dc9dc135 | 31 | body: &'a Body<'tcx>, |
f9f354fc | 32 | body_did: LocalDefId, |
dc9dc135 | 33 | tcx: TyCtxt<'tcx>, |
b7449926 XL |
34 | param_env: ty::ParamEnv<'tcx>, |
35 | ) -> Self { | |
ea8adc8c | 36 | Self { |
dc9dc135 | 37 | body, |
f9f354fc | 38 | body_did, |
ea8adc8c | 39 | violations: vec![], |
f9f354fc | 40 | source_info: SourceInfo::outermost(body.span), |
ea8adc8c XL |
41 | tcx, |
42 | param_env, | |
0bf4aa26 | 43 | used_unsafe: Default::default(), |
ea8adc8c XL |
44 | inherited_blocks: vec![], |
45 | } | |
46 | } | |
47 | } | |
48 | ||
49 | impl<'a, 'tcx> Visitor<'tcx> for UnsafetyChecker<'a, 'tcx> { | |
dfeec247 | 50 | fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { |
ea8adc8c XL |
51 | self.source_info = terminator.source_info; |
52 | match terminator.kind { | |
dfeec247 XL |
53 | TerminatorKind::Goto { .. } |
54 | | TerminatorKind::SwitchInt { .. } | |
55 | | TerminatorKind::Drop { .. } | |
56 | | TerminatorKind::Yield { .. } | |
57 | | TerminatorKind::Assert { .. } | |
58 | | TerminatorKind::DropAndReplace { .. } | |
59 | | TerminatorKind::GeneratorDrop | |
60 | | TerminatorKind::Resume | |
61 | | TerminatorKind::Abort | |
62 | | TerminatorKind::Return | |
63 | | TerminatorKind::Unreachable | |
f035d41b | 64 | | TerminatorKind::FalseEdge { .. } |
dfeec247 | 65 | | TerminatorKind::FalseUnwind { .. } => { |
ea8adc8c XL |
66 | // safe (at least as emitted during MIR construction) |
67 | } | |
68 | ||
69 | TerminatorKind::Call { ref func, .. } => { | |
dc9dc135 | 70 | let func_ty = func.ty(self.body, self.tcx); |
ea8adc8c XL |
71 | let sig = func_ty.fn_sig(self.tcx); |
72 | if let hir::Unsafety::Unsafe = sig.unsafety() { | |
dfeec247 | 73 | self.require_unsafe( |
cdc7bbd5 | 74 | UnsafetyViolationKind::General, |
3dfed10e | 75 | UnsafetyViolationDetails::CallToUnsafeFunction, |
dfeec247 | 76 | ) |
ea8adc8c | 77 | } |
f9f354fc | 78 | |
1b1a35ee XL |
79 | if let ty::FnDef(func_id, _) = func_ty.kind() { |
80 | self.check_target_features(*func_id); | |
f9f354fc | 81 | } |
ea8adc8c | 82 | } |
f9f354fc XL |
83 | |
84 | TerminatorKind::InlineAsm { .. } => self.require_unsafe( | |
f9f354fc | 85 | UnsafetyViolationKind::General, |
3dfed10e | 86 | UnsafetyViolationDetails::UseOfInlineAssembly, |
f9f354fc | 87 | ), |
ea8adc8c | 88 | } |
48663c56 | 89 | self.super_terminator(terminator, location); |
ea8adc8c XL |
90 | } |
91 | ||
dfeec247 | 92 | fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { |
ea8adc8c XL |
93 | self.source_info = statement.source_info; |
94 | match statement.kind { | |
dfeec247 XL |
95 | StatementKind::Assign(..) |
96 | | StatementKind::FakeRead(..) | |
97 | | StatementKind::SetDiscriminant { .. } | |
98 | | StatementKind::StorageLive(..) | |
99 | | StatementKind::StorageDead(..) | |
100 | | StatementKind::Retag { .. } | |
101 | | StatementKind::AscribeUserType(..) | |
3dfed10e | 102 | | StatementKind::Coverage(..) |
dfeec247 | 103 | | StatementKind::Nop => { |
ea8adc8c XL |
104 | // safe (at least as emitted during MIR construction) |
105 | } | |
106 | ||
ba9703b0 | 107 | StatementKind::LlvmInlineAsm { .. } => self.require_unsafe( |
dfeec247 | 108 | UnsafetyViolationKind::General, |
3dfed10e | 109 | UnsafetyViolationDetails::UseOfInlineAssembly, |
dfeec247 | 110 | ), |
6a06907d | 111 | StatementKind::CopyNonOverlapping(..) => unreachable!(), |
ea8adc8c | 112 | } |
48663c56 | 113 | self.super_statement(statement, location); |
ea8adc8c XL |
114 | } |
115 | ||
dfeec247 | 116 | fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { |
9fa01778 | 117 | match rvalue { |
dfeec247 XL |
118 | Rvalue::Aggregate(box ref aggregate, _) => match aggregate { |
119 | &AggregateKind::Array(..) | &AggregateKind::Tuple => {} | |
120 | &AggregateKind::Adt(ref def, ..) => { | |
121 | match self.tcx.layout_scalar_valid_range(def.did) { | |
122 | (Bound::Unbounded, Bound::Unbounded) => {} | |
123 | _ => self.require_unsafe( | |
cdc7bbd5 | 124 | UnsafetyViolationKind::General, |
3dfed10e | 125 | UnsafetyViolationDetails::InitializingTypeWith, |
dfeec247 | 126 | ), |
0731742a XL |
127 | } |
128 | } | |
dfeec247 XL |
129 | &AggregateKind::Closure(def_id, _) | &AggregateKind::Generator(def_id, _, _) => { |
130 | let UnsafetyCheckResult { violations, unsafe_blocks } = | |
f9f354fc | 131 | self.tcx.unsafety_check_result(def_id.expect_local()); |
dfeec247 XL |
132 | self.register_violations(&violations, &unsafe_blocks); |
133 | } | |
9fa01778 | 134 | }, |
dfeec247 | 135 | _ => {} |
ea8adc8c XL |
136 | } |
137 | self.super_rvalue(rvalue, location); | |
138 | } | |
139 | ||
dfeec247 | 140 | fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _location: Location) { |
f9f354fc | 141 | // On types with `scalar_valid_range`, prevent |
74b04a01 XL |
142 | // * `&mut x.field` |
143 | // * `x.field = y;` | |
144 | // * `&x.field` if `field`'s type has interior mutability | |
145 | // because either of these would allow modifying the layout constrained field and | |
146 | // insert values that violate the layout constraints. | |
147 | if context.is_mutating_use() || context.is_borrow() { | |
ba9703b0 | 148 | self.check_mut_borrowing_layout_constrained_field(*place, context.is_mutating_use()); |
74b04a01 XL |
149 | } |
150 | ||
fc512014 XL |
151 | // Some checks below need the extra metainfo of the local declaration. |
152 | let decl = &self.body.local_decls[place.local]; | |
153 | ||
154 | // Check the base local: it might be an unsafe-to-access static. We only check derefs of the | |
155 | // temporary holding the static pointer to avoid duplicate errors | |
156 | // <https://github.com/rust-lang/rust/pull/78068#issuecomment-731753506>. | |
157 | if decl.internal && place.projection.first() == Some(&ProjectionElem::Deref) { | |
158 | // If the projection root is an artifical local that we introduced when | |
159 | // desugaring `static`, give a more specific error message | |
160 | // (avoid the general "raw pointer" clause below, that would only be confusing). | |
161 | if let Some(box LocalInfo::StaticRef { def_id, .. }) = decl.local_info { | |
162 | if self.tcx.is_mutable_static(def_id) { | |
163 | self.require_unsafe( | |
164 | UnsafetyViolationKind::General, | |
165 | UnsafetyViolationDetails::UseOfMutableStatic, | |
166 | ); | |
167 | return; | |
168 | } else if self.tcx.is_foreign_item(def_id) { | |
74b04a01 | 169 | self.require_unsafe( |
fc512014 XL |
170 | UnsafetyViolationKind::General, |
171 | UnsafetyViolationDetails::UseOfExternStatic, | |
dfeec247 | 172 | ); |
fc512014 | 173 | return; |
0731742a | 174 | } |
e1599b0c | 175 | } |
fc512014 XL |
176 | } |
177 | ||
178 | // Check for raw pointer `Deref`. | |
179 | for (base, proj) in place.iter_projections() { | |
180 | if proj == ProjectionElem::Deref { | |
fc512014 XL |
181 | let base_ty = base.ty(self.body, self.tcx).ty; |
182 | if base_ty.is_unsafe_ptr() { | |
183 | self.require_unsafe( | |
cdc7bbd5 | 184 | UnsafetyViolationKind::General, |
fc512014 XL |
185 | UnsafetyViolationDetails::DerefOfRawPointer, |
186 | ) | |
0731742a | 187 | } |
fc512014 XL |
188 | } |
189 | } | |
190 | ||
191 | // Check for union fields. For this we traverse right-to-left, as the last `Deref` changes | |
192 | // whether we *read* the union field or potentially *write* to it (if this place is being assigned to). | |
193 | let mut saw_deref = false; | |
194 | for (base, proj) in place.iter_projections().rev() { | |
195 | if proj == ProjectionElem::Deref { | |
196 | saw_deref = true; | |
197 | continue; | |
e1599b0c | 198 | } |
fc512014 XL |
199 | |
200 | let base_ty = base.ty(self.body, self.tcx).ty; | |
17df50a5 | 201 | if base_ty.is_union() { |
fc512014 XL |
202 | // If we did not hit a `Deref` yet and the overall place use is an assignment, the |
203 | // rules are different. | |
204 | let assign_to_field = !saw_deref | |
205 | && matches!( | |
206 | context, | |
207 | PlaceContext::MutatingUse( | |
208 | MutatingUseContext::Store | |
209 | | MutatingUseContext::Drop | |
210 | | MutatingUseContext::AsmOutput | |
211 | ) | |
212 | ); | |
213 | // If this is just an assignment, determine if the assigned type needs dropping. | |
214 | if assign_to_field { | |
215 | // We have to check the actual type of the assignment, as that determines if the | |
216 | // old value is being dropped. | |
217 | let assigned_ty = place.ty(&self.body.local_decls, self.tcx).ty; | |
218 | // To avoid semver hazard, we only consider `Copy` and `ManuallyDrop` non-dropping. | |
219 | let manually_drop = assigned_ty | |
220 | .ty_adt_def() | |
221 | .map_or(false, |adt_def| adt_def.is_manually_drop()); | |
222 | let nodrop = manually_drop | |
223 | || assigned_ty.is_copy_modulo_regions( | |
224 | self.tcx.at(self.source_info.span), | |
225 | self.param_env, | |
226 | ); | |
227 | if !nodrop { | |
228 | self.require_unsafe( | |
cdc7bbd5 | 229 | UnsafetyViolationKind::General, |
fc512014 XL |
230 | UnsafetyViolationDetails::AssignToDroppingUnionField, |
231 | ); | |
232 | } else { | |
233 | // write to non-drop union field, safe | |
ea8adc8c | 234 | } |
fc512014 XL |
235 | } else { |
236 | self.require_unsafe( | |
cdc7bbd5 | 237 | UnsafetyViolationKind::General, |
fc512014 XL |
238 | UnsafetyViolationDetails::AccessToUnionField, |
239 | ) | |
ea8adc8c | 240 | } |
ea8adc8c | 241 | } |
e1599b0c | 242 | } |
ea8adc8c XL |
243 | } |
244 | } | |
245 | ||
246 | impl<'a, 'tcx> UnsafetyChecker<'a, 'tcx> { | |
3dfed10e | 247 | fn require_unsafe(&mut self, kind: UnsafetyViolationKind, details: UnsafetyViolationDetails) { |
cdc7bbd5 XL |
248 | // Violations can turn out to be `UnsafeFn` during analysis, but they should not start out as such. |
249 | assert_ne!(kind, UnsafetyViolationKind::UnsafeFn); | |
250 | ||
ea8adc8c | 251 | let source_info = self.source_info; |
f9f354fc XL |
252 | let lint_root = self.body.source_scopes[self.source_info.scope] |
253 | .local_data | |
254 | .as_ref() | |
255 | .assert_crate_local() | |
256 | .lint_root; | |
dfeec247 | 257 | self.register_violations( |
3dfed10e | 258 | &[UnsafetyViolation { source_info, lint_root, kind, details }], |
dfeec247 XL |
259 | &[], |
260 | ); | |
ea8adc8c XL |
261 | } |
262 | ||
dfeec247 XL |
263 | fn register_violations( |
264 | &mut self, | |
265 | violations: &[UnsafetyViolation], | |
266 | unsafe_blocks: &[(hir::HirId, bool)], | |
267 | ) { | |
60c5eb7d XL |
268 | let safety = self.body.source_scopes[self.source_info.scope] |
269 | .local_data | |
270 | .as_ref() | |
271 | .assert_crate_local() | |
272 | .safety; | |
0731742a XL |
273 | let within_unsafe = match safety { |
274 | // `unsafe` blocks are required in safe code | |
ea8adc8c XL |
275 | Safety::Safe => { |
276 | for violation in violations { | |
0731742a | 277 | match violation.kind { |
cdc7bbd5 XL |
278 | UnsafetyViolationKind::General => {} |
279 | UnsafetyViolationKind::UnsafeFn => { | |
f9f354fc XL |
280 | bug!("`UnsafetyViolationKind::UnsafeFn` in an `Safe` context") |
281 | } | |
0731742a | 282 | } |
cdc7bbd5 XL |
283 | if !self.violations.contains(violation) { |
284 | self.violations.push(*violation) | |
ea8adc8c XL |
285 | } |
286 | } | |
ea8adc8c XL |
287 | false |
288 | } | |
f9f354fc | 289 | // With the RFC 2585, no longer allow `unsafe` operations in `unsafe fn`s |
6a06907d | 290 | Safety::FnUnsafe => { |
f9f354fc XL |
291 | for violation in violations { |
292 | let mut violation = *violation; | |
293 | ||
cdc7bbd5 | 294 | violation.kind = UnsafetyViolationKind::UnsafeFn; |
f9f354fc XL |
295 | if !self.violations.contains(&violation) { |
296 | self.violations.push(violation) | |
297 | } | |
298 | } | |
299 | false | |
300 | } | |
6a06907d | 301 | Safety::BuiltinUnsafe => true, |
532ac7d7 | 302 | Safety::ExplicitUnsafe(hir_id) => { |
0731742a | 303 | // mark unsafe block as used if there are any unsafe operations inside |
ea8adc8c | 304 | if !violations.is_empty() { |
532ac7d7 | 305 | self.used_unsafe.insert(hir_id); |
ea8adc8c XL |
306 | } |
307 | true | |
308 | } | |
309 | }; | |
dfeec247 XL |
310 | self.inherited_blocks.extend( |
311 | unsafe_blocks.iter().map(|&(hir_id, is_used)| (hir_id, is_used && !within_unsafe)), | |
312 | ); | |
ea8adc8c | 313 | } |
0731742a XL |
314 | fn check_mut_borrowing_layout_constrained_field( |
315 | &mut self, | |
ba9703b0 | 316 | place: Place<'tcx>, |
0731742a XL |
317 | is_mut_use: bool, |
318 | ) { | |
5869c6ff | 319 | for (place_base, elem) in place.iter_projections().rev() { |
e1599b0c | 320 | match elem { |
74b04a01 XL |
321 | // Modifications behind a dereference don't affect the value of |
322 | // the pointer. | |
323 | ProjectionElem::Deref => return, | |
0731742a | 324 | ProjectionElem::Field(..) => { |
5869c6ff | 325 | let ty = place_base.ty(&self.body.local_decls, self.tcx).ty; |
1b1a35ee | 326 | if let ty::Adt(def, _) = ty.kind() { |
ba9703b0 XL |
327 | if self.tcx.layout_scalar_valid_range(def.did) |
328 | != (Bound::Unbounded, Bound::Unbounded) | |
329 | { | |
3dfed10e XL |
330 | let details = if is_mut_use { |
331 | UnsafetyViolationDetails::MutationOfLayoutConstrainedField | |
74b04a01 | 332 | |
ba9703b0 XL |
333 | // Check `is_freeze` as late as possible to avoid cycle errors |
334 | // with opaque types. | |
f035d41b XL |
335 | } else if !place |
336 | .ty(self.body, self.tcx) | |
337 | .ty | |
338 | .is_freeze(self.tcx.at(self.source_info.span), self.param_env) | |
339 | { | |
3dfed10e | 340 | UnsafetyViolationDetails::BorrowOfLayoutConstrainedField |
ba9703b0 XL |
341 | } else { |
342 | continue; | |
343 | }; | |
cdc7bbd5 | 344 | self.require_unsafe(UnsafetyViolationKind::General, details); |
ba9703b0 | 345 | } |
0731742a XL |
346 | } |
347 | } | |
348 | _ => {} | |
349 | } | |
0731742a XL |
350 | } |
351 | } | |
f9f354fc XL |
352 | |
353 | /// Checks whether calling `func_did` needs an `unsafe` context or not, i.e. whether | |
354 | /// the called function has target features the calling function hasn't. | |
355 | fn check_target_features(&mut self, func_did: DefId) { | |
17df50a5 XL |
356 | // Unsafety isn't required on wasm targets. For more information see |
357 | // the corresponding check in typeck/src/collect.rs | |
358 | if self.tcx.sess.target.options.is_like_wasm { | |
359 | return; | |
360 | } | |
361 | ||
f9f354fc XL |
362 | let callee_features = &self.tcx.codegen_fn_attrs(func_did).target_features; |
363 | let self_features = &self.tcx.codegen_fn_attrs(self.body_did).target_features; | |
364 | ||
365 | // Is `callee_features` a subset of `calling_features`? | |
366 | if !callee_features.iter().all(|feature| self_features.contains(feature)) { | |
367 | self.require_unsafe( | |
cdc7bbd5 | 368 | UnsafetyViolationKind::General, |
3dfed10e | 369 | UnsafetyViolationDetails::CallToFunctionWith, |
f9f354fc XL |
370 | ) |
371 | } | |
372 | } | |
ea8adc8c XL |
373 | } |
374 | ||
f035d41b | 375 | pub(crate) fn provide(providers: &mut Providers) { |
3dfed10e XL |
376 | *providers = Providers { |
377 | unsafety_check_result: |tcx, def_id| { | |
378 | if let Some(def) = ty::WithOptConstParam::try_lookup(def_id, tcx) { | |
379 | tcx.unsafety_check_result_for_const_arg(def) | |
380 | } else { | |
381 | unsafety_check_result(tcx, ty::WithOptConstParam::unknown(def_id)) | |
382 | } | |
383 | }, | |
384 | unsafety_check_result_for_const_arg: |tcx, (did, param_did)| { | |
385 | unsafety_check_result( | |
386 | tcx, | |
387 | ty::WithOptConstParam { did, const_param_did: Some(param_did) }, | |
388 | ) | |
389 | }, | |
3dfed10e XL |
390 | ..*providers |
391 | }; | |
ea8adc8c XL |
392 | } |
393 | ||
394 | struct UnusedUnsafeVisitor<'a> { | |
532ac7d7 XL |
395 | used_unsafe: &'a FxHashSet<hir::HirId>, |
396 | unsafe_blocks: &'a mut Vec<(hir::HirId, bool)>, | |
ea8adc8c XL |
397 | } |
398 | ||
dfeec247 | 399 | impl<'a, 'tcx> intravisit::Visitor<'tcx> for UnusedUnsafeVisitor<'a> { |
ba9703b0 | 400 | type Map = intravisit::ErasedMap<'tcx>; |
dfeec247 | 401 | |
ba9703b0 | 402 | fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> { |
dfeec247 | 403 | intravisit::NestedVisitorMap::None |
ea8adc8c XL |
404 | } |
405 | ||
dfeec247 XL |
406 | fn visit_block(&mut self, block: &'tcx hir::Block<'tcx>) { |
407 | intravisit::walk_block(self, block); | |
ea8adc8c | 408 | |
dfeec247 | 409 | if let hir::BlockCheckMode::UnsafeBlock(hir::UnsafeSource::UserProvided) = block.rules { |
532ac7d7 | 410 | self.unsafe_blocks.push((block.hir_id, self.used_unsafe.contains(&block.hir_id))); |
ea8adc8c XL |
411 | } |
412 | } | |
413 | } | |
414 | ||
416331ca XL |
415 | fn check_unused_unsafe( |
416 | tcx: TyCtxt<'_>, | |
f9f354fc | 417 | def_id: LocalDefId, |
dc9dc135 | 418 | used_unsafe: &FxHashSet<hir::HirId>, |
416331ca | 419 | unsafe_blocks: &mut Vec<(hir::HirId, bool)>, |
dc9dc135 | 420 | ) { |
3dfed10e | 421 | let body_id = tcx.hir().maybe_body_owned_by(tcx.hir().local_def_id_to_hir_id(def_id)); |
ea8adc8c XL |
422 | |
423 | let body_id = match body_id { | |
424 | Some(body) => body, | |
425 | None => { | |
426 | debug!("check_unused_unsafe({:?}) - no body found", def_id); | |
dfeec247 | 427 | return; |
ea8adc8c XL |
428 | } |
429 | }; | |
0731742a | 430 | let body = tcx.hir().body(body_id); |
dfeec247 | 431 | debug!("check_unused_unsafe({:?}, body={:?}, used_unsafe={:?})", def_id, body, used_unsafe); |
ea8adc8c | 432 | |
dfeec247 XL |
433 | let mut visitor = UnusedUnsafeVisitor { used_unsafe, unsafe_blocks }; |
434 | intravisit::Visitor::visit_body(&mut visitor, body); | |
ea8adc8c XL |
435 | } |
436 | ||
3dfed10e XL |
437 | fn unsafety_check_result<'tcx>( |
438 | tcx: TyCtxt<'tcx>, | |
439 | def: ty::WithOptConstParam<LocalDefId>, | |
440 | ) -> &'tcx UnsafetyCheckResult { | |
441 | debug!("unsafety_violations({:?})", def); | |
ea8adc8c | 442 | |
0731742a | 443 | // N.B., this borrow is valid because all the consumers of |
ea8adc8c | 444 | // `mir_built` force this. |
3dfed10e | 445 | let body = &tcx.mir_built(def).borrow(); |
ea8adc8c | 446 | |
3dfed10e | 447 | let param_env = tcx.param_env(def.did); |
9fa01778 | 448 | |
136023e0 | 449 | let mut checker = UnsafetyChecker::new(body, def.did, tcx, param_env); |
ba9703b0 | 450 | checker.visit_body(&body); |
ea8adc8c | 451 | |
3dfed10e XL |
452 | check_unused_unsafe(tcx, def.did, &checker.used_unsafe, &mut checker.inherited_blocks); |
453 | ||
454 | tcx.arena.alloc(UnsafetyCheckResult { | |
ea8adc8c | 455 | violations: checker.violations.into(), |
dfeec247 | 456 | unsafe_blocks: checker.inherited_blocks.into(), |
3dfed10e | 457 | }) |
ea8adc8c XL |
458 | } |
459 | ||
532ac7d7 | 460 | /// Returns the `HirId` for an enclosing scope that is also `unsafe`. |
dc9dc135 XL |
461 | fn is_enclosed( |
462 | tcx: TyCtxt<'_>, | |
463 | used_unsafe: &FxHashSet<hir::HirId>, | |
464 | id: hir::HirId, | |
5869c6ff XL |
465 | unsafe_op_in_unsafe_fn_allowed: bool, |
466 | ) -> Option<(&'static str, hir::HirId)> { | |
dc9dc135 | 467 | let parent_id = tcx.hir().get_parent_node(id); |
ea8adc8c XL |
468 | if parent_id != id { |
469 | if used_unsafe.contains(&parent_id) { | |
5869c6ff | 470 | Some(("block", parent_id)) |
b7449926 | 471 | } else if let Some(Node::Item(&hir::Item { |
dfeec247 XL |
472 | kind: hir::ItemKind::Fn(ref sig, _, _), .. |
473 | })) = tcx.hir().find(parent_id) | |
474 | { | |
5869c6ff XL |
475 | if sig.header.unsafety == hir::Unsafety::Unsafe && unsafe_op_in_unsafe_fn_allowed { |
476 | Some(("fn", parent_id)) | |
f9f354fc XL |
477 | } else { |
478 | None | |
0531ce1d | 479 | } |
ea8adc8c | 480 | } else { |
5869c6ff | 481 | is_enclosed(tcx, used_unsafe, parent_id, unsafe_op_in_unsafe_fn_allowed) |
ea8adc8c XL |
482 | } |
483 | } else { | |
484 | None | |
485 | } | |
486 | } | |
487 | ||
dc9dc135 | 488 | fn report_unused_unsafe(tcx: TyCtxt<'_>, used_unsafe: &FxHashSet<hir::HirId>, id: hir::HirId) { |
ba9703b0 | 489 | let span = tcx.sess.source_map().guess_head_span(tcx.hir().span(id)); |
74b04a01 XL |
490 | tcx.struct_span_lint_hir(UNUSED_UNSAFE, id, span, |lint| { |
491 | let msg = "unnecessary `unsafe` block"; | |
492 | let mut db = lint.build(msg); | |
493 | db.span_label(span, msg); | |
5869c6ff XL |
494 | if let Some((kind, id)) = |
495 | is_enclosed(tcx, used_unsafe, id, unsafe_op_in_unsafe_fn_allowed(tcx, id)) | |
496 | { | |
74b04a01 | 497 | db.span_label( |
ba9703b0 | 498 | tcx.sess.source_map().guess_head_span(tcx.hir().span(id)), |
74b04a01 XL |
499 | format!("because it's nested under this `unsafe` {}", kind), |
500 | ); | |
501 | } | |
502 | db.emit(); | |
503 | }); | |
ea8adc8c XL |
504 | } |
505 | ||
f9f354fc | 506 | pub fn check_unsafety(tcx: TyCtxt<'_>, def_id: LocalDefId) { |
ea8adc8c | 507 | debug!("check_unsafety({:?})", def_id); |
abe05a73 XL |
508 | |
509 | // closures are handled by their parent fn. | |
f9f354fc | 510 | if tcx.is_closure(def_id.to_def_id()) { |
abe05a73 XL |
511 | return; |
512 | } | |
ea8adc8c | 513 | |
dfeec247 | 514 | let UnsafetyCheckResult { violations, unsafe_blocks } = tcx.unsafety_check_result(def_id); |
ea8adc8c | 515 | |
3dfed10e XL |
516 | for &UnsafetyViolation { source_info, lint_root, kind, details } in violations.iter() { |
517 | let (description, note) = details.description_and_note(); | |
518 | ||
ea8adc8c | 519 | // Report an error. |
f9f354fc XL |
520 | let unsafe_fn_msg = |
521 | if unsafe_op_in_unsafe_fn_allowed(tcx, lint_root) { " function or" } else { "" }; | |
522 | ||
ff7c6d11 | 523 | match kind { |
cdc7bbd5 | 524 | UnsafetyViolationKind::General => { |
f9f354fc | 525 | // once |
ff7c6d11 | 526 | struct_span_err!( |
dfeec247 XL |
527 | tcx.sess, |
528 | source_info.span, | |
529 | E0133, | |
f9f354fc XL |
530 | "{} is unsafe and requires unsafe{} block", |
531 | description, | |
532 | unsafe_fn_msg, | |
dfeec247 | 533 | ) |
3dfed10e XL |
534 | .span_label(source_info.span, description) |
535 | .note(note) | |
dfeec247 | 536 | .emit(); |
ff7c6d11 | 537 | } |
f9f354fc XL |
538 | UnsafetyViolationKind::UnsafeFn => tcx.struct_span_lint_hir( |
539 | UNSAFE_OP_IN_UNSAFE_FN, | |
540 | lint_root, | |
541 | source_info.span, | |
542 | |lint| { | |
543 | lint.build(&format!( | |
544 | "{} is unsafe and requires unsafe block (error E0133)", | |
545 | description, | |
546 | )) | |
3dfed10e XL |
547 | .span_label(source_info.span, description) |
548 | .note(note) | |
f9f354fc XL |
549 | .emit(); |
550 | }, | |
551 | ), | |
ea8adc8c XL |
552 | } |
553 | } | |
554 | ||
ba9703b0 XL |
555 | let (mut unsafe_used, mut unsafe_unused): (FxHashSet<_>, Vec<_>) = Default::default(); |
556 | for &(block_id, is_used) in unsafe_blocks.iter() { | |
557 | if is_used { | |
558 | unsafe_used.insert(block_id); | |
559 | } else { | |
560 | unsafe_unused.push(block_id); | |
ea8adc8c XL |
561 | } |
562 | } | |
ba9703b0 XL |
563 | // The unused unsafe blocks might not be in source order; sort them so that the unused unsafe |
564 | // error messages are properly aligned and the issue-45107 and lint-unused-unsafe tests pass. | |
565 | unsafe_unused.sort_by_cached_key(|hir_id| tcx.hir().span(*hir_id)); | |
566 | ||
567 | for &block_id in &unsafe_unused { | |
568 | report_unused_unsafe(tcx, &unsafe_used, block_id); | |
569 | } | |
ea8adc8c | 570 | } |
f9f354fc XL |
571 | |
572 | fn unsafe_op_in_unsafe_fn_allowed(tcx: TyCtxt<'_>, id: HirId) -> bool { | |
573 | tcx.lint_level_at_node(UNSAFE_OP_IN_UNSAFE_FN, id).0 == Level::Allow | |
574 | } |