mirror of https://github.com/svaarala/duktape.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1332 lines
36 KiB
1332 lines
36 KiB
/*
|
|
* Ecmascript specification algorithm and conversion helpers.
|
|
*
|
|
* These helpers encapsulate the primitive Ecmascript operation
|
|
* semantics, and are used by the bytecode executor and the API
|
|
* (among other places). Note that some primitives are only
|
|
* implemented as part of the API and have no "internal" helper.
|
|
* (This is the case when an internal helper would not really be
|
|
* useful; e.g. the operation is rare, uses value stack heavily,
|
|
* etc.)
|
|
*
|
|
* The operation arguments depend on what is required to implement
|
|
* the operation:
|
|
*
|
|
* - If an operation is simple and stateless, and has no side
|
|
* effects, it won't take an duk_hthread argument and its
|
|
* arguments may be duk_tval pointers (which are safe as long
|
|
* as no side effects take place).
|
|
*
|
|
* - If complex coercions are required (e.g. a "ToNumber" coercion)
|
|
* or errors may be thrown, the operation takes an duk_hthread
|
|
* argument. This also implies that the operation may have
|
|
* arbitrary side effects, invalidating any duk_tval pointers.
|
|
*
|
|
* - For operations with potential side effects, arguments can be
|
|
* taken in several ways:
|
|
*
|
|
* a) as duk_tval pointers, which makes sense if the "common case"
|
|
* can be resolved without side effects (e.g. coercion); the
|
|
* arguments are pushed to the valstack for coercion if
|
|
* necessary
|
|
*
|
|
* b) as duk_tval values
|
|
*
|
|
* c) implicitly on value stack top
|
|
*
|
|
* d) as indices to the value stack
|
|
*
|
|
* Future work:
|
|
*
|
|
* - Argument styles may not be the most sensible in every case now.
|
|
*
|
|
* - In-place coercions might be useful for several operations, if
|
|
* in-place coercion is OK for the bytecode executor and the API.
|
|
*/
|
|
|
|
#include "duk_internal.h"
|
|
|
|
/*
|
|
* [[DefaultValue]] (E5 Section 8.12.8)
|
|
*
|
|
* ==> implemented in the API.
|
|
*/
|
|
|
|
/*
|
|
* ToPrimitive() (E5 Section 9.1)
|
|
*
|
|
* ==> implemented in the API.
|
|
*/
|
|
|
|
/*
|
|
* ToBoolean() (E5 Section 9.2)
|
|
*/
|
|
|
|
DUK_INTERNAL duk_bool_t duk_js_toboolean(duk_tval *tv) {
|
|
switch (DUK_TVAL_GET_TAG(tv)) {
|
|
case DUK_TAG_UNDEFINED:
|
|
case DUK_TAG_NULL:
|
|
return 0;
|
|
case DUK_TAG_BOOLEAN:
|
|
return DUK_TVAL_GET_BOOLEAN(tv);
|
|
case DUK_TAG_STRING: {
|
|
duk_hstring *h = DUK_TVAL_GET_STRING(tv);
|
|
DUK_ASSERT(h != NULL);
|
|
return (DUK_HSTRING_GET_BYTELEN(h) > 0 ? 1 : 0);
|
|
}
|
|
case DUK_TAG_OBJECT: {
|
|
return 1;
|
|
}
|
|
case DUK_TAG_BUFFER: {
|
|
/* Mimic ArrayBuffer semantics: objects coerce true, regardless
|
|
* of buffer length (zero or not) or context.
|
|
*/
|
|
return 1;
|
|
}
|
|
case DUK_TAG_POINTER: {
|
|
void *p = DUK_TVAL_GET_POINTER(tv);
|
|
return (p != NULL ? 1 : 0);
|
|
}
|
|
case DUK_TAG_LIGHTFUNC: {
|
|
return 1;
|
|
}
|
|
#if defined(DUK_USE_FASTINT)
|
|
case DUK_TAG_FASTINT:
|
|
if (DUK_TVAL_GET_FASTINT(tv) != 0) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
#endif
|
|
default: {
|
|
/* number */
|
|
duk_double_t d;
|
|
int c;
|
|
DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv));
|
|
DUK_ASSERT(DUK_TVAL_IS_DOUBLE(tv));
|
|
d = DUK_TVAL_GET_DOUBLE(tv);
|
|
c = DUK_FPCLASSIFY((double) d);
|
|
if (c == DUK_FP_ZERO || c == DUK_FP_NAN) {
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
DUK_UNREACHABLE();
|
|
}
|
|
|
|
/*
|
|
* ToNumber() (E5 Section 9.3)
|
|
*
|
|
* Value to convert must be on stack top, and is popped before exit.
|
|
*
|
|
* See: http://www.cs.indiana.edu/~burger/FP-Printing-PLDI96.pdf
|
|
* http://www.cs.indiana.edu/~burger/fp/index.html
|
|
*
|
|
* Notes on the conversion:
|
|
*
|
|
* - There are specific requirements on the accuracy of the conversion
|
|
* through a "Mathematical Value" (MV), so this conversion is not
|
|
* trivial.
|
|
*
|
|
* - Quick rejects (e.g. based on first char) are difficult because
|
|
* the grammar allows leading and trailing white space.
|
|
*
|
|
* - Quick reject based on string length is difficult even after
|
|
* accounting for white space; there may be arbitrarily many
|
|
* decimal digits.
|
|
*
|
|
* - Standard grammar allows decimal values ("123"), hex values
|
|
* ("0x123") and infinities
|
|
*
|
|
* - Unlike source code literals, ToNumber() coerces empty strings
|
|
* and strings with only whitespace to zero (not NaN).
|
|
*/
|
|
|
|
/* E5 Section 9.3.1 */
|
|
DUK_LOCAL duk_double_t duk__tonumber_string_raw(duk_hthread *thr) {
|
|
duk_context *ctx = (duk_context *) thr;
|
|
duk_small_uint_t s2n_flags;
|
|
duk_double_t d;
|
|
|
|
/* Quite lenient, e.g. allow empty as zero, but don't allow trailing
|
|
* garbage.
|
|
*/
|
|
s2n_flags = DUK_S2N_FLAG_TRIM_WHITE |
|
|
DUK_S2N_FLAG_ALLOW_EXP |
|
|
DUK_S2N_FLAG_ALLOW_PLUS |
|
|
DUK_S2N_FLAG_ALLOW_MINUS |
|
|
DUK_S2N_FLAG_ALLOW_INF |
|
|
DUK_S2N_FLAG_ALLOW_FRAC |
|
|
DUK_S2N_FLAG_ALLOW_NAKED_FRAC |
|
|
DUK_S2N_FLAG_ALLOW_EMPTY_FRAC |
|
|
DUK_S2N_FLAG_ALLOW_EMPTY_AS_ZERO |
|
|
DUK_S2N_FLAG_ALLOW_LEADING_ZERO |
|
|
DUK_S2N_FLAG_ALLOW_AUTO_HEX_INT;
|
|
|
|
duk_numconv_parse(ctx, 10 /*radix*/, s2n_flags);
|
|
d = duk_get_number(ctx, -1);
|
|
duk_pop(ctx);
|
|
|
|
return d;
|
|
}
|
|
|
|
DUK_INTERNAL duk_double_t duk_js_tonumber(duk_hthread *thr, duk_tval *tv) {
|
|
duk_context *ctx = (duk_hthread *) thr;
|
|
|
|
DUK_ASSERT(thr != NULL);
|
|
DUK_ASSERT(tv != NULL);
|
|
|
|
switch (DUK_TVAL_GET_TAG(tv)) {
|
|
case DUK_TAG_UNDEFINED: {
|
|
/* return a specific NaN (although not strictly necessary) */
|
|
duk_double_union du;
|
|
DUK_DBLUNION_SET_NAN(&du);
|
|
DUK_ASSERT(DUK_DBLUNION_IS_NORMALIZED(&du));
|
|
return du.d;
|
|
}
|
|
case DUK_TAG_NULL: {
|
|
/* +0.0 */
|
|
return 0.0;
|
|
}
|
|
case DUK_TAG_BOOLEAN: {
|
|
if (DUK_TVAL_IS_BOOLEAN_TRUE(tv)) {
|
|
return 1.0;
|
|
}
|
|
return 0.0;
|
|
}
|
|
case DUK_TAG_STRING: {
|
|
duk_hstring *h = DUK_TVAL_GET_STRING(tv);
|
|
duk_push_hstring(ctx, h);
|
|
return duk__tonumber_string_raw(thr);
|
|
}
|
|
case DUK_TAG_BUFFER: /* plain buffer treated like object */
|
|
case DUK_TAG_OBJECT: {
|
|
/* Note: ToPrimitive(object,hint) == [[DefaultValue]](object,hint),
|
|
* so use [[DefaultValue]] directly.
|
|
*/
|
|
duk_double_t d;
|
|
duk_push_tval(ctx, tv);
|
|
duk_to_defaultvalue(ctx, -1, DUK_HINT_NUMBER); /* 'tv' becomes invalid */
|
|
|
|
/* recursive call for a primitive value (guaranteed not to cause second
|
|
* recursion).
|
|
*/
|
|
DUK_ASSERT(duk_get_tval(ctx, -1) != NULL);
|
|
d = duk_js_tonumber(thr, duk_get_tval(ctx, -1));
|
|
|
|
duk_pop(ctx);
|
|
return d;
|
|
}
|
|
case DUK_TAG_POINTER: {
|
|
/* Coerce like boolean */
|
|
void *p = DUK_TVAL_GET_POINTER(tv);
|
|
return (p != NULL ? 1.0 : 0.0);
|
|
}
|
|
case DUK_TAG_LIGHTFUNC: {
|
|
/* +(function(){}) -> NaN */
|
|
return DUK_DOUBLE_NAN;
|
|
}
|
|
#if defined(DUK_USE_FASTINT)
|
|
case DUK_TAG_FASTINT:
|
|
return (duk_double_t) DUK_TVAL_GET_FASTINT(tv);
|
|
#endif
|
|
default: {
|
|
/* number */
|
|
DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv));
|
|
DUK_ASSERT(DUK_TVAL_IS_DOUBLE(tv));
|
|
return DUK_TVAL_GET_DOUBLE(tv);
|
|
}
|
|
}
|
|
|
|
DUK_UNREACHABLE();
|
|
}
|
|
|
|
/*
|
|
* ToInteger() (E5 Section 9.4)
|
|
*/
|
|
|
|
/* exposed, used by e.g. duk_bi_date.c */
|
|
DUK_INTERNAL duk_double_t duk_js_tointeger_number(duk_double_t x) {
|
|
duk_small_int_t c = (duk_small_int_t) DUK_FPCLASSIFY(x);
|
|
|
|
if (c == DUK_FP_NAN) {
|
|
return 0.0;
|
|
} else if (c == DUK_FP_ZERO || c == DUK_FP_INFINITE) {
|
|
/* XXX: FP_ZERO check can be removed, the else clause handles it
|
|
* correctly (preserving sign).
|
|
*/
|
|
return x;
|
|
} else {
|
|
duk_small_int_t s = (duk_small_int_t) DUK_SIGNBIT(x);
|
|
x = DUK_FLOOR(DUK_FABS(x)); /* truncate towards zero */
|
|
if (s) {
|
|
x = -x;
|
|
}
|
|
return x;
|
|
}
|
|
}
|
|
|
|
DUK_INTERNAL duk_double_t duk_js_tointeger(duk_hthread *thr, duk_tval *tv) {
|
|
/* XXX: fastint */
|
|
duk_double_t d = duk_js_tonumber(thr, tv); /* invalidates tv */
|
|
return duk_js_tointeger_number(d);
|
|
}
|
|
|
|
/*
|
|
* ToInt32(), ToUint32(), ToUint16() (E5 Sections 9.5, 9.6, 9.7)
|
|
*/
|
|
|
|
/* combined algorithm matching E5 Sections 9.5 and 9.6 */
|
|
DUK_LOCAL duk_double_t duk__toint32_touint32_helper(duk_double_t x, duk_bool_t is_toint32) {
|
|
duk_small_int_t c = (duk_small_int_t) DUK_FPCLASSIFY(x);
|
|
duk_small_int_t s;
|
|
|
|
if (c == DUK_FP_NAN || c == DUK_FP_ZERO || c == DUK_FP_INFINITE) {
|
|
return 0.0;
|
|
}
|
|
|
|
|
|
/* x = sign(x) * floor(abs(x)), i.e. truncate towards zero, keep sign */
|
|
s = (duk_small_int_t) DUK_SIGNBIT(x);
|
|
x = DUK_FLOOR(DUK_FABS(x));
|
|
if (s) {
|
|
x = -x;
|
|
}
|
|
|
|
/* NOTE: fmod(x) result sign is same as sign of x, which
|
|
* differs from what Javascript wants (see Section 9.6).
|
|
*/
|
|
|
|
x = DUK_FMOD(x, DUK_DOUBLE_2TO32); /* -> x in ]-2**32, 2**32[ */
|
|
|
|
if (x < 0.0) {
|
|
x += DUK_DOUBLE_2TO32;
|
|
}
|
|
/* -> x in [0, 2**32[ */
|
|
|
|
if (is_toint32) {
|
|
if (x >= DUK_DOUBLE_2TO31) {
|
|
/* x in [2**31, 2**32[ */
|
|
|
|
x -= DUK_DOUBLE_2TO32; /* -> x in [-2**31,2**31[ */
|
|
}
|
|
}
|
|
|
|
return x;
|
|
}
|
|
|
|
DUK_INTERNAL duk_int32_t duk_js_toint32(duk_hthread *thr, duk_tval *tv) {
|
|
duk_double_t d;
|
|
|
|
#if defined(DUK_USE_FASTINT)
|
|
if (DUK_TVAL_IS_FASTINT(tv)) {
|
|
return DUK_TVAL_GET_FASTINT_I32(tv);
|
|
}
|
|
#endif
|
|
|
|
d = duk_js_tonumber(thr, tv); /* invalidates tv */
|
|
d = duk__toint32_touint32_helper(d, 1);
|
|
DUK_ASSERT(DUK_FPCLASSIFY(d) == DUK_FP_ZERO || DUK_FPCLASSIFY(d) == DUK_FP_NORMAL);
|
|
DUK_ASSERT(d >= -2147483648.0 && d <= 2147483647.0); /* [-0x80000000,0x7fffffff] */
|
|
DUK_ASSERT(d == ((duk_double_t) ((duk_int32_t) d))); /* whole, won't clip */
|
|
return (duk_int32_t) d;
|
|
}
|
|
|
|
|
|
DUK_INTERNAL duk_uint32_t duk_js_touint32(duk_hthread *thr, duk_tval *tv) {
|
|
duk_double_t d;
|
|
|
|
#if defined(DUK_USE_FASTINT)
|
|
if (DUK_TVAL_IS_FASTINT(tv)) {
|
|
return DUK_TVAL_GET_FASTINT_U32(tv);
|
|
}
|
|
#endif
|
|
|
|
d = duk_js_tonumber(thr, tv); /* invalidates tv */
|
|
d = duk__toint32_touint32_helper(d, 0);
|
|
DUK_ASSERT(DUK_FPCLASSIFY(d) == DUK_FP_ZERO || DUK_FPCLASSIFY(d) == DUK_FP_NORMAL);
|
|
DUK_ASSERT(d >= 0.0 && d <= 4294967295.0); /* [0x00000000, 0xffffffff] */
|
|
DUK_ASSERT(d == ((duk_double_t) ((duk_uint32_t) d))); /* whole, won't clip */
|
|
return (duk_uint32_t) d;
|
|
|
|
}
|
|
|
|
DUK_INTERNAL duk_uint16_t duk_js_touint16(duk_hthread *thr, duk_tval *tv) {
|
|
/* should be a safe way to compute this */
|
|
return (duk_uint16_t) (duk_js_touint32(thr, tv) & 0x0000ffffU);
|
|
}
|
|
|
|
/*
|
|
* ToString() (E5 Section 9.8)
|
|
*
|
|
* ==> implemented in the API.
|
|
*/
|
|
|
|
/*
|
|
* ToObject() (E5 Section 9.9)
|
|
*
|
|
* ==> implemented in the API.
|
|
*/
|
|
|
|
/*
|
|
* CheckObjectCoercible() (E5 Section 9.10)
|
|
*
|
|
* Note: no API equivalent now.
|
|
*/
|
|
|
|
#if 0 /* unused */
|
|
DUK_INTERNAL void duk_js_checkobjectcoercible(duk_hthread *thr, duk_tval *tv_x) {
|
|
duk_small_uint_t tag = DUK_TVAL_GET_TAG(tv_x);
|
|
|
|
/* Note: this must match ToObject() behavior */
|
|
|
|
if (tag == DUK_TAG_UNDEFINED ||
|
|
tag == DUK_TAG_NULL ||
|
|
tag == DUK_TAG_POINTER ||
|
|
tag == DUK_TAG_BUFFER) {
|
|
DUK_ERROR_TYPE(thr, "not object coercible");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* IsCallable() (E5 Section 9.11)
|
|
*
|
|
* XXX: API equivalent is a separate implementation now, and this has
|
|
* currently no callers.
|
|
*/
|
|
|
|
#if 0 /* unused */
|
|
DUK_INTERNAL duk_bool_t duk_js_iscallable(duk_tval *tv_x) {
|
|
duk_hobject *obj;
|
|
|
|
if (!DUK_TVAL_IS_OBJECT(tv_x)) {
|
|
return 0;
|
|
}
|
|
obj = DUK_TVAL_GET_OBJECT(tv_x);
|
|
DUK_ASSERT(obj != NULL);
|
|
|
|
return DUK_HOBJECT_IS_CALLABLE(obj);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Loose equality, strict equality, and SameValue (E5 Sections 11.9.1, 11.9.4,
|
|
* 9.12). These have much in common so they can share some helpers.
|
|
*
|
|
* Future work notes:
|
|
*
|
|
* - Current implementation (and spec definition) has recursion; this should
|
|
* be fixed if possible.
|
|
*
|
|
* - String-to-number coercion should be possible without going through the
|
|
* value stack (and be more compact) if a shared helper is invoked.
|
|
*/
|
|
|
|
/* Note that this is the same operation for strict and loose equality:
|
|
* - E5 Section 11.9.3, step 1.c (loose)
|
|
* - E5 Section 11.9.6, step 4 (strict)
|
|
*/
|
|
|
|
DUK_LOCAL duk_bool_t duk__js_equals_number(duk_double_t x, duk_double_t y) {
|
|
#if defined(DUK_USE_PARANOID_MATH)
|
|
/* Straightforward algorithm, makes fewer compiler assumptions. */
|
|
duk_small_int_t cx = (duk_small_int_t) DUK_FPCLASSIFY(x);
|
|
duk_small_int_t cy = (duk_small_int_t) DUK_FPCLASSIFY(y);
|
|
if (cx == DUK_FP_NAN || cy == DUK_FP_NAN) {
|
|
return 0;
|
|
}
|
|
if (cx == DUK_FP_ZERO && cy == DUK_FP_ZERO) {
|
|
return 1;
|
|
}
|
|
if (x == y) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
#else /* DUK_USE_PARANOID_MATH */
|
|
/* Better equivalent algorithm. If the compiler is compliant, C and
|
|
* Ecmascript semantics are identical for this particular comparison.
|
|
* In particular, NaNs must never compare equal and zeroes must compare
|
|
* equal regardless of sign. Could also use a macro, but this inlines
|
|
* already nicely (no difference on gcc, for instance).
|
|
*/
|
|
if (x == y) {
|
|
/* IEEE requires that NaNs compare false */
|
|
DUK_ASSERT(DUK_FPCLASSIFY(x) != DUK_FP_NAN);
|
|
DUK_ASSERT(DUK_FPCLASSIFY(y) != DUK_FP_NAN);
|
|
return 1;
|
|
} else {
|
|
/* IEEE requires that zeros compare the same regardless
|
|
* of their signed, so if both x and y are zeroes, they
|
|
* are caught above.
|
|
*/
|
|
DUK_ASSERT(!(DUK_FPCLASSIFY(x) == DUK_FP_ZERO && DUK_FPCLASSIFY(y) == DUK_FP_ZERO));
|
|
return 0;
|
|
}
|
|
#endif /* DUK_USE_PARANOID_MATH */
|
|
}
|
|
|
|
DUK_LOCAL duk_bool_t duk__js_samevalue_number(duk_double_t x, duk_double_t y) {
|
|
#if defined(DUK_USE_PARANOID_MATH)
|
|
duk_small_int_t cx = (duk_small_int_t) DUK_FPCLASSIFY(x);
|
|
duk_small_int_t cy = (duk_small_int_t) DUK_FPCLASSIFY(y);
|
|
|
|
if (cx == DUK_FP_NAN && cy == DUK_FP_NAN) {
|
|
/* SameValue(NaN, NaN) = true, regardless of NaN sign or extra bits */
|
|
return 1;
|
|
}
|
|
if (cx == DUK_FP_ZERO && cy == DUK_FP_ZERO) {
|
|
/* Note: cannot assume that a non-zero return value of signbit() would
|
|
* always be the same -- hence cannot (portably) use something like:
|
|
*
|
|
* signbit(x) == signbit(y)
|
|
*/
|
|
duk_small_int_t sx = (DUK_SIGNBIT(x) ? 1 : 0);
|
|
duk_small_int_t sy = (DUK_SIGNBIT(y) ? 1 : 0);
|
|
return (sx == sy);
|
|
}
|
|
|
|
/* normal comparison; known:
|
|
* - both x and y are not NaNs (but one of them can be)
|
|
* - both x and y are not zero (but one of them can be)
|
|
* - x and y may be denormal or infinite
|
|
*/
|
|
|
|
return (x == y);
|
|
#else /* DUK_USE_PARANOID_MATH */
|
|
duk_small_int_t cx = (duk_small_int_t) DUK_FPCLASSIFY(x);
|
|
duk_small_int_t cy = (duk_small_int_t) DUK_FPCLASSIFY(y);
|
|
|
|
if (x == y) {
|
|
/* IEEE requires that NaNs compare false */
|
|
DUK_ASSERT(DUK_FPCLASSIFY(x) != DUK_FP_NAN);
|
|
DUK_ASSERT(DUK_FPCLASSIFY(y) != DUK_FP_NAN);
|
|
|
|
/* Using classification has smaller footprint than direct comparison. */
|
|
if (DUK_UNLIKELY(cx == DUK_FP_ZERO && cy == DUK_FP_ZERO)) {
|
|
/* Note: cannot assume that a non-zero return value of signbit() would
|
|
* always be the same -- hence cannot (portably) use something like:
|
|
*
|
|
* signbit(x) == signbit(y)
|
|
*/
|
|
duk_small_int_t sx = (DUK_SIGNBIT(x) ? 1 : 0);
|
|
duk_small_int_t sy = (DUK_SIGNBIT(y) ? 1 : 0);
|
|
return (sx == sy);
|
|
}
|
|
return 1;
|
|
} else {
|
|
/* IEEE requires that zeros compare the same regardless
|
|
* of their signed, so if both x and y are zeroes, they
|
|
* are caught above.
|
|
*/
|
|
DUK_ASSERT(!(DUK_FPCLASSIFY(x) == DUK_FP_ZERO && DUK_FPCLASSIFY(y) == DUK_FP_ZERO));
|
|
|
|
/* Difference to non-strict/strict comparison is that NaNs compare
|
|
* equal and signed zero signs matter.
|
|
*/
|
|
if (DUK_UNLIKELY(cx == DUK_FP_NAN && cy == DUK_FP_NAN)) {
|
|
/* SameValue(NaN, NaN) = true, regardless of NaN sign or extra bits */
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif /* DUK_USE_PARANOID_MATH */
|
|
}
|
|
|
|
DUK_INTERNAL duk_bool_t duk_js_equals_helper(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, duk_small_int_t flags) {
|
|
duk_context *ctx = (duk_context *) thr;
|
|
duk_tval *tv_tmp;
|
|
|
|
/* If flags != 0 (strict or SameValue), thr can be NULL. For loose
|
|
* equals comparison it must be != NULL.
|
|
*/
|
|
DUK_ASSERT(flags != 0 || thr != NULL);
|
|
|
|
/*
|
|
* Same type?
|
|
*
|
|
* Note: since number values have no explicit tag in the 8-byte
|
|
* representation, need the awkward if + switch.
|
|
*/
|
|
|
|
#if defined(DUK_USE_FASTINT)
|
|
if (DUK_TVAL_IS_FASTINT(tv_x) && DUK_TVAL_IS_FASTINT(tv_y)) {
|
|
if (DUK_TVAL_GET_FASTINT(tv_x) == DUK_TVAL_GET_FASTINT(tv_y)) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
if (DUK_TVAL_IS_NUMBER(tv_x) && DUK_TVAL_IS_NUMBER(tv_y)) {
|
|
/* Catches both doubles and cases where only one argument is a fastint */
|
|
if (DUK_UNLIKELY((flags & DUK_EQUALS_FLAG_SAMEVALUE) != 0)) {
|
|
/* SameValue */
|
|
return duk__js_samevalue_number(DUK_TVAL_GET_NUMBER(tv_x),
|
|
DUK_TVAL_GET_NUMBER(tv_y));
|
|
} else {
|
|
/* equals and strict equals */
|
|
return duk__js_equals_number(DUK_TVAL_GET_NUMBER(tv_x),
|
|
DUK_TVAL_GET_NUMBER(tv_y));
|
|
}
|
|
} else if (DUK_TVAL_GET_TAG(tv_x) == DUK_TVAL_GET_TAG(tv_y)) {
|
|
switch (DUK_TVAL_GET_TAG(tv_x)) {
|
|
case DUK_TAG_UNDEFINED:
|
|
case DUK_TAG_NULL: {
|
|
return 1;
|
|
}
|
|
case DUK_TAG_BOOLEAN: {
|
|
return DUK_TVAL_GET_BOOLEAN(tv_x) == DUK_TVAL_GET_BOOLEAN(tv_y);
|
|
}
|
|
case DUK_TAG_POINTER: {
|
|
return DUK_TVAL_GET_POINTER(tv_x) == DUK_TVAL_GET_POINTER(tv_y);
|
|
}
|
|
case DUK_TAG_STRING:
|
|
case DUK_TAG_OBJECT: {
|
|
/* heap pointer comparison suffices */
|
|
return DUK_TVAL_GET_HEAPHDR(tv_x) == DUK_TVAL_GET_HEAPHDR(tv_y);
|
|
}
|
|
case DUK_TAG_BUFFER: {
|
|
/* In Duktape 2.x plain buffers mimic ArrayBuffer objects
|
|
* so always compare by heap pointer. In Duktape 1.x
|
|
* strict comparison would compare heap pointers and
|
|
* non-strict would compare contents.
|
|
*/
|
|
return DUK_TVAL_GET_HEAPHDR(tv_x) == DUK_TVAL_GET_HEAPHDR(tv_y);
|
|
}
|
|
case DUK_TAG_LIGHTFUNC: {
|
|
/* At least 'magic' has a significant impact on function
|
|
* identity.
|
|
*/
|
|
duk_small_uint_t lf_flags_x;
|
|
duk_small_uint_t lf_flags_y;
|
|
duk_c_function func_x;
|
|
duk_c_function func_y;
|
|
|
|
DUK_TVAL_GET_LIGHTFUNC(tv_x, func_x, lf_flags_x);
|
|
DUK_TVAL_GET_LIGHTFUNC(tv_y, func_y, lf_flags_y);
|
|
return ((func_x == func_y) && (lf_flags_x == lf_flags_y)) ? 1 : 0;
|
|
}
|
|
#if defined(DUK_USE_FASTINT)
|
|
case DUK_TAG_FASTINT:
|
|
#endif
|
|
default: {
|
|
DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv_x));
|
|
DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv_y));
|
|
DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_x));
|
|
DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_y));
|
|
DUK_UNREACHABLE();
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((flags & (DUK_EQUALS_FLAG_STRICT | DUK_EQUALS_FLAG_SAMEVALUE)) != 0) {
|
|
return 0;
|
|
}
|
|
|
|
DUK_ASSERT(flags == 0); /* non-strict equality from here on */
|
|
|
|
/*
|
|
* Types are different; various cases for non-strict comparison
|
|
*
|
|
* Since comparison is symmetric, we use a "swap trick" to reduce
|
|
* code size.
|
|
*/
|
|
|
|
/* Undefined/null are considered equal (e.g. "null == undefined" -> true). */
|
|
if ((DUK_TVAL_IS_UNDEFINED(tv_x) && DUK_TVAL_IS_NULL(tv_y)) ||
|
|
(DUK_TVAL_IS_NULL(tv_x) && DUK_TVAL_IS_UNDEFINED(tv_y))) {
|
|
return 1;
|
|
}
|
|
|
|
/* Number/string -> coerce string to number (e.g. "'1.5' == 1.5" -> true). */
|
|
if (DUK_TVAL_IS_NUMBER(tv_x) && DUK_TVAL_IS_STRING(tv_y)) {
|
|
/* the next 'if' is guaranteed to match after swap */
|
|
tv_tmp = tv_x;
|
|
tv_x = tv_y;
|
|
tv_y = tv_tmp;
|
|
}
|
|
if (DUK_TVAL_IS_STRING(tv_x) && DUK_TVAL_IS_NUMBER(tv_y)) {
|
|
/* XXX: this is possible without resorting to the value stack */
|
|
duk_double_t d1, d2;
|
|
d2 = DUK_TVAL_GET_NUMBER(tv_y);
|
|
duk_push_tval(ctx, tv_x);
|
|
duk_to_number(ctx, -1);
|
|
d1 = duk_require_number(ctx, -1);
|
|
duk_pop(ctx);
|
|
return duk__js_equals_number(d1, d2);
|
|
}
|
|
|
|
/* Boolean/any -> coerce boolean to number and try again. If boolean is
|
|
* compared to a pointer, the final comparison after coercion now always
|
|
* yields false (as pointer vs. number compares to false), but this is
|
|
* not special cased.
|
|
*/
|
|
if (DUK_TVAL_IS_BOOLEAN(tv_x)) {
|
|
tv_tmp = tv_x;
|
|
tv_x = tv_y;
|
|
tv_y = tv_tmp;
|
|
}
|
|
if (DUK_TVAL_IS_BOOLEAN(tv_y)) {
|
|
/* ToNumber(bool) is +1.0 or 0.0. Tagged boolean value is always 0 or 1. */
|
|
duk_bool_t rc;
|
|
DUK_ASSERT(DUK_TVAL_GET_BOOLEAN(tv_y) == 0 || DUK_TVAL_GET_BOOLEAN(tv_y) == 1);
|
|
duk_push_tval(ctx, tv_x);
|
|
duk_push_int(ctx, DUK_TVAL_GET_BOOLEAN(tv_y));
|
|
rc = duk_js_equals_helper(thr,
|
|
DUK_GET_TVAL_NEGIDX(ctx, -2),
|
|
DUK_GET_TVAL_NEGIDX(ctx, -1),
|
|
0 /*flags:nonstrict*/);
|
|
duk_pop_2(ctx);
|
|
return rc;
|
|
}
|
|
|
|
/* String-number/object -> coerce object to primitive (apparently without hint), then try again. */
|
|
if ((DUK_TVAL_IS_STRING(tv_x) || DUK_TVAL_IS_NUMBER(tv_x)) && DUK_TVAL_IS_OBJECT(tv_y)) {
|
|
tv_tmp = tv_x;
|
|
tv_x = tv_y;
|
|
tv_y = tv_tmp;
|
|
}
|
|
if (DUK_TVAL_IS_OBJECT(tv_x) && (DUK_TVAL_IS_STRING(tv_y) || DUK_TVAL_IS_NUMBER(tv_y))) {
|
|
duk_bool_t rc;
|
|
duk_push_tval(ctx, tv_x);
|
|
duk_push_tval(ctx, tv_y);
|
|
duk_to_primitive(ctx, -2, DUK_HINT_NONE); /* apparently no hint? */
|
|
rc = duk_js_equals_helper(thr,
|
|
DUK_GET_TVAL_NEGIDX(ctx, -2),
|
|
DUK_GET_TVAL_NEGIDX(ctx, -1),
|
|
0 /*flags:nonstrict*/);
|
|
duk_pop_2(ctx);
|
|
return rc;
|
|
}
|
|
|
|
/* Nothing worked -> not equal. */
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Comparisons (x >= y, x > y, x <= y, x < y)
|
|
*
|
|
* E5 Section 11.8.5: implement 'x < y' and then use negate and eval_left_first
|
|
* flags to get the rest.
|
|
*/
|
|
|
|
/* XXX: this should probably just operate on the stack top, because it
|
|
* needs to push stuff on the stack anyway...
|
|
*/
|
|
|
|
DUK_INTERNAL duk_small_int_t duk_js_data_compare(const duk_uint8_t *buf1, const duk_uint8_t *buf2, duk_size_t len1, duk_size_t len2) {
|
|
duk_size_t prefix_len;
|
|
duk_small_int_t rc;
|
|
|
|
prefix_len = (len1 <= len2 ? len1 : len2);
|
|
|
|
/* DUK_MEMCMP() is guaranteed to return zero (equal) for zero length
|
|
* inputs so no zero length check is needed.
|
|
*/
|
|
rc = DUK_MEMCMP((const void *) buf1,
|
|
(const void *) buf2,
|
|
(size_t) prefix_len);
|
|
|
|
if (rc < 0) {
|
|
return -1;
|
|
} else if (rc > 0) {
|
|
return 1;
|
|
}
|
|
|
|
/* prefix matches, lengths matter now */
|
|
if (len1 < len2) {
|
|
/* e.g. "x" < "xx" */
|
|
return -1;
|
|
} else if (len1 > len2) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
DUK_INTERNAL duk_small_int_t duk_js_string_compare(duk_hstring *h1, duk_hstring *h2) {
|
|
/*
|
|
* String comparison (E5 Section 11.8.5, step 4), which
|
|
* needs to compare codepoint by codepoint.
|
|
*
|
|
* However, UTF-8 allows us to use strcmp directly: the shared
|
|
* prefix will be encoded identically (UTF-8 has unique encoding)
|
|
* and the first differing character can be compared with a simple
|
|
* unsigned byte comparison (which strcmp does).
|
|
*
|
|
* This will not work properly for non-xutf-8 strings, but this
|
|
* is not an issue for compliance.
|
|
*/
|
|
|
|
DUK_ASSERT(h1 != NULL);
|
|
DUK_ASSERT(h2 != NULL);
|
|
|
|
return duk_js_data_compare((const duk_uint8_t *) DUK_HSTRING_GET_DATA(h1),
|
|
(const duk_uint8_t *) DUK_HSTRING_GET_DATA(h2),
|
|
(duk_size_t) DUK_HSTRING_GET_BYTELEN(h1),
|
|
(duk_size_t) DUK_HSTRING_GET_BYTELEN(h2));
|
|
}
|
|
|
|
#if 0 /* unused */
|
|
DUK_INTERNAL duk_small_int_t duk_js_buffer_compare(duk_heap *heap, duk_hbuffer *h1, duk_hbuffer *h2) {
|
|
/* Similar to String comparison. */
|
|
|
|
DUK_ASSERT(h1 != NULL);
|
|
DUK_ASSERT(h2 != NULL);
|
|
DUK_UNREF(heap);
|
|
|
|
return duk_js_data_compare((const duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(heap, h1),
|
|
(const duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(heap, h2),
|
|
(duk_size_t) DUK_HBUFFER_GET_SIZE(h1),
|
|
(duk_size_t) DUK_HBUFFER_GET_SIZE(h2));
|
|
}
|
|
#endif
|
|
|
|
DUK_INTERNAL duk_bool_t duk_js_compare_helper(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, duk_small_int_t flags) {
|
|
duk_context *ctx = (duk_context *) thr;
|
|
duk_double_t d1, d2;
|
|
duk_small_int_t c1, c2;
|
|
duk_small_int_t s1, s2;
|
|
duk_small_int_t rc;
|
|
duk_bool_t retval;
|
|
|
|
/* Fast path for fastints */
|
|
#if defined(DUK_USE_FASTINT)
|
|
if (DUK_TVAL_IS_FASTINT(tv_x) && DUK_TVAL_IS_FASTINT(tv_y)) {
|
|
duk_int64_t v1 = DUK_TVAL_GET_FASTINT(tv_x);
|
|
duk_int64_t v2 = DUK_TVAL_GET_FASTINT(tv_y);
|
|
if (v1 < v2) {
|
|
/* 'lt is true' */
|
|
retval = 1;
|
|
} else {
|
|
retval = 0;
|
|
}
|
|
if (flags & DUK_COMPARE_FLAG_NEGATE) {
|
|
retval ^= 1;
|
|
}
|
|
return retval;
|
|
}
|
|
#endif /* DUK_USE_FASTINT */
|
|
|
|
/* Fast path for numbers (one of which may be a fastint) */
|
|
#if 1 /* XXX: make fast paths optional for size minimization? */
|
|
if (DUK_TVAL_IS_NUMBER(tv_x) && DUK_TVAL_IS_NUMBER(tv_y)) {
|
|
d1 = DUK_TVAL_GET_NUMBER(tv_x);
|
|
d2 = DUK_TVAL_GET_NUMBER(tv_y);
|
|
c1 = DUK_FPCLASSIFY(d1);
|
|
c2 = DUK_FPCLASSIFY(d2);
|
|
|
|
if (c1 == DUK_FP_NORMAL && c2 == DUK_FP_NORMAL) {
|
|
/* XXX: this is a very narrow check, and doesn't cover
|
|
* zeroes, subnormals, infinities, which compare normally.
|
|
*/
|
|
|
|
if (d1 < d2) {
|
|
/* 'lt is true' */
|
|
retval = 1;
|
|
} else {
|
|
retval = 0;
|
|
}
|
|
if (flags & DUK_COMPARE_FLAG_NEGATE) {
|
|
retval ^= 1;
|
|
}
|
|
return retval;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Slow path */
|
|
|
|
duk_push_tval(ctx, tv_x);
|
|
duk_push_tval(ctx, tv_y);
|
|
|
|
if (flags & DUK_COMPARE_FLAG_EVAL_LEFT_FIRST) {
|
|
duk_to_primitive(ctx, -2, DUK_HINT_NUMBER);
|
|
duk_to_primitive(ctx, -1, DUK_HINT_NUMBER);
|
|
} else {
|
|
duk_to_primitive(ctx, -1, DUK_HINT_NUMBER);
|
|
duk_to_primitive(ctx, -2, DUK_HINT_NUMBER);
|
|
}
|
|
|
|
/* Note: reuse variables */
|
|
tv_x = DUK_GET_TVAL_NEGIDX(ctx, -2);
|
|
tv_y = DUK_GET_TVAL_NEGIDX(ctx, -1);
|
|
|
|
if (DUK_TVAL_IS_STRING(tv_x) && DUK_TVAL_IS_STRING(tv_y)) {
|
|
duk_hstring *h1 = DUK_TVAL_GET_STRING(tv_x);
|
|
duk_hstring *h2 = DUK_TVAL_GET_STRING(tv_y);
|
|
DUK_ASSERT(h1 != NULL);
|
|
DUK_ASSERT(h2 != NULL);
|
|
|
|
rc = duk_js_string_compare(h1, h2);
|
|
if (rc < 0) {
|
|
goto lt_true;
|
|
} else {
|
|
goto lt_false;
|
|
}
|
|
} else {
|
|
/* Ordering should not matter (E5 Section 11.8.5, step 3.a) but
|
|
* preserve it just in case.
|
|
*/
|
|
|
|
if (flags & DUK_COMPARE_FLAG_EVAL_LEFT_FIRST) {
|
|
d1 = duk_to_number(ctx, -2);
|
|
d2 = duk_to_number(ctx, -1);
|
|
} else {
|
|
d2 = duk_to_number(ctx, -1);
|
|
d1 = duk_to_number(ctx, -2);
|
|
}
|
|
|
|
c1 = (duk_small_int_t) DUK_FPCLASSIFY(d1);
|
|
s1 = (duk_small_int_t) DUK_SIGNBIT(d1);
|
|
c2 = (duk_small_int_t) DUK_FPCLASSIFY(d2);
|
|
s2 = (duk_small_int_t) DUK_SIGNBIT(d2);
|
|
|
|
if (c1 == DUK_FP_NAN || c2 == DUK_FP_NAN) {
|
|
goto lt_undefined;
|
|
}
|
|
|
|
if (c1 == DUK_FP_ZERO && c2 == DUK_FP_ZERO) {
|
|
/* For all combinations: +0 < +0, +0 < -0, -0 < +0, -0 < -0,
|
|
* steps e, f, and g.
|
|
*/
|
|
goto lt_false;
|
|
}
|
|
|
|
if (d1 == d2) {
|
|
goto lt_false;
|
|
}
|
|
|
|
if (c1 == DUK_FP_INFINITE && s1 == 0) {
|
|
/* x == +Infinity */
|
|
goto lt_false;
|
|
}
|
|
|
|
if (c2 == DUK_FP_INFINITE && s2 == 0) {
|
|
/* y == +Infinity */
|
|
goto lt_true;
|
|
}
|
|
|
|
if (c2 == DUK_FP_INFINITE && s2 != 0) {
|
|
/* y == -Infinity */
|
|
goto lt_false;
|
|
}
|
|
|
|
if (c1 == DUK_FP_INFINITE && s1 != 0) {
|
|
/* x == -Infinity */
|
|
goto lt_true;
|
|
}
|
|
|
|
if (d1 < d2) {
|
|
goto lt_true;
|
|
}
|
|
|
|
goto lt_false;
|
|
}
|
|
|
|
lt_undefined:
|
|
/* Note: undefined from Section 11.8.5 always results in false
|
|
* return (see e.g. Section 11.8.3) - hence special treatment here.
|
|
*/
|
|
retval = 0;
|
|
goto cleanup;
|
|
|
|
lt_true:
|
|
if (flags & DUK_COMPARE_FLAG_NEGATE) {
|
|
retval = 0;
|
|
goto cleanup;
|
|
} else {
|
|
retval = 1;
|
|
goto cleanup;
|
|
}
|
|
/* never here */
|
|
|
|
lt_false:
|
|
if (flags & DUK_COMPARE_FLAG_NEGATE) {
|
|
retval = 1;
|
|
goto cleanup;
|
|
} else {
|
|
retval = 0;
|
|
goto cleanup;
|
|
}
|
|
/* never here */
|
|
|
|
cleanup:
|
|
duk_pop_2(ctx);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* instanceof
|
|
*/
|
|
|
|
/*
|
|
* E5 Section 11.8.6 describes the main algorithm, which uses
|
|
* [[HasInstance]]. [[HasInstance]] is defined for only
|
|
* function objects:
|
|
*
|
|
* - Normal functions:
|
|
* E5 Section 15.3.5.3
|
|
* - Functions established with Function.prototype.bind():
|
|
* E5 Section 15.3.4.5.3
|
|
*
|
|
* For other objects, a TypeError is thrown.
|
|
*
|
|
* Limited Proxy support: don't support 'getPrototypeOf' trap but
|
|
* continue lookup in Proxy target if the value is a Proxy.
|
|
*/
|
|
|
|
DUK_INTERNAL duk_bool_t duk_js_instanceof(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y) {
|
|
duk_context *ctx = (duk_context *) thr;
|
|
duk_hobject *func;
|
|
duk_hobject *val;
|
|
duk_hobject *proto;
|
|
duk_tval *tv;
|
|
duk_uint_t sanity;
|
|
duk_bool_t skip_first;
|
|
|
|
/*
|
|
* Get the values onto the stack first. It would be possible to cover
|
|
* some normal cases without resorting to the value stack.
|
|
*
|
|
* The right hand side could be a light function (as they generally
|
|
* behave like objects). Light functions never have a 'prototype'
|
|
* property so E5.1 Section 15.3.5.3 step 3 always throws a TypeError.
|
|
* Using duk_require_hobject() is thus correct (except for error msg).
|
|
*/
|
|
|
|
duk_push_tval(ctx, tv_x);
|
|
duk_push_tval(ctx, tv_y);
|
|
func = duk_require_hobject(ctx, -1);
|
|
|
|
/*
|
|
* For bound objects, [[HasInstance]] just calls the target function
|
|
* [[HasInstance]]. If that is again a bound object, repeat until
|
|
* we find a non-bound Function object.
|
|
*/
|
|
|
|
/* XXX: this bound function resolution also happens elsewhere,
|
|
* move into a shared helper.
|
|
*/
|
|
|
|
sanity = DUK_HOBJECT_BOUND_CHAIN_SANITY;
|
|
do {
|
|
/* check func supports [[HasInstance]] (this is checked for every function
|
|
* in the bound chain, including the final one)
|
|
*/
|
|
|
|
if (!DUK_HOBJECT_IS_CALLABLE(func)) {
|
|
/*
|
|
* Note: of native Ecmascript objects, only Function instances
|
|
* have a [[HasInstance]] internal property. Custom objects might
|
|
* also have it, but not in current implementation.
|
|
*
|
|
* XXX: add a separate flag, DUK_HOBJECT_FLAG_ALLOW_INSTANCEOF?
|
|
*/
|
|
DUK_ERROR_TYPE(thr, "invalid instanceof rval");
|
|
}
|
|
|
|
if (!DUK_HOBJECT_HAS_BOUNDFUNC(func)) {
|
|
break;
|
|
}
|
|
|
|
/* [ ... lval rval ] */
|
|
|
|
duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_TARGET); /* -> [ ... lval rval new_rval ] */
|
|
duk_replace(ctx, -1); /* -> [ ... lval new_rval ] */
|
|
func = duk_require_hobject(ctx, -1);
|
|
|
|
/* func support for [[HasInstance]] checked in the beginning of the loop */
|
|
} while (--sanity > 0);
|
|
|
|
if (sanity == 0) {
|
|
DUK_ERROR_RANGE(thr, DUK_STR_BOUND_CHAIN_LIMIT);
|
|
}
|
|
|
|
/*
|
|
* 'func' is now a non-bound object which supports [[HasInstance]]
|
|
* (which here just means DUK_HOBJECT_FLAG_CALLABLE). Move on
|
|
* to execute E5 Section 15.3.5.3.
|
|
*/
|
|
|
|
DUK_ASSERT(!DUK_HOBJECT_HAS_BOUNDFUNC(func));
|
|
DUK_ASSERT(DUK_HOBJECT_IS_CALLABLE(func));
|
|
|
|
/* [ ... lval rval(func) ] */
|
|
|
|
/* For lightfuncs, buffers, and pointers start the comparison directly
|
|
* from the virtual prototype object.
|
|
*/
|
|
skip_first = 0;
|
|
tv = DUK_GET_TVAL_NEGIDX(ctx, -2);
|
|
switch (DUK_TVAL_GET_TAG(tv)) {
|
|
case DUK_TAG_LIGHTFUNC:
|
|
val = thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE];
|
|
DUK_ASSERT(val != NULL);
|
|
break;
|
|
case DUK_TAG_BUFFER:
|
|
val = thr->builtins[DUK_BIDX_ARRAYBUFFER_PROTOTYPE];
|
|
DUK_ASSERT(val != NULL);
|
|
break;
|
|
case DUK_TAG_POINTER:
|
|
val = thr->builtins[DUK_BIDX_POINTER_PROTOTYPE];
|
|
DUK_ASSERT(val != NULL);
|
|
break;
|
|
case DUK_TAG_OBJECT:
|
|
skip_first = 1; /* Ignore object itself on first round. */
|
|
val = DUK_TVAL_GET_OBJECT(tv);
|
|
DUK_ASSERT(val != NULL);
|
|
break;
|
|
default:
|
|
goto pop_and_false;
|
|
}
|
|
DUK_ASSERT(val != NULL); /* Loop doesn't actually rely on this. */
|
|
|
|
duk_get_prop_stridx(ctx, -1, DUK_STRIDX_PROTOTYPE); /* -> [ ... lval rval rval.prototype ] */
|
|
proto = duk_require_hobject(ctx, -1);
|
|
duk_pop(ctx); /* -> [ ... lval rval ] */
|
|
|
|
sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY;
|
|
do {
|
|
/*
|
|
* Note: prototype chain is followed BEFORE first comparison. This
|
|
* means that the instanceof lval is never itself compared to the
|
|
* rval.prototype property. This is apparently intentional, see E5
|
|
* Section 15.3.5.3, step 4.a.
|
|
*
|
|
* Also note:
|
|
*
|
|
* js> (function() {}) instanceof Function
|
|
* true
|
|
* js> Function instanceof Function
|
|
* true
|
|
*
|
|
* For the latter, h_proto will be Function.prototype, which is the
|
|
* built-in Function prototype. Because Function.[[Prototype]] is
|
|
* also the built-in Function prototype, the result is true.
|
|
*/
|
|
|
|
if (!val) {
|
|
goto pop_and_false;
|
|
}
|
|
|
|
DUK_ASSERT(val != NULL);
|
|
#if defined(DUK_USE_ES6_PROXY)
|
|
val = duk_hobject_resolve_proxy_target(thr, val);
|
|
#endif
|
|
|
|
if (skip_first) {
|
|
skip_first = 0;
|
|
} else if (val == proto) {
|
|
goto pop_and_true;
|
|
}
|
|
|
|
DUK_ASSERT(val != NULL);
|
|
val = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, val);
|
|
} while (--sanity > 0);
|
|
|
|
if (sanity == 0) {
|
|
DUK_ERROR_RANGE(thr, DUK_STR_PROTOTYPE_CHAIN_LIMIT);
|
|
}
|
|
DUK_UNREACHABLE();
|
|
|
|
pop_and_false:
|
|
duk_pop_2(ctx);
|
|
return 0;
|
|
|
|
pop_and_true:
|
|
duk_pop_2(ctx);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* in
|
|
*/
|
|
|
|
/*
|
|
* E5 Sections 11.8.7, 8.12.6.
|
|
*
|
|
* Basically just a property existence check using [[HasProperty]].
|
|
*/
|
|
|
|
DUK_INTERNAL duk_bool_t duk_js_in(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y) {
|
|
duk_context *ctx = (duk_context *) thr;
|
|
duk_bool_t retval;
|
|
|
|
/*
|
|
* Get the values onto the stack first. It would be possible to cover
|
|
* some normal cases without resorting to the value stack (e.g. if
|
|
* lval is already a string).
|
|
*/
|
|
|
|
/* XXX: The ES5/5.1/6 specifications require that the key in 'key in obj'
|
|
* must be string coerced before the internal HasProperty() algorithm is
|
|
* invoked. A fast path skipping coercion could be safely implemented for
|
|
* numbers (as number-to-string coercion has no side effects). For ES6
|
|
* proxy behavior, the trap 'key' argument must be in a string coerced
|
|
* form (which is a shame).
|
|
*/
|
|
|
|
/* TypeError if rval is not an object or object like (e.g. lightfunc
|
|
* or plain buffer).
|
|
*/
|
|
duk_push_tval(ctx, tv_x);
|
|
duk_push_tval(ctx, tv_y);
|
|
duk_require_type_mask(ctx, -1, DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER);
|
|
duk_to_string(ctx, -2); /* coerce lval with ToString() */
|
|
|
|
retval = duk_hobject_hasprop(thr,
|
|
DUK_GET_TVAL_NEGIDX(ctx, -1),
|
|
DUK_GET_TVAL_NEGIDX(ctx, -2));
|
|
|
|
duk_pop_2(ctx);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* typeof
|
|
*
|
|
* E5 Section 11.4.3.
|
|
*
|
|
* Very straightforward. The only question is what to return for our
|
|
* non-standard tag / object types.
|
|
*
|
|
* There is an unfortunate string constant define naming problem with
|
|
* typeof return values for e.g. "Object" and "object"; careful with
|
|
* the built-in string defines. The LC_XXX defines are used for the
|
|
* lowercase variants now.
|
|
*/
|
|
|
|
DUK_INTERNAL duk_small_uint_t duk_js_typeof_stridx(duk_tval *tv_x) {
|
|
duk_small_uint_t stridx = 0;
|
|
|
|
switch (DUK_TVAL_GET_TAG(tv_x)) {
|
|
case DUK_TAG_UNDEFINED: {
|
|
stridx = DUK_STRIDX_LC_UNDEFINED;
|
|
break;
|
|
}
|
|
case DUK_TAG_NULL: {
|
|
/* Note: not a typo, "object" is returned for a null value. */
|
|
stridx = DUK_STRIDX_LC_OBJECT;
|
|
break;
|
|
}
|
|
case DUK_TAG_BOOLEAN: {
|
|
stridx = DUK_STRIDX_LC_BOOLEAN;
|
|
break;
|
|
}
|
|
case DUK_TAG_POINTER: {
|
|
/* Implementation specific. */
|
|
stridx = DUK_STRIDX_LC_POINTER;
|
|
break;
|
|
}
|
|
case DUK_TAG_STRING: {
|
|
stridx = DUK_STRIDX_LC_STRING;
|
|
break;
|
|
}
|
|
case DUK_TAG_OBJECT: {
|
|
duk_hobject *obj = DUK_TVAL_GET_OBJECT(tv_x);
|
|
DUK_ASSERT(obj != NULL);
|
|
if (DUK_HOBJECT_IS_CALLABLE(obj)) {
|
|
stridx = DUK_STRIDX_LC_FUNCTION;
|
|
} else {
|
|
stridx = DUK_STRIDX_LC_OBJECT;
|
|
}
|
|
break;
|
|
}
|
|
case DUK_TAG_BUFFER: {
|
|
/* Implementation specific. In Duktape 1.x this would be
|
|
* 'buffer', in Duktape 2.x changed to 'object' because plain
|
|
* buffers now mimic ArrayBuffer objects.
|
|
*/
|
|
stridx = DUK_STRIDX_LC_OBJECT;
|
|
break;
|
|
}
|
|
case DUK_TAG_LIGHTFUNC: {
|
|
stridx = DUK_STRIDX_LC_FUNCTION;
|
|
break;
|
|
}
|
|
#if defined(DUK_USE_FASTINT)
|
|
case DUK_TAG_FASTINT:
|
|
#endif
|
|
default: {
|
|
/* number */
|
|
DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv_x));
|
|
DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_x));
|
|
stridx = DUK_STRIDX_LC_NUMBER;
|
|
break;
|
|
}
|
|
}
|
|
|
|
DUK_ASSERT(stridx >= 0 && stridx < DUK_HEAP_NUM_STRINGS);
|
|
return stridx;
|
|
}
|
|
|
|
/*
|
|
* Array index and length
|
|
*
|
|
* Array index: E5 Section 15.4
|
|
* Array length: E5 Section 15.4.5.1 steps 3.c - 3.d (array length write)
|
|
*
|
|
* The DUK_HSTRING_GET_ARRIDX_SLOW() and DUK_HSTRING_GET_ARRIDX_FAST() macros
|
|
* call duk_js_to_arrayindex_string_helper().
|
|
*/
|
|
|
|
DUK_INTERNAL duk_small_int_t duk_js_to_arrayindex_raw_string(const duk_uint8_t *str, duk_uint32_t blen, duk_uarridx_t *out_idx) {
|
|
duk_uarridx_t res, new_res;
|
|
|
|
if (blen == 0 || blen > 10) {
|
|
goto parse_fail;
|
|
}
|
|
if (str[0] == (duk_uint8_t) '0' && blen > 1) {
|
|
goto parse_fail;
|
|
}
|
|
|
|
/* Accept 32-bit decimal integers, no leading zeroes, signs, etc.
|
|
* Leading zeroes are not accepted (zero index "0" is an exception
|
|
* handled above).
|
|
*/
|
|
|
|
res = 0;
|
|
while (blen-- > 0) {
|
|
duk_uint8_t c = *str++;
|
|
if (c >= (duk_uint8_t) '0' && c <= (duk_uint8_t) '9') {
|
|
new_res = res * 10 + (duk_uint32_t) (c - (duk_uint8_t) '0');
|
|
if (new_res < res) {
|
|
/* overflow, more than 32 bits -> not an array index */
|
|
goto parse_fail;
|
|
}
|
|
res = new_res;
|
|
} else {
|
|
goto parse_fail;
|
|
}
|
|
}
|
|
|
|
*out_idx = res;
|
|
return 1;
|
|
|
|
parse_fail:
|
|
*out_idx = DUK_HSTRING_NO_ARRAY_INDEX;
|
|
return 0;
|
|
}
|
|
|
|
/* Called by duk_hstring.h macros */
|
|
DUK_INTERNAL duk_uarridx_t duk_js_to_arrayindex_string_helper(duk_hstring *h) {
|
|
duk_uarridx_t res;
|
|
duk_small_int_t rc;
|
|
|
|
if (!DUK_HSTRING_HAS_ARRIDX(h)) {
|
|
return DUK_HSTRING_NO_ARRAY_INDEX;
|
|
}
|
|
|
|
rc = duk_js_to_arrayindex_raw_string(DUK_HSTRING_GET_DATA(h),
|
|
DUK_HSTRING_GET_BYTELEN(h),
|
|
&res);
|
|
DUK_UNREF(rc);
|
|
DUK_ASSERT(rc != 0);
|
|
return res;
|
|
}
|
|
|