rustc浅析: Unsafety Checking

Dec 15, 2024

这一步检查的实现位于check_unsafety.rs

引入

Rust中,所有无法由类型系统证明正确性的代码(如裸指针的解引用),以及被unsafe修饰的traitsfunctionsmethods,都被视为是unsafe的。这一部分的操作需要被放入unsafe块内。Unsafety Checking确保unsafe操作不被用于unsafe块之外。

THIR

-----------
Source Code
-----------
     |
-----------
 Rust AST
-----------
     |
-----------
    HIR
-----------
     |
-----------
   THIR
-----------
     |
-----------
    MIR     <-- Control Flow Graph
-----------

Unsafety Checking基于THIR,理由如下:

  1. 和HIR相比,使用THIR需要考虑的情况更少。比如unsafe function callsunsafe method calls在THIR中的表示是相同的。
  2. 不用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的能力,因此报错。

参考

  1. Rustc Dev Guide: Unsafety Checking
  2. Issue #133441: An unsafe const fn being used to compute an array length or const generic is incorrectly described as being an "item".
https://makai410.dev/posts/feed.xml