Skip to content

Commit d66824d

Browse files
committed
Make alignment checks a future incompat lint
1 parent ed71e32 commit d66824d

File tree

13 files changed

+180
-70
lines changed

13 files changed

+180
-70
lines changed

compiler/rustc_const_eval/src/const_eval/eval_queries.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::const_eval::CheckAlignment;
12
use std::borrow::Cow;
23

34
use either::{Left, Right};
@@ -76,7 +77,7 @@ fn eval_body_using_ecx<'mir, 'tcx>(
7677
None => InternKind::Constant,
7778
}
7879
};
79-
ecx.machine.check_alignment = false; // interning doesn't need to respect alignment
80+
ecx.machine.check_alignment = CheckAlignment::No; // interning doesn't need to respect alignment
8081
intern_const_alloc_recursive(ecx, intern_kind, &ret)?;
8182
// we leave alignment checks off, since this `ecx` will not be used for further evaluation anyway
8283

@@ -102,11 +103,7 @@ pub(super) fn mk_eval_cx<'mir, 'tcx>(
102103
tcx,
103104
root_span,
104105
param_env,
105-
CompileTimeInterpreter::new(
106-
tcx.const_eval_limit(),
107-
can_access_statics,
108-
/*check_alignment:*/ false,
109-
),
106+
CompileTimeInterpreter::new(tcx.const_eval_limit(), can_access_statics, CheckAlignment::No),
110107
)
111108
}
112109

@@ -311,7 +308,11 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
311308
CompileTimeInterpreter::new(
312309
tcx.const_eval_limit(),
313310
/*can_access_statics:*/ is_static,
314-
/*check_alignment:*/ true,
311+
if tcx.sess.opts.unstable_opts.extra_const_ub_checks {
312+
CheckAlignment::Error
313+
} else {
314+
CheckAlignment::FutureIncompat
315+
},
315316
),
316317
);
317318

compiler/rustc_const_eval/src/const_eval/machine.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,34 @@ pub struct CompileTimeInterpreter<'mir, 'tcx> {
4747
pub(super) can_access_statics: bool,
4848

4949
/// Whether to check alignment during evaluation.
50-
pub(super) check_alignment: bool,
50+
pub(super) check_alignment: CheckAlignment,
51+
}
52+
53+
#[derive(Copy, Clone)]
54+
pub enum CheckAlignment {
55+
/// Ignore alignment when following relocations.
56+
/// This is mainly used in interning.
57+
No,
58+
/// Hard error when dereferencing a misaligned pointer.
59+
Error,
60+
/// Emit a future incompat lint when dereferencing a misaligned pointer.
61+
FutureIncompat,
62+
}
63+
64+
impl CheckAlignment {
65+
pub fn should_check(&self) -> bool {
66+
match self {
67+
CheckAlignment::No => false,
68+
CheckAlignment::Error | CheckAlignment::FutureIncompat => true,
69+
}
70+
}
5171
}
5272

