diff --git a/deps/quickjs-release.env b/deps/quickjs-release.env index c06dc77..457d25d 100644 --- a/deps/quickjs-release.env +++ b/deps/quickjs-release.env @@ -1,5 +1,5 @@ QUICKJS_NG_REPO="quickjs-ng/quickjs" -QUICKJS_NG_TAG="v0.16.1" -QUICKJS_NG_TARBALL_URL="https://api.github.com/repos/quickjs-ng/quickjs/tarball/v0.16.1" -QUICKJS_NG_RELEASE_URL="https://github.com/quickjs-ng/quickjs/releases/tag/v0.16.1" -QUICKJS_NG_RELEASED_AT="2026-08-04T09:22:30Z" +QUICKJS_NG_TAG="v0.16.2" +QUICKJS_NG_TARBALL_URL="https://api.github.com/repos/quickjs-ng/quickjs/tarball/v0.16.2" +QUICKJS_NG_RELEASE_URL="https://github.com/quickjs-ng/quickjs/releases/tag/v0.16.2" +QUICKJS_NG_RELEASED_AT="2026-08-20T12:22:52Z" diff --git a/deps/quickjs/api-test.c b/deps/quickjs/api-test.c index ef38a7f..c19584d 100644 --- a/deps/quickjs/api-test.c +++ b/deps/quickjs/api-test.c @@ -2,6 +2,7 @@ #undef NDEBUG #endif #include +#include #include #include #include "quickjs.h" @@ -928,6 +929,59 @@ static void new_errors(void) 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[] = @@ -1822,6 +1876,182 @@ static void transfer_default_managed_array_buffer(void) 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); +} + int main(void) { cfunctions(); @@ -1840,6 +2070,7 @@ int main(void) promise_hook(); dump_memory_usage(); new_errors(); + dom_exception_added_twice(); backtrace_oom_current_exception(); backtrace_oom_callsite_array(); proxy_own_keys_huge_length(); @@ -1855,5 +2086,8 @@ int main(void) transfer_external_array_buffer(); resize_external_array_buffer(); transfer_default_managed_array_buffer(); + get_class_name(); + object_from(); + add_intrinsic_bigint(); return 0; } diff --git a/deps/quickjs/libregexp.c b/deps/quickjs/libregexp.c index 1492512..49c474c 100644 --- a/deps/quickjs/libregexp.c +++ b/deps/quickjs/libregexp.c @@ -1025,7 +1025,7 @@ static int parse_class_string_disjunction(REParseState *s, REStringList *cr, for(;;) { str.size = 0; while (*p != '}' && *p != '|') { - c = get_class_atom(s, NULL, &p, false); + c = get_class_atom(s, NULL, &p, true); if (c < 0) goto fail; if (dbuf_put_u32(&str, c)) { @@ -1119,6 +1119,23 @@ static int get_class_atom(REParseState *s, REStringList *cr, if (!inclass && s->is_unicode) goto invalid_escape; break; + case '&': + case '!': + case '#': + case '%': + case ',': + case ':': + case ';': + case '<': + case '=': + case '>': + case '@': + case '`': + case '~': + if (s->is_unicode && (!inclass || !s->unicode_sets)) + /* Only illegal if in unicode mode and not in a class */ + goto invalid_escape; + break; case '^': case '$': case '\\': diff --git a/deps/quickjs/meson.build b/deps/quickjs/meson.build index a444f10..f4135dd 100644 --- a/deps/quickjs/meson.build +++ b/deps/quickjs/meson.build @@ -1,7 +1,7 @@ project( 'quickjs-ng', 'c', - version: '0.16.1', + version: '0.16.2', default_options: [ 'c_std=gnu11,c11', 'warning_level=3', diff --git a/deps/quickjs/quickjs-libc.c b/deps/quickjs/quickjs-libc.c index 772b912..97133c0 100644 --- a/deps/quickjs/quickjs-libc.c +++ b/deps/quickjs/quickjs-libc.c @@ -1920,7 +1920,7 @@ static const JSCFunctionListEntry js_std_funcs[] = { JS_CFUNC_DEF("evalScript", 1, js_evalScript ), JS_CFUNC_DEF("loadScript", 1, js_loadScript ), JS_CFUNC_DEF("getenv", 1, js_std_getenv ), - JS_CFUNC_DEF("setenv", 1, js_std_setenv ), + JS_CFUNC_DEF("setenv", 2, js_std_setenv ), JS_CFUNC_DEF("unsetenv", 1, js_std_unsetenv ), JS_CFUNC_DEF("getenviron", 1, js_std_getenviron ), #if !defined(__wasi__) @@ -4449,7 +4449,7 @@ static const JSCFunctionListEntry js_os_funcs[] = { JS_CFUNC_DEF("sleepAsync", 1, js_os_sleepAsync ), JS_PROP_STRING_DEF("platform", OS_PLATFORM, 0 ), JS_CFUNC_DEF("getcwd", 0, js_os_getcwd ), - JS_CFUNC_DEF("chdir", 0, js_os_chdir ), + JS_CFUNC_DEF("chdir", 1, js_os_chdir ), JS_CFUNC_DEF("mkdir", 1, js_os_mkdir ), JS_CFUNC_DEF("readdir", 1, js_os_readdir ), #if !defined(_WIN32) && !defined(__wasi__) diff --git a/deps/quickjs/quickjs.c b/deps/quickjs/quickjs.c index bbac00c..91661ab 100644 --- a/deps/quickjs/quickjs.c +++ b/deps/quickjs/quickjs.c @@ -4178,7 +4178,7 @@ bool JS_IsRegisteredClass(JSRuntime *rt, JSClassID class_id) JSAtom JS_GetClassName(JSRuntime *rt, JSClassID class_id) { if (JS_IsRegisteredClass(rt, class_id)) { - return JS_DupAtomRT(rt, rt->class_array[class_id].class_id); + return JS_DupAtomRT(rt, rt->class_array[class_id].class_name); } else { return JS_ATOM_NULL; } @@ -6324,48 +6324,21 @@ JSValue JS_NewObjectProto(JSContext *ctx, JSValueConst proto) JSValue JS_NewObjectFrom(JSContext *ctx, int count, const JSAtom *props, const JSValue *values) { - JSShapeProperty *pr; - uint32_t *hash; - JSRuntime *rt; - JSObject *p; - JSShape *sh; JSValue obj; - JSAtom atom; - intptr_t h; int i; - rt = ctx->rt; obj = JS_NewObject(ctx); if (JS_IsException(obj)) return JS_EXCEPTION; - if (count > 0) { - p = JS_VALUE_GET_OBJ(obj); - sh = p->shape; - assert(sh->is_hashed); - assert(JS_REF_COUNT(sh) == 1); - js_shape_hash_unlink(rt, sh); - if (resize_properties(ctx, &sh, p, count)) { - js_shape_hash_link(rt, sh); - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } - p->shape = sh; - for (i = 0; i < count; i++) { - atom = props[i]; - pr = &get_shape_prop(sh)[i]; - sh->hash = shape_hash(shape_hash(sh->hash, atom), JS_PROP_C_W_E); - h = atom & sh->prop_hash_mask; - hash = &prop_hash_end(sh)[-h - 1]; - pr->hash_next = *hash; - *hash = i + 1; - pr->atom = JS_DupAtom(ctx, atom); - pr->flags = JS_PROP_C_W_E; - p->prop[i].u.value = values[i]; - } - js_shape_hash_link(rt, sh); - sh->prop_count = count; - } + for (i = 0; i < count; i++) + if (JS_SetProperty(ctx, obj, props[i], values[i]) < 0) + goto fail; return obj; +fail: + for (/*empty*/; i < count; i++) + JS_FreeValue(ctx, values[i]); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; } JSValue JS_NewObjectFromStr(JSContext *ctx, int count, const char **props, @@ -37051,8 +37024,8 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) goto fail; function_size = sizeof(*b); - cpool_offset = function_size; - function_size += fd->cpool_count * sizeof(*fd->cpool); + cpool_offset = (function_size + 7) & ~7; + function_size = cpool_offset + fd->cpool_count * sizeof(*fd->cpool); vardefs_offset = function_size; function_size += (fd->arg_count + fd->var_count) * sizeof(*b->vardefs); closure_var_offset = function_size; @@ -39753,8 +39726,8 @@ static JSValue JS_ReadFunctionTag(BCReaderState *s) goto fail; function_size = sizeof(*b); - cpool_offset = function_size; - function_size += bc.cpool_count * sizeof(*bc.cpool); + cpool_offset = (function_size + 7) & ~7; + function_size = cpool_offset + bc.cpool_count * sizeof(*bc.cpool); vardefs_offset = function_size; function_size += local_count * sizeof(*bc.vardefs); closure_var_offset = function_size; @@ -40947,7 +40920,7 @@ static JSValue JS_NewCConstructor(JSContext *ctx, int class_id, const char *name const JSCFunctionListEntry *proto_fields, int n_proto_fields, int flags) { - JSValue ctor = JS_UNDEFINED, proto, parent_proto; + JSValue ctor = JS_UNDEFINED, proto, parent_proto, *class_proto; int proto_class_id, proto_flags, ctor_flags; proto_flags = 0; @@ -40978,8 +40951,12 @@ static JSValue JS_NewCConstructor(JSContext *ctx, int class_id, const char *name n_proto_fields + 1); if (JS_IsException(proto)) goto fail; - if (class_id >= 0) - ctx->class_proto[class_id] = js_dup(proto); + if (class_id >= 0) { + class_proto = &ctx->class_proto[class_id]; + if (!JS_IsNull(*class_proto)) + JS_FreeValue(ctx, *class_proto); + *class_proto = js_dup(proto); + } } if (JS_SetPropertyFunctionList(ctx, proto, proto_fields, n_proto_fields)) goto fail; @@ -41178,8 +41155,9 @@ static int js_obj_to_desc(JSContext *ctx, JSPropertyDescriptor *d, if (present) { flags |= JS_PROP_HAS_GET; getter = JS_GetProperty(ctx, desc, JS_ATOM_get); - if (JS_IsException(getter) || - !(JS_IsUndefined(getter) || JS_IsFunction(ctx, getter))) { + if (JS_IsException(getter)) + goto fail; + if (!(JS_IsUndefined(getter) || JS_IsFunction(ctx, getter))) { JS_ThrowTypeError(ctx, "Getter must be a function"); goto fail; } @@ -41190,8 +41168,9 @@ static int js_obj_to_desc(JSContext *ctx, JSPropertyDescriptor *d, if (present) { flags |= JS_PROP_HAS_SET; setter = JS_GetProperty(ctx, desc, JS_ATOM_set); - if (JS_IsException(setter) || - !(JS_IsUndefined(setter) || JS_IsFunction(ctx, setter))) { + if (JS_IsException(setter)) + goto fail; + if (!(JS_IsUndefined(setter) || JS_IsFunction(ctx, setter))) { JS_ThrowTypeError(ctx, "Setter must be a function"); goto fail; } @@ -43564,6 +43543,8 @@ static JSValue js_array_every(JSContext *ctx, JSValueConst this_val, n = 0; for(k = 0; k < len; k++) { + if (js_poll_interrupts(ctx)) + goto exception; if (special & special_TA) { val = JS_GetPropertyInt64(ctx, obj, k); if (JS_IsException(val)) @@ -43685,6 +43666,8 @@ static JSValue js_array_reduce(JSContext *ctx, JSValueConst this_val, acc = js_dup(argv[1]); } else { for(;;) { + if (js_poll_interrupts(ctx)) + goto exception; if (k >= len) { JS_ThrowTypeError(ctx, "empty array"); goto exception; @@ -43706,6 +43689,8 @@ static JSValue js_array_reduce(JSContext *ctx, JSValueConst this_val, } } for (; k < len; k++) { + if (js_poll_interrupts(ctx)) + goto exception; k1 = (special & special_reduceRight) ? len - k - 1 : k; if (special & special_TA) { val = JS_GetPropertyInt64(ctx, obj, k1); @@ -43807,6 +43792,8 @@ static JSValue js_array_includes(JSContext *ctx, JSValueConst this_val, } } for (; n < len; n++) { + if (js_poll_interrupts(ctx)) + goto exception; val = JS_GetPropertyInt64(ctx, obj, n); if (JS_IsException(val)) goto exception; @@ -43853,6 +43840,8 @@ static JSValue js_array_indexOf(JSContext *ctx, JSValueConst this_val, } } for (; n < len; n++) { + if (js_poll_interrupts(ctx)) + goto exception; int present = JS_TryGetPropertyInt64(ctx, obj, n, &val); if (present < 0) goto exception; @@ -43882,6 +43871,7 @@ static JSValue js_array_lastIndexOf(JSContext *ctx, JSValueConst this_val, int64_t len, n; JSValue *arrp; uint32_t count; + int present; obj = JS_ToObject(ctx, this_val); if (js_get_length64(ctx, &len, obj)) @@ -43902,7 +43892,9 @@ static JSValue js_array_lastIndexOf(JSContext *ctx, JSValueConst this_val, } } for (; n >= 0; n--) { - int present = JS_TryGetPropertyInt64(ctx, obj, n, &val); + if (js_poll_interrupts(ctx)) + goto exception; + present = JS_TryGetPropertyInt64(ctx, obj, n, &val); if (present < 0) goto exception; if (present) { @@ -43965,6 +43957,8 @@ static JSValue js_array_find(JSContext *ctx, JSValueConst this_val, // TODO(bnoordhuis) add fast path for fast arrays for(; k != end; k += dir) { + if (js_poll_interrupts(ctx)) + goto exception; index_val = js_int64(k); val = JS_GetPropertyValue(ctx, obj, index_val); if (JS_IsException(val)) @@ -44673,11 +44667,6 @@ static int js_array_cmp_generic(const void *a, const void *b, void *opaque) { return 0; if (psc->has_method) { - /* custom sort function is specified as returning 0 for identical - * objects: avoid method call overhead. - */ - if (!memcmp(&ap->val, &bp->val, sizeof(ap->val))) - goto cmp_same; argv[0] = ap->val; argv[1] = bp->val; res = JS_Call(ctx, psc->method, JS_UNDEFINED, 2, argv); @@ -44712,7 +44701,6 @@ static int js_array_cmp_generic(const void *a, const void *b, void *opaque) { } if (cmp != 0) return cmp; -cmp_same: /* make sort stable: compare array offsets */ return (ap->pos > bp->pos) - (ap->pos < bp->pos); @@ -52224,7 +52212,7 @@ static int js_proxy_has(JSContext *ctx, JSValueConst obj, JSAtom atom) int res; JSObject *p; JSValueConst args[2]; - bool ret, res2; + bool ret; s = get_proxy_method(ctx, &method, obj, JS_ATOM_has); if (!s) @@ -52250,8 +52238,15 @@ static int js_proxy_has(JSContext *ctx, JSValueConst obj, JSAtom atom) if (res < 0) return -1; if (res) { - res2 = !(desc_flags & JS_PROP_CONFIGURABLE); - if (res2 || !p->extensible) { + if (!(desc_flags & JS_PROP_CONFIGURABLE)) + goto inconsistent; + /* must go through IsExtensible(): the target can be a proxy + itself, whose isExtensible trap is observable */ + res = JS_IsExtensible(ctx, s->target); + if (res < 0) + return -1; + if (!res) { + inconsistent: JS_ThrowTypeError(ctx, "proxy: inconsistent has"); return -1; } @@ -52447,7 +52442,14 @@ static int js_proxy_get_own_property(JSContext *ctx, JSPropertyDescriptor *pdesc js_free_desc(ctx, &target_desc); if (JS_IsUndefined(trap_result_obj)) { if (target_desc_ret) { - if (!(target_desc.flags & JS_PROP_CONFIGURABLE) || !p->extensible) + if (!(target_desc.flags & JS_PROP_CONFIGURABLE)) + goto fail; + /* must go through IsExtensible(): the target can be a proxy + itself, whose isExtensible trap is observable */ + res = JS_IsExtensible(ctx, s->target); + if (res < 0) + return -1; + if (!res) goto fail; } ret = false; @@ -52510,7 +52512,7 @@ static int js_proxy_define_own_property(JSContext *ctx, JSValueConst obj, { JSProxyData *s; JSValue method, ret1, prop_val, desc_val; - int res; + int res, extensible_target; JSObject *p; JSValueConst args[3]; JSPropertyDescriptor desc; @@ -52554,11 +52556,19 @@ static int js_proxy_define_own_property(JSContext *ctx, JSValueConst obj, res = JS_GetOwnPropertyInternal(ctx, &desc, p, prop); if (res < 0) return -1; + /* must go through IsExtensible(): the target can be a proxy itself, in + which case its isExtensible trap is observable and may throw */ + extensible_target = JS_IsExtensible(ctx, s->target); + if (extensible_target < 0) { + if (res) + js_free_desc(ctx, &desc); + return -1; + } setting_not_configurable = ((flags & (JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) == JS_PROP_HAS_CONFIGURABLE); if (!res) { - if (!p->extensible || setting_not_configurable) + if (!extensible_target || setting_not_configurable) goto fail; } else { if (!check_define_prop_flags(desc.flags, flags) || @@ -52687,6 +52697,12 @@ static int js_proxy_get_own_property_names(JSContext *ctx, prop_array = JS_CallFree(ctx, method, s->handler, 1, vc(&s->target)); if (JS_IsException(prop_array)) return -1; + /* CreateListFromArrayLike() requires an object */ + if (JS_VALUE_GET_TAG(prop_array) != JS_TAG_OBJECT) { + JS_FreeValue(ctx, prop_array); + JS_ThrowTypeError(ctx, "proxy: ownKeys must return an object"); + return -1; + } tab = NULL; len = 0; tab_size = 0; @@ -60102,10 +60118,13 @@ static JSValue js_typed_array_with(JSContext *ctx, JSValueConst this_val, if (idx < 0) idx = len + idx; - val = JS_ToPrimitive(ctx, argv[1], HINT_NUMBER); + if (p->class_id == JS_CLASS_BIG_INT64_ARRAY || p->class_id == JS_CLASS_BIG_UINT64_ARRAY) { + val = JS_ToBigInt(ctx, argv[1]); + } else { + val = JS_ToNumber(ctx, argv[1]); + } if (JS_IsException(val)) return JS_EXCEPTION; - /* re-validate after user code (spec step 9: IsValidIntegerIndex) */ if (typed_array_is_oob(p)) { JS_FreeValue(ctx, val); @@ -63761,7 +63780,7 @@ int JS_AddIntrinsicDOMException(JSContext *ctx) } JS_DefinePropertyValue(ctx, ctx->global_obj, JS_ATOM_DOMException, ctor, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - ctx->class_proto[JS_CLASS_DOM_EXCEPTION] = proto; + set_value(ctx, &ctx->class_proto[JS_CLASS_DOM_EXCEPTION], proto); return 0; } /* base64 */ @@ -64825,6 +64844,10 @@ uintptr_t js_std_cmd(int cmd, ...) { if (JS_IsString(*pv)) rv = JS_VALUE_GET_STRING(*pv)->kind; break; + case 4: // GetShapeHashCount + rt = va_arg(ap, JSRuntime *); + rv = rt->shape_hash_count; + break; default: rv = -1; } diff --git a/deps/quickjs/quickjs.h b/deps/quickjs/quickjs.h index 0617a61..a780779 100644 --- a/deps/quickjs/quickjs.h +++ b/deps/quickjs/quickjs.h @@ -1449,7 +1449,7 @@ JS_EXTERN int JS_SetModuleExportList(JSContext *ctx, JSModuleDef *m, #define QJS_VERSION_MAJOR 0 #define QJS_VERSION_MINOR 16 -#define QJS_VERSION_PATCH 1 +#define QJS_VERSION_PATCH 2 #define QJS_VERSION_SUFFIX "" JS_EXTERN const char* JS_GetVersion(void); diff --git a/deps/quickjs/run-test262.c b/deps/quickjs/run-test262.c index b025e19..562c8a3 100644 --- a/deps/quickjs/run-test262.c +++ b/deps/quickjs/run-test262.c @@ -1736,15 +1736,21 @@ JSContext *JS_NewCustomContext(JSRuntime *rt) return ctx; } +static int interrupt_handler(JSRuntime *rt, void *opaque) +{ + int *interrupt_countdown = opaque; + return !--*interrupt_countdown; +} + int run_test_buf(ThreadLocalStorage *tls, const char *filename, char *harness, namelist_t *ip, char *buf, size_t buf_len, const char* error_type, int eval_flags, bool is_negative, - bool is_async, bool can_block, bool track_promise_rejections, - int *msec) + bool is_async, bool can_block, bool set_interrupt_handler, + bool track_promise_rejections, int *msec) { + int i, ret, interrupt_countdown; JSRuntime *rt; JSContext *ctx; - int i, ret; rt = JS_NewRuntime(); if (rt == NULL) { @@ -1785,6 +1791,12 @@ int run_test_buf(ThreadLocalStorage *tls, const char *filename, char *harness, } } + // must be big enough that the script isn't interrupted before it + // gets to the meat but not so big that it takes ages to kick in + interrupt_countdown = 150; + if (set_interrupt_handler) + JS_SetInterruptHandler(rt, interrupt_handler, &interrupt_countdown); + ret = eval_buf(ctx, buf, buf_len, filename, true, is_negative, error_type, eval_flags, is_async, msec); ret = (ret != 0); @@ -1814,6 +1826,7 @@ int run_test(ThreadLocalStorage *tls, const char *filename, int *msec) int ret, eval_flags, use_strict, use_nostrict; bool is_negative, is_nostrict, is_onlystrict, is_async, is_module, skip; bool detect_module = true; + bool set_interrupt_handler = false; bool track_promise_rejections = false; bool can_block; namelist_t include_list = { 0 }, *ip = &include_list; @@ -1873,6 +1886,9 @@ int run_test(ThreadLocalStorage *tls, const char *filename, int *msec) else if (str_equal(option, "qjs:no-detect-module")) { detect_module = false; } + else if (str_equal(option, "qjs:set-interrupt-handler")) { + set_interrupt_handler = true; + } else if (str_equal(option, "qjs:track-promise-rejections")) { track_promise_rejections = true; } @@ -1970,12 +1986,14 @@ int run_test(ThreadLocalStorage *tls, const char *filename, int *msec) if (use_nostrict) { ret = run_test_buf(tls, filename, harness, ip, buf, buf_len, error_type, eval_flags, is_negative, is_async, - can_block, track_promise_rejections, msec); + can_block, set_interrupt_handler, + track_promise_rejections, msec); } if (use_strict) { ret |= run_test_buf(tls, filename, harness, ip, buf, buf_len, error_type, eval_flags | JS_EVAL_FLAG_STRICT, is_negative, is_async, can_block, + set_interrupt_handler, track_promise_rejections, msec); } } diff --git a/deps/quickjs/tests/array-sort-identical-values.js b/deps/quickjs/tests/array-sort-identical-values.js new file mode 100644 index 0000000..a64e97f --- /dev/null +++ b/deps/quickjs/tests/array-sort-identical-values.js @@ -0,0 +1,259 @@ +import { assert } from "./assert.js"; + +/* SortCompare calls comparefn for every pair it compares; there is no + allowance for skipping the call when the two values happen to be the same + value. Code that uses the comparator for its side effects depends on it. */ + +/* the jQuery uniqueSort() pattern: the comparator is handed the same object + twice and records that it saw a duplicate */ +{ + const o = {}; + let dups = false; + const r = [o, o].sort((a, b) => { if (a === b) dups = true; return 0; }); + assert(dups, true); + assert(r.length, 2); + assert(r[0] === o, true); + assert(r[1] === o, true); +} + +/* identical primitives get the same treatment */ +{ + const seen = []; + [1, 1].sort((a, b) => { seen.push(a, b); return 0; }); + assert(seen.length, 2); + assert(seen[0], 1); + assert(seen[1], 1); +} + +/* toSorted() sorts through the same path */ +{ + const o = {}; + let dups = false; + const r = [o, o].toSorted((a, b) => { if (a === b) dups = true; return 0; }); + assert(dups, true); + assert(r.length, 2); + assert(r[0] === o, true); +} + +/* an exception from the comparator is not swallowed for identical values */ +{ + const o = {}; + let err; + try { + [o, o].sort(() => { throw new RangeError("boom"); }); + } catch (e) { + err = e; + } + assert(err instanceof RangeError, true); + assert(err.message, "boom"); +} + +/* the same holds beyond the insertion sort cutoff: every comparison of the + all-identical array reaches the comparator */ +{ + const o = {}; + let calls = 0; + const r = new Array(100).fill(o).sort((x, y) => { + if (x === o && y === o) calls++; + return 0; + }); + assert(calls >= 99, true); /* at least one comparison per element */ + assert(r.length, 100); + assert(r.every(v => v === o), true); +} + +/* the shortcut compared the two values bit for bit, so it caught every type + whose JSValue is the value itself or a shared pointer, not just objects */ +{ + function calls(arr) { + let n = 0; + Array.prototype.sort.call(arr, () => { n++; return 0; }); + return n; + } + + const s = "abc"; + const sym = Symbol("s"); + const o = {}; + assert(calls([s, s]), 1, "same string"); + assert(calls([NaN, NaN]), 1, "same NaN"); + assert(calls([1n, 1n]), 1, "same bigint"); + assert(calls([sym, sym]), 1, "same symbol"); + assert(calls([true, true]), 1, "same boolean"); + assert(calls([null, null]), 1, "same null"); + assert(calls([o, o]), 1, "same object"); + assert(calls([o, o, o]), 2, "three identical objects"); + + /* an array-like sorted through .call() takes the same path */ + assert(calls({ length: 2, 0: o, 1: o }), 1, "array-like"); +} + +/* undefined and holes are still sorted to the end without ever reaching the + comparator: that is SortCompare's own rule, not the shortcut */ +{ + let n = 0; + const cmp = () => { n++; return 0; }; + + n = 0; + assert([undefined, undefined].sort(cmp).length, 2); + assert(n, 0, "two undefined"); + + n = 0; + const mixed = [undefined, 1].sort(cmp); + assert(n, 0, "undefined and a value"); + assert(mixed[0], 1); + assert(mixed[1], undefined); + + n = 0; + const holes = new Array(3); + holes[0] = 1; + holes.sort(cmp); + assert(n, 0, "holes"); + assert(holes[0], 1); + assert(1 in holes, false); +} + +/* typed arrays sort through a different comparison function that never had + the shortcut; it must keep calling the comparator too */ +{ + for (const Ctor of [Int8Array, Uint8Array, Int32Array, Float64Array]) { + let n = 0; + const t = new Ctor([1, 1, 1]); + t.sort(() => { n++; return 0; }); + assert(n, 2, Ctor.name); + + n = 0; + new Ctor([1, 1, 1]).toSorted(() => { n++; return 0; }); + assert(n, 2, Ctor.name + " toSorted"); + } + + /* including a bigint typed array */ + let n = 0; + new BigInt64Array([1n, 1n, 1n]).sort(() => { n++; return 0; }); + assert(n, 2, "BigInt64Array"); +} + +/* now that identical values reach the comparator too, there are more calls + from which user code can reach back into the array being sorted */ +{ + /* shrinking the array from the comparator */ + { + const a = [3, 1, 3, 1, 3, 1, 3, 1]; + let calls = 0; + a.sort((x, y) => { + calls++; + if (calls === 2) a.length = 3; + return x - y; + }); + assert(calls > 0, true); + /* sort collects the elements before it compares any of them, so the + truncation is undone when the sorted list is written back */ + assert(a.length, 8); + assert(a.join(","), "1,1,1,1,3,3,3,3"); + } + + /* growing it */ + { + const a = [2, 2, 2, 2]; + let calls = 0; + a.sort((x, y) => { + if (++calls === 1) a.push(1, 1); + return x - y; + }); + assert(a.length, 6); + assert(a.every(v => v === 1 || v === 2), true); + } + + /* deleting elements, which turns them into holes that sort to the end */ + { + const a = [1, 1, 1, 1, 1]; + a.sort((x, y) => { + delete a[4]; + return x - y; + }); + assert(a.length, 5); + } + + /* reversing it under the sort's feet */ + { + const a = [1, 1, 2, 2, 3, 3]; + let calls = 0; + const out = a.sort((x, y) => { + if (++calls === 3) a.reverse(); + return x - y; + }); + assert(out, a); + assert(a.length, 6); + } + + /* a comparator that sorts the same array again */ + { + const a = [2, 2, 1, 1]; + let depth = 0; + a.sort(function cmp(x, y) { + if (depth === 0) { + depth++; + a.slice().sort(cmp); + depth--; + } + return x - y; + }); + assert(a.join(","), "1,1,2,2"); + } +} + +/* the comparator's return value is coerced with ToNumber, and anything that + is not less than or greater than zero leaves the order alone */ +{ + const mk = () => [{ i: 0 }, { i: 1 }, { i: 2 }, { i: 3 }]; + const order = a => a.map(v => v.i).join(","); + + assert(order(mk().sort(() => NaN)), "0,1,2,3"); + assert(order(mk().sort(() => undefined)), "0,1,2,3"); + assert(order(mk().sort(() => "")), "0,1,2,3"); + assert(order(mk().sort(() => null)), "0,1,2,3"); + assert(order(mk().sort(() => -0)), "0,1,2,3"); + assert(order(mk().sort(() => "0")), "0,1,2,3"); + assert(order(mk().sort(() => false)), "0,1,2,3"); + assert(order(mk().sort(() => 0.5)), "3,2,1,0"); + assert(order(mk().sort(() => "-1")), "0,1,2,3"); + + /* a comparator that is not callable is a TypeError before any call */ + for (const bad of [null, 1, "x", true, {}, Symbol()]) { + let threw = false; + try { + mk().sort(bad); + } catch (e) { + threw = e instanceof TypeError; + } + assert(threw, true, String(bad)); + } + /* undefined means the default comparator, and is not an error */ + assert(mk().sort(undefined).length, 4); +} + +/* a long run of values the shortcut used to skip entirely still sorts, is + still stable, and calls the comparator for every comparison it makes */ +{ + const n = 2000; + const a = []; + for (let i = 0; i < n; i++) + a.push({ key: i % 3, i }); + let calls = 0; + a.sort((x, y) => { calls++; return x.key - y.key; }); + assert(calls > 0, true); + assert(a.length, n); + for (let i = 1; i < n; i++) { + assert(a[i - 1].key <= a[i].key, true, `order at ${i}`); + if (a[i - 1].key === a[i].key) + assert(a[i - 1].i < a[i].i, true, `stability at ${i}`); + } + + /* the same array where every element is the identical object */ + const same = {}; + const b = new Array(n).fill(same); + let same_calls = 0; + b.sort(() => { same_calls++; return 0; }); + assert(same_calls > 0, true); + assert(b.length, n); + assert(b.every(v => v === same), true); +} diff --git a/deps/quickjs/tests/bug1598.js b/deps/quickjs/tests/bug1598.js new file mode 100644 index 0000000..dd802ae --- /dev/null +++ b/deps/quickjs/tests/bug1598.js @@ -0,0 +1,22 @@ +import { assert, assertThrows } from "./assert.js"; + +// Test ClassSetReservedPunctuators: &, -, !, #, %, ,, :, ;, <, =, >, @, `, and ~ +assert(new RegExp("[\\q{\\-}]", "v").test("-"), true); +assert(new RegExp("[\\q{\\&}]", "v").test("&"), true); +assert(new RegExp("[\\q{\\!}]", "v").test("!"), true); +assert(new RegExp("[\\q{\\#}]", "v").test("#"), true); +assert(new RegExp("[\\q{\\%}]", "v").test("%"), true); +assert(new RegExp("[\\q{\\,}]", "v").test(","), true); +assert(new RegExp("[\\q{\\:}]", "v").test(":"), true); +assert(new RegExp("[\\q{\\;}]", "v").test(";"), true); +assert(new RegExp("[\\q{\\<}]", "v").test("<"), true); +assert(new RegExp("[\\q{\\=}]", "v").test("="), true); +assert(new RegExp("[\\q{\\>}]", "v").test(">"), true); +assert(new RegExp("[\\q{\\@}]", "v").test("@"), true); +assert(new RegExp("[\\q{\\`}]", "v").test("`"), true); +assert(new RegExp("[\\q{\\~}]", "v").test("~"), true); + +// Also test negative cases +assertThrows(SyntaxError, () => new RegExp("\\-", "v").test("-")); +assertThrows(SyntaxError, () => new RegExp("\\%", "v").test("%")); +assertThrows(SyntaxError, () => new RegExp("[\\&]", "u").test("&")); \ No newline at end of file diff --git a/deps/quickjs/tests/bug1625.js b/deps/quickjs/tests/bug1625.js new file mode 100644 index 0000000..4340e0b --- /dev/null +++ b/deps/quickjs/tests/bug1625.js @@ -0,0 +1,6 @@ +import { assertThrows } from "./assert.js"; + + +assertThrows(TypeError, () => new BigInt64Array().with()); +assertThrows(TypeError, () => new BigInt64Array().with(0, 1)); +assertThrows(RangeError, () => new BigInt64Array().with(0, BigInt(10))); \ No newline at end of file diff --git a/deps/quickjs/tests/bug1626.js b/deps/quickjs/tests/bug1626.js new file mode 100644 index 0000000..1f23713 --- /dev/null +++ b/deps/quickjs/tests/bug1626.js @@ -0,0 +1,74 @@ +import { assert, assertArrayEquals, assertThrows } from "./assert.js"; + +/* The post-trap invariant checks of the proxy [[DefineOwnProperty]], + [[GetOwnProperty]] and [[HasProperty]] internal methods must obtain the + target's extensibility through IsExtensible(). When the target is itself a + proxy that is observable: its isExtensible trap runs, and it can throw. */ + +function innerProxy(target, isExtensible) { + return new Proxy(target, { isExtensible }); +} + +/* A non-callable isExtensible trap on the target proxy must surface as a + TypeError instead of being silently ignored. */ +assertThrows(TypeError, () => Reflect.defineProperty( + new Proxy(innerProxy({}, 0), { defineProperty: () => true }), "x", {})); + +/* IsExtensible(target) is performed whether or not the target already has + the property. */ +assertThrows(TypeError, () => Reflect.defineProperty( + new Proxy(innerProxy({ x: 1 }, 0), { defineProperty: () => true }), "x", {})); + +assertThrows(TypeError, () => Reflect.getOwnPropertyDescriptor( + new Proxy(innerProxy({ x: 1 }, 0), { getOwnPropertyDescriptor: () => undefined }), "x")); + +assertThrows(TypeError, () => Reflect.has( + new Proxy(innerProxy({ x: 1 }, 0), { has: () => false }), "x")); + +/* An isExtensible trap that lies about an extensible target is rejected. */ +assertThrows(TypeError, () => Reflect.defineProperty( + new Proxy(innerProxy({}, () => false), { defineProperty: () => true }), "x", {})); + +/* An exception thrown by the trap propagates unchanged. */ +for (const op of [ + (p) => Reflect.defineProperty(new Proxy(p, { defineProperty: () => true }), "x", {}), + (p) => Reflect.getOwnPropertyDescriptor(new Proxy(p, { getOwnPropertyDescriptor: () => undefined }), "x"), + (p) => Reflect.has(new Proxy(p, { has: () => false }), "x"), +]) { + assertThrows(RangeError, () => op(innerProxy({ x: 1 }, () => { throw new RangeError(); }))); +} + +/* A well-behaved target proxy still allows the operations to complete, and the + traps are called in the order the spec prescribes: [[GetOwnProperty]] on the + target first, then IsExtensible(target). */ +function trapLog(target, op, expected) { + const log = []; + const p = new Proxy(target, { + getOwnPropertyDescriptor(t, k) { log.push("gOPD"); return Reflect.getOwnPropertyDescriptor(t, k); }, + isExtensible(t) { log.push("isExtensible"); return Reflect.isExtensible(t); }, + }); + assert(op(p), expected); + return log; +} + +assertArrayEquals(trapLog({ x: 1 }, (p) => Reflect.defineProperty( + new Proxy(p, { defineProperty: () => true }), "x", { value: 2 }), true), ["gOPD", "isExtensible"]); + +assertArrayEquals(trapLog({ x: 1 }, (p) => Reflect.getOwnPropertyDescriptor( + new Proxy(p, { getOwnPropertyDescriptor: () => undefined }), "x"), undefined), ["gOPD", "isExtensible"]); + +assertArrayEquals(trapLog({ x: 1 }, (p) => Reflect.has( + new Proxy(p, { has: () => false }), "x"), false), ["gOPD", "isExtensible"]); + +/* When the property is absent from the target, the configurability check + cannot fail, so [[HasProperty]] and [[GetOwnProperty]] stop before + IsExtensible(). [[DefineOwnProperty]] always performs it. */ +assertArrayEquals(trapLog({}, (p) => Reflect.has( + new Proxy(p, { has: () => false }), "x"), false), ["gOPD"]); + +assertArrayEquals(trapLog({}, (p) => Reflect.getOwnPropertyDescriptor( + new Proxy(p, { getOwnPropertyDescriptor: () => undefined }), "x"), undefined), ["gOPD"]); + +assertArrayEquals(trapLog({}, (p) => Reflect.defineProperty( + new Proxy(p, { defineProperty: () => true }), "x", { value: 1 }), true), ["gOPD", "isExtensible"]); + diff --git a/deps/quickjs/tests/bug1627.js b/deps/quickjs/tests/bug1627.js new file mode 100644 index 0000000..8571f5d --- /dev/null +++ b/deps/quickjs/tests/bug1627.js @@ -0,0 +1,33 @@ +import { assert, assertThrows } from "./assert.js"; + +// ToPropertyDescriptor must propagate an abrupt completion from Get(Obj, "get") +// or Get(Obj, "set") instead of reporting the callability check. + +assertThrows(ReferenceError, () => { + Object.create([], { x: { get get() { unresolvable; } } }); +}); + +assertThrows(ReferenceError, () => { + Object.create([], { x: { get set() { unresolvable; } } }); +}); + +for (const key of ["get", "set"]) { + const sentinel = new Error("thrown from the " + key + " accessor"); + const desc = { get [key]() { throw sentinel; } }; + let caught; + try { + Object.defineProperty({}, "p", desc); + } catch (e) { + caught = e; + } + assert(caught, sentinel, "abrupt completion of Get(Obj, \"" + key + "\")"); +} + +// A non-callable getter/setter still yields a TypeError. +assertThrows(TypeError, () => Object.defineProperty({}, "p", { get: 1 })); +assertThrows(TypeError, () => Object.defineProperty({}, "p", { set: 1 })); + +// Accessors that do return a function keep working. +const o = {}; +Object.defineProperty(o, "p", { get get() { return () => 42; } }); +assert(o.p, 42); diff --git a/deps/quickjs/tests/bug1628.js b/deps/quickjs/tests/bug1628.js new file mode 100644 index 0000000..82ebb92 --- /dev/null +++ b/deps/quickjs/tests/bug1628.js @@ -0,0 +1,53 @@ +import { assert, assertThrows } from "./assert.js"; + +/* The ownKeys trap result is fed to CreateListFromArrayLike(), which throws + a TypeError when the result is not an object. */ + +const primitives = [0, 1, -1, NaN, "", "ab", true, false, undefined, null, 1n, + Symbol("s")]; + +for (const v of primitives) { + const p = new Proxy({}, { ownKeys() { return v; } }); + assertThrows(TypeError, function() { Reflect.ownKeys(p); }); + assertThrows(TypeError, function() { Object.keys(p); }); + assertThrows(TypeError, function() { Object.getOwnPropertyNames(p); }); + assertThrows(TypeError, function() { Object.getOwnPropertySymbols(p); }); + assertThrows(TypeError, function() { Object.assign({}, p); }); + assertThrows(TypeError, function() { ({...p}); }); + assertThrows(TypeError, function() { JSON.stringify(p); }); + assertThrows(TypeError, function() { for (const k in p) ; }); +} + +{ + const p = new Proxy({}, { ownKeys() { return "ab"; } }); + assertThrows(TypeError, function() { Reflect.ownKeys(p); }); +} + +{ + assert(Reflect.ownKeys(new Proxy({}, { ownKeys: () => [] })).length, 0); + + const a = Reflect.ownKeys(new Proxy({}, { ownKeys: () => ["a", "b"] })); + assert(a.length, 2); + assert(a[0], "a"); + assert(a[1], "b"); + + const b = Reflect.ownKeys(new Proxy({}, { + ownKeys: () => ({length: 2, 0: "x", 1: "y"}), + })); + assert(b.length, 2); + assert(b[0], "x"); + assert(b[1], "y"); + + /* a function has no 'length' index properties, so an empty list */ + assert(Reflect.ownKeys(new Proxy({}, { ownKeys: () => function(){} })).length, 0); +} + +{ + let called = 0; + const p = new Proxy({}, { ownKeys() { called++; return 0; } }); + assertThrows(TypeError, function() { Reflect.ownKeys(p); }); + assert(called, 1); + + const q = new Proxy({}, { ownKeys() { throw new RangeError("boom"); } }); + assertThrows(RangeError, function() { Reflect.ownKeys(q); }); +} diff --git a/deps/quickjs/tests/bug1672/Array.prototype.every.js b/deps/quickjs/tests/bug1672/Array.prototype.every.js new file mode 100644 index 0000000..5736282 --- /dev/null +++ b/deps/quickjs/tests/bug1672/Array.prototype.every.js @@ -0,0 +1,7 @@ +/*--- +flags: [qjs:set-interrupt-handler] +negative: + phase: runtime + type: InternalError +---*/ +Array.prototype.every.call({length: 2**32-1}, () => true) diff --git a/deps/quickjs/tests/bug1672/Array.prototype.filter.js b/deps/quickjs/tests/bug1672/Array.prototype.filter.js new file mode 100644 index 0000000..8ee26e8 --- /dev/null +++ b/deps/quickjs/tests/bug1672/Array.prototype.filter.js @@ -0,0 +1,7 @@ +/*--- +flags: [qjs:set-interrupt-handler] +negative: + phase: runtime + type: InternalError +---*/ +Array.prototype.filter.call({length: 2**32-1}, () => false) diff --git a/deps/quickjs/tests/bug1672/Array.prototype.find.js b/deps/quickjs/tests/bug1672/Array.prototype.find.js new file mode 100644 index 0000000..894ed06 --- /dev/null +++ b/deps/quickjs/tests/bug1672/Array.prototype.find.js @@ -0,0 +1,7 @@ +/*--- +flags: [qjs:set-interrupt-handler] +negative: + phase: runtime + type: InternalError +---*/ +Array.prototype.find.call({length: 2**32-1}, () => false) diff --git a/deps/quickjs/tests/bug1672/Array.prototype.findIndex.js b/deps/quickjs/tests/bug1672/Array.prototype.findIndex.js new file mode 100644 index 0000000..be164e0 --- /dev/null +++ b/deps/quickjs/tests/bug1672/Array.prototype.findIndex.js @@ -0,0 +1,7 @@ +/*--- +flags: [qjs:set-interrupt-handler] +negative: + phase: runtime + type: InternalError +---*/ +Array.prototype.findIndex.call({length: 2**32-1}, () => false) diff --git a/deps/quickjs/tests/bug1672/Array.prototype.findLast.js b/deps/quickjs/tests/bug1672/Array.prototype.findLast.js new file mode 100644 index 0000000..2be1ffa --- /dev/null +++ b/deps/quickjs/tests/bug1672/Array.prototype.findLast.js @@ -0,0 +1,7 @@ +/*--- +flags: [qjs:set-interrupt-handler] +negative: + phase: runtime + type: InternalError +---*/ +Array.prototype.findLast.call({length: 2**32-1}, () => false) diff --git a/deps/quickjs/tests/bug1672/Array.prototype.findLastIndex.js b/deps/quickjs/tests/bug1672/Array.prototype.findLastIndex.js new file mode 100644 index 0000000..e18e2d9 --- /dev/null +++ b/deps/quickjs/tests/bug1672/Array.prototype.findLastIndex.js @@ -0,0 +1,7 @@ +/*--- +flags: [qjs:set-interrupt-handler] +negative: + phase: runtime + type: InternalError +---*/ +Array.prototype.findLastIndex.call({length: 2**32-1}, () => false) diff --git a/deps/quickjs/tests/bug1672/Array.prototype.forEach.js b/deps/quickjs/tests/bug1672/Array.prototype.forEach.js new file mode 100644 index 0000000..a282b6c --- /dev/null +++ b/deps/quickjs/tests/bug1672/Array.prototype.forEach.js @@ -0,0 +1,7 @@ +/*--- +flags: [qjs:set-interrupt-handler] +negative: + phase: runtime + type: InternalError +---*/ +Array.prototype.forEach.call({length: 2**32-1}, () => {}) diff --git a/deps/quickjs/tests/bug1672/Array.prototype.includes.js b/deps/quickjs/tests/bug1672/Array.prototype.includes.js new file mode 100644 index 0000000..8b3bf53 --- /dev/null +++ b/deps/quickjs/tests/bug1672/Array.prototype.includes.js @@ -0,0 +1,7 @@ +/*--- +flags: [qjs:set-interrupt-handler] +negative: + phase: runtime + type: InternalError +---*/ +Array.prototype.includes.call({length: 2**32-1}, 42) diff --git a/deps/quickjs/tests/bug1672/Array.prototype.indexOf.js b/deps/quickjs/tests/bug1672/Array.prototype.indexOf.js new file mode 100644 index 0000000..2e0b423 --- /dev/null +++ b/deps/quickjs/tests/bug1672/Array.prototype.indexOf.js @@ -0,0 +1,7 @@ +/*--- +flags: [qjs:set-interrupt-handler] +negative: + phase: runtime + type: InternalError +---*/ +Array.prototype.indexOf.call({length: 2**32-1}, 42) diff --git a/deps/quickjs/tests/bug1672/Array.prototype.lastIndexOf.js b/deps/quickjs/tests/bug1672/Array.prototype.lastIndexOf.js new file mode 100644 index 0000000..0aa5ccd --- /dev/null +++ b/deps/quickjs/tests/bug1672/Array.prototype.lastIndexOf.js @@ -0,0 +1,7 @@ +/*--- +flags: [qjs:set-interrupt-handler] +negative: + phase: runtime + type: InternalError +---*/ +Array.prototype.lastIndexOf.call({length: 2**32-1}, 42) diff --git a/deps/quickjs/tests/bug1672/Array.prototype.map.js b/deps/quickjs/tests/bug1672/Array.prototype.map.js new file mode 100644 index 0000000..a699b74 --- /dev/null +++ b/deps/quickjs/tests/bug1672/Array.prototype.map.js @@ -0,0 +1,7 @@ +/*--- +flags: [qjs:set-interrupt-handler] +negative: + phase: runtime + type: InternalError +---*/ +Array.prototype.map.call({length: 2**32-1}, () => {}) diff --git a/deps/quickjs/tests/bug1672/Array.prototype.reduce.js b/deps/quickjs/tests/bug1672/Array.prototype.reduce.js new file mode 100644 index 0000000..ba72184 --- /dev/null +++ b/deps/quickjs/tests/bug1672/Array.prototype.reduce.js @@ -0,0 +1,7 @@ +/*--- +flags: [qjs:set-interrupt-handler] +negative: + phase: runtime + type: InternalError +---*/ +Array.prototype.reduce.call({length: 2**32-1}, () => {}) diff --git a/deps/quickjs/tests/bug1672/Array.prototype.reduceRight.js b/deps/quickjs/tests/bug1672/Array.prototype.reduceRight.js new file mode 100644 index 0000000..d12967e --- /dev/null +++ b/deps/quickjs/tests/bug1672/Array.prototype.reduceRight.js @@ -0,0 +1,7 @@ +/*--- +flags: [qjs:set-interrupt-handler] +negative: + phase: runtime + type: InternalError +---*/ +Array.prototype.reduceRight.call({length: 2**32-1}, () => {}) diff --git a/deps/quickjs/tests/bug1672/Array.prototype.some.js b/deps/quickjs/tests/bug1672/Array.prototype.some.js new file mode 100644 index 0000000..8fb7f06 --- /dev/null +++ b/deps/quickjs/tests/bug1672/Array.prototype.some.js @@ -0,0 +1,7 @@ +/*--- +flags: [qjs:set-interrupt-handler] +negative: + phase: runtime + type: InternalError +---*/ +Array.prototype.some.call({length: 2**32-1}, () => false)