cosmopolitan/third_party/quickjs/proxy.c
mataha 879bb84244
Update quickjs (#890)
Includes additional fixes from main repo's unmerged PRs:

 - quickjs#132: Fix undefined behavior: shift 32 bits for uint32_t in bf_set_ui
 - quickjs#171: Fix locale-aware representation of hours in Date class
 - quickjs#182: Fix stack overflow in CVE-2023-31922
2023-11-30 10:51:16 -08:00

964 lines
32 KiB
C

/*
* 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\"");
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 (js_check_stack_overflow(ctx->rt, 0)) {
JS_ThrowStackOverflow(ctx);
return -1;
}
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);
}