From ddbdd00496552927ef2debbe5c0fca1b7232fa80 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Mon, 2 Feb 2026 22:37:42 -0600 Subject: [PATCH] update text and object to be gc aware --- source/quickjs.c | 211 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 151 insertions(+), 60 deletions(-) diff --git a/source/quickjs.c b/source/quickjs.c index e8bde13e..e5b4441e 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -284,13 +284,13 @@ typedef enum JSErrorEnum { struct JSFunctionBytecode; #define JS_VALUE_GET_ARRAY(v) ((JSArray *)chase (v)) -#define JS_VALUE_GET_OBJ(v) ((JSRecord *)JS_VALUE_GET_PTR (v)) -#define JS_VALUE_GET_TEXT(v) ((JSText *)JS_VALUE_GET_PTR (v)) +#define JS_VALUE_GET_OBJ(v) ((JSRecord *)chase (v)) +#define JS_VALUE_GET_TEXT(v) ((JSText *)chase (v)) #define JS_VALUE_GET_BLOB(v) ((JSBlob *)JS_VALUE_GET_PTR (v)) #define JS_VALUE_GET_FUNCTION(v) ((JSFunction *)JS_VALUE_GET_PTR (v)) #define JS_VALUE_GET_FRAME(v) ((JSFrame *)JS_VALUE_GET_PTR (v)) #define JS_VALUE_GET_CODE(v) ((JSFunctionBytecode *)JS_VALUE_GET_PTR (v)) -#define JS_VALUE_GET_STRING(v) ((JSText *)JS_VALUE_GET_PTR (v)) +#define JS_VALUE_GET_STRING(v) ((JSText *)chase (v)) /* Compatibility: JS_TAG_STRING is an alias for text type checks */ #define JS_TAG_STRING JS_TAG_STRING_IMM @@ -588,10 +588,12 @@ typedef struct JSCode { /* ============================================================ Record key helpers (JSValue keys) ============================================================ */ +/* GC-SAFE: No allocations. */ static inline JS_BOOL rec_key_is_empty (JSValue key) { return JS_IsNull (key); } +/* GC-SAFE: No allocations. */ static inline JS_BOOL rec_key_is_tomb (JSValue key) { return JS_IsException (key); } @@ -1206,7 +1208,7 @@ typedef struct JSRegExp { #define obj_is_stone(rec) objhdr_s ((rec)->mist_hdr) #define obj_set_stone(rec) ((rec)->mist_hdr = objhdr_set_s ((rec)->mist_hdr, true)) -#define JS_VALUE_GET_RECORD(v) ((JSRecord *)JS_VALUE_GET_PTR (v)) +#define JS_VALUE_GET_RECORD(v) ((JSRecord *)chase (v)) /* Get prototype from object (works for both JSRecord and JSRecord since they * share layout) */ @@ -1317,7 +1319,8 @@ static JS_BOOL js_key_equal_str (JSValue a, const char *str) { return TRUE; } -/* Find slot for a key in record's own table. +/* GC-SAFE: No allocations. Caller must pass freshly-chased rec. + Find slot for a key in record's own table. Returns slot index (>0) if found, or -(insert_slot) if not found. */ static int rec_find_slot (JSRecord *rec, JSValue k) { uint64_t mask = objhdr_cap56 (rec->mist_hdr); @@ -1350,7 +1353,8 @@ static int rec_find_slot (JSRecord *rec, JSValue k) { return first_tomb ? -(int)first_tomb : -1; } -/* Get own property from record, returns JS_NULL if not found */ +/* GC-SAFE: No allocations. Caller must pass freshly-chased rec. + Get own property from record, returns JS_NULL if not found */ static JSValue rec_get_own (JSContext *ctx, JSRecord *rec, JSValue k) { (void)ctx; @@ -1361,7 +1365,8 @@ static JSValue rec_get_own (JSContext *ctx, JSRecord *rec, JSValue k) { return JS_NULL; } -/* Get property with proto chain lookup */ +/* GC-SAFE: No allocations. Walks prototype chain via direct pointers. + Caller must pass freshly-chased rec. */ static JSValue rec_get (JSContext *ctx, JSRecord *rec, JSValue k) { if (rec_key_is_empty (k) || rec_key_is_tomb (k)) return JS_NULL; @@ -1385,7 +1390,9 @@ static int rec_resize (JSContext *ctx, JSRecord *rec, uint64_t new_mask) { return -1; } -/* Set own property on record, returns 0 on success, -1 on error */ +/* NOT GC-SAFE: May call rec_resize which allocates. + Caller must pass freshly-chased rec and handle potential staleness. + Currently safe because rec_resize always fails. */ static int rec_set_own (JSContext *ctx, JSRecord *rec, JSValue k, JSValue val) { if (rec_key_is_empty (k) || rec_key_is_tomb (k)) { return -1; @@ -1683,7 +1690,7 @@ static void mark_function_children_decref (JSRuntime *rt, JSFunction *func); static inline objhdr_t objhdr_set_cap56 (objhdr_t h, uint64_t cap); /* JS_VALUE_GET_STRING is an alias for getting JSText from a string value */ -#define JS_VALUE_GET_STRING(v) ((JSText *)JS_VALUE_GET_PTR(v)) +/* Note: Uses chase() for GC safety - already defined at line 293 */ /* JS_ThrowMemoryError is an alias for JS_ThrowOutOfMemory */ #define JS_ThrowMemoryError(ctx) JS_ThrowOutOfMemory(ctx) @@ -1859,7 +1866,7 @@ static inline uint32_t js_string_value_get (JSValue v, int idx) { if (MIST_IsImmediateASCII (v)) { return MIST_GetImmediateASCIIChar (v, idx); } else { - JSText *s = JS_VALUE_GET_PTR (v); + JSText *s = JS_VALUE_GET_TEXT (v); return string_get (s, idx); } } @@ -1869,7 +1876,7 @@ static inline int js_string_value_len (JSValue v) { if (MIST_IsImmediateASCII (v)) { return MIST_GetImmediateASCIILen (v); } else { - return JSText_len (JS_VALUE_GET_PTR (v)); + return JSText_len (JS_VALUE_GET_TEXT (v)); } } @@ -1881,7 +1888,7 @@ static JSValue js_key_from_string (JSContext *ctx, JSValue val) { return val; /* Immediates can be used directly as keys */ } if (JS_IsText (val)) { - JSText *p = JS_VALUE_GET_PTR (val); + JSText *p = JS_VALUE_GET_TEXT (val); int64_t len = p->length; /* Extract UTF-32 characters and intern */ uint32_t *utf32_buf = alloca (len * sizeof (uint32_t)); @@ -4008,32 +4015,42 @@ JSValue JS_GetProperty (JSContext *ctx, JSValue obj, JSValue prop) { return rec_get (ctx, rec, prop); } -/* Get own property names from an object. +/* GC-SAFE: Collects keys to stack buffer before any allocation. Returns a JSValue array of text keys. */ JSValue JS_GetOwnPropertyNames (JSContext *ctx, JSValue obj) { - JSRecord *rec; uint32_t mask, count, i; - JSValue arr; if (!JS_IsRecord (obj)) { JS_ThrowTypeErrorNotAnObject (ctx); return JS_EXCEPTION; } - rec = (JSRecord *)JS_VALUE_GET_OBJ (obj); - arr = JS_NewArray (ctx); - if (JS_IsException (arr)) return JS_EXCEPTION; - + /* Reading slots is GC-safe - no allocation */ + JSRecord *rec = JS_VALUE_GET_OBJ (obj); mask = (uint32_t)objhdr_cap56 (rec->mist_hdr); - count = 0; - /* Add string keys to array */ + /* Count text keys first */ + count = 0; + for (i = 1; i <= mask; i++) { + if (JS_IsText (rec->slots[i].key)) count++; + } + + if (count == 0) return JS_NewArrayLen (ctx, 0); + + /* Collect keys into stack buffer (JSValues are just uint64_t) */ + JSValue *keys = alloca (count * sizeof (JSValue)); + uint32_t idx = 0; for (i = 1; i <= mask; i++) { JSValue k = rec->slots[i].key; - if (!rec_key_is_empty (k) && !rec_key_is_tomb (k) && JS_IsText (k)) { - JS_SetPropertyUint32 (ctx, arr, count, k); - count++; - } + if (JS_IsText (k)) keys[idx++] = k; + } + + /* Now allocate and fill - GC point, but keys are on stack */ + JSValue arr = JS_NewArrayLen (ctx, count); + if (JS_IsException (arr)) return JS_EXCEPTION; + + for (i = 0; i < count; i++) { + JS_SetPropertyUint32 (ctx, arr, i, keys[i]); } return arr; @@ -4066,19 +4083,17 @@ int JS_GetOwnProperty (JSContext *ctx, JSValue *desc, JSValue obj, JSValue prop) return JS_GetOwnPropertyInternal (ctx, desc, JS_VALUE_GET_OBJ (obj), prop); } -/* return -1 if exception otherwise TRUE or FALSE */ +/* GC-SAFE: Only calls rec_find_slot and reads prototype pointers. + return -1 if exception otherwise TRUE or FALSE */ int JS_HasProperty (JSContext *ctx, JSValue obj, JSValue prop) { JSRecord *p; int ret; if (unlikely (!JS_IsRecord (obj))) return FALSE; p = JS_VALUE_GET_OBJ (obj); for (;;) { - /* JS_GetOwnPropertyInternal can free the prototype */ - JS_MKPTR (p); ret = JS_GetOwnPropertyInternal (ctx, NULL, p, prop); - /* JS_FreeValue removed - copying GC handles memory */ if (ret != 0) return ret; - p = JS_OBJ_GET_PROTO (p); + p = p->proto; /* Direct pointer chase is safe - no allocation */ if (!p) break; } return FALSE; @@ -4336,6 +4351,9 @@ JSValue JS_GetPropertyKey (JSContext *ctx, JSValue this_obj, JSValue key) { return JS_GetProperty (ctx, this_obj, key); } +/* CAUTION: rec_set_own is NOT GC-safe if rec_resize is implemented. + Currently safe because rec_resize always fails. + When resize is implemented, rec pointer may become stale. */ int JS_SetPropertyKey (JSContext *ctx, JSValue this_obj, JSValue key, JSValue val) { if (JS_IsRecord (key)) { if (!JS_IsRecord (this_obj)) { @@ -4360,7 +4378,8 @@ int JS_SetPropertyKey (JSContext *ctx, JSValue this_obj, JSValue key, JSValue va return JS_SetPropertyInternal (ctx, this_obj, key, val); } -/* Property existence check with JSValue key (supports object keys) */ +/* GC-SAFE for record keys (no allocations). + String keys call js_key_from_string then JS_HasProperty which re-chases. */ int JS_HasPropertyKey (JSContext *ctx, JSValue obj, JSValue key) { if (JS_IsRecord (key)) { if (!JS_IsRecord (obj)) return FALSE; @@ -4383,7 +4402,7 @@ int JS_HasPropertyKey (JSContext *ctx, JSValue obj, JSValue key) { return JS_HasProperty (ctx, obj, key); } -/* Property deletion with JSValue key (supports object keys) */ +/* GC-SAFE: Only calls rec_find_slot and modifies slots directly */ int JS_DeletePropertyKey (JSContext *ctx, JSValue obj, JSValue key) { if (JS_IsRecord (key)) { if (!JS_IsRecord (obj)) return FALSE; @@ -4398,7 +4417,6 @@ int JS_DeletePropertyKey (JSContext *ctx, JSValue obj, JSValue key) { rec->slots[slot].key = JS_EXCEPTION; /* tombstone */ rec->slots[slot].val = JS_NULL; rec->len--; - /* tombs tracking removed - not needed with copying GC */ return TRUE; } @@ -4459,7 +4477,8 @@ static JSValue JS_ThrowSyntaxErrorVarRedeclaration (JSContext *ctx, return JS_ThrowSyntaxError (ctx, "redeclaration of '%s'", JS_KeyGetStr (ctx, buf, sizeof (buf), prop)); } -/* flags is 0, DEFINE_GLOBAL_LEX_VAR or DEFINE_GLOBAL_FUNC_VAR */ +/* GC-SAFE: Only calls rec_find_slot (no allocations). + flags is 0, DEFINE_GLOBAL_LEX_VAR or DEFINE_GLOBAL_FUNC_VAR */ static int JS_CheckDefineGlobalVar (JSContext *ctx, JSValue prop, int flags) { JSRecord *rec; int slot; @@ -4486,7 +4505,9 @@ static int JS_CheckDefineGlobalVar (JSContext *ctx, JSValue prop, int flags) { return 0; } -/* Simplified: def_flags is (0, DEFINE_GLOBAL_LEX_VAR) */ +/* CAUTION: rec_set_own is NOT GC-safe if rec_resize is implemented. + Currently safe because rec_resize always fails. + def_flags is (0, DEFINE_GLOBAL_LEX_VAR) */ static int JS_DefineGlobalVar (JSContext *ctx, JSValue prop, int def_flags) { JSRecord *rec; JSValue val; @@ -4515,6 +4536,7 @@ static int JS_DefineGlobalFunction (JSContext *ctx, JSValue prop, JSValue func, return 0; } +/* GC-SAFE: Only calls rec_find_slot and rec_get (no allocations) */ static JSValue JS_GetGlobalVar (JSContext *ctx, JSValue prop, BOOL throw_ref_error) { JSRecord *rec; int slot; @@ -4535,7 +4557,8 @@ static JSValue JS_GetGlobalVar (JSContext *ctx, JSValue prop, BOOL throw_ref_err return val; } -/* construct a reference to a global variable */ +/* GC-SAFE: Only calls rec_find_slot and JS_HasProperty (no allocations). + Construct a reference to a global variable */ static int JS_GetGlobalVarRef (JSContext *ctx, JSValue prop, JSValue *sp) { JSRecord *rec; int slot; @@ -4562,7 +4585,8 @@ static int JS_GetGlobalVarRef (JSContext *ctx, JSValue prop, JSValue *sp) { return 0; } -/* use for strict variable access: test if the variable exists */ +/* GC-SAFE: Only calls rec_find_slot and JS_HasProperty (no allocations). + Use for strict variable access: test if the variable exists */ static int JS_CheckGlobalVar (JSContext *ctx, JSValue prop) { JSRecord *rec; int slot, ret; @@ -21066,11 +21090,27 @@ static JSValue js_cell_text_lower (JSContext *ctx, JSValue this_val, int argc, J if (argc < 1) return JS_NULL; if (!JS_VALUE_IS_TEXT (argv[0])) return JS_NULL; - JSText *p = JS_VALUE_GET_STRING (argv[0]); - JSText *b = pretext_init (ctx, (int)JSText_len (p)); + /* Handle immediate ASCII - no GC concern */ + if (MIST_IsImmediateASCII (argv[0])) { + int len = MIST_GetImmediateASCIILen (argv[0]); + JSText *b = pretext_init (ctx, len); + if (!b) return JS_EXCEPTION; + for (int i = 0; i < len; i++) { + uint32_t c = MIST_GetImmediateASCIIChar (argv[0], i); + if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a'; + b = pretext_putc (ctx, b, c); + if (!b) return JS_EXCEPTION; + } + return pretext_end (ctx, b); + } + + /* Heap text: must re-chase after GC points */ + int len = (int)JSText_len (JS_VALUE_GET_STRING (argv[0])); + JSText *b = pretext_init (ctx, len); if (!b) return JS_EXCEPTION; - for (int i = 0; i < (int)JSText_len (p); i++) { + for (int i = 0; i < len; i++) { + JSText *p = JS_VALUE_GET_STRING (argv[0]); /* Re-chase each iteration */ uint32_t c = string_get (p, i); if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a'; b = pretext_putc (ctx, b, c); @@ -21085,11 +21125,27 @@ static JSValue js_cell_text_upper (JSContext *ctx, JSValue this_val, int argc, J if (argc < 1) return JS_NULL; if (!JS_VALUE_IS_TEXT (argv[0])) return JS_NULL; - JSText *p = JS_VALUE_GET_STRING (argv[0]); - JSText *b = pretext_init (ctx, (int)JSText_len (p)); + /* Handle immediate ASCII - no GC concern */ + if (MIST_IsImmediateASCII (argv[0])) { + int len = MIST_GetImmediateASCIILen (argv[0]); + JSText *b = pretext_init (ctx, len); + if (!b) return JS_EXCEPTION; + for (int i = 0; i < len; i++) { + uint32_t c = MIST_GetImmediateASCIIChar (argv[0], i); + if (c >= 'a' && c <= 'z') c = c - 'a' + 'A'; + b = pretext_putc (ctx, b, c); + if (!b) return JS_EXCEPTION; + } + return pretext_end (ctx, b); + } + + /* Heap text: must re-chase after GC points */ + int len = (int)JSText_len (JS_VALUE_GET_STRING (argv[0])); + JSText *b = pretext_init (ctx, len); if (!b) return JS_EXCEPTION; - for (int i = 0; i < (int)JSText_len (p); i++) { + for (int i = 0; i < len; i++) { + JSText *p = JS_VALUE_GET_STRING (argv[0]); /* Re-chase each iteration */ uint32_t c = string_get (p, i); if (c >= 'a' && c <= 'z') c = c - 'a' + 'A'; b = pretext_putc (ctx, b, c); @@ -21121,6 +21177,9 @@ static JSValue js_cell_text_trim (JSContext *ctx, JSValue this_val, int argc, JS } size_t reject_len = strlen (reject); + /* Re-chase p after JS_ToCString which can trigger GC */ + p = JS_VALUE_GET_STRING (str); + while (start < end) { uint32_t c = string_get (p, start); int found = 0; @@ -21154,6 +21213,8 @@ static JSValue js_cell_text_trim (JSContext *ctx, JSValue this_val, int argc, JS end--; } + /* Re-chase before js_sub_string */ + p = JS_VALUE_GET_STRING (str); JSValue result = js_sub_string (ctx, p, start, end); /* str removed - arg not owned */ return result; @@ -21270,8 +21331,7 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, if (!JS_VALUE_IS_TEXT (argv[0])) return JS_ThrowInternalError (ctx, "Replace must have text in arg0."); - JSText *sp = JS_VALUE_GET_STRING (argv[0]); - int len = (int)JSText_len (sp); + int len = (int)JSText_len (JS_VALUE_GET_STRING (argv[0])); int32_t limit = -1; if (argc > 3 && !JS_IsNull (argv[3])) { @@ -21287,8 +21347,7 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, return JS_ThrowInternalError ( ctx, "Second arg of replace must be pattern or text."); - JSText *tp = JS_VALUE_GET_STRING (argv[1]); - int t_len = (int)JSText_len (tp); + int t_len = (int)JSText_len (JS_VALUE_GET_STRING (argv[1])); if (t_len == 0) { int32_t count = 0; @@ -21311,6 +21370,8 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, } if (boundary < len) { + /* Re-chase sp after GC points */ + JSText *sp = JS_VALUE_GET_STRING (argv[0]); JSValue ch = js_sub_string (ctx, sp, boundary, boundary + 1); if (JS_IsException (ch)) goto fail_str_target; b = pretext_concat_value (ctx, b, ch); @@ -21327,6 +21388,10 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, while (pos <= len - t_len && (limit < 0 || count < limit)) { int found = -1; + /* Re-chase sp and tp before string_cmp */ + JSText *sp = JS_VALUE_GET_STRING (argv[0]); + JSText *tp = JS_VALUE_GET_STRING (argv[1]); + for (int i = pos; i <= len - t_len; i++) { if (!string_cmp (sp, tp, i, 0, t_len)) { found = i; @@ -21336,12 +21401,14 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, if (found < 0) break; if (found > pos) { + sp = JS_VALUE_GET_STRING (argv[0]); /* Re-chase before js_sub_string */ JSValue sub = js_sub_string (ctx, sp, pos, found); if (JS_IsException (sub)) goto fail_str_target; b = pretext_concat_value (ctx, b, sub); if (!b) goto fail_str_target; } + sp = JS_VALUE_GET_STRING (argv[0]); /* Re-chase before js_sub_string */ JSValue match = js_sub_string (ctx, sp, found, found + t_len); if (JS_IsException (match)) goto fail_str_target; @@ -21360,6 +21427,7 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, } if (pos < len) { + JSText *sp = JS_VALUE_GET_STRING (argv[0]); /* Re-chase before js_sub_string */ JSValue sub = js_sub_string (ctx, sp, pos, len); if (JS_IsException (sub)) goto fail_str_target; b = pretext_concat_value (ctx, b, sub); @@ -21385,6 +21453,7 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, if (JS_SetPropertyStr (ctx, rx, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) goto fail_rx; + JSText *sp = JS_VALUE_GET_STRING (argv[0]); /* Re-chase before js_sub_string */ JSValue sub_str = js_sub_string (ctx, sp, pos, len); if (JS_IsException (sub_str)) goto fail_rx; @@ -21432,6 +21501,7 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, if (match_len < 0) match_len = 0; if (found > pos) { + sp = JS_VALUE_GET_STRING (argv[0]); /* Re-chase before js_sub_string */ JSValue prefix = js_sub_string (ctx, sp, pos, found); if (JS_IsException (prefix)) goto fail_rx; b = pretext_concat_value (ctx, b, prefix); @@ -21459,6 +21529,7 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, } if (pos < len) { + JSText *sp = JS_VALUE_GET_STRING (argv[0]); /* Re-chase before js_sub_string */ JSValue tail = js_sub_string (ctx, sp, pos, len); if (JS_IsException (tail)) goto fail_rx; b = pretext_concat_value (ctx, b, tail); @@ -21498,8 +21569,7 @@ static JSValue js_cell_text_search (JSContext *ctx, JSValue this_val, int argc, JSValue str = JS_ToString (ctx, argv[0]); if (JS_IsException (str)) return str; - JSText *p = JS_VALUE_GET_STRING (str); - int len = (int)JSText_len (p); + int len = (int)JSText_len (JS_VALUE_GET_STRING (str)); int from = 0; if (argc > 2 && !JS_IsNull (argv[2])) { @@ -21522,11 +21592,13 @@ static JSValue js_cell_text_search (JSContext *ctx, JSValue this_val, int argc, return target; } - JSText *t = JS_VALUE_GET_STRING (target); - int t_len = (int)JSText_len (t); + int t_len = (int)JSText_len (JS_VALUE_GET_STRING (target)); int result = -1; if (len >= t_len) { + /* Re-chase both p and t right before the loop */ + JSText *p = JS_VALUE_GET_STRING (str); + JSText *t = JS_VALUE_GET_STRING (target); for (int i = from; i <= len - t_len; i++) { if (!string_cmp (p, t, i, 0, t_len)) { result = i; @@ -21553,6 +21625,8 @@ static JSValue js_cell_text_search (JSContext *ctx, JSValue this_val, int argc, if (JS_SetPropertyStr (ctx, rx, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) goto fail_rx_search; + /* Re-chase p before js_sub_string */ + JSText *p = JS_VALUE_GET_STRING (str); JSValue sub_str = js_sub_string (ctx, p, from, len); if (JS_IsException (sub_str)) goto fail_rx_search; @@ -21633,8 +21707,7 @@ static JSValue js_cell_text_extract (JSContext *ctx, JSValue this_val, int argc, JSValue str = JS_ToString (ctx, argv[0]); if (JS_IsException (str)) return JS_EXCEPTION; - JSText *p = JS_VALUE_GET_STRING (str); - int len = (int)JSText_len (p); + int len = (int)JSText_len (JS_VALUE_GET_STRING (str)); int from = 0; if (argc >= 3 && !JS_IsNull (argv[2])) { @@ -21668,6 +21741,8 @@ static JSValue js_cell_text_extract (JSContext *ctx, JSValue this_val, int argc, if (from == 0 && to == len) { sub_str = str; } else { + /* Re-chase p before js_sub_string */ + JSText *p = JS_VALUE_GET_STRING (str); sub_str = js_sub_string (ctx, p, from, to); if (JS_IsException (sub_str)) goto fail_rx; } @@ -21733,9 +21808,11 @@ static JSValue js_cell_text_extract (JSContext *ctx, JSValue this_val, int argc, JSValue needle_val = JS_ToString (ctx, argv[1]); if (JS_IsException (needle_val)) return JS_EXCEPTION; - JSText *needle = JS_VALUE_GET_STRING (needle_val); - int needle_len = (int)JSText_len (needle); + int needle_len = (int)JSText_len (JS_VALUE_GET_STRING (needle_val)); + /* Re-chase both p and needle right before js_str_find_range */ + JSText *p = JS_VALUE_GET_STRING (str); + JSText *needle = JS_VALUE_GET_STRING (needle_val); int pos = js_str_find_range (p, from, to, needle); if (pos < 0) return JS_NULL; @@ -21743,6 +21820,8 @@ static JSValue js_cell_text_extract (JSContext *ctx, JSValue this_val, int argc, JSValue arr = JS_NewArrayLen (ctx, 1); if (JS_IsException (arr)) return JS_EXCEPTION; + /* Re-chase p before js_sub_string */ + p = JS_VALUE_GET_STRING (str); JSValue match = js_sub_string (ctx, p, pos, pos + needle_len); if (JS_IsException (match)) { return JS_EXCEPTION; @@ -21772,14 +21851,16 @@ static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, int is_record = JS_IsRecord (collection); if (!is_array && !is_record) return JS_NULL; - JSText *sp = JS_VALUE_GET_STRING (text_val); - int len = (int)JSText_len (sp); + int len = (int)JSText_len (JS_VALUE_GET_STRING (text_val)); JSText *result = pretext_init (ctx, len); if (!result) return JS_EXCEPTION; int pos = 0; while (pos < len) { + /* Re-chase sp at the start of each loop iteration */ + JSText *sp = JS_VALUE_GET_STRING (text_val); + /* Find next '{' */ int brace_start = -1; for (int i = pos; i < len; i++) { @@ -21791,6 +21872,7 @@ static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, if (brace_start < 0) { /* No more braces, copy rest of string */ + sp = JS_VALUE_GET_STRING (text_val); /* Re-chase before js_sub_string */ JSValue tail = js_sub_string (ctx, sp, pos, len); if (JS_IsException (tail)) return JS_EXCEPTION; result = pretext_concat_value (ctx, result, tail); @@ -21799,12 +21881,16 @@ static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, /* Copy text before brace */ if (brace_start > pos) { + sp = JS_VALUE_GET_STRING (text_val); /* Re-chase before js_sub_string */ JSValue prefix = js_sub_string (ctx, sp, pos, brace_start); if (JS_IsException (prefix)) return JS_EXCEPTION; result = pretext_concat_value (ctx, result, prefix); if (!result) return JS_EXCEPTION; } + /* Re-chase sp before searching for closing brace */ + sp = JS_VALUE_GET_STRING (text_val); + /* Find closing '}' */ int brace_end = -1; for (int i = brace_start + 1; i < len; i++) { @@ -21816,6 +21902,7 @@ static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, if (brace_end < 0) { /* No closing brace, copy '{' and continue */ + sp = JS_VALUE_GET_STRING (text_val); /* Re-chase before js_sub_string */ JSValue ch = js_sub_string (ctx, sp, brace_start, brace_start + 1); if (JS_IsException (ch)) return JS_EXCEPTION; result = pretext_concat_value (ctx, result, ch); @@ -21825,6 +21912,7 @@ static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, } /* Extract content between braces */ + sp = JS_VALUE_GET_STRING (text_val); /* Re-chase before js_sub_string */ JSValue middle = js_sub_string (ctx, sp, brace_start + 1, brace_end); if (JS_IsException (middle)) return JS_EXCEPTION; @@ -21842,7 +21930,9 @@ static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, JSValue name_val, format_spec; if (colon_pos >= 0) { + middle_str = JS_VALUE_GET_STRING (middle); /* Re-chase before js_sub_string */ name_val = js_sub_string (ctx, middle_str, 0, colon_pos); + middle_str = JS_VALUE_GET_STRING (middle); /* Re-chase before js_sub_string */ format_spec = js_sub_string (ctx, middle_str, colon_pos + 1, middle_len); } else { name_val = middle; @@ -21903,9 +21993,9 @@ static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, /* If still no substitution but we have a value, convert to text */ if (!made_substitution && !JS_IsNull (coll_value)) { - JSValue text_val = JS_ToString (ctx, coll_value); - if (JS_IsText (text_val)) { - substitution = text_val; + JSValue conv_text_val = JS_ToString (ctx, coll_value); + if (JS_IsText (conv_text_val)) { + substitution = conv_text_val; made_substitution = 1; } } @@ -21915,6 +22005,7 @@ static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, if (!result) return JS_EXCEPTION; } else { /* No substitution, keep original {name} or {name:format} */ + sp = JS_VALUE_GET_STRING (text_val); /* Re-chase before js_sub_string */ JSValue orig = js_sub_string (ctx, sp, brace_start, brace_end + 1); if (JS_IsException (orig)) return JS_EXCEPTION; result = pretext_concat_value (ctx, result, orig);