diff --git a/CLAUDE.md b/CLAUDE.md index fae50672..910ec6e7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -103,6 +103,7 @@ var v = a[] // pop: v is 3, a is [1, 2] - Most files don't have headers; files in a package are not shared between packages - No undefined in C API: use `JS_IsNull` and `JS_NULL` only - A C file with correct macros (`CELL_USE_FUNCS` etc) is loaded as a module by its name (e.g., `png.c` in a package → `use('/png')`) +- Use `JS_FRAME`/`JS_ROOT`/`JS_RETURN` macros for any C function that allocates multiple heap objects. Any `JS_New*`/`JS_SetProperty*` call can trigger GC. ## Project Layout diff --git a/archive/miniz.c b/archive/miniz.c index 4a8281d6..dced2a1c 100644 --- a/archive/miniz.c +++ b/archive/miniz.c @@ -381,19 +381,21 @@ static const JSCFunctionListEntry js_reader_funcs[] = { JSValue js_miniz_use(JSContext *js) { + JS_FRAME(js); + JS_NewClassID(&js_reader_class_id); JS_NewClass(js, js_reader_class_id, &js_reader_class); - JSValue reader_proto = JS_NewObject(js); - JS_SetPropertyFunctionList(js, reader_proto, js_reader_funcs, sizeof(js_reader_funcs) / sizeof(JSCFunctionListEntry)); - JS_SetClassProto(js, js_reader_class_id, reader_proto); + JS_ROOT(reader_proto, JS_NewObject(js)); + JS_SetPropertyFunctionList(js, reader_proto.val, js_reader_funcs, sizeof(js_reader_funcs) / sizeof(JSCFunctionListEntry)); + JS_SetClassProto(js, js_reader_class_id, reader_proto.val); JS_NewClassID(&js_writer_class_id); JS_NewClass(js, js_writer_class_id, &js_writer_class); - JSValue writer_proto = JS_NewObject(js); - JS_SetPropertyFunctionList(js, writer_proto, js_writer_funcs, sizeof(js_writer_funcs) / sizeof(JSCFunctionListEntry)); - JS_SetClassProto(js, js_writer_class_id, writer_proto); - - JSValue export = JS_NewObject(js); - JS_SetPropertyFunctionList(js, export, js_miniz_funcs, sizeof(js_miniz_funcs)/sizeof(JSCFunctionListEntry)); - return export; + JS_ROOT(writer_proto, JS_NewObject(js)); + JS_SetPropertyFunctionList(js, writer_proto.val, js_writer_funcs, sizeof(js_writer_funcs) / sizeof(JSCFunctionListEntry)); + JS_SetClassProto(js, js_writer_class_id, writer_proto.val); + + JS_ROOT(export, JS_NewObject(js)); + JS_SetPropertyFunctionList(js, export.val, js_miniz_funcs, sizeof(js_miniz_funcs)/sizeof(JSCFunctionListEntry)); + JS_RETURN(export.val); } diff --git a/crypto.c b/crypto.c index 0c79b2a0..104aca0f 100644 --- a/crypto.c +++ b/crypto.c @@ -240,7 +240,8 @@ static const JSCFunctionListEntry js_crypto_funcs[] = { JSValue js_crypto_use(JSContext *js) { - JSValue obj = JS_NewObject(js); - JS_SetPropertyFunctionList(js, obj, js_crypto_funcs, sizeof(js_crypto_funcs)/sizeof(js_crypto_funcs[0])); - return obj; + JS_FRAME(js); + JS_ROOT(mod, JS_NewObject(js)); + JS_SetPropertyFunctionList(js, mod.val, js_crypto_funcs, sizeof(js_crypto_funcs)/sizeof(js_crypto_funcs[0])); + JS_RETURN(mod.val); } diff --git a/debug/debug.c b/debug/debug.c index 2679ad57..fdc3ea1e 100644 --- a/debug/debug.c +++ b/debug/debug.c @@ -22,7 +22,8 @@ static const JSCFunctionListEntry js_debug_funcs[] = { }; JSValue js_debug_use(JSContext *js) { - JSValue mod = JS_NewObject(js); - JS_SetPropertyFunctionList(js,mod,js_debug_funcs,countof(js_debug_funcs)); - return mod; + JS_FRAME(js); + JS_ROOT(mod, JS_NewObject(js)); + JS_SetPropertyFunctionList(js, mod.val, js_debug_funcs, countof(js_debug_funcs)); + JS_RETURN(mod.val); } diff --git a/debug/js.c b/debug/js.c index e2d4d2f7..fa2a8ec1 100644 --- a/debug/js.c +++ b/debug/js.c @@ -21,7 +21,8 @@ static const JSCFunctionListEntry js_js_funcs[] = { }; JSValue js_js_use(JSContext *js) { - JSValue mod = JS_NewObject(js); - JS_SetPropertyFunctionList(js,mod,js_js_funcs,countof(js_js_funcs)); - return mod; + JS_FRAME(js); + JS_ROOT(mod, JS_NewObject(js)); + JS_SetPropertyFunctionList(js, mod.val, js_js_funcs, countof(js_js_funcs)); + JS_RETURN(mod.val); } diff --git a/fd.c b/fd.c index 6044f933..b06ccf5b 100644 --- a/fd.c +++ b/fd.c @@ -412,117 +412,117 @@ JSC_CCALL(fd_close, JSC_CCALL(fd_fstat, int fd = js2fd(js, argv[0]); if (fd < 0) return JS_EXCEPTION; - + struct stat st; if (fstat(fd, &st) != 0) return JS_ThrowInternalError(js, "fstat failed: %s", strerror(errno)); - JSValue obj = JS_NewObject(js); - JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size)); - JS_SetPropertyStr(js, obj, "mode", JS_NewInt32(js, st.st_mode)); - JS_SetPropertyStr(js, obj, "uid", JS_NewInt32(js, st.st_uid)); - JS_SetPropertyStr(js, obj, "gid", JS_NewInt32(js, st.st_gid)); - JS_SetPropertyStr(js, obj, "atime", JS_NewInt64(js, st.st_atime)); - JS_SetPropertyStr(js, obj, "mtime", JS_NewInt64(js, st.st_mtime)); - JS_SetPropertyStr(js, obj, "ctime", JS_NewInt64(js, st.st_ctime)); - JS_SetPropertyStr(js, obj, "nlink", JS_NewInt32(js, st.st_nlink)); - JS_SetPropertyStr(js, obj, "ino", JS_NewInt64(js, st.st_ino)); - JS_SetPropertyStr(js, obj, "dev", JS_NewInt32(js, st.st_dev)); - JS_SetPropertyStr(js, obj, "rdev", JS_NewInt32(js, st.st_rdev)); + JS_FRAME(js); + JS_ROOT(obj, JS_NewObject(js)); + JS_SetPropertyStr(js, obj.val, "size", JS_NewInt64(js, st.st_size)); + JS_SetPropertyStr(js, obj.val, "mode", JS_NewInt32(js, st.st_mode)); + JS_SetPropertyStr(js, obj.val, "uid", JS_NewInt32(js, st.st_uid)); + JS_SetPropertyStr(js, obj.val, "gid", JS_NewInt32(js, st.st_gid)); + JS_SetPropertyStr(js, obj.val, "atime", JS_NewInt64(js, st.st_atime)); + JS_SetPropertyStr(js, obj.val, "mtime", JS_NewInt64(js, st.st_mtime)); + JS_SetPropertyStr(js, obj.val, "ctime", JS_NewInt64(js, st.st_ctime)); + JS_SetPropertyStr(js, obj.val, "nlink", JS_NewInt32(js, st.st_nlink)); + JS_SetPropertyStr(js, obj.val, "ino", JS_NewInt64(js, st.st_ino)); + JS_SetPropertyStr(js, obj.val, "dev", JS_NewInt32(js, st.st_dev)); + JS_SetPropertyStr(js, obj.val, "rdev", JS_NewInt32(js, st.st_rdev)); #ifndef _WIN32 - JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, st.st_blksize)); - JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_blocks)); + JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, st.st_blksize)); + JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_blocks)); #else - JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, 4096)); - JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_size / 512)); + JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, 4096)); + JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_size / 512)); #endif - - // Add boolean properties for file type - JS_SetPropertyStr(js, obj, "isFile", JS_NewBool(js, S_ISREG(st.st_mode))); - JS_SetPropertyStr(js, obj, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode))); - JS_SetPropertyStr(js, obj, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode))); - JS_SetPropertyStr(js, obj, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode))); - JS_SetPropertyStr(js, obj, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode))); - JS_SetPropertyStr(js, obj, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode))); - JS_SetPropertyStr(js, obj, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode))); - - return obj; + JS_SetPropertyStr(js, obj.val, "isFile", JS_NewBool(js, S_ISREG(st.st_mode))); + JS_SetPropertyStr(js, obj.val, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode))); + JS_SetPropertyStr(js, obj.val, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode))); + JS_SetPropertyStr(js, obj.val, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode))); + JS_SetPropertyStr(js, obj.val, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode))); + JS_SetPropertyStr(js, obj.val, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode))); + JS_SetPropertyStr(js, obj.val, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode))); + JS_RETURN(obj.val); ) JSC_CCALL(fd_stat, const char *path = JS_ToCString(js, argv[0]); if (!path) return JS_EXCEPTION; - + struct stat st; if (stat(path, &st) != 0) { JS_FreeCString(js, path); return JS_NewObject(js); } - JSValue obj = JS_NewObject(js); - JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size)); - JS_SetPropertyStr(js, obj, "mode", JS_NewInt32(js, st.st_mode)); - JS_SetPropertyStr(js, obj, "uid", JS_NewInt32(js, st.st_uid)); - JS_SetPropertyStr(js, obj, "gid", JS_NewInt32(js, st.st_gid)); - JS_SetPropertyStr(js, obj, "atime", JS_NewInt64(js, st.st_atime)); - JS_SetPropertyStr(js, obj, "mtime", JS_NewInt64(js, st.st_mtime)); - JS_SetPropertyStr(js, obj, "ctime", JS_NewInt64(js, st.st_ctime)); - JS_SetPropertyStr(js, obj, "nlink", JS_NewInt32(js, st.st_nlink)); - JS_SetPropertyStr(js, obj, "ino", JS_NewInt64(js, st.st_ino)); - JS_SetPropertyStr(js, obj, "dev", JS_NewInt32(js, st.st_dev)); - JS_SetPropertyStr(js, obj, "rdev", JS_NewInt32(js, st.st_rdev)); + JS_FRAME(js); + JS_ROOT(obj, JS_NewObject(js)); + JS_SetPropertyStr(js, obj.val, "size", JS_NewInt64(js, st.st_size)); + JS_SetPropertyStr(js, obj.val, "mode", JS_NewInt32(js, st.st_mode)); + JS_SetPropertyStr(js, obj.val, "uid", JS_NewInt32(js, st.st_uid)); + JS_SetPropertyStr(js, obj.val, "gid", JS_NewInt32(js, st.st_gid)); + JS_SetPropertyStr(js, obj.val, "atime", JS_NewInt64(js, st.st_atime)); + JS_SetPropertyStr(js, obj.val, "mtime", JS_NewInt64(js, st.st_mtime)); + JS_SetPropertyStr(js, obj.val, "ctime", JS_NewInt64(js, st.st_ctime)); + JS_SetPropertyStr(js, obj.val, "nlink", JS_NewInt32(js, st.st_nlink)); + JS_SetPropertyStr(js, obj.val, "ino", JS_NewInt64(js, st.st_ino)); + JS_SetPropertyStr(js, obj.val, "dev", JS_NewInt32(js, st.st_dev)); + JS_SetPropertyStr(js, obj.val, "rdev", JS_NewInt32(js, st.st_rdev)); #ifndef _WIN32 - JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, st.st_blksize)); - JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_blocks)); + JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, st.st_blksize)); + JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_blocks)); #else - JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, 4096)); - JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_size / 512)); + JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, 4096)); + JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_size / 512)); #endif - - // Add boolean properties for file type - JS_SetPropertyStr(js, obj, "isFile", JS_NewBool(js, S_ISREG(st.st_mode))); - JS_SetPropertyStr(js, obj, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode))); - JS_SetPropertyStr(js, obj, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode))); - JS_SetPropertyStr(js, obj, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode))); - JS_SetPropertyStr(js, obj, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode))); - JS_SetPropertyStr(js, obj, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode))); - JS_SetPropertyStr(js, obj, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode))); - + JS_SetPropertyStr(js, obj.val, "isFile", JS_NewBool(js, S_ISREG(st.st_mode))); + JS_SetPropertyStr(js, obj.val, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode))); + JS_SetPropertyStr(js, obj.val, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode))); + JS_SetPropertyStr(js, obj.val, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode))); + JS_SetPropertyStr(js, obj.val, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode))); + JS_SetPropertyStr(js, obj.val, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode))); + JS_SetPropertyStr(js, obj.val, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode))); JS_FreeCString(js, path); - return obj; + JS_RETURN(obj.val); ) JSC_SCALL(fd_readdir, + JS_FRAME(js); #ifdef _WIN32 WIN32_FIND_DATA ffd; char path[PATH_MAX]; snprintf(path, sizeof(path), "%s\\*", str); HANDLE hFind = FindFirstFile(path, &ffd); if (hFind == INVALID_HANDLE_VALUE) { - ret = JS_ThrowInternalError(js, "FindFirstFile failed for %s", path); + ret = JS_ThrowInternalError(js, "FindFirstFile failed for %s", path); } else { - ret = JS_NewArray(js); - do { - if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue; - JS_ArrayPush(js, &ret, JS_NewString(js, ffd.cFileName)); - } while (FindNextFile(hFind, &ffd) != 0); - FindClose(hFind); + JS_ROOT(arr, JS_NewArray(js)); + do { + if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue; + JS_ArrayPush(js, &arr.val, JS_NewString(js, ffd.cFileName)); + } while (FindNextFile(hFind, &ffd) != 0); + FindClose(hFind); + ret = arr.val; } #else DIR *d; struct dirent *dir; d = opendir(str); if (d) { - ret = JS_NewArray(js); + JS_ROOT(arr, JS_NewArray(js)); while ((dir = readdir(d)) != NULL) { if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue; - JS_ArrayPush(js, &ret, JS_NewString(js, dir->d_name)); + JS_ArrayPush(js, &arr.val, JS_NewString(js, dir->d_name)); } closedir(d); + ret = arr.val; } else { ret = JS_ThrowInternalError(js, "opendir failed for %s: %s", str, strerror(errno)); } #endif + JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); ) JSC_CCALL(fd_is_file, @@ -585,9 +585,9 @@ JSC_CCALL(fd_slurpwrite, ) // Helper function for recursive enumeration -static void visit_directory(JSContext *js, JSValue results, int *result_count, const char *curr_path, const char *rel_prefix, int recurse) { +static void visit_directory(JSContext *js, JSValue *results, int *result_count, const char *curr_path, const char *rel_prefix, int recurse) { if (!curr_path) return; - + #ifdef _WIN32 WIN32_FIND_DATA ffd; char search_path[PATH_MAX]; @@ -602,7 +602,7 @@ static void visit_directory(JSContext *js, JSValue results, int *result_count, c } else { strcpy(item_rel, ffd.cFileName); } - JS_SetPropertyNumber(js, results, (*result_count)++, JS_NewString(js, item_rel)); + JS_SetPropertyNumber(js, *results, (*result_count)++, JS_NewString(js, item_rel)); if (recurse) { struct stat st; @@ -627,7 +627,7 @@ static void visit_directory(JSContext *js, JSValue results, int *result_count, c } else { strcpy(item_rel, dir->d_name); } - JS_SetPropertyNumber(js, results, (*result_count)++, JS_NewString(js, item_rel)); + JS_SetPropertyNumber(js, *results, (*result_count)++, JS_NewString(js, item_rel)); if (recurse) { struct stat st; @@ -651,14 +651,16 @@ JSC_SCALL(fd_enumerate, if (argc > 1) recurse = JS_ToBool(js, argv[1]); - JSValue results = JS_NewArray(js); + JS_FRAME(js); + JS_ROOT(arr, JS_NewArray(js)); int result_count = 0; struct stat st; if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) - visit_directory(js, results, &result_count, path, "", recurse); + visit_directory(js, &arr.val, &result_count, path, "", recurse); - ret = results; + ret = arr.val; + JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); ) JSC_CCALL(fd_realpath, @@ -753,7 +755,8 @@ static const JSCFunctionListEntry js_fd_funcs[] = { }; JSValue js_fd_use(JSContext *js) { - JSValue mod = JS_NewObject(js); - JS_SetPropertyFunctionList(js, mod, js_fd_funcs, countof(js_fd_funcs)); - return mod; + JS_FRAME(js); + JS_ROOT(mod, JS_NewObject(js)); + JS_SetPropertyFunctionList(js, mod.val, js_fd_funcs, countof(js_fd_funcs)); + JS_RETURN(mod.val); } \ No newline at end of file diff --git a/fit.c b/fit.c index 845bf1c3..9d123f9f 100644 --- a/fit.c +++ b/fit.c @@ -250,7 +250,8 @@ static const JSCFunctionListEntry js_fit_funcs[] = { JSValue js_fit_use(JSContext *js) { - JSValue mod = JS_NewObject(js); - JS_SetPropertyFunctionList(js, mod, js_fit_funcs, countof(js_fit_funcs)); - return mod; + JS_FRAME(js); + JS_ROOT(mod, JS_NewObject(js)); + JS_SetPropertyFunctionList(js, mod.val, js_fit_funcs, countof(js_fit_funcs)); + JS_RETURN(mod.val); } \ No newline at end of file diff --git a/internal/kim.c b/internal/kim.c index b2c53111..85ecc6b4 100644 --- a/internal/kim.c +++ b/internal/kim.c @@ -75,7 +75,8 @@ static const JSCFunctionListEntry js_kim_funcs[] = { JSValue js_kim_use(JSContext *js) { - JSValue mod = JS_NewObject(js); - JS_SetPropertyFunctionList(js, mod, js_kim_funcs, countof(js_kim_funcs)); - return mod; + JS_FRAME(js); + JS_ROOT(mod, JS_NewObject(js)); + JS_SetPropertyFunctionList(js, mod.val, js_kim_funcs, countof(js_kim_funcs)); + JS_RETURN(mod.val); } \ No newline at end of file diff --git a/internal/os.c b/internal/os.c index fa07324c..336d8ae8 100644 --- a/internal/os.c +++ b/internal/os.c @@ -277,29 +277,31 @@ JSC_CCALL(os_mallinfo, ) static JSValue js_os_rusage(JSContext *js, JSValue self, int argc, JSValue *argv) { - JSValue ret = JS_NULL; - ret = JS_NewObject(js); + JS_FRAME(js); + JS_ROOT(ret, JS_NewObject(js)); #if defined(__linux__) || defined(__APPLE__) struct rusage jsmem; getrusage(RUSAGE_SELF, &jsmem); - JSJMEMRET(ru_maxrss); - JSJMEMRET(ru_ixrss); - JSJMEMRET(ru_idrss); - JSJMEMRET(ru_isrss); - JSJMEMRET(ru_minflt); - JSJMEMRET(ru_majflt); - JSJMEMRET(ru_nswap); - JSJMEMRET(ru_inblock); - JSJMEMRET(ru_oublock); - JSJMEMRET(ru_msgsnd); - JSJMEMRET(ru_msgrcv); - JSJMEMRET(ru_nsignals); - JSJMEMRET(ru_nvcsw); - JSJMEMRET(ru_nivcsw); +#define JSJMEMRET_GC(FIELD) JS_SetPropertyStr(js, ret.val, #FIELD, number2js(js, jsmem.FIELD)); + JSJMEMRET_GC(ru_maxrss); + JSJMEMRET_GC(ru_ixrss); + JSJMEMRET_GC(ru_idrss); + JSJMEMRET_GC(ru_isrss); + JSJMEMRET_GC(ru_minflt); + JSJMEMRET_GC(ru_majflt); + JSJMEMRET_GC(ru_nswap); + JSJMEMRET_GC(ru_inblock); + JSJMEMRET_GC(ru_oublock); + JSJMEMRET_GC(ru_msgsnd); + JSJMEMRET_GC(ru_msgrcv); + JSJMEMRET_GC(ru_nsignals); + JSJMEMRET_GC(ru_nvcsw); + JSJMEMRET_GC(ru_nivcsw); +#undef JSJMEMRET_GC #endif - return ret; + JS_RETURN(ret.val); } JSC_SCALL(os_system, @@ -628,7 +630,8 @@ JSValue js_os_use(JSContext *js) { JS_NewClassID(&js_dylib_class_id); JS_NewClass(js, js_dylib_class_id, &js_dylib_class); - JSValue mod = JS_NewObject(js); - JS_SetPropertyFunctionList(js,mod,js_os_funcs,countof(js_os_funcs)); - return mod; + JS_FRAME(js); + JS_ROOT(mod, JS_NewObject(js)); + JS_SetPropertyFunctionList(js, mod.val, js_os_funcs, countof(js_os_funcs)); + JS_RETURN(mod.val); } diff --git a/meson.build b/meson.build index 85fcbe5a..6428f181 100644 --- a/meson.build +++ b/meson.build @@ -22,6 +22,10 @@ if get_option('validate_gc') add_project_arguments('-DVALIDATE_GC', language: 'c') endif +if get_option('force_gc') + add_project_arguments('-DFORCE_GC_AT_MALLOC', language: 'c') +endif + deps = [] if host_machine.system() == 'darwin' diff --git a/meson.options b/meson.options index f110d192..c3a5bf7c 100644 --- a/meson.options +++ b/meson.options @@ -1,2 +1,4 @@ option('validate_gc', type: 'boolean', value: false, description: 'Enable GC validation checks (stale pointer detection, pre-GC frame validation)') +option('force_gc', type: 'boolean', value: false, + description: 'Force GC on every allocation (makes stale pointer bugs deterministic)') diff --git a/net/enet.c b/net/enet.c index 8b4bac59..509ee80f 100644 --- a/net/enet.c +++ b/net/enet.c @@ -570,19 +570,21 @@ static const JSCFunctionListEntry js_enet_peer_funcs[] = { JSValue js_enet_use(JSContext *ctx) { + JS_FRAME(ctx); + JS_NewClassID(&enet_host_id); JS_NewClass(ctx, enet_host_id, &enet_host); - JSValue host_proto = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, host_proto, js_enet_host_funcs, countof(js_enet_host_funcs)); - JS_SetClassProto(ctx, enet_host_id, host_proto); + JS_ROOT(host_proto, JS_NewObject(ctx)); + JS_SetPropertyFunctionList(ctx, host_proto.val, js_enet_host_funcs, countof(js_enet_host_funcs)); + JS_SetClassProto(ctx, enet_host_id, host_proto.val); JS_NewClassID(&enet_peer_class_id); JS_NewClass(ctx, enet_peer_class_id, &enet_peer_class); - JSValue peer_proto = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, peer_proto, js_enet_peer_funcs, countof(js_enet_peer_funcs)); - JS_SetClassProto(ctx, enet_peer_class_id, peer_proto); + JS_ROOT(peer_proto, JS_NewObject(ctx)); + JS_SetPropertyFunctionList(ctx, peer_proto.val, js_enet_peer_funcs, countof(js_enet_peer_funcs)); + JS_SetClassProto(ctx, enet_peer_class_id, peer_proto.val); - JSValue export_obj = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, export_obj, js_enet_funcs, countof(js_enet_funcs)); - return export_obj; + JS_ROOT(export_obj, JS_NewObject(ctx)); + JS_SetPropertyFunctionList(ctx, export_obj.val, js_enet_funcs, countof(js_enet_funcs)); + JS_RETURN(export_obj.val); } diff --git a/net/http.c b/net/http.c index fb3b8cd4..5fcccd0e 100644 --- a/net/http.c +++ b/net/http.c @@ -319,9 +319,10 @@ static const JSCFunctionListEntry js_http_funcs[] = { }; JSValue js_http_use(JSContext *js) { + JS_FRAME(js); par_easycurl_init(0); // Initialize platform HTTP backend - JSValue obj = JS_NewObject(js); - JS_SetPropertyFunctionList(js, obj, js_http_funcs, + JS_ROOT(mod, JS_NewObject(js)); + JS_SetPropertyFunctionList(js, mod.val, js_http_funcs, sizeof(js_http_funcs)/sizeof(js_http_funcs[0])); - return obj; + JS_RETURN(mod.val); } diff --git a/net/socket.c b/net/socket.c index bde1e156..d5bead4b 100644 --- a/net/socket.c +++ b/net/socket.c @@ -595,26 +595,27 @@ static const JSCFunctionListEntry js_socket_funcs[] = { }; JSValue js_socket_use(JSContext *js) { - JSValue mod = JS_NewObject(js); - JS_SetPropertyFunctionList(js, mod, js_socket_funcs, countof(js_socket_funcs)); - + JS_FRAME(js); + JS_ROOT(mod, JS_NewObject(js)); + JS_SetPropertyFunctionList(js, mod.val, js_socket_funcs, countof(js_socket_funcs)); + // Add constants - JS_SetPropertyStr(js, mod, "AF_UNSPEC", JS_NewInt32(js, AF_UNSPEC)); - JS_SetPropertyStr(js, mod, "AF_INET", JS_NewInt32(js, AF_INET)); - JS_SetPropertyStr(js, mod, "AF_INET6", JS_NewInt32(js, AF_INET6)); - JS_SetPropertyStr(js, mod, "AF_UNIX", JS_NewInt32(js, AF_UNIX)); - - JS_SetPropertyStr(js, mod, "SOCK_STREAM", JS_NewInt32(js, SOCK_STREAM)); - JS_SetPropertyStr(js, mod, "SOCK_DGRAM", JS_NewInt32(js, SOCK_DGRAM)); - - JS_SetPropertyStr(js, mod, "AI_PASSIVE", JS_NewInt32(js, AI_PASSIVE)); - - JS_SetPropertyStr(js, mod, "SHUT_RD", JS_NewInt32(js, SHUT_RD)); - JS_SetPropertyStr(js, mod, "SHUT_WR", JS_NewInt32(js, SHUT_WR)); - JS_SetPropertyStr(js, mod, "SHUT_RDWR", JS_NewInt32(js, SHUT_RDWR)); - - JS_SetPropertyStr(js, mod, "SOL_SOCKET", JS_NewInt32(js, SOL_SOCKET)); - JS_SetPropertyStr(js, mod, "SO_REUSEADDR", JS_NewInt32(js, SO_REUSEADDR)); - - return mod; + JS_SetPropertyStr(js, mod.val, "AF_UNSPEC", JS_NewInt32(js, AF_UNSPEC)); + JS_SetPropertyStr(js, mod.val, "AF_INET", JS_NewInt32(js, AF_INET)); + JS_SetPropertyStr(js, mod.val, "AF_INET6", JS_NewInt32(js, AF_INET6)); + JS_SetPropertyStr(js, mod.val, "AF_UNIX", JS_NewInt32(js, AF_UNIX)); + + JS_SetPropertyStr(js, mod.val, "SOCK_STREAM", JS_NewInt32(js, SOCK_STREAM)); + JS_SetPropertyStr(js, mod.val, "SOCK_DGRAM", JS_NewInt32(js, SOCK_DGRAM)); + + JS_SetPropertyStr(js, mod.val, "AI_PASSIVE", JS_NewInt32(js, AI_PASSIVE)); + + JS_SetPropertyStr(js, mod.val, "SHUT_RD", JS_NewInt32(js, SHUT_RD)); + JS_SetPropertyStr(js, mod.val, "SHUT_WR", JS_NewInt32(js, SHUT_WR)); + JS_SetPropertyStr(js, mod.val, "SHUT_RDWR", JS_NewInt32(js, SHUT_RDWR)); + + JS_SetPropertyStr(js, mod.val, "SOL_SOCKET", JS_NewInt32(js, SOL_SOCKET)); + JS_SetPropertyStr(js, mod.val, "SO_REUSEADDR", JS_NewInt32(js, SO_REUSEADDR)); + + JS_RETURN(mod.val); } diff --git a/qop.c b/qop.c index 1516ad23..9fdc9a18 100644 --- a/qop.c +++ b/qop.c @@ -457,19 +457,21 @@ static const JSCFunctionListEntry js_qop_funcs[] = { }; JSValue js_qop_use(JSContext *js) { + JS_FRAME(js); + JS_NewClassID(&js_qop_archive_class_id); JS_NewClass(js, js_qop_archive_class_id, &js_qop_archive_class); - JSValue archive_proto = JS_NewObject(js); - JS_SetPropertyFunctionList(js, archive_proto, js_qop_archive_funcs, countof(js_qop_archive_funcs)); - JS_SetClassProto(js, js_qop_archive_class_id, archive_proto); + JS_ROOT(archive_proto, JS_NewObject(js)); + JS_SetPropertyFunctionList(js, archive_proto.val, js_qop_archive_funcs, countof(js_qop_archive_funcs)); + JS_SetClassProto(js, js_qop_archive_class_id, archive_proto.val); JS_NewClassID(&js_qop_writer_class_id); JS_NewClass(js, js_qop_writer_class_id, &js_qop_writer_class); - JSValue writer_proto = JS_NewObject(js); - JS_SetPropertyFunctionList(js, writer_proto, js_qop_writer_funcs, countof(js_qop_writer_funcs)); - JS_SetClassProto(js, js_qop_writer_class_id, writer_proto); + JS_ROOT(writer_proto, JS_NewObject(js)); + JS_SetPropertyFunctionList(js, writer_proto.val, js_qop_writer_funcs, countof(js_qop_writer_funcs)); + JS_SetClassProto(js, js_qop_writer_class_id, writer_proto.val); - JSValue mod = JS_NewObject(js); - JS_SetPropertyFunctionList(js, mod, js_qop_funcs, countof(js_qop_funcs)); - return mod; + JS_ROOT(mod, JS_NewObject(js)); + JS_SetPropertyFunctionList(js, mod.val, js_qop_funcs, countof(js_qop_funcs)); + JS_RETURN(mod.val); } \ No newline at end of file diff --git a/source/cell.c b/source/cell.c index 09f1398c..1a61eeb4 100644 --- a/source/cell.c +++ b/source/cell.c @@ -275,34 +275,47 @@ void script_startup(cell_rt *prt) } // Create hidden environment - JSValue hidden_env = JS_NewObject(js); - JS_SetPropertyStr(js, hidden_env, "os", js_os_use(js)); - JS_SetPropertyStr(js, hidden_env, "json", js_json_use(js)); - JS_SetPropertyStr(js, hidden_env, "nota", js_nota_use(js)); - JS_SetPropertyStr(js, hidden_env, "wota", js_wota_use(js)); + // Note: evaluate allocating calls into temporaries before passing to + // JS_SetPropertyStr, so env_ref.val is read AFTER GC may have moved it. + JSGCRef env_ref; + JS_AddGCRef(js, &env_ref); + env_ref.val = JS_NewObject(js); + JSValue tmp; + tmp = js_os_use(js); + JS_SetPropertyStr(js, env_ref.val, "os", tmp); + tmp = js_json_use(js); + JS_SetPropertyStr(js, env_ref.val, "json", tmp); + tmp = js_nota_use(js); + JS_SetPropertyStr(js, env_ref.val, "nota", tmp); + tmp = js_wota_use(js); + JS_SetPropertyStr(js, env_ref.val, "wota", tmp); crt->actor_sym_ref.val = JS_NewObject(js); - JS_SetPropertyStr(js, hidden_env, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val)); + JS_SetPropertyStr(js, env_ref.val, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val)); // Always set init (even if null) if (crt->init_wota) { - JS_SetPropertyStr(js, hidden_env, "init", wota2value(js, crt->init_wota)); + tmp = wota2value(js, crt->init_wota); + JS_SetPropertyStr(js, env_ref.val, "init", tmp); free(crt->init_wota); crt->init_wota = NULL; } else { - JS_SetPropertyStr(js, hidden_env, "init", JS_NULL); + JS_SetPropertyStr(js, env_ref.val, "init", JS_NULL); } // Set args to null for actor spawn (not CLI mode) - JS_SetPropertyStr(js, hidden_env, "args", JS_NULL); + JS_SetPropertyStr(js, env_ref.val, "args", JS_NULL); - if (core_path) - JS_SetPropertyStr(js, hidden_env, "core_path", JS_NewString(js, core_path)); - JS_SetPropertyStr(js, hidden_env, "shop_path", - shop_path ? JS_NewString(js, shop_path) : JS_NULL); + if (core_path) { + tmp = JS_NewString(js, core_path); + JS_SetPropertyStr(js, env_ref.val, "core_path", tmp); + } + tmp = shop_path ? JS_NewString(js, shop_path) : JS_NULL; + JS_SetPropertyStr(js, env_ref.val, "shop_path", tmp); // Stone the environment - hidden_env = JS_Stone(js, hidden_env); + JSValue hidden_env = JS_Stone(js, env_ref.val); + JS_DeleteGCRef(js, &env_ref); // Run from binary crt->state = ACTOR_RUNNING; @@ -504,23 +517,35 @@ int cell_init(int argc, char **argv) JS_FreeValue(ctx, js_blob_use(ctx)); - JSValue hidden_env = JS_NewObject(ctx); - JS_SetPropertyStr(ctx, hidden_env, "os", js_os_use(ctx)); - JS_SetPropertyStr(ctx, hidden_env, "core_path", JS_NewString(ctx, core_path)); - JS_SetPropertyStr(ctx, hidden_env, "shop_path", - shop_path ? JS_NewString(ctx, shop_path) : JS_NULL); - JS_SetPropertyStr(ctx, hidden_env, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val)); - JS_SetPropertyStr(ctx, hidden_env, "json", js_json_use(ctx)); - JS_SetPropertyStr(ctx, hidden_env, "nota", js_nota_use(ctx)); - JS_SetPropertyStr(ctx, hidden_env, "wota", js_wota_use(ctx)); - JS_SetPropertyStr(ctx, hidden_env, "init", JS_NULL); - JSValue args_arr = JS_NewArray(ctx); + JSGCRef env_ref; + JS_AddGCRef(ctx, &env_ref); + env_ref.val = JS_NewObject(ctx); + JSValue tmp; + tmp = js_os_use(ctx); + JS_SetPropertyStr(ctx, env_ref.val, "os", tmp); + tmp = JS_NewString(ctx, core_path); + JS_SetPropertyStr(ctx, env_ref.val, "core_path", tmp); + tmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL; + JS_SetPropertyStr(ctx, env_ref.val, "shop_path", tmp); + JS_SetPropertyStr(ctx, env_ref.val, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val)); + tmp = js_json_use(ctx); + JS_SetPropertyStr(ctx, env_ref.val, "json", tmp); + tmp = js_nota_use(ctx); + JS_SetPropertyStr(ctx, env_ref.val, "nota", tmp); + tmp = js_wota_use(ctx); + JS_SetPropertyStr(ctx, env_ref.val, "wota", tmp); + JS_SetPropertyStr(ctx, env_ref.val, "init", JS_NULL); + JSGCRef args_ref; + JS_AddGCRef(ctx, &args_ref); + args_ref.val = JS_NewArray(ctx); for (int i = arg_start; i < argc; i++) { JSValue str = JS_NewString(ctx, argv[i]); - JS_ArrayPush(ctx, &args_arr, str); + JS_ArrayPush(ctx, &args_ref.val, str); } - JS_SetPropertyStr(ctx, hidden_env, "args", args_arr); - hidden_env = JS_Stone(ctx, hidden_env); + JS_SetPropertyStr(ctx, env_ref.val, "args", args_ref.val); + JS_DeleteGCRef(ctx, &args_ref); + JSValue hidden_env = JS_Stone(ctx, env_ref.val); + JS_DeleteGCRef(ctx, &env_ref); JSValue result = JS_RunMachBin(ctx, (const uint8_t *)bin_data, bin_size, hidden_env); free(bin_data); diff --git a/source/cell.h b/source/cell.h index 3735e66a..a1eccad7 100644 --- a/source/cell.h +++ b/source/cell.h @@ -156,6 +156,43 @@ JS_SetClassProto(js, js_##TYPE##_id, TYPE##_proto); \ #define countof(x) (sizeof(x)/sizeof((x)[0])) +/* GC safety macros for C functions that allocate multiple heap objects. + Any allocation call (JS_NewObject, JS_SetPropertyStr, etc.) can trigger GC. + JS_ROOT style: explicit, use .val to access the rooted value. + JS_LOCAL style: transparent, GC updates the C local through a pointer. */ + +#define JS_FRAME(ctx) \ + JSContext *_js_ctx = (ctx); \ + JSGCRef *_js_gc_frame = JS_GetGCFrame(_js_ctx); \ + JSLocalRef *_js_local_frame = JS_GetLocalFrame(_js_ctx) + +#define JS_ROOT(name, init) \ + JSGCRef name; \ + JS_PushGCRef(_js_ctx, &name); \ + name.val = (init) + +#define JS_LOCAL(name, init) \ + JSValue name = (init); \ + JSLocalRef name##__lr; \ + name##__lr.ptr = &name; \ + JS_PushLocalRef(_js_ctx, &name##__lr) + +#define JS_RETURN(val) do { \ + JSValue _js_ret = (val); \ + JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); \ + return _js_ret; \ +} while (0) + +#define JS_RETURN_NULL() do { \ + JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); \ + return JS_NULL; \ +} while (0) + +#define JS_RETURN_EX() do { \ + JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); \ + return JS_EXCEPTION; \ +} while (0) + // Common macros for property access #define JS_GETPROP(JS, TARGET, VALUE, PROP, TYPE) {\ JSValue __##PROP##__v = JS_GetPropertyStr(JS,VALUE,#PROP); \ diff --git a/source/mach.c b/source/mach.c index 70d1f67d..4fd75d86 100644 --- a/source/mach.c +++ b/source/mach.c @@ -1127,6 +1127,17 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, result = frame->slots[a]; if (JS_IsNull(frame->caller)) goto done; { +#ifdef VALIDATE_GC + const char *callee_name = "?"; + const char *callee_file = "?"; + { + JSFunction *callee_fn = JS_VALUE_GET_FUNCTION(frame->function); + if (callee_fn->kind == JS_FUNC_KIND_REGISTER && callee_fn->u.reg.code) { + if (callee_fn->u.reg.code->name_cstr) callee_name = callee_fn->u.reg.code->name_cstr; + if (callee_fn->u.reg.code->filename_cstr) callee_file = callee_fn->u.reg.code->filename_cstr; + } + } +#endif JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); frame->caller = JS_NULL; frame = caller; @@ -1143,8 +1154,11 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, void *rp = JS_VALUE_GET_PTR(result); if ((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free) { if (!is_ct_ptr(ctx, rp)) - fprintf(stderr, "VALIDATE_GC: stale RETURN into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u\n", - ret_slot, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc); + fprintf(stderr, "VALIDATE_GC: stale RETURN into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u callee=%s (%s) caller=%s (%s)\n", + ret_slot, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc, + callee_name, callee_file, + code->name_cstr ? code->name_cstr : "?", + code->filename_cstr ? code->filename_cstr : "?"); } } #endif @@ -1617,9 +1631,15 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, if (JS_IsPtr(ret)) { void *rp = JS_VALUE_GET_PTR(ret); if ((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free) { - if (!is_ct_ptr(ctx, rp)) - fprintf(stderr, "VALIDATE_GC: stale INVOKE result into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u kind=%d\n", - b, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc - 1, fn->kind); + if (!is_ct_ptr(ctx, rp)) { + int magic = (fn->kind == JS_FUNC_KIND_C) ? fn->u.cfunc.magic : -1; + void *cfp = (fn->kind == JS_FUNC_KIND_C) ? (void *)fn->u.cfunc.c_function.generic : NULL; + fprintf(stderr, "VALIDATE_GC: stale INVOKE result into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u kind=%d magic=%d cfunc=%p caller=%s (%s)\n", + b, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc - 1, fn->kind, + magic, cfp, + code->name_cstr ? code->name_cstr : "?", + code->filename_cstr ? code->filename_cstr : "?"); + } } } #endif diff --git a/source/qjs_actor.c b/source/qjs_actor.c index bfcbf069..52cf26dc 100644 --- a/source/qjs_actor.c +++ b/source/qjs_actor.c @@ -156,7 +156,8 @@ static const JSCFunctionListEntry js_actor_funcs[] = { }; JSValue js_actor_use(JSContext *js) { - JSValue mod = JS_NewObject(js); - JS_SetPropertyFunctionList(js,mod,js_actor_funcs,countof(js_actor_funcs)); - return mod; + JS_FRAME(js); + JS_ROOT(mod, JS_NewObject(js)); + JS_SetPropertyFunctionList(js, mod.val, js_actor_funcs, countof(js_actor_funcs)); + JS_RETURN(mod.val); } \ No newline at end of file diff --git a/source/quickjs-internal.h b/source/quickjs-internal.h index fa6bc954..d8ad55be 100644 --- a/source/quickjs-internal.h +++ b/source/quickjs-internal.h @@ -1092,6 +1092,7 @@ struct JSContext { JSGCRef *top_gc_ref; /* used to reference temporary GC roots (stack top) */ JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */ + JSLocalRef *top_local_ref; /* for JS_LOCAL macro - GC updates C locals through pointers */ CCallRoot *c_call_root; /* stack of auto-rooted C call argv arrays */ int class_count; /* size of class_array and class_proto */ @@ -1185,7 +1186,15 @@ static int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size); /* Helper to check if a pointer is in constant text pool memory */ static inline int is_ct_ptr (JSContext *ctx, void *ptr) { - return (uint8_t *)ptr >= ctx->ct_base && (uint8_t *)ptr < ctx->ct_end; + uint8_t *p = (uint8_t *)ptr; + if (p >= ctx->ct_base && p < ctx->ct_end) return 1; + /* Also check overflow pages */ + CTPage *page = (CTPage *)ctx->ct_pages; + while (page) { + if (p >= page->data && p < page->data + page->size) return 1; + page = page->next; + } + return 0; } #ifdef HEAP_CHECK diff --git a/source/quickjs.h b/source/quickjs.h index 0f96d25a..3d09b1e4 100644 --- a/source/quickjs.h +++ b/source/quickjs.h @@ -146,10 +146,22 @@ typedef struct JSGCRef { struct JSGCRef *prev; } JSGCRef; +/* JSLocalRef - GC updates C locals through pointers (OCaml-style) */ +typedef struct JSLocalRef { + JSValue *ptr; + struct JSLocalRef *prev; +} JSLocalRef; + /* stack of JSGCRef */ JSValue *JS_PushGCRef(JSContext *ctx, JSGCRef *ref); JSValue JS_PopGCRef(JSContext *ctx, JSGCRef *ref); +/* JS_FRAME/JS_ROOT/JS_LOCAL helpers (for use from cell.h macros) */ +JSGCRef *JS_GetGCFrame(JSContext *ctx); +JSLocalRef *JS_GetLocalFrame(JSContext *ctx); +void JS_PushLocalRef(JSContext *ctx, JSLocalRef *ref); +void JS_RestoreFrame(JSContext *ctx, JSGCRef *gc_frame, JSLocalRef *local_frame); + #define JS_PUSH_VALUE(ctx, v) do { JS_PushGCRef(ctx, &v ## _ref); v ## _ref.val = v; } while (0) #define JS_POP_VALUE(ctx, v) v = JS_PopGCRef(ctx, &v ## _ref) diff --git a/source/runtime.c b/source/runtime.c index 847c3de5..8c1f3667 100644 --- a/source/runtime.c +++ b/source/runtime.c @@ -183,6 +183,25 @@ void JS_DeleteGCRef (JSContext *ctx, JSGCRef *ref) { } } +/* JS_FRAME/JS_ROOT/JS_LOCAL helper functions */ +JSGCRef *JS_GetGCFrame (JSContext *ctx) { + return ctx->top_gc_ref; +} + +JSLocalRef *JS_GetLocalFrame (JSContext *ctx) { + return ctx->top_local_ref; +} + +void JS_PushLocalRef (JSContext *ctx, JSLocalRef *ref) { + ref->prev = ctx->top_local_ref; + ctx->top_local_ref = ref; +} + +void JS_RestoreFrame (JSContext *ctx, JSGCRef *gc_frame, JSLocalRef *local_frame) { + ctx->top_gc_ref = gc_frame; + ctx->top_local_ref = local_frame; +} + void *ct_alloc (JSContext *ctx, size_t bytes, size_t align) { /* Align the request */ bytes = (bytes + align - 1) & ~(align - 1); @@ -1608,6 +1627,14 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) { ref->val = gc_copy_value (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end); } + /* Copy JS_LOCAL roots (update C locals through pointers) */ +#ifdef DUMP_GC_DETAIL + printf(" roots: top_local_ref\n"); fflush(stdout); +#endif + for (JSLocalRef *ref = ctx->top_local_ref; ref != NULL; ref = ref->prev) { + *ref->ptr = gc_copy_value (ctx, *ref->ptr, from_base, from_end, to_base, &to_free, to_end); + } + /* Copy JS_AddGCRef/JS_DeleteGCRef roots */ #ifdef DUMP_GC_DETAIL printf(" roots: last_gc_ref\n"); fflush(stdout); @@ -4383,6 +4410,10 @@ JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, ctx->trace_hook (ctx, JS_HOOK_CALL, &dbg, ctx->trace_data); } +#ifdef VALIDATE_GC + uint8_t *pre_heap_base = ctx->heap_base; +#endif + switch (cproto) { case JS_CFUNC_generic: ret_val = func.generic (ctx, this_obj, argc, arg_copy); @@ -4449,6 +4480,21 @@ JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, abort (); } +#ifdef VALIDATE_GC + if (ctx->heap_base != pre_heap_base && JS_IsPtr (ret_val)) { + void *rp = JS_VALUE_GET_PTR (ret_val); + if (!is_ct_ptr (ctx, rp) && + ((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free)) { + /* Note: f is stale after GC (func_obj was passed by value), so we + cannot read f->name here. Just report the pointer. */ + fprintf (stderr, "VALIDATE_GC: C function returned stale ptr=%p " + "heap=[%p,%p) after GC\n", rp, + (void *)ctx->heap_base, (void *)ctx->heap_free); + fflush (stderr); + } + } +#endif + ctx->c_call_root = root.prev; if (unlikely (ctx->trace_hook) && (ctx->trace_type & JS_HOOK_RET)) @@ -5356,19 +5402,27 @@ static JSValue cjson_to_jsvalue (JSContext *ctx, const cJSON *item) { if (cJSON_IsString (item)) return JS_NewString (ctx, item->valuestring); if (cJSON_IsArray (item)) { int n = cJSON_GetArraySize (item); - JSValue arr = JS_NewArrayLen (ctx,n); + JSGCRef arr_ref; + JS_AddGCRef (ctx, &arr_ref); + arr_ref.val = JS_NewArrayLen (ctx, n); for (int i = 0; i < n; i++) { cJSON *child = cJSON_GetArrayItem (item, i); - JS_SetPropertyNumber (ctx, arr, i, cjson_to_jsvalue (ctx, child)); + JS_SetPropertyNumber (ctx, arr_ref.val, i, cjson_to_jsvalue (ctx, child)); } - return arr; + JSValue result = arr_ref.val; + JS_DeleteGCRef (ctx, &arr_ref); + return result; } if (cJSON_IsObject (item)) { - JSValue obj = JS_NewObject (ctx); + JSGCRef obj_ref; + JS_AddGCRef (ctx, &obj_ref); + obj_ref.val = JS_NewObject (ctx); for (cJSON *child = item->child; child; child = child->next) { - JS_SetPropertyStr (ctx, obj, child->string, cjson_to_jsvalue (ctx, child)); + JS_SetPropertyStr (ctx, obj_ref.val, child->string, cjson_to_jsvalue (ctx, child)); } - return obj; + JSValue result = obj_ref.val; + JS_DeleteGCRef (ctx, &obj_ref); + return result; } return JS_NULL; } @@ -11879,9 +11933,13 @@ static const JSCFunctionListEntry js_math_radians_funcs[] JS_CFUNC_DEF ("e", 1, js_math_e) }; JSValue js_math_radians_use (JSContext *ctx) { - JSValue obj = JS_NewObject (ctx); - JS_SetPropertyFunctionList (ctx, obj, js_math_radians_funcs, countof (js_math_radians_funcs)); - return obj; + JSGCRef obj_ref; + JS_PushGCRef (ctx, &obj_ref); + obj_ref.val = JS_NewObject (ctx); + JS_SetPropertyFunctionList (ctx, obj_ref.val, js_math_radians_funcs, countof (js_math_radians_funcs)); + JSValue result = obj_ref.val; + JS_PopGCRef (ctx, &obj_ref); + return result; } /* ============================================================================ @@ -11945,9 +12003,13 @@ static const JSCFunctionListEntry js_math_degrees_funcs[] JS_CFUNC_DEF ("e", 1, js_math_e) }; JSValue js_math_degrees_use (JSContext *ctx) { - JSValue obj = JS_NewObject (ctx); - JS_SetPropertyFunctionList (ctx, obj, js_math_degrees_funcs, countof (js_math_degrees_funcs)); - return obj; + JSGCRef obj_ref; + JS_PushGCRef (ctx, &obj_ref); + obj_ref.val = JS_NewObject (ctx); + JS_SetPropertyFunctionList (ctx, obj_ref.val, js_math_degrees_funcs, countof (js_math_degrees_funcs)); + JSValue result = obj_ref.val; + JS_PopGCRef (ctx, &obj_ref); + return result; } /* ============================================================================ @@ -12010,9 +12072,13 @@ static const JSCFunctionListEntry js_math_cycles_funcs[] JS_CFUNC_DEF ("e", 1, js_math_e) }; JSValue js_math_cycles_use (JSContext *ctx) { - JSValue obj = JS_NewObject (ctx); - JS_SetPropertyFunctionList (ctx, obj, js_math_cycles_funcs, countof (js_math_cycles_funcs)); - return obj; + JSGCRef obj_ref; + JS_PushGCRef (ctx, &obj_ref); + obj_ref.val = JS_NewObject (ctx); + JS_SetPropertyFunctionList (ctx, obj_ref.val, js_math_cycles_funcs, countof (js_math_cycles_funcs)); + JSValue result = obj_ref.val; + JS_PopGCRef (ctx, &obj_ref); + return result; } /* Public API: get stack trace as cJSON array */ cJSON *JS_GetStack(JSContext *ctx) { diff --git a/time.c b/time.c index 0dbffaf1..c3837f2a 100644 --- a/time.c +++ b/time.c @@ -71,12 +71,13 @@ static const JSCFunctionListEntry js_time_funcs[] = { JSValue js_internal_time_use(JSContext *ctx) { - JSValue obj = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, obj, + JS_FRAME(ctx); + JS_ROOT(mod, JS_NewObject(ctx)); + JS_SetPropertyFunctionList(ctx, mod.val, js_time_funcs, sizeof(js_time_funcs) / sizeof(js_time_funcs[0])); - return obj; + JS_RETURN(mod.val); } JSValue diff --git a/wildstar.c b/wildstar.c index c813e139..78226dbc 100644 --- a/wildstar.c +++ b/wildstar.c @@ -29,7 +29,8 @@ static const JSCFunctionListEntry js_wildstar_funcs[] = { }; JSValue js_wildstar_use(JSContext *js) { - JSValue mod = JS_NewObject(js); - JS_SetPropertyFunctionList(js, mod, js_wildstar_funcs, countof(js_wildstar_funcs)); - return mod; + JS_FRAME(js); + JS_ROOT(mod, JS_NewObject(js)); + JS_SetPropertyFunctionList(js, mod.val, js_wildstar_funcs, countof(js_wildstar_funcs)); + JS_RETURN(mod.val); }