Merge branch 'master' into fix_aot
This commit is contained in:
@@ -29,6 +29,7 @@ static int run_test_suite(size_t heap_size);
|
||||
cell_rt *root_cell = NULL;
|
||||
static char *shop_path = NULL;
|
||||
static char *core_path = NULL;
|
||||
static int native_mode = 0;
|
||||
static JSRuntime *g_runtime = NULL;
|
||||
|
||||
// Compute blake2b hash of data and return hex string (caller must free)
|
||||
@@ -434,6 +435,7 @@ static void print_usage(const char *prog)
|
||||
printf(" --core <path> Set core path directly (overrides CELL_CORE)\n");
|
||||
printf(" --shop <path> Set shop path (overrides CELL_SHOP)\n");
|
||||
printf(" --dev Dev mode (shop=.cell, core=.)\n");
|
||||
printf(" --native Use AOT native code instead of bytecode\n");
|
||||
printf(" --heap <size> Initial heap size (e.g. 256MB, 1GB)\n");
|
||||
printf(" --test [heap_size] Run C test suite\n");
|
||||
printf(" -h, --help Show this help message\n");
|
||||
@@ -510,6 +512,9 @@ int cell_init(int argc, char **argv)
|
||||
if (lstat(".cell/packages/core", &lst) != 0)
|
||||
symlink("../..", ".cell/packages/core");
|
||||
arg_start++;
|
||||
} else if (strcmp(argv[arg_start], "--native") == 0) {
|
||||
native_mode = 1;
|
||||
arg_start++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -648,7 +653,16 @@ int cell_init(int argc, char **argv)
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
|
||||
tmp = js_core_json_use(ctx);
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "json", tmp);
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "init", JS_NULL);
|
||||
if (native_mode) {
|
||||
JSGCRef init_ref;
|
||||
JS_AddGCRef(ctx, &init_ref);
|
||||
init_ref.val = JS_NewObject(ctx);
|
||||
JS_SetPropertyStr(ctx, init_ref.val, "native_mode", JS_NewBool(ctx, 1));
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "init", init_ref.val);
|
||||
JS_DeleteGCRef(ctx, &init_ref);
|
||||
} else {
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "init", JS_NULL);
|
||||
}
|
||||
JSGCRef args_ref;
|
||||
JS_AddGCRef(ctx, &args_ref);
|
||||
args_ref.val = JS_NewArray(ctx);
|
||||
|
||||
@@ -5749,6 +5749,18 @@ exception:
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
|
||||
/* Check if val is already on the visited stack (circular reference detection).
|
||||
Uses identity comparison (===) since we're checking for the same object. */
|
||||
static BOOL json_stack_has (JSContext *ctx, JSValue stack, JSValue val) {
|
||||
if (!JS_IsArray (stack)) return FALSE;
|
||||
JSArray *arr = JS_VALUE_GET_ARRAY (stack);
|
||||
for (word_t i = 0; i < arr->len; i++) {
|
||||
if (JS_StrictEq (ctx, arr->values[i], val))
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue holder, JSValue val, JSValue indent) {
|
||||
JSValue v;
|
||||
int64_t i, len;
|
||||
@@ -5784,9 +5796,7 @@ static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue ho
|
||||
|
||||
if (mist_is_gc_object (
|
||||
val_ref.val)) { /* includes arrays (OBJ_ARRAY) since they have JS_TAG_PTR */
|
||||
v = js_array_includes (ctx, jsc->stack, 1, &val_ref.val);
|
||||
if (JS_IsException (v)) goto exception;
|
||||
if (JS_ToBool (ctx, v)) {
|
||||
if (json_stack_has (ctx, jsc->stack, val_ref.val)) {
|
||||
JS_ThrowTypeError (ctx, "circular reference");
|
||||
goto exception;
|
||||
}
|
||||
@@ -5801,8 +5811,7 @@ static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue ho
|
||||
sep_ref.val = jsc->empty;
|
||||
sep1_ref.val = jsc->empty;
|
||||
}
|
||||
v = js_cell_push (ctx, jsc->stack, 1, &val_ref.val);
|
||||
if (check_exception_free (ctx, v)) goto exception;
|
||||
if (JS_ArrayPush (ctx, &jsc->stack, val_ref.val) < 0) goto exception;
|
||||
ret = JS_IsArray (val_ref.val);
|
||||
if (ret < 0) goto exception;
|
||||
if (ret) {
|
||||
@@ -5890,8 +5899,8 @@ static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue ho
|
||||
}
|
||||
JSC_B_PUTC (jsc, '}');
|
||||
}
|
||||
if (check_exception_free (ctx, js_cell_pop (ctx, jsc->stack, 0, NULL)))
|
||||
goto exception;
|
||||
v = JS_ArrayPop (ctx, jsc->stack);
|
||||
if (JS_IsException (v)) goto exception;
|
||||
goto done;
|
||||
}
|
||||
switch (JS_VALUE_GET_NORM_TAG (val_ref.val)) {
|
||||
|
||||
116
source/suite.c
116
source/suite.c
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user