Checkpoint before integration and package validation. Assisted-by: SurmAgent:unknown [read] [edit] [bash]
2496 lines
79 KiB
C
2496 lines
79 KiB
C
#ifdef NDEBUG
|
|
#undef NDEBUG
|
|
#endif
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#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), "<input>", 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 <eval> (<input>: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 <eval> (<input>: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 <eval> (<input>: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), "<input>", 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), "<m>", 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), "<t>", 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, "<input>", 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), "<input>", 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), "<input>", 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), "<input>", 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), "<input>", 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), "<input>", 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), "<input>", 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;
|
|
}
|