Compare commits

...
4 Commits
Author SHA1 Message Date
d5c8ad3227 Release shared reference on zero truncate (#842)
Co-authored-by: JSap0914 <[email protected]>
2026-07-16 12:19:37 +02:00
bestgopherandGitHub 002df10b8c Simplify shared alignment assertions (#841)
Replace array-underflow checks with const assert expressions.
Add diagnostics that explain the pointer-tagging alignment invariant.

Signed-off-by: bestgopher <[email protected]>
2026-07-14 09:45:41 +02:00
Alice RyhlandGitHub 76c0fbb54e Release bytes v1.12.1 (#838) 2026-07-08 12:00:46 +02:00
Alice RyhlandGitHub 924c82bf00 Handle unwinding from Box::new (#837) 2026-07-07 15:33:07 +02:00
5 changed files with 103 additions and 34 deletions
+5
View File
@@ -1,3 +1,8 @@
# 1.12.1 (July 8th, 2026)
### Fixed
- Properly handle when `Box::new` panics (#837)
# 1.12.0 (June 18th, 2026)
### Added
+1 -1
View File
@@ -4,7 +4,7 @@ name = "bytes"
# When releasing to crates.io:
# - Update CHANGELOG.md.
# - Create "v1.x.y" git tag.
version = "1.12.0"
version = "1.12.1"
edition = "2021"
rust-version = "1.57"
license = "MIT"
+45 -25
View File
@@ -1,4 +1,4 @@
use core::mem::{self, ManuallyDrop};
use core::mem::{self, ManuallyDrop, MaybeUninit};
use core::ops::{Deref, RangeBounds};
use core::ptr::NonNull;
use core::{cmp, fmt, hash, ptr, slice};
@@ -567,7 +567,9 @@ impl Bytes {
/// ```
#[inline]
pub fn truncate(&mut self, len: usize) {
if len < self.len {
if len == 0 {
drop(mem::replace(self, Bytes::new_empty_with_ptr(self.ptr)));
} else if len < self.len {
// The Vec "promotable" vtables do not store the capacity,
// so we cannot truncate while using this repr. We *have* to
// promote using `split_off` so the capacity can be stored.
@@ -946,24 +948,26 @@ impl From<&'static str> for Bytes {
impl From<Vec<u8>> for Bytes {
fn from(vec: Vec<u8>) -> Bytes {
// Avoid an extra allocation if possible.
if vec.len() == vec.capacity() {
return Bytes::from(vec.into_boxed_slice());
}
let shared = Box::new(MaybeUninit::<Shared>::uninit());
let mut vec = ManuallyDrop::new(vec);
let ptr = vec.as_mut_ptr();
let len = vec.len();
let cap = vec.capacity();
// Avoid an extra allocation if possible.
if len == cap {
let vec = ManuallyDrop::into_inner(vec);
return Bytes::from(vec.into_boxed_slice());
}
let shared = Shared::init_to_raw(
shared,
Shared {
buf: ptr,
cap,
ref_cnt: AtomicUsize::new(1),
},
);
let shared = Box::new(Shared {
buf: ptr,
cap,
ref_cnt: AtomicUsize::new(1),
});
let shared = Box::into_raw(shared);
// The pointer should be aligned, so this assert should
// always succeed.
debug_assert!(
@@ -1327,6 +1331,15 @@ struct Shared {
ref_cnt: AtomicUsize,
}
impl Shared {
fn init_to_raw(b: Box<MaybeUninit<Self>>, v: Self) -> *mut Self {
let shared = Box::into_raw(b).cast::<Self>();
// SAFETY: The Box has the right layout.
unsafe { shared.write(v) };
shared
}
}
impl Drop for Shared {
fn drop(&mut self) {
unsafe { dealloc(self.buf, Layout::from_size_align(self.cap, 1).unwrap()) }
@@ -1337,7 +1350,12 @@ impl Drop for Shared {
// This is a necessary invariant since we depend on allocating `Shared` a
// shared object to implicitly carry the `KIND_ARC` flag in its pointer.
// This flag is set when the LSB is 0.
const _: [(); 0 - mem::align_of::<Shared>() % 2] = []; // Assert that the alignment of `Shared` is divisible by 2.
const _: () = {
assert!(
mem::align_of::<Shared>() % 2 == 0,
"Shared alignment must be divisible by 2 for pointer tagging"
);
};
static SHARED_VTABLE: Vtable = Vtable {
clone: shared_clone,
@@ -1472,16 +1490,18 @@ unsafe fn shallow_clone_vec(
// updated and since the buffer hasn't been promoted to an
// `Arc`, those three fields still are the components of the
// vector.
let shared = Box::new(Shared {
buf,
cap: offset.offset_from(buf) as usize + len,
// Initialize refcount to 2. One for this reference, and one
// for the new clone that will be returned from
// `shallow_clone`.
ref_cnt: AtomicUsize::new(2),
});
let shared = Box::into_raw(shared);
let shared = Box::new(MaybeUninit::<Shared>::uninit());
let shared = Shared::init_to_raw(
shared,
Shared {
buf,
cap: offset.offset_from(buf) as usize + len,
// Initialize refcount to 2. One for this reference, and one
// for the new clone that will be returned from
// `shallow_clone`.
ref_cnt: AtomicUsize::new(2),
},
);
// The pointer should be aligned, so this assert should
// always succeed.
+27 -8
View File
@@ -79,11 +79,25 @@ struct Shared {
ref_count: AtomicUsize,
}
impl Shared {
fn init_to_raw(b: Box<MaybeUninit<Self>>, v: Self) -> *mut Self {
let shared = Box::into_raw(b).cast::<Self>();
// SAFETY: The Box has the right layout.
unsafe { shared.write(v) };
shared
}
}
// Assert that the alignment of `Shared` is divisible by 2.
// This is a necessary invariant since we depend on allocating `Shared` a
// shared object to implicitly carry the `KIND_ARC` flag in its pointer.
// This flag is set when the LSB is 0.
const _: [(); 0 - mem::align_of::<Shared>() % 2] = []; // Assert that the alignment of `Shared` is divisible by 2.
const _: () = {
assert!(
mem::align_of::<Shared>() % 2 == 0,
"Shared alignment must be divisible by 2 for pointer tagging"
);
};
// Buffer storage strategy flags.
const KIND_ARC: usize = 0b0;
@@ -1112,13 +1126,18 @@ impl BytesMut {
// updated and since the buffer hasn't been promoted to an
// `Arc`, those three fields still are the components of the
// vector.
let shared = Box::new(Shared {
vec: rebuild_vec(self.ptr.as_ptr(), self.len, self.cap, off),
original_capacity_repr,
ref_count: AtomicUsize::new(ref_cnt),
});
let shared = Box::into_raw(shared);
//
// Explicitly allocate before invoking rebuild_vec() so that
// the vector is not dropped if Box::new() panics.
let shared = Box::new(MaybeUninit::<Shared>::uninit());
let shared = Shared::init_to_raw(
shared,
Shared {
vec: rebuild_vec(self.ptr.as_ptr(), self.len, self.cap, off),
original_capacity_repr,
ref_count: AtomicUsize::new(ref_cnt),
},
);
// The pointer should be aligned, so this assert should
// always succeed.
+25
View File
@@ -339,6 +339,31 @@ fn truncate() {
assert_eq!(hello, "hello");
}
#[test]
fn truncate_to_zero_releases_shared_reference() {
let mut bytes = BytesMut::from(&b"hello"[..]);
drop(bytes.split_off(bytes.len()));
let mut truncated = bytes.freeze();
let remaining = truncated.clone();
truncated.truncate(0);
assert!(truncated.is_empty());
let mut remaining = remaining.try_into_mut().unwrap();
remaining[0] = b'H';
assert_eq!(remaining, b"Hello"[..]);
let mut bytes = BytesMut::from(&b"hello"[..]);
drop(bytes.split_off(bytes.len()));
let mut nonempty = bytes.freeze();
let remaining = nonempty.clone();
nonempty.truncate(1);
assert_eq!(nonempty, b"h"[..]);
assert!(remaining.try_into_mut().is_err());
}
#[test]
fn freeze_clone_shared() {
let s = &b"abcdefgh"[..];