/* * QuickJS Javascript Engine * * Copyright (c) 2017-2021 Fabrice Bellard * Copyright (c) 2017-2021 Charlie Gordon * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "libc/assert.h" #include "third_party/quickjs/internal.h" asm(".ident\t\"\\n\\n\ QuickJS (MIT License)\\n\ Copyright (c) 2017-2021 Fabrice Bellard\\n\ Copyright (c) 2017-2021 Charlie Gordon\""); asm(".include \"libc/disclaimer.inc\""); /* clang-format off */ static void js_proxy_finalizer(JSRuntime *rt, JSValue val) { JSProxyData *s = JS_GetOpaque(val, JS_CLASS_PROXY); if (s) { JS_FreeValueRT(rt, s->target); JS_FreeValueRT(rt, s->handler); js_free_rt(rt, s); } } static void js_proxy_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSProxyData *s = JS_GetOpaque(val, JS_CLASS_PROXY); if (s) { JS_MarkValue(rt, s->target, mark_func); JS_MarkValue(rt, s->handler, mark_func); } } static JSProxyData *get_proxy_method(JSContext *ctx, JSValue *pmethod, JSValueConst obj, JSAtom name) { JSProxyData *s = JS_GetOpaque(obj, JS_CLASS_PROXY); JSValue method; /* safer to test recursion in all proxy methods */ if (js_check_stack_overflow(ctx->rt, 0)) { JS_ThrowStackOverflow(ctx); return NULL; } /* 's' should never be NULL */ if (s->is_revoked) { JS_ThrowTypeErrorRevokedProxy(ctx); return NULL; } method = JS_GetProperty(ctx, s->handler, name); if (JS_IsException(method)) return NULL; if (JS_IsNull(method)) method = JS_UNDEFINED; *pmethod = method; return s; } JSValue js_proxy_getPrototypeOf(JSContext *ctx, JSValueConst obj) { JSProxyData *s; JSValue method, ret, proto1; int res; s = get_proxy_method(ctx, &method, obj, JS_ATOM_getPrototypeOf); if (!s) return JS_EXCEPTION; if (JS_IsUndefined(method)) return JS_GetPrototype(ctx, s->target); ret = JS_CallFree(ctx, method, s->handler, 1, (JSValueConst *)&s->target); if (JS_IsException(ret)) return ret; if (JS_VALUE_GET_TAG(ret) != JS_TAG_NULL && JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) { goto fail; } res = JS_IsExtensible(ctx, s->target); if (res < 0) { JS_FreeValue(ctx, ret); return JS_EXCEPTION; } if (!res) { /* check invariant */ proto1 = JS_GetPrototype(ctx, s->target); if (JS_IsException(proto1)) { JS_FreeValue(ctx, ret); return JS_EXCEPTION; } if (JS_VALUE_GET_OBJ(proto1) != JS_VALUE_GET_OBJ(ret)) { JS_FreeValue(ctx, proto1); fail: JS_FreeValue(ctx, ret); return JS_ThrowTypeError(ctx, "proxy: inconsistent prototype"); } JS_FreeValue(ctx, proto1); } return ret; } int js_proxy_setPrototypeOf(JSContext *ctx, JSValueConst obj, JSValueConst proto_val, BOOL throw_flag) { JSProxyData *s; JSValue method, ret, proto1; JSValueConst args[2]; BOOL res; int res2; s = get_proxy_method(ctx, &method, obj, JS_ATOM_setPrototypeOf); if (!s) return -1; if (JS_IsUndefined(method)) return JS_SetPrototypeInternal(ctx, s->target, proto_val, throw_flag); args[0] = s->target; args[1] = proto_val; ret = JS_CallFree(ctx, method, s->handler, 2, args); if (JS_IsException(ret)) return -1; res = JS_ToBoolFree(ctx, ret); if (!res) { if (throw_flag) { JS_ThrowTypeError(ctx, "proxy: bad prototype"); return -1; } else { return FALSE; } } res2 = JS_IsExtensible(ctx, s->target); if (res2 < 0) return -1; if (!res2) { proto1 = JS_GetPrototype(ctx, s->target); if (JS_IsException(proto1)) return -1; if (JS_VALUE_GET_OBJ(proto_val) != JS_VALUE_GET_OBJ(proto1)) { JS_FreeValue(ctx, proto1); JS_ThrowTypeError(ctx, "proxy: inconsistent prototype"); return -1; } JS_FreeValue(ctx, proto1); } return TRUE; } int js_proxy_isExtensible(JSContext *ctx, JSValueConst obj) { JSProxyData *s; JSValue method, ret; BOOL res; int res2; s = get_proxy_method(ctx, &method, obj, JS_ATOM_isExtensible); if (!s) return -1; if (JS_IsUndefined(method)) return JS_IsExtensible(ctx, s->target); ret = JS_CallFree(ctx, method, s->handler, 1, (JSValueConst *)&s->target); if (JS_IsException(ret)) return -1; res = JS_ToBoolFree(ctx, ret); res2 = JS_IsExtensible(ctx, s->target); if (res2 < 0) return res2; if (res != res2) { JS_ThrowTypeError(ctx, "proxy: inconsistent isExtensible"); return -1; } return res; } int js_proxy_preventExtensions(JSContext *ctx, JSValueConst obj) { JSProxyData *s; JSValue method, ret; BOOL res; int res2; s = get_proxy_method(ctx, &method, obj, JS_ATOM_preventExtensions); if (!s) return -1; if (JS_IsUndefined(method)) return JS_PreventExtensions(ctx, s->target); ret = JS_CallFree(ctx, method, s->handler, 1, (JSValueConst *)&s->target); if (JS_IsException(ret)) return -1; res = JS_ToBoolFree(ctx, ret); if (res) { res2 = JS_IsExtensible(ctx, s->target); if (res2 < 0) return res2; if (res2) { JS_ThrowTypeError(ctx, "proxy: inconsistent preventExtensions"); return -1; } } return res; } static int js_proxy_has(JSContext *ctx, JSValueConst obj, JSAtom atom) { JSProxyData *s; JSValue method, ret1, atom_val; int ret, res; JSObject *p; JSValueConst args[2]; BOOL res2; s = get_proxy_method(ctx, &method, obj, JS_ATOM_has); if (!s) return -1; if (JS_IsUndefined(method)) return JS_HasProperty(ctx, s->target, atom); atom_val = JS_AtomToValue(ctx, atom); if (JS_IsException(atom_val)) { JS_FreeValue(ctx, method); return -1; } args[0] = s->target; args[1] = atom_val; ret1 = JS_CallFree(ctx, method, s->handler, 2, args); JS_FreeValue(ctx, atom_val); if (JS_IsException(ret1)) return -1; ret = JS_ToBoolFree(ctx, ret1); if (!ret) { JSPropertyDescriptor desc; p = JS_VALUE_GET_OBJ(s->target); res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom); if (res < 0) return -1; if (res) { res2 = !(desc.flags & JS_PROP_CONFIGURABLE); js_free_desc(ctx, &desc); if (res2 || !p->extensible) { JS_ThrowTypeError(ctx, "proxy: inconsistent has"); return -1; } } } return ret; } static JSValue js_proxy_get(JSContext *ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver) { JSProxyData *s; JSValue method, ret, atom_val; int res; JSValueConst args[3]; JSPropertyDescriptor desc; s = get_proxy_method(ctx, &method, obj, JS_ATOM_get); if (!s) return JS_EXCEPTION; /* Note: recursion is possible thru the prototype of s->target */ if (JS_IsUndefined(method)) return JS_GetPropertyInternal(ctx, s->target, atom, receiver, FALSE); atom_val = JS_AtomToValue(ctx, atom); if (JS_IsException(atom_val)) { JS_FreeValue(ctx, method); return JS_EXCEPTION; } args[0] = s->target; args[1] = atom_val; args[2] = receiver; ret = JS_CallFree(ctx, method, s->handler, 3, args); JS_FreeValue(ctx, atom_val); if (JS_IsException(ret)) return JS_EXCEPTION; res = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(s->target), atom); if (res < 0) return JS_EXCEPTION; if (res) { if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0) { if (!js_same_value(ctx, desc.value, ret)) { goto fail; } } else if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE)) == JS_PROP_GETSET) { if (JS_IsUndefined(desc.getter) && !JS_IsUndefined(ret)) { fail: js_free_desc(ctx, &desc); JS_FreeValue(ctx, ret); return JS_ThrowTypeError(ctx, "proxy: inconsistent get"); } } js_free_desc(ctx, &desc); } return ret; } static int js_proxy_set(JSContext *ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags) { JSProxyData *s; JSValue method, ret1, atom_val; int ret, res; JSValueConst args[4]; s = get_proxy_method(ctx, &method, obj, JS_ATOM_set); if (!s) return -1; if (JS_IsUndefined(method)) { return JS_SetPropertyGeneric(ctx, s->target, atom, JS_DupValue(ctx, value), receiver, flags); } atom_val = JS_AtomToValue(ctx, atom); if (JS_IsException(atom_val)) { JS_FreeValue(ctx, method); return -1; } args[0] = s->target; args[1] = atom_val; args[2] = value; args[3] = receiver; ret1 = JS_CallFree(ctx, method, s->handler, 4, args); JS_FreeValue(ctx, atom_val); if (JS_IsException(ret1)) return -1; ret = JS_ToBoolFree(ctx, ret1); if (ret) { JSPropertyDescriptor desc; res = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(s->target), atom); if (res < 0) return -1; if (res) { if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0) { if (!js_same_value(ctx, desc.value, value)) { goto fail; } } else if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE)) == JS_PROP_GETSET && JS_IsUndefined(desc.setter)) { fail: js_free_desc(ctx, &desc); JS_ThrowTypeError(ctx, "proxy: inconsistent set"); return -1; } js_free_desc(ctx, &desc); } } else { if ((flags & JS_PROP_THROW) || ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { JS_ThrowTypeError(ctx, "proxy: cannot set property"); return -1; } } return ret; } static JSValue js_create_desc(JSContext *ctx, JSValueConst val, JSValueConst getter, JSValueConst setter, int flags) { JSValue ret; ret = JS_NewObject(ctx); if (JS_IsException(ret)) return ret; if (flags & JS_PROP_HAS_GET) { JS_DefinePropertyValue(ctx, ret, JS_ATOM_get, JS_DupValue(ctx, getter), JS_PROP_C_W_E); } if (flags & JS_PROP_HAS_SET) { JS_DefinePropertyValue(ctx, ret, JS_ATOM_set, JS_DupValue(ctx, setter), JS_PROP_C_W_E); } if (flags & JS_PROP_HAS_VALUE) { JS_DefinePropertyValue(ctx, ret, JS_ATOM_value, JS_DupValue(ctx, val), JS_PROP_C_W_E); } if (flags & JS_PROP_HAS_WRITABLE) { JS_DefinePropertyValue(ctx, ret, JS_ATOM_writable, JS_NewBool(ctx, (flags & JS_PROP_WRITABLE) != 0), JS_PROP_C_W_E); } if (flags & JS_PROP_HAS_ENUMERABLE) { JS_DefinePropertyValue(ctx, ret, JS_ATOM_enumerable, JS_NewBool(ctx, (flags & JS_PROP_ENUMERABLE) != 0), JS_PROP_C_W_E); } if (flags & JS_PROP_HAS_CONFIGURABLE) { JS_DefinePropertyValue(ctx, ret, JS_ATOM_configurable, JS_NewBool(ctx, (flags & JS_PROP_CONFIGURABLE) != 0), JS_PROP_C_W_E); } return ret; } static int js_proxy_get_own_property(JSContext *ctx, JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop) { JSProxyData *s; JSValue method, trap_result_obj, prop_val; int res, target_desc_ret, ret; JSObject *p; JSValueConst args[2]; JSPropertyDescriptor result_desc, target_desc; s = get_proxy_method(ctx, &method, obj, JS_ATOM_getOwnPropertyDescriptor); if (!s) return -1; p = JS_VALUE_GET_OBJ(s->target); if (JS_IsUndefined(method)) { return JS_GetOwnPropertyInternal(ctx, pdesc, p, prop); } prop_val = JS_AtomToValue(ctx, prop); if (JS_IsException(prop_val)) { JS_FreeValue(ctx, method); return -1; } args[0] = s->target; args[1] = prop_val; trap_result_obj = JS_CallFree(ctx, method, s->handler, 2, args); JS_FreeValue(ctx, prop_val); if (JS_IsException(trap_result_obj)) return -1; if (!JS_IsObject(trap_result_obj) && !JS_IsUndefined(trap_result_obj)) { JS_FreeValue(ctx, trap_result_obj); goto fail; } target_desc_ret = JS_GetOwnPropertyInternal(ctx, &target_desc, p, prop); if (target_desc_ret < 0) { JS_FreeValue(ctx, trap_result_obj); return -1; } if (target_desc_ret) 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) goto fail; } ret = FALSE; } else { int flags1, extensible_target; extensible_target = JS_IsExtensible(ctx, s->target); if (extensible_target < 0) { JS_FreeValue(ctx, trap_result_obj); return -1; } res = js_obj_to_desc(ctx, &result_desc, trap_result_obj); JS_FreeValue(ctx, trap_result_obj); if (res < 0) return -1; if (target_desc_ret) { /* convert result_desc.flags to defineProperty flags */ flags1 = result_desc.flags | JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_ENUMERABLE; if (result_desc.flags & JS_PROP_GETSET) flags1 |= JS_PROP_HAS_GET | JS_PROP_HAS_SET; else flags1 |= JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE; /* XXX: not complete check: need to compare value & getter/setter as in defineproperty */ if (!check_define_prop_flags(target_desc.flags, flags1)) goto fail1; } else { if (!extensible_target) goto fail1; } if (!(result_desc.flags & JS_PROP_CONFIGURABLE)) { if (!target_desc_ret || (target_desc.flags & JS_PROP_CONFIGURABLE)) goto fail1; if ((result_desc.flags & (JS_PROP_GETSET | JS_PROP_WRITABLE)) == 0 && target_desc_ret && (target_desc.flags & JS_PROP_WRITABLE) != 0) { /* proxy-missing-checks */ fail1: js_free_desc(ctx, &result_desc); fail: JS_ThrowTypeError(ctx, "proxy: inconsistent getOwnPropertyDescriptor"); return -1; } } ret = TRUE; if (pdesc) { *pdesc = result_desc; } else { js_free_desc(ctx, &result_desc); } } return ret; } static int js_proxy_define_own_property(JSContext *ctx, JSValueConst obj, JSAtom prop, JSValueConst val, JSValueConst getter, JSValueConst setter, int flags) { JSProxyData *s; JSValue method, ret1, prop_val, desc_val; int res, ret; JSObject *p; JSValueConst args[3]; JSPropertyDescriptor desc; BOOL setting_not_configurable; s = get_proxy_method(ctx, &method, obj, JS_ATOM_defineProperty); if (!s) return -1; if (JS_IsUndefined(method)) { return JS_DefineProperty(ctx, s->target, prop, val, getter, setter, flags); } prop_val = JS_AtomToValue(ctx, prop); if (JS_IsException(prop_val)) { JS_FreeValue(ctx, method); return -1; } desc_val = js_create_desc(ctx, val, getter, setter, flags); if (JS_IsException(desc_val)) { JS_FreeValue(ctx, prop_val); JS_FreeValue(ctx, method); return -1; } args[0] = s->target; args[1] = prop_val; args[2] = desc_val; ret1 = JS_CallFree(ctx, method, s->handler, 3, args); JS_FreeValue(ctx, prop_val); JS_FreeValue(ctx, desc_val); if (JS_IsException(ret1)) return -1; ret = JS_ToBoolFree(ctx, ret1); if (!ret) { if (flags & JS_PROP_THROW) { JS_ThrowTypeError(ctx, "proxy: defineProperty exception"); return -1; } else { return 0; } } p = JS_VALUE_GET_OBJ(s->target); res = JS_GetOwnPropertyInternal(ctx, &desc, p, prop); if (res < 0) 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) goto fail; } else { if (!check_define_prop_flags(desc.flags, flags) || ((desc.flags & JS_PROP_CONFIGURABLE) && setting_not_configurable)) { goto fail1; } if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE)) == JS_PROP_GETSET) { if ((flags & JS_PROP_HAS_GET) && !js_same_value(ctx, getter, desc.getter)) { goto fail1; } if ((flags & JS_PROP_HAS_SET) && !js_same_value(ctx, setter, desc.setter)) { goto fail1; } } } else if (flags & JS_PROP_HAS_VALUE) { if ((desc.flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == JS_PROP_WRITABLE && !(flags & JS_PROP_WRITABLE)) { /* missing-proxy-check feature */ goto fail1; } else if ((desc.flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0 && !js_same_value(ctx, val, desc.value)) { goto fail1; } } if (flags & JS_PROP_HAS_WRITABLE) { if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == JS_PROP_WRITABLE) { /* proxy-missing-checks */ fail1: js_free_desc(ctx, &desc); fail: JS_ThrowTypeError(ctx, "proxy: inconsistent defineProperty"); return -1; } } js_free_desc(ctx, &desc); } return 1; } static int js_proxy_delete_property(JSContext *ctx, JSValueConst obj, JSAtom atom) { JSProxyData *s; JSValue method, ret, atom_val; int res, res2, is_extensible; JSValueConst args[2]; s = get_proxy_method(ctx, &method, obj, JS_ATOM_deleteProperty); if (!s) return -1; if (JS_IsUndefined(method)) { return JS_DeleteProperty(ctx, s->target, atom, 0); } atom_val = JS_AtomToValue(ctx, atom);; if (JS_IsException(atom_val)) { JS_FreeValue(ctx, method); return -1; } args[0] = s->target; args[1] = atom_val; ret = JS_CallFree(ctx, method, s->handler, 2, args); JS_FreeValue(ctx, atom_val); if (JS_IsException(ret)) return -1; res = JS_ToBoolFree(ctx, ret); if (res) { JSPropertyDescriptor desc; res2 = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(s->target), atom); if (res2 < 0) return -1; if (res2) { if (!(desc.flags & JS_PROP_CONFIGURABLE)) goto fail; is_extensible = JS_IsExtensible(ctx, s->target); if (is_extensible < 0) goto fail1; if (!is_extensible) { /* proxy-missing-checks */ fail: JS_ThrowTypeError(ctx, "proxy: inconsistent deleteProperty"); fail1: js_free_desc(ctx, &desc); return -1; } js_free_desc(ctx, &desc); } } return res; } /* return the index of the property or -1 if not found */ static int find_prop_key(const JSPropertyEnum *tab, int n, JSAtom atom) { int i; for(i = 0; i < n; i++) { if (tab[i].atom == atom) return i; } return -1; } static int js_proxy_get_own_property_names(JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj) { JSProxyData *s; JSValue method, prop_array, val; uint32_t len, i, len2; JSPropertyEnum *tab, *tab2; JSAtom atom; JSPropertyDescriptor desc; int res, is_extensible, idx; s = get_proxy_method(ctx, &method, obj, JS_ATOM_ownKeys); if (!s) return -1; if (JS_IsUndefined(method)) { return JS_GetOwnPropertyNamesInternal(ctx, ptab, plen, JS_VALUE_GET_OBJ(s->target), JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK); } prop_array = JS_CallFree(ctx, method, s->handler, 1, (JSValueConst *)&s->target); if (JS_IsException(prop_array)) return -1; tab = NULL; len = 0; tab2 = NULL; len2 = 0; if (js_get_length32(ctx, &len, prop_array)) goto fail; if (len > 0) { tab = js_mallocz(ctx, sizeof(tab[0]) * len); if (!tab) goto fail; } for(i = 0; i < len; i++) { val = JS_GetPropertyUint32(ctx, prop_array, i); if (JS_IsException(val)) goto fail; if (!JS_IsString(val) && !JS_IsSymbol(val)) { JS_FreeValue(ctx, val); JS_ThrowTypeError(ctx, "proxy: properties must be strings or symbols"); goto fail; } atom = JS_ValueToAtom(ctx, val); JS_FreeValue(ctx, val); if (atom == JS_ATOM_NULL) goto fail; tab[i].atom = atom; tab[i].is_enumerable = FALSE; /* XXX: redundant? */ } /* check duplicate properties (XXX: inefficient, could store the * properties an a temporary object to use the hash) */ for(i = 1; i < len; i++) { if (find_prop_key(tab, i, tab[i].atom) >= 0) { JS_ThrowTypeError(ctx, "proxy: duplicate property"); goto fail; } } is_extensible = JS_IsExtensible(ctx, s->target); if (is_extensible < 0) goto fail; /* check if there are non configurable properties */ if (s->is_revoked) { JS_ThrowTypeErrorRevokedProxy(ctx); goto fail; } if (JS_GetOwnPropertyNamesInternal(ctx, &tab2, &len2, JS_VALUE_GET_OBJ(s->target), JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK)) goto fail; for(i = 0; i < len2; i++) { if (s->is_revoked) { JS_ThrowTypeErrorRevokedProxy(ctx); goto fail; } res = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(s->target), tab2[i].atom); if (res < 0) goto fail; if (res) { /* safety, property should be found */ js_free_desc(ctx, &desc); if (!(desc.flags & JS_PROP_CONFIGURABLE) || !is_extensible) { idx = find_prop_key(tab, len, tab2[i].atom); if (idx < 0) { JS_ThrowTypeError(ctx, "proxy: target property must be present in proxy ownKeys"); goto fail; } /* mark the property as found */ if (!is_extensible) tab[idx].is_enumerable = TRUE; } } } if (!is_extensible) { /* check that all property in 'tab' were checked */ for(i = 0; i < len; i++) { if (!tab[i].is_enumerable) { JS_ThrowTypeError(ctx, "proxy: property not present in target were returned by non extensible proxy"); goto fail; } } } js_free_prop_enum(ctx, tab2, len2); JS_FreeValue(ctx, prop_array); *ptab = tab; *plen = len; return 0; fail: js_free_prop_enum(ctx, tab2, len2); js_free_prop_enum(ctx, tab, len); JS_FreeValue(ctx, prop_array); return -1; } static JSValue js_proxy_call_constructor(JSContext *ctx, JSValueConst func_obj, JSValueConst new_target, int argc, JSValueConst *argv) { JSProxyData *s; JSValue method, arg_array, ret; JSValueConst args[3]; s = get_proxy_method(ctx, &method, func_obj, JS_ATOM_construct); if (!s) return JS_EXCEPTION; if (!JS_IsConstructor(ctx, s->target)) return JS_ThrowTypeError(ctx, "not a constructor"); if (JS_IsUndefined(method)) return JS_CallConstructor2(ctx, s->target, new_target, argc, argv); arg_array = js_create_array(ctx, argc, argv); if (JS_IsException(arg_array)) { ret = JS_EXCEPTION; goto fail; } args[0] = s->target; args[1] = arg_array; args[2] = new_target; ret = JS_Call(ctx, method, s->handler, 3, args); if (!JS_IsException(ret) && JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) { JS_FreeValue(ctx, ret); ret = JS_ThrowTypeErrorNotAnObject(ctx); } fail: JS_FreeValue(ctx, method); JS_FreeValue(ctx, arg_array); return ret; } static JSValue js_proxy_call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, int argc, JSValueConst *argv, int flags) { JSProxyData *s; JSValue method, arg_array, ret; JSValueConst args[3]; if (flags & JS_CALL_FLAG_CONSTRUCTOR) return js_proxy_call_constructor(ctx, func_obj, this_obj, argc, argv); s = get_proxy_method(ctx, &method, func_obj, JS_ATOM_apply); if (!s) return JS_EXCEPTION; if (!s->is_func) { JS_FreeValue(ctx, method); return JS_ThrowTypeError(ctx, "not a function"); } if (JS_IsUndefined(method)) return JS_Call(ctx, s->target, this_obj, argc, argv); arg_array = js_create_array(ctx, argc, argv); if (JS_IsException(arg_array)) { ret = JS_EXCEPTION; goto fail; } args[0] = s->target; args[1] = this_obj; args[2] = arg_array; ret = JS_Call(ctx, method, s->handler, 3, args); fail: JS_FreeValue(ctx, method); JS_FreeValue(ctx, arg_array); return ret; } int js_proxy_isArray(JSContext *ctx, JSValueConst obj) { JSProxyData *s = JS_GetOpaque(obj, JS_CLASS_PROXY); if (!s) return FALSE; if (s->is_revoked) { JS_ThrowTypeErrorRevokedProxy(ctx); return -1; } return JS_IsArray(ctx, s->target); } static const JSClassExoticMethods js_proxy_exotic_methods = { .get_own_property = js_proxy_get_own_property, .define_own_property = js_proxy_define_own_property, .delete_property = js_proxy_delete_property, .get_own_property_names = js_proxy_get_own_property_names, .has_property = js_proxy_has, .get_property = js_proxy_get, .set_property = js_proxy_set, }; static JSValue js_proxy_constructor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst target, handler; JSValue obj; JSProxyData *s; target = argv[0]; handler = argv[1]; if (JS_VALUE_GET_TAG(target) != JS_TAG_OBJECT || JS_VALUE_GET_TAG(handler) != JS_TAG_OBJECT) return JS_ThrowTypeErrorNotAnObject(ctx); obj = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_PROXY); if (JS_IsException(obj)) return obj; s = js_malloc(ctx, sizeof(JSProxyData)); if (!s) { JS_FreeValue(ctx, obj); return JS_EXCEPTION; } s->target = JS_DupValue(ctx, target); s->handler = JS_DupValue(ctx, handler); s->is_func = JS_IsFunction(ctx, target); s->is_revoked = FALSE; JS_SetOpaque(obj, s); JS_SetConstructorBit(ctx, obj, JS_IsConstructor(ctx, target)); return obj; } static JSValue js_proxy_revoke(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, JSValue *func_data) { JSProxyData *s = JS_GetOpaque(func_data[0], JS_CLASS_PROXY); if (s) { /* We do not free the handler and target in case they are referenced as constants in the C call stack */ s->is_revoked = TRUE; JS_FreeValue(ctx, func_data[0]); func_data[0] = JS_NULL; } return JS_UNDEFINED; } static JSValue js_proxy_revoke_constructor(JSContext *ctx, JSValueConst proxy_obj) { return JS_NewCFunctionData(ctx, js_proxy_revoke, 0, 0, 1, &proxy_obj); } static JSValue js_proxy_revocable(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue proxy_obj, revoke_obj = JS_UNDEFINED, obj; proxy_obj = js_proxy_constructor(ctx, JS_UNDEFINED, argc, argv); if (JS_IsException(proxy_obj)) goto fail; revoke_obj = js_proxy_revoke_constructor(ctx, proxy_obj); if (JS_IsException(revoke_obj)) goto fail; obj = JS_NewObject(ctx); if (JS_IsException(obj)) goto fail; // XXX: exceptions? JS_DefinePropertyValue(ctx, obj, JS_ATOM_proxy, proxy_obj, JS_PROP_C_W_E); JS_DefinePropertyValue(ctx, obj, JS_ATOM_revoke, revoke_obj, JS_PROP_C_W_E); return obj; fail: JS_FreeValue(ctx, proxy_obj); JS_FreeValue(ctx, revoke_obj); return JS_EXCEPTION; } static const JSCFunctionListEntry js_proxy_funcs[] = { JS_CFUNC_DEF("revocable", 2, js_proxy_revocable ), }; static const JSClassShortDef js_proxy_class_def[] = { { JS_ATOM_Object, js_proxy_finalizer, js_proxy_mark }, /* JS_CLASS_PROXY */ }; void JS_AddIntrinsicProxy(JSContext *ctx) { JSRuntime *rt = ctx->rt; JSValue obj1; if (!JS_IsRegisteredClass(rt, JS_CLASS_PROXY)) { init_class_range(rt, js_proxy_class_def, JS_CLASS_PROXY, countof(js_proxy_class_def)); rt->class_array[JS_CLASS_PROXY].exotic = &js_proxy_exotic_methods; rt->class_array[JS_CLASS_PROXY].call = js_proxy_call; } obj1 = JS_NewCFunction2(ctx, js_proxy_constructor, "Proxy", 2, JS_CFUNC_constructor, 0); JS_SetConstructorBit(ctx, obj1, TRUE); JS_SetPropertyFunctionList(ctx, obj1, js_proxy_funcs, countof(js_proxy_funcs)); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "Proxy", obj1, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); }