Skip to content

Commit 380aa34

Browse files
committed
Add support for @@toStringTag
1 parent 24a71fb commit 380aa34

5 files changed

Lines changed: 171 additions & 49 deletions

File tree

src-input/builtins.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2838,6 +2838,7 @@ objects:
28382838
value: "Math"
28392839
attributes: "c"
28402840
es6: true
2841+
present_if: DUK_USE_SYMBOL_BUILTIN
28412842

28422843
- id: bi_json
28432844
class: JSON
@@ -2868,6 +2869,7 @@ objects:
28682869
# string: "Symbol.toStringTag"
28692870
# value: XXX
28702871
# es6: true
2872+
# present_if: DUK_USE_SYMBOL_BUILTIN
28712873

28722874
# E5 Section 13.2.3
28732875
- id: bi_type_error_thrower
@@ -3451,7 +3453,7 @@ objects:
34513453
type: symbol
34523454
variant: wellknown
34533455
string: "Symbol.toStringTag"
3454-
value: "symbol"
3456+
value: "Symbol"
34553457
attributes: "c"
34563458
es6: true
34573459
present_if: DUK_USE_SYMBOL_BUILTIN

src-input/duk_api_internal.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ DUK_INTERNAL_DECL duk_bool_t duk_to_boolean_top_pop(duk_hthread *thr);
147147
#if defined(DUK_USE_DEBUGGER_SUPPORT) /* only needed by debugger for now */
148148
DUK_INTERNAL_DECL duk_hstring *duk_safe_to_hstring(duk_hthread *thr, duk_idx_t idx);
149149
#endif
150-
DUK_INTERNAL_DECL void duk_push_class_string_tval(duk_hthread *thr, duk_tval *tv);
150+
DUK_INTERNAL_DECL void duk_push_class_string_tval(duk_hthread *thr, duk_tval *tv, duk_bool_t avoid_side_effects);
151151

152152
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 */
153153
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);

src-input/duk_api_stack.c

