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 lightfunc which should behave * like a Function instance). */ 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_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; }
DUK_INTERNAL void duk_resolve_nonbound_function(duk_context *ctx) { duk_uint_t sanity; duk_tval *tv; sanity = DUK_HOBJECT_BOUND_CHAIN_SANITY; do { tv = DUK_GET_TVAL_NEGIDX(ctx, -1); if (DUK_TVAL_IS_LIGHTFUNC(tv)) { /* Lightweight function: never bound, so terminate. */ break; } else if (DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *func; func = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(func != NULL); if (!DUK_HOBJECT_IS_CALLABLE(func) || !DUK_HOBJECT_HAS_BOUNDFUNC(func)) { break; } duk_get_prop_stridx_short(ctx, -1, DUK_STRIDX_INT_TARGET); duk_replace(ctx, -2); } else { break; } } while (--sanity > 0); }
/* Resolve a bound function on value stack top to a non-bound target * (leave other values as is). */ DUK_INTERNAL void duk_resolve_nonbound_function(duk_context *ctx) { duk_tval *tv; tv = DUK_GET_TVAL_NEGIDX(ctx, -1); if (DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *h; h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); if (DUK_HOBJECT_HAS_BOUNDFUNC(h)) { duk_push_tval(ctx, &((duk_hboundfunc *) h)->target); duk_replace(ctx, -2); #if 0 DUK_TVAL_SET_TVAL(tv, &((duk_hboundfunc *) h)->target); DUK_TVAL_INCREF(thr, tv); DUK_HOBJECT_DECREF_NORZ(thr, h); #endif /* Rely on Function.prototype.bind() on never creating a bound * function whose target is not proper. This is now safe * because the target is not even an internal property but a * struct member. */ DUK_ASSERT(duk_is_lightfunc(ctx, -1) || duk_is_callable(ctx, -1)); } } /* Lightfuncs cannot be bound but are always callable and * constructable. */ }
DUK_EXTERNAL void duk_new(duk_context *ctx, duk_idx_t nargs) { /* * There are two [[Construct]] operations in the specification: * * - E5 Section 13.2.2: for Function objects * - E5 Section 15.3.4.5.2: for "bound" Function objects * * The chain of bound functions is resolved in Section 15.3.4.5.2, * with arguments "piling up" until the [[Construct]] internal * method is called on the final, actual Function object. Note * that the "prototype" property is looked up *only* from the * final object, *before* calling the constructor. * * Currently we follow the bound function chain here to get the * "prototype" property value from the final, non-bound function. * However, we let duk_handle_call() handle the argument "piling" * when the constructor is called. The bound function chain is * thus now processed twice. * * When constructing new Array instances, an unnecessary object is * created and discarded now: the standard [[Construct]] creates an * object, and calls the Array constructor. The Array constructor * returns an Array instance, which is used as the result value for * the "new" operation; the object created before the Array constructor * call is discarded. * * This would be easy to fix, e.g. by knowing that the Array constructor * will always create a replacement object and skip creating the fallback * object in that case. * * Note: functions called via "new" need to know they are called as a * constructor. For instance, built-in constructors behave differently * depending on how they are called. */ /* XXX: merge this with duk_js_call.c, as this function implements * core semantics (or perhaps merge the two files altogether). */ duk_hthread *thr = (duk_hthread *) ctx; duk_hobject *proto; duk_hobject *cons; duk_hobject *fallback; duk_idx_t idx_cons; duk_small_uint_t call_flags; DUK_ASSERT_CTX_VALID(ctx); /* [... constructor arg1 ... argN] */ idx_cons = duk_require_normalize_index(ctx, -nargs - 1); DUK_DDD(DUK_DDDPRINT("top=%ld, nargs=%ld, idx_cons=%ld", (long) duk_get_top(ctx), (long) nargs, (long) idx_cons)); /* XXX: code duplication */ /* * Figure out the final, non-bound constructor, to get "prototype" * property. */ duk_dup(ctx, idx_cons); for (;;) { duk_tval *tv; tv = DUK_GET_TVAL_NEGIDX(ctx, -1); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_OBJECT(tv)) { cons = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(cons != NULL); if (!DUK_HOBJECT_IS_CALLABLE(cons) || !DUK_HOBJECT_HAS_CONSTRUCTABLE(cons)) { /* Checking callability of the immediate target * is important, same for constructability. * Checking it for functions down the bound * function chain is not strictly necessary * because .bind() should normally reject them. * But it's good to check anyway because it's * technically possible to edit the bound function * chain via internal keys. */ goto not_constructable; } if (!DUK_HOBJECT_HAS_BOUNDFUNC(cons)) { break; } } else if (DUK_TVAL_IS_LIGHTFUNC(tv)) { /* Lightfuncs cannot be bound. */ break; } else { /* Anything else is not constructable. */ goto not_constructable; } duk_get_prop_stridx_short(ctx, -1, DUK_STRIDX_INT_TARGET); /* -> [... cons target] */ duk_remove_m2(ctx); /* -> [... target] */ } DUK_ASSERT(duk_is_callable(ctx, -1)); DUK_ASSERT(duk_is_lightfunc(ctx, -1) || (duk_get_hobject(ctx, -1) != NULL && !DUK_HOBJECT_HAS_BOUNDFUNC(duk_get_hobject(ctx, -1)))); /* [... constructor arg1 ... argN final_cons] */ /* * Create "fallback" object to be used as the object instance, * unless the constructor returns a replacement value. * Its internal prototype needs to be set based on "prototype" * property of the constructor. */ duk_push_object(ctx); /* class Object, extensible */ /* [... constructor arg1 ... argN final_cons fallback] */ duk_get_prop_stridx_short(ctx, -2, DUK_STRIDX_PROTOTYPE); proto = duk_get_hobject(ctx, -1); if (!proto) { DUK_DDD(DUK_DDDPRINT("constructor has no 'prototype' property, or value not an object " "-> leave standard Object prototype as fallback prototype")); } else { DUK_DDD(DUK_DDDPRINT("constructor has 'prototype' property with object value " "-> set fallback prototype to that value: %!iO", (duk_heaphdr *) proto)); fallback = duk_get_hobject(ctx, -2); DUK_ASSERT(fallback != NULL); DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, fallback, proto); } duk_pop(ctx); /* [... constructor arg1 ... argN final_cons fallback] */ /* * Manipulate callstack for the call. */ duk_dup_top(ctx); duk_insert(ctx, idx_cons + 1); /* use fallback as 'this' value */ duk_insert(ctx, idx_cons); /* also stash it before constructor, * in case we need it (as the fallback value) */ duk_pop(ctx); /* pop final_cons */ /* [... fallback constructor fallback(this) arg1 ... argN]; * Note: idx_cons points to first 'fallback', not 'constructor'. */ DUK_DDD(DUK_DDDPRINT("before call, idx_cons+1 (constructor) -> %!T, idx_cons+2 (fallback/this) -> %!T, " "nargs=%ld, top=%ld", (duk_tval *) duk_get_tval(ctx, idx_cons + 1), (duk_tval *) duk_get_tval(ctx, idx_cons + 2), (long) nargs, (long) duk_get_top(ctx))); /* * Call the constructor function (called in "constructor mode"). */ call_flags = DUK_CALL_FLAG_CONSTRUCTOR_CALL; /* not protected, respect reclimit, is a constructor call */ duk_handle_call_unprotected(thr, /* thread */ nargs, /* num_stack_args */ call_flags); /* call_flags */ /* [... fallback retval] */ DUK_DDD(DUK_DDDPRINT("constructor call finished, fallback=%!iT, retval=%!iT", (duk_tval *) duk_get_tval(ctx, -2), (duk_tval *) duk_get_tval(ctx, -1))); /* * Determine whether to use the constructor return value as the created * object instance or not. */ if (duk_check_type_mask(ctx, -1, DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_BUFFER | DUK_TYPE_MASK_LIGHTFUNC)) { duk_remove_m2(ctx); } else { duk_pop(ctx); } /* * Augment created errors upon creation (not when they are thrown or * rethrown). __FILE__ and __LINE__ are not desirable here; the call * stack reflects the caller which is correct. */ #if defined(DUK_USE_AUGMENT_ERROR_CREATE) duk_hthread_sync_currpc(thr); duk_err_augment_error_create(thr, thr, NULL, 0, 1 /*noblame_fileline*/); #endif /* [... retval] */ return; not_constructable: #if defined(DUK_USE_VERBOSE_ERRORS) #if defined(DUK_USE_PARANOID_ERRORS) DUK_ERROR_FMT1(thr, DUK_ERR_TYPE_ERROR, "%s not constructable", duk_get_type_name(ctx, -1)); #else DUK_ERROR_FMT1(thr, DUK_ERR_TYPE_ERROR, "%s not constructable", duk_push_string_readable(ctx, -1)); #endif #else DUK_ERROR_TYPE(thr, "not constructable"); #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; }
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: { if ((flags & (DUK_EQUALS_FLAG_STRICT | DUK_EQUALS_FLAG_SAMEVALUE)) != 0) { /* heap pointer comparison suffices */ return DUK_TVAL_GET_HEAPHDR(tv_x) == DUK_TVAL_GET_HEAPHDR(tv_y); } else { /* non-strict equality for buffers compares contents */ duk_hbuffer *h_x = DUK_TVAL_GET_BUFFER(tv_x); duk_hbuffer *h_y = DUK_TVAL_GET_BUFFER(tv_y); duk_size_t len_x = DUK_HBUFFER_GET_SIZE(h_x); duk_size_t len_y = DUK_HBUFFER_GET_SIZE(h_y); void *buf_x; void *buf_y; if (len_x != len_y) { return 0; } buf_x = (void *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_x); buf_y = (void *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_y); /* if len_x == len_y == 0, buf_x and/or buf_y may * be NULL, but that's OK. */ DUK_ASSERT(len_x == len_y); DUK_ASSERT(len_x == 0 || buf_x != NULL); DUK_ASSERT(len_y == 0 || buf_y != NULL); return (DUK_MEMCMP((const void *) buf_x, (const void *) buf_y, (size_t) len_x) == 0) ? 1 : 0; } } 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-or-buffer -> coerce string to number (e.g. "'1.5' == 1.5" -> true). */ if (DUK_TVAL_IS_NUMBER(tv_x) && (DUK_TVAL_IS_STRING(tv_y) || DUK_TVAL_IS_BUFFER(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_BUFFER(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_string(ctx, -1); /* buffer values are coerced first to string here */ duk_to_number(ctx, -1); d1 = duk_require_number(ctx, -1); duk_pop(ctx); return duk__js_equals_number(d1, d2); } /* Buffer/string -> compare contents. */ if (DUK_TVAL_IS_BUFFER(tv_x) && DUK_TVAL_IS_STRING(tv_y)) { tv_tmp = tv_x; tv_x = tv_y; tv_y = tv_tmp; } if (DUK_TVAL_IS_STRING(tv_x) && DUK_TVAL_IS_BUFFER(tv_y)) { duk_hstring *h_x = DUK_TVAL_GET_STRING(tv_x); duk_hbuffer *h_y = DUK_TVAL_GET_BUFFER(tv_y); duk_size_t len_x = DUK_HSTRING_GET_BYTELEN(h_x); duk_size_t len_y = DUK_HBUFFER_GET_SIZE(h_y); const void *buf_x; const void *buf_y; if (len_x != len_y) { return 0; } buf_x = (const void *) DUK_HSTRING_GET_DATA(h_x); buf_y = (const void *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_y); /* if len_x == len_y == 0, buf_x and/or buf_y may * be NULL, but that's OK. */ DUK_ASSERT(len_x == len_y); DUK_ASSERT(len_x == 0 || buf_x != NULL); DUK_ASSERT(len_y == 0 || buf_y != NULL); return (DUK_MEMCMP((const void *) buf_x, (const void *) buf_y, (size_t) len_x) == 0) ? 1 : 0; } /* 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-buffer/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_BUFFER(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_TVAL_IS_BUFFER(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; }