#ifdef NDEBUG #undef NDEBUG #endif #include #include #include #include #include "quickjs.h" #include "quickjs-libc.h" #include "cutils.h" static JSRuntime *new_runtime(void) { JSRuntime *rt = JS_NewRuntime(); if (rt) JS_SetDumpFlags(rt, JS_ABORT_ON_LEAKS); return rt; } static JSValue eval(JSContext *ctx, const char *code) { return JS_Eval(ctx, code, strlen(code), "", JS_EVAL_TYPE_GLOBAL); } static JSValue cfunc_callback(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return JS_ThrowTypeError(ctx, "from cfunc"); } static JSValue cfuncdata_callback(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, JSValueConst *func_data) { return JS_ThrowTypeError(ctx, "from cfuncdata"); } static JSValue cclosure_callback(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, void *func_data) { return JS_ThrowTypeError(ctx, "from cclosure"); } static bool closure_finalized = false; static void cclosure_opaque_finalize(void *opaque) { if ((intptr_t)opaque == 12) closure_finalized = true; } static void cfunctions(void) { uint32_t length; const char *s; JSValue ret, stack; JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); JSValue cfunc = JS_NewCFunction(ctx, cfunc_callback, "cfunc", 42); JSValue cfuncdata = JS_NewCFunctionData2(ctx, cfuncdata_callback, "cfuncdata", /*length*/1337, /*magic*/0, /*data_len*/0, NULL); JSValue cclosure = JS_NewCClosure(ctx, cclosure_callback, "cclosure", cclosure_opaque_finalize, /*length*/0xC0DE, /*magic*/11, /*opaque*/(void*)12); JSValue global = JS_GetGlobalObject(ctx); JS_SetPropertyStr(ctx, global, "cfunc", cfunc); JS_SetPropertyStr(ctx, global, "cfuncdata", cfuncdata); JS_SetPropertyStr(ctx, global, "cclosure", cclosure); JS_FreeValue(ctx, global); ret = eval(ctx, "cfunc.name"); assert(!JS_IsException(ret)); assert(JS_IsString(ret)); s = JS_ToCString(ctx, ret); JS_FreeValue(ctx, ret); assert(s); assert(!strcmp(s, "cfunc")); JS_FreeCString(ctx, s); ret = eval(ctx, "cfunc.length"); assert(!JS_IsException(ret)); assert(JS_IsNumber(ret)); assert(0 == JS_ToUint32(ctx, &length, ret)); assert(length == 42); ret = eval(ctx, "cfuncdata.name"); assert(!JS_IsException(ret)); assert(JS_IsString(ret)); s = JS_ToCString(ctx, ret); JS_FreeValue(ctx, ret); assert(s); assert(!strcmp(s, "cfuncdata")); JS_FreeCString(ctx, s); ret = eval(ctx, "cfuncdata.length"); assert(!JS_IsException(ret)); assert(JS_IsNumber(ret)); assert(0 == JS_ToUint32(ctx, &length, ret)); assert(length == 1337); ret = eval(ctx, "cclosure.name"); assert(!JS_IsException(ret)); assert(JS_IsString(ret)); s = JS_ToCString(ctx, ret); JS_FreeValue(ctx, ret); assert(s); assert(!strcmp(s, "cclosure")); JS_FreeCString(ctx, s); ret = eval(ctx, "cclosure.length"); assert(!JS_IsException(ret)); assert(JS_IsNumber(ret)); assert(0 == JS_ToUint32(ctx, &length, ret)); assert(length == 0xC0DE); ret = eval(ctx, "cfunc()"); assert(JS_IsException(ret)); ret = JS_GetException(ctx); assert(JS_IsError(ret)); stack = JS_GetPropertyStr(ctx, ret, "stack"); assert(JS_IsString(stack)); s = JS_ToCString(ctx, stack); JS_FreeValue(ctx, stack); assert(s); assert(!strcmp(s, " at cfunc (native)\n at (:1:1)\n")); JS_FreeCString(ctx, s); s = JS_ToCString(ctx, ret); JS_FreeValue(ctx, ret); assert(s); assert(!strcmp(s, "TypeError: from cfunc")); JS_FreeCString(ctx, s); ret = eval(ctx, "cfuncdata()"); assert(JS_IsException(ret)); ret = JS_GetException(ctx); assert(JS_IsError(ret)); stack = JS_GetPropertyStr(ctx, ret, "stack"); assert(JS_IsString(stack)); s = JS_ToCString(ctx, stack); JS_FreeValue(ctx, stack); assert(s); assert(!strcmp(s, " at cfuncdata (native)\n at (:1:1)\n")); JS_FreeCString(ctx, s); s = JS_ToCString(ctx, ret); JS_FreeValue(ctx, ret); assert(s); assert(!strcmp(s, "TypeError: from cfuncdata")); JS_FreeCString(ctx, s); ret = eval(ctx, "cclosure()"); assert(JS_IsException(ret)); ret = JS_GetException(ctx); assert(JS_IsError(ret)); stack = JS_GetPropertyStr(ctx, ret, "stack"); assert(JS_IsString(stack)); s = JS_ToCString(ctx, stack); JS_FreeValue(ctx, stack); assert(s); assert(!strcmp(s, " at cclosure (native)\n at (:1:1)\n")); JS_FreeCString(ctx, s); s = JS_ToCString(ctx, ret); JS_FreeValue(ctx, ret); assert(s); assert(!strcmp(s, "TypeError: from cclosure")); JS_FreeCString(ctx, s); JS_FreeContext(ctx); JS_FreeRuntime(rt); assert(closure_finalized); } #define MAX_TIME 10 static int timeout_interrupt_handler(JSContext *ctx, void *opaque) { int *time = (int *)opaque; return (*time)++ > MAX_TIME; } static void sync_call(void) { static const char code[] = "(function() { \ try { \ while (true) {} \ } catch (e) {} \ })();"; JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); int time = 0; JS_SetInterruptHandler(rt, timeout_interrupt_handler, &time); JSValue ret = eval(ctx, code); assert(time > MAX_TIME); assert(JS_IsException(ret)); JS_FreeValue(ctx, ret); assert(JS_HasException(ctx)); JSValue e = JS_GetException(ctx); assert(JS_IsUncatchableError(e)); JS_FreeValue(ctx, e); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void async_call(void) { static const char code[] = "(async function() { \ const loop = async () => { \ await Promise.resolve(); \ while (true) {} \ }; \ await loop().catch(() => {}); \ })();"; JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); int time = 0; JS_SetInterruptHandler(rt, timeout_interrupt_handler, &time); JSValue ret = eval(ctx, code); assert(!JS_IsException(ret)); JS_FreeValue(ctx, ret); assert(JS_IsJobPending(rt)); int r = 0; while (JS_IsJobPending(rt)) { r = JS_ExecutePendingJob(rt, &ctx); } assert(time > MAX_TIME); assert(r == -1); assert(JS_HasException(ctx)); JSValue e = JS_GetException(ctx); assert(JS_IsUncatchableError(e)); JS_FreeValue(ctx, e); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void std_eval_interrupt_handler(void) { static const char code[] = "import * as std from 'std'; std.evalScript('for(;;){}')"; JSRuntime *rt = new_runtime(); js_std_init_handlers(rt); JSContext *ctx = JS_NewContext(rt); js_init_module_std(ctx, "std"); int time = 0; JS_SetInterruptHandler(rt, timeout_interrupt_handler, &time); JSValue ret = JS_Eval(ctx, code, strlen(code), "", JS_EVAL_TYPE_MODULE); ret = js_std_await(ctx, ret); assert(time > MAX_TIME); assert(JS_IsException(ret)); ret = JS_GetException(ctx); assert(JS_IsError(ret)); // uncatchable "interrupted" exception is turned into a regular exception assert(!JS_IsUncatchableError(ret)); const char *str = JS_ToCString(ctx, ret); assert(str != NULL); assert(strstr(str, "InternalError: interrupted")); JS_FreeCString(ctx, str); JS_FreeValue(ctx, ret); uintptr_t interrupt_handler = js_std_cmd(/*GetInterruptHandler*/5, rt); uintptr_t interrupt_opaque = js_std_cmd(/*GetInterruptOpaque*/6, rt); assert(interrupt_handler == (uintptr_t)timeout_interrupt_handler); assert(interrupt_opaque == (uintptr_t)&time); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static JSValue save_value(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { assert(argc == 1); JSValue *p = (JSValue *)JS_GetContextOpaque(ctx); *p = JS_DupValue(ctx, argv[0]); return JS_UNDEFINED; } static void async_call_stack_overflow(void) { static const char code[] = "(async function() { \ const f = () => f(); \ try { \ await Promise.resolve(); \ f(); \ } catch (e) { \ save_value(e); \ } \ })();"; JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); JSValue value = JS_UNDEFINED; JS_SetContextOpaque(ctx, &value); JSValue global = JS_GetGlobalObject(ctx); JS_SetPropertyStr(ctx, global, "save_value", JS_NewCFunction(ctx, save_value, "save_value", 1)); JS_FreeValue(ctx, global); JSValue ret = eval(ctx, code); assert(!JS_IsException(ret)); JS_FreeValue(ctx, ret); assert(JS_IsJobPending(rt)); int r = 0; while (JS_IsJobPending(rt)) { r = JS_ExecutePendingJob(rt, &ctx); } assert(r == 1); assert(!JS_HasException(ctx)); assert(JS_IsError(value)); // stack overflow should be caught JS_FreeValue(ctx, value); JS_FreeContext(ctx); JS_FreeRuntime(rt); } // https://github.com/quickjs-ng/quickjs/issues/914 static void raw_context_global_var(void) { JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContextRaw(rt); JS_AddIntrinsicEval(ctx); { JSValue ret = eval(ctx, "globalThis"); assert(JS_IsException(ret)); JS_FreeValue(ctx, ret); } { JSValue ret = eval(ctx, "var x = 42"); assert(JS_IsUndefined(ret)); JS_FreeValue(ctx, ret); } { JSValue ret = eval(ctx, "function f() {}"); assert(JS_IsUndefined(ret)); JS_FreeValue(ctx, ret); } JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void is_array(void) { JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); { JSValue ret = eval(ctx, "[]"); assert(!JS_IsException(ret)); assert(JS_IsArray(ret)); JS_FreeValue(ctx, ret); } { JSValue ret = eval(ctx, "new Proxy([], {})"); assert(!JS_IsException(ret)); assert(!JS_IsArray(ret)); assert(JS_IsProxy(ret)); JSValue handler = JS_GetProxyHandler(ctx, ret); JSValue target = JS_GetProxyTarget(ctx, ret); assert(!JS_IsException(handler)); assert(!JS_IsException(target)); assert(!JS_IsProxy(handler)); assert(!JS_IsProxy(target)); assert(JS_IsObject(handler)); assert(JS_IsArray(target)); JS_FreeValue(ctx, handler); JS_FreeValue(ctx, target); JS_FreeValue(ctx, ret); } JS_FreeContext(ctx); JS_FreeRuntime(rt); } static int loader_calls; static JSModuleDef *loader(JSContext *ctx, const char *name, void *opaque) { loader_calls++; assert(!strcmp(name, "b")); static const char code[] = "export function f(x){}"; JSValue ret = JS_Eval(ctx, code, strlen(code), "b", JS_EVAL_TYPE_MODULE|JS_EVAL_FLAG_COMPILE_ONLY); assert(!JS_IsException(ret)); JSModuleDef *m = JS_VALUE_GET_PTR(ret); assert(m); JS_FreeValue(ctx, ret); return m; } static void module_serde(void) { JSRuntime *rt = new_runtime(); //JS_SetDumpFlags(rt, JS_DUMP_MODULE_RESOLVE); JS_SetModuleLoaderFunc(rt, NULL, loader, NULL); JSContext *ctx = JS_NewContext(rt); static const char code[] = "import {f} from 'b'; f()"; assert(loader_calls == 0); JSValue mod = JS_Eval(ctx, code, strlen(code), "a", JS_EVAL_TYPE_MODULE|JS_EVAL_FLAG_COMPILE_ONLY); assert(loader_calls == 1); assert(!JS_IsException(mod)); assert(JS_IsModule(mod)); size_t len = 0; uint8_t *buf = JS_WriteObject(ctx, &len, mod, JS_WRITE_OBJ_BYTECODE|JS_WRITE_OBJ_REFERENCE); assert(buf); assert(len > 0); JS_FreeValue(ctx, mod); assert(loader_calls == 1); mod = JS_ReadObject(ctx, buf, len, JS_READ_OBJ_BYTECODE); js_free(ctx, buf); assert(loader_calls == 1); // 'b' is returned from cache assert(!JS_IsException(mod)); JSValue ret = JS_EvalFunction(ctx, mod); assert(!JS_IsException(ret)); assert(JS_IsPromise(ret)); JSValue result = JS_PromiseResult(ctx, ret); assert(!JS_IsException(result)); assert(JS_IsUndefined(result)); JS_FreeValue(ctx, result); JS_FreeValue(ctx, ret); JS_FreeValue(ctx, mod); JS_FreeContext(ctx); JS_FreeRuntime(rt); } struct rejection_counts { int reject_count; int handle_count; }; static void rejection_counter(JSContext *ctx, JSValueConst promise, JSValueConst reason, bool is_handled, void *opaque) { struct rejection_counts *c = opaque; if (is_handled) c->handle_count++; else c->reject_count++; } // A synchronous module that throws at top level must surface exactly one unhandled rejection static void module_unhandled_rejection(void) { struct rejection_counts c = {0, 0}; JSRuntime *rt = new_runtime(); JS_SetHostPromiseRejectionTracker(rt, rejection_counter, &c); JSContext *ctx = JS_NewContext(rt); static const char code[] = "throw new Error('Nuke the entire site from orbit. It\\'s the only way to be sure.')"; JSValue v = JS_Eval(ctx, code, strlen(code), "", JS_EVAL_TYPE_MODULE); JS_FreeValue(ctx, v); JSContext *c1; while (JS_ExecutePendingJob(rt, &c1) > 0) ; // net unhandled rejections == (2 rejects - 1 handled) assert(c.reject_count == 2); assert(c.handle_count == 1); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void promise_mark_as_handled(void) { struct rejection_counts c = {0, 0}; JSRuntime *rt = new_runtime(); JS_SetHostPromiseRejectionTracker(rt, rejection_counter, &c); JSContext *ctx = JS_NewContext(rt); JSContext *c1; // marking an already-rejected promise notifies the tracker exactly once static const char code[] = "Promise.reject('kaboom')"; JSValue promise = JS_Eval(ctx, code, strlen(code), "", JS_EVAL_TYPE_GLOBAL); assert(JS_IsPromise(promise)); while (JS_ExecutePendingJob(rt, &c1) > 0) ; assert(c.reject_count == 1); assert(c.handle_count == 0); JS_PromiseMarkAsHandled(ctx, promise); assert(c.handle_count == 1); JS_PromiseMarkAsHandled(ctx, promise); assert(c.handle_count == 1); JS_FreeValue(ctx, promise); // marking a pending promise suppresses the report when it later rejects JSValue resolving_funcs[2]; JSValue promise2 = JS_NewPromiseCapability(ctx, resolving_funcs); JS_PromiseMarkAsHandled(ctx, promise2); JSValue reason = JS_NewString(ctx, "unseen"); JSValue ret = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, 1, (JSValueConst *)&reason); while (JS_ExecutePendingJob(rt, &c1) > 0) ; assert(c.reject_count == 1); assert(c.handle_count == 1); JS_FreeValue(ctx, ret); JS_FreeValue(ctx, reason); JS_FreeValue(ctx, resolving_funcs[0]); JS_FreeValue(ctx, resolving_funcs[1]); JS_FreeValue(ctx, promise2); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void promise_then(void) { const char *s; JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); JSContext *c1; JSValue got = JS_UNDEFINED; JS_SetContextOpaque(ctx, &got); // the result promise is intrinsic: neither an overridden then() nor // Symbol.species is consulted static const char code[] = "class P extends Promise {" " static get [Symbol.species]() { throw new Error('species'); }" " then() { throw new Error('then'); }" "}" "P.resolve('ok')"; JSValue promise = eval(ctx, code); assert(JS_IsPromise(promise)); JSValue on_fulfilled = JS_NewCFunction(ctx, save_value, "onFulfilled", 1); JSValue result = JS_PromiseThen(ctx, promise, on_fulfilled, JS_UNDEFINED); assert(JS_IsPromise(result)); while (JS_ExecutePendingJob(rt, &c1) > 0) ; s = JS_ToCString(ctx, got); assert(s); assert(!strcmp(s, "ok")); JS_FreeCString(ctx, s); JS_FreeValue(ctx, got); assert(JS_PromiseState(ctx, result) == JS_PROMISE_FULFILLED); JS_FreeValue(ctx, result); JS_FreeValue(ctx, on_fulfilled); JS_FreeValue(ctx, promise); // rejection handler receives the reason when the promise rejects later JSValue resolving_funcs[2]; JSValue promise2 = JS_NewPromiseCapability(ctx, resolving_funcs); JSValue on_rejected = JS_NewCFunction(ctx, save_value, "onRejected", 1); JSValue result2 = JS_PromiseThen(ctx, promise2, JS_UNDEFINED, on_rejected); assert(JS_IsPromise(result2)); got = JS_UNDEFINED; JSValue reason = JS_NewString(ctx, "boom"); JSValue ret = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, 1, (JSValueConst *)&reason); while (JS_ExecutePendingJob(rt, &c1) > 0) ; s = JS_ToCString(ctx, got); assert(s); assert(!strcmp(s, "boom")); JS_FreeCString(ctx, s); JS_FreeValue(ctx, got); assert(JS_PromiseState(ctx, result2) == JS_PROMISE_FULFILLED); JS_FreeValue(ctx, ret); JS_FreeValue(ctx, reason); JS_FreeValue(ctx, result2); JS_FreeValue(ctx, on_rejected); JS_FreeValue(ctx, resolving_funcs[0]); JS_FreeValue(ctx, resolving_funcs[1]); JS_FreeValue(ctx, promise2); // a non-promise argument is a TypeError JSValue bad = JS_PromiseThen(ctx, JS_UNDEFINED, JS_UNDEFINED, JS_UNDEFINED); assert(JS_IsException(bad)); JSValue exc = JS_GetException(ctx); JS_FreeValue(ctx, exc); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void runtime_cstring_free(void) { JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); // string -> cstring + JS_FreeCStringRT { JSValue ret = eval(ctx, "\"testStringPleaseIgnore\""); assert(JS_IsString(ret)); const char *s = JS_ToCString(ctx, ret); assert(s); assert(strcmp(s, "testStringPleaseIgnore") == 0); JS_FreeCStringRT(rt, s); JS_FreeValue(ctx, ret); } // string -> cstring + JS_FreeCStringRT, destroying the source value first { JSValue ret = eval(ctx, "\"testStringPleaseIgnore\""); assert(JS_IsString(ret)); const char *s = JS_ToCString(ctx, ret); assert(s); JS_FreeValue(ctx, ret); assert(strcmp(s, "testStringPleaseIgnore") == 0); JS_FreeCStringRT(rt, s); } // number -> cstring + JS_FreeCStringRT { JSValue ret = eval(ctx, "123987"); assert(JS_IsNumber(ret)); const char *s = JS_ToCString(ctx, ret); assert(s); assert(strcmp(s, "123987") == 0); JS_FreeCStringRT(rt, s); JS_FreeValue(ctx, ret); } JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void utf16_string(void) { JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); { JSValue v = JS_NewStringUTF16(ctx, NULL, 0); assert(!JS_IsException(v)); const char *s = JS_ToCString(ctx, v); assert(s); assert(!strcmp(s, "")); JS_FreeCString(ctx, s); JS_FreeValue(ctx, v); } { JSValue v = JS_NewStringUTF16(ctx, (uint16_t[]){'o','k'}, 2); assert(!JS_IsException(v)); const char *s = JS_ToCString(ctx, v); assert(s); assert(!strcmp(s, "ok")); JS_FreeCString(ctx, s); size_t n; const uint16_t *u = JS_ToCStringLenUTF16(ctx, &n, v); assert(u); assert(n == 2); assert(u[0] == 'o'); assert(u[1] == 'k'); JS_FreeCStringUTF16(ctx, u); JS_FreeValue(ctx, v); } { JSValue v = JS_NewStringUTF16(ctx, (uint16_t[]){0xD800}, 1); assert(!JS_IsException(v)); const char *s = JS_ToCString(ctx, v); assert(s); // questionable but surrogates don't map to UTF-8 without WTF-8 assert(!strcmp(s, "\xED\xA0\x80")); JS_FreeCString(ctx, s); size_t n; const uint16_t *u = JS_ToCStringLenUTF16(ctx, &n, v); assert(u); assert(n == 1); assert(u[0] == 0xD800); JS_FreeCStringUTF16(ctx, u); JS_FreeValue(ctx, v); } { JSValue v = JS_NewStringLen(ctx, "ok", 2); // ascii -> ucs assert(!JS_IsException(v)); size_t n; const uint16_t *u = JS_ToCStringLenUTF16(ctx, &n, v); assert(u); assert(n == 2); assert(u[0] == 'o'); assert(u[1] == 'k'); JS_FreeCStringUTF16(ctx, u); JS_FreeValue(ctx, v); } { JSValue v = JS_NewStringUTF16(ctx, NULL, (size_t)INT_MAX + 1); assert(JS_IsException(v)); JSValue e = JS_GetException(ctx); assert(JS_IsError(e)); const char *s = JS_ToCString(ctx, e); assert(s); assert(strstr(s, "invalid string length") != NULL); JS_FreeCString(ctx, s); JS_FreeValue(ctx, e); } { // create wide char slice string, see JS_STRING_SLICE_LEN_MAX JSValue v = eval(ctx, "`\\uD800\\uDC00`.repeat(8192).slice(0, -1)"); assert(!JS_IsException(v)); size_t n; const uint16_t *u = JS_ToCStringLenUTF16(ctx, &n, v); assert(u); assert(n == 2*8192-1); assert(u[0] == 0xD800); assert(u[1] == 0xDC00); JS_FreeCStringUTF16(ctx, u); JS_FreeValue(ctx, v); } JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void weak_map_gc_check(void) { static const char init_code[] = "const map = new WeakMap(); \ function addItem() { \ const k = { \ text: 'a', \ }; \ map.set(k, {k}); \ }"; static const char test_code[] = "addItem()"; JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); JSValue ret = eval(ctx, init_code); assert(!JS_IsException(ret)); JSValue ret_test = eval(ctx, test_code); assert(!JS_IsException(ret_test)); JS_RunGC(rt); JSMemoryUsage memory_usage; JS_ComputeMemoryUsage(rt, &memory_usage); for (int i = 0; i < 3; i++) { JSValue ret_test2 = eval(ctx, test_code); assert(!JS_IsException(ret_test2)); JS_RunGC(rt); JSMemoryUsage memory_usage2; JS_ComputeMemoryUsage(rt, &memory_usage2); assert(memory_usage.memory_used_count == memory_usage2.memory_used_count); assert(memory_usage.memory_used_size == memory_usage2.memory_used_size); JS_FreeValue(ctx, ret_test2); } JS_FreeValue(ctx, ret); JS_FreeValue(ctx, ret_test); JS_FreeContext(ctx); JS_FreeRuntime(rt); } struct { int hook_type_call_count[4]; } promise_hook_state; static void promise_hook_cb(JSContext *ctx, JSPromiseHookType type, JSValueConst promise, JSValueConst parent_promise, void *opaque) { assert(type == JS_PROMISE_HOOK_INIT || type == JS_PROMISE_HOOK_BEFORE || type == JS_PROMISE_HOOK_AFTER || type == JS_PROMISE_HOOK_RESOLVE); promise_hook_state.hook_type_call_count[type]++; assert(opaque == (void *)&promise_hook_state); if (!JS_IsUndefined(parent_promise)) { JSValue global_object = JS_GetGlobalObject(ctx); JS_SetPropertyStr(ctx, global_object, "actual", JS_DupValue(ctx, parent_promise)); JS_FreeValue(ctx, global_object); } } static void promise_hook(void) { int *cc = promise_hook_state.hook_type_call_count; JSContext *unused; JSRuntime *rt = new_runtime(); //JS_SetDumpFlags(rt, JS_DUMP_PROMISE); JS_SetPromiseHook(rt, promise_hook_cb, &promise_hook_state); JSContext *ctx = JS_NewContext(rt); JSValue global_object = JS_GetGlobalObject(ctx); { // empty module; creates an outer and inner module promise; // JS_Eval returns the outer promise JSValue ret = JS_Eval(ctx, "", 0, "", JS_EVAL_TYPE_MODULE); assert(!JS_IsException(ret)); assert(JS_IsPromise(ret)); assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); JS_FreeValue(ctx, ret); assert(2 == cc[JS_PROMISE_HOOK_INIT]); assert(0 == cc[JS_PROMISE_HOOK_BEFORE]); assert(0 == cc[JS_PROMISE_HOOK_AFTER]); assert(2 == cc[JS_PROMISE_HOOK_RESOLVE]); assert(!JS_IsJobPending(rt)); } memset(&promise_hook_state, 0, sizeof(promise_hook_state)); { // module with unresolved promise; the outer and inner module promises // are resolved but not the user's promise static const char code[] = "new Promise(() => {})"; JSValue ret = JS_Eval(ctx, code, strlen(code), "", JS_EVAL_TYPE_MODULE); assert(!JS_IsException(ret)); assert(JS_IsPromise(ret)); assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); // outer module promise JS_FreeValue(ctx, ret); assert(3 == cc[JS_PROMISE_HOOK_INIT]); assert(0 == cc[JS_PROMISE_HOOK_BEFORE]); assert(0 == cc[JS_PROMISE_HOOK_AFTER]); assert(2 == cc[JS_PROMISE_HOOK_RESOLVE]); // outer and inner module promise assert(!JS_IsJobPending(rt)); } memset(&promise_hook_state, 0, sizeof(promise_hook_state)); { // module with resolved promise static const char code[] = "new Promise((resolve,reject) => resolve())"; JSValue ret = JS_Eval(ctx, code, strlen(code), "", JS_EVAL_TYPE_MODULE); assert(!JS_IsException(ret)); assert(JS_IsPromise(ret)); assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); // outer module promise JS_FreeValue(ctx, ret); assert(3 == cc[JS_PROMISE_HOOK_INIT]); assert(0 == cc[JS_PROMISE_HOOK_BEFORE]); assert(0 == cc[JS_PROMISE_HOOK_AFTER]); assert(3 == cc[JS_PROMISE_HOOK_RESOLVE]); assert(!JS_IsJobPending(rt)); } memset(&promise_hook_state, 0, sizeof(promise_hook_state)); { // module with rejected promise static const char code[] = "new Promise((resolve,reject) => reject())"; JSValue ret = JS_Eval(ctx, code, strlen(code), "", JS_EVAL_TYPE_MODULE); assert(!JS_IsException(ret)); assert(JS_IsPromise(ret)); assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); // outer module promise JS_FreeValue(ctx, ret); assert(3 == cc[JS_PROMISE_HOOK_INIT]); assert(0 == cc[JS_PROMISE_HOOK_BEFORE]); assert(0 == cc[JS_PROMISE_HOOK_AFTER]); assert(2 == cc[JS_PROMISE_HOOK_RESOLVE]); assert(!JS_IsJobPending(rt)); } memset(&promise_hook_state, 0, sizeof(promise_hook_state)); { // module with promise chain static const char code[] = "globalThis.count = 0;" "globalThis.actual = undefined;" // set by promise_hook_cb "globalThis.expected = new Promise(resolve => resolve());" "expected.then(_ => count++)"; JSValue ret = JS_Eval(ctx, code, strlen(code), "", JS_EVAL_TYPE_MODULE); assert(!JS_IsException(ret)); assert(JS_IsPromise(ret)); assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); // outer module promise JS_FreeValue(ctx, ret); assert(4 == cc[JS_PROMISE_HOOK_INIT]); assert(0 == cc[JS_PROMISE_HOOK_BEFORE]); assert(0 == cc[JS_PROMISE_HOOK_AFTER]); assert(3 == cc[JS_PROMISE_HOOK_RESOLVE]); JSValue v = JS_GetPropertyStr(ctx, global_object, "count"); assert(!JS_IsException(v)); int32_t count; assert(0 == JS_ToInt32(ctx, &count, v)); assert(0 == count); JS_FreeValue(ctx, v); assert(JS_IsJobPending(rt)); assert(1 == JS_ExecutePendingJob(rt, &unused)); assert(!JS_HasException(ctx)); assert(4 == cc[JS_PROMISE_HOOK_INIT]); assert(0 == cc[JS_PROMISE_HOOK_BEFORE]); assert(0 == cc[JS_PROMISE_HOOK_AFTER]); assert(4 == cc[JS_PROMISE_HOOK_RESOLVE]); assert(!JS_IsJobPending(rt)); v = JS_GetPropertyStr(ctx, global_object, "count"); assert(!JS_IsException(v)); assert(0 == JS_ToInt32(ctx, &count, v)); assert(1 == count); JS_FreeValue(ctx, v); JSValue actual = JS_GetPropertyStr(ctx, global_object, "actual"); JSValue expected = JS_GetPropertyStr(ctx, global_object, "expected"); assert(!JS_IsException(actual)); assert(!JS_IsException(expected)); assert(JS_IsSameValue(ctx, actual, expected)); JS_FreeValue(ctx, actual); JS_FreeValue(ctx, expected); } memset(&promise_hook_state, 0, sizeof(promise_hook_state)); { // module with thenable; fires before and after hooks static const char code[] = "new Promise(resolve => resolve({then(resolve){ resolve() }}))"; JSValue ret = JS_Eval(ctx, code, strlen(code), "", JS_EVAL_TYPE_MODULE); assert(!JS_IsException(ret)); assert(JS_IsPromise(ret)); assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); // outer module promise JS_FreeValue(ctx, ret); assert(3 == cc[JS_PROMISE_HOOK_INIT]); assert(0 == cc[JS_PROMISE_HOOK_BEFORE]); assert(0 == cc[JS_PROMISE_HOOK_AFTER]); assert(2 == cc[JS_PROMISE_HOOK_RESOLVE]); assert(JS_IsJobPending(rt)); assert(1 == JS_ExecutePendingJob(rt, &unused)); assert(!JS_HasException(ctx)); assert(3 == cc[JS_PROMISE_HOOK_INIT]); assert(1 == cc[JS_PROMISE_HOOK_BEFORE]); assert(1 == cc[JS_PROMISE_HOOK_AFTER]); assert(3 == cc[JS_PROMISE_HOOK_RESOLVE]); assert(!JS_IsJobPending(rt)); } JS_FreeValue(ctx, global_object); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void dump_memory_usage(void) { JSMemoryUsage stats; JSRuntime *rt = NULL; JSContext *ctx = NULL; rt = new_runtime(); ctx = JS_NewContext(rt); //JS_SetDumpFlags(rt, JS_DUMP_PROMISE); static const char code[] = "globalThis.count = 0;" "globalThis.actual = undefined;" // set by promise_hook_cb "globalThis.expected = new Promise(resolve => resolve());" "expected.then(_ => count++)"; JSValue evalVal = JS_Eval(ctx, code, strlen(code), "", 0); JS_FreeValue(ctx, evalVal); FILE *temp = tmpfile(); assert(temp != NULL); JS_ComputeMemoryUsage(rt, &stats); JS_DumpMemoryUsage(temp, &stats, rt); // JS_DumpMemoryUsage(stdout, &stats, rt); fclose(temp); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void new_errors(void) { typedef struct { const char name[16]; JSValue (*func)(JSContext *, const char *, ...); } Entry; static const Entry entries[] = { {"Error", JS_NewPlainError}, {"InternalError", JS_NewInternalError}, {"RangeError", JS_NewRangeError}, {"ReferenceError", JS_NewReferenceError}, {"SyntaxError", JS_NewSyntaxError}, {"TypeError", JS_NewTypeError}, }; const Entry *e; JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); for (e = entries; e < endof(entries); e++) { JSValue obj = (*e->func)(ctx, "the %s", "needle"); assert(!JS_IsException(obj)); assert(JS_IsObject(obj)); assert(JS_IsError(obj)); const char *haystack = JS_ToCString(ctx, obj); char needle[256]; snprintf(needle, sizeof(needle), "%s: the needle", e->name); assert(strstr(haystack, needle)); JS_FreeCString(ctx, haystack); JSValue stack = JS_GetPropertyStr(ctx, obj, "stack"); assert(!JS_IsException(stack)); assert(JS_IsString(stack)); JS_FreeValue(ctx, stack); JS_FreeValue(ctx, obj); } JS_FreeContext(ctx); JS_FreeRuntime(rt); } // JS_NewContext() already installs DOMException by way of // JS_AddIntrinsicAToB(), so a host that also calls JS_AddIntrinsicDOMException() // explicitly installs it twice. The second install must release the prototype // the first one left in ctx->class_proto[] instead of overwriting the slot; // new_runtime() aborts at JS_FreeRuntime() if it does not. static void dom_exception_added_twice(void) { JSValue ret; const char *s; int i; JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); // the implicit install left a working DOMException behind ret = eval(ctx, "DOMException.prototype === " "Object.getPrototypeOf(new DOMException)"); assert(JS_ToBool(ctx, ret) == true); JS_FreeValue(ctx, ret); // installing it again, repeatedly, must not leak the previous prototype for (i = 0; i < 3; i++) assert(JS_AddIntrinsicDOMException(ctx) == 0); // and the class prototype still matches the global constructor, i.e. the // slot tracks the newest install rather than a freed or stale object ret = eval(ctx, "DOMException.prototype === " "Object.getPrototypeOf(new DOMException)"); assert(JS_ToBool(ctx, ret) == true); JS_FreeValue(ctx, ret); ret = eval(ctx, "new DOMException('m', 'InvalidCharacterError').name"); assert(!JS_IsException(ret)); s = JS_ToCString(ctx, ret); assert(s); assert(!strcmp(s, "InvalidCharacterError")); JS_FreeCString(ctx, s); JS_FreeValue(ctx, ret); // the internally thrown DOMExceptions pick up the current prototype too ret = eval(ctx, "try { atob('a') } catch (e) { " " e instanceof DOMException && e.name } "); assert(!JS_IsException(ret)); s = JS_ToCString(ctx, ret); assert(s); assert(!strcmp(s, "InvalidCharacterError")); JS_FreeCString(ctx, s); JS_FreeValue(ctx, ret); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void backtrace_oom_callsite_array(void) { static const char setup_code[] = "Error.prepareStackTrace = (e, frames) => frames;\n" "globalThis.f = () => new Error();\n"; JSValue global_object, func, ret; JSMemoryUsage stats; uint32_t headroom; JSRuntime *rt; JSContext *ctx; rt = new_runtime(); ctx = JS_NewContext(rt); global_object = JS_GetGlobalObject(ctx); ret = eval(ctx, setup_code); assert(!JS_IsException(ret)); JS_FreeValue(ctx, ret); func = JS_GetPropertyStr(ctx, global_object, "f"); assert(JS_IsFunction(ctx, func)); for (headroom = 0; headroom < 2048; headroom++) { JS_ComputeMemoryUsage(rt, &stats); JS_SetMemoryLimit(rt, (size_t)stats.malloc_size + headroom); ret = JS_Call(ctx, func, JS_UNDEFINED, 0, NULL); JS_SetMemoryLimit(rt, 0); JS_FreeValue(ctx, ret); JS_FreeValue(ctx, JS_GetException(ctx)); } JS_FreeValue(ctx, func); JS_FreeValue(ctx, global_object); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void large_allocation_accounting(void) { static const size_t block_size = 1024 * 1024; JSMemoryUsage before, after; JSValue ret, exception; JSRuntime *rt; JSContext *ctx; const char *str; rt = new_runtime(); ctx = JS_NewContext(rt); JS_ComputeMemoryUsage(rt, &before); ret = eval(ctx, "globalThis.a = new Uint8Array(1024 * 1024)"); assert(!JS_IsException(ret)); JS_FreeValue(ctx, ret); JS_ComputeMemoryUsage(rt, &after); assert(after.malloc_size - before.malloc_size >= (int64_t)block_size); JS_ComputeMemoryUsage(rt, &before); JS_SetMemoryLimit(rt, (size_t)before.malloc_size + 4 * block_size); ret = eval(ctx, "globalThis.a = [];\n" "for (let i = 0; i < 64; i++) {\n" " // OOM exception should be uncatchable\n" " try {\n" " a.push(new Uint8Array(1024 * 1024));" " } catch (e) {\n" " throw 'unexpected exception';\n" " }\n" "}"); assert(JS_IsException(ret)); JS_SetMemoryLimit(rt, 0); JS_ComputeMemoryUsage(rt, &after); assert(after.malloc_size - before.malloc_size < (int64_t)(8 * block_size)); exception = JS_GetException(ctx); assert(JS_IsError(exception)); str = JS_ToCString(ctx, exception); assert(str); assert(!strcmp(str, "InternalError: out of memory")); JS_FreeCString(ctx, str); JS_FreeValue(ctx, exception); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void backtrace_oom_current_exception(void) { static const char setup_code[] = "globalThis.f = function() { missing; };\n" "Object.defineProperty(f, 'name', { value: 'x'.repeat(2 * 1024 * 1024) });"; JSMemoryUsage stats; JSValue ret, exception; JSRuntime *rt; JSContext *ctx; rt = new_runtime(); ctx = JS_NewContext(rt); ret = eval(ctx, setup_code); assert(!JS_IsException(ret)); JS_FreeValue(ctx, ret); JS_ComputeMemoryUsage(rt, &stats); JS_SetMemoryLimit(rt, (size_t)stats.malloc_size + 128 * 1024); ret = eval(ctx, "f()"); assert(JS_IsException(ret)); assert(JS_HasException(ctx)); exception = JS_GetException(ctx); JS_FreeValue(ctx, exception); JS_SetMemoryLimit(rt, 0); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void proxy_own_keys_huge_length(void) { static const char setup_code[] = "globalThis.p = new Proxy({}, {\n" " ownKeys() { return { length: 0x20000000 }; },\n" "});"; JSMemoryUsage stats; JSValue ret, exception; JSRuntime *rt; JSContext *ctx; const char *str; rt = new_runtime(); ctx = JS_NewContext(rt); ret = eval(ctx, setup_code); assert(!JS_IsException(ret)); JS_FreeValue(ctx, ret); JS_ComputeMemoryUsage(rt, &stats); JS_SetMemoryLimit(rt, (size_t)stats.malloc_size + 128 * 1024); ret = eval(ctx, "Object.keys(p)"); assert(JS_IsException(ret)); JS_FreeValue(ctx, ret); exception = JS_GetException(ctx); str = JS_ToCString(ctx, exception); assert(str); /* the trap result has no index 0, so the first key must be rejected */ assert(!strcmp(str, "TypeError: proxy: properties must be strings or symbols")); JS_FreeCString(ctx, str); JS_FreeValue(ctx, exception); JS_SetMemoryLimit(rt, 0); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static int gop_get_own_property(JSContext *ctx, JSPropertyDescriptor *desc, JSValueConst obj, JSAtom prop) { const char *name; int found; found = 0; name = JS_AtomToCString(ctx, prop); if (!name) return -1; if (!strcmp(name, "answer")) { found = 1; *desc = (JSPropertyDescriptor){ .value = JS_NewInt32(ctx, 42), .flags = JS_PROP_C_W_E | JS_PROP_HAS_VALUE, }; } JS_FreeCString(ctx, name); return found; } static void global_object_prototype(void) { static const char code[] = "answer"; JSValue global_object, proto, ret; JSClassID class_id; JSRuntime *rt; JSContext *ctx; int32_t answer; int res; { rt = new_runtime(); ctx = JS_NewContext(rt); proto = JS_NewObject(ctx); assert(JS_IsObject(proto)); JSCFunctionListEntry prop = JS_PROP_INT32_DEF("answer", 42, JS_PROP_C_W_E); JS_SetPropertyFunctionList(ctx, proto, &prop, 1); global_object = JS_GetGlobalObject(ctx); res = JS_SetPrototype(ctx, global_object, proto); assert(res == true); JS_FreeValue(ctx, global_object); JS_FreeValue(ctx, proto); ret = eval(ctx, code); assert(!JS_IsException(ret)); assert(JS_IsNumber(ret)); res = JS_ToInt32(ctx, &answer, ret); assert(res == 0); assert(answer == 42); JS_FreeValue(ctx, ret); JS_FreeContext(ctx); JS_FreeRuntime(rt); } { JSClassExoticMethods exotic = (JSClassExoticMethods){ .get_own_property = gop_get_own_property, }; JSClassDef def = (JSClassDef){ .class_name = "Global Object", .exotic = &exotic, }; rt = new_runtime(); class_id = 0; JS_NewClassID(rt, &class_id); res = JS_NewClass(rt, class_id, &def); assert(res == 0); ctx = JS_NewContext(rt); proto = JS_NewObjectClass(ctx, class_id); assert(JS_IsObject(proto)); global_object = JS_GetGlobalObject(ctx); res = JS_SetPrototype(ctx, global_object, proto); assert(res == true); JS_FreeValue(ctx, global_object); JS_FreeValue(ctx, proto); ret = eval(ctx, code); assert(!JS_IsException(ret)); assert(JS_IsNumber(ret)); res = JS_ToInt32(ctx, &answer, ret); assert(res == 0); assert(answer == 42); JS_FreeValue(ctx, ret); JS_FreeContext(ctx); JS_FreeRuntime(rt); } } // https://github.com/quickjs-ng/quickjs/issues/1178 static void slice_string_tocstring(void) { JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); JSValue ret = eval(ctx, "'.'.repeat(16384).slice(1, -1)"); assert(!JS_IsException(ret)); assert(JS_IsString(ret)); const char *str = JS_ToCString(ctx, ret); assert(strlen(str) == 16382); JS_FreeCString(ctx, str); JS_FreeValue(ctx, ret); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void immutable_array_buffer(void) { JSValue obj, ret; bool immutable; char buf[96]; int i, v; JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); for (i = 0; i < 2; i++) { obj = JS_NewObject(ctx); immutable = (i == 0); assert(-1 == JS_IsImmutableArrayBuffer(JS_NULL)); assert(-1 == JS_IsImmutableArrayBuffer(JS_UNDEFINED)); assert(-1 == JS_IsImmutableArrayBuffer(obj)); assert(-1 == JS_SetImmutableArrayBuffer(JS_NULL, immutable)); assert(-1 == JS_SetImmutableArrayBuffer(JS_UNDEFINED, immutable)); assert(-1 == JS_SetImmutableArrayBuffer(obj, immutable)); JS_FreeValue(ctx, obj); } obj = eval(ctx, "globalThis.ab = new ArrayBuffer(1)"); assert(!JS_IsException(obj)); assert(JS_IsArrayBuffer(obj)); assert(!JS_IsImmutableArrayBuffer(obj)); for (i = 1; i <= 3; i++) { immutable = (i == 2); if (i > 1) JS_SetImmutableArrayBuffer(obj, immutable); assert(immutable == JS_IsImmutableArrayBuffer(obj)); snprintf(buf, sizeof(buf), "var ta = new Uint8Array(ab); ta[0] = %d; ta[0]", i); ret = eval(ctx, buf); assert(!JS_IsException(ret)); assert(JS_IsNumber(ret)); assert(0 == JS_ToInt32(ctx, &v, ret)); JS_FreeValue(ctx, ret); if (immutable) { assert(v != i); } else { assert(v == i); } } JS_FreeValue(ctx, obj); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void *sab_test_alloc(void *opaque, size_t size) { return malloc(size); } static void sab_test_free(void *opaque, void *ptr) { free(ptr); } static void shared_array_buffer_growth(void) { JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); JSValue ret, exception; ret = eval(ctx, "new SharedArrayBuffer(16)"); assert(!JS_IsException(ret)); JS_FreeValue(ctx, ret); ret = eval(ctx, "const sab = new SharedArrayBuffer(16, { maxByteLength: 16 });" "sab.grow(16);" "sab.byteLength === 16 && sab.maxByteLength === 16"); assert(!JS_IsException(ret)); assert(JS_IsBool(ret)); assert(JS_VALUE_GET_BOOL(ret)); JS_FreeValue(ctx, ret); ret = eval(ctx, "new SharedArrayBuffer(16, { maxByteLength: 16384 })"); assert(JS_IsException(ret)); assert(JS_HasException(ctx)); exception = JS_GetException(ctx); assert(JS_IsError(exception)); JS_FreeValue(ctx, exception); JS_FreeContext(ctx); JS_FreeRuntime(rt); JSSharedArrayBufferFunctions funcs = { .sab_alloc = sab_test_alloc, .sab_free = sab_test_free, .sab_dup = NULL, .sab_opaque = NULL, }; rt = new_runtime(); JS_SetSharedArrayBufferFunctions(rt, &funcs); ctx = JS_NewContext(rt); ret = eval(ctx, "const sab = new SharedArrayBuffer(16, { maxByteLength: 16384 });" "const u8 = new Uint8Array(sab);" "sab.grow(16384);" "u8[1024] === 0 && u8.byteLength === 16384"); assert(!JS_IsException(ret)); assert(JS_IsBool(ret)); assert(JS_VALUE_GET_BOOL(ret)); JS_FreeValue(ctx, ret); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void get_uint8array(void) { JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); JSValue val; uint8_t *p; size_t size; uint8_t buf[3] = { 1, 2, 3 }; val = eval(ctx, "new Uint8Array(0)"); assert(!JS_IsException(val)); p = JS_GetUint8Array(ctx, &size, val); assert(p != NULL); assert(size == 0); JS_FreeValue(ctx, val); val = JS_NewUint8Array(ctx, NULL, 0, NULL, NULL, false); assert(!JS_IsException(val)); p = JS_GetUint8Array(ctx, &size, val); assert(p != NULL); assert(size == 0); JS_FreeValue(ctx, val); val = JS_NewUint8ArrayCopy(ctx, NULL, 0); assert(!JS_IsException(val)); p = JS_GetUint8Array(ctx, &size, val); assert(p != NULL); assert(size == 0); JS_FreeValue(ctx, val); val = JS_NewUint8ArrayCopy(ctx, buf, sizeof(buf)); assert(!JS_IsException(val)); p = JS_GetUint8Array(ctx, &size, val); assert(p != NULL); assert(size == 3); assert(p[0] == 1 && p[1] == 2 && p[2] == 3); JS_FreeValue(ctx, val); val = eval(ctx, "new Uint8Array([4, 5, 6])"); assert(!JS_IsException(val)); p = JS_GetUint8Array(ctx, &size, val); assert(p != NULL); assert(size == 3); assert(p[0] == 4 && p[1] == 5 && p[2] == 6); JS_FreeValue(ctx, val); val = eval(ctx, "new Int32Array(4)"); assert(!JS_IsException(val)); p = JS_GetUint8Array(ctx, &size, val); assert(p == NULL); assert(size == 0); JS_FreeValue(ctx, val); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void new_symbol(void) { JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); JSValue global = JS_GetGlobalObject(ctx); JSValue sym, ret; /* Local symbol with NULL description -> Symbol() */ sym = JS_NewSymbol(ctx, NULL, false); assert(!JS_IsException(sym)); assert(JS_IsSymbol(sym)); JS_SetPropertyStr(ctx, global, "sym_local_null", sym); ret = eval(ctx, "typeof sym_local_null === 'symbol' && sym_local_null.description === undefined && Symbol.keyFor(sym_local_null) === undefined"); assert(JS_IsBool(ret)); assert(JS_VALUE_GET_BOOL(ret)); JS_FreeValue(ctx, ret); /* Global symbol with NULL description -> Symbol.for() -> Symbol.for('undefined') */ sym = JS_NewSymbol(ctx, NULL, true); assert(!JS_IsException(sym)); assert(JS_IsSymbol(sym)); JS_SetPropertyStr(ctx, global, "sym_global_null", sym); ret = eval(ctx, "typeof sym_global_null === 'symbol' && sym_global_null.description === 'undefined' && Symbol.keyFor(sym_global_null) === 'undefined'"); assert(JS_IsBool(ret)); assert(JS_VALUE_GET_BOOL(ret)); JS_FreeValue(ctx, ret); /* Local symbol with description -> Symbol('test_local') */ sym = JS_NewSymbol(ctx, "test_local", false); assert(!JS_IsException(sym)); assert(JS_IsSymbol(sym)); JS_SetPropertyStr(ctx, global, "sym_local_str", sym); ret = eval(ctx, "sym_local_str.description === 'test_local' && Symbol.keyFor(sym_local_str) === undefined"); assert(JS_IsBool(ret)); assert(JS_VALUE_GET_BOOL(ret)); JS_FreeValue(ctx, ret); /* Global symbol with description -> Symbol.for('test_global') */ sym = JS_NewSymbol(ctx, "test_global", true); assert(!JS_IsException(sym)); assert(JS_IsSymbol(sym)); JS_SetPropertyStr(ctx, global, "sym_global_str", sym); ret = eval(ctx, "sym_global_str.description === 'test_global' && Symbol.keyFor(sym_global_str) === 'test_global'"); assert(JS_IsBool(ret)); assert(JS_VALUE_GET_BOOL(ret)); JS_FreeValue(ctx, ret); JS_FreeValue(ctx, global); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void bulk_free_macros(void) { JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); JSValue val0 = JS_NewObject(ctx); JSValue val1 = JS_NewArray(ctx); JSValue val2 = JS_NewDate(ctx, 0.0); JSMemoryUsage mem_usage; JS_ComputeMemoryUsage(rt, &mem_usage); int obj_count = mem_usage.obj_count; JS_FreeValues(ctx, val0, val1); JS_FreeValuesRT(rt, val2); // silly atoms to ensure qjs doesn't find built-ins that match JSAtom atom0 = JS_NewAtom(ctx, "ALL!!"); JSAtom atom1 = JS_NewAtom(ctx, "YOUR!!"); JSAtom atom2 = JS_NewAtom(ctx, "ATOMS!!"); JSAtom atom3 = JS_NewAtom(ctx, "ARE!!"); JSAtom atom4 = JS_NewAtom(ctx, "BELONG!!"); JSAtom atom5 = JS_NewAtom(ctx, "TO US!!"); JS_ComputeMemoryUsage(rt, &mem_usage); assert((obj_count - 3) == mem_usage.obj_count); int atom_count = mem_usage.atom_count; JS_FreeAtoms(ctx, atom1, atom3, atom4); JS_FreeAtomsRT(rt, atom0, atom2, atom5); JS_ComputeMemoryUsage(rt, &mem_usage); assert((atom_count - 6) == mem_usage.atom_count); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static int detach_free_count; static void *detach_realloc_func(JSRuntime *rt, void *opaque, void *ptr, size_t size) { if (size == 0) { detach_free_count++; free(ptr); return NULL; } return realloc(ptr, size); } static void detach_array_buffer_free_once(void) { JSValue obj; uint8_t *buf; JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); detach_free_count = 0; buf = malloc(8); obj = JS_NewArrayBuffer(ctx, buf, 8, /*max_len*/0, detach_realloc_func, NULL, false); assert(JS_IsArrayBuffer(obj)); /* detaching releases the backing store exactly once */ JS_DetachArrayBuffer(ctx, obj); assert(detach_free_count == 1); /* finalizing the detached buffer must not release it a second time */ JS_FreeValue(ctx, obj); assert(detach_free_count == 1); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void *realloc_array_buffer_data(JSRuntime *rt, void *opaque, void *ptr, size_t size) { if (size == 0) { free(ptr); return NULL; } return realloc(ptr, size); } static void typed_array_sort_index_overflow(void) { const size_t len = 0x40000010; /* 2**30 + 16 */ JSMemoryUsage stats; JSValue global, buffer, ret; JSRuntime *rt; JSContext *ctx; uint8_t *buf; /* the data is never read, but keep it real memory so a regression corrupts the heap in a way ASan can see rather than segfaulting */ buf = calloc(1, len); rt = new_runtime(); ctx = JS_NewContext(rt); buffer = JS_NewArrayBuffer(ctx, buf, len, /*max_len*/0, realloc_array_buffer_data, NULL, false); assert(JS_IsArrayBuffer(buffer)); global = JS_GetGlobalObject(ctx); JS_SetPropertyStr(ctx, global, "buf", buffer); JS_FreeValue(ctx, global); /* Cap memory so that the correctly computed 4 GB index array also fails to allocate on 64 bit platforms; the undersized allocation a 32 bit platform computes fits well within the cap. */ JS_ComputeMemoryUsage(rt, &stats); JS_SetMemoryLimit(rt, (size_t)stats.malloc_size + 1024 * 1024); ret = eval(ctx, "new Uint8Array(buf).sort((a, b) => a - b)"); assert(JS_IsException(ret)); assert(JS_HasException(ctx)); JS_FreeValue(ctx, ret); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static int transfer_free_count; static int transfer_realloc_count; static void *transfer_realloc_func(JSRuntime *rt, void *opaque, void *ptr, size_t size) { if (size == 0) { transfer_free_count++; free(ptr); return NULL; } transfer_realloc_count++; return realloc(ptr, size); } /* a realloc function that can only free, never resize */ static void *failing_realloc_func(JSRuntime *rt, void *opaque, void *ptr, size_t size) { if (size == 0) { transfer_free_count++; free(ptr); } return NULL; } static JSValue make_external_ab(JSContext *ctx, uint8_t **pbuf, size_t len, size_t max_len, JSReallocArrayBufferDataFunc *realloc_func) { uint8_t *buf = malloc(max_len > len ? max_len : len); memset(buf, 0xAB, len); JSValue obj = JS_NewArrayBuffer(ctx, buf, len, max_len, realloc_func, NULL, /*is_shared*/false); assert(JS_IsArrayBuffer(obj)); JSValue global = JS_GetGlobalObject(ctx); JS_SetPropertyStr(ctx, global, "ab", JS_DupValue(ctx, obj)); JS_FreeValue(ctx, global); *pbuf = buf; return obj; } static void transfer_external_array_buffer(void) { JSValue obj, ret, exc, det; const char *s; uint8_t *buf, *p; size_t i, size; JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); // same-length transfer succeeds and reuses the backing store; each variant // detaches the source, so use a fresh buffer per iteration { static const char *const exprs[] = { "ab.transfer()", "ab.transfer(8)", "ab.transferToFixedLength()", "ab.transferToFixedLength(8)", "ab.transferToImmutable()", }; for (i = 0; i < countof(exprs); i++) { transfer_free_count = 0; transfer_realloc_count = 0; obj = make_external_ab(ctx, &buf, 8, /*max_len*/0, transfer_realloc_func); ret = eval(ctx, exprs[i]); assert(!JS_IsException(ret)); assert(JS_IsArrayBuffer(ret)); assert(transfer_realloc_count == 0); p = JS_GetArrayBuffer(ctx, &size, ret); assert(p == buf); assert(size == 8); assert(p[0] == 0xAB && p[7] == 0xAB); det = eval(ctx, "ab.detached"); assert(JS_IsBool(det)); assert(JS_VALUE_GET_BOOL(det)); JS_FreeValue(ctx, det); assert(transfer_free_count == 0); JS_FreeValue(ctx, ret); assert(transfer_free_count == 1); ret = eval(ctx, "globalThis.ab = undefined"); JS_FreeValue(ctx, ret); JS_FreeValue(ctx, obj); assert(transfer_free_count == 1); } } // a length-changing transfer resizes the external store through the // realloc function and hands it over to the new ArrayBuffer { static const struct { const char *code; size_t len; } cases[] = { { "ab.transfer(4)", 4 }, { "ab.transfer(16)", 16 }, { "ab.transferToFixedLength(4)", 4 }, { "ab.transferToImmutable(16)", 16 }, }; for (i = 0; i < countof(cases); i++) { transfer_free_count = 0; transfer_realloc_count = 0; obj = make_external_ab(ctx, &buf, 8, /*max_len*/0, transfer_realloc_func); ret = eval(ctx, cases[i].code); assert(!JS_IsException(ret)); assert(JS_IsArrayBuffer(ret)); assert(transfer_realloc_count == 1); assert(transfer_free_count == 0); p = JS_GetArrayBuffer(ctx, &size, ret); assert(size == cases[i].len); assert(p[0] == 0xAB && p[3] == 0xAB); if (cases[i].len > 8) assert(p[8] == 0 && p[15] == 0); // the grown tail is zeroed det = eval(ctx, "ab.detached"); assert(JS_IsBool(det)); assert(JS_VALUE_GET_BOOL(det)); JS_FreeValue(ctx, det); // the source handed the store over; releasing the new AB frees it JS_FreeValue(ctx, ret); assert(transfer_free_count == 1); ret = eval(ctx, "globalThis.ab = undefined"); JS_FreeValue(ctx, ret); JS_FreeValue(ctx, obj); assert(transfer_free_count == 1); } } // a transfer that fails to realloc throws and leaves the source alone { transfer_free_count = 0; obj = make_external_ab(ctx, &buf, 8, /*max_len*/0, failing_realloc_func); ret = eval(ctx, "ab.transfer(16)"); assert(JS_IsException(ret)); JS_FreeValue(ctx, ret); exc = JS_GetException(ctx); assert(JS_IsError(exc)); s = JS_ToCString(ctx, exc); assert(s); assert(strstr(s, "out of memory")); JS_FreeCString(ctx, s); JS_FreeValue(ctx, exc); det = eval(ctx, "ab.detached"); assert(JS_IsBool(det)); assert(!JS_VALUE_GET_BOOL(det)); JS_FreeValue(ctx, det); p = JS_GetArrayBuffer(ctx, &size, obj); assert(p == buf && size == 8 && p[0] == 0xAB); assert(transfer_free_count == 0); ret = eval(ctx, "globalThis.ab = undefined"); JS_FreeValue(ctx, ret); JS_FreeValue(ctx, obj); assert(transfer_free_count == 1); } // transfer(0) changes the length but doesn't realloc: it detaches the // source (freeing the external store) and yields a fresh empty AB { transfer_free_count = 0; transfer_realloc_count = 0; obj = make_external_ab(ctx, &buf, 8, /*max_len*/0, transfer_realloc_func); ret = eval(ctx, "ab.transfer(0)"); assert(!JS_IsException(ret)); assert(JS_IsArrayBuffer(ret)); p = JS_GetArrayBuffer(ctx, &size, ret); assert(size == 0); assert(transfer_free_count == 1); JS_FreeValue(ctx, ret); det = eval(ctx, "ab.detached"); assert(JS_IsBool(det)); assert(JS_VALUE_GET_BOOL(det)); JS_FreeValue(ctx, det); ret = eval(ctx, "globalThis.ab = undefined"); JS_FreeValue(ctx, ret); JS_FreeValue(ctx, obj); assert(transfer_free_count == 1); } JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void resize_external_array_buffer(void) { JSValue obj, global, ret, exc, det; const char *s; uint8_t *buf, *p; size_t size; JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); transfer_free_count = 0; transfer_realloc_count = 0; obj = make_external_ab(ctx, &buf, 8, /*max_len*/16, transfer_realloc_func); ret = eval(ctx, "ab.resizable && ab.maxByteLength === 16"); assert(JS_IsBool(ret)); assert(JS_VALUE_GET_BOOL(ret)); JS_FreeValue(ctx, ret); // growing reallocs through the callback and zeroes the new tail ret = eval(ctx, "ab.resize(16), ab.byteLength"); assert(!JS_IsException(ret)); assert(JS_VALUE_GET_INT(ret) == 16); JS_FreeValue(ctx, ret); assert(transfer_realloc_count == 1); p = JS_GetArrayBuffer(ctx, &size, obj); assert(size == 16); assert(p[0] == 0xAB && p[7] == 0xAB); assert(p[8] == 0 && p[15] == 0); // and so does shrinking ret = eval(ctx, "ab.resize(4), ab.byteLength"); assert(!JS_IsException(ret)); assert(JS_VALUE_GET_INT(ret) == 4); JS_FreeValue(ctx, ret); assert(transfer_realloc_count == 2); p = JS_GetArrayBuffer(ctx, &size, obj); assert(size == 4); assert(p[0] == 0xAB && p[3] == 0xAB); // resizing past maxByteLength still throws, without calling the callback ret = eval(ctx, "ab.resize(17)"); assert(JS_IsException(ret)); JS_FreeValue(ctx, ret); exc = JS_GetException(ctx); s = JS_ToCString(ctx, exc); assert(s); assert(strstr(s, "invalid array buffer length")); JS_FreeCString(ctx, s); JS_FreeValue(ctx, exc); assert(transfer_realloc_count == 2); // a resizable external AB carries its maxByteLength across a transfer ret = eval(ctx, "ab.transfer(8)"); assert(!JS_IsException(ret)); assert(transfer_realloc_count == 3); det = eval(ctx, "ab.detached"); assert(JS_IsBool(det)); assert(JS_VALUE_GET_BOOL(det)); JS_FreeValue(ctx, det); global = JS_GetGlobalObject(ctx); JS_SetPropertyStr(ctx, global, "ab2", ret); JS_FreeValue(ctx, global); ret = eval(ctx, "ab2.resizable && ab2.maxByteLength === 16"); assert(JS_IsBool(ret)); assert(JS_VALUE_GET_BOOL(ret)); JS_FreeValue(ctx, ret); ret = eval(ctx, "ab2.resize(16), ab2.byteLength"); assert(!JS_IsException(ret)); assert(JS_VALUE_GET_INT(ret) == 16); JS_FreeValue(ctx, ret); assert(transfer_realloc_count == 4); assert(transfer_free_count == 0); ret = eval(ctx, "globalThis.ab = globalThis.ab2 = undefined"); JS_FreeValue(ctx, ret); JS_FreeValue(ctx, obj); assert(transfer_free_count == 1); JS_FreeContext(ctx); JS_FreeRuntime(rt); } /* hands the buffer over to the runtime allocator, the way quickjs manages the memory of ArrayBuffers it allocates itself */ static void *runtime_realloc_func(JSRuntime *rt, void *opaque, void *ptr, size_t size) { return js_realloc_rt(rt, ptr, size); } static void transfer_default_managed_array_buffer(void) { JSValue obj, global, ret, det; uint8_t *buf, *p; size_t size; JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); buf = js_malloc(ctx, 8); memset(buf, 0xAB, 8); obj = JS_NewArrayBuffer(ctx, buf, 8, /*max_len*/0, runtime_realloc_func, NULL, false); assert(JS_IsArrayBuffer(obj)); global = JS_GetGlobalObject(ctx); JS_SetPropertyStr(ctx, global, "ab", JS_DupValue(ctx, obj)); JS_FreeValue(ctx, global); ret = eval(ctx, "ab.transfer(16)"); assert(!JS_IsException(ret)); assert(JS_IsArrayBuffer(ret)); p = JS_GetArrayBuffer(ctx, &size, ret); assert(size == 16); assert(p[0] == 0xAB && p[7] == 0xAB); assert(p[8] == 0 && p[15] == 0); det = eval(ctx, "ab.detached"); assert(JS_IsBool(det)); assert(JS_VALUE_GET_BOOL(det)); JS_FreeValue(ctx, det); JS_FreeValue(ctx, ret); ret = eval(ctx, "globalThis.ab = undefined"); JS_FreeValue(ctx, ret); JS_FreeValue(ctx, obj); buf = js_malloc(ctx, 8); memset(buf, 0xCD, 8); obj = JS_NewArrayBuffer(ctx, buf, 8, /*max_len*/0, runtime_realloc_func, NULL, false); global = JS_GetGlobalObject(ctx); JS_SetPropertyStr(ctx, global, "ab", JS_DupValue(ctx, obj)); JS_FreeValue(ctx, global); ret = eval(ctx, "ab.transferToFixedLength(4)"); assert(!JS_IsException(ret)); p = JS_GetArrayBuffer(ctx, &size, ret); assert(size == 4); assert(p[0] == 0xCD && p[3] == 0xCD); JS_FreeValue(ctx, ret); ret = eval(ctx, "globalThis.ab = undefined"); JS_FreeValue(ctx, ret); JS_FreeValue(ctx, obj); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void get_class_name(void) { static const struct { const char *code; const char *name; } builtins[] = { { "({})", "Object" }, { "[]", "Array" }, { "new Error()", "Error" }, { "new Date()", "Date" }, { "/re/", "RegExp" }, { "new Map()", "Map" }, { "new ArrayBuffer(0)", "ArrayBuffer" }, { "new Uint8Array(0)", "Uint8Array" }, { "(function(){})", "Function" }, }; JSClassDef def = (JSClassDef){ .class_name = "MyClass" }; JSClassID class_id, unregistered_class_id; JSAtom atom, expected; JSRuntime *rt; JSContext *ctx; const char *s; JSValue obj; size_t i; rt = new_runtime(); class_id = 0; JS_NewClassID(rt, &class_id); assert(0 == JS_NewClass(rt, class_id, &def)); /* handed out by JS_NewClassID() but never passed to JS_NewClass() */ unregistered_class_id = 0; JS_NewClassID(rt, &unregistered_class_id); ctx = JS_NewContext(rt); /* the name of a class registered from C, not its class id */ expected = JS_NewAtom(ctx, "MyClass"); atom = JS_GetClassName(rt, class_id); assert(atom == expected); s = JS_AtomToCString(ctx, atom); assert(s); assert(!strcmp(s, "MyClass")); JS_FreeCString(ctx, s); JS_FreeAtom(ctx, atom); JS_FreeAtom(ctx, expected); /* the names of the built-in classes */ for (i = 0; i < countof(builtins); i++) { obj = eval(ctx, builtins[i].code); assert(!JS_IsException(obj)); atom = JS_GetClassName(rt, JS_GetClassID(obj)); assert(atom != JS_ATOM_NULL); s = JS_AtomToCString(ctx, atom); assert(s); assert(!strcmp(s, builtins[i].name)); JS_FreeCString(ctx, s); JS_FreeAtom(ctx, atom); JS_FreeValue(ctx, obj); } /* class ids without a registered class have no name */ assert(JS_ATOM_NULL == JS_GetClassName(rt, JS_INVALID_CLASS_ID)); assert(JS_ATOM_NULL == JS_GetClassName(rt, unregistered_class_id)); assert(JS_ATOM_NULL == JS_GetClassName(rt, class_id + 4096)); /* the caller owns the returned atom; the class keeps its own reference, so releasing it repeatedly doesn't free the name out from under it */ for (i = 0; i < 64; i++) JS_FreeAtom(ctx, JS_GetClassName(rt, class_id)); atom = JS_GetClassName(rt, class_id); s = JS_AtomToCString(ctx, atom); assert(s); assert(!strcmp(s, "MyClass")); JS_FreeCString(ctx, s); JS_FreeAtom(ctx, atom); /* every registered class has a name, and it is a name rather than a number: returning the class id instead produced a valid-looking atom, either an unrelated predefined one for a small id or the id spelled out in decimal for a large one, so check the whole table rather than the handful of classes spelled out above */ { JSClassID id; int registered = 0; for (id = 1; id < class_id + 16; id++) { char decimal[32]; if (!JS_IsRegisteredClass(rt, id)) continue; registered++; atom = JS_GetClassName(rt, id); assert(atom != JS_ATOM_NULL); s = JS_AtomToCString(ctx, atom); assert(s); // a few internal classes are deliberately unnamed, but none is // named after a number snprintf(decimal, sizeof(decimal), "%u", (unsigned)id); assert(strcmp(s, decimal)); assert(s[0] < '0' || s[0] > '9'); JS_FreeCString(ctx, s); JS_FreeAtom(ctx, atom); } /* the built-ins alone are far more than this */ assert(registered > 20); } /* two classes sharing a name share the interned atom, and the name does not depend on which context asks */ { JSClassDef def2 = (JSClassDef){ .class_name = "MyClass" }; JSClassID class_id2 = 0; JSContext *ctx2; JSAtom a1, a2; JS_NewClassID(rt, &class_id2); assert(0 == JS_NewClass(rt, class_id2, &def2)); assert(class_id2 != class_id); a1 = JS_GetClassName(rt, class_id); a2 = JS_GetClassName(rt, class_id2); assert(a1 == a2); JS_FreeAtom(ctx, a1); JS_FreeAtom(ctx, a2); ctx2 = JS_NewContext(rt); a1 = JS_GetClassName(rt, class_id); a2 = JS_GetClassName(rt, class_id); assert(a1 == a2); JS_FreeAtom(ctx2, a1); JS_FreeAtom(ctx2, a2); JS_FreeContext(ctx2); } JS_FreeContext(ctx); JS_FreeRuntime(rt); } void object_from(void) { JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); JSValue t0 = JS_NewObject(ctx); assert(!JS_IsException(t0)); assert(JS_IsObject(t0)); JSAtom prop = JS_NewAtomLen(ctx, "prop", 4); assert(prop != JS_ATOM_NULL); JSValue val = JS_NULL; JSValue t1 = JS_NewObjectFrom(ctx, 1, &prop, &val); assert(!JS_IsException(t1)); assert(JS_IsObject(t1)); uint32_t old_shape_hash_count = js_std_cmd(/*GetShapeHashCount*/4, rt); JSValue t2 = JS_NewObjectFrom(ctx, 1, &prop, &val); assert(!JS_IsException(t2)); assert(JS_IsObject(t2)); uint32_t new_shape_hash_count = js_std_cmd(/*GetShapeHashCount*/4, rt); assert(old_shape_hash_count == new_shape_hash_count); JS_FreeAtom(ctx, prop); JS_FreeValue(ctx, t2); JS_FreeValue(ctx, t1); JS_FreeValue(ctx, t0); JS_FreeContext(ctx); JS_FreeRuntime(rt); } void add_intrinsic_bigint(void) { JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContextRaw(rt); JS_AddIntrinsicBaseObjects(ctx); JS_AddIntrinsicBigInt(ctx); assert(!JS_HasException(ctx)); JS_FreeContext(ctx); JS_FreeRuntime(rt); } void new_typed_array(void) { JSValueConst argv[4]; JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); uint8_t *buf = js_malloc(ctx, 8); JSValue ab = JS_NewArrayBuffer(ctx, buf, 8, /*max_len*/0, NULL, NULL, false); assert(JS_IsArrayBuffer(ab)); for (int argc = 0; argc < (int)countof(argv); argc++) { JSValue ret = JS_NewTypedArray(ctx, argc, argv, JS_TYPED_ARRAY_UINT8); assert(!JS_IsException(ret)); assert(JS_IsObject(ret)); JS_FreeValue(ctx, ret); argv[argc] = JS_UNDEFINED; argv[0] = ab; } JS_FreeValue(ctx, ab); js_free(ctx, buf); JS_FreeContext(ctx); JS_FreeRuntime(rt); } void private_symbols(void) { JSRuntime *rt = new_runtime(); JSContext *ctx = JS_NewContext(rt); JSValue pub = JS_NewSymbol(ctx, "public", /*is_global*/false); JSValue priv = JS_NewPrivateSymbol(ctx, "private"); assert(JS_IsSymbol(pub)); assert(JS_IsSymbol(priv)); JSValue obj = JS_NewObject(ctx); assert(JS_IsObject(obj)); assert(true == JS_SetPropertyValue(ctx, obj, pub, JS_TRUE, JS_PROP_C_W_E)); assert(true == JS_SetPropertyValue(ctx, obj, priv, JS_FALSE, JS_PROP_C_W_E)); JSValue global_object = JS_GetGlobalObject(ctx); assert(true == JS_SetPropertyStr(ctx, global_object, "o", JS_DupValue(ctx, obj))); JS_FreeValue(ctx, global_object); JSValue result = eval(ctx, "Object.getOwnPropertySymbols(o)"); assert(JS_IsArray(result)); int64_t length = -1; assert(0 == JS_GetLength(ctx, result, &length)); assert(length == 1); JSValue item = JS_GetPropertyUint32(ctx, result, 0); assert(JS_IsSymbol(item)); assert(JS_IsSameValue(ctx, item, pub)); JS_FreeValue(ctx, item); JS_FreeValue(ctx, result); result = JS_GetPropertyValue(ctx, obj, JS_DupValue(ctx, pub)); assert(JS_IsBool(result)); assert(JS_IsSameValue(ctx, result, JS_TRUE)); result = JS_GetPropertyValue(ctx, obj, JS_DupValue(ctx, priv)); assert(JS_IsBool(result)); assert(JS_IsSameValue(ctx, result, JS_FALSE)); JS_FreeValue(ctx, obj); JS_FreeContext(ctx); JS_FreeRuntime(rt); } // https://github.com/quickjs-ng/quickjs/issues/1181 // JS_NewContext() must fail cleanly, not crash or leak, whichever of its // allocations runs out of memory. static void new_context_low_memory(void) { JSMemoryUsage stats; size_t base, headroom; JSRuntime *rt; JSContext *ctx; int all, ok; all = ok = 0; rt = new_runtime(); JS_ComputeMemoryUsage(rt, &stats); base = (size_t)stats.malloc_size; // Small steps first, so that each of the first allocations gets its turn // to fail, then bigger ones until a few contexts fit. for (headroom = 0; ok < 4 && headroom < 4 * 1024 * 1024; headroom += headroom < 8192 ? 8 : 1024) { JS_SetMemoryLimit(rt, base + headroom); ctx = JS_NewContext(rt); // expected to fail, not to crash if (ctx) { JS_FreeContext(ctx); ok++; } all++; } JS_SetMemoryLimit(rt, 0); JS_FreeRuntime(rt); assert(ok > 0); // expect some successes... assert(ok < all); // ...but not all } // running out of memory in JSON.stringify must not leak the array element // that was being converted static void json_stringify_oom(void) { static const char setup_code[] = "globalThis.f = () => JSON.stringify([{}]);\n" "f();\n"; // JSON.stringify is set up on first access, do it now JSValue global_object, func, ret; JSMemoryUsage stats; uint32_t headroom; JSRuntime *rt; JSContext *ctx; rt = new_runtime(); ctx = JS_NewContext(rt); global_object = JS_GetGlobalObject(ctx); ret = eval(ctx, setup_code); assert(!JS_IsException(ret)); JS_FreeValue(ctx, ret); func = JS_GetPropertyStr(ctx, global_object, "f"); assert(JS_IsFunction(ctx, func)); for (headroom = 0; headroom < 2048; headroom++) { JS_ComputeMemoryUsage(rt, &stats); JS_SetMemoryLimit(rt, (size_t)stats.malloc_size + headroom); ret = JS_Call(ctx, func, JS_UNDEFINED, 0, NULL); JS_SetMemoryLimit(rt, 0); JS_FreeValue(ctx, ret); JS_FreeValue(ctx, JS_GetException(ctx)); } JS_FreeValue(ctx, func); JS_FreeValue(ctx, global_object); JS_FreeContext(ctx); JS_FreeRuntime(rt); } struct dynamic_import_state { int import_calls; int attribute_calls; bool defer; char *basename; char *filename; JSValue token; JSValue attributes; }; static JSModuleDef *dynamic_import_loader(JSContext *ctx, const char *name, void *opaque, JSValueConst attributes) { struct dynamic_import_state *s = opaque; if (!strcmp(name, "missing")) { JS_ThrowReferenceError(ctx, "no such module"); return NULL; } if (JS_IsObject(attributes)) { JSValue type = JS_GetPropertyStr(ctx, attributes, "type"); const char *str = JS_ToCString(ctx, type); assert(str); assert(!strcmp(str, "x")); JS_FreeCString(ctx, str); JS_FreeValue(ctx, type); s->attribute_calls++; } static const char code[] = "export const v = 42"; JSValue ret = JS_Eval(ctx, code, strlen(code), name, JS_EVAL_TYPE_MODULE|JS_EVAL_FLAG_COMPILE_ONLY); assert(!JS_IsException(ret)); JSModuleDef *m = JS_VALUE_GET_PTR(ret); JS_FreeValue(ctx, ret); return m; } static void dynamic_import_func(JSContext *ctx, const char *basename, const char *filename, JSValue token, JSValueConst attributes, void *opaque) { struct dynamic_import_state *s = opaque; s->import_calls++; if (s->defer) { s->basename = strdup(basename); s->filename = strdup(filename); assert(s->basename); assert(s->filename); s->token = token; s->attributes = JS_DupValue(ctx, attributes); return; } JSValue exception = JS_UNDEFINED; JSValue promise = JS_LoadModule(ctx, basename, filename, attributes); if (JS_IsException(promise)) exception = JS_GetException(ctx); assert(0 == JS_FinishDynamicImport(ctx, token, promise, exception)); } static JSRuntime *dynamic_import_runtime(struct dynamic_import_state *s) { JSRuntime *rt = new_runtime(); JS_SetModuleLoaderFunc2(rt, NULL, dynamic_import_loader, NULL, s); JS_SetDynamicImportFunc(rt, dynamic_import_func, s); return rt; } static void dynamic_import_drain_jobs(JSRuntime *rt) { JSContext *ctx; while (JS_ExecutePendingJob(rt, &ctx) > 0) ; } static void dynamic_import_assert_namespace(JSContext *ctx, JSValueConst promise) { assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, promise)); JSValue ns = JS_PromiseResult(ctx, promise); JSValue v = JS_GetPropertyStr(ctx, ns, "v"); assert(JS_IsNumber(v)); assert(42 == JS_VALUE_GET_INT(v)); JS_FreeValue(ctx, v); JS_FreeValue(ctx, ns); } static void dynamic_import_sync_ok(void) { struct dynamic_import_state s = {0}; JSRuntime *rt = dynamic_import_runtime(&s); JSContext *ctx = JS_NewContext(rt); JSValue promise = eval(ctx, "import('dep', {with: {type: 'x'}})"); assert(JS_PROMISE_PENDING == JS_PromiseState(ctx, promise)); dynamic_import_drain_jobs(rt); assert(1 == s.import_calls); assert(1 == s.attribute_calls); dynamic_import_assert_namespace(ctx, promise); JS_FreeValue(ctx, promise); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void dynamic_import_sync_missing(void) { struct dynamic_import_state s = {0}; JSRuntime *rt = dynamic_import_runtime(&s); JSContext *ctx = JS_NewContext(rt); JSValue promise = eval(ctx, "import('missing')"); dynamic_import_drain_jobs(rt); assert(1 == s.import_calls); assert(JS_PROMISE_REJECTED == JS_PromiseState(ctx, promise)); JSValue reason = JS_PromiseResult(ctx, promise); assert(JS_IsError(reason)); JS_FreeValue(ctx, reason); JS_FreeValue(ctx, promise); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void dynamic_import_deferred(void) { struct dynamic_import_state s = {.defer = true}; JSRuntime *rt = dynamic_import_runtime(&s); JSContext *ctx = JS_NewContext(rt); JSValue promise = eval(ctx, "import('dep', {with: {type: 'x'}})"); dynamic_import_drain_jobs(rt); assert(1 == s.import_calls); assert(0 == s.attribute_calls); assert(JS_PROMISE_PENDING == JS_PromiseState(ctx, promise)); // token and attributes must survive a collection while the import is parked JS_RunGC(rt); JSValue module_promise = JS_LoadModule(ctx, s.basename, s.filename, s.attributes); assert(!JS_IsException(module_promise)); assert(0 == JS_FinishDynamicImport(ctx, s.token, module_promise, JS_UNDEFINED)); JS_FreeValue(ctx, s.attributes); free(s.basename); free(s.filename); dynamic_import_drain_jobs(rt); assert(1 == s.attribute_calls); dynamic_import_assert_namespace(ctx, promise); JS_FreeValue(ctx, promise); JS_FreeContext(ctx); JS_FreeRuntime(rt); } static void dynamic_import_deferred_reject(void) { struct dynamic_import_state s = {.defer = true}; JSRuntime *rt = dynamic_import_runtime(&s); JSContext *ctx = JS_NewContext(rt); JSValue promise = eval(ctx, "import('dep')"); dynamic_import_drain_jobs(rt); assert(JS_PROMISE_PENDING == JS_PromiseState(ctx, promise)); JSValue error = JS_NewError(ctx); assert(1 == JS_SetPropertyStr(ctx, error, "tag", JS_NewInt32(ctx, 7))); assert(0 == JS_FinishDynamicImport(ctx, s.token, JS_EXCEPTION, error)); JS_FreeValue(ctx, s.attributes); free(s.basename); free(s.filename); dynamic_import_drain_jobs(rt); assert(JS_PROMISE_REJECTED == JS_PromiseState(ctx, promise)); JSValue reason = JS_PromiseResult(ctx, promise); JSValue tag = JS_GetPropertyStr(ctx, reason, "tag"); assert(7 == JS_VALUE_GET_INT(tag)); JS_FreeValue(ctx, tag); JS_FreeValue(ctx, reason); JS_FreeValue(ctx, promise); JS_FreeContext(ctx); JS_FreeRuntime(rt); } int main(void) { cfunctions(); sync_call(); async_call(); async_call_stack_overflow(); raw_context_global_var(); is_array(); module_serde(); module_unhandled_rejection(); dynamic_import_sync_ok(); dynamic_import_sync_missing(); dynamic_import_deferred(); dynamic_import_deferred_reject(); promise_mark_as_handled(); promise_then(); runtime_cstring_free(); utf16_string(); weak_map_gc_check(); promise_hook(); dump_memory_usage(); new_errors(); dom_exception_added_twice(); large_allocation_accounting(); backtrace_oom_current_exception(); backtrace_oom_callsite_array(); proxy_own_keys_huge_length(); global_object_prototype(); slice_string_tocstring(); immutable_array_buffer(); shared_array_buffer_growth(); get_uint8array(); new_symbol(); bulk_free_macros(); detach_array_buffer_free_once(); typed_array_sort_index_overflow(); transfer_external_array_buffer(); resize_external_array_buffer(); transfer_default_managed_array_buffer(); get_class_name(); object_from(); add_intrinsic_bigint(); new_typed_array(); std_eval_interrupt_handler(); private_symbols(); new_context_low_memory(); json_stringify_oom(); return 0; }