Lines changed: 161 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3081,83 +3081,202 @@ DUK_INTERNAL duk_hstring *duk_safe_to_hstring(duk_hthread *thr, duk_idx_t idx) {
30813081
#endif
30823082

30833083
/* Push Object.prototype.toString() output for 'tv'. */
3084-
DUK_INTERNAL void duk_push_class_string_tval(duk_hthread *thr, duk_tval *tv) {
3085-
duk_small_uint_t stridx;
3086-
duk_hstring *h_strclass;
3084+
#if 0 /* See XXX note why this variant doesn't work. */
3085+
DUK_INTERNAL void duk_push_class_string_tval(duk_hthread *thr, duk_tval *tv, duk_bool_t avoid_side_effects) {
3086+
duk_uint_t stridx_bidx = 0; /* (prototype_bidx << 16) + default_tag_stridx */
30873087

30883088
DUK_ASSERT_API_ENTRY(thr);
30893089

3090+
/* Conceptually for any non-undefined/null value we should do a
3091+
* ToObject() coercion and look up @@toStringTag (from the object
3092+
* prototype) to see if a custom tag should be used. Avoid the
3093+
* actual conversion by doing a prototype lookup without the object
3094+
* coercion. However, see problem below.
3095+
*/
3096+
3097+
duk_push_literal(thr, "[object "); /* -> [ ... "[object" ] */
3098+
30903099
switch (DUK_TVAL_GET_TAG(tv)) {
30913100
case DUK_TAG_UNUSED: /* Treat like 'undefined', shouldn't happen. */
30923101
case DUK_TAG_UNDEFINED: {
3093-
stridx = DUK_STRIDX_UC_UNDEFINED;
3094-
break;
3102+
stridx_bidx = DUK_STRIDX_UC_UNDEFINED;
3103+
goto use_stridx;
30953104
}
30963105
case DUK_TAG_NULL: {
3097-
stridx = DUK_STRIDX_UC_NULL;
3098-
break;
3106+
stridx_bidx = DUK_STRIDX_UC_NULL;
3107+
goto use_stridx;
30993108
}
31003109
case DUK_TAG_BOOLEAN: {
3101-
stridx = DUK_STRIDX_UC_BOOLEAN;
3102-
break;
3110+
stridx_bidx = (DUK_BIDX_BOOLEAN_PROTOTYPE << 16) + DUK_STRIDX_UC_BOOLEAN;
3111+
goto use_proto_bidx;
31033112
}
31043113
case DUK_TAG_POINTER: {
3105-
stridx = DUK_STRIDX_UC_POINTER;
3106-
break;
3114+
stridx_bidx = (DUK_BIDX_POINTER_PROTOTYPE << 16) + DUK_STRIDX_UC_POINTER;
3115+
goto use_proto_bidx;
31073116
}
31083117
case DUK_TAG_LIGHTFUNC: {
3109-
stridx = DUK_STRIDX_UC_FUNCTION;
3110-
break;
3118+
stridx_bidx = (DUK_BIDX_FUNCTION_PROTOTYPE << 16) + DUK_STRIDX_UC_FUNCTION;
3119+
goto use_proto_bidx;
31113120
}
31123121
case DUK_TAG_STRING: {
31133122
duk_hstring *h;
31143123
h = DUK_TVAL_GET_STRING(tv);
31153124
DUK_ASSERT(h != NULL);
31163125
if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) {
3117-
stridx = DUK_STRIDX_UC_SYMBOL;
3126+
/* Even without DUK_USE_SYMBOL_BUILTIN the Symbol
3127+
* prototype exists so we can lookup @@toStringTag
3128+
* and provide [object Symbol] for symbol values
3129+
* created from C code.
3130+
*/
3131+
stridx_bidx = (DUK_BIDX_SYMBOL_PROTOTYPE << 16) + DUK_STRIDX_UC_SYMBOL;
31183132
} else {
3119-
stridx = DUK_STRIDX_UC_STRING;
3133+
stridx_bidx = (DUK_BIDX_STRING_PROTOTYPE << 16) + DUK_STRIDX_UC_STRING;
31203134
}
3121-
break;
3135+
goto use_proto_bidx;
31223136
}
31233137
case DUK_TAG_OBJECT: {
3124-
duk_hobject *h;
3125-
duk_small_uint_t classnum;
3126-
3127-
h = DUK_TVAL_GET_OBJECT(tv);
3128-
DUK_ASSERT(h != NULL);
3129-
classnum = DUK_HOBJECT_GET_CLASS_NUMBER(h);
3130-
stridx = DUK_HOBJECT_CLASS_NUMBER_TO_STRIDX(classnum);
3131-
3132-
/* XXX: This is not entirely correct anymore; in ES2015 the
3133-
* default lookup should use @@toStringTag to come up with
3134-
* e.g. [object Symbol], [object Uint8Array], etc. See
3135-
* ES2015 Section 19.1.3.6. The downside of implementing that
3136-
* directly is that the @@toStringTag lookup may have side
3137-
* effects, so all call sites must be checked for that.
3138-
* Some may need a side-effect free lookup, e.g. avoiding
3139-
* getters which are not typical.
3140-
*/
3141-
break;
3138+
duk_push_tval(thr, tv);
3139+
stridx_bidx = 0xffffffffUL; /* Marker value. */
3140+
goto use_pushed_object;
31423141
}
31433142
case DUK_TAG_BUFFER: {
3144-
stridx = DUK_STRIDX_UINT8_ARRAY;
3145-
break;
3143+
stridx_bidx = (DUK_BIDX_UINT8ARRAY_PROTOTYPE << 16) + DUK_STRIDX_UINT8_ARRAY;
3144+
goto use_proto_bidx;
31463145
}
31473146
#if defined(DUK_USE_FASTINT)
31483147
case DUK_TAG_FASTINT:
31493148
/* Fall through to generic number case. */
31503149
#endif
31513150
default: {
31523151
DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); /* number (maybe fastint) */
3153-
stridx = DUK_STRIDX_UC_NUMBER;
3154-
break;
3152+
stridx_bidx = (DUK_BIDX_NUMBER_PROTOTYPE << 16) + DUK_STRIDX_UC_NUMBER;
3153+
goto use_proto_bidx;
3154+
}
3155+
}
3156+
DUK_ASSERT(0); /* Never here. */
3157+
3158+
use_proto_bidx:
3159+
DUK_ASSERT_BIDX_VALID((stridx_bidx >> 16) & 0xffffUL);
3160+
duk_push_hobject(thr, thr->builtins[(stridx_bidx >> 16) & 0xffffUL]);
3161+
/* Fall through. */
3162+
3163+
use_pushed_object:
3164+
/* [ ... "[object" obj ] */
3165+
3166+
#if defined(DUK_USE_SYMBOL_BUILTIN)
3167+
/* XXX: better handling with avoid_side_effects == 1; lookup tval
3168+
* without Proxy or getter side effects, and use it in sanitized
3169+
* form if it's a string.
3170+
*/
3171+
if (!avoid_side_effects) {
3172+
/* XXX: The problem with using the prototype object as the
3173+
* lookup base is that if @@toStringTag is a getter, its
3174+
* 'this' binding must be the ToObject() coerced input value,
3175+
* not the prototype object of the type.
3176+
*/
3177+
(void) duk_get_prop_stridx(thr, -1, DUK_STRIDX_WELLKNOWN_SYMBOL_TO_STRING_TAG);
3178+
if (duk_is_string_notsymbol(thr, -1)) {
3179+
duk_remove_m2(thr);
3180+
goto finish;
3181+
}
3182+
duk_pop_unsafe(thr);
3183+
}
3184+
#endif
3185+
3186+
if (stridx_bidx == 0xffffffffUL) {
3187+
duk_hobject *h_obj;
3188+
duk_small_uint_t classnum;
3189+
3190+
h_obj = duk_known_hobject(thr, -1);
3191+
DUK_ASSERT(h_obj != NULL);
3192+
classnum = DUK_HOBJECT_GET_CLASS_NUMBER(h_obj);
3193+
stridx_bidx = DUK_HOBJECT_CLASS_NUMBER_TO_STRIDX(classnum);
3194+
} else {
3195+
/* stridx_bidx already has the desired fallback stridx. */
3196+
;
3197+
}
3198+
duk_pop_unsafe(thr);
3199+
/* Fall through. */
3200+
3201+
use_stridx:
3202+
/* [ ... "[object" ] */
3203+
duk_push_hstring_stridx(thr, stridx_bidx & 0xffffUL);
3204+
3205+
finish:
3206+
/* [ ... "[object" tag ] */
3207+
duk_push_literal(thr, "]");
3208+
duk_concat(thr, 3); /* [ ... "[object" tag "]" ] -> [ ... res ] */
3209+
}
3210+
#endif /* 0 */
3211+
3212+
DUK_INTERNAL void duk_push_class_string_tval(duk_hthread *thr, duk_tval *tv, duk_bool_t avoid_side_effects) {
3213+
duk_hobject *h_obj;
3214+
duk_small_uint_t classnum;
3215+
duk_small_uint_t stridx;
3216+
3217+
DUK_ASSERT_API_ENTRY(thr);
3218+
3219+
/* Conceptually for any non-undefined/null value we should do a
3220+
* ToObject() coercion and look up @@toStringTag (from the object
3221+
* prototype) to see if a custom result should be used. We'd like to
3222+
* avoid the actual conversion, but even for primitive types the
3223+
* prototype may have @@toStringTag. What's worse, the @@toStringTag
3224+
* property may be a getter that must get the object coerced value
3225+
* (not the prototype) as its 'this' binding.
3226+
*
3227+
* For now, do an actual object coercion. This could be avoided by
3228+
* doing a side effect free lookup to see if a getter would be invoked.
3229+
* If not, the value can be read directly and the object coercion could
3230+
* be avoided. This may not be worth it in practice, because
3231+
* Object.prototype.toString() is usually not performance critical.
3232+
*/
3233+
3234+
duk_push_literal(thr, "[object "); /* -> [ ... "[object" ] */
3235+
3236+
switch (DUK_TVAL_GET_TAG(tv)) {
3237+
case DUK_TAG_UNUSED: /* Treat like 'undefined', shouldn't happen. */
3238+
case DUK_TAG_UNDEFINED: {
3239+
duk_push_hstring_stridx(thr, DUK_STRIDX_UC_UNDEFINED);
3240+
goto finish;
3241+
}
3242+
case DUK_TAG_NULL: {
3243+
duk_push_hstring_stridx(thr, DUK_STRIDX_UC_NULL);
3244+
goto finish;
31553245
}
31563246
}
3157-
h_strclass = DUK_HTHREAD_GET_STRING(thr, stridx);
3158-
DUK_ASSERT(h_strclass != NULL);
31593247

3160-
duk_push_sprintf(thr, "[object %s]", (const char *) DUK_HSTRING_GET_DATA(h_strclass));
3248+
duk_push_tval(thr, tv);
3249+
tv = NULL; /* Invalidated by ToObject(). */
3250+
duk_to_object(thr, -1);
3251+
3252+
/* [ ... "[object" obj ] */
3253+
3254+
#if defined(DUK_USE_SYMBOL_BUILTIN)
3255+
/* XXX: better handling with avoid_side_effects == 1; lookup tval
3256+
* without Proxy or getter side effects, and use it in sanitized
3257+
* form if it's a string.
3258+
*/
3259+
if (!avoid_side_effects) {
3260+
(void) duk_get_prop_stridx(thr, -1, DUK_STRIDX_WELLKNOWN_SYMBOL_TO_STRING_TAG);
3261+
if (duk_is_string_notsymbol(thr, -1)) {
3262+
duk_remove_m2(thr);
3263+
goto finish;
3264+
}
3265+
duk_pop_unsafe(thr);
3266+
}
3267+
#endif
3268+
3269+
h_obj = duk_known_hobject(thr, -1);
3270+
DUK_ASSERT(h_obj != NULL);
3271+
classnum = DUK_HOBJECT_GET_CLASS_NUMBER(h_obj);
3272+
stridx = DUK_HOBJECT_CLASS_NUMBER_TO_STRIDX(classnum);
3273+
duk_pop_unsafe(thr);
3274+
duk_push_hstring_stridx(thr, stridx);
3275+
3276+
finish:
3277+
/* [ ... "[object" tag ] */
3278+
duk_push_literal(thr, "]");
3279+
duk_concat(thr, 3); /* [ ... "[object" tag "]" ] -> [ ... res ] */
31613280
}
31623281