5373
impl<'mir, 'tcx> CompileTimeInterpreter<'mir, 'tcx> {
5474
pub(crate) fn new(
5575
const_eval_limit: Limit,
5676
can_access_statics: bool,
57-
check_alignment: bool,
77+
check_alignment: CheckAlignment,
5878
) -> Self {
5979
CompileTimeInterpreter {
6080
steps_remaining: const_eval_limit.0,
@@ -309,7 +329,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
309329
const PANIC_ON_ALLOC_FAIL: bool = false; // will be raised as a proper error
310330

311331
#[inline(always)]
312-
fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
332+
fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> CheckAlignment {
313333
ecx.machine.check_alignment
314334
}
315335

compiler/rustc_const_eval/src/interpret/eval_context.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,15 @@ impl<'mir, 'tcx, Prov: Provenance, Extra> Frame<'mir, 'tcx, Prov, Extra> {
248248
Right(span) => span,
249249
}
250250
}
251+
252+
pub fn lint_root(&self) -> Option<hir::HirId> {
253+
self.current_source_info().and_then(|source_info| {
254+
match &self.body.source_scopes[source_info.scope].local_data {
255+
mir::ClearCrossCrate::Set(data) => Some(data.lint_root),
256+
mir::ClearCrossCrate::Clear => None,
257+
}
258+
})
259+
}
251260
}
252261

253262
impl<'tcx> fmt::Display for FrameInfo<'tcx> {
@@ -954,12 +963,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
954963
// This deliberately does *not* honor `requires_caller_location` since it is used for much
955964
// more than just panics.
956965
for frame in stack.iter().rev() {
957-
let lint_root = frame.current_source_info().and_then(|source_info| {
958-
match &frame.body.source_scopes[source_info.scope].local_data {
959-
mir::ClearCrossCrate::Set(data) => Some(data.lint_root),
960-
mir::ClearCrossCrate::Clear => None,
961-
}
962-
});
966+
let lint_root = frame.lint_root();
963967
let span = frame.current_span();
964968

965969
frames.push(FrameInfo { span, instance: frame.instance, lint_root });

compiler/rustc_const_eval/src/interpret/machine.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use rustc_span::def_id::DefId;
1313
use rustc_target::abi::Size;
1414
use rustc_target::spec::abi::Abi as CallAbi;
1515

16+
use crate::const_eval::CheckAlignment;
17+
1618
use super::{
1719
AllocId, AllocRange, Allocation, ConstAllocation, Frame, ImmTy, InterpCx, InterpResult,
1820
MemoryKind, OpTy, Operand, PlaceTy, Pointer, Provenance, Scalar, StackPopUnwind,
@@ -122,7 +124,7 @@ pub trait Machine<'mir, 'tcx>: Sized {
122124
const PANIC_ON_ALLOC_FAIL: bool;
123125

124126
/// Whether memory accesses should be alignment-checked.
125-
fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool;
127+
fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> CheckAlignment;
126128

127129
/// Whether, when checking alignment, we should look at the actual address and thus support
128130
/// custom alignment logic based on whatever the integer address happens to be.

compiler/rustc_const_eval/src/interpret/memory.rs

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,15 @@ use std::ptr;
1414

1515
use rustc_ast::Mutability;
1616
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
17+
use rustc_hir::CRATE_HIR_ID;
1718
use rustc_middle::mir::display_allocation;
19+
use rustc_middle::mir::interpret::UndefinedBehaviorInfo;
1820
use rustc_middle::ty::{self, Instance, ParamEnv, Ty, TyCtxt};
21+
use rustc_session::lint::builtin::INVALID_ALIGNMENT;
1922
use rustc_target::abi::{Align, HasDataLayout, Size};
2023

24+
use crate::const_eval::CheckAlignment;
25+
2126
use super::{
2227
alloc_range, AllocId, AllocMap, AllocRange, Allocation, CheckInAllocMsg, GlobalAlloc, InterpCx,
2328
InterpResult, Machine, MayLeak, Pointer, PointerArithmetic, Provenance, Scalar,
@@ -377,7 +382,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
377382
ptr,
378383
size,
379384
align,
380-
/* force_alignment_check */ true,
385+
CheckAlignment::Error,
381386
msg,
382387
|alloc_id, _, _| {
383388
let (size, align) = self.get_live_alloc_size_and_align(alloc_id)?;
@@ -396,27 +401,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
396401
ptr: Pointer<Option<M::Provenance>>,
397402
size: Size,
398403
align: Align,
399-
force_alignment_check: bool,
404+
check: CheckAlignment,
400405
msg: CheckInAllocMsg,
401406
alloc_size: impl FnOnce(
402407
AllocId,
403408
Size,
404409
M::ProvenanceExtra,
405410
) -> InterpResult<'tcx, (Size, Align, T)>,
406411
) -> InterpResult<'tcx, Option<T>> {
407-
fn check_offset_align<'tcx>(offset: u64, align: Align) -> InterpResult<'tcx> {
408-
if offset % align.bytes() == 0 {
409-
Ok(())
410-
} else {
411-
// The biggest power of two through which `offset` is divisible.
412-
let offset_pow2 = 1 << offset.trailing_zeros();
413-
throw_ub!(AlignmentCheckFailed {
414-
has: Align::from_bytes(offset_pow2).unwrap(),
415-
required: align,
416-
})
417-
}
418-
}
419-
420412
Ok(match self.ptr_try_get_alloc_id(ptr) {
421413
Err(addr) => {
422414
// We couldn't get a proper allocation. This is only okay if the access size is 0,
@@ -425,8 +417,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
425417
throw_ub!(DanglingIntPointer(addr, msg));
426418
}
427419
// Must be aligned.
428-
if force_alignment_check {
429-
check_offset_align(addr, align)?;
420+
if check.should_check() {
421+
self.check_offset_align(addr, align, check)?;
430422
}
431423
None
432424
}
@@ -449,16 +441,16 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
449441
}
450442
// Test align. Check this last; if both bounds and alignment are violated
451443
// we want the error to be about the bounds.
452-
if force_alignment_check {
444+
if check.should_check() {
453445
if M::use_addr_for_alignment_check(self) {
454446
// `use_addr_for_alignment_check` can only be true if `OFFSET_IS_ADDR` is true.
455-
check_offset_align(ptr.addr().bytes(), align)?;
447+
self.check_offset_align(ptr.addr().bytes(), align, check)?;
456448
} else {
457449
// Check allocation alignment and offset alignment.
458450
if alloc_align.bytes() < align.bytes() {
459-
throw_ub!(AlignmentCheckFailed { has: alloc_align, required: align });
451+
self.alignment_check_failed(alloc_align, align, check)?;
460452
}
461-
check_offset_align(offset.bytes(), align)?;
453+
self.check_offset_align(offset.bytes(), align, check)?;
462454
}
463455
}
464456

@@ -468,6 +460,55 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
468460
}
469461
})
470462
}
463+
464+
fn check_offset_align(
465+
&self,
466+
offset: u64,
467+
align: Align,
468+
check: CheckAlignment,
469+
) -> InterpResult<'tcx> {
470+
if offset % align.bytes() == 0 {
471+
Ok(())
472+
} else {
473+
// The biggest power of two through which `offset` is divisible.
474+
let offset_pow2 = 1 << offset.trailing_zeros();
475+
self.alignment_check_failed(Align::from_bytes(offset_pow2).unwrap(), align, check)
476+
}
477+
}
478+
479+
fn alignment_check_failed(
480+
&self,
481+
has: Align,
482+
required: Align,
483+
check: CheckAlignment,
484+
) -> InterpResult<'tcx, ()> {
485+
match check {
486+
CheckAlignment::Error => {
487+
throw_ub!(AlignmentCheckFailed { has, required })
488+
}
489+
CheckAlignment::No => span_bug!(
490+
self.cur_span(),
491+
"`alignment_check_failed` called when no alignment check requested"
492+
),
493+
CheckAlignment::FutureIncompat => self.tcx.struct_span_lint_hir(
494+
INVALID_ALIGNMENT,
495+
self.stack().iter().find_map(|frame| frame.lint_root()).unwrap_or(CRATE_HIR_ID),
496+
self.cur_span(),
497+
UndefinedBehaviorInfo::AlignmentCheckFailed { has, required }.to_string(),
498+
|db| {
499+
let mut stacktrace = self.generate_stacktrace();
500+
// Filter out `requires_caller_location` frames.
501+
stacktrace
502+
.retain(|frame| !frame.instance.def.requires_caller_location(*self.tcx));
503+
for frame in stacktrace {
504+
db.span_label(frame.span, format!("inside `{}`", frame.instance));
505+
}
506+
db
507+
},
508+
),
509+
}
510+
Ok(())
511+
}
471512
}
472513

473514
/// Allocation accessors

compiler/rustc_const_eval/src/interpret/place.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -364,13 +364,8 @@ where
364364
.size_and_align_of_mplace(&mplace)?
365365
.unwrap_or((mplace.layout.size, mplace.layout.align.abi));
366366
assert!(mplace.align <= align, "dynamic alignment less strict than static one?");
367-
let align = M::enforce_alignment(self).then_some(align);
368-
self.check_ptr_access_align(
369-
mplace.ptr,
370-
size,
371-
align.unwrap_or(Align::ONE),
372-
CheckInAllocMsg::DerefTest,
373-
)?;
367+
let align = if M::enforce_alignment(self).should_check() { align } else { Align::ONE };
368+
self.check_ptr_access_align(mplace.ptr, size, align, CheckInAllocMsg::DerefTest)?;
374369
Ok(())
375370
}
376371

