Skip to main content

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