Skip to content

Commit 17b60b8

Browse files
committed
Auto merge of #83129 - LeSeulArtichaut:thir-unsafeck, r=nikomatsakis
Introduce the beginning of a THIR unsafety checker This poses the foundations for the THIR unsafety checker, so that it can be implemented incrementally: - implements a rudimentary `Visitor` for the THIR (which will definitely need some tweaking in the future) - introduces a new `-Zthir-unsafeck` flag which tells the compiler to use THIR unsafeck instead of MIR unsafeck - implements detection of unsafe functions - adds revisions to the UI tests to test THIR unsafeck alongside MIR unsafeck This uses a very simple query design, where bodies are unsafety-checked on a body per body basis. This however has some big flaws: - the unsafety-checker builds the THIR itself, which means a lot of work is duplicated with MIR building constructing its own copy of the THIR - unsafety-checking closures is currently completely wrong: closures should take into account the "safety context" in which they are created, here we are considering that closures are always a safe context I had intended to fix these problems in follow-up PRs since they are always gated under the `-Zthir-unsafeck` flag (which is explicitely noted to be unsound). r? `@nikomatsakis` cc rust-lang/project-thir-unsafeck#3 rust-lang/project-thir-unsafeck#7
2 parents 703f2e1 + 985fb4c commit 17b60b8

File tree

60 files changed

+1013
-52
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+1013
-52
lines changed

