Skip to content

Commit ef3f6f7

Browse files
committed
Be smarter and less UB about pointer alignment.
1 parent ec8a93d commit ef3f6f7

File tree

6 files changed

+60
-12
lines changed

6 files changed

+60
-12
lines changed

src/boxed.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use alloc::{alloc::Layout, string::String};
66
use core::{
7-
mem::forget,
7+
mem::{align_of, forget},
88
ops::{Deref, DerefMut},
99
ptr::NonNull,
1010
};
@@ -22,7 +22,7 @@ pub(crate) struct BoxedString {
2222
#[cfg(endian = "big")]
2323
#[repr(C)]
2424
pub(crate) struct BoxedString {
25-
length: usize,
25+
len: usize,
2626
cap: usize,
2727
ptr: NunNull<u8>,
2828
}
@@ -44,8 +44,17 @@ impl GenericString for BoxedString {
4444
impl BoxedString {
4545
const MINIMAL_CAPACITY: usize = MAX_INLINE * 2;
4646

47+
pub(crate) fn check_alignment(this: &Self) -> usize {
48+
let ptr: *const u8 = this.ptr.as_ptr();
49+
ptr.align_offset(2)
50+
}
51+
4752
fn layout_for(cap: usize) -> Layout {
48-
let layout = Layout::array::<u8>(cap).unwrap();
53+
// Always request memory that is specifically aligned to at least 2, so
54+
// the least significant bit is guaranteed to be 0.
55+
let layout = Layout::array::<u8>(cap)
56+
.and_then(|layout| layout.align_to(align_of::<u16>()))
57+
.unwrap();
4958
assert!(
5059
layout.size() <= isize::MAX as usize,
5160
"allocation too large!"
@@ -56,11 +65,12 @@ impl BoxedString {
5665
fn alloc(cap: usize) -> NonNull<u8> {
5766
let layout = Self::layout_for(cap);
5867
#[allow(unsafe_code)]
59-
let ptr = unsafe { alloc::alloc::alloc(layout) };
60-
match NonNull::new(ptr) {
68+
let ptr = match NonNull::new(unsafe { alloc::alloc::alloc(layout) }) {
6169
Some(ptr) => ptr,
6270
None => alloc::alloc::handle_alloc_error(layout),
63-
}
71+
};
72+
debug_assert!(ptr.as_ptr().align_offset(2) == 0);
73+
ptr
6474
}
6575

6676
fn realloc(&mut self, cap: usize) {
@@ -74,6 +84,7 @@ impl BoxedString {
7484
None => alloc::alloc::handle_alloc_error(layout),
7585
};
7686
self.cap = cap;
87+
debug_assert!(self.ptr.as_ptr().align_offset(2) == 0);
7788
}
7889

7990
pub(crate) fn ensure_capacity(&mut self, target_cap: usize) {

src/config.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use crate::{boxed::BoxedString, inline::InlineString, SmartString};
66
use alloc::string::String;
77
use core::mem::{align_of, size_of};
8-
use static_assertions::{assert_eq_size, const_assert, const_assert_eq};
8+
use static_assertions::{assert_eq_align, assert_eq_size, const_assert, const_assert_eq};
99

1010
/// A compact string representation equal to [`String`] in size with guaranteed inlining.
1111
///
@@ -71,6 +71,11 @@ assert_eq_size!(BoxedString, SmartString<LazyCompact>);
7171
assert_eq_size!(InlineString, SmartString<Compact>);
7272
assert_eq_size!(InlineString, SmartString<LazyCompact>);
7373

74+
assert_eq_align!(BoxedString, String);
75+
assert_eq_align!(InlineString, String);
76+
assert_eq_align!(SmartString<Compact>, String);
77+
assert_eq_align!(SmartString<LazyCompact>, String);
78+
7479
assert_eq_size!(String, SmartString<Compact>);
7580
assert_eq_size!(String, SmartString<LazyCompact>);
7681

src/inline.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@ use core::{
1010

1111
#[cfg(not(endian = "big"))]
1212
#[repr(C)]
13+
#[cfg_attr(target_pointer_width = "64", repr(align(8)))]
14+
#[cfg_attr(target_pointer_width = "32", repr(align(4)))]
1315
pub(crate) struct InlineString {
1416
pub(crate) marker: Marker,
1517
pub(crate) data: [u8; MAX_INLINE],
1618
}
1719

1820
#[cfg(endian = "big")]
1921
#[repr(C)]
22+
#[cfg_attr(target_pointer_width = "64", repr(align(8)))]
23+
#[cfg_attr(target_pointer_width = "32", repr(align(4)))]
2024
pub(crate) struct InlineString {
2125
pub(crate) data: [u8; MAX_INLINE],
2226
pub(crate) marker: Marker,

src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,6 @@ pub mod alias {
188188
/// state changes wouldn't carry over if the inline string is promoted to a boxed
189189
/// one - not without also storing that state in the inline representation, which
190190
/// would waste precious bytes for inline string data.
191-
#[repr(C)]
192-
#[cfg_attr(target_pointer_width = "64", repr(align(8)))]
193-
#[cfg_attr(target_pointer_width = "32", repr(align(4)))]
194191
pub struct SmartString<Mode: SmartStringMode> {
195192
data: MaybeUninit<InlineString>,
196193
mode: PhantomData<Mode>,
@@ -300,8 +297,11 @@ impl<Mode: SmartStringMode> SmartString<Mode> {
300297
}
301298

302299
fn discriminant(&self) -> Discriminant {
300+
// unsafe { self.data.assume_init() }.marker.discriminant()
301+
let str_ptr: *const BoxedString =
302+
self.data.as_ptr().cast() as *const _ as *const BoxedString;
303303
#[allow(unsafe_code)]
304-
unsafe { self.data.assume_init() }.marker.discriminant()
304+
Discriminant::from_bit(BoxedString::check_alignment(unsafe { &*str_ptr }) == 1)
305305
}
306306

307307
fn cast(&self) -> StringCast<'_> {

src/marker_byte.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub(crate) enum Discriminant {
1010

1111
impl Discriminant {
1212
#[inline(always)]
13-
const fn from_bit(bit: bool) -> Self {
13+
pub(crate) const fn from_bit(bit: bool) -> Self {
1414
if bit {
1515
Self::Inline
1616
} else {

src/test.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,4 +523,32 @@ mod tests {
523523
SmartString::<Compact>::from("\u{323}\u{323}\u{323}ω\u{323}\u{323}\u{323}\u{e323}㤘");
524524
s.remove(20);
525525
}
526+
527+
#[test]
528+
fn check_alignment() {
529+
use crate::boxed::BoxedString;
530+
use crate::inline::InlineString;
531+
use crate::marker_byte::Discriminant;
532+
533+
let inline = InlineString::new();
534+
let inline_ptr: *const InlineString = &inline;
535+
let boxed_ptr: *const BoxedString = inline_ptr.cast();
536+
#[allow(unsafe_code)]
537+
let discriminant =
538+
Discriminant::from_bit(BoxedString::check_alignment(unsafe { &*boxed_ptr }) == 1);
539+
assert_eq!(Discriminant::Inline, discriminant);
540+
541+
let boxed = BoxedString::from_str(32, "welp");
542+
let discriminant = Discriminant::from_bit(BoxedString::check_alignment(&boxed) == 1);
543+
assert_eq!(Discriminant::Boxed, discriminant);
544+
545+
let mut s = SmartString::<Compact>::new();
546+
assert_eq!(Discriminant::Inline, s.discriminant());
547+
let big_str = "1234567890123456789012345678901234567890";
548+
assert!(big_str.len() > MAX_INLINE);
549+
s.push_str(big_str);
550+
assert_eq!(Discriminant::Boxed, s.discriminant());
551+
s.clear();
552+
assert_eq!(Discriminant::Inline, s.discriminant());
553+
}
526554
}

0 commit comments

Comments
 (0)