mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 19:43:32 +00:00
459 lines
15 KiB
C
459 lines
15 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 "third_party/quickjs/internal.h"
|
|
#include "third_party/quickjs/libregexp.h"
|
|
#include "third_party/quickjs/quickjs.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 */
|
|
|
|
/* XXX: Should take JSValueConst arguments */
|
|
BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2,
|
|
JSStrictEqModeEnum eq_mode)
|
|
{
|
|
BOOL res;
|
|
int tag1, tag2;
|
|
double d1, d2;
|
|
tag1 = JS_VALUE_GET_NORM_TAG(op1);
|
|
tag2 = JS_VALUE_GET_NORM_TAG(op2);
|
|
switch(tag1) {
|
|
case JS_TAG_BOOL:
|
|
if (tag1 != tag2) {
|
|
res = FALSE;
|
|
} else {
|
|
res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2);
|
|
goto done_no_free;
|
|
}
|
|
break;
|
|
case JS_TAG_NULL:
|
|
case JS_TAG_UNDEFINED:
|
|
res = (tag1 == tag2);
|
|
break;
|
|
case JS_TAG_STRING:
|
|
{
|
|
JSString *p1, *p2;
|
|
if (tag1 != tag2) {
|
|
res = FALSE;
|
|
} else {
|
|
p1 = JS_VALUE_GET_STRING(op1);
|
|
p2 = JS_VALUE_GET_STRING(op2);
|
|
res = (js_string_compare(ctx, p1, p2) == 0);
|
|
}
|
|
}
|
|
break;
|
|
case JS_TAG_SYMBOL:
|
|
{
|
|
JSAtomStruct *p1, *p2;
|
|
if (tag1 != tag2) {
|
|
res = FALSE;
|
|
} else {
|
|
p1 = JS_VALUE_GET_PTR(op1);
|
|
p2 = JS_VALUE_GET_PTR(op2);
|
|
res = (p1 == p2);
|
|
}
|
|
}
|
|
break;
|
|
case JS_TAG_OBJECT:
|
|
if (tag1 != tag2)
|
|
res = FALSE;
|
|
else
|
|
res = JS_VALUE_GET_OBJ(op1) == JS_VALUE_GET_OBJ(op2);
|
|
break;
|
|
case JS_TAG_INT:
|
|
d1 = JS_VALUE_GET_INT(op1);
|
|
if (tag2 == JS_TAG_INT) {
|
|
d2 = JS_VALUE_GET_INT(op2);
|
|
goto number_test;
|
|
} else if (tag2 == JS_TAG_FLOAT64) {
|
|
d2 = JS_VALUE_GET_FLOAT64(op2);
|
|
goto number_test;
|
|
} else {
|
|
res = FALSE;
|
|
}
|
|
break;
|
|
case JS_TAG_FLOAT64:
|
|
d1 = JS_VALUE_GET_FLOAT64(op1);
|
|
if (tag2 == JS_TAG_FLOAT64) {
|
|
d2 = JS_VALUE_GET_FLOAT64(op2);
|
|
} else if (tag2 == JS_TAG_INT) {
|
|
d2 = JS_VALUE_GET_INT(op2);
|
|
} else {
|
|
res = FALSE;
|
|
break;
|
|
}
|
|
number_test:
|
|
if (UNLIKELY(eq_mode >= JS_EQ_SAME_VALUE)) {
|
|
JSFloat64Union u1, u2;
|
|
/* NaN is not always normalized, so this test is necessary */
|
|
if (isnan(d1) || isnan(d2)) {
|
|
res = isnan(d1) == isnan(d2);
|
|
} else if (eq_mode == JS_EQ_SAME_VALUE_ZERO) {
|
|
res = (d1 == d2); /* +0 == -0 */
|
|
} else {
|
|
u1.d = d1;
|
|
u2.d = d2;
|
|
res = (u1.u64 == u2.u64); /* +0 != -0 */
|
|
}
|
|
} else {
|
|
res = (d1 == d2); /* if NaN return false and +0 == -0 */
|
|
}
|
|
goto done_no_free;
|
|
#ifdef CONFIG_BIGNUM
|
|
case JS_TAG_BIG_INT:
|
|
{
|
|
bf_t a_s, *a, b_s, *b;
|
|
if (tag1 != tag2) {
|
|
res = FALSE;
|
|
break;
|
|
}
|
|
a = JS_ToBigFloat(ctx, &a_s, op1);
|
|
b = JS_ToBigFloat(ctx, &b_s, op2);
|
|
res = bf_cmp_eq(a, b);
|
|
if (a == &a_s)
|
|
bf_delete(a);
|
|
if (b == &b_s)
|
|
bf_delete(b);
|
|
}
|
|
break;
|
|
case JS_TAG_BIG_FLOAT:
|
|
{
|
|
JSBigFloat *p1, *p2;
|
|
const bf_t *a, *b;
|
|
if (tag1 != tag2) {
|
|
res = FALSE;
|
|
break;
|
|
}
|
|
p1 = JS_VALUE_GET_PTR(op1);
|
|
p2 = JS_VALUE_GET_PTR(op2);
|
|
a = &p1->num;
|
|
b = &p2->num;
|
|
if (UNLIKELY(eq_mode >= JS_EQ_SAME_VALUE)) {
|
|
if (eq_mode == JS_EQ_SAME_VALUE_ZERO &&
|
|
a->expn == BF_EXP_ZERO && b->expn == BF_EXP_ZERO) {
|
|
res = TRUE;
|
|
} else {
|
|
res = (bf_cmp_full(a, b) == 0);
|
|
}
|
|
} else {
|
|
res = bf_cmp_eq(a, b);
|
|
}
|
|
}
|
|
break;
|
|
case JS_TAG_BIG_DECIMAL:
|
|
{
|
|
JSBigDecimal *p1, *p2;
|
|
const bfdec_t *a, *b;
|
|
if (tag1 != tag2) {
|
|
res = FALSE;
|
|
break;
|
|
}
|
|
p1 = JS_VALUE_GET_PTR(op1);
|
|
p2 = JS_VALUE_GET_PTR(op2);
|
|
a = &p1->num;
|
|
b = &p2->num;
|
|
res = bfdec_cmp_eq(a, b);
|
|
}
|
|
break;
|
|
#endif
|
|
default:
|
|
res = FALSE;
|
|
break;
|
|
}
|
|
JS_FreeValue(ctx, op1);
|
|
JS_FreeValue(ctx, op2);
|
|
done_no_free:
|
|
return res;
|
|
}
|
|
|
|
BOOL js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2)
|
|
{
|
|
return js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT);
|
|
}
|
|
|
|
BOOL js_same_value(JSContext *ctx, JSValueConst op1, JSValueConst op2)
|
|
{
|
|
return js_strict_eq2(ctx,
|
|
JS_DupValue(ctx, op1), JS_DupValue(ctx, op2),
|
|
JS_EQ_SAME_VALUE);
|
|
}
|
|
|
|
BOOL js_same_value_zero(JSContext *ctx, JSValueConst op1, JSValueConst op2)
|
|
{
|
|
return js_strict_eq2(ctx,
|
|
JS_DupValue(ctx, op1), JS_DupValue(ctx, op2),
|
|
JS_EQ_SAME_VALUE_ZERO);
|
|
}
|
|
|
|
int js_strict_eq_slow(JSContext *ctx, JSValue *sp, BOOL is_neq)
|
|
{
|
|
BOOL res;
|
|
res = js_strict_eq(ctx, sp[-2], sp[-1]);
|
|
sp[-2] = JS_NewBool(ctx, res ^ is_neq);
|
|
return 0;
|
|
}
|
|
|
|
static BOOL tag_is_number(uint32_t tag)
|
|
{
|
|
return (tag == JS_TAG_INT || tag == JS_TAG_BIG_INT ||
|
|
tag == JS_TAG_FLOAT64 || tag == JS_TAG_BIG_FLOAT ||
|
|
tag == JS_TAG_BIG_DECIMAL);
|
|
}
|
|
|
|
static inline BOOL JS_IsHTMLDDA(JSContext *ctx, JSValueConst obj)
|
|
{
|
|
JSObject *p;
|
|
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
|
|
return FALSE;
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
return p->is_HTMLDDA;
|
|
}
|
|
|
|
int js_eq_slow(JSContext *ctx, JSValue *sp, BOOL is_neq)
|
|
{
|
|
#ifdef CONFIG_BIGNUM
|
|
JSValue op1, op2, ret;
|
|
int res;
|
|
uint32_t tag1, tag2;
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
redo:
|
|
tag1 = JS_VALUE_GET_NORM_TAG(op1);
|
|
tag2 = JS_VALUE_GET_NORM_TAG(op2);
|
|
if (tag_is_number(tag1) && tag_is_number(tag2)) {
|
|
if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) {
|
|
res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2);
|
|
} else if ((tag1 == JS_TAG_FLOAT64 &&
|
|
(tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64)) ||
|
|
(tag2 == JS_TAG_FLOAT64 &&
|
|
(tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64))) {
|
|
double d1, d2;
|
|
if (tag1 == JS_TAG_FLOAT64) {
|
|
d1 = JS_VALUE_GET_FLOAT64(op1);
|
|
} else {
|
|
d1 = JS_VALUE_GET_INT(op1);
|
|
}
|
|
if (tag2 == JS_TAG_FLOAT64) {
|
|
d2 = JS_VALUE_GET_FLOAT64(op2);
|
|
} else {
|
|
d2 = JS_VALUE_GET_INT(op2);
|
|
}
|
|
res = (d1 == d2);
|
|
} else if (tag1 == JS_TAG_BIG_DECIMAL || tag2 == JS_TAG_BIG_DECIMAL) {
|
|
res = ctx->rt->bigdecimal_ops.compare(ctx, OP_eq, op1, op2);
|
|
if (res < 0)
|
|
goto exception;
|
|
} else if (tag1 == JS_TAG_BIG_FLOAT || tag2 == JS_TAG_BIG_FLOAT) {
|
|
res = ctx->rt->bigfloat_ops.compare(ctx, OP_eq, op1, op2);
|
|
if (res < 0)
|
|
goto exception;
|
|
} else {
|
|
res = ctx->rt->bigint_ops.compare(ctx, OP_eq, op1, op2);
|
|
if (res < 0)
|
|
goto exception;
|
|
}
|
|
} else if (tag1 == tag2) {
|
|
if (tag1 == JS_TAG_OBJECT) {
|
|
/* try the fallback operator */
|
|
res = js_call_binary_op_fallback(ctx, &ret, op1, op2,
|
|
is_neq ? OP_neq : OP_eq,
|
|
FALSE, HINT_NONE);
|
|
if (res != 0) {
|
|
JS_FreeValue(ctx, op1);
|
|
JS_FreeValue(ctx, op2);
|
|
if (res < 0) {
|
|
goto exception;
|
|
} else {
|
|
sp[-2] = ret;
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT);
|
|
} else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) ||
|
|
(tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) {
|
|
res = TRUE;
|
|
} else if ((tag1 == JS_TAG_STRING && tag_is_number(tag2)) ||
|
|
(tag2 == JS_TAG_STRING && tag_is_number(tag1))) {
|
|
if ((tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) &&
|
|
!is_math_mode(ctx)) {
|
|
if (tag1 == JS_TAG_STRING) {
|
|
op1 = JS_StringToBigInt(ctx, op1);
|
|
if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT)
|
|
goto invalid_bigint_string;
|
|
}
|
|
if (tag2 == JS_TAG_STRING) {
|
|
op2 = JS_StringToBigInt(ctx, op2);
|
|
if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT) {
|
|
invalid_bigint_string:
|
|
JS_FreeValue(ctx, op1);
|
|
JS_FreeValue(ctx, op2);
|
|
res = FALSE;
|
|
goto done;
|
|
}
|
|
}
|
|
} else {
|
|
op1 = JS_ToNumericFree(ctx, op1);
|
|
if (JS_IsException(op1)) {
|
|
JS_FreeValue(ctx, op2);
|
|
goto exception;
|
|
}
|
|
op2 = JS_ToNumericFree(ctx, op2);
|
|
if (JS_IsException(op2)) {
|
|
JS_FreeValue(ctx, op1);
|
|
goto exception;
|
|
}
|
|
}
|
|
res = js_strict_eq(ctx, op1, op2);
|
|
} else if (tag1 == JS_TAG_BOOL) {
|
|
op1 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op1));
|
|
goto redo;
|
|
} else if (tag2 == JS_TAG_BOOL) {
|
|
op2 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op2));
|
|
goto redo;
|
|
} else if ((tag1 == JS_TAG_OBJECT &&
|
|
(tag_is_number(tag2) || tag2 == JS_TAG_STRING || tag2 == JS_TAG_SYMBOL)) ||
|
|
(tag2 == JS_TAG_OBJECT &&
|
|
(tag_is_number(tag1) || tag1 == JS_TAG_STRING || tag1 == JS_TAG_SYMBOL))) {
|
|
/* try the fallback operator */
|
|
res = js_call_binary_op_fallback(ctx, &ret, op1, op2,
|
|
is_neq ? OP_neq : OP_eq,
|
|
FALSE, HINT_NONE);
|
|
if (res != 0) {
|
|
JS_FreeValue(ctx, op1);
|
|
JS_FreeValue(ctx, op2);
|
|
if (res < 0) {
|
|
goto exception;
|
|
} else {
|
|
sp[-2] = ret;
|
|
return 0;
|
|
}
|
|
}
|
|
op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE);
|
|
if (JS_IsException(op1)) {
|
|
JS_FreeValue(ctx, op2);
|
|
goto exception;
|
|
}
|
|
op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE);
|
|
if (JS_IsException(op2)) {
|
|
JS_FreeValue(ctx, op1);
|
|
goto exception;
|
|
}
|
|
goto redo;
|
|
} else {
|
|
/* IsHTMLDDA object is equivalent to undefined for '==' and '!=' */
|
|
if ((JS_IsHTMLDDA(ctx, op1) &&
|
|
(tag2 == JS_TAG_NULL || tag2 == JS_TAG_UNDEFINED)) ||
|
|
(JS_IsHTMLDDA(ctx, op2) &&
|
|
(tag1 == JS_TAG_NULL || tag1 == JS_TAG_UNDEFINED))) {
|
|
res = TRUE;
|
|
} else {
|
|
res = FALSE;
|
|
}
|
|
JS_FreeValue(ctx, op1);
|
|
JS_FreeValue(ctx, op2);
|
|
}
|
|
done:
|
|
sp[-2] = JS_NewBool(ctx, res ^ is_neq);
|
|
return 0;
|
|
exception:
|
|
sp[-2] = JS_UNDEFINED;
|
|
sp[-1] = JS_UNDEFINED;
|
|
return -1;
|
|
#else /* CONFIG_BIGNUM */
|
|
JSValue op1, op2;
|
|
int tag1, tag2;
|
|
BOOL res;
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
redo:
|
|
tag1 = JS_VALUE_GET_NORM_TAG(op1);
|
|
tag2 = JS_VALUE_GET_NORM_TAG(op2);
|
|
if (tag1 == tag2 ||
|
|
(tag1 == JS_TAG_INT && tag2 == JS_TAG_FLOAT64) ||
|
|
(tag2 == JS_TAG_INT && tag1 == JS_TAG_FLOAT64)) {
|
|
res = js_strict_eq(ctx, op1, op2);
|
|
} else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) ||
|
|
(tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) {
|
|
res = TRUE;
|
|
} else if ((tag1 == JS_TAG_STRING && (tag2 == JS_TAG_INT ||
|
|
tag2 == JS_TAG_FLOAT64)) ||
|
|
(tag2 == JS_TAG_STRING && (tag1 == JS_TAG_INT ||
|
|
tag1 == JS_TAG_FLOAT64))) {
|
|
double d1;
|
|
double d2;
|
|
if (JS_ToFloat64Free(ctx, &d1, op1)) {
|
|
JS_FreeValue(ctx, op2);
|
|
goto exception;
|
|
}
|
|
if (JS_ToFloat64Free(ctx, &d2, op2))
|
|
goto exception;
|
|
res = (d1 == d2);
|
|
} else if (tag1 == JS_TAG_BOOL) {
|
|
op1 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op1));
|
|
goto redo;
|
|
} else if (tag2 == JS_TAG_BOOL) {
|
|
op2 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op2));
|
|
goto redo;
|
|
} else if (tag1 == JS_TAG_OBJECT &&
|
|
(tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64 || tag2 == JS_TAG_STRING || tag2 == JS_TAG_SYMBOL)) {
|
|
op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE);
|
|
if (JS_IsException(op1)) {
|
|
JS_FreeValue(ctx, op2);
|
|
goto exception;
|
|
}
|
|
goto redo;
|
|
} else if (tag2 == JS_TAG_OBJECT &&
|
|
(tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64 || tag1 == JS_TAG_STRING || tag1 == JS_TAG_SYMBOL)) {
|
|
op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE);
|
|
if (JS_IsException(op2)) {
|
|
JS_FreeValue(ctx, op1);
|
|
goto exception;
|
|
}
|
|
goto redo;
|
|
} else {
|
|
/* IsHTMLDDA object is equivalent to undefined for '==' and '!=' */
|
|
if ((JS_IsHTMLDDA(ctx, op1) &&
|
|
(tag2 == JS_TAG_NULL || tag2 == JS_TAG_UNDEFINED)) ||
|
|
(JS_IsHTMLDDA(ctx, op2) &&
|
|
(tag1 == JS_TAG_NULL || tag1 == JS_TAG_UNDEFINED))) {
|
|
res = TRUE;
|
|
} else {
|
|
res = FALSE;
|
|
}
|
|
JS_FreeValue(ctx, op1);
|
|
JS_FreeValue(ctx, op2);
|
|
}
|
|
sp[-2] = JS_NewBool(ctx, res ^ is_neq);
|
|
return 0;
|
|
exception:
|
|
sp[-2] = JS_UNDEFINED;
|
|
sp[-1] = JS_UNDEFINED;
|
|
return -1;
|
|
#endif /* CONFIG_BIGNUM */
|
|
}
|