{"record":{"id":"2e571a5ba2ea5ab8","repo":"swc-project/swc","slug":"index-begin-and-or-end-in-s-do-not-lie-on","errorCode":null,"errorMessage":"index {begin} and/or {end} in {s:?} do not lie on character boundary","messagePattern":"index (.+?) and/or (.+?) in (.+?) do not lie on character boundary","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/hstr/src/wtf8/not_quite_std.rs","lineNumber":185,"sourceCode":"        None => false,\n        Some(&b) => !(128u8..192u8).contains(&b),\n    }\n}\n\n/// Copied from core::str::raw::slice_unchecked\n#[inline]\npub unsafe fn slice_unchecked(s: &Wtf8, begin: usize, end: usize) -> &Wtf8 {\n    mem::transmute(slice::from_raw_parts(\n        s.bytes.as_ptr().add(begin),\n        end - begin,\n    ))\n}\n\n/// Copied from core::str::raw::slice_error_fail\n#[inline(never)]\npub fn slice_error_fail(s: &Wtf8, begin: usize, end: usize) -> ! {\n    assert!(begin <= end);\n    panic!(\"index {begin} and/or {end} in {s:?} do not lie on character boundary\");\n}\n\n/// Copied from core::str::Utf16CodeUnits::next\npub fn next_utf16_code_unit(iter: &mut IllFormedUtf16CodeUnits) -> Option<u16> {\n    if iter.extra != 0 {\n        let tmp = iter.extra;\n        iter.extra = 0;\n        return Some(tmp);\n    }\n\n    let mut buf = [0u16; 2];\n    iter.code_points.next().map(|code_point| {\n        let n = encode_utf16_raw(code_point.to_u32(), &mut buf).unwrap_or(0);\n        if n == 2 {\n            iter.extra = buf[1];\n        }\n        buf[0]\n    })","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/swc-project/swc/blob/5176682b65416c6b5de6b47379ae1588ea3ecb3f/crates/hstr/src/wtf8/not_quite_std.rs#L167-L203","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":"// before\nlet part = wtf8.slice(start, start + len); // can panic mid-code-point\n\n// after\nif hstr::wtf8::is_code_point_boundary(wtf8, start)\n    && hstr::wtf8::is_code_point_boundary(wtf8, start + len)\n{\n    let part = wtf8.slice(start, start + len);\n}","handlingStrategy":"type-guard","validationCode":"use hstr::wtf8::Wtf8;\n\n// hstr keeps `is_code_point_boundary` private, so replicate its predicate:\n// an index is a boundary iff it is the end, or the byte there is not a UTF-8\n// continuation byte (0b10xxxxxx).\nfn is_code_point_boundary(s: &Wtf8, index: usize) -> bool {\n    if index == s.len() {\n        return true;\n    }\n    match s.as_bytes().get(index) {\n        None => false,\n        Some(&b) => !(128..192).contains(&b),\n    }\n}\n\n// call before Wtf8::slice / slice_from / slice_to\nfn valid_range(s: &Wtf8, begin: usize, end: usize) -> Option<(usize, usize)> {\n    (begin <= end\n        && end <= s.len()\n        && is_code_point_boundary(s, begin)\n        && is_code_point_boundary(s, end))\n        .then_some((begin, end))\n}","typeGuard":"fn safe_slice<'a>(s: &'a hstr::wtf8::Wtf8, begin: usize, end: usize) -> Option<&'a hstr::wtf8::Wtf8> {\n    let boundary = |i: usize| {\n        i == s.len()\n            || matches!(s.as_bytes().get(i), Some(&b) if !(128..192).contains(&b))\n    };\n    (begin <= end\n        && end <= s.len()\n        && boundary(begin)\n        && boundary(end))\n        .then(|| s.slice(begin, end))\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["rust","wtf8","unicode","string-slicing","panic","hstr"],"backgroundTag":"string-slice-not-char-boundary","analyzedSha":"5176682b65416c6b5de6b47379ae1588ea3ecb3f","analyzedAt":"2026-08-17T16:16:52.067Z","contentChangedAt":"2026-08-17T16:16:52.067Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}