Skip to content

Commit 45ae860

Browse files
committed
fix(miri): Do not take &mut for a whole chunk
* fix: StringBuffer It's UB to make a &mut to a whole chunk while an `Interned` is referencing a part of the chunk * chore: Fix typo
1 parent 4e77682 commit 45ae860

3 files changed

Lines changed: 42 additions & 26 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ resolver = "2"
33
members = ["crates/*"]
44

55
[workspace.dependencies]
6-
any-intern = { version = "0.1.4", path = "crates/any-intern" }
6+
any-intern = { version = "0.1.5", path = "crates/any-intern" }
77
logic-eval-util = { version = "0.1.5", path = "crates/logic-eval-util" }
88
indexmap = "2.2.1"
99
smallvec = "1"

crates/any-intern/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "any-intern"
3-
version = "0.1.4"
3+
version = "0.1.5"
44
edition = "2021"
55
rust-version = "1.65.0"
66
description = "An interner for various types"

crates/any-intern/src/dropless.rs

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -567,12 +567,12 @@ impl<S: BuildHasher> DroplessInternSet<S> {
567567
/// A buffer for strings.
568568
#[derive(Debug, Default)]
569569
struct StringBuffer {
570-
chunks: Vec<Box<[MaybeUninit<u8>]>>,
570+
chunks: Vec<Vec<MaybeUninit<u8>>>,
571571
last_chunk_start: usize,
572572
}
573573

574574
const INIT_CHUNK_SIZE: usize = 1 << 5;
575-
const GLOW_MAX_CHUNK_SIZE: usize = 1 << 12;
575+
const GROW_MAX_CHUNK_SIZE: usize = 1 << 12;
576576

577577
impl StringBuffer {
578578
fn speculative_alloc(&mut self, upper_size: usize) -> StringWriteBuffer<'_> {
@@ -583,21 +583,21 @@ impl StringBuffer {
583583
}
584584
Some(last_chunk) if last_chunk.len() - self.last_chunk_start < upper_size => {
585585
let chunk_size = (last_chunk.len() * 2)
586-
.min(GLOW_MAX_CHUNK_SIZE)
586+
.min(GROW_MAX_CHUNK_SIZE)
587587
.max(upper_size.next_power_of_two());
588588
self.append_new_chunk(chunk_size);
589589
}
590590
_ => {}
591591
}
592592

593593
// Safety: We added the last chunk above.
594-
let last_chunk = unsafe { self.chunks.last_mut().unwrap_unchecked() };
595-
596-
let start = self.last_chunk_start;
597-
let end = self.last_chunk_start + upper_size;
598-
let buf = &mut last_chunk[start..end];
594+
let buf = unsafe {
595+
let last_chunk = self.chunks.last_mut().unwrap_unchecked();
596+
last_chunk.as_mut_ptr().add(self.last_chunk_start)
597+
};
599598
StringWriteBuffer {
600599
buf,
600+
buf_cap: upper_size,
601601
last_chuck_start: &mut self.last_chunk_start,
602602
written: 0,
603603
}
@@ -610,49 +610,50 @@ impl StringBuffer {
610610
// Safety: We reserved enough amount of memory, also it can contain uninitialized data.
611611
unsafe { chunk.set_len(chunk_size) };
612612

613-
self.chunks.push(chunk.into_boxed_slice());
613+
self.chunks.push(chunk);
614614
self.last_chunk_start = 0;
615615
}
616616
}
617617

618618
struct StringWriteBuffer<'a> {
619-
buf: &'a mut [MaybeUninit<u8>],
619+
buf: *mut MaybeUninit<u8>,
620+
buf_cap: usize,
620621
last_chuck_start: &'a mut usize,
621622
written: usize,
622623
}
623624

