@@ -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 : {
0 commit comments