json rooting fix

This commit is contained in:
2026-02-17 14:00:23 -06:00
parent 2e78e7e0b8
commit 51815b66d8
3 changed files with 219 additions and 9 deletions

View File

@@ -1847,7 +1847,25 @@ TEST(is_integer_vs_number) {
/* JSON Tests */
TEST(json_encode_object) {
/* Skip - requires GC rooting fixes in JS_JSONStringify */
/* Build an object with several properties and stringify with pretty=true */
JSGCRef obj_ref;
JS_PushGCRef(ctx, &obj_ref);
obj_ref.val = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj_ref.val, "name", JS_NewString(ctx, "test"));
JS_SetPropertyStr(ctx, obj_ref.val, "value", JS_NewInt32(ctx, 42));
JS_SetPropertyStr(ctx, obj_ref.val, "active", JS_NewBool(ctx, 1));
JS_SetPropertyStr(ctx, obj_ref.val, "tag", JS_NewString(ctx, "hello world"));
JSValue space = JS_NewInt32(ctx, 2);
JSValue str = JS_JSONStringify(ctx, obj_ref.val, JS_NULL, space, 1);
JS_PopGCRef(ctx, &obj_ref);
ASSERT(!JS_IsException(str));
ASSERT(JS_IsText(str));
const char *s = JS_ToCString(ctx, str);
ASSERT(s != NULL);
ASSERT(strstr(s, "\"name\"") != NULL);
ASSERT(strstr(s, "\"test\"") != NULL);
ASSERT(strstr(s, "42") != NULL);
JS_FreeCString(ctx, s);
return 1;
}
@@ -1867,7 +1885,98 @@ TEST(json_decode_object) {
}
TEST(json_roundtrip_array) {
/* Skip - requires GC rooting fixes in JS_JSONStringify */
JSGCRef arr_ref;
JS_PushGCRef(ctx, &arr_ref);
arr_ref.val = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 10));
JS_ArrayPush(ctx, &arr_ref.val, JS_NewString(ctx, "two"));
JS_ArrayPush(ctx, &arr_ref.val, JS_NewBool(ctx, 0));
JSValue str = JS_JSONStringify(ctx, arr_ref.val, JS_NULL, JS_NULL, 0);
JS_PopGCRef(ctx, &arr_ref);
ASSERT(!JS_IsException(str));
ASSERT(JS_IsText(str));
const char *s = JS_ToCString(ctx, str);
ASSERT(s != NULL);
ASSERT(strstr(s, "10") != NULL);
ASSERT(strstr(s, "\"two\"") != NULL);
ASSERT(strstr(s, "false") != NULL);
JS_FreeCString(ctx, s);
return 1;
}
TEST(json_encode_large_object) {
/* Stress test: build object with many properties, stringify with pretty print.
Under FORCE_GC_AT_MALLOC this will trigger GC on every allocation,
exposing any GC rooting bugs in the JSON encoder. */
JSGCRef obj_ref, str_ref;
JS_PushGCRef(ctx, &obj_ref);
JS_PushGCRef(ctx, &str_ref);
obj_ref.val = JS_NewObject(ctx);
char key[32], val[64];
for (int i = 0; i < 50; i++) {
snprintf(key, sizeof(key), "key_%d", i);
snprintf(val, sizeof(val), "value_%d_with_some_padding_text", i);
JS_SetPropertyStr(ctx, obj_ref.val, key, JS_NewString(ctx, val));
}
JSValue space = JS_NewInt32(ctx, 2);
str_ref.val = JS_JSONStringify(ctx, obj_ref.val, JS_NULL, space, 1);
ASSERT(!JS_IsException(str_ref.val));
ASSERT(JS_IsText(str_ref.val));
const char *s = JS_ToCString(ctx, str_ref.val);
ASSERT(s != NULL);
ASSERT(strstr(s, "\"key_0\"") != NULL);
ASSERT(strstr(s, "\"key_49\"") != NULL);
JS_FreeCString(ctx, s);
JS_PopGCRef(ctx, &str_ref);
JS_PopGCRef(ctx, &obj_ref);
return 1;
}
TEST(json_encode_nested) {
/* Nested objects stress test */
JSGCRef outer_ref, inner_ref, str_ref;
JS_PushGCRef(ctx, &outer_ref);
JS_PushGCRef(ctx, &inner_ref);
JS_PushGCRef(ctx, &str_ref);
outer_ref.val = JS_NewObject(ctx);
char key[32], val[64];
for (int i = 0; i < 20; i++) {
inner_ref.val = JS_NewObject(ctx);
for (int j = 0; j < 10; j++) {
snprintf(key, sizeof(key), "f%d", j);
snprintf(val, sizeof(val), "v_%d_%d", i, j);
JS_SetPropertyStr(ctx, inner_ref.val, key, JS_NewString(ctx, val));
}
snprintf(key, sizeof(key), "obj_%d", i);
JS_SetPropertyStr(ctx, outer_ref.val, key, inner_ref.val);
}
JSValue space = JS_NewInt32(ctx, 2);
str_ref.val = JS_JSONStringify(ctx, outer_ref.val, JS_NULL, space, 1);
ASSERT(!JS_IsException(str_ref.val));
ASSERT(JS_IsText(str_ref.val));
const char *s = JS_ToCString(ctx, str_ref.val);
ASSERT(s != NULL);
ASSERT(strstr(s, "\"obj_0\"") != NULL);
ASSERT(strstr(s, "\"f0\"") != NULL);
JS_FreeCString(ctx, s);
JS_PopGCRef(ctx, &str_ref);
JS_PopGCRef(ctx, &inner_ref);
JS_PopGCRef(ctx, &outer_ref);
return 1;
}
TEST(json_circular_reference) {
/* Circular reference should throw, not infinite recurse */
JSGCRef obj_ref;
JS_PushGCRef(ctx, &obj_ref);
obj_ref.val = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj_ref.val, "name", JS_NewString(ctx, "root"));
/* Create circular reference: obj.self = obj */
JS_SetPropertyStr(ctx, obj_ref.val, "self", obj_ref.val);
JSValue str = JS_JSONStringify(ctx, obj_ref.val, JS_NULL, JS_NULL, 0);
JS_PopGCRef(ctx, &obj_ref);
/* Should be an exception (circular reference), not a crash */
ASSERT(JS_IsException(str));
return 1;
}
@@ -2196,6 +2305,9 @@ int run_c_test_suite(JSContext *ctx)
RUN_TEST(json_encode_object);
RUN_TEST(json_decode_object);
RUN_TEST(json_roundtrip_array);
RUN_TEST(json_encode_large_object);
RUN_TEST(json_encode_nested);
RUN_TEST(json_circular_reference);
printf("\nSerialization - NOTA:\n");
RUN_TEST(nota_encode_int);