diff --git a/Makefile b/Makefile index 95885e374..75200321b 100644 --- a/Makefile +++ b/Makefile @@ -216,9 +216,9 @@ else QJSC_CC=$(CC) QJSC=./qjsc$(EXE) endif -PROGS+=libquickjs.a +PROGS+=libquickjs.a libquickjs-bytecode.a ifdef CONFIG_LTO -PROGS+=libquickjs.lto.a +PROGS+=libquickjs.lto.a libquickjs-bytecode.lto.a endif # examples @@ -244,6 +244,7 @@ endif all: $(OBJDIR) $(OBJDIR)/quickjs.check.o $(OBJDIR)/qjs.check.o $(PROGS) QJS_LIB_OBJS=$(OBJDIR)/quickjs.o $(OBJDIR)/dtoa.o $(OBJDIR)/libregexp.o $(OBJDIR)/libunicode.o $(OBJDIR)/cutils.o $(OBJDIR)/quickjs-libc.o +QJS_LIB_BYTECODE_OBJS=$(patsubst $(OBJDIR)/%.o, $(OBJDIR)/%.bytecode.o, $(QJS_LIB_OBJS)) QJS_OBJS=$(OBJDIR)/qjs.o $(OBJDIR)/repl.o $(QJS_LIB_OBJS) @@ -303,9 +304,15 @@ endif libquickjs$(LTOEXT).a: $(QJS_LIB_OBJS) $(AR) rcs $@ $^ +libquickjs-bytecode$(LTOEXT).a: $(QJS_LIB_BYTECODE_OBJS) + $(AR) rcs $@ $^ + ifdef CONFIG_LTO libquickjs.a: $(patsubst %.o, %.nolto.o, $(QJS_LIB_OBJS)) $(AR) rcs $@ $^ + +libquickjs-bytecode.a: $(patsubst %.o, %.bytecode.nolto.o, $(QJS_LIB_OBJS)) + $(AR) rcs $@ $^ endif # CONFIG_LTO libquickjs.fuzz.a: $(patsubst %.o, %.fuzz.o, $(QJS_LIB_OBJS)) @@ -344,6 +351,12 @@ $(OBJDIR)/%.pic.o: %.c | $(OBJDIR) $(OBJDIR)/%.nolto.o: %.c | $(OBJDIR) $(CC) $(CFLAGS_NOLTO) -c -o $@ $< +$(OBJDIR)/%.bytecode.o: %.c | $(OBJDIR) + $(CC) $(CFLAGS_OPT) -DCONFIG_BYTECODE_ONLY_RUNTIME -c -o $@ $< + +$(OBJDIR)/%.bytecode.nolto.o: %.c | $(OBJDIR) + $(CC) $(CFLAGS_NOLTO) -DCONFIG_BYTECODE_ONLY_RUNTIME -c -o $@ $< + $(OBJDIR)/%.debug.o: %.c | $(OBJDIR) $(CC) $(CFLAGS_DEBUG) -c -o $@ $< @@ -389,6 +402,8 @@ HELLO_OPTS=-fno-string-normalize -fno-map -fno-promise -fno-typedarray \ -fno-typedarray -fno-regexp -fno-json -fno-eval -fno-proxy \ -fno-date -fno-module-loader +BYTECODE_RUNTIME_OPTS=-fno-eval -fno-regexp -fno-json -fno-module-loader + hello.c: $(QJSC) $(HELLO_SRCS) $(QJSC) -e $(HELLO_OPTS) -o $@ $(HELLO_SRCS) @@ -465,6 +480,27 @@ endif stats: qjs$(EXE) $(WINE) ./qjs$(EXE) -qd +test-bytecode-runtime: qjsc$(EXE) libquickjs-bytecode.lto.a + ./qjsc -e $(BYTECODE_RUNTIME_OPTS) -o hello_bytecode.c examples/hello.js + $(CC) $(CFLAGS_OPT) -flto -o hello_bytecode hello_bytecode.c libquickjs-bytecode.lto.a $(LIBS) + ./hello_bytecode | grep "Hello World" + ./qjsc -e -m $(BYTECODE_RUNTIME_OPTS) -o test_bytecode_runtime.c tests/test_bytecode_runtime.js + $(CC) $(CFLAGS_OPT) -flto -o test_bytecode_runtime test_bytecode_runtime.c libquickjs-bytecode.lto.a $(LIBS) + ./test_bytecode_runtime + @if nm hello_bytecode | grep -E "__JS_EvalInternal|js_parse_program|JS_ParseJSON3|js_compile_regexp|JS_LoadModule"; then \ + echo "Error: forbidden symbols found in hello_bytecode"; \ + nm hello_bytecode | grep -E "__JS_EvalInternal|js_parse_program|JS_ParseJSON3|js_compile_regexp|JS_LoadModule"; \ + exit 1; \ + fi + @echo "Testing corrupted bytecode rejection..." + echo "corrupted" > /tmp/bad.bin + @if ./hello_bytecode /tmp/bad.bin 2>&1 | grep -q "Segmentation fault"; then \ + echo "Error: Bytecode-only runtime crashed on corrupted input"; \ + exit 1; \ + fi + @echo "Bytecode-only runtime test passed" + rm -f hello_bytecode hello_bytecode.c test_bytecode_runtime test_bytecode_runtime.c /tmp/bad.bin + microbench: qjs$(EXE) $(WINE) ./qjs$(EXE) --std tests/microbench.js diff --git a/qjs.c b/qjs.c index 0224f7cfa..eea60449a 100644 --- a/qjs.c +++ b/qjs.c @@ -482,7 +482,7 @@ int main(int argc, char **argv) } if (!empty_run) { - js_std_add_helpers(ctx, argc - optind, argv + optind); + js_std_add_helpers(ctx, argc - optind, argv + optind, TRUE); /* make 'std' and 'os' visible to non module code */ if (load_std) { diff --git a/qjsc.c b/qjsc.c index e55ca61ce..6dbd13fd4 100644 --- a/qjsc.c +++ b/qjsc.c @@ -36,6 +36,10 @@ #include "cutils.h" #include "quickjs-libc.h" +#ifdef CONFIG_BYTECODE_ONLY_RUNTIME +#error "qjsc must be built with the full QuickJS engine" +#endif + typedef struct { char *name; char *short_name; @@ -79,6 +83,15 @@ static const FeatureEntry feature_list[] = { { "weakref", "WeakRef" }, }; +#define FE_MASK(i) ((uint64_t)1 << (i)) +#define BYTECODE_ONLY_TRIGGER_MASK \ + (FE_MASK(1) | FE_MASK(3) | FE_MASK(4) | FE_MASK(FE_MODULE_LOADER)) + +static BOOL runtime_needs_parser(void) +{ + return (feature_bitmap & BYTECODE_ONLY_TRIGGER_MASK) != 0; +} + void namelist_add(namelist_t *lp, const char *name, const char *short_name, int flags) { @@ -477,6 +490,8 @@ static int output_executable(const char *out_filename, const char *cfilename, lto_suffix = ""; bn_suffix = ""; + if (!runtime_needs_parser()) + bn_suffix = "-bytecode"; arg = argv; *arg++ = CONFIG_CC; @@ -841,14 +856,10 @@ int main(int argc, char **argv) (unsigned int)stack_size); } - /* add the module loader if necessary */ - if (feature_bitmap & (1 << FE_MODULE_LOADER)) { - fprintf(fo, " JS_SetModuleLoaderFunc2(rt, NULL, js_module_loader, js_module_check_attributes, NULL);\n"); - } - fprintf(fo, " ctx = JS_NewCustomContext(rt);\n" - " js_std_add_helpers(ctx, argc, argv);\n"); + " js_std_add_helpers(ctx, argc, argv, %d);\n", + (feature_bitmap & (1 << FE_MODULE_LOADER)) != 0); for(i = 0; i < cname_list.count; i++) { namelist_entry_t *e = &cname_list.array[i]; diff --git a/quickjs-libc.c b/quickjs-libc.c index c24b6d53e..d3757df7d 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -67,7 +67,7 @@ typedef sig_t sighandler_t; /* enable the os.Worker API. It relies on POSIX threads */ #define USE_WORKER -#ifdef USE_WORKER +#if defined(USE_WORKER) && !defined(CONFIG_BYTECODE_ONLY_RUNTIME) #include #include #endif @@ -123,7 +123,7 @@ typedef struct JSWaker { typedef struct { int ref_count; -#ifdef USE_WORKER +#if defined(USE_WORKER) && !defined(CONFIG_BYTECODE_ONLY_RUNTIME) pthread_mutex_t mutex; #endif struct list_head msg_queue; /* list of JSWorkerMessage.link */ @@ -431,6 +431,7 @@ uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename) } /* load and evaluate a file */ +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME static JSValue js_loadScript(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -454,6 +455,7 @@ static JSValue js_loadScript(JSContext *ctx, JSValueConst this_val, JS_FreeCString(ctx, filename); return ret; } +#endif /* load a file as a UTF-8 encoded string */ static JSValue js_std_loadFile(JSContext *ctx, JSValueConst this_val, @@ -679,11 +681,16 @@ JSModuleDef *js_module_loader(JSContext *ctx, JSValueConst attributes) { JSModuleDef *m; - int res; if (has_suffix(module_name, ".so")) { m = js_module_loader_so(ctx, module_name); } else { +#ifdef CONFIG_BYTECODE_ONLY_RUNTIME + JS_ThrowTypeError(ctx, "could not load module filename '%s': source module loading is not supported", + module_name); + return NULL; +#else + int res; size_t buf_len; uint8_t *buf; @@ -723,6 +730,7 @@ JSModuleDef *js_module_loader(JSContext *ctx, m = JS_VALUE_GET_PTR(func_val); JS_FreeValue(ctx, func_val); } +#endif } return m; } @@ -870,6 +878,7 @@ static int get_bool_option(JSContext *ctx, BOOL *pbool, return 0; } +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -918,6 +927,7 @@ static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val, } return ret; } +#endif static JSClassID js_std_file_class_id; @@ -957,6 +967,7 @@ static JSValue js_std_strerror(JSContext *ctx, JSValueConst this_val, return JS_NewString(ctx, strerror(err)); } +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME static JSValue js_std_parseExtJSON(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -971,6 +982,7 @@ static JSValue js_std_parseExtJSON(JSContext *ctx, JSValueConst this_val, JS_FreeCString(ctx, str); return obj; } +#endif static JSValue js_new_std_file(JSContext *ctx, FILE *f, BOOL close_in_finalizer, @@ -1640,8 +1652,10 @@ static const JSCFunctionListEntry js_std_error_props[] = { static const JSCFunctionListEntry js_std_funcs[] = { JS_CFUNC_DEF("exit", 1, js_std_exit ), JS_CFUNC_DEF("gc", 0, js_std_gc ), +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME JS_CFUNC_DEF("evalScript", 1, js_evalScript ), JS_CFUNC_DEF("loadScript", 1, js_loadScript ), +#endif JS_CFUNC_DEF("getenv", 1, js_std_getenv ), JS_CFUNC_DEF("setenv", 1, js_std_setenv ), JS_CFUNC_DEF("unsetenv", 1, js_std_unsetenv ), @@ -1649,7 +1663,9 @@ static const JSCFunctionListEntry js_std_funcs[] = { JS_CFUNC_DEF("urlGet", 1, js_std_urlGet ), JS_CFUNC_DEF("loadFile", 1, js_std_loadFile ), JS_CFUNC_DEF("strerror", 1, js_std_strerror ), +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME JS_CFUNC_DEF("parseExtJSON", 1, js_std_parseExtJSON ), +#endif /* FILE I/O */ JS_CFUNC_DEF("open", 2, js_std_open ), @@ -2268,7 +2284,7 @@ static void call_handler(JSContext *ctx, JSValueConst func) JS_FreeValue(ctx, ret); } -#ifdef USE_WORKER +#if defined(USE_WORKER) && !defined(CONFIG_BYTECODE_ONLY_RUNTIME) #ifdef _WIN32 @@ -3421,7 +3437,7 @@ static JSValue js_os_dup2(JSContext *ctx, JSValueConst this_val, #endif /* !_WIN32 */ -#ifdef USE_WORKER +#if defined(USE_WORKER) && !defined(CONFIG_BYTECODE_ONLY_RUNTIME) /* Worker */ @@ -3610,7 +3626,7 @@ static void *worker_func(void *opaque) JS_SetCanBlock(rt, TRUE); - js_std_add_helpers(ctx, -1, NULL); + js_std_add_helpers(ctx, -1, NULL, TRUE); val = JS_LoadModule(ctx, args->basename, args->filename); free(args->filename); @@ -3870,7 +3886,7 @@ static const JSCFunctionListEntry js_worker_proto_funcs[] = { void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)) { -#ifdef USE_WORKER +#if defined(USE_WORKER) && !defined(CONFIG_BYTECODE_ONLY_RUNTIME) js_worker_new_context_func = func; #endif } @@ -3976,7 +3992,7 @@ static int js_os_init(JSContext *ctx, JSModuleDef *m) { os_poll_func = js_os_poll; -#ifdef USE_WORKER +#if defined(USE_WORKER) && !defined(CONFIG_BYTECODE_ONLY_RUNTIME) { JSRuntime *rt = JS_GetRuntime(ctx); JSThreadState *ts = JS_GetRuntimeOpaque(rt); @@ -4015,7 +4031,7 @@ JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name) if (!m) return NULL; JS_AddModuleExportList(ctx, m, js_os_funcs, countof(js_os_funcs)); -#ifdef USE_WORKER +#if defined(USE_WORKER) && !defined(CONFIG_BYTECODE_ONLY_RUNTIME) JS_AddModuleExport(ctx, m, "Worker"); #endif return m; @@ -4058,7 +4074,7 @@ static JSValue js_console_log(JSContext *ctx, JSValueConst this_val, return ret; } -void js_std_add_helpers(JSContext *ctx, int argc, char **argv) +void js_std_add_helpers(JSContext *ctx, int argc, char **argv, int use_module_loader) { JSValue global_obj, console, args, performance; int i; @@ -4066,6 +4082,10 @@ void js_std_add_helpers(JSContext *ctx, int argc, char **argv) /* XXX: should these global definitions be enumerable? */ global_obj = JS_GetGlobalObject(ctx); + if (use_module_loader) { + JS_SetModuleLoaderFunc2(JS_GetRuntime(ctx), NULL, js_module_loader, js_module_check_attributes, NULL); + } + console = JS_NewObject(ctx); JS_SetPropertyStr(ctx, console, "log", JS_NewCFunction(ctx, js_console_log, "log", 1)); @@ -4087,8 +4107,10 @@ void js_std_add_helpers(JSContext *ctx, int argc, char **argv) JS_SetPropertyStr(ctx, global_obj, "print", JS_NewCFunction(ctx, js_print, "print", 1)); +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME JS_SetPropertyStr(ctx, global_obj, "__loadScript", JS_NewCFunction(ctx, js_loadScript, "__loadScript", 1)); +#endif JS_FreeValue(ctx, global_obj); } @@ -4112,7 +4134,7 @@ void js_std_init_handlers(JSRuntime *rt) JS_SetRuntimeOpaque(rt, ts); -#ifdef USE_WORKER +#if defined(USE_WORKER) && !defined(CONFIG_BYTECODE_ONLY_RUNTIME) /* set the SharedArrayBuffer memory handlers */ { JSSharedArrayBufferFunctions sf; @@ -4152,7 +4174,7 @@ void js_std_free_handlers(JSRuntime *rt) free(rp); } -#ifdef USE_WORKER +#if defined(USE_WORKER) && !defined(CONFIG_BYTECODE_ONLY_RUNTIME) js_free_message_pipe(ts->recv_pipe); js_free_message_pipe(ts->send_pipe); diff --git a/quickjs-libc.h b/quickjs-libc.h index 5c8301b71..d15496630 100644 --- a/quickjs-libc.h +++ b/quickjs-libc.h @@ -35,7 +35,7 @@ extern "C" { JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name); JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name); -void js_std_add_helpers(JSContext *ctx, int argc, char **argv); +void js_std_add_helpers(JSContext *ctx, int argc, char **argv, int use_module_loader); void js_std_loop(JSContext *ctx); JSValue js_std_await(JSContext *ctx, JSValue obj); void js_std_init_handlers(JSRuntime *rt); diff --git a/quickjs.c b/quickjs.c index 2b33bfa5f..5943083ef 100644 --- a/quickjs.c +++ b/quickjs.c @@ -2219,6 +2219,7 @@ JSContext *JS_NewContext(JSRuntime *rt) JS_AddIntrinsicDate(ctx) || JS_AddIntrinsicEval(ctx) || JS_AddIntrinsicStringNormalize(ctx) || + (JS_AddIntrinsicRegExpCompiler(ctx), 0) || JS_AddIntrinsicRegExp(ctx) || JS_AddIntrinsicJSON(ctx) || JS_AddIntrinsicProxy(ctx) || @@ -35439,6 +35440,7 @@ static int add_global_variables(JSContext *ctx, JSFunctionDef *fd) /* create a function object from a function definition. The function definition is freed. All the child functions are also created. It must be done this way to resolve all the variables. */ +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) { JSValue func_obj; @@ -35697,6 +35699,7 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) js_free_function_def(ctx, fd); return JS_EXCEPTION; } +#endif static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b) { @@ -36493,6 +36496,7 @@ static __exception int js_parse_function_decl(JSParseState *s, JS_PARSE_EXPORT_NONE, NULL); } +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME static __exception int js_parse_program(JSParseState *s) { JSFunctionDef *fd = s->cur_func; @@ -36544,7 +36548,9 @@ static __exception int js_parse_program(JSParseState *s) return 0; } +#endif +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME static void js_parse_init(JSContext *ctx, JSParseState *s, const char *input, size_t input_len, const char *filename) @@ -36562,6 +36568,7 @@ static void js_parse_init(JSContext *ctx, JSParseState *s, s->get_line_col_cache.line_num = 0; s->get_line_col_cache.col_num = 0; } +#endif static JSValue JS_EvalFunctionInternal(JSContext *ctx, JSValue fun_obj, JSValueConst this_obj, @@ -36596,13 +36603,13 @@ static JSValue JS_EvalFunctionInternal(JSContext *ctx, JSValue fun_obj, } return ret_val; } - JSValue JS_EvalFunction(JSContext *ctx, JSValue fun_obj) { return JS_EvalFunctionInternal(ctx, fun_obj, ctx->global_obj, NULL, NULL); } /* 'input' must be zero terminated i.e. input[input_len] = '\0'. */ +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj, const char *input, size_t input_len, const char *filename, int flags, int scope_idx) @@ -36717,6 +36724,7 @@ static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj, JS_FreeValue(ctx, JS_MKPTR(JS_TAG_MODULE, m)); return JS_EXCEPTION; } +#endif /* the indirection is needed to make 'eval' optional */ static JSValue JS_EvalInternal(JSContext *ctx, JSValueConst this_obj, @@ -40509,6 +40517,11 @@ static JSValue js_function_constructor(JSContext *ctx, JSValueConst new_target, if (JS_IsException(s)) goto fail1; + if (unlikely(!ctx->eval_internal)) { + JS_FreeValue(ctx, s); + JS_ThrowTypeError(ctx, "eval is not supported"); + goto fail1; + } obj = JS_EvalObject(ctx, ctx->global_obj, s, JS_EVAL_TYPE_INDIRECT, -1); JS_FreeValue(ctx, s); if (JS_IsException(obj)) @@ -48566,15 +48579,15 @@ static const JSCFunctionListEntry js_regexp_string_iterator_proto_funcs[] = { void JS_AddIntrinsicRegExpCompiler(JSContext *ctx) { +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME ctx->compile_regexp = js_compile_regexp; +#endif } int JS_AddIntrinsicRegExp(JSContext *ctx) { JSValue obj; - JS_AddIntrinsicRegExpCompiler(ctx); - obj = JS_NewCConstructor(ctx, JS_CLASS_REGEXP, "RegExp", js_regexp_constructor, 2, JS_CFUNC_constructor_or_func, 0, JS_UNDEFINED, @@ -48973,6 +48986,7 @@ static JSValue json_parse_value(JSParseState *s, JSONParseRecord *pr) return JS_EXCEPTION; } +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME JSValue JS_ParseJSON3(JSContext *ctx, const char *buf, size_t buf_len, const char *filename, int flags, JSONParseRecord *pr) { @@ -49010,8 +49024,10 @@ JSValue JS_ParseJSON(JSContext *ctx, const char *buf, size_t buf_len, { return JS_ParseJSON3(ctx, buf, buf_len, filename, 0, NULL); } +#endif /* if pr != NULL, then pr->value = holder by construction */ +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, JSAtom name, JSValueConst reviver, const char *text_str, JSONParseRecord *pr) @@ -49116,10 +49132,14 @@ static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, JS_FreeValue(ctx, val); return JS_EXCEPTION; } +#endif static JSValue js_json_parse(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { +#ifdef CONFIG_BYTECODE_ONLY_RUNTIME + return JS_ThrowTypeError(ctx, "JSON.parse is not supported"); +#else JSValue obj; const char *str; size_t len; @@ -49168,6 +49188,7 @@ static JSValue js_json_parse(JSContext *ctx, JSValueConst this_val, fail: JS_FreeCString(ctx, str); return JS_EXCEPTION; +#endif /* !CONFIG_BYTECODE_ONLY_RUNTIME */ } static JSValue js_json_isRawJSON(JSContext *ctx, JSValueConst this_val, @@ -55479,7 +55500,9 @@ int JS_AddIntrinsicDate(JSContext *ctx) int JS_AddIntrinsicEval(JSContext *ctx) { +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME ctx->eval_internal = __JS_EvalInternal; +#endif return 0; } diff --git a/tests/test_bytecode_runtime.js b/tests/test_bytecode_runtime.js new file mode 100644 index 000000000..e96f83c74 --- /dev/null +++ b/tests/test_bytecode_runtime.js @@ -0,0 +1,97 @@ +function assert(actual, expected, message) { + if (actual !== expected) { + throw new Error(message + ": expected " + expected + ", got " + actual); + } +} + +function assert_throws(expected_error, func) { + try { + func(); + } catch (e) { + if (e instanceof expected_error) { + return; + } + throw new Error("Expected " + expected_error.name + " but got " + e); + } + throw new Error("Expected " + expected_error.name + " but no error was thrown"); +} + +console.log("Testing bytecode-only runtime safety and functionality..."); + +// 1. Ensure core features still work +// BigInt +assert(1n + 2n, 3n, "BigInt addition"); + +// Closures with mutation +function make_counter() { + let count = 0; + return function() { + return ++count; + }; +} +let counter = make_counter(); +assert(counter(), 1, "Counter 1"); +assert(counter(), 2, "Counter 2"); + +// Generators +function* gen() { + yield 1; + yield 2; +} +let g = gen(); +assert(g.next().value, 1, "Generator 1"); +assert(g.next().value, 2, "Generator 2"); + +// Async/await +async function async_test() { + return await Promise.resolve(42); +} +async_test().then(val => { + assert(val, 42, "Async/await"); +}); + +// Map/Set iteration +let m = new Map(); +m.set("a", 1); +for (let [k, v] of m) { + assert(k, "a", "Map key"); + assert(v, 1, "Map value"); +} + +// 2. Test std functions are absent +import * as std from "std"; +import * as os from "os"; + +assert(std.evalScript, undefined, "std.evalScript should be absent"); +assert(std.loadScript, undefined, "std.loadScript should be absent"); +assert(std.parseExtJSON, undefined, "std.parseExtJSON should be absent"); + +// 3. Test os.Worker is absent +assert(os.Worker, undefined, "os.Worker should be absent"); + +// 4. Test Function.prototype.toString() on a bytecode function +function test_func(a, b) { return a + b; } +let s = test_func.toString(); +if (typeof s !== "string" || s.length === 0) { + throw new Error("Function.prototype.toString() failed"); +} +console.log("toString() result: " + s.substring(0, 20) + "..."); + +// 5. Test import.meta.url +// This depends on how the module is loaded, but it shouldn't crash. +console.log("import.meta.url: " + import.meta.url); + +// 6. Test dynamic feature TypeErrors (Behavioral check) +if (typeof eval !== "undefined") { + assert_throws(TypeError, function() { eval("1+1"); }); +} + +if (typeof Function !== "undefined") { + assert_throws(TypeError, function() { new Function("return 1"); }); +} + +if (typeof JSON !== "undefined") { + assert_throws(TypeError, function() { JSON.parse("{}"); }); +} + +console.log("All comprehensive tests passed");