MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

prevent OOM on json parsing

+357 -54
+29 -1
include/gc/roots.h
··· 4 4 #include "types.h" 5 5 #include <assert.h> 6 6 #include <stdbool.h> 7 + #include <stddef.h> 7 8 8 9 #define GC_MAX_STATIC_ROOTS 128 9 10 10 - typedef void (*gc_root_visitor_t)(ant_t *js, ant_value_t v); 11 + typedef struct gc_temp_root_scope { 12 + ant_t *js; 13 + ant_value_t *items; 14 + size_t len; 15 + size_t cap; 16 + struct gc_temp_root_scope *prev; 17 + } gc_temp_root_scope_t; 18 + 19 + typedef struct { 20 + gc_temp_root_scope_t *scope; 21 + size_t index; 22 + } gc_temp_root_handle_t; 23 + 24 + typedef void (*gc_root_visitor_t)( 25 + ant_t *js, 26 + ant_value_t v 27 + ); 11 28 12 29 size_t gc_root_scope(ant_t *js); 30 + 13 31 bool gc_push_root(ant_t *js, ant_value_t *slot); 32 + bool gc_temp_root_set(gc_temp_root_handle_t handle, ant_value_t value); 14 33 15 34 void gc_register_root(ant_value_t *slot); 16 35 void gc_pop_roots(ant_t *js, size_t mark); 17 36 void gc_visit_roots(ant_t *js, gc_root_visitor_t visitor); 37 + void gc_temp_root_scope_begin(ant_t *js, gc_temp_root_scope_t *scope); 38 + void gc_temp_root_scope_end(gc_temp_root_scope_t *scope); 39 + 40 + ant_value_t gc_temp_root_get(gc_temp_root_handle_t handle); 41 + gc_temp_root_handle_t gc_temp_root_add(gc_temp_root_scope_t *scope, ant_value_t value); 18 42 19 43 #define GC_ROOT_PIN(js, slot) do { \ 20 44 bool _gc_root_ok = gc_push_root((js), &(slot)); \ ··· 24 48 25 49 #define GC_ROOT_SAVE(name, js) size_t name = gc_root_scope((js)) 26 50 #define GC_ROOT_RESTORE(js, mark) gc_pop_roots((js), (mark)) 51 + 52 + static inline bool gc_temp_root_handle_valid(gc_temp_root_handle_t handle) { 53 + return handle.scope != NULL; 54 + } 27 55 28 56 #endif
+1
include/internal.h
··· 144 144 ant_value_t **c_roots; 145 145 size_t c_root_count; 146 146 size_t c_root_cap; 147 + struct gc_temp_root_scope *temp_roots; 147 148 148 149 const char *code; 149 150 const char *filename;
+63 -4
src/gc/roots.c
··· 1 1 #include "gc/roots.h" 2 - #include "internal.h" 2 + #include "internal.h" // IWYU pragma: keep 3 3 4 4 #include <stddef.h> 5 5 #include <stdlib.h> ··· 36 36 js->c_root_count = (mark <= js->c_root_count) ? mark : 0; 37 37 } 38 38 39 + void gc_temp_root_scope_begin(ant_t *js, gc_temp_root_scope_t *scope) { 40 + if (!scope) return; 41 + scope->js = js; 42 + scope->items = NULL; 43 + scope->len = 0; 44 + scope->cap = 0; 45 + scope->prev = js ? js->temp_roots : NULL; 46 + if (js) js->temp_roots = scope; 47 + } 48 + 49 + void gc_temp_root_scope_end(gc_temp_root_scope_t *scope) { 50 + if (!scope) return; 51 + 52 + ant_t *js = scope->js; 53 + if (js && js->temp_roots == scope) js->temp_roots = scope->prev; 54 + 55 + free(scope->items); 56 + scope->items = NULL; 57 + scope->len = 0; 58 + scope->cap = 0; 59 + scope->prev = NULL; 60 + scope->js = NULL; 61 + } 62 + 63 + gc_temp_root_handle_t gc_temp_root_add(gc_temp_root_scope_t *scope, ant_value_t value) { 64 + gc_temp_root_handle_t invalid = {0}; 65 + if (!scope) return invalid; 66 + 67 + if (scope->len >= scope->cap) { 68 + size_t new_cap = scope->cap ? scope->cap * 2 : 16; 69 + ant_value_t *next = realloc(scope->items, new_cap * sizeof(*next)); 70 + if (!next) return invalid; 71 + scope->items = next; 72 + scope->cap = new_cap; 73 + } 74 + 75 + size_t index = scope->len++; 76 + scope->items[index] = value; 77 + gc_temp_root_handle_t handle = { 78 + .scope = scope, 79 + .index = index, 80 + }; 81 + 82 + return handle; 83 + } 84 + 85 + bool gc_temp_root_set(gc_temp_root_handle_t handle, ant_value_t value) { 86 + if (!handle.scope || handle.index >= handle.scope->len) return false; 87 + handle.scope->items[handle.index] = value; 88 + return true; 89 + } 90 + 91 + ant_value_t gc_temp_root_get(gc_temp_root_handle_t handle) { 92 + if (!handle.scope || handle.index >= handle.scope->len) return js_mkundef(); 93 + return handle.scope->items[handle.index]; 94 + } 95 + 39 96 void gc_visit_roots(ant_t *js, gc_root_visitor_t visitor) { 40 - for (size_t i = 0; i < g_root_count; i++) { 97 + for (size_t i = 0; i < g_root_count; i++) 41 98 if (g_roots[i] && *g_roots[i]) visitor(js, *g_roots[i]); 42 - } 43 - 99 + 44 100 if (!js) return; 45 101 for (size_t i = 0; i < js->c_root_count; i++) { 46 102 ant_value_t *slot = js->c_roots[i]; 47 103 if (slot && *slot) visitor(js, *slot); 48 104 } 105 + 106 + for (gc_temp_root_scope_t *scope = js->temp_roots; scope; scope = scope->prev) 107 + for (size_t i = 0; i < scope->len; i++) if (scope->items[i]) visitor(js, scope->items[i]); 49 108 }
+212 -49
src/modules/json.c
··· 5 5 #include <yyjson.h> 6 6 #include <uthash.h> 7 7 8 - #include "gc.h" 8 + #include "gc/roots.h" 9 9 #include "utf8.h" 10 10 #include "errors.h" 11 11 #include "runtime.h" ··· 22 22 UT_hash_handle hh; 23 23 } json_key_entry_t; 24 24 25 - static ant_value_t yyjson_to_jsval(ant_t *js, yyjson_val *val) { 25 + static inline bool json_value_needs_temp_root(ant_value_t value) { 26 + if (value <= NANBOX_PREFIX) return false; 27 + 28 + static const uint32_t mask = 29 + (1u << T_STR) | (1u << T_OBJ) | (1u << T_ARR) | (1u << T_FUNC) | 30 + (1u << T_PROMISE) | (1u << T_GENERATOR) | (1u << T_SYMBOL) | (1u << T_BIGINT); 31 + 32 + uint8_t t = vtype(value); 33 + return t < 32 && (mask >> t) & 1; 34 + } 35 + 36 + static inline bool json_temp_pin(gc_temp_root_scope_t *roots, ant_value_t value) { 37 + if (!json_value_needs_temp_root(value)) return true; 38 + return gc_temp_root_handle_valid(gc_temp_root_add(roots, value)); 39 + } 40 + 41 + static inline ant_value_t json_parse_oom(ant_t *js) { 42 + return js_mkerr(js, "JSON.parse() failed: out of memory"); 43 + } 44 + 45 + static inline ant_value_t json_stringify_oom(ant_t *js) { 46 + return js_mkerr(js, "JSON.stringify() failed: out of memory"); 47 + } 48 + 49 + static ant_value_t yyjson_to_jsval(ant_t *js, yyjson_val *val, gc_temp_root_scope_t *roots) { 26 50 if (!val) return js_mkundef(); 27 51 28 52 switch (yyjson_get_type(val)) { 29 53 case YYJSON_TYPE_NULL: return js_mknull(); 30 54 case YYJSON_TYPE_BOOL: return js_bool(yyjson_get_bool(val)); 31 - case YYJSON_TYPE_STR: return js_mkstr(js, yyjson_get_str(val), yyjson_get_len(val)); 55 + 56 + case YYJSON_TYPE_STR: { 57 + ant_value_t str = js_mkstr(js, yyjson_get_str(val), yyjson_get_len(val)); 58 + if (is_err(str)) return str; 59 + if (!json_temp_pin(roots, str)) return json_parse_oom(js); 60 + return str; 61 + } 32 62 33 63 case YYJSON_TYPE_NUM: { 34 64 if (yyjson_is_sint(val)) return js_mknum((double)yyjson_get_sint(val)); ··· 38 68 39 69 case YYJSON_TYPE_ARR: { 40 70 ant_value_t arr = js_mkarr(js); 71 + if (is_err(arr)) return arr; 72 + if (!json_temp_pin(roots, arr)) return json_parse_oom(js); 41 73 size_t idx, max; 42 74 yyjson_val *item; 43 75 44 76 yyjson_arr_foreach(val, idx, max, item) { 45 - ant_value_t elem = yyjson_to_jsval(js, item); 77 + ant_value_t elem = yyjson_to_jsval(js, item, roots); 78 + if (is_err(elem)) return elem; 46 79 js_arr_push(js, arr, elem); 47 80 } 48 81 ··· 51 84 52 85 case YYJSON_TYPE_OBJ: { 53 86 ant_value_t obj = js_newobj(js); 87 + if (is_err(obj)) return obj; 88 + if (!json_temp_pin(roots, obj)) return json_parse_oom(js); 54 89 55 90 size_t idx, max; yyjson_val *key, *item; 56 91 json_key_entry_t *hash = NULL, *entry, *tmp; ··· 59 94 const char *k = yyjson_get_str(key); 60 95 61 96 size_t klen = yyjson_get_len(key); 62 - ant_value_t v = yyjson_to_jsval(js, item); 97 + ant_value_t v = yyjson_to_jsval(js, item, roots); 98 + if (is_err(v)) { 99 + HASH_ITER(hh, hash, entry, tmp) 100 + HASH_DEL(hash, entry); free(entry); 101 + return v; 102 + } 63 103 64 104 HASH_FIND(hh, hash, k, klen, entry); 65 105 if (entry) js_saveval(js, entry->prop_off, v); else { 66 106 ant_offset_t off = js_mkprop_fast_off(js, obj, k, klen, v); 107 + if (off == 0) { 108 + HASH_ITER(hh, hash, entry, tmp) 109 + HASH_DEL(hash, entry); free(entry); 110 + return json_parse_oom(js); 111 + } 67 112 entry = malloc(sizeof(json_key_entry_t)); 113 + if (!entry) { 114 + HASH_ITER(hh, hash, entry, tmp) 115 + HASH_DEL(hash, entry); free(entry); 116 + return json_parse_oom(js); 117 + } 68 118 entry->key = k; entry->key_len = klen; entry->prop_off = off; 69 119 HASH_ADD_KEYPTR(hh, hash, entry->key, entry->key_len, entry); 70 120 }} 71 121 72 - HASH_ITER(hh, hash, entry, tmp) { 122 + HASH_ITER(hh, hash, entry, tmp) 73 123 HASH_DEL(hash, entry); free(entry); 74 - } 75 - 124 + 76 125 return obj; 77 126 } 78 127 ··· 86 135 ant_value_t replacer_arr; 87 136 ant_value_t error; 88 137 ant_value_t holder; 138 + 139 + gc_temp_root_scope_t temp_roots; 140 + gc_temp_root_handle_t error_handle; 141 + gc_temp_root_handle_t holder_handle; 142 + 89 143 int stack_size; 90 144 int stack_cap; 91 145 int replacer_arr_len; ··· 101 155 return value; 102 156 } 103 157 158 + static void json_set_error(json_cycle_ctx *ctx, ant_value_t value) { 159 + ctx->error = value; 160 + gc_temp_root_set(ctx->error_handle, value); 161 + } 162 + 163 + static inline bool json_ctx_pin_value(json_cycle_ctx *ctx, ant_value_t value) { 164 + if (json_temp_pin(&ctx->temp_roots, value)) return true; 165 + json_set_error(ctx, json_stringify_oom(ctx->js)); 166 + return false; 167 + } 168 + 169 + static inline void json_set_holder(json_cycle_ctx *ctx, ant_value_t value) { 170 + ctx->holder = value; 171 + gc_temp_root_set(ctx->holder_handle, value); 172 + } 173 + 104 174 static void json_capture_error(json_cycle_ctx *ctx, ant_value_t value) { 105 175 if (vtype(ctx->error) != T_UNDEF) return; 106 176 if (ctx->js->thrown_exists) { 107 - ctx->error = ctx->js->thrown_value; 177 + json_set_error(ctx, ctx->js->thrown_value); 108 178 ctx->js->thrown_exists = false; 109 179 ctx->js->thrown_value = js_mkundef(); 110 180 return; 111 181 } 112 - ctx->error = json_normalize_error(value); 182 + json_set_error(ctx, json_normalize_error(value)); 113 183 } 114 184 115 185 static yyjson_mut_val *json_string_to_yyjson(ant_t *js, yyjson_mut_doc *doc, ant_value_t value) { ··· 188 258 189 259 static ant_value_t apply_reviver( 190 260 ant_t *js, ant_value_t holder, 191 - const char *key, ant_value_t reviver 261 + const char *key, ant_value_t reviver, 262 + gc_temp_root_scope_t *roots 192 263 ); 193 264 194 265 static ant_value_t json_apply_tojson( ··· 206 277 } 207 278 208 279 if (!is_callable(toJSON)) return val; 209 - ant_value_t args[1] = { js_mkstr(js, key, strlen(key)) }; 280 + ant_value_t key_arg = js_mkstr(js, key, strlen(key)); 281 + if (is_err(key_arg)) { 282 + json_capture_error(ctx, key_arg); 283 + return js_mkundef(); 284 + } 285 + 286 + if (!json_ctx_pin_value(ctx, key_arg)) return js_mkundef(); 287 + ant_value_t args[1] = { key_arg }; 210 288 211 289 ant_value_t transformed = sv_vm_call( 212 290 js->vm, js, ··· 218 296 json_capture_error(ctx, transformed); 219 297 return js_mkundef(); 220 298 } 299 + if (!json_ctx_pin_value(ctx, transformed)) return js_mkundef(); 221 300 222 301 return transformed; 223 302 } ··· 229 308 json_cycle_ctx *ctx 230 309 ) { 231 310 if (!is_callable(ctx->replacer_func)) return val; 232 - ant_value_t args[2] = { js_mkstr(js, key, strlen(key)), val }; 311 + ant_value_t key_arg = js_mkstr(js, key, strlen(key)); 312 + if (is_err(key_arg)) { 313 + json_capture_error(ctx, key_arg); 314 + return js_mkundef(); 315 + } 316 + if (!json_ctx_pin_value(ctx, key_arg)) return js_mkundef(); 317 + ant_value_t args[2] = { key_arg, val }; 233 318 234 319 ant_value_t transformed = sv_vm_call( 235 320 js->vm, js, ··· 241 326 json_capture_error(ctx, transformed); 242 327 return js_mkundef(); 243 328 } 329 + if (!json_ctx_pin_value(ctx, transformed)) return js_mkundef(); 244 330 245 331 return transformed; 246 332 } 247 333 248 - static inline ant_value_t json_create_root_holder(ant_t *js, ant_value_t value) { 334 + static inline ant_value_t json_create_root_holder(ant_t *js, ant_value_t value, json_cycle_ctx *ctx) { 249 335 ant_value_t holder = js_mkobj(js); 250 - if (!is_err(holder)) js_set(js, holder, "", value); 336 + if (is_err(holder)) return holder; 337 + if (!json_ctx_pin_value(ctx, holder)) return js_mkundef(); 338 + js_set(js, holder, "", value); 251 339 return holder; 252 340 } 253 341 ··· 258 346 ant_offset_t length = js_arr_len(js, val); 259 347 ant_value_t saved_holder = ctx->holder; 260 348 261 - ctx->holder = val; 349 + json_set_holder(ctx, val); 262 350 for (ant_offset_t i = 0; i < length; i++) { 263 351 char idxstr[32]; 264 352 uint_to_str(idxstr, sizeof(idxstr), (uint64_t)i); 265 353 ant_value_t elem = js_arr_get(js, val, i); 266 354 yyjson_mut_val *item = ant_value_to_yyjson_with_key(js, doc, idxstr, elem, ctx, 1); 267 355 if (json_has_abort(ctx)) { 268 - ctx->holder = saved_holder; 356 + json_set_holder(ctx, saved_holder); 269 357 return NULL; 270 358 } 271 359 yyjson_mut_arr_add_val(arr, item); 272 360 } 273 361 274 - ctx->holder = saved_holder; 362 + json_set_holder(ctx, saved_holder); 275 363 return arr; 276 364 } 277 365 ··· 286 374 json_capture_error(ctx, keys); 287 375 return NULL; 288 376 } 377 + if (!json_ctx_pin_value(ctx, keys)) return NULL; 289 378 290 - ctx->holder = val; 379 + json_set_holder(ctx, val); 291 380 ant_offset_t key_count = js_arr_len(js, keys); 292 381 293 382 for (ant_offset_t i = 0; i < key_count; i++) { ··· 301 390 ant_value_t prop = js_get(js, val, key); 302 391 if (is_err(prop)) { 303 392 json_capture_error(ctx, prop); 304 - ctx->holder = saved_holder; 393 + json_set_holder(ctx, saved_holder); 305 394 return NULL; 306 395 } 307 396 308 397 yyjson_mut_val *jval = ant_value_to_yyjson_with_key(js, doc, key, prop, ctx, 0); 309 398 if (json_has_abort(ctx)) { 310 - ctx->holder = saved_holder; 399 + json_set_holder(ctx, saved_holder); 311 400 return NULL; 312 401 } 313 402 ··· 315 404 yyjson_mut_obj_add(obj, yyjson_mut_strncpy(doc, key, key_len), jval); 316 405 } 317 406 318 - ctx->holder = saved_holder; 407 + json_set_holder(ctx, saved_holder); 319 408 return obj; 320 409 } 321 410 ··· 379 468 return ant_value_to_yyjson_with_key(js, doc, "", val, ctx, 0); 380 469 } 381 470 382 - static ant_value_t apply_reviver_call(ant_t *js, ant_value_t holder, const char *key, ant_value_t reviver) { 471 + static ant_value_t apply_reviver_call( 472 + ant_t *js, 473 + ant_value_t holder, 474 + const char *key, 475 + ant_value_t reviver, 476 + gc_temp_root_scope_t *roots 477 + ) { 383 478 ant_value_t key_str = js_mkstr(js, key, strlen(key)); 479 + if (is_err(key_str)) return key_str; 480 + if (!json_temp_pin(roots, key_str)) return json_parse_oom(js); 384 481 ant_value_t current_value = js_get(js, holder, key); 385 482 ant_value_t call_args[2] = { key_str, current_value }; 386 483 ··· 388 485 js->vm, js, reviver, holder, 389 486 call_args, 2, NULL, false 390 487 ); 488 + if (!is_err(result) && !json_temp_pin(roots, result)) return json_parse_oom(js); 391 489 392 490 return result; 393 491 } 394 492 395 - static void apply_reviver_to_array(ant_t *js, ant_value_t value, ant_value_t reviver) { 493 + static void apply_reviver_to_array( 494 + ant_t *js, 495 + ant_value_t value, 496 + ant_value_t reviver, 497 + gc_temp_root_scope_t *roots 498 + ) { 396 499 ant_offset_t length = js_arr_len(js, value); 397 500 398 501 for (ant_offset_t i = 0; i < length; i++) { 399 502 char idxstr[32]; 400 503 size_t idx_len = uint_to_str(idxstr, sizeof(idxstr), (uint64_t)i); 401 - ant_value_t new_elem = apply_reviver(js, value, idxstr, reviver); 504 + ant_value_t new_elem = apply_reviver(js, value, idxstr, reviver, roots); 402 505 if (vtype(new_elem) == T_UNDEF) js_delete_prop(js, value, idxstr, idx_len); 403 506 else { 404 507 ant_value_t key_val = js_mkstr(js, idxstr, idx_len); 508 + if (is_err(key_val)) return; 509 + if (!json_temp_pin(roots, key_val)) return; 405 510 js_setprop(js, value, key_val, new_elem); 406 511 }} 407 512 } 408 513 409 - static void apply_reviver_to_object(ant_t *js, ant_value_t value, ant_value_t reviver) { 514 + static void apply_reviver_to_object( 515 + ant_t *js, 516 + ant_value_t value, 517 + ant_value_t reviver, 518 + gc_temp_root_scope_t *roots 519 + ) { 410 520 ant_value_t keys = json_snapshot_keys(js, value); 411 521 if (is_err(keys) || vtype(keys) != T_ARR) return; 522 + if (!json_temp_pin(roots, keys)) return; 412 523 413 524 ant_offset_t key_count = js_arr_len(js, keys); 414 525 for (ant_offset_t i = 0; i < key_count; i++) { ··· 416 527 size_t key_len = 0; 417 528 char *key = js_getstr(js, key_val, &key_len); 418 529 if (!key) continue; 419 - ant_value_t new_val = apply_reviver(js, value, key, reviver); 530 + ant_value_t new_val = apply_reviver(js, value, key, reviver, roots); 420 531 if (vtype(new_val) == T_UNDEF) js_delete_prop(js, value, key, key_len); 421 532 else js_set(js, value, key, new_val); 422 533 } 423 534 } 424 535 425 - static ant_value_t apply_reviver(ant_t *js, ant_value_t holder, const char *key, ant_value_t reviver) { 536 + static ant_value_t apply_reviver( 537 + ant_t *js, 538 + ant_value_t holder, 539 + const char *key, 540 + ant_value_t reviver, 541 + gc_temp_root_scope_t *roots 542 + ) { 426 543 ant_value_t val = js_get(js, holder, key); 427 544 428 - if (json_is_array(val)) apply_reviver_to_array(js, val, reviver); 429 - else if (vtype(val) == T_OBJ) apply_reviver_to_object(js, val, reviver); 545 + if (json_is_array(val)) apply_reviver_to_array(js, val, reviver, roots); 546 + else if (vtype(val) == T_OBJ) apply_reviver_to_object(js, val, reviver, roots); 430 547 431 - return apply_reviver_call(js, holder, key, reviver); 548 + return apply_reviver_call(js, holder, key, reviver, roots); 432 549 } 433 550 434 551 ant_value_t js_json_parse(ant_t *js, ant_value_t *args, int nargs) { 435 552 if (nargs < 1) return js_mkerr(js, "JSON.parse() requires at least 1 argument"); 436 553 if (vtype(args[0]) != T_STR) return js_mkerr(js, "JSON.parse() argument must be a string"); 437 - bool saved_gc_disabled = gc_disabled; 554 + gc_temp_root_scope_t temp_roots; 555 + gc_temp_root_scope_begin(js, &temp_roots); 438 556 439 557 size_t len; 440 558 char *json_str = js_getstr(js, args[0], &len); 441 559 442 - gc_disabled = true; 443 560 yyjson_doc *doc = yyjson_read(json_str, len, 0); 444 561 445 562 if (!doc) { 446 - gc_disabled = saved_gc_disabled; 563 + gc_temp_root_scope_end(&temp_roots); 447 564 return js_mkerr_typed(js, JS_ERR_SYNTAX, "JSON.parse: unexpected character"); 448 565 } 449 566 450 - ant_value_t result = yyjson_to_jsval(js, yyjson_doc_get_root(doc)); 567 + ant_value_t result = yyjson_to_jsval(js, yyjson_doc_get_root(doc), &temp_roots); 451 568 yyjson_doc_free(doc); 569 + if (is_err(result)) { 570 + gc_temp_root_scope_end(&temp_roots); 571 + return result; 572 + } 452 573 453 574 if (nargs >= 2 && is_callable(args[1])) { 454 575 ant_value_t reviver = args[1]; 576 + if (!json_temp_pin(&temp_roots, reviver)) { 577 + gc_temp_root_scope_end(&temp_roots); 578 + return json_parse_oom(js); 579 + } 455 580 ant_value_t root = js_mkobj(js); 581 + if (is_err(root)) { 582 + gc_temp_root_scope_end(&temp_roots); 583 + return root; 584 + } 585 + if (!json_temp_pin(&temp_roots, root)) { 586 + gc_temp_root_scope_end(&temp_roots); 587 + return json_parse_oom(js); 588 + } 456 589 js_set(js, root, "", result); 457 - result = apply_reviver(js, root, "", reviver); 590 + result = apply_reviver(js, root, "", reviver, &temp_roots); 458 591 } 459 592 460 - gc_disabled = saved_gc_disabled; 593 + gc_temp_root_scope_end(&temp_roots); 461 594 return result; 462 595 } 463 596 ··· 483 616 ant_value_t js_json_stringify(ant_t *js, ant_value_t *args, int nargs) { 484 617 ant_value_t result; 485 618 yyjson_mut_doc *doc = NULL; 486 - bool saved_gc_disabled = gc_disabled; 487 619 488 620 json_cycle_ctx ctx = { 489 621 .js = js, ··· 498 630 ant_value_t root_holder = js_mkundef(); 499 631 500 632 if (nargs < 1) return js_mkerr(js, "JSON.stringify() requires at least 1 argument"); 633 + gc_temp_root_scope_begin(js, &ctx.temp_roots); 634 + ctx.error_handle = gc_temp_root_add(&ctx.temp_roots, ctx.error); 635 + ctx.holder_handle = gc_temp_root_add(&ctx.temp_roots, ctx.holder); 636 + 637 + if (!gc_temp_root_handle_valid(ctx.error_handle) || !gc_temp_root_handle_valid(ctx.holder_handle)) { 638 + gc_temp_root_scope_end(&ctx.temp_roots); 639 + return json_stringify_oom(js); 640 + } 641 + 642 + if (!json_ctx_pin_value(&ctx, args[0])) { 643 + result = ctx.error; 644 + goto cleanup; 645 + } 646 + 501 647 int top_type = vtype(args[0]); 502 648 503 649 if (nargs < 2 && top_type == T_STR) { ··· 507 653 char *str = js_getstr(js, args[0], &byte_len); 508 654 char *raw = utf8_json_quote(str, byte_len, &raw_len); 509 655 510 - if (!raw) return js_mkerr(js, "JSON.stringify() failed: out of memory"); 656 + if (!raw) { 657 + result = js_mkerr(js, "JSON.stringify() failed: out of memory"); 658 + goto cleanup; 659 + } 511 660 result = js_mkstr(js, raw, raw_len); 512 661 free(raw); 513 - 514 - return result; 662 + goto cleanup; 515 663 } 516 - 517 - gc_disabled = true; 518 664 519 665 if (nargs >= 2) { 520 666 ant_value_t replacer = args[1]; 521 - if (is_callable(replacer)) ctx.replacer_func = replacer; 667 + if (is_callable(replacer)) { 668 + ctx.replacer_func = replacer; 669 + if (!json_ctx_pin_value(&ctx, replacer)) { 670 + result = ctx.error; 671 + goto cleanup; 672 + }} 522 673 523 674 else if (is_special_object(replacer)) { 524 675 ant_value_t len_val = js_get(js, replacer, "length"); ··· 526 677 if (vtype(len_val) == T_NUM) { 527 678 ctx.replacer_arr = replacer; 528 679 ctx.replacer_arr_len = (int)js_getnum(len_val); 529 - }}} 680 + if (!json_ctx_pin_value(&ctx, replacer)) { 681 + result = ctx.error; 682 + goto cleanup; 683 + } 684 + }}} 530 685 531 686 doc = yyjson_mut_doc_new(NULL); 532 - if (!doc) return js_mkerr(js, "JSON.stringify() failed: out of memory"); 687 + if (!doc) { 688 + result = js_mkerr(js, "JSON.stringify() failed: out of memory"); 689 + goto cleanup; 690 + } 533 691 534 - root_holder = json_create_root_holder(js, args[0]); 692 + root_holder = json_create_root_holder(js, args[0], &ctx); 535 693 if (is_err(root_holder)) { 536 694 result = root_holder; 537 695 goto cleanup; 538 696 } 539 - ctx.holder = root_holder; 697 + 698 + if (vtype(root_holder) == T_UNDEF && vtype(ctx.error) != T_UNDEF) { 699 + result = ctx.error; 700 + goto cleanup; 701 + } 540 702 703 + json_set_holder(&ctx, root_holder); 541 704 yyjson_mut_val *root = ant_value_to_yyjson(js, doc, args[0], &ctx); 542 705 543 706 if (vtype(ctx.error) != T_UNDEF) { ··· 567 730 result = js_mkstr(js, json_str, len); 568 731 569 732 cleanup: 570 - gc_disabled = saved_gc_disabled; 571 733 free(json_str); 572 734 free(ctx.stack); 573 735 yyjson_mut_doc_free(doc); 736 + gc_temp_root_scope_end(&ctx.temp_roots); 574 737 return result; 575 738 } 576 739
+52
tests/json_temp_roots_stress.cjs
··· 1 + function assert(condition, message) { 2 + if (!condition) throw new Error(message); 3 + } 4 + 5 + function gcPressure() { 6 + const junk = []; 7 + for (let i = 0; i < 256; i++) { 8 + junk.push({ 9 + i, 10 + label: 'x'.repeat(32), 11 + nested: [i, i + 1, i + 2] 12 + }); 13 + } 14 + return junk.length; 15 + } 16 + 17 + const parsed = JSON.parse('{"outer":{"items":[{"name":"a"},{"name":"b"}]}}', function (key, value) { 18 + gcPressure(); 19 + if (key === 'name') return value.toUpperCase(); 20 + return value; 21 + }); 22 + 23 + assert(parsed.outer.items[0].name === 'A', 'reviver should preserve nested values under GC pressure'); 24 + assert(parsed.outer.items[1].name === 'B', 'reviver should preserve sibling values under GC pressure'); 25 + 26 + const source = { 27 + title: 'root', 28 + nested: { 29 + keep: 'ok', 30 + value: 7, 31 + toJSON() { 32 + gcPressure(); 33 + return { 34 + keep: this.keep, 35 + value: this.value 36 + }; 37 + } 38 + } 39 + }; 40 + 41 + const json = JSON.stringify(source, function (key, value) { 42 + gcPressure(); 43 + if (key === 'title') return value + '-done'; 44 + return value; 45 + }); 46 + 47 + const roundTrip = JSON.parse(json); 48 + assert(roundTrip.title === 'root-done', 'replacer should preserve transformed root properties'); 49 + assert(roundTrip.nested.keep === 'ok', 'toJSON result should survive GC pressure'); 50 + assert(roundTrip.nested.value === 7, 'nested numeric values should survive GC pressure'); 51 + 52 + console.log('json temp roots stress: ok');