这一步检查的实现位于check_unsafety.rs。
引入
Rust中,所有无法由类型系统证明正确性的代码(如裸指针的解引用),以及被unsafe修饰的traits,functions和methods,都被视为是unsafe的。这一部分的操作需要被放入unsafe块内。Unsafety Checking确保unsafe操作不被用于unsafe块之外。
THIR
-----------
Source Code
-----------
|
-----------
Rust AST
-----------
|
-----------
HIR
-----------
|
-----------
THIR
-----------
|
-----------
MIR <-- Control Flow Graph
-----------
Unsafety Checking基于THIR,理由如下:
- 和HIR相比,使用THIR需要考虑的情况更少。比如
unsafe function calls和unsafe method calls在THIR中的表示是相同的。 - 不用MIR是因为Unsafety Checking不需要控制流,并且对于一些表达式,MIR没有像THIR那么精确的span。
辨认unsafe操作
绝大部分的unsafe操作可以通过检查THIR中的ExprKind和检查参数类型来辨认。比如说想要辨认裸指针解引用的操作,可以通过检查具有裸指针参数的 ExprKind::Deref:
UnsafeVisitor::visit_expr():
// https://github.com/rust-lang/rust/blob/master/compiler/rustc_mir_build/src/check_unsafety.rs#L538-L553
ExprKind::Deref { arg } => {
if let ExprKind::StaticRef { def_id, .. } | ExprKind::ThreadLocalRef(def_id) =
self.thir[arg].kind
{
if self.tcx.is_mutable_static(def_id) {
self.requires_unsafe(expr.span, UseOfMutableStatic);
} else if self.tcx.is_foreign_item(def_id) {
match self.tcx.def_kind(def_id) {
DefKind::Static { safety: hir::Safety::Safe, .. } => {}
_ => self.requires_unsafe(expr.span, UseOfExternStatic),
}
}
} else if self.thir[arg].ty.is_unsafe_ptr() {
self.requires_unsafe(expr.span, DerefOfRawPointer);
}
}
Access fields of a union
考虑以下代码:
#[repr(C)]
union MyUnion {
f1: i32,
f2: f32,
}
fn foo(u: MyUnion) {
u.f1 = 6;
let f = unsafe { u.f1 };
}
- 会发现,写入
u.f1是safe的,而读取u.f1是unsafe的。
因此检查器会允许union fields直接出现在赋值表达式的LHS,而其它情况全部报错。
同时读取union fields的操作也会发生在模式匹配,因此那里也需要被检查器走一遍:
UnsafeVisitor::visit_expr():
// https://github.com/rust-lang/rust/blob/master/compiler/rustc_mir_build/src/check_unsafety.rs#L623-L643
ExprKind::Field { lhs, variant_index, name } => {
let lhs = &self.thir[lhs];
if let ty::Adt(adt_def, _) = lhs.ty.kind() {
if adt_def.variant(variant_index).fields[name].safety == Safety::Unsafe {
self.requires_unsafe(expr.span, UseOfUnsafeField);
} else if adt_def.is_union() {
if let Some(assigned_ty) = self.assignment_info {
if assigned_ty.needs_drop(self.tcx, self.typing_env) {
// This would be unsafe, but should be outright impossible since we
// reject such unions.
assert!(
self.tcx.dcx().has_errors().is_some(),
"union fields that need dropping should be impossible: {assigned_ty}"
);
}
} else {
self.requires_unsafe(expr.span, AccessToUnionField);
}
}
}
}
UnsafeVisitor::visit_pat():
// https://github.com/rust-lang/rust/blob/master/compiler/rustc_mir_build/src/check_unsafety.rs#L341-L366
match &pat.kind {
PatKind::Leaf { subpatterns, .. } => {
if let ty::Adt(adt_def, ..) = pat.ty.kind() {
for pat in subpatterns {
if adt_def.non_enum_variant().fields[pat.field].safety == Safety::Unsafe {
self.requires_unsafe(pat.pattern.span, UseOfUnsafeField);
}
}
if adt_def.is_union() {
let old_in_union_destructure =
std::mem::replace(&mut self.in_union_destructure, true);
visit::walk_pat(self, pat);
self.in_union_destructure = old_in_union_destructure;
} else if (Bound::Unbounded, Bound::Unbounded)
!= self.tcx.layout_scalar_valid_range(adt_def.did())
{
let old_inside_adt = std::mem::replace(&mut self.inside_adt, true);
visit::walk_pat(self, pat);
self.inside_adt = old_inside_adt;
} else {
visit::walk_pat(self, pat);
}
} else {
visit::walk_pat(self, pat);
}
}
Anonymous Constant
HIR用AnonConst表示 anonymous constant(匿名常量)。
// https://github.com/rust-lang/rust/blob/master/compiler/rustc_hir/src/hir.rs#L1682-L1696
/// A constant (expression) that's not an item or associated item,
/// but needs its own `DefId` for type-checking, const-eval, etc.
/// These are usually found nested inside types (e.g., array lengths)
/// or expressions (e.g., repeat counts), and also used to define
/// explicit discriminant values for enum variants.
///
/// You can check if this anon const is a default in a const param
/// `const N: usize = { ... }` with `tcx.hir().opt_const_param_default_param_def_id(..)`
#[derive(Copy, Clone, Debug, HashStable_Generic)]
pub struct AnonConst {
pub hir_id: HirId,
pub def_id: LocalDefId,
pub body: BodyId,
pub span: Span,
}
- 这里表明到,anonymous constant 并不是一个item。
但实际上,AnonConst在许多地方会被当作成一个item来处理,比如本文讲到的Unsafety Checking。anonymous constant并不具备继承unsafety的能力,考虑以下代码:
const unsafe fn foo() -> usize { 1 }
fn main() {
unsafe {
let _x = [0; foo()];
}
}
有如下报错信息:
Compiling playground v0.0.1 (/playground)
warning: unnecessary `unsafe` block
--> src/main.rs:4:5
|
4 | unsafe {
| ^^^^^^ unnecessary `unsafe` block
|
= note: `#[warn(unused_unsafe)]` on by default
error[E0133]: call to unsafe function `foo` is unsafe and requires unsafe function or block
--> src/main.rs:5:22
|
4 | unsafe {
| ------ items do not inherit unsafety from separate enclosing items
5 | let _x = [0; foo()];
| ^^^^^ call to unsafe function
|
= note: consult the function's documentation for information on how to avoid undefined behavior
For more information about this error, try `rustc --explain E0133`.
warning: `playground` (bin "playground") generated 1 warning
error: could not compile `playground` (bin "playground") due to 1 previous error; 1 warning emitted
因为这里foo()被表示为anonymous constant,而anonymous constant在Unsafety Checking中被当作成一个item来处理,因此其不具备继承unsafety的能力,因此报错。