From 37bd114925b6e6fcba816638beb940696205b0bc Mon Sep 17 00:00:00 2001 From: "dslomov@chromium.org" Date: Tue, 21 Oct 2014 17:21:32 +0000 Subject: [PATCH] Update ObjectToString to Harmony-draft algorithm Updates Object.prototype.toString() to use algorithm described in harmony drafts. Currently, the behaviour is essentially the same as ES262's version, however this changes when internal structures such as Promise make use of symbolToStringTag (as they are supposed to, see v8:3241), and changes further once Symbol.toStringTag is exposed publicly. BUG=v8:3241, v8:3502 LOG=N R=dslomov@chromium.org Review URL: https://codereview.chromium.org/546803003 Patch from Caitlin Potter . git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@24783 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- BUILD.gn | 1 + include/v8.h | 1 + src/api.cc | 58 ++++++++++++++ src/array.js | 2 +- src/arraybuffer.js | 3 + src/bootstrapper.cc | 4 + src/collection.js | 4 + src/flag-definitions.h | 3 +- src/generator.js | 2 + src/harmony-tostring.js | 66 ++++++++++++++++ src/heap/heap.h | 5 +- src/messages.js | 12 +-- src/promise.js | 2 + src/symbol.js | 4 + src/v8natives.js | 5 +- src/weak-collection.js | 4 + test/cctest/test-api.cc | 130 ++++++++++++++++++++++++++++++++ test/mjsunit/es6/collections.js | 13 +++- test/mjsunit/es6/generators-objects.js | 3 +- test/mjsunit/es6/object-tostring.js | 133 +++++++++++++++++++++++++++++++++ test/mjsunit/es6/promises.js | 14 +++- test/mjsunit/es6/symbols.js | 2 +- test/mjsunit/harmony/typedarrays.js | 7 ++ tools/gyp/v8.gyp | 1 + 24 files changed, 462 insertions(+), 17 deletions(-) create mode 100644 src/harmony-tostring.js create mode 100644 test/mjsunit/es6/object-tostring.js diff --git a/BUILD.gn b/BUILD.gn index 515e969..da5ff91 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -245,6 +245,7 @@ action("js2c_experimental") { "src/harmony-array.js", "src/harmony-typedarray.js", "src/harmony-classes.js", + "src/harmony-tostring.js" ] outputs = [ diff --git a/include/v8.h b/include/v8.h index 74d0354..f8519db 100644 --- a/include/v8.h +++ b/include/v8.h @@ -2130,6 +2130,7 @@ class V8_EXPORT Symbol : public Name { // Well-known symbols static Local GetIterator(Isolate* isolate); static Local GetUnscopables(Isolate* isolate); + static Local GetToStringTag(Isolate* isolate); V8_INLINE static Symbol* Cast(v8::Value* obj); diff --git a/src/api.cc b/src/api.cc index b6c7176..1698a3e 100644 --- a/src/api.cc +++ b/src/api.cc @@ -3409,6 +3409,37 @@ Local v8::Object::GetOwnPropertyNames() { } +static bool GetPredefinedToString(i::Handle tag, + Local* result) { + i::Isolate* i_isolate = tag->GetIsolate(); + Isolate* isolate = reinterpret_cast(i_isolate); + i::Factory* factory = i_isolate->factory(); + + if (i::String::Equals(tag, factory->Arguments_string())) { + *result = v8::String::NewFromUtf8(isolate, "[object ~Arguments]"); + } else if (i::String::Equals(tag, factory->Array_string())) { + *result = v8::String::NewFromUtf8(isolate, "[object ~Array]"); + } else if (i::String::Equals(tag, factory->Boolean_string())) { + *result = v8::String::NewFromUtf8(isolate, "[object ~Boolean]"); + } else if (i::String::Equals(tag, factory->Date_string())) { + *result = v8::String::NewFromUtf8(isolate, "[object ~Date]"); + } else if (i::String::Equals(tag, factory->Error_string())) { + *result = v8::String::NewFromUtf8(isolate, "[object ~Error]"); + } else if (i::String::Equals(tag, factory->Function_string())) { + *result = v8::String::NewFromUtf8(isolate, "[object ~Function]"); + } else if (i::String::Equals(tag, factory->Number_string())) { + *result = v8::String::NewFromUtf8(isolate, "[object ~Number]"); + } else if (i::String::Equals(tag, factory->RegExp_string())) { + *result = v8::String::NewFromUtf8(isolate, "[object ~RegExp]"); + } else if (i::String::Equals(tag, factory->String_string())) { + *result = v8::String::NewFromUtf8(isolate, "[object ~String]"); + } else { + return false; + } + return true; +} + + Local v8::Object::ObjectProtoToString() { i::Isolate* i_isolate = Utils::OpenHandle(this)->GetIsolate(); Isolate* isolate = reinterpret_cast(i_isolate); @@ -3418,6 +3449,7 @@ Local v8::Object::ObjectProtoToString() { i::Handle self = Utils::OpenHandle(this); i::Handle name(self->class_name(), i_isolate); + i::Handle tag; // Native implementation of Object.prototype.toString (v8natives.js): // var c = %_ClassOf(this); @@ -3432,6 +3464,27 @@ Local v8::Object::ObjectProtoToString() { i_isolate->factory()->Arguments_string())) { return v8::String::NewFromUtf8(isolate, "[object Object]"); } else { + if (internal::FLAG_harmony_tostring) { + i::Handle toStringTag = + Utils::OpenHandle(*Symbol::GetToStringTag(isolate)); + EXCEPTION_PREAMBLE(i_isolate); + has_pending_exception = + !i::Runtime::GetObjectProperty(i_isolate, self, toStringTag) + .ToHandle(&tag); + EXCEPTION_BAILOUT_CHECK(i_isolate, Local()); + + if (!tag->IsUndefined()) { + if (!tag->IsString()) + return v8::String::NewFromUtf8(isolate, "[object ???]"); + i::Handle tag_name = i::Handle::cast(tag); + if (!i::String::Equals(class_name, tag_name)) { + Local result; + if (GetPredefinedToString(tag_name, &result)) return result; + + class_name = tag_name; + } + } + } const char* prefix = "[object "; Local str = Utils::ToLocal(class_name); const char* postfix = "]"; @@ -6245,6 +6298,11 @@ Local v8::Symbol::GetUnscopables(Isolate* isolate) { } +Local v8::Symbol::GetToStringTag(Isolate* isolate) { + return GetWellKnownSymbol(isolate, "Symbol.toStringTag"); +} + + Local v8::Private::New(Isolate* isolate, Local name) { i::Isolate* i_isolate = reinterpret_cast(isolate); LOG_API(i_isolate, "Private::New()"); diff --git a/src/array.js b/src/array.js index 35fd545..ca3e7be 100644 --- a/src/array.js +++ b/src/array.js @@ -349,7 +349,7 @@ function ArrayToString() { func = array.join; } if (!IS_SPEC_FUNCTION(func)) { - return %_CallFunction(array, ObjectToString); + return %_CallFunction(array, NoSideEffectsObjectToString); } return %_CallFunction(array, func); } diff --git a/src/arraybuffer.js b/src/arraybuffer.js index e1c887f..cf00693 100644 --- a/src/arraybuffer.js +++ b/src/arraybuffer.js @@ -77,6 +77,9 @@ function SetUpArrayBuffer() { %AddNamedProperty( $ArrayBuffer.prototype, "constructor", $ArrayBuffer, DONT_ENUM); + %AddNamedProperty($ArrayBuffer.prototype, + symbolToStringTag, "ArrayBuffer", DONT_ENUM | READ_ONLY); + InstallGetter($ArrayBuffer.prototype, "byteLength", ArrayBufferGetByteLen); InstallFunctions($ArrayBuffer, DONT_ENUM, $Array( diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index 029dc52..8fd762c 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -1600,6 +1600,7 @@ EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_object_literals) EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_regexps) EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_arrow_functions) EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_numeric_literals) +EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_tostring) void Genesis::InstallNativeFunctions_harmony_proxies() { @@ -1624,6 +1625,7 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_classes) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_object_literals) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_arrow_functions) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_numeric_literals) +EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_tostring) void Genesis::InitializeGlobal_harmony_regexps() { Handle builtins(native_context()->builtins()); @@ -2164,6 +2166,8 @@ bool Genesis::InstallExperimentalNatives() { static const char* harmony_regexps_natives[] = {NULL}; static const char* harmony_arrow_functions_natives[] = {NULL}; static const char* harmony_numeric_literals_natives[] = {NULL}; + static const char* harmony_tostring_natives[] = {"native harmony-tostring.js", + NULL}; for (int i = ExperimentalNatives::GetDebuggerCount(); i < ExperimentalNatives::GetBuiltinsCount(); i++) { diff --git a/src/collection.js b/src/collection.js index 7d185d9..6a32d69 100644 --- a/src/collection.js +++ b/src/collection.js @@ -133,6 +133,8 @@ function SetUpSet() { %SetCode($Set, SetConstructor); %FunctionSetPrototype($Set, new $Object()); %AddNamedProperty($Set.prototype, "constructor", $Set, DONT_ENUM); + %AddNamedProperty( + $Set.prototype, symbolToStringTag, "Set", DONT_ENUM | READ_ONLY); %FunctionSetLength(SetForEach, 1); @@ -282,6 +284,8 @@ function SetUpMap() { %SetCode($Map, MapConstructor); %FunctionSetPrototype($Map, new $Object()); %AddNamedProperty($Map.prototype, "constructor", $Map, DONT_ENUM); + %AddNamedProperty( + $Map.prototype, symbolToStringTag, "Map", DONT_ENUM | READ_ONLY); %FunctionSetLength(MapForEach, 1); diff --git a/src/flag-definitions.h b/src/flag-definitions.h index 4707635..f5fdb8e 100644 --- a/src/flag-definitions.h +++ b/src/flag-definitions.h @@ -162,7 +162,8 @@ DEFINE_IMPLICATION(harmony, es_staging) V(harmony_classes, "harmony classes") \ V(harmony_object_literals, "harmony object literal extensions") \ V(harmony_regexps, "reg-exp related harmony features") \ - V(harmony_arrow_functions, "harmony arrow functions") + V(harmony_arrow_functions, "harmony arrow functions") \ + V(harmony_tostring, "harmony Symbol.toStringTag") #define STAGED_FEATURES(V) \ V(harmony_numeric_literals, "harmony numeric literals (0o77, 0b11)") diff --git a/src/generator.js b/src/generator.js index 72e64dc..b35744a 100644 --- a/src/generator.js +++ b/src/generator.js @@ -74,6 +74,8 @@ function SetUpGenerators() { GeneratorObjectIterator, DONT_ENUM | DONT_DELETE | READ_ONLY); %AddNamedProperty(GeneratorObjectPrototype, "constructor", GeneratorFunctionPrototype, DONT_ENUM | DONT_DELETE | READ_ONLY); + %AddNamedProperty(GeneratorObjectPrototype, + symbolToStringTag, "Generator", DONT_ENUM | READ_ONLY); %InternalSetPrototype(GeneratorFunctionPrototype, $Function.prototype); %SetCode(GeneratorFunctionPrototype, GeneratorFunctionPrototypeConstructor); %AddNamedProperty(GeneratorFunctionPrototype, "constructor", diff --git a/src/harmony-tostring.js b/src/harmony-tostring.js new file mode 100644 index 0000000..282b3a0 --- /dev/null +++ b/src/harmony-tostring.js @@ -0,0 +1,66 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +// This file relies on the fact that the following declaration has been made +// in runtime.js and symbol.js: +// var $Object = global.Object; +// var $Symbol = global.Symbol; + +var symbolToStringTag = InternalSymbol("Symbol.toStringTag"); + +var kBuiltinStringTags = { + "__proto__": null, + "Arguments": true, + "Array": true, + "Boolean": true, + "Date": true, + "Error": true, + "Function": true, + "Number": true, + "RegExp": true, + "String": true +}; + +DefaultObjectToString = ObjectToStringHarmony; +// ES6 draft 08-24-14, section 19.1.3.6 +function ObjectToStringHarmony() { + if (IS_UNDEFINED(this) && !IS_UNDETECTABLE(this)) return "[object Undefined]"; + if (IS_NULL(this)) return "[object Null]"; + var O = ToObject(this); + var builtinTag = %_ClassOf(O); + var tag = O[symbolToStringTag]; + if (IS_UNDEFINED(tag)) { + tag = builtinTag; + } else if (!IS_STRING(tag)) { + return "[object ???]" + } else if (tag !== builtinTag && kBuiltinStringTags[tag]) { + return "[object ~" + tag + "]"; + } + return "[object " + tag + "]"; +} + +function HarmonyToStringExtendSymbolPrototype() { + %CheckIsBootstrapping(); + + InstallConstants($Symbol, $Array( + // TODO(dslomov, caitp): Move to symbol.js when shipping + "toStringTag", symbolToStringTag + )); +} + +HarmonyToStringExtendSymbolPrototype(); + +function HarmonyToStringExtendObjectPrototype() { + %CheckIsBootstrapping(); + + // Set up the non-enumerable functions on the Array prototype object. + var desc = ToPropertyDescriptor({ + value: ObjectToStringHarmony + }); + DefineOwnProperty($Object.prototype, "toString", desc, false); +} + +HarmonyToStringExtendObjectPrototype(); diff --git a/src/heap/heap.h b/src/heap/heap.h index e844b3a..695ad8a 100644 --- a/src/heap/heap.h +++ b/src/heap/heap.h @@ -325,7 +325,10 @@ namespace internal { V(next_string, "next") \ V(byte_length_string, "byteLength") \ V(byte_offset_string, "byteOffset") \ - V(minus_zero_string, "-0") + V(minus_zero_string, "-0") \ + V(Array_string, "Array") \ + V(Error_string, "Error") \ + V(RegExp_string, "RegExp") #define PRIVATE_SYMBOL_LIST(V) \ V(frozen_symbol) \ diff --git a/src/messages.js b/src/messages.js index 2dddffa..6578e8d 100644 --- a/src/messages.js +++ b/src/messages.js @@ -223,7 +223,8 @@ function NoSideEffectToString(obj) { return str; } if (IS_SYMBOL(obj)) return %_CallFunction(obj, SymbolToString); - if (IS_OBJECT(obj) && %GetDataProperty(obj, "toString") === ObjectToString) { + if (IS_OBJECT(obj) + && %GetDataProperty(obj, "toString") === DefaultObjectToString) { var constructor = %GetDataProperty(obj, "constructor"); if (typeof constructor == "function") { var constructorName = constructor.name; @@ -235,7 +236,8 @@ function NoSideEffectToString(obj) { if (CanBeSafelyTreatedAsAnErrorObject(obj)) { return %_CallFunction(obj, ErrorToString); } - return %_CallFunction(obj, ObjectToString); + + return %_CallFunction(obj, NoSideEffectsObjectToString); } // To determine whether we can safely stringify an object using ErrorToString @@ -274,7 +276,7 @@ function ToStringCheckErrorObject(obj) { function ToDetailString(obj) { - if (obj != null && IS_OBJECT(obj) && obj.toString === ObjectToString) { + if (obj != null && IS_OBJECT(obj) && obj.toString === DefaultObjectToString) { var constructor = obj.constructor; if (typeof constructor == "function") { var constructorName = constructor.name; @@ -1105,12 +1107,12 @@ function GetTypeName(receiver, requireConstructor) { var constructor = receiver.constructor; if (!constructor) { return requireConstructor ? null : - %_CallFunction(receiver, ObjectToString); + %_CallFunction(receiver, NoSideEffectsObjectToString); } var constructorName = constructor.name; if (!constructorName) { return requireConstructor ? null : - %_CallFunction(receiver, ObjectToString); + %_CallFunction(receiver, NoSideEffectsObjectToString); } return constructorName; } diff --git a/src/promise.js b/src/promise.js index e402a18..443a3b8 100644 --- a/src/promise.js +++ b/src/promise.js @@ -365,6 +365,8 @@ var lastMicrotaskId = 0; %CheckIsBootstrapping(); %AddNamedProperty(global, 'Promise', $Promise, DONT_ENUM); + %AddNamedProperty( + $Promise.prototype, symbolToStringTag, "Promise", DONT_ENUM | READ_ONLY); InstallFunctions($Promise, DONT_ENUM, [ "defer", PromiseDeferred, "accept", PromiseResolved, diff --git a/src/symbol.js b/src/symbol.js index e009790..d6ac527 100644 --- a/src/symbol.js +++ b/src/symbol.js @@ -101,6 +101,8 @@ function SetUpSymbol() { // "isConcatSpreadable", symbolIsConcatSpreadable, // "isRegExp", symbolIsRegExp, "iterator", symbolIterator, + // TODO(dslomov, caitp): Currently defined in harmony-tostring.js --- + // Move here when shipping // "toStringTag", symbolToStringTag, "unscopables", symbolUnscopables )); @@ -110,6 +112,8 @@ function SetUpSymbol() { )); %AddNamedProperty($Symbol.prototype, "constructor", $Symbol, DONT_ENUM); + %AddNamedProperty( + $Symbol.prototype, symbolToStringTag, "Symbol", DONT_ENUM | READ_ONLY); InstallFunctions($Symbol.prototype, DONT_ENUM, $Array( "toString", SymbolToString, "valueOf", SymbolValueOf diff --git a/src/v8natives.js b/src/v8natives.js index 92f377c..7636b70 100644 --- a/src/v8natives.js +++ b/src/v8natives.js @@ -215,8 +215,9 @@ SetUpGlobal(); // ---------------------------------------------------------------------------- // Object +var DefaultObjectToString = NoSideEffectsObjectToString; // ECMA-262 - 15.2.4.2 -function ObjectToString() { +function NoSideEffectsObjectToString() { if (IS_UNDEFINED(this) && !IS_UNDETECTABLE(this)) return "[object Undefined]"; if (IS_NULL(this)) return "[object Null]"; return "[object " + %_ClassOf(ToObject(this)) + "]"; @@ -1409,7 +1410,7 @@ function SetUpObject() { // Set up non-enumerable functions on the Object.prototype object. InstallFunctions($Object.prototype, DONT_ENUM, $Array( - "toString", ObjectToString, + "toString", NoSideEffectsObjectToString, "toLocaleString", ObjectToLocaleString, "valueOf", ObjectValueOf, "hasOwnProperty", ObjectHasOwnProperty, diff --git a/src/weak-collection.js b/src/weak-collection.js index 1160176..273060a 100644 --- a/src/weak-collection.js +++ b/src/weak-collection.js @@ -114,6 +114,8 @@ function SetUpWeakMap() { %SetCode($WeakMap, WeakMapConstructor); %FunctionSetPrototype($WeakMap, new $Object()); %AddNamedProperty($WeakMap.prototype, "constructor", $WeakMap, DONT_ENUM); + %AddNamedProperty( + $WeakMap.prototype, symbolToStringTag, "WeakMap", DONT_ENUM | READ_ONLY); // Set up the non-enumerable functions on the WeakMap prototype object. InstallFunctions($WeakMap.prototype, DONT_ENUM, $Array( @@ -214,6 +216,8 @@ function SetUpWeakSet() { %SetCode($WeakSet, WeakSetConstructor); %FunctionSetPrototype($WeakSet, new $Object()); %AddNamedProperty($WeakSet.prototype, "constructor", $WeakSet, DONT_ENUM); + %AddNamedProperty( + $WeakSet.prototype, symbolToStringTag, "WeakSet", DONT_ENUM | READ_ONLY); // Set up the non-enumerable functions on the WeakSet prototype object. InstallFunctions($WeakSet.prototype, DONT_ENUM, $Array( diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc index 35a7156..2e8d4ae 100644 --- a/test/cctest/test-api.cc +++ b/test/cctest/test-api.cc @@ -2021,6 +2021,19 @@ void SymbolAccessorSetter(Local name, Local value, SimpleAccessorSetter(Local::Cast(sym->Name()), value, info); } +void SymbolAccessorGetterReturnsDefault( + Local name, const v8::PropertyCallbackInfo& info) { + CHECK(name->IsSymbol()); + Local sym = Local::Cast(name); + if (sym->Name()->IsUndefined()) return; + info.GetReturnValue().Set(info.Data()); +} + +static void ThrowingSymbolAccessorGetter( + Local name, const v8::PropertyCallbackInfo& info) { + info.GetReturnValue().Set(info.GetIsolate()->ThrowException(name)); +} + void EmptyInterceptorGetter(Local name, const v8::PropertyCallbackInfo& info) { } @@ -13492,6 +13505,123 @@ THREADED_TEST(ObjectProtoToString) { } +TEST(ObjectProtoToStringES6) { + // TODO(dslomov, caitp): merge into ObjectProtoToString test once shipped. + i::FLAG_harmony_tostring = true; + LocalContext context; + v8::Isolate* isolate = CcTest::isolate(); + v8::HandleScope scope(isolate); + Local templ = v8::FunctionTemplate::New(isolate); + templ->SetClassName(v8_str("MyClass")); + + Local customized_tostring = v8_str("customized toString"); + + // Replace Object.prototype.toString + CompileRun( + "Object.prototype.toString = function() {" + " return 'customized toString';" + "}"); + + // Normal ToString call should call replaced Object.prototype.toString + Local instance = templ->GetFunction()->NewInstance(); + Local value = instance->ToString(); + CHECK(value->IsString() && value->Equals(customized_tostring)); + + // ObjectProtoToString should not call replace toString function. + value = instance->ObjectProtoToString(); + CHECK(value->IsString() && value->Equals(v8_str("[object MyClass]"))); + + // Check global + value = context->Global()->ObjectProtoToString(); + CHECK(value->IsString() && value->Equals(v8_str("[object global]"))); + + // Check ordinary object + Local object = CompileRun("new Object()"); + value = object.As()->ObjectProtoToString(); + CHECK(value->IsString() && value->Equals(v8_str("[object Object]"))); + + // Check that ES6 semantics using @@toStringTag work + Local toStringTag = v8::Symbol::GetToStringTag(isolate); + +#define TEST_TOSTRINGTAG(type, tag, expected) \ + do { \ + object = CompileRun("new " #type "()"); \ + object.As()->Set(toStringTag, v8_str(#tag)); \ + value = object.As()->ObjectProtoToString(); \ + CHECK(value->IsString() && \ + value->Equals(v8_str("[object " #expected "]"))); \ + } while (0) + + TEST_TOSTRINGTAG(Array, Object, Object); + TEST_TOSTRINGTAG(Object, Arguments, ~Arguments); + TEST_TOSTRINGTAG(Object, Array, ~Array); + TEST_TOSTRINGTAG(Object, Boolean, ~Boolean); + TEST_TOSTRINGTAG(Object, Date, ~Date); + TEST_TOSTRINGTAG(Object, Error, ~Error); + TEST_TOSTRINGTAG(Object, Function, ~Function); + TEST_TOSTRINGTAG(Object, Number, ~Number); + TEST_TOSTRINGTAG(Object, RegExp, ~RegExp); + TEST_TOSTRINGTAG(Object, String, ~String); + TEST_TOSTRINGTAG(Object, Foo, Foo); + +#undef TEST_TOSTRINGTAG + + // @@toStringTag getter throws + Local obj = v8::Object::New(isolate); + obj.As()->SetAccessor(toStringTag, ThrowingSymbolAccessorGetter); + { + TryCatch try_catch; + value = obj.As()->ObjectProtoToString(); + CHECK(value.IsEmpty()); + CHECK(try_catch.HasCaught()); + } + + // @@toStringTag getter does not throw + obj = v8::Object::New(isolate); + obj.As()->SetAccessor( + toStringTag, SymbolAccessorGetterReturnsDefault, 0, v8_str("Test")); + { + TryCatch try_catch; + value = obj.As()->ObjectProtoToString(); + CHECK(value->IsString() && value->Equals(v8_str("[object Test]"))); + CHECK(!try_catch.HasCaught()); + } + + // JS @@toStringTag value + obj = CompileRun("obj = {}; obj[Symbol.toStringTag] = 'Test'; obj"); + { + TryCatch try_catch; + value = obj.As()->ObjectProtoToString(); + CHECK(value->IsString() && value->Equals(v8_str("[object Test]"))); + CHECK(!try_catch.HasCaught()); + } + + // JS @@toStringTag getter throws + obj = CompileRun( + "obj = {}; Object.defineProperty(obj, Symbol.toStringTag, {" + " get: function() { throw 'Test'; }" + "}); obj"); + { + TryCatch try_catch; + value = obj.As()->ObjectProtoToString(); + CHECK(value.IsEmpty()); + CHECK(try_catch.HasCaught()); + } + + // JS @@toStringTag getter does not throw + obj = CompileRun( + "obj = {}; Object.defineProperty(obj, Symbol.toStringTag, {" + " get: function() { return 'Test'; }" + "}); obj"); + { + TryCatch try_catch; + value = obj.As()->ObjectProtoToString(); + CHECK(value->IsString() && value->Equals(v8_str("[object Test]"))); + CHECK(!try_catch.HasCaught()); + } +} + + THREADED_TEST(ObjectGetConstructorName) { LocalContext context; v8::HandleScope scope(context->GetIsolate()); diff --git a/test/mjsunit/es6/collections.js b/test/mjsunit/es6/collections.js index 94b2aea..60ce46b 100644 --- a/test/mjsunit/es6/collections.js +++ b/test/mjsunit/es6/collections.js @@ -25,7 +25,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// Flags: --expose-gc --allow-natives-syntax +// Flags: --expose-gc --allow-natives-syntax --harmony-tostring function assertSize(expected, collection) { @@ -300,7 +300,7 @@ assertEquals("WeakSet", WeakSet.name); function TestPrototype(C) { assertTrue(C.prototype instanceof Object); assertEquals({ - value: {}, + value: C.prototype, writable: false, enumerable: false, configurable: false @@ -1423,3 +1423,12 @@ function TestMapConstructorIterableValue(ctor) { } TestMapConstructorIterableValue(Map); TestMapConstructorIterableValue(WeakMap); + +function TestCollectionToString(C) { + assertEquals("[object " + C.name + "]", + Object.prototype.toString.call(new C())); +} +TestCollectionToString(Map); +TestCollectionToString(Set); +TestCollectionToString(WeakMap); +TestCollectionToString(WeakSet); diff --git a/test/mjsunit/es6/generators-objects.js b/test/mjsunit/es6/generators-objects.js index 8a052ff..25bd0de 100644 --- a/test/mjsunit/es6/generators-objects.js +++ b/test/mjsunit/es6/generators-objects.js @@ -25,7 +25,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// Flags: --harmony-scoping --allow-natives-syntax +// Flags: --harmony-scoping --allow-natives-syntax --harmony-tostring // Test instantations of generators. @@ -66,6 +66,7 @@ function TestGeneratorObject() { assertTrue(iter instanceof g); assertEquals("Generator", %_ClassOf(iter)); assertEquals("[object Generator]", String(iter)); + assertEquals("[object Generator]", Object.prototype.toString.call(iter)); assertEquals([], Object.getOwnPropertyNames(iter)); assertTrue(iter !== new g()); } diff --git a/test/mjsunit/es6/object-tostring.js b/test/mjsunit/es6/object-tostring.js new file mode 100644 index 0000000..26dff14 --- /dev/null +++ b/test/mjsunit/es6/object-tostring.js @@ -0,0 +1,133 @@ +// Copyright 2014 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --harmony-tostring + +var global = this; + +var funs = { + Object: [ Object ], + Function: [ Function ], + Array: [ Array ], + String: [ String ], + Boolean: [ Boolean ], + Number: [ Number ], + Date: [ Date ], + RegExp: [ RegExp ], + Error: [ Error, TypeError, RangeError, SyntaxError, ReferenceError, + EvalError, URIError ] +} +for (f in funs) { + for (i in funs[f]) { + assertEquals("[object " + f + "]", + Object.prototype.toString.call(new funs[f][i]), + funs[f][i]); + assertEquals("[object Function]", + Object.prototype.toString.call(funs[f][i]), + funs[f][i]); + } +} + +function testToStringTag(className) { + // Using builtin toStringTags + var obj = {}; + obj[Symbol.toStringTag] = className; + assertEquals("[object ~" + className + "]", + Object.prototype.toString.call(obj)); + + // Getter throws + obj = {}; + Object.defineProperty(obj, Symbol.toStringTag, { + get: function() { throw className; } + }); + assertThrows(function() { + Object.prototype.toString.call(obj); + }, className); + + // Getter does not throw + obj = {}; + Object.defineProperty(obj, Symbol.toStringTag, { + get: function() { return className; } + }); + assertEquals("[object ~" + className + "]", + Object.prototype.toString.call(obj)); + + // Custom, non-builtin toStringTags + obj = {}; + obj[Symbol.toStringTag] = "X" + className; + assertEquals("[object X" + className + "]", + Object.prototype.toString.call(obj)); + + // With getter + obj = {}; + Object.defineProperty(obj, Symbol.toStringTag, { + get: function() { return "X" + className; } + }); + assertEquals("[object X" + className + "]", + Object.prototype.toString.call(obj)); + + // Undefined toStringTag should return [object className] + var obj = className === "Arguments" ? + (function() { return arguments; })() : new global[className]; + obj[Symbol.toStringTag] = undefined; + assertEquals("[object " + className + "]", + Object.prototype.toString.call(obj)); + + // With getter + var obj = className === "Arguments" ? + (function() { return arguments; })() : new global[className]; + Object.defineProperty(obj, Symbol.toStringTag, { + get: function() { return undefined; } + }); + assertEquals("[object " + className + "]", + Object.prototype.toString.call(obj)); +} + +[ + "Arguments", + "Array", + "Boolean", + "Date", + "Error", + "Function", + "Number", + "RegExp", + "String" +].forEach(testToStringTag); + +function testToStringTagNonString(value) { + var obj = {}; + obj[Symbol.toStringTag] = value; + assertEquals("[object ???]", Object.prototype.toString.call(obj)); + + // With getter + obj = {}; + Object.defineProperty(obj, Symbol.toStringTag, { + get: function() { return value; } + }); + assertEquals("[object ???]", Object.prototype.toString.call(obj)); +} + +[ + null, + function() {}, + [], + {}, + /regexp/, + 42, + Symbol("sym"), + new Date(), + (function() { return arguments; })(), + true, + new Error("oops"), + new String("str") +].forEach(testToStringTagNonString); + +function testObjectToStringPropertyDesc() { + var desc = Object.getOwnPropertyDescriptor(Object.prototype, "toString"); + assertTrue(desc.writable); + assertFalse(desc.enumerable); + assertTrue(desc.configurable); +} +testObjectToStringPropertyDesc(); diff --git a/test/mjsunit/es6/promises.js b/test/mjsunit/es6/promises.js index faf154e..04059aa 100644 --- a/test/mjsunit/es6/promises.js +++ b/test/mjsunit/es6/promises.js @@ -25,13 +25,21 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// Flags: --allow-natives-syntax +// Flags: --allow-natives-syntax --harmony-tostring // Make sure we don't rely on functions patchable by monkeys. var call = Function.prototype.call.call.bind(Function.prototype.call) var observe = Object.observe; -var getOwnPropertyNames = Object.getOwnPropertyNames -var defineProperty = Object.defineProperty +var getOwnPropertyNames = Object.getOwnPropertyNames; +var defineProperty = Object.defineProperty; + + +(function() { + // Test before clearing global (fails otherwise) + assertEquals("[object Promise]", + Object.prototype.toString.call(new Promise(function() {}))); +})(); + function clear(o) { if (o === null || (typeof o !== 'object' && typeof o !== 'function')) return diff --git a/test/mjsunit/es6/symbols.js b/test/mjsunit/es6/symbols.js index d4a968a..b9811f5 100644 --- a/test/mjsunit/es6/symbols.js +++ b/test/mjsunit/es6/symbols.js @@ -25,7 +25,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// Flags: --expose-gc --allow-natives-syntax +// Flags: --expose-gc --allow-natives-syntax --harmony-tostring var symbols = [] diff --git a/test/mjsunit/harmony/typedarrays.js b/test/mjsunit/harmony/typedarrays.js index a2f3dcc..537fba3 100644 --- a/test/mjsunit/harmony/typedarrays.js +++ b/test/mjsunit/harmony/typedarrays.js @@ -25,6 +25,8 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// Flags: --harmony-tostring + // ArrayBuffer function TestByteLength(param, expectedByteLength) { @@ -52,6 +54,8 @@ function TestArrayBufferCreation() { var ab = new ArrayBuffer(); assertSame(0, ab.byteLength); + assertEquals("[object ArrayBuffer]", + Object.prototype.toString.call(ab)); } TestArrayBufferCreation(); @@ -123,6 +127,9 @@ function TestTypedArray(constr, elementSize, typicalElement) { var ab = new ArrayBuffer(256*elementSize); var a0 = new constr(30); + assertEquals("[object " + constr.name + "]", + Object.prototype.toString.call(a0)); + assertTrue(ArrayBuffer.isView(a0)); assertSame(elementSize, a0.BYTES_PER_ELEMENT); assertSame(30, a0.length); diff --git a/tools/gyp/v8.gyp b/tools/gyp/v8.gyp index e62ad07..9bf3a95 100644 --- a/tools/gyp/v8.gyp +++ b/tools/gyp/v8.gyp @@ -1608,6 +1608,7 @@ '../../src/generator.js', '../../src/harmony-string.js', '../../src/harmony-array.js', + '../../src/harmony-tostring.js', '../../src/harmony-typedarray.js', '../../src/harmony-classes.js', ], -- 2.7.4