Skip to content

Commit cfd9ece

Browse files
committed
Make too-large byte sizes a validity error in Layout
1 parent c396bb3 commit cfd9ece

File tree

7 files changed

+145
-7
lines changed

7 files changed

+145
-7
lines changed

library/core/src/alloc/layout.rs

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::cmp;
22
use crate::fmt;
3-
use crate::mem::{self, ValidAlign};
3+
use crate::mem::{self, ValidAlign, ValidSize};
44
use crate::ptr::NonNull;
55

66
// While this function is used in one place and its implementation
@@ -30,7 +30,7 @@ const fn size_align<T>() -> (usize, usize) {
3030
#[lang = "alloc_layout"]
3131
pub struct Layout {
3232
// size of the requested block of memory, measured in bytes.
33-
size: usize,
33+
size: ValidSize,
3434

3535
// alignment of the requested block of memory, measured in bytes.
3636
// we ensure that this is always a power-of-two, because API's
@@ -96,8 +96,11 @@ impl Layout {
9696
#[must_use]
9797
#[inline]
9898
pub const unsafe fn from_size_align_unchecked(size: usize, align: usize) -> Self {
99-
// SAFETY: the caller must ensure that `align` is a power of two.
100-
Layout { size, align: unsafe { ValidAlign::new_unchecked(align) } }
99+
// SAFETY: the caller must ensure that `align` is a power of two and
100+
// that `size` is no more than `isize::MAX`.
101+
unsafe {
102+
Layout { size: ValidSize::new_unchecked(size), align: ValidAlign::new_unchecked(align) }
103+
}
101104
}
102105

103106
/// The minimum size in bytes for a memory block of this layout.
@@ -106,7 +109,7 @@ impl Layout {
106109
#[must_use]
107110
#[inline]
108111
pub const fn size(&self) -> usize {
109-
self.size
112+
self.size.as_usize()
110113
}
111114

112115
/// The minimum byte alignment for a memory block of this layout.

library/core/src/mem/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ mod valid_align;
2727
// alignment as a parameter, such as `Layout::padding_needed_for`.
2828
pub(crate) use valid_align::ValidAlign;
2929

30+
mod valid_size;
31+
pub(crate) use valid_size::ValidSize;
32+
3033
#[stable(feature = "rust1", since = "1.0.0")]
3134
#[doc(inline)]
3235
pub use crate::intrinsics::transmute;

library/core/src/mem/valid_size.rs

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use crate::convert::TryFrom;
2+
use crate::{fmt, num};
3+
4+
/// A type storing a possible object size (in bytes) in the rust abstract machine.
5+
///
6+
/// This can be thought of as a positive `isize`, or `usize` without the high bit
7+
/// set. This is important because [`pointer::offset`] is UB for *byte* sizes
8+
/// too large for an `isize`, and there's a corresponding language limit on the
9+
/// size of any allocated object.
10+
///
11+
/// Note that particularly large sizes, while representable in this type, are
12+
/// likely not to be supported by actual allocators and machines.
13+
#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
14+
#[repr(transparent)]
15+
#[cfg_attr(target_pointer_width = "16", rustc_layout_scalar_valid_range_end(0x7FFF))]
16+
#[cfg_attr(target_pointer_width = "32", rustc_layout_scalar_valid_range_end(0x7FFF_FFFF))]
17+
#[cfg_attr(target_pointer_width = "64", rustc_layout_scalar_valid_range_end(0x7FFF_FFFF_FFFF_FFFF))]
18+
pub(crate) struct ValidSize(usize);
19+
20+
const MAX_SIZE: usize = isize::MAX as usize;
21+
22+
const _: () = unsafe { ValidSize::new_unchecked(MAX_SIZE); };
23+
24+
impl ValidSize {
25+
/// Creates a `ValidSize` from a `usize` that fits in an `isize`.
26+
///
27+
/// # Safety
28+
///
29+
/// `size` must be less than or equal to `isize::MAX`.
30+
///
31+
/// Equivalently, it must not have its high bit set.
32+
#[inline]
33+
pub(crate) const unsafe fn new_unchecked(size: usize) -> Self {
34+
debug_assert!(size <= MAX_SIZE);
35+
36+
// SAFETY: By precondition, this must be within our validity invariant.
37+
unsafe { ValidSize(size) }
38+
}
39+
40+
#[inline]
41+
pub(crate) const fn as_usize(self) -> usize {
42+
self.0
43+
}
44+
}
45+
46+
impl TryFrom<usize> for ValidSize {
47+
type Error = num::TryFromIntError;
48+
49+
#[inline]
50+
fn try_from(size: usize) -> Result<ValidSize, Self::Error> {
51+
if size <= MAX_SIZE {
52+
// SAFETY: Just checked it's within our validity invariant.
53+
unsafe { Ok(ValidSize(size)) }
54+
} else {
55+
Err(num::TryFromIntError(()))
56+
}
57+
}
58+
}
59+
60+
impl fmt::Debug for ValidSize {
61+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62+
self.as_usize().fmt(f)
63+
}
64+
}

library/core/tests/alloc.rs

+16
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ fn layout_debug_shows_log2_of_alignment() {
2020
assert_eq!(s, "Layout { size: 24576, align: 8192 (1 << 13) }");
2121
}
2222

23+
#[test]
24+
fn layout_rejects_invalid_combinations() {
25+
assert!(Layout::from_size_align(3, 3).is_err()); // bad align
26+
assert!(Layout::from_size_align(1 << (usize::BITS - 1), 1).is_err()); // bad size
27+
assert!(Layout::from_size_align(isize::MAX as usize, 2).is_err()); // fails round-up
28+
assert!(Layout::from_size_align(1, 1 << (usize::BITS - 1)).is_err()); // fails round-up
29+
}
30+
2331
// Running this normally doesn't do much, but it's also run in Miri, which
2432
// will double-check that these are allowed by the validity invariants.
2533
#[test]
@@ -29,3 +37,11 @@ fn layout_accepts_all_valid_alignments() {
2937
assert_eq!(layout.align(), 1_usize << align);
3038
}
3139
}
40+
41+
#[test]
42+
fn layout_accepts_various_valid_sizes() {
43+
for shift in 1..usize::BITS {
44+
let layout = Layout::from_size_align(usize::MAX >> shift, 1).unwrap();
45+
assert_eq!(layout.size(), usize::MAX >> shift);
46+
}
47+
}

src/test/ui/consts/std/alloc.32bit.stderr

+23-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,28 @@ LL | const LAYOUT_INVALID_THREE: Layout = unsafe { Layout::from_size_align_unche
2020
09 00 00 00 03 00 00 00 │ ........
2121
}
2222

23-
error: aborting due to 2 previous errors
23+
error[E0080]: it is undefined behavior to use this value
24+
--> $DIR/alloc.rs:17:1
25+
|
26+
LL | const LAYOUT_SIZE_NEGATIVE_ONE: Layout = unsafe { Layout::from_size_align_unchecked(-1 as _, 1) };
27+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .size: encountered 4294967295, but expected something less or equal to 2147483647
28+
|
29+
= 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.
30+
= note: the raw bytes of the constant (size: 8, align: 4) {
31+
ff ff ff ff 01 00 00 00 │ ........
32+
}
33+
34+
error[E0080]: it is undefined behavior to use this value
35+
--> $DIR/alloc.rs:21:1
36+
|
37+
LL | const LAYOUT_SIZE_HIGH_BIT: Layout = unsafe { Layout::from_size_align_unchecked((isize::MAX as usize) + 1, 1) };
38+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .size: encountered 2147483648, but expected something less or equal to 2147483647
39+
|
40+
= 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.
41+
= note: the raw bytes of the constant (size: 8, align: 4) {
42+
00 00 00 80 01 00 00 00 │ ........
43+
}
44+
45+
error: aborting due to 4 previous errors
2446

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

src/test/ui/consts/std/alloc.64bit.stderr

+23-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,28 @@ LL | const LAYOUT_INVALID_THREE: Layout = unsafe { Layout::from_size_align_unche
2020
09 00 00 00 00 00 00 00 03 00 00 00 00 00 00 00 │ ................
2121
}
2222

23-
error: aborting due to 2 previous errors
23+
error[E0080]: it is undefined behavior to use this value
24+
--> $DIR/alloc.rs:17:1
25+
|
26+
LL | const LAYOUT_SIZE_NEGATIVE_ONE: Layout = unsafe { Layout::from_size_align_unchecked(-1 as _, 1) };
27+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .size: encountered 18446744073709551615, but expected something less or equal to 9223372036854775807
28+
|
29+
= 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.
30+
= note: the raw bytes of the constant (size: 16, align: 8) {
31+
ff ff ff ff ff ff ff ff 01 00 00 00 00 00 00 00 │ ................
32+
}
33+
34+
error[E0080]: it is undefined behavior to use this value
35+
--> $DIR/alloc.rs:21:1
36+
|
37+
LL | const LAYOUT_SIZE_HIGH_BIT: Layout = unsafe { Layout::from_size_align_unchecked((isize::MAX as usize) + 1, 1) };
38+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .size: encountered 9223372036854775808, but expected something less or equal to 9223372036854775807
39+
|
40+
= 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.
41+
= note: the raw bytes of the constant (size: 16, align: 8) {
42+
00 00 00 00 00 00 00 80 01 00 00 00 00 00 00 00 │ ................
43+
}
44+
45+
error: aborting due to 4 previous errors
2446

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

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

+8
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,12 @@ const LAYOUT_INVALID_ZERO: Layout = unsafe { Layout::from_size_align_unchecked(0
1313
const LAYOUT_INVALID_THREE: Layout = unsafe { Layout::from_size_align_unchecked(9, 3) };
1414
//~^ ERROR it is undefined behavior to use this value
1515

16+
// not ok, since size needs to be no more than `isize::MAX`
17+
const LAYOUT_SIZE_NEGATIVE_ONE: Layout = unsafe { Layout::from_size_align_unchecked(-1 as _, 1) };
18+
//~^ ERROR it is undefined behavior to use this value
19+
20+
// not ok, since size needs to be no more than `isize::MAX`
21+
const LAYOUT_SIZE_HIGH_BIT: Layout = unsafe { Layout::from_size_align_unchecked((isize::MAX as usize) + 1, 1) };
22+
//~^ ERROR it is undefined behavior to use this value
23+
1624
fn main() {}

0 commit comments

Comments
 (0)