624625
impl<'a> StringWriteBuffer<'a> {
625626
#[inline]
626627
fn as_bytes(&self) -> &[u8] {
627-
let ptr = self.buf.as_ptr().cast::<u8>();
628-
unsafe { slice::from_raw_parts(ptr, self.written) }
628+
// Safety: bytes 0..written were initialized by write_str.
629+
unsafe { slice::from_raw_parts(self.buf.cast::<u8>(), self.written) }
629630
}
630631

631632
#[inline]
632633
fn commit(self) -> &'a [u8] {
633634
*self.last_chuck_start += self.written;
634635

635-
let ptr = self.buf.as_ptr().cast::<u8>();
636-
unsafe { slice::from_raw_parts(ptr, self.written) }
636+
// Safety: bytes 0..written were initialized by write_str.
637+
// The lifetime 'a is valid because buf points into a Vec owned by the StringBuffer that is
638+
// mutably borrowed for 'a (evidenced by last_chuck_start: &'a mut usize).
639+
unsafe { slice::from_raw_parts(self.buf.cast::<u8>(), self.written) }
637640
}
638641
}
639642

640643
impl Write for StringWriteBuffer<'_> {
641644
fn write_str(&mut self, s: &str) -> std::fmt::Result {
642645
let size = s.len();
643646

644-
if self.buf.len() - self.written >= size {
647+
if self.buf_cap - self.written >= size {
645648
let src = s.as_ptr();
646649

647-
// Safety: `written` cannot be over the buf size by the if condition
648-
let buf = unsafe { self.buf.get_unchecked_mut(self.written..) };
649-
let dst = buf.as_mut_ptr().cast::<u8>();
650-
651650
// Safety
652651
// * `src` is valid for reading of `size` bytes
653-
// * `dst` is valid for reading of `size` bytes
654-
// * Two pointers are well aligned. Both alignments are 1
655-
// * `src` and `dst` are not overlapping
652+
// * `dst` is valid for writing of `size` bytes
653+
// (`written + size <= buf_cap <= chunk len`)
654+
// * Both alignments are 1
655+
// * `src` and `dst` are not overlapping (src is a string literal / caller data)
656+
let dst = unsafe { self.buf.add(self.written).cast::<u8>() };
656657
unsafe { ptr::copy_nonoverlapping(src, dst, size) };
657658

658659
self.written += size;
@@ -694,6 +695,7 @@ mod tests {
694695
test_dropless_interner_many();
695696
test_dropless_interner_alignment_handling();
696697
test_dropless_interner_complex_display_type();
698+
test_dropless_interner_consecutive_formatted_strs();
697699
}
698700

699701
fn test_dropless_interner_int() {
@@ -914,6 +916,20 @@ mod tests {
914916
assert_eq!(&*interned, s.as_str());
915917
}
916918

919+
// UB if the interner gets a mutable reference to a whole chunk while a reference(i0) is alive.
920+
fn test_dropless_interner_consecutive_formatted_strs() {
921+
let dropless = DroplessInterner::new();
922+
923+
let value = 0;
924+
let i1 = dropless.intern_formatted_str(&value, 1).unwrap();
925+
926+
let value = 1;
927+
let i2 = dropless.intern_formatted_str(&value, 1).unwrap();
928+
929+
// Prevents i1 and i2 from being optimized away.
930+
let _c = format!("{i1}{i2}");
931+
}
932+
917933
#[test]
918934
fn test_string_buffer() {
919935
test_string_buffer_chunk();
@@ -946,7 +962,7 @@ mod tests {
946962
assert_eq!(buf.last_chunk_start, 1);
947963

948964
// Forces to make another chunk
949-
let mut write_buf = buf.speculative_alloc(GLOW_MAX_CHUNK_SIZE);
965+
let mut write_buf = buf.speculative_alloc(GROW_MAX_CHUNK_SIZE);
950966
write_buf.write_str("aa").unwrap();
951967
assert_eq!(write_buf.as_bytes(), b"aa");
952968
write_buf.commit();
@@ -986,7 +1002,7 @@ mod tests {
9861002
fn test_string_buffer_long_string() {
9871003
let mut buf = StringBuffer::default();
9881004

989-
let s = "a".repeat(GLOW_MAX_CHUNK_SIZE * 10);
1005+
let s = "a".repeat(GROW_MAX_CHUNK_SIZE * 10);
9901006
let mut write_buf = buf.speculative_alloc(s.len());
9911007
write_buf.write_str(&s).unwrap();
9921008
assert_eq!(write_buf.as_bytes(), s.as_bytes());

0 commit comments

Comments
 (0)