diff --git a/src-input/builtins.yaml b/src-input/builtins.yaml index b27a7f92..dae51671 100644 --- a/src-input/builtins.yaml +++ b/src-input/builtins.yaml @@ -2838,6 +2838,7 @@ objects: value: "Math" attributes: "c" es6: true + present_if: DUK_USE_SYMBOL_BUILTIN - id: bi_json class: JSON @@ -2868,6 +2869,7 @@ objects: # string: "Symbol.toStringTag" # value: XXX # es6: true + # present_if: DUK_USE_SYMBOL_BUILTIN # E5 Section 13.2.3 - id: bi_type_error_thrower @@ -3451,7 +3453,7 @@ objects: type: symbol variant: wellknown string: "Symbol.toStringTag" - value: "symbol" + value: "Symbol" attributes: "c" es6: true present_if: DUK_USE_SYMBOL_BUILTIN diff --git a/src-input/duk_api_internal.h b/src-input/duk_api_internal.h index 004255bd..d33c8e80 100644 --- a/src-input/duk_api_internal.h +++ b/src-input/duk_api_internal.h @@ -147,7 +147,7 @@ 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 -DUK_INTERNAL_DECL void duk_push_class_string_tval(duk_hthread *thr, duk_tval *tv); +DUK_INTERNAL_DECL void duk_push_class_string_tval(duk_hthread *thr, duk_tval *tv, duk_bool_t avoid_side_effects); DUK_INTERNAL_DECL duk_int_t duk_to_int_clamped_raw(duk_hthread *thr, duk_idx_t idx, duk_int_t minval, duk_int_t maxval, duk_bool_t *out_clamped); /* out_clamped=NULL, RangeError if outside range */ DUK_INTERNAL_DECL duk_int_t duk_to_int_clamped(duk_hthread *thr, duk_idx_t idx, duk_int_t minval, duk_int_t maxval); diff --git a/src-input/duk_api_stack.c b/src-input/duk_api_stack.c index 5aa701e2..afed9dd7 100644 --- a/src-input/duk_api_stack.c +++ b/src-input/duk_api_stack.c @@ -3081,68 +3081,67 @@ DUK_INTERNAL duk_hstring *duk_safe_to_hstring(duk_hthread *thr, duk_idx_t idx) { #endif /* Push Object.prototype.toString() output for 'tv'. */ -DUK_INTERNAL void duk_push_class_string_tval(duk_hthread *thr, duk_tval *tv) { - duk_small_uint_t stridx; - duk_hstring *h_strclass; +#if 0 /* See XXX note why this variant doesn't work. */ +DUK_INTERNAL void duk_push_class_string_tval(duk_hthread *thr, duk_tval *tv, duk_bool_t avoid_side_effects) { + duk_uint_t stridx_bidx = 0; /* (prototype_bidx << 16) + default_tag_stridx */ DUK_ASSERT_API_ENTRY(thr); + /* Conceptually for any non-undefined/null value we should do a + * ToObject() coercion and look up @@toStringTag (from the object + * prototype) to see if a custom tag should be used. Avoid the + * actual conversion by doing a prototype lookup without the object + * coercion. However, see problem below. + */ + + duk_push_literal(thr, "[object "); /* -> [ ... "[object" ] */ + switch (DUK_TVAL_GET_TAG(tv)) { case DUK_TAG_UNUSED: /* Treat like 'undefined', shouldn't happen. */ case DUK_TAG_UNDEFINED: { - stridx = DUK_STRIDX_UC_UNDEFINED; - break; + stridx_bidx = DUK_STRIDX_UC_UNDEFINED; + goto use_stridx; } case DUK_TAG_NULL: { - stridx = DUK_STRIDX_UC_NULL; - break; + stridx_bidx = DUK_STRIDX_UC_NULL; + goto use_stridx; } case DUK_TAG_BOOLEAN: { - stridx = DUK_STRIDX_UC_BOOLEAN; - break; + stridx_bidx = (DUK_BIDX_BOOLEAN_PROTOTYPE << 16) + DUK_STRIDX_UC_BOOLEAN; + goto use_proto_bidx; } case DUK_TAG_POINTER: { - stridx = DUK_STRIDX_UC_POINTER; - break; + stridx_bidx = (DUK_BIDX_POINTER_PROTOTYPE << 16) + DUK_STRIDX_UC_POINTER; + goto use_proto_bidx; } case DUK_TAG_LIGHTFUNC: { - stridx = DUK_STRIDX_UC_FUNCTION; - break; + stridx_bidx = (DUK_BIDX_FUNCTION_PROTOTYPE << 16) + DUK_STRIDX_UC_FUNCTION; + goto use_proto_bidx; } case DUK_TAG_STRING: { duk_hstring *h; h = DUK_TVAL_GET_STRING(tv); DUK_ASSERT(h != NULL); if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) { - stridx = DUK_STRIDX_UC_SYMBOL; + /* Even without DUK_USE_SYMBOL_BUILTIN the Symbol + * prototype exists so we can lookup @@toStringTag + * and provide [object Symbol] for symbol values + * created from C code. + */ + stridx_bidx = (DUK_BIDX_SYMBOL_PROTOTYPE << 16) + DUK_STRIDX_UC_SYMBOL; } else { - stridx = DUK_STRIDX_UC_STRING; + stridx_bidx = (DUK_BIDX_STRING_PROTOTYPE << 16) + DUK_STRIDX_UC_STRING; } - break; + goto use_proto_bidx; } case DUK_TAG_OBJECT: { - duk_hobject *h; - duk_small_uint_t classnum; - - h = DUK_TVAL_GET_OBJECT(tv); - DUK_ASSERT(h != NULL); - classnum = DUK_HOBJECT_GET_CLASS_NUMBER(h); - stridx = DUK_HOBJECT_CLASS_NUMBER_TO_STRIDX(classnum); - - /* XXX: This is not entirely correct anymore; in ES2015 the - * default lookup should use @@toStringTag to come up with - * e.g. [object Symbol], [object Uint8Array], etc. See - * ES2015 Section 19.1.3.6. The downside of implementing that - * directly is that the @@toStringTag lookup may have side - * effects, so all call sites must be checked for that. - * Some may need a side-effect free lookup, e.g. avoiding - * getters which are not typical. - */ - break; + duk_push_tval(thr, tv); + stridx_bidx = 0xffffffffUL; /* Marker value. */ + goto use_pushed_object; } case DUK_TAG_BUFFER: { - stridx = DUK_STRIDX_UINT8_ARRAY; - break; + stridx_bidx = (DUK_BIDX_UINT8ARRAY_PROTOTYPE << 16) + DUK_STRIDX_UINT8_ARRAY; + goto use_proto_bidx; } #if defined(DUK_USE_FASTINT) case DUK_TAG_FASTINT: @@ -3150,14 +3149,134 @@ DUK_INTERNAL void duk_push_class_string_tval(duk_hthread *thr, duk_tval *tv) { #endif default: { DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); /* number (maybe fastint) */ - stridx = DUK_STRIDX_UC_NUMBER; - break; + stridx_bidx = (DUK_BIDX_NUMBER_PROTOTYPE << 16) + DUK_STRIDX_UC_NUMBER; + goto use_proto_bidx; + } + } + DUK_ASSERT(0); /* Never here. */ + + use_proto_bidx: + DUK_ASSERT_BIDX_VALID((stridx_bidx >> 16) & 0xffffUL); + duk_push_hobject(thr, thr->builtins[(stridx_bidx >> 16) & 0xffffUL]); + /* Fall through. */ + + use_pushed_object: + /* [ ... "[object" obj ] */ + +#if defined(DUK_USE_SYMBOL_BUILTIN) + /* XXX: better handling with avoid_side_effects == 1; lookup tval + * without Proxy or getter side effects, and use it in sanitized + * form if it's a string. + */ + if (!avoid_side_effects) { + /* XXX: The problem with using the prototype object as the + * lookup base is that if @@toStringTag is a getter, its + * 'this' binding must be the ToObject() coerced input value, + * not the prototype object of the type. + */ + (void) duk_get_prop_stridx(thr, -1, DUK_STRIDX_WELLKNOWN_SYMBOL_TO_STRING_TAG); + if (duk_is_string_notsymbol(thr, -1)) { + duk_remove_m2(thr); + goto finish; + } + duk_pop_unsafe(thr); + } +#endif + + if (stridx_bidx == 0xffffffffUL) { + duk_hobject *h_obj; + duk_small_uint_t classnum; + + h_obj = duk_known_hobject(thr, -1); + DUK_ASSERT(h_obj != NULL); + classnum = DUK_HOBJECT_GET_CLASS_NUMBER(h_obj); + stridx_bidx = DUK_HOBJECT_CLASS_NUMBER_TO_STRIDX(classnum); + } else { + /* stridx_bidx already has the desired fallback stridx. */ + ; + } + duk_pop_unsafe(thr); + /* Fall through. */ + + use_stridx: + /* [ ... "[object" ] */ + duk_push_hstring_stridx(thr, stridx_bidx & 0xffffUL); + + finish: + /* [ ... "[object" tag ] */ + duk_push_literal(thr, "]"); + duk_concat(thr, 3); /* [ ... "[object" tag "]" ] -> [ ... res ] */ +} +#endif /* 0 */ + +DUK_INTERNAL void duk_push_class_string_tval(duk_hthread *thr, duk_tval *tv, duk_bool_t avoid_side_effects) { + duk_hobject *h_obj; + duk_small_uint_t classnum; + duk_small_uint_t stridx; + + DUK_ASSERT_API_ENTRY(thr); + + /* Conceptually for any non-undefined/null value we should do a + * ToObject() coercion and look up @@toStringTag (from the object + * prototype) to see if a custom result should be used. We'd like to + * avoid the actual conversion, but even for primitive types the + * prototype may have @@toStringTag. What's worse, the @@toStringTag + * property may be a getter that must get the object coerced value + * (not the prototype) as its 'this' binding. + * + * For now, do an actual object coercion. This could be avoided by + * doing a side effect free lookup to see if a getter would be invoked. + * If not, the value can be read directly and the object coercion could + * be avoided. This may not be worth it in practice, because + * Object.prototype.toString() is usually not performance critical. + */ + + duk_push_literal(thr, "[object "); /* -> [ ... "[object" ] */ + + switch (DUK_TVAL_GET_TAG(tv)) { + case DUK_TAG_UNUSED: /* Treat like 'undefined', shouldn't happen. */ + case DUK_TAG_UNDEFINED: { + duk_push_hstring_stridx(thr, DUK_STRIDX_UC_UNDEFINED); + goto finish; + } + case DUK_TAG_NULL: { + duk_push_hstring_stridx(thr, DUK_STRIDX_UC_NULL); + goto finish; } } - h_strclass = DUK_HTHREAD_GET_STRING(thr, stridx); - DUK_ASSERT(h_strclass != NULL); - duk_push_sprintf(thr, "[object %s]", (const char *) DUK_HSTRING_GET_DATA(h_strclass)); + duk_push_tval(thr, tv); + tv = NULL; /* Invalidated by ToObject(). */ + duk_to_object(thr, -1); + + /* [ ... "[object" obj ] */ + +#if defined(DUK_USE_SYMBOL_BUILTIN) + /* XXX: better handling with avoid_side_effects == 1; lookup tval + * without Proxy or getter side effects, and use it in sanitized + * form if it's a string. + */ + if (!avoid_side_effects) { + (void) duk_get_prop_stridx(thr, -1, DUK_STRIDX_WELLKNOWN_SYMBOL_TO_STRING_TAG); + if (duk_is_string_notsymbol(thr, -1)) { + duk_remove_m2(thr); + goto finish; + } + duk_pop_unsafe(thr); + } +#endif + + h_obj = duk_known_hobject(thr, -1); + DUK_ASSERT(h_obj != NULL); + classnum = DUK_HOBJECT_GET_CLASS_NUMBER(h_obj); + stridx = DUK_HOBJECT_CLASS_NUMBER_TO_STRIDX(classnum); + duk_pop_unsafe(thr); + duk_push_hstring_stridx(thr, stridx); + + finish: + /* [ ... "[object" tag ] */ + duk_push_literal(thr, "]"); + duk_concat(thr, 3); /* [ ... "[object" tag "]" ] -> [ ... res ] */ } /* XXX: other variants like uint, u32 etc */ @@ -6600,7 +6719,7 @@ DUK_LOCAL const char *duk__push_string_tval_readable(duk_hthread *thr, duk_tval break; } } - duk_push_class_string_tval(thr, tv); + duk_push_class_string_tval(thr, tv, 1 /*avoid_side_effects*/); break; } case DUK_TAG_BUFFER: { diff --git a/src-input/duk_bi_object.c b/src-input/duk_bi_object.c index 9751fe95..79406293 100644 --- a/src-input/duk_bi_object.c +++ b/src-input/duk_bi_object.c @@ -7,12 +7,9 @@ /* Needed even when Object built-in disabled. */ DUK_INTERNAL duk_ret_t duk_bi_object_prototype_to_string(duk_hthread *thr) { duk_tval *tv; + tv = DUK_HTHREAD_THIS_PTR(thr); - /* XXX: This is not entirely correct anymore; in ES2015 the - * default lookup should use @@toStringTag to come up with - * e.g. [object Symbol]. - */ - duk_push_class_string_tval(thr, tv); + duk_push_class_string_tval(thr, tv, 0 /*avoid_side_effects*/); return 1; } diff --git a/src-input/strings.yaml b/src-input/strings.yaml index 3927b2f4..cf1d742b 100644 --- a/src-input/strings.yaml +++ b/src-input/strings.yaml @@ -486,6 +486,10 @@ strings: type: symbol variant: wellknown string: "Symbol.hasInstance" + - str: + type: symbol + variant: wellknown + string: "Symbol.toStringTag" # Misc - str: "setPrototypeOf"