Browse Source

Add support for @@toStringTag

pull/1822/head
Sami Vaarala 7 years ago
parent
commit
380aa34b41
  1. 4
      src-input/builtins.yaml
  2. 2
      src-input/duk_api_internal.h
  3. 203
      src-input/duk_api_stack.c
  4. 7
      src-input/duk_bi_object.c
  5. 4
      src-input/strings.yaml

4
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

2
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);

203
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: {

7
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;
}

4
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"

Loading…
Cancel
Save