From ae1f09a28f78d38b23f401aa5aa4a5de493fce14 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Sat, 7 Feb 2026 14:53:14 -0600 Subject: [PATCH] fix all memory errors --- source/quickjs.c | 538 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 365 insertions(+), 173 deletions(-) diff --git a/source/quickjs.c b/source/quickjs.c index f65e2419..764a6cff 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -3589,7 +3589,13 @@ static JSText *pretext_concat (JSContext *ctx, JSText *s, const JSText *p, uint3 int cur_len = (int)s->length; int cap = (int)objhdr_cap56 (s->hdr); if (cur_len + len > cap) { + /* Root p across pretext_realloc which can trigger GC */ + JSGCRef p_ref; + JS_PushGCRef (ctx, &p_ref); + p_ref.val = JS_MKPTR ((void *)p); s = pretext_realloc (ctx, s, cur_len + len); + p = (const JSText *)chase (p_ref.val); + JS_PopGCRef (ctx, &p_ref); if (!s) return NULL; } for (int i = 0; i < len; i++) { @@ -19903,22 +19909,42 @@ static JSValue js_regexp_constructor_internal (JSContext *ctx, JSValue pattern, return JS_EXCEPTION; } + /* Root pattern and bc across allocating calls */ + JSGCRef pat_ref, bc_ref; + JS_PushGCRef (ctx, &pat_ref); + pat_ref.val = pattern; + JS_PushGCRef (ctx, &bc_ref); + bc_ref.val = bc; + obj = JS_NewObjectProtoClass (ctx, ctx->class_proto[JS_CLASS_REGEXP], JS_CLASS_REGEXP); - if (JS_IsException (obj)) goto fail; - p = JS_VALUE_GET_OBJ (obj); - /* Allocate JSRegExp and store in opaque slot */ - re = js_malloc (ctx, sizeof(JSRegExp)); - if (!re) goto fail; + if (JS_IsException (obj)) { + JS_PopGCRef (ctx, &bc_ref); + JS_PopGCRef (ctx, &pat_ref); + goto fail; + } + + /* Root obj across allocating calls */ + JSGCRef obj_ref; + JS_PushGCRef (ctx, &obj_ref); + obj_ref.val = obj; + +#define REGEXP_CLEANUP() do { JS_PopGCRef (ctx, &obj_ref); JS_PopGCRef (ctx, &bc_ref); JS_PopGCRef (ctx, &pat_ref); } while(0) + + /* Allocate JSRegExp off-heap (not on GC heap) so opaque pointer stays valid after GC */ + re = js_malloc_rt (sizeof(JSRegExp)); + if (!re) { REGEXP_CLEANUP (); JS_ThrowOutOfMemory (ctx); goto fail; } + p = JS_VALUE_GET_OBJ (obj_ref.val); REC_SET_OPAQUE(p, re); re->pattern = NULL; re->bytecode = NULL; /* Extract pattern as UTF-8 C string */ - pat_cstr = JS_ToCStringLen (ctx, &pat_len, pattern); - if (!pat_cstr) goto fail; + pat_cstr = JS_ToCStringLen (ctx, &pat_len, pat_ref.val); + if (!pat_cstr) { REGEXP_CLEANUP (); goto fail; } re->pattern = js_malloc_rt (pat_len + 1); if (!re->pattern) { JS_FreeCString (ctx, pat_cstr); + REGEXP_CLEANUP (); goto fail; } memcpy (re->pattern, pat_cstr, pat_len + 1); @@ -19927,17 +19953,18 @@ static JSValue js_regexp_constructor_internal (JSContext *ctx, JSValue pattern, /* Extract bytecode as raw bytes via string_get (not JS_ToCStringLen which UTF-8 encodes and would mangle bytes >= 128) */ + bc = bc_ref.val; if (MIST_IsImmediateASCII (bc)) { bc_len = MIST_GetImmediateASCIILen (bc); re->bytecode = js_malloc_rt (bc_len); - if (!re->bytecode) goto fail; + if (!re->bytecode) { REGEXP_CLEANUP (); goto fail; } for (i = 0; i < bc_len; i++) re->bytecode[i] = (uint8_t)MIST_GetImmediateASCIIChar (bc, i); } else { - JSText *bc_str = (JSText *)JS_VALUE_GET_PTR (bc); + JSText *bc_str = (JSText *)chase (bc_ref.val); bc_len = (int)JSText_len (bc_str); re->bytecode = js_malloc_rt (bc_len); - if (!re->bytecode) goto fail; + if (!re->bytecode) { REGEXP_CLEANUP (); goto fail; } for (i = 0; i < bc_len; i++) re->bytecode[i] = (uint8_t)string_get (bc_str, i); } @@ -19945,10 +19972,14 @@ static JSValue js_regexp_constructor_internal (JSContext *ctx, JSValue pattern, { JSValue key = JS_KEY_STR (ctx, "lastIndex"); + obj = obj_ref.val; /* re-read after JS_KEY_STR allocation */ JS_SetPropertyInternal (ctx, obj, key, JS_NewInt32 (ctx, 0)); } + obj = obj_ref.val; + REGEXP_CLEANUP (); return obj; } +#undef REGEXP_CLEANUP static JSRegExp *js_get_regexp (JSContext *ctx, JSValue obj, BOOL throw_error) { if (JS_VALUE_GET_TAG (obj) == JS_TAG_PTR) { @@ -20143,13 +20174,13 @@ void *lre_realloc (void *opaque, void *ptr, size_t size) { } /* Convert UTF-32 JSText to UTF-16 buffer for regex engine. - Returns allocated uint16_t buffer that must be freed by caller. + Returns allocated uint16_t buffer (via js_malloc_rt) that must be freed with js_free_rt. Sets *out_len to number of uint16 code units. */ static uint16_t *js_string_to_utf16 (JSContext *ctx, JSText *str, int *out_len) { int len = (int)JSText_len (str); /* Worst case: each UTF-32 char becomes 2 UTF-16 surrogates */ - uint16_t *buf = js_malloc (ctx, len * 2 * sizeof (uint16_t)); - if (!buf) return NULL; + uint16_t *buf = js_malloc_rt (len * 2 * sizeof (uint16_t)); + if (!buf) { JS_ThrowOutOfMemory (ctx); return NULL; } int j = 0; for (int i = 0; i < len; i++) { @@ -20170,7 +20201,7 @@ static uint16_t *js_string_to_utf16 (JSContext *ctx, JSText *str, int *out_len) static JSValue js_regexp_exec (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { JSRegExp *re = js_get_regexp (ctx, this_val, TRUE); JSText *str; - JSGCRef str_ref; + JSGCRef str_ref, this_ref; JSValue ret, res, val, groups, captures_arr, match0; uint8_t *re_bytecode; uint8_t **capture, *str_buf; @@ -20182,10 +20213,15 @@ static JSValue js_regexp_exec (JSContext *ctx, JSValue this_val, int argc, JSVal if (!re) return JS_EXCEPTION; + /* Root this_val across allocating calls */ + JS_PushGCRef (ctx, &this_ref); + this_ref.val = this_val; + JS_PushGCRef (ctx, &str_ref); str_ref.val = JS_ToString (ctx, argv[0]); if (JS_IsException (str_ref.val)) { JS_PopGCRef (ctx, &str_ref); + JS_PopGCRef (ctx, &this_ref); return JS_EXCEPTION; } /* Ensure str_val is a heap string for JS_VALUE_GET_STRING */ @@ -20194,6 +20230,7 @@ static JSValue js_regexp_exec (JSContext *ctx, JSValue this_val, int argc, JSVal JSText *hs = js_alloc_string (ctx, imm_len > 0 ? imm_len : 1); if (!hs) { JS_PopGCRef (ctx, &str_ref); + JS_PopGCRef (ctx, &this_ref); return JS_EXCEPTION; } for (int ci = 0; ci < imm_len; ci++) @@ -20211,10 +20248,12 @@ static JSValue js_regexp_exec (JSContext *ctx, JSValue this_val, int argc, JSVal match0 = JS_NULL; capture = NULL; - val = JS_GetPropertyStr (ctx, this_val, "lastIndex"); + val = JS_GetPropertyStr (ctx, this_ref.val, "lastIndex"); if (JS_IsException (val) || JS_ToLength (ctx, &last_index, val)) goto fail; + /* Re-chase re after allocating calls (JS_ToString, js_alloc_string, JS_GetPropertyStr) */ + re = js_get_regexp (ctx, this_ref.val, TRUE); re_bytecode = re->bytecode; re_flags = lre_get_flags (re_bytecode); if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) last_index = 0; @@ -20222,20 +20261,20 @@ static JSValue js_regexp_exec (JSContext *ctx, JSValue this_val, int argc, JSVal capture_count = lre_get_capture_count (re_bytecode); if (capture_count > 0) { - capture = js_malloc (ctx, sizeof (capture[0]) * capture_count * 2); - if (!capture) goto fail; + capture = js_malloc_rt (sizeof (capture[0]) * capture_count * 2); + if (!capture) { JS_ThrowOutOfMemory (ctx); goto fail; } } /* Refresh str after potential GC from js_malloc */ str = JS_VALUE_GET_STRING (str_ref.val); - /* Convert UTF-32 string to UTF-16 for regex engine */ + /* Convert UTF-32 string to UTF-16 for regex engine (uses js_malloc_rt, no GC) */ utf16_buf = js_string_to_utf16 (ctx, str, &utf16_len); if (!utf16_buf) goto fail; shift = 1; /* UTF-16 mode */ str_buf = (uint8_t *)utf16_buf; - /* Refresh str again after potential GC from js_string_to_utf16 */ + /* Refresh str after potential GC from js_string_to_utf16 */ str = JS_VALUE_GET_STRING (str_ref.val); if (last_index > (int)JSText_len (str)) { rc = 2; @@ -20246,7 +20285,7 @@ static JSValue js_regexp_exec (JSContext *ctx, JSValue this_val, int argc, JSVal if (rc != 1) { if (rc >= 0) { if (rc == 2 || (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY))) { - if (JS_SetPropertyStr (ctx, this_val, "lastIndex", JS_NewInt32 (ctx, 0)) + if (JS_SetPropertyStr (ctx, this_ref.val, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) goto fail; } @@ -20261,7 +20300,7 @@ static JSValue js_regexp_exec (JSContext *ctx, JSValue this_val, int argc, JSVal } if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) { - if (JS_SetPropertyStr (ctx, this_val, "lastIndex", JS_NewInt32 (ctx, (capture[1] - str_buf) >> shift)) + if (JS_SetPropertyStr (ctx, this_ref.val, "lastIndex", JS_NewInt32 (ctx, (capture[1] - str_buf) >> shift)) < 0) goto fail; } @@ -20269,16 +20308,36 @@ static JSValue js_regexp_exec (JSContext *ctx, JSValue this_val, int argc, JSVal res = JS_NewObjectProto (ctx, JS_NULL); if (JS_IsException (res)) goto fail; + /* Root res, captures_arr, groups, match0 across allocating calls */ + JSGCRef res_ref, cap_ref, grp_ref, m0_ref; + JS_PushGCRef (ctx, &res_ref); + res_ref.val = res; + JS_PushGCRef (ctx, &cap_ref); + cap_ref.val = JS_NULL; + JS_PushGCRef (ctx, &grp_ref); + grp_ref.val = JS_NULL; + JS_PushGCRef (ctx, &m0_ref); + m0_ref.val = JS_NULL; + +#define REGEXP_RESULT_CLEANUP() do { \ + JS_PopGCRef (ctx, &m0_ref); \ + JS_PopGCRef (ctx, &grp_ref); \ + JS_PopGCRef (ctx, &cap_ref); \ + JS_PopGCRef (ctx, &res_ref); \ +} while(0) + { int cap_groups = (capture_count > 1) ? (capture_count - 1) : 0; captures_arr = JS_NewArrayLen (ctx, cap_groups); - if (JS_IsException (captures_arr)) goto fail; + if (JS_IsException (captures_arr)) { REGEXP_RESULT_CLEANUP (); goto fail; } + cap_ref.val = captures_arr; } group_name_ptr = lre_get_groupnames (re_bytecode); if (group_name_ptr) { groups = JS_NewObjectProto (ctx, JS_NULL); - if (JS_IsException (groups)) goto fail; + if (JS_IsException (groups)) { REGEXP_RESULT_CLEANUP (); goto fail; } + grp_ref.val = groups; } { @@ -20306,62 +20365,80 @@ static JSValue js_regexp_exec (JSContext *ctx, JSValue this_val, int argc, JSVal if (start != -1) { str = JS_VALUE_GET_STRING (str_ref.val); s = js_sub_string (ctx, str, start, end); - if (JS_IsException (s)) goto fail; + if (JS_IsException (s)) { REGEXP_RESULT_CLEANUP (); goto fail; } } if (i == 0) { match_start = start; match_end = end; match0 = s; + m0_ref.val = match0; continue; } if (name) { + groups = grp_ref.val; if (JS_SetPropertyStr (ctx, groups, name, s) < 0) { + REGEXP_RESULT_CLEANUP (); goto fail; } } - if (JS_SetPropertyUint32 (ctx, captures_arr, (uint32_t)(i - 1), s) < 0) + captures_arr = cap_ref.val; + if (JS_SetPropertyUint32 (ctx, captures_arr, (uint32_t)(i - 1), s) < 0) { + REGEXP_RESULT_CLEANUP (); goto fail; + } } if (match_start < 0) match_start = 0; if (match_end < match_start) match_end = match_start; + res = res_ref.val; if (JS_SetPropertyStr (ctx, res, "index", JS_NewInt32 (ctx, match_start)) - < 0) + < 0) { + REGEXP_RESULT_CLEANUP (); goto fail; - if (JS_SetPropertyStr (ctx, res, "end", JS_NewInt32 (ctx, match_end)) < 0) + } + res = res_ref.val; + if (JS_SetPropertyStr (ctx, res, "end", JS_NewInt32 (ctx, match_end)) < 0) { + REGEXP_RESULT_CLEANUP (); goto fail; + } - if (JS_SetPropertyStr (ctx, res, "match", match0) < 0) goto fail; - match0 = JS_NULL; + res = res_ref.val; + match0 = m0_ref.val; + if (JS_SetPropertyStr (ctx, res, "match", match0) < 0) { REGEXP_RESULT_CLEANUP (); goto fail; } - if (JS_SetPropertyStr (ctx, res, "captures", captures_arr) < 0) goto fail; - captures_arr = JS_NULL; + res = res_ref.val; + captures_arr = cap_ref.val; + if (JS_SetPropertyStr (ctx, res, "captures", captures_arr) < 0) { REGEXP_RESULT_CLEANUP (); goto fail; } + groups = grp_ref.val; if (!JS_IsNull (groups)) { - if (JS_SetPropertyStr (ctx, res, "groups", groups) < 0) goto fail; - groups = JS_NULL; + res = res_ref.val; + if (JS_SetPropertyStr (ctx, res, "groups", groups) < 0) { REGEXP_RESULT_CLEANUP (); goto fail; } } else { + res = res_ref.val; JS_SetPropertyStr (ctx, res, "groups", JS_NULL); } } - ret = res; - res = JS_NULL; + ret = res_ref.val; + REGEXP_RESULT_CLEANUP (); done: JS_PopGCRef (ctx, &str_ref); - js_free (ctx, capture); - js_free (ctx, utf16_buf); + JS_PopGCRef (ctx, &this_ref); + js_free_rt (capture); + js_free_rt (utf16_buf); return ret; fail: JS_PopGCRef (ctx, &str_ref); - js_free (ctx, capture); - js_free (ctx, utf16_buf); + JS_PopGCRef (ctx, &this_ref); + js_free_rt (capture); + js_free_rt (utf16_buf); return JS_EXCEPTION; } @@ -22389,36 +22466,42 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, return JS_EXCEPTION; } - /* Regex target */ - JSValue rx = argv[1]; - JSValue orig_last_index = JS_GetPropertyStr (ctx, rx, "lastIndex"); - if (JS_IsException (orig_last_index)) { B_CLEANUP (); goto fail_rx; } + /* Regex target - root rx across allocating calls */ + JSGCRef rx_ref; + JS_PushGCRef (ctx, &rx_ref); + rx_ref.val = argv[1]; + +#define RX_CLEANUP() do { JS_PopGCRef (ctx, &rx_ref); B_CLEANUP (); } while(0) +#define RX_VAL (rx_ref.val) + + JSValue orig_last_index = JS_GetPropertyStr (ctx, RX_VAL, "lastIndex"); + if (JS_IsException (orig_last_index)) { RX_CLEANUP (); goto fail_rx; } int have_orig_last_index = 1; int pos = 0; int32_t count = 0; while (pos <= len && (limit < 0 || count < limit)) { - if (JS_SetPropertyStr (ctx, rx, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) { - B_CLEANUP (); goto fail_rx; + if (JS_SetPropertyStr (ctx, RX_VAL, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) { + RX_CLEANUP (); goto fail_rx; } JSValue sub_str = js_sub_string_val (ctx, argv[0], pos, len); - if (JS_IsException (sub_str)) { B_CLEANUP (); goto fail_rx; } + if (JS_IsException (sub_str)) { RX_CLEANUP (); goto fail_rx; } JSValue exec_res - = JS_Invoke (ctx, rx, JS_KEY_exec, 1, (JSValue *)&sub_str); - if (JS_IsException (exec_res)) { B_CLEANUP (); goto fail_rx; } + = JS_Invoke (ctx, RX_VAL, JS_KEY_exec, 1, (JSValue *)&sub_str); + if (JS_IsException (exec_res)) { RX_CLEANUP (); goto fail_rx; } if (JS_IsNull (exec_res)) { break; } JSValue idx_val = JS_GetPropertyStr (ctx, exec_res, "index"); - if (JS_IsException (idx_val)) { B_CLEANUP (); goto fail_rx; } + if (JS_IsException (idx_val)) { RX_CLEANUP (); goto fail_rx; } int32_t local_index = 0; - if (JS_ToInt32 (ctx, &local_index, idx_val)) { B_CLEANUP (); goto fail_rx; } + if (JS_ToInt32 (ctx, &local_index, idx_val)) { RX_CLEANUP (); goto fail_rx; } if (local_index < 0) local_index = 0; int found = pos + local_index; @@ -22428,34 +22511,34 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, } JSValue match = JS_GetPropertyStr (ctx, exec_res, "match"); - if (JS_IsException (match)) { B_CLEANUP (); goto fail_rx; } + if (JS_IsException (match)) { RX_CLEANUP (); goto fail_rx; } JSValue end_val = JS_GetPropertyStr (ctx, exec_res, "end"); - if (JS_IsException (end_val)) { B_CLEANUP (); goto fail_rx; } + if (JS_IsException (end_val)) { RX_CLEANUP (); goto fail_rx; } int32_t end = 0; - if (JS_ToInt32 (ctx, &end, end_val)) { B_CLEANUP (); goto fail_rx; } + if (JS_ToInt32 (ctx, &end, end_val)) { RX_CLEANUP (); goto fail_rx; } int match_len = end - local_index; if (match_len < 0) match_len = 0; if (found > pos) { JSValue prefix = js_sub_string_val (ctx, argv[0], pos, found); - if (JS_IsException (prefix)) { B_CLEANUP (); goto fail_rx; } + if (JS_IsException (prefix)) { RX_CLEANUP (); goto fail_rx; } B_RECHASE (); B_UPDATE (pretext_concat_value (ctx, b, prefix)); - if (!b) { B_CLEANUP (); goto fail_rx; } + if (!b) { RX_CLEANUP (); goto fail_rx; } } JSValue rep = make_replacement (ctx, argc, argv, found, match); - if (JS_IsException (rep)) { B_CLEANUP (); goto fail_rx; } + if (JS_IsException (rep)) { RX_CLEANUP (); goto fail_rx; } count++; if (!JS_IsNull (rep)) { B_RECHASE (); B_UPDATE (pt_concat_value_to_string_free (ctx, b, rep)); - if (!b) { B_CLEANUP (); goto fail_rx; } + if (!b) { RX_CLEANUP (); goto fail_rx; } } pos = found + match_len; @@ -22469,27 +22552,29 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, if (pos < len) { JSValue tail = js_sub_string_val (ctx, argv[0], pos, len); - if (JS_IsException (tail)) { B_CLEANUP (); goto fail_rx; } + if (JS_IsException (tail)) { RX_CLEANUP (); goto fail_rx; } B_RECHASE (); B_UPDATE (pretext_concat_value (ctx, b, tail)); - if (!b) { B_CLEANUP (); goto fail_rx; } + if (!b) { RX_CLEANUP (); goto fail_rx; } } if (have_orig_last_index) - JS_SetPropertyStr (ctx, rx, "lastIndex", orig_last_index); + JS_SetPropertyStr (ctx, RX_VAL, "lastIndex", orig_last_index); B_RECHASE (); - B_CLEANUP (); + RX_CLEANUP (); return pretext_end (ctx, b); fail_rx: if (!JS_IsNull (orig_last_index) && !JS_IsException (orig_last_index)) { - JS_SetPropertyStr (ctx, rx, "lastIndex", orig_last_index); + JS_SetPropertyStr (ctx, argv[1], "lastIndex", orig_last_index); } else { } return JS_EXCEPTION; } +#undef RX_CLEANUP +#undef RX_VAL #undef B_RECHASE #undef B_UPDATE #undef B_CLEANUP @@ -22549,28 +22634,35 @@ static JSValue js_cell_text_search (JSContext *ctx, JSValue this_val, int argc, return JS_NewInt32 (ctx, result); } - /* Regex target */ - JSValue rx = argv[1]; - JSValue orig_last_index = JS_GetPropertyStr (ctx, rx, "lastIndex"); + /* Regex target - root rx and str across allocating calls */ + JSGCRef rx_ref, str_ref; + JS_PushGCRef (ctx, &rx_ref); + rx_ref.val = argv[1]; + JS_PushGCRef (ctx, &str_ref); + str_ref.val = str; + +#define SEARCH_CLEANUP() do { JS_PopGCRef (ctx, &str_ref); JS_PopGCRef (ctx, &rx_ref); } while(0) + + JSValue orig_last_index = JS_GetPropertyStr (ctx, rx_ref.val, "lastIndex"); if (JS_IsException (orig_last_index)) { - /* str removed - arg not owned */ + SEARCH_CLEANUP (); return JS_EXCEPTION; } int have_orig_last_index = 1; - if (JS_SetPropertyStr (ctx, rx, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) + if (JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) goto fail_rx_search; - JSValue sub_str = js_sub_string_val (ctx, str, from, len); + JSValue sub_str = js_sub_string_val (ctx, str_ref.val, from, len); if (JS_IsException (sub_str)) goto fail_rx_search; - JSValue exec_res = JS_Invoke (ctx, rx, JS_KEY_exec, 1, (JSValue *)&sub_str); + JSValue exec_res = JS_Invoke (ctx, rx_ref.val, JS_KEY_exec, 1, (JSValue *)&sub_str); if (JS_IsException (exec_res)) goto fail_rx_search; if (JS_IsNull (exec_res)) { if (have_orig_last_index) - JS_SetPropertyStr (ctx, rx, "lastIndex", orig_last_index); - /* str removed - arg not owned */ + JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); + SEARCH_CLEANUP (); return JS_NULL; } @@ -22587,19 +22679,19 @@ static JSValue js_cell_text_search (JSContext *ctx, JSValue this_val, int argc, if (local_index < 0) local_index = 0; if (have_orig_last_index) - JS_SetPropertyStr (ctx, rx, "lastIndex", orig_last_index); + JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); - /* str removed - arg not owned */ + SEARCH_CLEANUP (); return JS_NewInt32 (ctx, from + local_index); fail_rx_search: if (!JS_IsNull (orig_last_index) && !JS_IsException (orig_last_index)) { - JS_SetPropertyStr (ctx, rx, "lastIndex", orig_last_index); - } else { + JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); } - /* str removed - arg not owned */ + SEARCH_CLEANUP (); return JS_EXCEPTION; } +#undef SEARCH_CLEANUP static inline uint32_t js_str_get (JSText *s, int idx) { return string_get (s, idx); } @@ -22660,61 +22752,75 @@ static JSValue js_cell_text_extract (JSContext *ctx, JSValue this_val, int argc, /* RegExp path: convert new exec result record -> classic array */ if (JS_IsObject (argv[1]) && JS_IsRegExp (ctx, argv[1])) { - JSValue rx = argv[1]; + /* Root rx, str, out across allocating calls */ + JSGCRef rx_ref, str_ref, out_ref; + JS_PushGCRef (ctx, &rx_ref); + rx_ref.val = argv[1]; + JS_PushGCRef (ctx, &str_ref); + str_ref.val = str; + JS_PushGCRef (ctx, &out_ref); + out_ref.val = JS_NULL; - JSValue orig_last_index = JS_GetPropertyStr (ctx, rx, "lastIndex"); - if (JS_IsException (orig_last_index)) return JS_EXCEPTION; +#define EXT_CLEANUP() do { JS_PopGCRef (ctx, &out_ref); JS_PopGCRef (ctx, &str_ref); JS_PopGCRef (ctx, &rx_ref); } while(0) - if (JS_SetPropertyStr (ctx, rx, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) + JSValue orig_last_index = JS_GetPropertyStr (ctx, rx_ref.val, "lastIndex"); + if (JS_IsException (orig_last_index)) { EXT_CLEANUP (); return JS_EXCEPTION; } + + if (JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) goto fail_rx; JSValue sub_str; if (from == 0 && to == len) { - sub_str = str; + sub_str = str_ref.val; } else { - sub_str = js_sub_string_val (ctx, str, from, to); + sub_str = js_sub_string_val (ctx, str_ref.val, from, to); if (JS_IsException (sub_str)) goto fail_rx; } - JSValue exec_res - = JS_Invoke (ctx, rx, JS_KEY_exec, 1, (JSValue *)&sub_str); - if (JS_IsException (exec_res)) goto fail_rx; + JSGCRef exec_ref; + JS_PushGCRef (ctx, &exec_ref); + exec_ref.val = JS_Invoke (ctx, rx_ref.val, JS_KEY_exec, 1, (JSValue *)&sub_str); + if (JS_IsException (exec_ref.val)) { JS_PopGCRef (ctx, &exec_ref); goto fail_rx; } - if (JS_IsNull (exec_res)) { - JS_SetPropertyStr (ctx, rx, "lastIndex", orig_last_index); + if (JS_IsNull (exec_ref.val)) { + JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); + JS_PopGCRef (ctx, &exec_ref); + EXT_CLEANUP (); return JS_NULL; } /* Build result array */ JSValue out = JS_NewArray (ctx); if (JS_IsException (out)) { + JS_PopGCRef (ctx, &exec_ref); goto fail_rx; } + out_ref.val = out; /* out[0] = exec_res.match */ - JSValue match0 = JS_GetPropertyStr (ctx, exec_res, "match"); + JSValue match0 = JS_GetPropertyStr (ctx, exec_ref.val, "match"); if (JS_IsException (match0)) { + JS_PopGCRef (ctx, &exec_ref); goto fail_rx; } + out = out_ref.val; if (JS_SetPropertyUint32 (ctx, out, 0, match0) < 0) { + JS_PopGCRef (ctx, &exec_ref); goto fail_rx; } - /* Append capture groups from exec_res.captures (skip [0] if it mirrors - * full match) */ - JSValue caps = JS_GetPropertyStr (ctx, exec_res, "captures"); + /* Append capture groups from exec_res.captures */ + JSValue caps = JS_GetPropertyStr (ctx, exec_ref.val, "captures"); + JS_PopGCRef (ctx, &exec_ref); /* exec_ref no longer needed */ if (!JS_IsException (caps) && JS_IsArray (caps)) { int64_t caps_len = 0; if (js_get_length64 (ctx, &caps_len, caps) == 0 && caps_len > 0) { - /* Many engines put full match at captures[0]. Your record already has - match separately. We assume captures[0] corresponds to group1, - group2, ... OR it includes full match. To satisfy your tests, we - treat captures[0] as capture group 1. */ for (int64_t i = 0; i < caps_len; i++) { JSValue cap = JS_GetPropertyInt64 (ctx, caps, i); if (JS_IsException (cap)) { goto fail_rx; } + out = out_ref.val; if (JS_SetPropertyInt64 (ctx, out, i + 1, cap) < 0) { goto fail_rx; } @@ -22722,16 +22828,19 @@ static JSValue js_cell_text_extract (JSContext *ctx, JSValue this_val, int argc, } } - JS_SetPropertyStr (ctx, rx, "lastIndex", orig_last_index); + JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); + out = out_ref.val; + EXT_CLEANUP (); return out; fail_rx: if (!JS_IsNull (orig_last_index) && !JS_IsException (orig_last_index)) { - JS_SetPropertyStr (ctx, rx, "lastIndex", orig_last_index); - } else { + JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); } + EXT_CLEANUP (); return JS_EXCEPTION; } +#undef EXT_CLEANUP /* Literal text path */ JSValue needle_val = JS_ToString (ctx, argv[1]); @@ -22757,12 +22866,16 @@ static JSValue js_cell_text_extract (JSContext *ctx, JSValue this_val, int argc, if (pos < 0) return JS_NULL; - JSValue arr = JS_NewArrayLen (ctx, 1); - if (JS_IsException (arr)) return JS_EXCEPTION; + JSGCRef arr_ref; + JS_PushGCRef (ctx, &arr_ref); + arr_ref.val = JS_NewArrayLen (ctx, 1); + if (JS_IsException (arr_ref.val)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } JSValue match = js_sub_string_val (ctx, str, pos, pos + needle_len); - if (JS_IsException (match)) - return JS_EXCEPTION; + if (JS_IsException (match)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + + JSValue arr = arr_ref.val; + JS_PopGCRef (ctx, &arr_ref); if (JS_SetPropertyUint32 (ctx, arr, 0, match) < 0) return JS_EXCEPTION; @@ -23604,14 +23717,25 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu if (JS_IsObject (argv[1]) && JS_IsRegExp (ctx, argv[1])) { /* Split by regex (manual "global" iteration; ignore g flag semantics) */ - JSValue rx = argv[1]; + /* Root rx, result, arg across allocating calls */ + JSGCRef rx_ref, res_ref, arg_ref; + JS_PushGCRef (ctx, &rx_ref); + rx_ref.val = argv[1]; + JS_PushGCRef (ctx, &res_ref); + res_ref.val = JS_NULL; + JS_PushGCRef (ctx, &arg_ref); + arg_ref.val = arg; + +#define RXS_CLEANUP() do { JS_PopGCRef (ctx, &arg_ref); JS_PopGCRef (ctx, &res_ref); JS_PopGCRef (ctx, &rx_ref); } while(0) JSValue result = JS_NewArray (ctx); - if (JS_IsException (result)) { return result; } + if (JS_IsException (result)) { RXS_CLEANUP (); return result; } + res_ref.val = result; /* Save & restore lastIndex to avoid mutating caller-visible state */ - JSValue orig_last_index = JS_GetPropertyStr (ctx, rx, "lastIndex"); + JSValue orig_last_index = JS_GetPropertyStr (ctx, rx_ref.val, "lastIndex"); if (JS_IsException (orig_last_index)) { + RXS_CLEANUP (); return JS_EXCEPTION; } @@ -23619,20 +23743,22 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu int64_t out_idx = 0; while (pos <= len) { - if (JS_SetPropertyStr (ctx, rx, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) + if (JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) goto fail_rx_split; - JSValue sub_str = js_sub_string_val (ctx, arg, pos, len); + JSValue sub_str = js_sub_string_val (ctx, arg_ref.val, pos, len); if (JS_IsException (sub_str)) goto fail_rx_split; JSValue exec_res - = JS_Invoke (ctx, rx, JS_KEY_exec, 1, (JSValue *)&sub_str); + = JS_Invoke (ctx, rx_ref.val, JS_KEY_exec, 1, (JSValue *)&sub_str); if (JS_IsException (exec_res)) goto fail_rx_split; if (JS_IsNull (exec_res)) { - JSValue tail = js_sub_string_val (ctx, arg, pos, len); + JSValue tail = js_sub_string_val (ctx, arg_ref.val, pos, len); if (JS_IsException (tail)) goto fail_rx_split; - if (JS_ArrayPush (ctx, &result, tail) < 0) { goto fail_rx_split; } + result = res_ref.val; + if (JS_ArrayPush (ctx, &result, tail) < 0) { res_ref.val = result; goto fail_rx_split; } + res_ref.val = result; break; } @@ -23646,8 +23772,9 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu int found = pos + local_index; if (found < pos) found = pos; if (found > len) { - JSValue tail = js_sub_string_val (ctx, arg, pos, len); + JSValue tail = js_sub_string_val (ctx, arg_ref.val, pos, len); if (JS_IsException (tail)) goto fail_rx_split; + result = res_ref.val; JS_SetPropertyInt64 (ctx, result, out_idx++, tail); break; } @@ -23661,31 +23788,39 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu int match_len = end - local_index; if (match_len < 0) match_len = 0; - JSValue part = js_sub_string_val (ctx, arg, pos, found); + JSValue part = js_sub_string_val (ctx, arg_ref.val, pos, found); if (JS_IsException (part)) goto fail_rx_split; - if (JS_ArrayPush (ctx, &result, part) < 0) { goto fail_rx_split; } + result = res_ref.val; + if (JS_ArrayPush (ctx, &result, part) < 0) { res_ref.val = result; goto fail_rx_split; } + res_ref.val = result; pos = found + match_len; if (match_len == 0) { if (found >= len) { JSValue empty = JS_NewStringLen (ctx, "", 0); if (JS_IsException (empty)) goto fail_rx_split; - if (JS_ArrayPush (ctx, &result, empty) < 0) { goto fail_rx_split; } + result = res_ref.val; + if (JS_ArrayPush (ctx, &result, empty) < 0) { res_ref.val = result; goto fail_rx_split; } + res_ref.val = result; break; } pos = found + 1; } } - JS_SetPropertyStr (ctx, rx, "lastIndex", orig_last_index); + JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); + result = res_ref.val; + RXS_CLEANUP (); return result; fail_rx_split: if (!JS_IsException (orig_last_index)) { - JS_SetPropertyStr (ctx, rx, "lastIndex", orig_last_index); + JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); } + RXS_CLEANUP (); return JS_EXCEPTION; } +#undef RXS_CLEANUP if (JS_VALUE_IS_NUMBER (argv[1])) { /* Dice into chunks */ @@ -24242,99 +24377,144 @@ static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSVal /* object(object) - shallow mutable copy */ if (JS_IsObject (arg) && !JS_IsArray (arg) && !JS_IsFunction (arg)) { if (argc < 2 || JS_IsNull (argv[1])) { - /* Shallow copy */ - JSValue result = JS_NewObject (ctx); - if (JS_IsException (result)) return result; + /* Shallow copy - root arg, result, keys across allocating calls */ + JSGCRef arg_ref, res_ref, keys_ref; + JS_PushGCRef (ctx, &arg_ref); + arg_ref.val = arg; + JS_PushGCRef (ctx, &res_ref); + res_ref.val = JS_NewObject (ctx); + JS_PushGCRef (ctx, &keys_ref); + keys_ref.val = JS_NULL; - JSValue keys = JS_GetOwnPropertyNames (ctx, arg); - if (JS_IsException (keys)) { +#define OBJ_COPY_CLEANUP() do { JS_PopGCRef (ctx, &keys_ref); JS_PopGCRef (ctx, &res_ref); JS_PopGCRef (ctx, &arg_ref); } while(0) + + if (JS_IsException (res_ref.val)) { OBJ_COPY_CLEANUP (); return JS_EXCEPTION; } + + keys_ref.val = JS_GetOwnPropertyNames (ctx, arg_ref.val); + if (JS_IsException (keys_ref.val)) { + OBJ_COPY_CLEANUP (); return JS_EXCEPTION; } uint32_t len; - if (js_get_length32 (ctx, &len, keys)) { + if (js_get_length32 (ctx, &len, keys_ref.val)) { + OBJ_COPY_CLEANUP (); return JS_EXCEPTION; } for (uint32_t i = 0; i < len; i++) { - JSValue key = JS_GetPropertyUint32 (ctx, keys, i); - JSValue val = JS_GetProperty (ctx, arg, key); + JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); + JSValue val = JS_GetProperty (ctx, arg_ref.val, key); if (JS_IsException (val)) { + OBJ_COPY_CLEANUP (); return JS_EXCEPTION; } - JS_SetProperty (ctx, result, key, val); + JS_SetProperty (ctx, res_ref.val, key, val); } + JSValue result = res_ref.val; + OBJ_COPY_CLEANUP (); return result; } +#undef OBJ_COPY_CLEANUP /* object(object, another_object) - combine */ if (JS_IsObject (argv[1]) && !JS_IsArray (argv[1])) { - JSValue result = JS_NewObject (ctx); - if (JS_IsException (result)) return result; + JSGCRef arg_ref, arg2_ref, res_ref, keys_ref; + JS_PushGCRef (ctx, &arg_ref); + arg_ref.val = arg; + JS_PushGCRef (ctx, &arg2_ref); + arg2_ref.val = argv[1]; + JS_PushGCRef (ctx, &res_ref); + res_ref.val = JS_NewObject (ctx); + JS_PushGCRef (ctx, &keys_ref); + keys_ref.val = JS_NULL; + +#define OBJ_COMBINE_CLEANUP() do { JS_PopGCRef (ctx, &keys_ref); JS_PopGCRef (ctx, &res_ref); JS_PopGCRef (ctx, &arg2_ref); JS_PopGCRef (ctx, &arg_ref); } while(0) + + if (JS_IsException (res_ref.val)) { OBJ_COMBINE_CLEANUP (); return JS_EXCEPTION; } /* Copy from first object */ - JSValue keys = JS_GetOwnPropertyNames (ctx, arg); - if (JS_IsException (keys)) { + keys_ref.val = JS_GetOwnPropertyNames (ctx, arg_ref.val); + if (JS_IsException (keys_ref.val)) { + OBJ_COMBINE_CLEANUP (); return JS_EXCEPTION; } uint32_t len; - if (js_get_length32 (ctx, &len, keys)) { + if (js_get_length32 (ctx, &len, keys_ref.val)) { + OBJ_COMBINE_CLEANUP (); return JS_EXCEPTION; } for (uint32_t i = 0; i < len; i++) { - JSValue key = JS_GetPropertyUint32 (ctx, keys, i); - JSValue val = JS_GetProperty (ctx, arg, key); + JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); + JSValue val = JS_GetProperty (ctx, arg_ref.val, key); if (JS_IsException (val)) { + OBJ_COMBINE_CLEANUP (); return JS_EXCEPTION; } - JS_SetProperty (ctx, result, key, val); + JS_SetProperty (ctx, res_ref.val, key, val); } /* Copy from second object */ - keys = JS_GetOwnPropertyNames (ctx, argv[1]); - if (JS_IsException (keys)) { + keys_ref.val = JS_GetOwnPropertyNames (ctx, arg2_ref.val); + if (JS_IsException (keys_ref.val)) { + OBJ_COMBINE_CLEANUP (); return JS_EXCEPTION; } - if (js_get_length32 (ctx, &len, keys)) { + if (js_get_length32 (ctx, &len, keys_ref.val)) { + OBJ_COMBINE_CLEANUP (); return JS_EXCEPTION; } for (uint32_t i = 0; i < len; i++) { - JSValue key = JS_GetPropertyUint32 (ctx, keys, i); - JSValue val = JS_GetProperty (ctx, argv[1], key); + JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); + JSValue val = JS_GetProperty (ctx, arg2_ref.val, key); if (JS_IsException (val)) { + OBJ_COMBINE_CLEANUP (); return JS_EXCEPTION; } - JS_SetProperty (ctx, result, key, val); + JS_SetProperty (ctx, res_ref.val, key, val); } + JSValue result = res_ref.val; + OBJ_COMBINE_CLEANUP (); return result; } +#undef OBJ_COMBINE_CLEANUP /* object(object, array_of_keys) - select */ if (JS_IsArray (argv[1])) { - JSValue result = JS_NewObject (ctx); - if (JS_IsException (result)) return result; + JSGCRef arg_ref, res_ref, karr_ref; + JS_PushGCRef (ctx, &arg_ref); + arg_ref.val = arg; + JS_PushGCRef (ctx, &res_ref); + res_ref.val = JS_NewObject (ctx); + JS_PushGCRef (ctx, &karr_ref); + karr_ref.val = argv[1]; - JSArray *keys = JS_VALUE_GET_ARRAY (argv[1]); +#define OBJ_SEL_CLEANUP() do { JS_PopGCRef (ctx, &karr_ref); JS_PopGCRef (ctx, &res_ref); JS_PopGCRef (ctx, &arg_ref); } while(0) + + if (JS_IsException (res_ref.val)) { OBJ_SEL_CLEANUP (); return JS_EXCEPTION; } + + JSArray *keys = JS_VALUE_GET_ARRAY (karr_ref.val); int len = keys->len; for (int i = 0; i < len; i++) { - keys = JS_VALUE_GET_ARRAY (argv[1]); /* re-chase each iteration */ + keys = JS_VALUE_GET_ARRAY (karr_ref.val); /* re-chase each iteration */ if (i >= (int)keys->len) break; JSValue key = keys->values[i]; if (JS_IsText (key)) { - /* Use JSValue key directly - create interned key */ JSValue prop_key = js_key_from_string (ctx, key); - int has = JS_HasProperty (ctx, arg, prop_key); + int has = JS_HasProperty (ctx, arg_ref.val, prop_key); if (has > 0) { - JSValue val = JS_GetProperty (ctx, arg, prop_key); + JSValue val = JS_GetProperty (ctx, arg_ref.val, prop_key); if (!JS_IsException (val)) { - JS_SetProperty (ctx, result, prop_key, val); + JS_SetProperty (ctx, res_ref.val, prop_key, val); } } - /* prop_key is interned, no need to free */ } } + JSValue result = res_ref.val; + OBJ_SEL_CLEANUP (); return result; } +#undef OBJ_SEL_CLEANUP } /* object(array_of_keys) - set with true values */ @@ -25722,71 +25902,83 @@ static JSValue js_cell_splat (JSContext *ctx, JSValue this_val, int argc, JSValu JSValue obj = argv[0]; if (!JS_IsObject (obj) || JS_IsNull (obj)) return JS_NULL; - JSValue result = JS_NewObject (ctx); - if (JS_IsException (result)) return JS_EXCEPTION; + /* Root obj, result, current, keys across allocating calls */ + JSGCRef obj_ref, res_ref, cur_ref, keys_ref; + JS_PushGCRef (ctx, &obj_ref); + obj_ref.val = obj; + JS_PushGCRef (ctx, &res_ref); + res_ref.val = JS_NewObject (ctx); + JS_PushGCRef (ctx, &cur_ref); + cur_ref.val = obj_ref.val; /* use rooted value, not stale local */ + JS_PushGCRef (ctx, &keys_ref); + keys_ref.val = JS_NULL; - JSValue current = obj; +#define SPLAT_CLEANUP() do { JS_PopGCRef (ctx, &keys_ref); JS_PopGCRef (ctx, &cur_ref); JS_PopGCRef (ctx, &res_ref); JS_PopGCRef (ctx, &obj_ref); } while(0) + + if (JS_IsException (res_ref.val)) { SPLAT_CLEANUP (); return JS_EXCEPTION; } /* Walk prototype chain and collect text keys */ - while (!JS_IsNull (current)) { - JSValue keys = JS_GetOwnPropertyNames (ctx, current); - if (JS_IsException (keys)) { + while (!JS_IsNull (cur_ref.val)) { + keys_ref.val = JS_GetOwnPropertyNames (ctx, cur_ref.val); + if (JS_IsException (keys_ref.val)) { + SPLAT_CLEANUP (); return JS_EXCEPTION; } uint32_t len; - if (js_get_length32 (ctx, &len, keys)) { + if (js_get_length32 (ctx, &len, keys_ref.val)) { + SPLAT_CLEANUP (); return JS_EXCEPTION; } for (uint32_t i = 0; i < len; i++) { - JSValue key = JS_GetPropertyUint32 (ctx, keys, i); - /* Check if property not already in result */ - int has = JS_HasProperty (ctx, result, key); + JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); + int has = JS_HasProperty (ctx, res_ref.val, key); if (has < 0) { + SPLAT_CLEANUP (); return JS_EXCEPTION; } if (!has) { - JSValue val = JS_GetProperty (ctx, current, key); + JSValue val = JS_GetProperty (ctx, cur_ref.val, key); if (JS_IsException (val)) { + SPLAT_CLEANUP (); return JS_EXCEPTION; } - /* Only include serializable types */ int tag = JS_VALUE_GET_TAG (val); if (JS_IsObject (val) || JS_IsNumber (val) || tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM || tag == JS_TAG_BOOL) { - JS_SetProperty (ctx, result, key, val); - } else { + JS_SetProperty (ctx, res_ref.val, key, val); } - } else { } } - JSValue next = JS_GetPrototype (ctx, current); - current = next; + cur_ref.val = JS_GetPrototype (ctx, cur_ref.val); } /* Call to_data if present */ - JSValue to_data = JS_GetPropertyStr (ctx, obj, "to_data"); + JSValue to_data = JS_GetPropertyStr (ctx, obj_ref.val, "to_data"); if (JS_IsFunction (to_data)) { - JSValue args[1] = { result }; - JSValue extra = JS_Call (ctx, to_data, obj, 1, args); + JSValue args[1] = { res_ref.val }; + JSValue extra = JS_Call (ctx, to_data, obj_ref.val, 1, args); if (!JS_IsException (extra) && JS_IsObject (extra)) { - JSValue keys2 = JS_GetOwnPropertyNames (ctx, extra); - if (!JS_IsException (keys2)) { + keys_ref.val = JS_GetOwnPropertyNames (ctx, extra); + if (!JS_IsException (keys_ref.val)) { uint32_t len; - if (!js_get_length32 (ctx, &len, keys2)) { + if (!js_get_length32 (ctx, &len, keys_ref.val)) { for (uint32_t i = 0; i < len; i++) { - JSValue key = JS_GetPropertyUint32 (ctx, keys2, i); + JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); JSValue val = JS_GetProperty (ctx, extra, key); - JS_SetProperty (ctx, result, key, val); + JS_SetProperty (ctx, res_ref.val, key, val); } } } } } + JSValue result = res_ref.val; + SPLAT_CLEANUP (); return result; } +#undef SPLAT_CLEANUP /* ============================================================================ * length() function