diff --git a/RELEASES.rst b/RELEASES.rst index 6f1def6a..2dc5ffb0 100644 --- a/RELEASES.rst +++ b/RELEASES.rst @@ -3190,6 +3190,10 @@ Planned 2.3.0 (XXXX-XX-XX) ------------------ +* Add support for Symbol.hasInstance (@@hasInstance) check for 'instanceof' + operator and duk_instanceof() API call; also add Symbol.hasInstance and + Function.prototype[@@hasInstance] (GH-1821) + * Add duk_random() to allow C code access to the same random number source as Ecmascript code (GH-1815) @@ -3250,6 +3254,8 @@ Planned * Add a CBOR encoder/decoder as an extra (GH-1781, GH-1800, GH-1801) +* Fix performance.now() property attributes to 'wec' (earlier 'wc') (GH-1821) + * Fix debugger StepOver behavior when a tailcall happens in a nested function (not the function where stepping started from) (GH-1786, GH-1787) diff --git a/src-input/builtins.yaml b/src-input/builtins.yaml index 1df317dc..b27a7f92 100644 --- a/src-input/builtins.yaml +++ b/src-input/builtins.yaml @@ -871,12 +871,17 @@ objects: present_if: DUK_USE_FUNCTION_BUILTIN # ES2015 - #- key: # @@hasInstance - # type: symbol - # variant: wellknown - # string: "Symbol.hasInstance" - # value: XXX - # es6: true + - key: # @@hasInstance + type: symbol + variant: wellknown + string: "Symbol.hasInstance" + value: + type: function + native: duk_bi_function_prototype_hasinstance + length: 1 + attributes: "" + es6: true + present_if: DUK_USE_SYMBOL_BUILTIN # Duktape specific %NativeFunctionPrototype% which provides some getters. # @@ -3314,13 +3319,14 @@ objects: attributes: "wc" es6: true - #- key: "hasInstance" - # value: - # type: symbol - # variant: wellknown - # string: "Symbol.hasInstance" - # attributes: "" - # es6: true + - key: "hasInstance" + value: + type: symbol + variant: wellknown + string: "Symbol.hasInstance" + attributes: "" + es6: true + present_if: DUK_USE_SYMBOL_BUILTIN #- key: "isConcatSpreadable" # value: # type: symbol @@ -3335,6 +3341,7 @@ objects: string: "Symbol.iterator" attributes: "" es6: true + present_if: DUK_USE_SYMBOL_BUILTIN #- key: "match" # value: # type: symbol @@ -5469,16 +5476,13 @@ objects: # Firefox and Chrome: data property with 'wec' attributes, # inherited from PerformancePrototype. Use own data property # for now. - # XXX: we'd like to use 'wec' but current built-in init data doesn't - # support it; use 'wc' for consistency so ROM built-ins have the same - # behavior. - key: "now" value: type: function native: duk_bi_performance_now length: 0 nargs: 0 - attributes: "wc" + attributes: "wec" performance_api: true # Missing until semantics decided. #- key: "timeOrigin" diff --git a/src-input/duk_api_internal.h b/src-input/duk_api_internal.h index 0e8ddc23..004255bd 100644 --- a/src-input/duk_api_internal.h +++ b/src-input/duk_api_internal.h @@ -142,6 +142,8 @@ DUK_INTERNAL_DECL duk_hobject *duk_to_hobject(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_double_t duk_to_number_m1(duk_hthread *thr); DUK_INTERNAL_DECL duk_double_t duk_to_number_m2(duk_hthread *thr); +DUK_INTERNAL_DECL duk_bool_t duk_to_boolean_top_pop(duk_hthread *thr); + #if defined(DUK_USE_DEBUGGER_SUPPORT) /* only needed by debugger for now */ DUK_INTERNAL_DECL duk_hstring *duk_safe_to_hstring(duk_hthread *thr, duk_idx_t idx); #endif @@ -283,6 +285,8 @@ DUK_INTERNAL_DECL void duk_xdef_prop_stridx_builtin(duk_hthread *thr, duk_idx_t DUK_INTERNAL_DECL void duk_xdef_prop_stridx_thrower(duk_hthread *thr, duk_idx_t obj_idx, duk_small_uint_t stridx); /* [] -> [] */ +DUK_INTERNAL_DECL duk_bool_t duk_get_method_stridx(duk_hthread *thr, duk_idx_t idx, duk_small_uint_t stridx); + DUK_INTERNAL_DECL void duk_pack(duk_hthread *thr, duk_idx_t count); DUK_INTERNAL_DECL duk_idx_t duk_unpack_array_like(duk_hthread *thr, duk_idx_t idx); #if 0 diff --git a/src-input/duk_api_object.c b/src-input/duk_api_object.c index 2388ba79..9a9019e6 100644 --- a/src-input/duk_api_object.c +++ b/src-input/duk_api_object.c @@ -105,10 +105,7 @@ DUK_INTERNAL duk_bool_t duk_get_prop_stridx_boolean(duk_hthread *thr, duk_idx_t if (out_has_prop) { *out_has_prop = rc; } - rc = duk_to_boolean(thr, -1); - DUK_ASSERT(rc == 0 || rc == 1); - duk_pop(thr); - return rc; + return duk_to_boolean_top_pop(thr); } DUK_LOCAL duk_bool_t duk__put_prop_shared(duk_hthread *thr, duk_idx_t obj_idx, duk_idx_t idx_key) { @@ -840,6 +837,23 @@ DUK_EXTERNAL duk_bool_t duk_put_global_heapptr(duk_hthread *thr, void *ptr) { return ret; } +/* + * ES2015 GetMethod() + */ + +DUK_INTERNAL duk_bool_t duk_get_method_stridx(duk_hthread *thr, duk_idx_t idx, duk_small_uint_t stridx) { + (void) duk_get_prop_stridx(thr, idx, stridx); + if (duk_is_null_or_undefined(thr, -1)) { + duk_pop_nodecref_unsafe(thr); + return 0; + } + if (!duk_is_callable(thr, -1)) { + DUK_ERROR_TYPE(thr, DUK_STR_NOT_CALLABLE); + DUK_WO_NORETURN(return 0;); + } + return 1; +} + /* * Object prototype */ diff --git a/src-input/duk_api_stack.c b/src-input/duk_api_stack.c index 0abb115c..5aa701e2 100644 --- a/src-input/duk_api_stack.c +++ b/src-input/duk_api_stack.c @@ -2786,6 +2786,22 @@ DUK_EXTERNAL duk_bool_t duk_to_boolean(duk_hthread *thr, duk_idx_t idx) { return val; } +DUK_INTERNAL duk_bool_t duk_to_boolean_top_pop(duk_hthread *thr) { + duk_tval *tv; + duk_bool_t val; + + DUK_ASSERT_API_ENTRY(thr); + + tv = duk_require_tval(thr, -1); + DUK_ASSERT(tv != NULL); + + val = duk_js_toboolean(tv); + DUK_ASSERT(val == 0 || val == 1); + + duk_pop_unsafe(thr); + return val; +} + DUK_EXTERNAL duk_double_t duk_to_number(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; duk_double_t d; diff --git a/src-input/duk_bi_function.c b/src-input/duk_bi_function.c index ccc6a649..8d4da949 100644 --- a/src-input/duk_bi_function.c +++ b/src-input/duk_bi_function.c @@ -440,3 +440,14 @@ DUK_INTERNAL duk_ret_t duk_bi_native_function_name(duk_hthread *thr) { fail_type: DUK_DCERROR_TYPE_INVALID_ARGS(thr); } + +#if defined(DUK_USE_SYMBOL_BUILTIN) +DUK_INTERNAL duk_ret_t duk_bi_function_prototype_hasinstance(duk_hthread *thr) { + /* This binding: RHS, stack index 0: LHS. */ + duk_bool_t ret; + + ret = duk_js_instanceof_ordinary(thr, DUK_GET_TVAL_POSIDX(thr, 0), DUK_GET_THIS_TVAL_PTR(thr)); + duk_push_boolean(thr, ret); + return 1; +} +#endif /* DUK_USE_SYMBOL_BUILTIN */ diff --git a/src-input/duk_bi_thread.c b/src-input/duk_bi_thread.c index 7d6b17cd..c9f520c9 100644 --- a/src-input/duk_bi_thread.c +++ b/src-input/duk_bi_thread.c @@ -66,8 +66,9 @@ DUK_INTERNAL duk_ret_t duk_bi_thread_resume(duk_hthread *ctx) { DUK_ASSERT(thr->heap->curr_thread == thr); thr_resume = duk_require_hthread(thr, 0); - is_error = (duk_small_uint_t) duk_to_boolean(thr, 2); - duk_set_top(thr, 2); + DUK_ASSERT(duk_get_top(thr) == 3); + is_error = (duk_small_uint_t) duk_to_boolean_top_pop(thr); + DUK_ASSERT(duk_get_top(thr) == 2); /* [ thread value ] */ @@ -215,8 +216,9 @@ DUK_INTERNAL duk_ret_t duk_bi_thread_yield(duk_hthread *thr) { DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING); DUK_ASSERT(thr->heap->curr_thread == thr); - is_error = (duk_small_uint_t) duk_to_boolean(thr, 1); - duk_set_top(thr, 1); + DUK_ASSERT(duk_get_top(thr) == 2); + is_error = (duk_small_uint_t) duk_to_boolean_top_pop(thr); + DUK_ASSERT(duk_get_top(thr) == 1); /* [ value ] */ diff --git a/src-input/duk_hobject_props.c b/src-input/duk_hobject_props.c index 2883623a..1da0ec93 100644 --- a/src-input/duk_hobject_props.c +++ b/src-input/duk_hobject_props.c @@ -2907,7 +2907,7 @@ DUK_INTERNAL duk_bool_t duk_hobject_hasprop(duk_hthread *thr, duk_tval *tv_obj, duk_push_hobject(thr, h_target); /* target */ duk_push_tval(thr, tv_key); /* P */ duk_call_method(thr, 2 /*nargs*/); - tmp_bool = duk_to_boolean(thr, -1); + tmp_bool = duk_to_boolean_top_pop(thr); if (!tmp_bool) { /* Target object must be checked for a conflicting * non-configurable property. @@ -2931,7 +2931,7 @@ DUK_INTERNAL duk_bool_t duk_hobject_hasprop(duk_hthread *thr, duk_tval *tv_obj, } } - duk_pop_2_unsafe(thr); /* [ key trap_result ] -> [] */ + duk_pop_unsafe(thr); /* [ key ] -> [] */ return tmp_bool; } @@ -3495,8 +3495,7 @@ DUK_INTERNAL duk_bool_t duk_hobject_putprop(duk_hthread *thr, duk_tval *tv_obj, duk_push_tval(thr, tv_val); /* V */ duk_push_tval(thr, tv_obj); /* Receiver: Proxy object */ duk_call_method(thr, 4 /*nargs*/); - tmp_bool = duk_to_boolean(thr, -1); - duk_pop_nodecref_unsafe(thr); + tmp_bool = duk_to_boolean_top_pop(thr); if (!tmp_bool) { goto fail_proxy_rejected; } @@ -4470,8 +4469,7 @@ DUK_INTERNAL duk_bool_t duk_hobject_delprop(duk_hthread *thr, duk_tval *tv_obj, duk_push_hobject(thr, h_target); /* target */ duk_dup_m4(thr); /* P */ duk_call_method(thr, 2 /*nargs*/); - tmp_bool = duk_to_boolean(thr, -1); - duk_pop_nodecref_unsafe(thr); + tmp_bool = duk_to_boolean_top_pop(thr); if (!tmp_bool) { goto fail_proxy_rejected; /* retval indicates delete failed */ } @@ -4976,7 +4974,7 @@ void duk_hobject_prepare_property_descriptor(duk_hthread *thr, if (duk_get_prop_stridx(thr, idx_in, DUK_STRIDX_WRITABLE)) { is_data_desc = 1; - if (duk_to_boolean(thr, -1)) { + if (duk_to_boolean_top_pop(thr)) { defprop_flags |= DUK_DEFPROP_HAVE_WRITABLE | DUK_DEFPROP_WRITABLE; } else { defprop_flags |= DUK_DEFPROP_HAVE_WRITABLE; @@ -5028,7 +5026,7 @@ void duk_hobject_prepare_property_descriptor(duk_hthread *thr, } if (duk_get_prop_stridx(thr, idx_in, DUK_STRIDX_ENUMERABLE)) { - if (duk_to_boolean(thr, -1)) { + if (duk_to_boolean_top_pop(thr)) { defprop_flags |= DUK_DEFPROP_HAVE_ENUMERABLE | DUK_DEFPROP_ENUMERABLE; } else { defprop_flags |= DUK_DEFPROP_HAVE_ENUMERABLE; @@ -5036,7 +5034,7 @@ void duk_hobject_prepare_property_descriptor(duk_hthread *thr, } if (duk_get_prop_stridx(thr, idx_in, DUK_STRIDX_CONFIGURABLE)) { - if (duk_to_boolean(thr, -1)) { + if (duk_to_boolean_top_pop(thr)) { defprop_flags |= DUK_DEFPROP_HAVE_CONFIGURABLE | DUK_DEFPROP_CONFIGURABLE; } else { defprop_flags |= DUK_DEFPROP_HAVE_CONFIGURABLE; diff --git a/src-input/duk_hthread_builtins.c b/src-input/duk_hthread_builtins.c index a2268e81..bc63744e 100644 --- a/src-input/duk_hthread_builtins.c +++ b/src-input/duk_hthread_builtins.c @@ -444,11 +444,9 @@ DUK_INTERNAL void duk_hthread_create_builtin_objects(duk_hthread *thr) { * signaled using a single flag bit in the bitstream. */ - if (duk_bd_decode_flag(bd)) { - defprop_flags = (duk_small_uint_t) duk_bd_decode(bd, DUK__PROP_FLAGS_BITS); - } else { - defprop_flags = DUK_PROPDESC_FLAGS_WC; - } + defprop_flags = (duk_small_uint_t) duk_bd_decode_flagged(bd, + DUK__PROP_FLAGS_BITS, + (duk_uint32_t) DUK_PROPDESC_FLAGS_WC); defprop_flags |= DUK_DEFPROP_FORCE | DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_HAVE_WRITABLE | @@ -553,6 +551,7 @@ DUK_INTERNAL void duk_hthread_create_builtin_objects(duk_hthread *thr) { #if defined(DUK_USE_LIGHTFUNC_BUILTINS) duk_small_int_t lightfunc_eligible; #endif + duk_small_uint_t defprop_flags; duk__push_stridx_or_string(thr, bd); h_key = duk_known_hstring(thr, -1); @@ -672,10 +671,19 @@ DUK_INTERNAL void duk_hthread_create_builtin_objects(duk_hthread *thr) { lightfunc_skip: #endif - /* XXX: So far all ES builtins are 'wc' but e.g. - * performance.now() should be 'wec'. - */ - duk_xdef_prop(thr, (duk_idx_t) i, DUK_PROPDESC_FLAGS_WC); + defprop_flags = (duk_small_uint_t) duk_bd_decode_flagged(bd, + DUK__PROP_FLAGS_BITS, + (duk_uint32_t) DUK_PROPDESC_FLAGS_WC); + defprop_flags |= DUK_DEFPROP_FORCE | + DUK_DEFPROP_HAVE_VALUE | + DUK_DEFPROP_HAVE_WRITABLE | + DUK_DEFPROP_HAVE_ENUMERABLE | + DUK_DEFPROP_HAVE_CONFIGURABLE; + DUK_ASSERT(DUK_PROPDESC_FLAG_WRITABLE == DUK_DEFPROP_WRITABLE); + DUK_ASSERT(DUK_PROPDESC_FLAG_ENUMERABLE == DUK_DEFPROP_ENUMERABLE); + DUK_ASSERT(DUK_PROPDESC_FLAG_CONFIGURABLE == DUK_DEFPROP_CONFIGURABLE); + + duk_def_prop(thr, (duk_idx_t) i, defprop_flags); /* [ (builtin objects) ] */ } diff --git a/src-input/duk_js.h b/src-input/duk_js.h index a455548f..e4c474d1 100644 --- a/src-input/duk_js.h +++ b/src-input/duk_js.h @@ -43,6 +43,9 @@ DUK_INTERNAL_DECL duk_small_int_t duk_js_buffer_compare(duk_heap *heap, duk_hbuf #endif DUK_INTERNAL_DECL duk_bool_t duk_js_compare_helper(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, duk_small_uint_t flags); DUK_INTERNAL_DECL duk_bool_t duk_js_instanceof(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y); +#if defined(DUK_USE_SYMBOL_BUILTIN) +DUK_INTERNAL_DECL duk_bool_t duk_js_instanceof_ordinary(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y); +#endif DUK_INTERNAL_DECL duk_bool_t duk_js_in(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y); DUK_INTERNAL_DECL duk_small_uint_t duk_js_typeof_stridx(duk_tval *tv_x); diff --git a/src-input/duk_js_ops.c b/src-input/duk_js_ops.c index 833b394e..2531e138 100644 --- a/src-input/duk_js_ops.c +++ b/src-input/duk_js_ops.c @@ -998,22 +998,19 @@ DUK_INTERNAL duk_bool_t duk_js_compare_helper(duk_hthread *thr, duk_tval *tv_x, */ /* - * E5 Section 11.8.6 describes the main algorithm, which uses - * [[HasInstance]]. [[HasInstance]] is defined for only - * function objects: + * ES2015 Section 7.3.19 describes the OrdinaryHasInstance() algorithm + * which covers both bound and non-bound functions; in effect the algorithm + * includes E5 Sections 11.8.6, 15.3.5.3, and 15.3.4.5.3. * - * - 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. + * ES2015 Section 12.9.4 describes the instanceof operator which first + * checks @@hasInstance well-known symbol and falls back to + * OrdinaryHasInstance(). * * 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_LOCAL duk_bool_t duk__js_instanceof_helper(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, duk_bool_t skip_sym_check) { duk_hobject *func; duk_hobject *val; duk_hobject *proto; @@ -1036,6 +1033,23 @@ DUK_INTERNAL duk_bool_t duk_js_instanceof(duk_hthread *thr, duk_tval *tv_x, duk_ func = duk_require_hobject(thr, -1); DUK_ASSERT(func != NULL); +#if defined(DUK_USE_SYMBOL_BUILTIN) + /* + * @@hasInstance check, ES2015 Section 12.9.4, Steps 2-4. + */ + if (!skip_sym_check) { + if (duk_get_method_stridx(thr, -1, DUK_STRIDX_WELLKNOWN_SYMBOL_HAS_INSTANCE)) { + /* [ ... lhs rhs func ] */ + duk_insert(thr, -3); /* -> [ ... func lhs rhs ] */ + duk_swap_top(thr, -2); /* -> [ ... func rhs(this) lhs ] */ + duk_call_method(thr, 1); + return duk_to_boolean_top_pop(thr); + } + } +#else + DUK_UNREF(skip_sym_check); +#endif + /* * For bound objects, [[HasInstance]] just calls the target function * [[HasInstance]]. If that is again a bound object, repeat until @@ -1189,6 +1203,16 @@ DUK_INTERNAL duk_bool_t duk_js_instanceof(duk_hthread *thr, duk_tval *tv_x, duk_ #endif } +#if defined(DUK_USE_SYMBOL_BUILTIN) +DUK_INTERNAL duk_bool_t duk_js_instanceof_ordinary(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y) { + return duk__js_instanceof_helper(thr, tv_x, tv_y, 1 /*skip_sym_check*/); +} +#endif + +DUK_INTERNAL duk_bool_t duk_js_instanceof(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y) { + return duk__js_instanceof_helper(thr, tv_x, tv_y, 0 /*skip_sym_check*/); +} + /* * in */ diff --git a/src-input/strings.yaml b/src-input/strings.yaml index be3e2cc5..3927b2f4 100644 --- a/src-input/strings.yaml +++ b/src-input/strings.yaml @@ -482,6 +482,10 @@ strings: type: symbol variant: wellknown string: "Symbol.toPrimitive" + - str: + type: symbol + variant: wellknown + string: "Symbol.hasInstance" # Misc - str: "setPrototypeOf" diff --git a/tests/api/test-instanceof-hasinstance.c b/tests/api/test-instanceof-hasinstance.c new file mode 100644 index 00000000..d983c003 --- /dev/null +++ b/tests/api/test-instanceof-hasinstance.c @@ -0,0 +1,36 @@ +/* + * duk_instanceof() with rhs having @@hasInstance + */ + +/*=== +*** test_1 (duk_safe_call) +hasinst called +instanceof: 1 +final top: 2 +==> rc=0, result='undefined' +===*/ + +static duk_ret_t test_1(duk_context *ctx, void *udata) { + (void) udata; + + /* Function.prototype[@@hasInstance] is not writable or configurable. + * To set it, use duk_def_prop() or Object.defineProperty() to avoid + * the ancestor blocking the write. + */ + duk_eval_string(ctx, "123"); + duk_eval_string(ctx, "(function foo() {})"); + duk_push_string(ctx, DUK_WELLKNOWN_SYMBOL("Symbol.hasInstance")); + duk_eval_string(ctx, "(function hasinst() { print('hasinst called'); return true; })"); + duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE); + + /* [ lhs rhs ] */ + + printf("instanceof: %d\n", (int) duk_instanceof(ctx, 0, 1)); + + printf("final top: %ld\n", (long) duk_get_top(ctx)); + return 0; +} + +void test(duk_context *ctx) { + TEST_SAFE_CALL(test_1); +} diff --git a/tests/ecmascript/test-bi-performance.js b/tests/ecmascript/test-bi-performance.js index e369d49a..392db967 100644 --- a/tests/ecmascript/test-bi-performance.js +++ b/tests/ecmascript/test-bi-performance.js @@ -5,7 +5,7 @@ true true true true function true -true false true +true true true undefined true number @@ -28,9 +28,8 @@ function test() { print(pd.value !== void 0); print(pd.writable, pd.enumerable, pd.configurable); - // 'performance.now' is a function. - // XXX: attributes should be 'wec', not 'wc'; this is due to built-in - // init data limitations. + // 'performance.now' is a function, property attributes don't follow + // the convention of normal built-ins and are 'wec'. print(typeof performance.now); pd = Object.getOwnPropertyDescriptor(performance, 'now'); print(pd.value !== void 0); diff --git a/tests/ecmascript/test-expr-instanceof-hasinstance.js b/tests/ecmascript/test-expr-instanceof-hasinstance.js new file mode 100644 index 00000000..a3272be9 --- /dev/null +++ b/tests/ecmascript/test-expr-instanceof-hasinstance.js @@ -0,0 +1,181 @@ +/* + * instanceof and @@hasInstance + */ + +/*=== +- No @@hasInstance case; inherited from Function.prototype +false +true +- Function overrides @@hasInstance +false +true +- Function inherits a non-standard @@hasInstance +inherited hasInstance true 123 +true +inherited hasInstance true [object Object] +true +- Function inherits a non-standard @@hasInstance, but function itself also provides an undefined @@hasInstance +false +true +true +- Same but overriding value is null +false +true +true +- Same but overriding value is not undefined/null, but also not a callable object +TypeError +- Same, plain non-callable object +TypeError +- @@hasInstance is a getter with side effects +@@hasInstance getter +hasInstance true 123 +true +@@hasInstance getter +hasInstance true [object Object] +true +- Function.prototype[@@hasInstance] exists +true +true +false +function false false false +- Function.prototype[@@hasInstance] access to OrdinaryHasInstance() +false +true +false +true +===*/ + +function basicTest() { + print('- No @@hasInstance case; inherited from Function.prototype'); + var rhs = function () {}; + print(123 instanceof rhs); + print(Object.create(rhs.prototype) instanceof rhs); + + print('- Function overrides @@hasInstance'); + var rhs = function () {}; + rhs[Symbol.hasInstance] = function (v) { + print('hasInstance', this === rhs, v); + return 1; + } + print(123 instanceof rhs); + print(Object.create(rhs.prototype) instanceof rhs); + + print('- Function inherits a non-standard @@hasInstance'); + var rhs = function () {}; + var o = {}; + Object.setPrototypeOf(rhs, o); + o[Symbol.hasInstance] = function (v) { + print('inherited hasInstance', this === rhs, v); + return true; + } + print(123 instanceof rhs); + print(Object.create(rhs.prototype) instanceof rhs); + + // In this case we fall back to OrdinaryHasInstance(). + print('- Function inherits a non-standard @@hasInstance, but function itself also provides an undefined @@hasInstance'); + var rhs = function () {}; + var o = {}; + Object.setPrototypeOf(rhs, o); + o[Symbol.hasInstance] = function (v) { + print('undefined hasInstance', this === rhs, v); + return 1; + } + rhs[Symbol.hasInstance] = void 0; + print(123 instanceof rhs); + print(Object.create(rhs.prototype) instanceof rhs); + var inst = new rhs(); + print(inst instanceof rhs); + + // For null value, GetMethod (https://www.ecma-international.org/ecma-262/6.0/#sec-getmethod) + // returns 'undefined' for both undefined AND null, so that InstanceofOperator(O, C) + // handles them the same in Step 4 of https://www.ecma-international.org/ecma-262/6.0/#sec-instanceofoperator. + // In other words, for both undefined and null we must fall back to the + // OrdinaryHasInstance() algorithm. V8 treats null and undefined differently + // (TypeError for null), Firefox treats them the same. + + // https://www.ecma-international.org/ecma-262/6.0/#sec-instanceofoperator + // A non-undefined/null value, causes a "not callable" TypeError + print('- Same but overriding value is null'); + var rhs = function () {}; + var o = {}; + Object.setPrototypeOf(rhs, o); + o[Symbol.hasInstance] = function (v) { + print('null hasInstance', this === rhs, v); + return 1; + } + rhs[Symbol.hasInstance] = null; + print(123 instanceof rhs); + print(Object.create(rhs.prototype) instanceof rhs); + var inst = new rhs(); + print(inst instanceof rhs); + + print('- Same but overriding value is not undefined/null, but also not a callable object'); + var rhs = function () {}; + var o = {}; + Object.setPrototypeOf(rhs, o); + o[Symbol.hasInstance] = function (v) { + print('fail hasInstance', this === rhs, v); + return 1; + } + try { + rhs[Symbol.hasInstance] = true; + print(123 instanceof rhs); + } catch (e) { + print(e.name); + } + + print('- Same, plain non-callable object'); + var rhs = function () {}; + var o = {}; + Object.setPrototypeOf(rhs, o); + o[Symbol.hasInstance] = function (v) { + print('fail hasInstance', this === rhs, v); + return 1; + } + try { + rhs[Symbol.hasInstance] = { plain: true }; + print(123 instanceof rhs); + } catch (e) { + print(e.name); + } + + print('- @@hasInstance is a getter with side effects'); + var rhs = function () {}; + Object.defineProperty(rhs, Symbol.hasInstance, { + get: function () { + print('@@hasInstance getter'); + return function (v) { + print('hasInstance', this === rhs, v); + return 1; + } + } + }); + print(123 instanceof rhs); + print(Object.create(rhs.prototype) instanceof rhs); + + print('- Function.prototype[@@hasInstance] exists'); + print(Symbol.hasInstance in Function.prototype); + print(Function.prototype[Symbol.hasInstance].call(Error, new RangeError())); + print(Function.prototype[Symbol.hasInstance].call(Error, new Date())); + var pd = Object.getOwnPropertyDescriptor(Function.prototype, Symbol.hasInstance); + print(typeof pd.value, pd.writable, pd.enumerable, pd.configurable); + + // Function.prototype[@@hasInstance] allows direct access to the + // ES2015 OrdinaryHasInstance() specification method. + print('- Function.prototype[@@hasInstance] access to OrdinaryHasInstance()'); + var rhs = function () {}; + rhs[Symbol.hasInstance] = function (v) { + print('hasInstance', this === rhs, v); + return 0; + } + print(123 instanceof rhs); + print(Object.create(rhs.prototype) instanceof rhs); + print(Function.prototype[Symbol.hasInstance].call(rhs, {})); + print(Function.prototype[Symbol.hasInstance].call(rhs, Object.create(rhs.prototype))); +} + +try { + basicTest(); +} catch (e) { + print(e.stack || e); +} diff --git a/tools/genbuiltins.py b/tools/genbuiltins.py index 74527b83..ad9b59cb 100644 --- a/tools/genbuiltins.py +++ b/tools/genbuiltins.py @@ -874,11 +874,16 @@ def metadata_add_string_define_names(strlist, special_defs): if len(v) >= 1 and v[0] == '\x82': pfx = 'DUK_STRIDX_INT_' v = v[1:] + elif len(v) >= 1 and v[0] == '\x81' and v[-1] == '\xff': + pfx = 'DUK_STRIDX_WELLKNOWN_' + v = v[1:-1] else: pfx = 'DUK_STRIDX_' t = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', v) # add underscores: aB -> a_B + t = re.sub(r'\.', '_', t) # replace . with _, e.g. Symbol.iterator s['define'] = pfx + t.upper() + logger.debug('stridx define: ' + s['define']) # Add a 'stridx_used' flag for strings which need a stridx. def metadata_add_string_used_stridx(strlist, used_stridx_meta): @@ -1389,6 +1394,7 @@ def steal_prop(props, key, allow_accessor=True): LENGTH_PROPERTY_ATTRIBUTES = 'c' ACCESSOR_PROPERTY_ATTRIBUTES = 'c' DEFAULT_DATA_PROPERTY_ATTRIBUTES = 'wc' +DEFAULT_FUNC_PROPERTY_ATTRIBUTES = 'wc' # Encoding constants (must match duk_hthread_builtins.c). PROP_FLAGS_BITS = 3 @@ -1961,6 +1967,16 @@ def gen_ramobj_initdata_for_props(meta, be, bi, string_to_stridx, natfunc_name_t assert(magic <= 0xffff) be.varuint(magic) + default_attrs = DEFAULT_FUNC_PROPERTY_ATTRIBUTES + attrs = funprop.get('attributes', default_attrs) + attrs = attrs.replace('a', '') # ram bitstream doesn't encode 'accessor' attribute + if attrs != default_attrs: + logger.debug('non-default attributes: %s -> %r (default %r)' % (funprop['key'], attrs, default_attrs)) + be.bits(1, 1) # flag: have custom attributes + be.bits(encode_property_flags(attrs), PROP_FLAGS_BITS) + else: + be.bits(0, 1) # flag: no custom attributes + return count_normal_props, count_function_props # Get helper maps for RAM objects. diff --git a/website/api/duk_instanceof.yaml b/website/api/duk_instanceof.yaml index 72ab6588..f0f31e45 100644 --- a/website/api/duk_instanceof.yaml +++ b/website/api/duk_instanceof.yaml @@ -8,7 +8,7 @@ stack: | summary: |
Compare values at idx1
and idx2
using the
- Ecmascript instanceof
+ Ecmascript instanceof
operator. Returns 1 if val1 instanceof val2
, 0 if not. Throws an error
if either index is invalid; instanceof
itself also throws errors for
invalid argument types.