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