rustc_const_eval/util/
check_validity_requirement.rs

1use rustc_abi::{BackendRepr, FieldsShape, Scalar, Variants};
2use rustc_middle::ty::layout::{
3    HasTyCtxt, LayoutCx, LayoutError, LayoutOf, TyAndLayout, ValidityRequirement,
4};
5use rustc_middle::ty::{PseudoCanonicalInput, ScalarInt, Ty, TyCtxt};
6use rustc_middle::{bug, ty};
7use rustc_span::DUMMY_SP;
8
9use crate::const_eval::{CanAccessMutGlobal, CheckAlignment, CompileTimeMachine};
10use crate::interpret::{InterpCx, MemoryKind};
11
12/// Determines if this type permits "raw" initialization by just transmuting some memory into an
13/// instance of `T`.
14///
15/// `init_kind` indicates if the memory is zero-initialized or left uninitialized. We assume
16/// uninitialized memory is mitigated by filling it with 0x01, which reduces the chance of causing
17/// LLVM UB.
18///
19/// By default we check whether that operation would cause *LLVM UB*, i.e., whether the LLVM IR we
20/// generate has UB or not. This is a mitigation strategy, which is why we are okay with accepting
21/// Rust UB as long as there is no risk of miscompilations. The `strict_init_checks` can be set to
22/// do a full check against Rust UB instead (in which case we will also ignore the 0x01-filling and
23/// to the full uninit check).
24pub fn check_validity_requirement<'tcx>(
25    tcx: TyCtxt<'tcx>,
26    kind: ValidityRequirement,
27    input: PseudoCanonicalInput<'tcx, Ty<'tcx>>,
28) -> Result<bool, &'tcx LayoutError<'tcx>> {
29    let layout = tcx.layout_of(input)?;
30
31    // There is nothing strict or lax about inhabitedness.
32    if kind == ValidityRequirement::Inhabited {
33        return Ok(!layout.is_uninhabited());
34    }
35
36    let layout_cx = LayoutCx::new(tcx, input.typing_env);
37    if kind == ValidityRequirement::Uninit || tcx.sess.opts.unstable_opts.strict_init_checks {
38        Ok(check_validity_requirement_strict(layout, &layout_cx, kind))
39    } else {
40        check_validity_requirement_lax(layout, &layout_cx, kind)
41    }
42}
43
44/// Implements the 'strict' version of the [`check_validity_requirement`] checks; see that function
45/// for details.
46fn check_validity_requirement_strict<'tcx>(
47    ty: TyAndLayout<'tcx>,
48    cx: &LayoutCx<'tcx>,
49    kind: ValidityRequirement,
50) -> bool {
51    let machine = CompileTimeMachine::new(CanAccessMutGlobal::No, CheckAlignment::Error);
52
53    let mut cx = InterpCx::new(cx.tcx(), DUMMY_SP, cx.typing_env, machine);
54
55    let allocated = cx
56        .allocate(ty, MemoryKind::Machine(crate::const_eval::MemoryKind::Heap))
57        .expect("OOM: failed to allocate for uninit check");
58
59    if kind == ValidityRequirement::Zero {
60        cx.write_bytes_ptr(
61            allocated.ptr(),
62            std::iter::repeat(0_u8).take(ty.layout.size().bytes_usize()),
63        )
64        .expect("failed to write bytes for zero valid check");
65    }
66
67    // Assume that if it failed, it's a validation failure.
68    // This does *not* actually check that references are dereferenceable, but since all types that
69    // require dereferenceability also require non-null, we don't actually get any false negatives
70    // due to this.
71    // The value we are validating is temporary and discarded at the end of this function, so
72    // there is no point in reseting provenance and padding.
73    cx.validate_operand(
74        &allocated.into(),
75        /*recursive*/ false,
76        /*reset_provenance_and_padding*/ false,
77    )
78    .discard_err()
79    .is_some()
80}
81
82/// Implements the 'lax' (default) version of the [`check_validity_requirement`] checks; see that
83/// function for details.
84fn check_validity_requirement_lax<'tcx>(
85    this: TyAndLayout<'tcx>,
86    cx: &LayoutCx<'tcx>,
87    init_kind: ValidityRequirement,
88) -> Result<bool, &'tcx LayoutError<'tcx>> {
89    let scalar_allows_raw_init = move |s: Scalar| -> bool {
90        match init_kind {
91            ValidityRequirement::Inhabited => {
92                bug!("ValidityRequirement::Inhabited should have been handled above")
93            }
94            ValidityRequirement::Zero => {
95                // The range must contain 0.
96                s.valid_range(cx).contains(0)
97            }
98            ValidityRequirement::UninitMitigated0x01Fill => {
99                // The range must include an 0x01-filled buffer.
100                let mut val: u128 = 0x01;
101                for _ in 1..s.size(cx).bytes() {
102                    // For sizes >1, repeat the 0x01.
103                    val = (val << 8) | 0x01;
104                }
105                s.valid_range(cx).contains(val)
106            }
107            ValidityRequirement::Uninit => {
108                bug!("ValidityRequirement::Uninit should have been handled above")
109            }
110        }
111    };
112
113    // Check the ABI.
114    let valid = match this.backend_repr {
115        BackendRepr::Uninhabited => false, // definitely UB
116        BackendRepr::Scalar(s) => scalar_allows_raw_init(s),
117        BackendRepr::ScalarPair(s1, s2) => scalar_allows_raw_init(s1) && scalar_allows_raw_init(s2),
118        BackendRepr::Vector { element: s, count } => count == 0 || scalar_allows_raw_init(s),
119        BackendRepr::Memory { .. } => true, // Fields are checked below.
120    };
121    if !valid {
122        // This is definitely not okay.
123        return Ok(false);
124    }
125
126    // Special magic check for references and boxes (i.e., special pointer types).
127    if let Some(pointee) = this.ty.builtin_deref(false) {
128        let pointee = cx.layout_of(pointee)?;
129        // We need to ensure that the LLVM attributes `aligned` and `dereferenceable(size)` are satisfied.
130        if pointee.align.abi.bytes() > 1 {
131            // 0x01-filling is not aligned.
132            return Ok(false);
133        }
134        if pointee.size.bytes() > 0 {
135            // A 'fake' integer pointer is not sufficiently dereferenceable.
136            return Ok(false);
137        }
138    }
139
140    // If we have not found an error yet, we need to recursively descend into fields.
141    match &this.fields {
142        FieldsShape::Primitive | FieldsShape::Union { .. } => {}
143        FieldsShape::Array { .. } => {
144            // Arrays never have scalar layout in LLVM, so if the array is not actually
145            // accessed, there is no LLVM UB -- therefore we can skip this.
146        }
147        FieldsShape::Arbitrary { offsets, .. } => {
148            for idx in 0..offsets.len() {
149                if !check_validity_requirement_lax(this.field(cx, idx), cx, init_kind)? {
150                    // We found a field that is unhappy with this kind of initialization.
151                    return Ok(false);
152                }
153            }
154        }
155    }
156
157    match &this.variants {
158        Variants::Empty => return Ok(false),
159        Variants::Single { .. } => {
160            // All fields of this single variant have already been checked above, there is nothing
161            // else to do.
162        }
163        Variants::Multiple { .. } => {
164            // We cannot tell LLVM anything about the details of this multi-variant layout, so
165            // invalid values "hidden" inside the variant cannot cause LLVM trouble.
166        }
167    }
168
169    Ok(true)
170}
171
172pub(crate) fn validate_scalar_in_layout<'tcx>(
173    tcx: TyCtxt<'tcx>,
174    scalar: ScalarInt,
175    ty: Ty<'tcx>,
176) -> bool {
177    let machine = CompileTimeMachine::new(CanAccessMutGlobal::No, CheckAlignment::Error);
178
179    let typing_env = ty::TypingEnv::fully_monomorphized();
180    let mut cx = InterpCx::new(tcx, DUMMY_SP, typing_env, machine);
181
182    let Ok(layout) = cx.layout_of(ty) else {
183        bug!("could not compute layout of {scalar:?}:{ty:?}")
184    };
185    let allocated = cx
186        .allocate(layout, MemoryKind::Machine(crate::const_eval::MemoryKind::Heap))
187        .expect("OOM: failed to allocate for uninit check");
188
189    cx.write_scalar(scalar, &allocated).unwrap();
190
191    cx.validate_operand(
192        &allocated.into(),
193        /*recursive*/ false,
194        /*reset_provenance_and_padding*/ false,
195    )
196    .discard_err()
197    .is_some()
198}