Skip to content

Commit fe779dc

Browse files
committed
fix(string): stabilize rope implementation and address edge cases
1 parent 951b4da commit fe779dc

File tree

2 files changed

+62
-40
lines changed

2 files changed

+62
-40
lines changed

core/string/src/iter.rs

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -392,49 +392,48 @@ impl<'a> Iterator for RopeCodePointsIter<'a> {
392392
self.current = None;
393393
}
394394

395-
if let Some(slice) = self.stack.pop() {
396-
if slice.header.vtable.kind == crate::JsStringKind::Rope {
397-
// SAFETY: The header is guaranteed to be a `RopeString` because the kind is `Rope`.
398-
let r = unsafe {
399-
&*std::ptr::from_ref(slice.header).cast::<crate::vtable::RopeString>()
400-
};
401-
let left_len = r.left.len();
395+
let slice = self.stack.pop()?;
402396

403-
if slice.start < left_len {
404-
let left_end = std::cmp::min(slice.end, left_len);
405-
if slice.end > left_len {
406-
self.stack.push(RopeSlice {
407-
// SAFETY: The child pointers in a `RopeString` are always valid `JsStringHeader` pointers.
408-
header: unsafe { &*r.right.ptr.as_ptr().cast() },
409-
start: 0,
410-
end: slice.end - left_len,
411-
});
412-
}
413-
self.stack.push(RopeSlice {
414-
// SAFETY: The child pointers in a `RopeString` are always valid `JsStringHeader` pointers.
415-
header: unsafe { &*r.left.ptr.as_ptr().cast() },
416-
start: slice.start,
417-
end: left_end,
418-
});
419-
} else {
397+
if slice.header.vtable.kind == crate::JsStringKind::Rope {
398+
// SAFETY: The header is guaranteed to be a `RopeString` because the kind is `Rope`.
399+
let r = unsafe {
400+
&*std::ptr::from_ref(slice.header).cast::<crate::vtable::RopeString>()
401+
};
402+
let left_len = r.left.len();
403+
404+
if slice.start < left_len {
405+
let left_end = std::cmp::min(slice.end, left_len);
406+
if slice.end > left_len {
420407
self.stack.push(RopeSlice {
421408
// SAFETY: The child pointers in a `RopeString` are always valid `JsStringHeader` pointers.
422409
header: unsafe { &*r.right.ptr.as_ptr().cast() },
423-
start: slice.start - left_len,
410+
start: 0,
424411
end: slice.end - left_len,
425412
});
426413
}
414+
self.stack.push(RopeSlice {
415+
// SAFETY: The child pointers in a `RopeString` are always valid `JsStringHeader` pointers.
416+
header: unsafe { &*r.left.ptr.as_ptr().cast() },
417+
start: slice.start,
418+
end: left_end,
419+
});
427420
} else {
428-
let s = (slice.header.vtable.as_str)(slice.header)
429-
.get(slice.start..slice.end)
430-
.expect("valid slice");
431-
let iter = s.code_points();
432-
// SAFETY: The lifetime of the code points is tied to the rope string, which is guaranteed to live for 'a.
433-
let iter = unsafe {
434-
std::mem::transmute::<CodePointsIter<'_>, CodePointsIter<'a>>(iter)
435-
};
436-
self.current = Some((None, iter));
421+
self.stack.push(RopeSlice {
422+
// SAFETY: The child pointers in a `RopeString` are always valid `JsStringHeader` pointers.
423+
header: unsafe { &*r.right.ptr.as_ptr().cast() },
424+
start: slice.start - left_len,
425+
end: slice.end - left_len,
426+
});
437427
}
428+
} else {
429+
let s = (slice.header.vtable.as_str)(slice.header)
430+
.get(slice.start..slice.end)
431+
.expect("valid slice");
432+
let iter = s.code_points();
433+
// SAFETY: The lifetime of the code points is tied to the rope string, which is guaranteed to live for 'a.
434+
let iter =
435+
unsafe { std::mem::transmute::<CodePointsIter<'_>, CodePointsIter<'a>>(iter) };
436+
self.current = Some((None, iter));
438437
}
439438
}
440439
}

core/string/src/vtable/slice.rs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,6 @@ impl SliceString {
5454
}
5555
}
5656

57-
// Unused slice_clone removed.
58-
5957
#[inline]
6058
fn slice_as_str(header: &JsStringHeader) -> JsStr<'_> {
6159
// SAFETY: The header is part of a SliceString and it's aligned.
@@ -65,10 +63,35 @@ fn slice_as_str(header: &JsStringHeader) -> JsStr<'_> {
6563

6664
#[inline]
6765
fn slice_dealloc(ptr: NonNull<JsStringHeader>) {
68-
// SAFETY: This is part of the correct vtable which is validated on construction.
69-
// The pointer is guaranteed to be a valid `NonNull<JsStringHeader>` pointing to a `SliceString`.
70-
unsafe {
71-
drop(Box::from_raw(ptr.cast::<SliceString>().as_ptr()));
66+
// Iteratively destroy slice chains to avoid recursive drop stack overflow.
67+
let mut current = ptr;
68+
69+
loop {
70+
// SAFETY: the vtable ensures this pointer refers to `SliceString`.
71+
unsafe {
72+
// Take ownership of the slice node.
73+
let slice_ptr = current.cast::<SliceString>();
74+
let mut slice_box = Box::from_raw(slice_ptr.as_ptr());
75+
76+
// Extract the parent string.
77+
let parent =
78+
std::mem::replace(&mut slice_box.owned, crate::StaticJsStrings::EMPTY_STRING);
79+
80+
// Drop the slice node itself.
81+
drop(slice_box);
82+
83+
// If the parent is another slice and we are the last reference,
84+
// continue iteratively instead of recursing.
85+
if parent.kind() == JsStringKind::Slice && parent.refcount() == Some(1) {
86+
current = parent.ptr;
87+
std::mem::forget(parent);
88+
continue;
89+
}
90+
91+
// Otherwise drop normally and finish.
92+
drop(parent);
93+
break;
94+
}
7295
}
7396
}
7497

0 commit comments

Comments
 (0)