mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 11:37:35 +00:00
962 lines
32 KiB
C
962 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\"");
|
||
|
/* 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);
|
||
|
}
|