Skip to content

Commit fe0c08a

Browse files
committed
Make non-power-of-two alignments a validity error in Layout
Inspired by the zulip conversation about how `Layout` should better enforce `size < isize::MAX as usize`, this uses an N-variant enum on N-bit platforms to require at the validity level that the existing invariant of "must be a power of two" is upheld. This was MIRI can catch it, and means there's a more-specific type for `Layout` to store than just `NonZeroUsize`.
1 parent 185a3f0 commit fe0c08a

File tree

7 files changed

+305
-15
lines changed

7 files changed

+305
-15
lines changed

library/core/src/alloc/layout.rs

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use crate::cmp;
22
use crate::fmt;
3-
use crate::mem;
4-
use crate::num::NonZeroUsize;
3+
use crate::mem::{self, ValidAlign};
54
use crate::ptr::NonNull;
65

76
// While this function is used in one place and its implementation
@@ -40,7 +39,7 @@ pub struct Layout {
4039
//
4140
// (However, we do not analogously require `align >= sizeof(void*)`,
4241
// even though that is *also* a requirement of `posix_memalign`.)
43-
align_: NonZeroUsize,
42+
align_: ValidAlign,
4443
}
4544

4645
impl Layout {
@@ -97,8 +96,8 @@ impl Layout {
9796
#[must_use]
9897
#[inline]
9998
pub const unsafe fn from_size_align_unchecked(size: usize, align: usize) -> Self {
100-
// SAFETY: the caller must ensure that `align` is greater than zero.
101-
Layout { size_: size, align_: unsafe { NonZeroUsize::new_unchecked(align) } }
99+
// SAFETY: the caller must ensure that `align` is a power of two.
100+
Layout { size_: size, align_: unsafe { ValidAlign::new_unchecked(align) } }
102101
}
103102

104103
/// The minimum size in bytes for a memory block of this layout.
@@ -117,7 +116,7 @@ impl Layout {
117116
without modifying the layout"]
118117
#[inline]
119118
pub const fn align(&self) -> usize {
120-
self.align_.get()
119+
self.align_.as_nonzero().get()
121120
}
122121

123122
/// Constructs a `Layout` suitable for holding a value of type `T`.

library/core/src/mem/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ mod maybe_uninit;
2121
#[stable(feature = "maybe_uninit", since = "1.36.0")]
2222
pub use maybe_uninit::MaybeUninit;
2323

24+
mod valid_align;
25+
// For now this type is left crate-local. It could potentially make sense to expose
26+
// it publicly, as it would be a nice parameter type for methods which need to take
27+
// alignment as a parameter, such as `Layout::padding_needed_for`.
28+
pub(crate) use valid_align::ValidAlign;
29+
2430
#[stable(feature = "rust1", since = "1.0.0")]
2531
#[doc(inline)]
2632
pub use crate::intrinsics::transmute;

library/core/src/mem/valid_align.rs

+240
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
use crate::convert::TryFrom;
2+
use crate::num::NonZeroUsize;
3+
use crate::{cmp, fmt, mem, num};
4+
5+
/// A type storing a `usize` which is a power of two, and thus
6+
/// represents a possible alignment in the rust abstract machine.
7+
///
8+
/// Note that particularly large alignments, while representable in this type,
9+
/// are likely not to be supported by actual allocators and linkers.
10+
#[derive(Copy, Clone)]
11+
#[repr(transparent)]
12+
pub(crate) struct ValidAlign(ValidAlignEnum);
13+
14+
// ValidAlign is `repr(usize)`, but via extra steps.
15+
const _: () = assert!(mem::size_of::<ValidAlign>() == mem::size_of::<usize>());
16+
const _: () = assert!(mem::align_of::<ValidAlign>() == mem::align_of::<usize>());
17+
18+
impl ValidAlign {
19+
/// Creates a `ValidAlign` from a power-of-two `usize`.
20+
///
21+
/// # Safety
22+
///
23+
/// `align` must be a power of two.
24+
///
25+
/// Equivalently, it must be `1 << exp` for some `exp` in `0..usize::BITS`.
26+
/// It must *not* be zero.
27+
#[inline]
28+
pub(crate) const unsafe fn new_unchecked(align: usize) -> Self {
29+
debug_assert!(align.is_power_of_two());
30+
31+
// SAFETY: By precondition, this must be a power of two, and
32+
// our variants encompass all possible powers of two.
33+
unsafe { mem::transmute::<usize, ValidAlign>(align) }
34+
}
35+
36+
#[inline]
37+
pub(crate) const fn as_nonzero(self) -> NonZeroUsize {
38+
// SAFETY: All the discriminants are non-zero.
39+
unsafe { NonZeroUsize::new_unchecked(self.0 as usize) }
40+
}
41+
42+
/// Returns the base 2 logarithm of the alignment.
43+
///
44+
/// This is always exact, as `self` represents a power of two.
45+
#[inline]
46+
pub(crate) fn log2(self) -> u32 {
47+
self.as_nonzero().trailing_zeros()
48+
}
49+
}
50+
51+
impl fmt::Debug for ValidAlign {
52+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53+
write!(f, "{:?} (1 << {:?})", self.as_nonzero(), self.log2())
54+
}
55+
}
56+
57+
impl TryFrom<NonZeroUsize> for ValidAlign {
58+
type Error = num::TryFromIntError;
59+
60+
#[inline]
61+
fn try_from(align: NonZeroUsize) -> Result<ValidAlign, Self::Error> {
62+
if align.is_power_of_two() {
63+
// SAFETY: Just checked for power-of-two
64+
unsafe { Ok(ValidAlign::new_unchecked(align.get())) }
65+
} else {
66+
Err(num::TryFromIntError(()))
67+
}
68+
}
69+
}
70+
71+
impl TryFrom<usize> for ValidAlign {
72+
type Error = num::TryFromIntError;
73+
74+
#[inline]
75+
fn try_from(align: usize) -> Result<ValidAlign, Self::Error> {
76+
if align.is_power_of_two() {
77+
// SAFETY: Just checked for power-of-two
78+
unsafe { Ok(ValidAlign::new_unchecked(align)) }
79+
} else {
80+
Err(num::TryFromIntError(()))
81+
}
82+
}
83+
}
84+
85+
impl cmp::Eq for ValidAlign {}
86+
87+
impl cmp::PartialEq for ValidAlign {
88+
#[inline]
89+
fn eq(&self, other: &Self) -> bool {
90+
self.as_nonzero() == other.as_nonzero()
91+
}
92+
}
93+
94+
impl cmp::Ord for ValidAlign {
95+
#[inline]
96+
fn cmp(&self, other: &Self) -> cmp::Ordering {
97+
self.as_nonzero().cmp(&other.as_nonzero())
98+
}
99+
}
100+
101+
impl cmp::PartialOrd for ValidAlign {
102+
#[inline]
103+
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
104+
Some(self.cmp(other))
105+
}
106+
}
107+
108+
#[cfg(target_pointer_width = "16")]
109+
type ValidAlignEnum = ValidAlignEnum16;
110+
#[cfg(target_pointer_width = "32")]
111+
type ValidAlignEnum = ValidAlignEnum32;
112+
#[cfg(target_pointer_width = "64")]
113+
type ValidAlignEnum = ValidAlignEnum64;
114+
115+
#[derive(Copy, Clone)]
116+
#[repr(u16)]
117+
enum ValidAlignEnum16 {
118+
_Align1Shl0 = 1 << 0,
119+
_Align1Shl1 = 1 << 1,
120+
_Align1Shl2 = 1 << 2,
121+
_Align1Shl3 = 1 << 3,
122+
_Align1Shl4 = 1 << 4,
123+
_Align1Shl5 = 1 << 5,
124+
_Align1Shl6 = 1 << 6,
125+
_Align1Shl7 = 1 << 7,
126+
_Align1Shl8 = 1 << 8,
127+
_Align1Shl9 = 1 << 9,
128+
_Align1Shl10 = 1 << 10,
129+
_Align1Shl11 = 1 << 11,
130+
_Align1Shl12 = 1 << 12,
131+
_Align1Shl13 = 1 << 13,
132+
_Align1Shl14 = 1 << 14,
133+
_Align1Shl15 = 1 << 15,
134+
}
135+
136+
#[derive(Copy, Clone)]
137+
#[repr(u32)]
138+
enum ValidAlignEnum32 {
139+
_Align1Shl0 = 1 << 0,
140+
_Align1Shl1 = 1 << 1,
141+
_Align1Shl2 = 1 << 2,
142+
_Align1Shl3 = 1 << 3,
143+
_Align1Shl4 = 1 << 4,
144+
_Align1Shl5 = 1 << 5,
145+
_Align1Shl6 = 1 << 6,
146+
_Align1Shl7 = 1 << 7,
147+
_Align1Shl8 = 1 << 8,
148+
_Align1Shl9 = 1 << 9,
149+
_Align1Shl10 = 1 << 10,
150+
_Align1Shl11 = 1 << 11,
151+
_Align1Shl12 = 1 << 12,
152+
_Align1Shl13 = 1 << 13,
153+
_Align1Shl14 = 1 << 14,
154+
_Align1Shl15 = 1 << 15,
155+
_Align1Shl16 = 1 << 16,
156+
_Align1Shl17 = 1 << 17,
157+
_Align1Shl18 = 1 << 18,
158+
_Align1Shl19 = 1 << 19,
159+
_Align1Shl20 = 1 << 20,
160+
_Align1Shl21 = 1 << 21,
161+
_Align1Shl22 = 1 << 22,
162+
_Align1Shl23 = 1 << 23,
163+
_Align1Shl24 = 1 << 24,
164+
_Align1Shl25 = 1 << 25,
165+
_Align1Shl26 = 1 << 26,
166+
_Align1Shl27 = 1 << 27,
167+
_Align1Shl28 = 1 << 28,
168+
_Align1Shl29 = 1 << 29,
169+
_Align1Shl30 = 1 << 30,
170+
_Align1Shl31 = 1 << 31,
171+
}
172+
173+
#[derive(Copy, Clone)]
174+
#[repr(u64)]
175+
enum ValidAlignEnum64 {
176+
_Align1Shl0 = 1 << 0,
177+
_Align1Shl1 = 1 << 1,
178+
_Align1Shl2 = 1 << 2,
179+
_Align1Shl3 = 1 << 3,
180+
_Align1Shl4 = 1 << 4,
181+
_Align1Shl5 = 1 << 5,
182+
_Align1Shl6 = 1 << 6,
183+
_Align1Shl7 = 1 << 7,
184+
_Align1Shl8 = 1 << 8,
185+
_Align1Shl9 = 1 << 9,
186+
_Align1Shl10 = 1 << 10,
187+
_Align1Shl11 = 1 << 11,
188+
_Align1Shl12 = 1 << 12,
189+
_Align1Shl13 = 1 << 13,
190+
_Align1Shl14 = 1 << 14,
191+
_Align1Shl15 = 1 << 15,
192+
_Align1Shl16 = 1 << 16,
193+
_Align1Shl17 = 1 << 17,
194+
_Align1Shl18 = 1 << 18,
195+
_Align1Shl19 = 1 << 19,
196+
_Align1Shl20 = 1 << 20,
197+
_Align1Shl21 = 1 << 21,
198+
_Align1Shl22 = 1 << 22,
199+
_Align1Shl23 = 1 << 23,
200+
_Align1Shl24 = 1 << 24,
201+
_Align1Shl25 = 1 << 25,
202+
_Align1Shl26 = 1 << 26,
203+
_Align1Shl27 = 1 << 27,
204+
_Align1Shl28 = 1 << 28,
205+
_Align1Shl29 = 1 << 29,
206+
_Align1Shl30 = 1 << 30,
207+
_Align1Shl31 = 1 << 31,
208+
_Align1Shl32 = 1 << 32,
209+
_Align1Shl33 = 1 << 33,
210+
_Align1Shl34 = 1 << 34,
211+
_Align1Shl35 = 1 << 35,
212+
_Align1Shl36 = 1 << 36,
213+
_Align1Shl37 = 1 << 37,
214+
_Align1Shl38 = 1 << 38,
215+
_Align1Shl39 = 1 << 39,
216+
_Align1Shl40 = 1 << 40,
217+
_Align1Shl41 = 1 << 41,
218+
_Align1Shl42 = 1 << 42,
219+
_Align1Shl43 = 1 << 43,
220+
_Align1Shl44 = 1 << 44,
221+
_Align1Shl45 = 1 << 45,
222+
_Align1Shl46 = 1 << 46,
223+
_Align1Shl47 = 1 << 47,
224+
_Align1Shl48 = 1 << 48,
225+
_Align1Shl49 = 1 << 49,
226+
_Align1Shl50 = 1 << 50,
227+
_Align1Shl51 = 1 << 51,
228+
_Align1Shl52 = 1 << 52,
229+
_Align1Shl53 = 1 << 53,
230+
_Align1Shl54 = 1 << 54,
231+
_Align1Shl55 = 1 << 55,
232+
_Align1Shl56 = 1 << 56,
233+
_Align1Shl57 = 1 << 57,
234+
_Align1Shl58 = 1 << 58,
235+
_Align1Shl59 = 1 << 59,
236+
_Align1Shl60 = 1 << 60,
237+
_Align1Shl61 = 1 << 61,
238+
_Align1Shl62 = 1 << 62,
239+
_Align1Shl63 = 1 << 63,
240+
}

library/core/tests/alloc.rs

+18
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,21 @@ fn const_unchecked_layout() {
1111
assert_eq!(LAYOUT.align(), ALIGN);
1212
assert_eq!(Some(DANGLING), NonNull::new(ALIGN as *mut u8));
1313
}
14+
15+
#[test]
16+
fn layout_debug_shows_log2_of_alignment() {
17+
// `Debug` is not stable, but here's what it does right now
18+
let layout = Layout::from_size_align(24576, 8192).unwrap();
19+
let s = format!("{:?}", layout);
20+
assert_eq!(s, "Layout { size_: 24576, align_: 8192 (1 << 13) }");
21+
}
22+
23+
// Running this normally doesn't do much, but it's also run in Miri, which
24+
// will double-check that these are allowed by the validity invariants.
25+
#[test]
26+
fn layout_accepts_all_valid_alignments() {
27+
for align in 0..usize::BITS {
28+
let layout = Layout::from_size_align(0, 1_usize << align).unwrap();
29+
assert_eq!(layout.align(), 1_usize << align);
30+
}
31+
}
+15-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
error[E0080]: it is undefined behavior to use this value
2-
--> $DIR/alloc.rs:8:1
2+
--> $DIR/alloc.rs:9:1
33
|
4-
LL | const LAYOUT_INVALID: Layout = unsafe { Layout::from_size_align_unchecked(0x1000, 0x00) };
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed at .align_: encountered 0, but expected something greater or equal to 1
4+
LL | const LAYOUT_INVALID_ZERO: Layout = unsafe { Layout::from_size_align_unchecked(0x1000, 0x00) };
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed at .align_.0.<enum-tag>: encountered 0x00000000, but expected a valid enum tag
66
|
77
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.
88
= note: the raw bytes of the constant (size: 8, align: 4) {
99
00 10 00 00 00 00 00 00 │ ........
1010
}
1111

12-
error: aborting due to previous error
12+
error[E0080]: it is undefined behavior to use this value
13+
--> $DIR/alloc.rs:13:1
14+
|
15+
LL | const LAYOUT_INVALID_THREE: Layout = unsafe { Layout::from_size_align_unchecked(9, 3) };
16+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed at .align_.0.<enum-tag>: encountered 0x00000003, but expected a valid enum tag
17+
|
18+
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.
19+
= note: the raw bytes of the constant (size: 8, align: 4) {
20+
09 00 00 00 03 00 00 00 │ ........
21+
}
22+
23+
error: aborting due to 2 previous errors
1324

1425
For more information about this error, try `rustc --explain E0080`.
+15-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
error[E0080]: it is undefined behavior to use this value
2-
--> $DIR/alloc.rs:8:1
2+
--> $DIR/alloc.rs:9:1
33
|
4-
LL | const LAYOUT_INVALID: Layout = unsafe { Layout::from_size_align_unchecked(0x1000, 0x00) };
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed at .align_: encountered 0, but expected something greater or equal to 1
4+
LL | const LAYOUT_INVALID_ZERO: Layout = unsafe { Layout::from_size_align_unchecked(0x1000, 0x00) };
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed at .align_.0.<enum-tag>: encountered 0x0000000000000000, but expected a valid enum tag
66
|
77
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.
88
= note: the raw bytes of the constant (size: 16, align: 8) {
99
00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │ ................
1010
}
1111

12-
error: aborting due to previous error
12+
error[E0080]: it is undefined behavior to use this value
13+
--> $DIR/alloc.rs:13:1
14+
|
15+
LL | const LAYOUT_INVALID_THREE: Layout = unsafe { Layout::from_size_align_unchecked(9, 3) };
16+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed at .align_.0.<enum-tag>: encountered 0x0000000000000003, but expected a valid enum tag
17+
|
18+
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.
19+
= note: the raw bytes of the constant (size: 16, align: 8) {
20+
09 00 00 00 00 00 00 00 03 00 00 00 00 00 00 00 │ ................
21+
}
22+
23+
error: aborting due to 2 previous errors
1324

1425
For more information about this error, try `rustc --explain E0080`.

src/test/ui/consts/std/alloc.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
// stderr-per-bitwidth
2+
// ignore-debug (the debug assertions change the error)
23
use std::alloc::Layout;
34

45
// ok
56
const LAYOUT_VALID: Layout = unsafe { Layout::from_size_align_unchecked(0x1000, 0x08) };
67

78
// not ok, since alignment needs to be non-zero.
8-
const LAYOUT_INVALID: Layout = unsafe { Layout::from_size_align_unchecked(0x1000, 0x00) };
9+
const LAYOUT_INVALID_ZERO: Layout = unsafe { Layout::from_size_align_unchecked(0x1000, 0x00) };
10+
//~^ ERROR it is undefined behavior to use this value
11+
12+
// not ok, since alignment needs to be a power of two.
13+
const LAYOUT_INVALID_THREE: Layout = unsafe { Layout::from_size_align_unchecked(9, 3) };
914
//~^ ERROR it is undefined behavior to use this value
1015

1116
fn main() {}

0 commit comments

Comments
 (0)