compiler/rustc_const_eval/src/util/might_permit_raw_init.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use rustc_middle::ty::{ParamEnv, TyCtxt};
33
use rustc_session::Limit;
44
use rustc_target::abi::{Abi, FieldsShape, InitKind, Scalar, Variants};
55

6-
use crate::const_eval::CompileTimeInterpreter;
6+
use crate::const_eval::{CheckAlignment, CompileTimeInterpreter};
77
use crate::interpret::{InterpCx, MemoryKind, OpTy};
88

99
/// Determines if this type permits "raw" initialization by just transmuting some memory into an
@@ -41,7 +41,7 @@ fn might_permit_raw_init_strict<'tcx>(
4141
let machine = CompileTimeInterpreter::new(
4242
Limit::new(0),
4343
/*can_access_statics:*/ false,
44-
/*check_alignment:*/ true,
44+
CheckAlignment::Error,
4545
);
4646

4747
let mut cx = InterpCx::new(tcx, rustc_span::DUMMY_SP, ParamEnv::reveal_all(), machine);

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,6 +1019,42 @@ declare_lint! {
10191019
};
10201020
}
10211021

1022+
declare_lint! {
1023+
/// The `invalid_alignment` lint detects dereferences of misaligned pointers during
1024+
/// constant evluation.
1025+
///
1026+
/// ### Example
1027+
///
1028+
/// ```rust,compile_fail
1029+
/// const FOO: () = unsafe {
1030+
/// let x = [0_u8; 10];
1031+
/// let y = x.as_ptr() as *const u32;
1032+
/// *y; // the address of a `u8` array is unknown and thus we don't know if
1033+
/// // it is aligned enough for reading a `u32`.
1034+
/// }
1035+
/// ```
1036+
///
1037+
/// {{produces}}
1038+
///
1039+
/// ### Explanation
1040+
///
1041+
/// The compiler allowed dereferencing raw pointers irrespective of alignment
1042+
/// during const eval due to the const evaluator at the time not making it easy
1043+
/// or cheap to check. Now that it is both, this is not accepted anymore.
1044+
///
1045+
/// Since it was undefined behaviour to begin with, this breakage does not violate
1046+
/// Rust's stability guarantees. Using undefined behaviour can cause arbitrary
1047+
/// behaviour, including failure to build.
1048+
///
1049+
/// [future-incompatible]: ../index.md#future-incompatible-lints
1050+
pub INVALID_ALIGNMENT,
1051+
Deny,
1052+
"raw pointers must be aligned before dereferencing",
1053+
@future_incompatible = FutureIncompatibleInfo {
1054+
reference: "issue #68585 <https://github.com/rust-lang/rust/issues/104616>",
1055+
};
1056+
}
1057+
10221058
declare_lint! {
10231059
/// The `exported_private_dependencies` lint detects private dependencies
10241060
/// that are exposed in a public interface.

compiler/rustc_mir_transform/src/const_prop.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::cell::Cell;
66
use either::Right;
77

88
use rustc_ast::Mutability;
9+
use rustc_const_eval::const_eval::CheckAlignment;
910
use rustc_data_structures::fx::FxHashSet;
1011
use rustc_hir::def::DefKind;
1112
use rustc_index::bit_set::BitSet;
@@ -186,10 +187,10 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine<'mir, 'tcx>
186187
type MemoryKind = !;
187188

188189
#[inline(always)]
189-
fn enforce_alignment(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
190+
fn enforce_alignment(_ecx: &InterpCx<'mir, 'tcx, Self>) -> CheckAlignment {
190191
// We do not check for alignment to avoid having to carry an `Align`
191192
// in `ConstValue::ByRef`.
192-
false
193+
CheckAlignment::No
193194
}
194195

195196
#[inline(always)]

compiler/rustc_mir_transform/src/dataflow_const_prop.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//!
33
//! Currently, this pass only propagates scalar values.
44
5+
use rustc_const_eval::const_eval::CheckAlignment;
56
use rustc_const_eval::interpret::{ConstValue, ImmTy, Immediate, InterpCx, Scalar};
67
use rustc_data_structures::fx::FxHashMap;
78
use rustc_middle::mir::visit::{MutVisitor, Visitor};
@@ -448,7 +449,7 @@ impl<'mir, 'tcx> rustc_const_eval::interpret::Machine<'mir, 'tcx> for DummyMachi
448449
type MemoryKind = !;
449450
const PANIC_ON_ALLOC_FAIL: bool = true;
450451

451-
fn enforce_alignment(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
452+
fn enforce_alignment(_ecx: &InterpCx<'mir, 'tcx, Self>) -> CheckAlignment {
452453
unimplemented!()
453454
}
454455

0 commit comments

Comments
 (0)