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

  1. Check boundaries first with `hstr::wtf8::is_code_point_boundary` (the same predicate the safe `slice` uses) before slicing
  2. Derive offsets by iterating code points (e.g. `s.code_points().enumerate()`) instead of raw arithmetic
  3. 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

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


AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17). Data as JSON: /api/errors/2e571a5ba2ea5ab8. Report an issue: GitHub.