Skip to content

Commit aa0afb1

Browse files
committed
doc: Update safety documents
and some assertion fix
1 parent 1ec1003 commit aa0afb1

File tree

3 files changed

+76
-32
lines changed

3 files changed

+76
-32
lines changed

sailfish/src/runtime/buffer.rs

Lines changed: 67 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub struct Buffer {
1515
}
1616

1717
impl Buffer {
18+
/// Create an empty buffer
1819
#[inline]
1920
pub const fn new() -> Buffer {
2021
Self {
@@ -24,17 +25,15 @@ impl Buffer {
2425
}
2526
}
2627

27-
#[cfg_attr(feature = "perf-inline", inline)]
28+
#[inline]
2829
pub fn with_capacity(n: usize) -> Buffer {
29-
unsafe {
30-
if unlikely!(n == 0) {
31-
Self::new()
32-
} else {
33-
Self {
34-
data: safe_alloc(n),
35-
len: 0,
36-
capacity: n,
37-
}
30+
if unlikely!(n == 0) {
31+
Self::new()
32+
} else {
33+
Self {
34+
data: safe_alloc(n),
35+
len: 0,
36+
capacity: n,
3837
}
3938
}
4039
}
@@ -68,6 +67,11 @@ impl Buffer {
6867
self.len = new_len;
6968
}
7069

70+
/// Same as String::truncate
71+
///
72+
/// # Panics
73+
///
74+
/// This method panics if `new_len > self.len()`.
7175
pub(crate) fn truncate(&mut self, new_len: usize) {
7276
assert!(new_len <= self.len);
7377
self.len = new_len;
@@ -89,12 +93,19 @@ impl Buffer {
8993
self.len == 0
9094
}
9195

96+
/// Same as String::reserve
97+
///
98+
/// # Panics
99+
///
100+
/// This method panics if `size` overflows `isize::MAX`.
92101
#[inline]
93102
pub fn reserve(&mut self, size: usize) {
94103
assert!(size <= isize::MAX as usize);
95104
unsafe { self.reserve_small(size) };
96105
}
97106

107+
/// Same as String::reserve except that undefined behaviour can result if `size`
108+
/// overflows `isize::MAX`.
98109
#[inline]
99110
pub(crate) unsafe fn reserve_small(&mut self, size: usize) {
100111
debug_assert!(size <= isize::MAX as usize);
@@ -110,13 +121,14 @@ impl Buffer {
110121
self.len = 0;
111122
}
112123

113-
/// Converts a `Buffer` into a `String`.
114-
///
115-
/// This consumes the `Buffer`, so we do not need to copy its contents.
124+
/// Converts a `Buffer` into a `String` without copy/realloc operation.
116125
#[inline]
117126
pub fn into_string(self) -> String {
118127
debug_assert!(self.len <= self.capacity);
119128
let buf = ManuallyDrop::new(self);
129+
130+
// SAFETY: This operations satisfy all requirements specified in
131+
// https://doc.rust-lang.org/std/string/struct.String.html#safety
120132
unsafe { String::from_raw_parts(buf.data, buf.len, buf.capacity) }
121133
}
122134

@@ -145,31 +157,44 @@ impl Buffer {
145157
#[cold]
146158
fn reserve_internal(&mut self, size: usize) {
147159
debug_assert!(size <= isize::MAX as usize);
148-
unsafe {
149-
let new_capacity = std::cmp::max(self.capacity * 2, self.capacity + size);
150-
debug_assert!(new_capacity > self.capacity);
151-
self.data = safe_realloc(self.data, self.capacity, new_capacity);
152-
self.capacity = new_capacity;
153-
}
160+
161+
let new_capacity = std::cmp::max(self.capacity * 2, self.capacity + size);
162+
debug_assert!(new_capacity > self.capacity);
163+
self.data = unsafe { safe_realloc(self.data, self.capacity, new_capacity) };
164+
self.capacity = new_capacity;
165+
154166
debug_assert!(!self.data.is_null());
155167
debug_assert!(self.len <= self.capacity);
156168
}
157169
}
158170

159-
unsafe fn safe_alloc(capacity: usize) -> *mut u8 {
171+
#[inline(never)]
172+
fn safe_alloc(capacity: usize) -> *mut u8 {
173+
assert!(capacity > 0);
160174
assert!(capacity <= isize::MAX as usize, "capacity is too large");
161-
let layout = Layout::from_size_align_unchecked(capacity, 1);
162-
let data = alloc(layout);
163-
if data.is_null() {
164-
handle_alloc_error(layout);
165-
}
166175

167-
data
176+
// SAFETY: capacity is non-zero, and always multiple of alignment (1).
177+
unsafe {
178+
let layout = Layout::from_size_align_unchecked(capacity, 1);
179+
let data = alloc(layout);
180+
if data.is_null() {
181+
handle_alloc_error(layout);
182+
}
183+
184+
data
185+
}
168186
}
169187

188+
/// # Safety
189+
///
190+
/// - if `capacity > 0`, `capacity` is the same value that was used to allocate the block
191+
/// of memory pointed by `ptr`.
170192
#[cold]
193+
#[inline(never)]
171194
unsafe fn safe_realloc(ptr: *mut u8, capacity: usize, new_capacity: usize) -> *mut u8 {
195+
assert!(new_capacity > 0);
172196
assert!(new_capacity <= isize::MAX as usize, "capacity is too large");
197+
173198
let data = if unlikely!(capacity == 0) {
174199
let new_layout = Layout::from_size_align_unchecked(new_capacity, 1);
175200
alloc(new_layout)
@@ -188,7 +213,7 @@ unsafe fn safe_realloc(ptr: *mut u8, capacity: usize, new_capacity: usize) -> *m
188213
impl Clone for Buffer {
189214
fn clone(&self) -> Self {
190215
unsafe {
191-
if self.capacity == 0 {
216+
if self.is_empty() {
192217
Self::new()
193218
} else {
194219
let buf = Self {
@@ -213,6 +238,8 @@ impl fmt::Debug for Buffer {
213238
impl Drop for Buffer {
214239
fn drop(&mut self) {
215240
if self.capacity != 0 {
241+
// SAFETY: when `self.capacity > 0`, `self.capacity` is the same value
242+
// used for allocate the block of memory pointed by `self.data`.
216243
unsafe {
217244
let layout = Layout::from_size_align_unchecked(self.capacity, 1);
218245
dealloc(self.data, layout);
@@ -236,7 +263,7 @@ impl From<String> for Buffer {
236263
#[inline]
237264
fn from(other: String) -> Buffer {
238265
let bs = other.into_boxed_str();
239-
let data = unsafe { &mut *Box::into_raw(bs) };
266+
let data = Box::leak(bs);
240267
Buffer {
241268
data: data.as_mut_ptr(),
242269
len: data.len(),
@@ -249,10 +276,16 @@ impl From<&str> for Buffer {
249276
#[inline]
250277
fn from(other: &str) -> Buffer {
251278
let mut buf = Buffer::with_capacity(other.len());
252-
unsafe {
253-
ptr::copy_nonoverlapping(other.as_ptr(), buf.as_mut_ptr(), other.len());
254-
buf.advance(other.len());
279+
280+
if !other.is_empty() {
281+
// SAFETY: `Buffer.capacity()` should be same as `other.len()`, so if `other`
282+
// is not empty, `buf.as_mut_ptr()` is supporsed to point to valid memory.
283+
unsafe {
284+
ptr::copy_nonoverlapping(other.as_ptr(), buf.as_mut_ptr(), other.len());
285+
buf.advance(other.len());
286+
}
255287
}
288+
256289
buf
257290
}
258291
}
@@ -363,5 +396,8 @@ mod tests {
363396

364397
assert_eq!(s1.as_str(), "foobar");
365398
assert_eq!(s2.as_str(), "foobaz");
399+
400+
s2.clear();
401+
let _ = s2.clone();
366402
}
367403
}

sailfish/src/runtime/filter.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,16 @@ fn trim_impl(b: &mut Buffer, old_len: usize) {
131131
}
132132

133133
debug_assert!(b.capacity() >= old_len + trimmed_len);
134+
135+
// SAFETY: `new_contents.len() = b.len() - old_len` and
136+
// `trimmed_len < new_contents.len()`, so `old_len + trimmed_len < b.len()`.
134137
unsafe {
135138
b._set_len(old_len + trimmed_len);
136139
}
137140
}
138141
}
139142

140-
/// convert the rendered contents to lowercase
143+
/// Remove leading and trailing writespaces from rendered results
141144
#[inline]
142145
pub fn trim<T: Render>(expr: &T) -> Trim<T> {
143146
Trim(expr)

sailfish/src/runtime/render.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,14 @@ macro_rules! render_int {
192192
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
193193
use itoap::Integer;
194194

195+
// SAFETY: `MAX_LEN < 40` and then does not overflows `isize::MAX`.
196+
// Also `b.len()` should be always less than or equal to `isize::MAX`.
195197
unsafe {
196198
b.reserve_small(Self::MAX_LEN);
197199
let ptr = b.as_mut_ptr().add(b.len());
200+
201+
// SAFETY: `MAX_LEN` is always greater than zero, so
202+
// `b.as_mut_ptr()` always point to valid block of memory
198203
let l = itoap::write_to_ptr(ptr, *self);
199204
b.advance(l);
200205
}

0 commit comments

Comments
 (0)