From 13a3f725e26d2797dc04377b3f6df8c38a77bcc6 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Sat, 11 Oct 2025 13:57:25 +0200 Subject: [PATCH] Store start of slice string as bytes, not chars Not all functions access the string's backing memory according to the string's encoding. js_regexp_match in particular uses str8 for both ascii and wide strings. Because the offset into the parent string was stored in characters, js_regexp_match used the wrong offset (off by 50%) for wide slice strings. It's conceivable other functions do something similarly ill-advised, so store the start in bytes instead of characters from now on. Fixes: https://github.com/quickjs-ng/quickjs/issues/1178 --- quickjs.c | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/quickjs.c b/quickjs.c index 274746a20..793a13e9f 100644 --- a/quickjs.c +++ b/quickjs.c @@ -580,10 +580,10 @@ struct JSString { typedef struct JSStringSlice { JSString *parent; - uint32_t start; // in characters, not bytes + uint32_t start; // in bytes, not characters } JSStringSlice; -static inline uint8_t *str8(JSString *p) +static inline void *strv(JSString *p) { JSStringSlice *slice; @@ -592,25 +592,20 @@ static inline uint8_t *str8(JSString *p) return (void *)&p[1]; case JS_STRING_KIND_SLICE: slice = (void *)&p[1]; - return str8(slice->parent) + slice->start; + return (char *)&slice->parent[1] + slice->start; } abort(); return NULL; } -static inline uint16_t *str16(JSString *p) +static inline uint8_t *str8(JSString *p) { - JSStringSlice *slice; + return strv(p); +} - switch (p->kind) { - case JS_STRING_KIND_NORMAL: - return (void *)&p[1]; - case JS_STRING_KIND_SLICE: - slice = (void *)&p[1]; - return str16(slice->parent) + slice->start; - } - abort(); - return NULL; +static inline uint16_t *str16(JSString *p) +{ + return strv(p); } typedef struct JSClosureVar { @@ -3750,7 +3745,7 @@ static JSValue js_sub_string(JSContext *ctx, JSString *p, int start, int end) if (p->kind == JS_STRING_KIND_SLICE) { slice = (void *)&p[1]; p = slice->parent; - start += slice->start; + start += slice->start >> p->is_wide_char; // bytes -> chars } // allocate as 16 bit wide string to avoid wastage; // js_alloc_string allocates 1 byte extra for 8 bit strings; @@ -3762,7 +3757,7 @@ static JSValue js_sub_string(JSContext *ctx, JSString *p, int start, int end) q->len = len; slice = (void *)&q[1]; slice->parent = p; - slice->start = start; + slice->start = start << p->is_wide_char; // chars -> bytes p->header.ref_count++; return JS_MKPTR(JS_TAG_STRING, q); }