31633282
/* 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
66006719
break;
66016720
}
66026721
}
6603-
duk_push_class_string_tval(thr, tv);
6722+
duk_push_class_string_tval(thr, tv, 1 /*avoid_side_effects*/);
66046723
break;
66056724
}
66066725
case DUK_TAG_BUFFER: {

src-input/duk_bi_object.c

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,9 @@
77
/* Needed even when Object built-in disabled. */
88
DUK_INTERNAL duk_ret_t duk_bi_object_prototype_to_string(duk_hthread *thr) {
99
duk_tval *tv;
10+
1011
tv = DUK_HTHREAD_THIS_PTR(thr);
11-
/* XXX: This is not entirely correct anymore; in ES2015 the
12-
* default lookup should use @@toStringTag to come up with
13-
* e.g. [object Symbol].
14-
*/
15-
duk_push_class_string_tval(thr, tv);
12+
duk_push_class_string_tval(thr, tv, 0 /*avoid_side_effects*/);
1613
return 1;
1714
}
1815

src-input/strings.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,10 @@ strings:
486486
type: symbol
487487
variant: wellknown
488488
string: "Symbol.hasInstance"
489+
- str:
490+
type: symbol
491+
variant: wellknown
492+
string: "Symbol.toStringTag"
489493

490494
# Misc
491495
- str: "setPrototypeOf"

0 commit comments

Comments
 (0)