swc-project/swc · error
index {begin} and/or {end} in {s:?} do not lie on character
Error message
index {begin} and/or {end} in {s:?} do not lie on character boundary What it means
Runtime panic from hstr's WTF-8 implementation (a vendored copy of Rust's str internals). `Wtf8::slice`, `slice_from`, and `slice_to` call `slice_error_fail` when a boundary index does not land on a code point boundary — the WTF-8 analogue of str's "byte index is not a char boundary" panic. WTF-8 also stores lone surrogates as 3-byte sequences, so any index splitting a multi-byte sequence (including a surrogate) panics.
Source
Thrown at crates/hstr/src/wtf8/not_quite_std.rs:185
None => false,
Some(&b) => !(128u8..192u8).contains(&b),
}
}
/// Copied from core::str::raw::slice_unchecked
#[inline]
pub unsafe fn slice_unchecked(s: &Wtf8, begin: usize, end: usize) -> &Wtf8 {
mem::transmute(slice::from_raw_parts(
s.bytes.as_ptr().add(begin),
end - begin,
))
}
/// Copied from core::str::raw::slice_error_fail
#[inline(never)]
pub fn slice_error_fail(s: &Wtf8, begin: usize, end: usize) -> ! {
assert!(begin <= end);
panic!("index {begin} and/or {end} in {s:?} do not lie on character boundary");
}
/// Copied from core::str::Utf16CodeUnits::next
pub fn next_utf16_code_unit(iter: &mut IllFormedUtf16CodeUnits) -> Option<u16> {
if iter.extra != 0 {
let tmp = iter.extra;
iter.extra = 0;
return Some(tmp);
}
let mut buf = [0u16; 2];
iter.code_points.next().map(|code_point| {
let n = encode_utf16_raw(code_point.to_u32(), &mut buf).unwrap_or(0);
if n == 2 {
iter.extra = buf[1];
}
buf[0]
})View on GitHub (pinned to 5176682b65)
Solutions
- Check boundaries first with `hstr::wtf8::is_code_point_boundary` (the same predicate the safe `slice` uses) before slicing
- Derive offsets by iterating code points (e.g. `s.code_points().enumerate()`) instead of raw arithmetic
- If you got the index from UTF-16 positions, convert it to a byte index first (WTF-16 -> WTF-8 roundtrip)
Example fix
// before
let part = wtf8.slice(start, start + len); // can panic mid-code-point
// after
if hstr::wtf8::is_code_point_boundary(wtf8, start)
&& hstr::wtf8::is_code_point_boundary(wtf8, start + len)
{
let part = wtf8.slice(start, start + len);
} Defensive patterns
Strategy: type-guard
Validate before calling
use hstr::wtf8::Wtf8;
// hstr keeps `is_code_point_boundary` private, so replicate its predicate:
// an index is a boundary iff it is the end, or the byte there is not a UTF-8
// continuation byte (0b10xxxxxx).
fn is_code_point_boundary(s: &Wtf8, index: usize) -> bool {
if index == s.len() {
return true;
}
match s.as_bytes().get(index) {
None => false,
Some(&b) => !(128..192).contains(&b),
}
}
// call before Wtf8::slice / slice_from / slice_to
fn valid_range(s: &Wtf8, begin: usize, end: usize) -> Option<(usize, usize)> {
(begin <= end
&& end <= s.len()
&& is_code_point_boundary(s, begin)
&& is_code_point_boundary(s, end))
.then_some((begin, end))
} Type guard
fn safe_slice<'a>(s: &'a hstr::wtf8::Wtf8, begin: usize, end: usize) -> Option<&'a hstr::wtf8::Wtf8> {
let boundary = |i: usize| {
i == s.len()
|| matches!(s.as_bytes().get(i), Some(&b) if !(128..192).contains(&b))
};
(begin <= end
&& end <= s.len()
&& boundary(begin)
&& boundary(end))
.then(|| s.slice(begin, end))
} Prevention
- Never slice Wtf8/str with UTF-16-derived or arithmetic offsets; recompute from code point iteration
- Validate boundaries with `is_code_point_boundary` before slicing
- Fuzz string-boundary paths when offsets come from user input or lexer state
When it happens
Trigger: Calling `Wtf8::slice(begin, end)` / `slice_from` / `slice_to` with byte offsets computed from UTF-16 code units, from half of an emoji/surrogate pair, or from arbitrary lexer arithmetic that can land mid-sequence.
Common situations: Handling JavaScript source strings that may contain lone surrogates and mixing UTF-16 index math with byte slicing; porting string-processing code that assumed ASCII offsets; feeding user-controlled offsets into slicing.
Related errors
- failed to emit error: {e}
- illegal conversion: Cannot convert {:?} to PatOutput
- unable to access unknown nodes
- illegal conversion: Cannot convert {:?} to Pattern
- illegal conversion: Cannot convert {:?} to LVal
AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17).
Data as JSON: /api/errors/2e571a5ba2ea5ab8.
Report an issue: GitHub.