compiler/rustc_interface/src/passes.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -876,7 +876,11 @@ fn analysis(tcx: TyCtxt<'_>, cnum: CrateNum) -> Result<()> {
876876

877877
sess.time("MIR_effect_checking", || {
878878
for def_id in tcx.body_owners() {
879-
mir::transform::check_unsafety::check_unsafety(tcx, def_id);
879+
if tcx.sess.opts.debugging_opts.thir_unsafeck {
880+
tcx.ensure().thir_check_unsafety(def_id);
881+
} else {
882+
mir::transform::check_unsafety::check_unsafety(tcx, def_id);
883+
}
880884

881885
if tcx.hir().body_const_context(def_id).is_some() {
882886
tcx.ensure()

compiler/rustc_interface/src/tests.rs

+1
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,7 @@ fn test_debugging_options_tracking_hash() {
736736
tracked!(symbol_mangling_version, Some(SymbolManglingVersion::V0));
737737
tracked!(teach, true);
738738
tracked!(thinlto, Some(true));
739+
tracked!(thir_unsafeck, true);
739740
tracked!(tune_cpu, Some(String::from("abc")));
740741
tracked!(tls_model, Some(TlsModel::GeneralDynamic));
741742
tracked!(trap_unreachable, Some(false));

compiler/rustc_middle/src/query/mod.rs

+13
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,19 @@ rustc_queries! {
611611
}
612612
}
613613

614+
/// Unsafety-check this `LocalDefId` with THIR unsafeck. This should be
615+
/// used with `-Zthir-unsafeck`.
616+
query thir_check_unsafety(key: LocalDefId) {
617+
desc { |tcx| "unsafety-checking `{}`", tcx.def_path_str(key.to_def_id()) }
618+
cache_on_disk_if { true }
619+
}
620+
query thir_check_unsafety_for_const_arg(key: (LocalDefId, DefId)) {
621+
desc {
622+
|tcx| "unsafety-checking the const argument `{}`",
623+
tcx.def_path_str(key.0.to_def_id())
624+
}
625+
}
626+
614627
/// HACK: when evaluated, this reports a "unsafe derive on repr(packed)" error.
615628
///
616629
/// Unsafety checking is executed for each method separately, but we only want
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
use crate::thir::visit::{self, Visitor};
2+
use crate::thir::*;
3+
4+
use rustc_errors::struct_span_err;
5+
use rustc_hir as hir;
6+
use rustc_middle::ty::{self, TyCtxt};
7+
use rustc_session::lint::builtin::{UNSAFE_OP_IN_UNSAFE_FN, UNUSED_UNSAFE};
8+
use rustc_session::lint::Level;
9+
use rustc_span::def_id::{DefId, LocalDefId};
10+
use rustc_span::Span;
11+
12+
struct UnsafetyVisitor<'tcx> {
13+
tcx: TyCtxt<'tcx>,
14+
/// The `HirId` of the current scope, which would be the `HirId`
15+
/// of the current HIR node, modulo adjustments. Used for lint levels.
16+
hir_context: hir::HirId,
17+
/// The current "safety context". This notably tracks whether we are in an
18+
/// `unsafe` block, and whether it has been used.
19+
safety_context: SafetyContext,
20+
body_unsafety: BodyUnsafety,
21+
}
22+
23+
impl<'tcx> UnsafetyVisitor<'tcx> {
24+
fn in_safety_context<R>(
25+
&mut self,
26+
safety_context: SafetyContext,
27+
f: impl FnOnce(&mut Self) -> R,
28+
) {
29+
if let (
30+
SafetyContext::UnsafeBlock { span: enclosing_span, .. },
31+
SafetyContext::UnsafeBlock { span: block_span, hir_id, .. },
32+
) = (self.safety_context, safety_context)
33+
{
34+
self.warn_unused_unsafe(
35+
hir_id,
36+
block_span,
37+
Some(self.tcx.sess.source_map().guess_head_span(enclosing_span)),
38+
);
39+
f(self);
40+
} else {
41+
let prev_context = self.safety_context;
42+
self.safety_context = safety_context;
43+
44+
f(self);
45+
46+
if let SafetyContext::UnsafeBlock { used: false, span, hir_id } = self.safety_context {
47+
self.warn_unused_unsafe(hir_id, span, self.body_unsafety.unsafe_fn_sig_span());
48+
}
49+
self.safety_context = prev_context;
50+
return;
51+
}
52+
}
53+
54+
fn requires_unsafe(&mut self, span: Span, kind: UnsafeOpKind) {
55+
let (description, note) = kind.description_and_note();
56+
let unsafe_op_in_unsafe_fn_allowed = self.unsafe_op_in_unsafe_fn_allowed();
57+
match self.safety_context {
58+
SafetyContext::UnsafeBlock { ref mut used, .. } => {
59+
if !self.body_unsafety.is_unsafe() || !unsafe_op_in_unsafe_fn_allowed {
60+
// Mark this block as useful
61+
*used = true;
62+
}
63+
}
64+
SafetyContext::UnsafeFn if unsafe_op_in_unsafe_fn_allowed => {}
65+
SafetyContext::UnsafeFn => {
66+
// unsafe_op_in_unsafe_fn is disallowed
67+
if kind == BorrowOfPackedField {
68+
// FIXME handle borrows of packed fields
69+
} else {
70+
struct_span_err!(
71+
self.tcx.sess,
72+
span,
73+
E0133,
74+
"{} is unsafe and requires unsafe block",
75+
description,
76+
)
77+
.span_label(span, description)
78+
.note(note)
79+
.emit();
80+
}
81+
}
82+
SafetyContext::Safe => {
83+
if kind == BorrowOfPackedField {
84+
// FIXME handle borrows of packed fields
85+
} else {
86+
let fn_sugg = if unsafe_op_in_unsafe_fn_allowed { " function or" } else { "" };
87+
struct_span_err!(
88+
self.tcx.sess,
89+
span,
90+
E0133,
91+
"{} is unsafe and requires unsafe{} block",
92+
description,
93+
fn_sugg,
94+
)
95+
.span_label(span, description)
96+
.note(note)
97+
.emit();
98+
}
99+
}
100+
}
101+
}
102+
103+
fn warn_unused_unsafe(
104+
&self,
105+
hir_id: hir::HirId,
106+
block_span: Span,
107+
enclosing_span: Option<Span>,
108+
) {
109+
let block_span = self.tcx.sess.source_map().guess_head_span(block_span);
110+
self.tcx.struct_span_lint_hir(UNUSED_UNSAFE, hir_id, block_span, |lint| {
111+
let msg = "unnecessary `unsafe` block";
112+
let mut db = lint.build(msg);
113+
db.span_label(block_span, msg);
114+
if let Some(enclosing_span) = enclosing_span {
115+
db.span_label(
116+
enclosing_span,
117+
format!("because it's nested under this `unsafe` block"),
118+
);
119+
}
120+
db.emit();
121+
});
122+
}
123+
124+
/// Whether the `unsafe_op_in_unsafe_fn` lint is `allow`ed at the current HIR node.
125+
fn unsafe_op_in_unsafe_fn_allowed(&self) -> bool {
126+
self.tcx.lint_level_at_node(UNSAFE_OP_IN_UNSAFE_FN, self.hir_context).0 == Level::Allow
127+
}
128+
}
129+
130+
impl<'thir, 'tcx> Visitor<'thir, 'tcx> for UnsafetyVisitor<'tcx> {
131+
fn visit_block(&mut self, block: &Block<'thir, 'tcx>) {
132+
if let BlockSafety::ExplicitUnsafe(hir_id) = block.safety_mode {
133+
self.in_safety_context(
134+
SafetyContext::UnsafeBlock { span: block.span, hir_id, used: false },
135+
|this| visit::walk_block(this, block),
136+
);
137+
} else {
138+
visit::walk_block(self, block);
139+
}
140+
}
141+
142+
fn visit_expr(&mut self, expr: &'thir Expr<'thir, 'tcx>) {
143+
match expr.kind {
144+
ExprKind::Scope { value, lint_level: LintLevel::Explicit(hir_id), region_scope: _ } => {
145+
let prev_id = self.hir_context;
146+
self.hir_context = hir_id;
147+
self.visit_expr(value);
148+
self.hir_context = prev_id;
149+
return;
150+
}
151+
ExprKind::Call { fun, ty: _, args: _, from_hir_call: _, fn_span: _ } => {
152+
if fun.ty.fn_sig(self.tcx).unsafety() == hir::Unsafety::Unsafe {
153+
self.requires_unsafe(expr.span, CallToUnsafeFunction);
154+
}
155+
}
156+
_ => {}
157+
}
158+
159+
visit::walk_expr(self, expr);
160+
}
161+
}
162+
163+
#[derive(Clone, Copy)]
164+
enum SafetyContext {
165+
Safe,
166+
UnsafeFn,
167+
UnsafeBlock { span: Span, hir_id: hir::HirId, used: bool },
168+
}
169+
170+
#[derive(Clone, Copy)]
171+
enum BodyUnsafety {
172+
/// The body is not unsafe.
173+
Safe,
174+
/// The body is an unsafe function. The span points to
175+
/// the signature of the function.
176+
Unsafe(Span),
177+
}
178+
179+
impl BodyUnsafety {
180+
/// Returns whether the body is unsafe.
181+
fn is_unsafe(&self) -> bool {
182+
matches!(self, BodyUnsafety::Unsafe(_))
183+
}
184+
185+
/// If the body is unsafe, returns the `Span` of its signature.
186+
fn unsafe_fn_sig_span(self) -> Option<Span> {
187+
match self {
188+
BodyUnsafety::Unsafe(span) => Some(span),
189+
BodyUnsafety::Safe => None,
190+
}
191+
}
192+
}
193+
194+
#[derive(Clone, Copy, PartialEq)]
195+
enum UnsafeOpKind {
196+
CallToUnsafeFunction,
197+
#[allow(dead_code)] // FIXME
198+
UseOfInlineAssembly,
199+
#[allow(dead_code)] // FIXME
200+
InitializingTypeWith,
201+
#[allow(dead_code)] // FIXME
202+
CastOfPointerToInt,
203+
#[allow(dead_code)] // FIXME
204+
BorrowOfPackedField,
205+
#[allow(dead_code)] // FIXME
206+
UseOfMutableStatic,
207+
#[allow(dead_code)] // FIXME
208+
UseOfExternStatic,
209+
#[allow(dead_code)] // FIXME
210+
DerefOfRawPointer,
211+
#[allow(dead_code)] // FIXME
212+
AssignToDroppingUnionField,
213+
#[allow(dead_code)] // FIXME
214+
AccessToUnionField,
215+
#[allow(dead_code)] // FIXME
216+
MutationOfLayoutConstrainedField,
217+
#[allow(dead_code)] // FIXME
218+
BorrowOfLayoutConstrainedField,
219+
#[allow(dead_code)] // FIXME
220+
CallToFunctionWith,
221+
}
222+
223+
use UnsafeOpKind::*;
224+
225+
impl UnsafeOpKind {
226+
pub fn description_and_note(&self) -> (&'static str, &'static str) {
227+
match self {
228+
CallToUnsafeFunction => (
229+
"call to unsafe function",
230+
"consult the function's documentation for information on how to avoid undefined \
231+
behavior",
232+
),
233+
UseOfInlineAssembly => (
234+
"use of inline assembly",
235+
"inline assembly is entirely unchecked and can cause undefined behavior",
236+
),
237+
InitializingTypeWith => (
238+
"initializing type with `rustc_layout_scalar_valid_range` attr",
239+
"initializing a layout restricted type's field with a value outside the valid \
240+
range is undefined behavior",
241+
),
242+
CastOfPointerToInt => {
243+
("cast of pointer to int", "casting pointers to integers in constants")
244+
}
245+
BorrowOfPackedField => (
246+
"borrow of packed field",
247+
"fields of packed structs might be misaligned: dereferencing a misaligned pointer \
248+
or even just creating a misaligned reference is undefined behavior",
249+
),
250+
UseOfMutableStatic => (
251+
"use of mutable static",
252+
"mutable statics can be mutated by multiple threads: aliasing violations or data \
253+
races will cause undefined behavior",
254+
),
255+
UseOfExternStatic => (
256+
"use of extern static",
257+
"extern statics are not controlled by the Rust type system: invalid data, \
258+
aliasing violations or data races will cause undefined behavior",
259+
),
260+
DerefOfRawPointer => (
261+
"dereference of raw pointer",
262+
"raw pointers may be NULL, dangling or unaligned; they can violate aliasing rules \
263+
and cause data races: all of these are undefined behavior",
264+
),
265+
AssignToDroppingUnionField => (
266+
"assignment to union field that might need dropping",
267+
"the previous content of the field will be dropped, which causes undefined \
268+
behavior if the field was not properly initialized",
269+
),
270+
AccessToUnionField => (
271+
"access to union field",
272+
"the field may not be properly initialized: using uninitialized data will cause \
273+
undefined behavior",
274+
),
275+
MutationOfLayoutConstrainedField => (
276+
"mutation of layout constrained field",
277+
"mutating layout constrained fields cannot statically be checked for valid values",
278+
),
279+
BorrowOfLayoutConstrainedField => (
280+
"borrow of layout constrained field with interior mutability",
281+
"references to fields of layout constrained fields lose the constraints. Coupled \
282+
with interior mutability, the field can be changed to invalid values",
283+
),
284+
CallToFunctionWith => (
285+
"call to function with `#[target_feature]`",
286+
"can only be called if the required target features are available",
287+
),
288+
}
289+
}
290+
}
291+
292+
// FIXME: checking unsafety for closures should be handled by their parent body,
293+
// as they inherit their "safety context" from their declaration site.
294+
pub fn check_unsafety<'tcx>(tcx: TyCtxt<'tcx>, thir: &Expr<'_, 'tcx>, hir_id: hir::HirId) {
295+
let body_unsafety = tcx.hir().fn_sig_by_hir_id(hir_id).map_or(BodyUnsafety::Safe, |fn_sig| {
296+
if fn_sig.header.unsafety == hir::Unsafety::Unsafe {
297+
BodyUnsafety::Unsafe(fn_sig.span)
298+
} else {
299+
BodyUnsafety::Safe
300+
}
301+
});
302+
let safety_context =
303+
if body_unsafety.is_unsafe() { SafetyContext::UnsafeFn } else { SafetyContext::Safe };
304+
let mut visitor = UnsafetyVisitor { tcx, safety_context, hir_context: hir_id, body_unsafety };
305+
visitor.visit_expr(thir);
306+
}
307+
308+
crate fn thir_check_unsafety_inner<'tcx>(
309+
tcx: TyCtxt<'tcx>,
310+
def: ty::WithOptConstParam<LocalDefId>,
311+
) {
312+
let hir_id = tcx.hir().local_def_id_to_hir_id(def.did);
313+
let body_id = tcx.hir().body_owned_by(hir_id);
314+
let body = tcx.hir().body(body_id);
315+
316+
let arena = Arena::default();
317+
let thir = cx::build_thir(tcx, def, &arena, &body.value);
318+
check_unsafety(tcx, thir, hir_id);
319+
}
320+
321+
crate fn thir_check_unsafety<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) {
322+
if let Some(def) = ty::WithOptConstParam::try_lookup(def_id, tcx) {
323+
tcx.thir_check_unsafety_for_const_arg(def)
324+
} else {
325+
thir_check_unsafety_inner(tcx, ty::WithOptConstParam::unknown(def_id))
326+
}
327+
}
328+
329+
crate fn thir_check_unsafety_for_const_arg<'tcx>(
330+
tcx: TyCtxt<'tcx>,
331+
(did, param_did): (LocalDefId, DefId),
332+
) {
333+
thir_check_unsafety_inner(tcx, ty::WithOptConstParam { did, const_param_did: Some(param_did) })
334+
}

compiler/rustc_mir_build/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ extern crate tracing;
1919
extern crate rustc_middle;
2020

2121
mod build;
22+
mod check_unsafety;
2223
mod lints;
2324
pub mod thir;
2425

@@ -28,4 +29,6 @@ pub fn provide(providers: &mut Providers) {
2829
providers.check_match = thir::pattern::check_match;
2930
providers.lit_to_const = thir::constant::lit_to_const;
3031
providers.mir_built = build::mir_built;
32+
providers.thir_check_unsafety = check_unsafety::thir_check_unsafety;
33+
providers.thir_check_unsafety_for_const_arg = check_unsafety::thir_check_unsafety_for_const_arg;
3134
}

compiler/rustc_mir_build/src/thir/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ mod arena;
2929
pub use arena::Arena;
3030

3131
mod util;
32+
pub mod visit;
3233

3334
#[derive(Copy, Clone, Debug)]
3435
pub enum LintLevel {

0 commit comments

Comments
 (0)