[ES6] Add support for Symbol.toPrimitive
authorkeith_miller@apple.com <keith_miller@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 4 Mar 2016 00:47:55 +0000 (00:47 +0000)
committerkeith_miller@apple.com <keith_miller@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 4 Mar 2016 00:47:55 +0000 (00:47 +0000)
https://bugs.webkit.org/show_bug.cgi?id=154877

Reviewed by Saam Barati.

Source/JavaScriptCore:

This patch adds suport for Symbol.toPrimitive. Since we don't currently
generate snippits for one side of a binary operation we only need to change
the JSObject::ToPrimitive function and update some optimizations in the DFG
that need to know how conversions to primitive values should work. As of
ES6, the date prototype is also no longer special cased in the ToPrimitive
operation. Instead, Date.prototype has a Symbol.species function that
replicates the old behavior.

* bytecode/ObjectPropertyConditionSet.cpp:
(JSC::generateConditionsForPropertyMissConcurrently):
* bytecode/ObjectPropertyConditionSet.h:
* dfg/DFGGraph.cpp:
(JSC::DFG::Graph::watchConditions):
(JSC::DFG::Graph::canOptimizeStringObjectAccess):
* dfg/DFGGraph.h:
* runtime/CommonIdentifiers.h:
* runtime/DatePrototype.cpp:
(JSC::DatePrototype::finishCreation):
(JSC::dateProtoFuncToPrimitiveSymbol):
* runtime/Error.cpp:
(JSC::throwTypeError):
* runtime/Error.h:
* runtime/JSCJSValueInlines.h:
(JSC::toPreferredPrimitiveType):
* runtime/JSObject.cpp:
(JSC::callToPrimitiveFunction):
(JSC::JSObject::ordinaryToPrimitive):
(JSC::JSObject::defaultValue):
(JSC::JSObject::toPrimitive):
(JSC::JSObject::getPrimitiveNumber):
(JSC::callDefaultValueFunction): Deleted.
(JSC::throwTypeError): Deleted.
* runtime/JSObject.h:
(JSC::JSObject::toPrimitive): Deleted.
* runtime/SmallStrings.h:
* runtime/SymbolPrototype.cpp:
(JSC::SymbolPrototype::finishCreation):
* runtime/SymbolPrototype.h:
(JSC::SymbolPrototype::create):
* tests/es6.yaml:
* tests/stress/date-symbol-toprimitive.js: Added.
* tests/stress/ropes-symbol-toprimitive.js: Added.
(ropify):
(String.prototype.Symbol.toPrimitive):
* tests/stress/symbol-toprimitive.js: Added.
(foo.Symbol.toPrimitive):
(catch):

LayoutTests:

Update test for Symbol.toPrimitive.

* js/Object-getOwnPropertyNames-expected.txt:
* js/script-tests/Object-getOwnPropertyNames.js:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@197531 268f45cc-cd09-0410-ab3c-d52691b4dbfc

25 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/security/cross-frame-access-custom-expected.txt
LayoutTests/http/tests/security/cross-frame-access-location-get-expected.txt
LayoutTests/http/tests/security/cross-frame-access-object-setPrototypeOf-expected.txt
LayoutTests/js/Object-getOwnPropertyNames-expected.txt
LayoutTests/js/script-tests/Object-getOwnPropertyNames.js
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.cpp
Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.h
Source/JavaScriptCore/dfg/DFGGraph.cpp
Source/JavaScriptCore/dfg/DFGGraph.h
Source/JavaScriptCore/runtime/CommonIdentifiers.h
Source/JavaScriptCore/runtime/DatePrototype.cpp
Source/JavaScriptCore/runtime/Error.cpp
Source/JavaScriptCore/runtime/Error.h
Source/JavaScriptCore/runtime/JSCJSValueInlines.h
Source/JavaScriptCore/runtime/JSObject.cpp
Source/JavaScriptCore/runtime/JSObject.h
Source/JavaScriptCore/runtime/SmallStrings.h
Source/JavaScriptCore/runtime/SymbolPrototype.cpp
Source/JavaScriptCore/runtime/SymbolPrototype.h
Source/JavaScriptCore/tests/es6.yaml
Source/JavaScriptCore/tests/stress/date-symbol-toprimitive.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/ropes-symbol-toprimitive.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/symbol-toprimitive.js [new file with mode: 0644]

index 69bcdea..afba173 100644 (file)
@@ -1,3 +1,15 @@
+2016-03-03  Keith Miller  <keith_miller@apple.com>
+
+        [ES6] Add support for Symbol.toPrimitive
+        https://bugs.webkit.org/show_bug.cgi?id=154877
+
+        Reviewed by Saam Barati.
+
+        Update test for Symbol.toPrimitive.
+
+        * js/Object-getOwnPropertyNames-expected.txt:
+        * js/script-tests/Object-getOwnPropertyNames.js:
+
 2016-03-02  Ryosuke Niwa  <rniwa@webkit.org>
 
         Disallow custom elements inside template elements and share the registry for windowless documents
index 63889a7..b5e055e 100644 (file)
@@ -17,6 +17,7 @@ CONSOLE MESSAGE: line 81: Blocked a frame with origin "http://127.0.0.1:8000" fr
 CONSOLE MESSAGE: line 82: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
 CONSOLE MESSAGE: line 81: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
 CONSOLE MESSAGE: line 107: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
+CONSOLE MESSAGE: line 107: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
 
 
 ----- tests for getting/setting interesting properties -----
index f2447c8..a886c4e 100644 (file)
@@ -1,5 +1,6 @@
 CONSOLE MESSAGE: line 107: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
 CONSOLE MESSAGE: line 107: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
+CONSOLE MESSAGE: line 107: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
 CONSOLE MESSAGE: line 55: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
 CONSOLE MESSAGE: line 55: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
 CONSOLE MESSAGE: line 55: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
index 0bc062f..dc8e280 100644 (file)
@@ -1,5 +1,6 @@
 CONSOLE MESSAGE: line 1: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
 CONSOLE MESSAGE: line 22: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
+CONSOLE MESSAGE: line 22: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
 CONSOLE MESSAGE: line 25: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
 This tests that you can't set the prototype of the window or history objects cross-origin using Object.setPrototypeOf().
 
index 5b9b8a1..fd7f682 100644 (file)
@@ -61,7 +61,7 @@ PASS getSortedOwnPropertyNames(Error) is ['length', 'name', 'prototype']
 PASS getSortedOwnPropertyNames(Error.prototype) is ['constructor', 'message', 'name', 'toString']
 PASS getSortedOwnPropertyNames(Math) is ['E','LN10','LN2','LOG10E','LOG2E','PI','SQRT1_2','SQRT2','abs','acos','acosh','asin','asinh','atan','atan2','atanh','cbrt','ceil','clz32','cos','cosh','exp','expm1','floor','fround','hypot','imul','log','log10','log1p','log2','max','min','pow','random','round','sign','sin','sinh','sqrt','tan','tanh','trunc']
 PASS getSortedOwnPropertyNames(JSON) is ['parse', 'stringify']
-PASS getSortedOwnPropertyNames(Symbol) is ['for', 'hasInstance', 'iterator', 'keyFor', 'length', 'name', 'prototype', 'search', 'species', 'toStringTag', 'unscopables']
+PASS getSortedOwnPropertyNames(Symbol) is ['for', 'hasInstance', 'iterator', 'keyFor', 'length', 'name', 'prototype', 'search', 'species', 'toPrimitive', 'toStringTag', 'unscopables']
 PASS getSortedOwnPropertyNames(Symbol.prototype) is ['constructor', 'toString', 'valueOf']
 PASS getSortedOwnPropertyNames(Map) is ['length', 'name', 'prototype']
 PASS getSortedOwnPropertyNames(Map.prototype) is ['clear', 'constructor', 'delete', 'entries', 'forEach', 'get', 'has', 'keys', 'set', 'size', 'values']
index 924ab05..f45d01a 100644 (file)
@@ -70,7 +70,7 @@ var expectedPropertyNamesSet = {
     "Error.prototype": "['constructor', 'message', 'name', 'toString']",
     "Math": "['E','LN10','LN2','LOG10E','LOG2E','PI','SQRT1_2','SQRT2','abs','acos','acosh','asin','asinh','atan','atan2','atanh','cbrt','ceil','clz32','cos','cosh','exp','expm1','floor','fround','hypot','imul','log','log10','log1p','log2','max','min','pow','random','round','sign','sin','sinh','sqrt','tan','tanh','trunc']",
     "JSON": "['parse', 'stringify']",
-    "Symbol": "['for', 'hasInstance', 'iterator', 'keyFor', 'length', 'name', 'prototype', 'search', 'species', 'toStringTag', 'unscopables']",
+    "Symbol": "['for', 'hasInstance', 'iterator', 'keyFor', 'length', 'name', 'prototype', 'search', 'species', 'toPrimitive', 'toStringTag', 'unscopables']",
     "Symbol.prototype": "['constructor', 'toString', 'valueOf']",
     "Map": "['length', 'name', 'prototype']",
     "Map.prototype": "['clear', 'constructor', 'delete', 'entries', 'forEach', 'get', 'has', 'keys', 'set', 'size', 'values']",
index 697e933..772d608 100644 (file)
@@ -1,3 +1,58 @@
+2016-03-03  Keith Miller  <keith_miller@apple.com>
+
+        [ES6] Add support for Symbol.toPrimitive
+        https://bugs.webkit.org/show_bug.cgi?id=154877
+
+        Reviewed by Saam Barati.
+
+        This patch adds suport for Symbol.toPrimitive. Since we don't currently
+        generate snippits for one side of a binary operation we only need to change
+        the JSObject::ToPrimitive function and update some optimizations in the DFG
+        that need to know how conversions to primitive values should work. As of
+        ES6, the date prototype is also no longer special cased in the ToPrimitive
+        operation. Instead, Date.prototype has a Symbol.species function that
+        replicates the old behavior.
+
+        * bytecode/ObjectPropertyConditionSet.cpp:
+        (JSC::generateConditionsForPropertyMissConcurrently):
+        * bytecode/ObjectPropertyConditionSet.h:
+        * dfg/DFGGraph.cpp:
+        (JSC::DFG::Graph::watchConditions):
+        (JSC::DFG::Graph::canOptimizeStringObjectAccess):
+        * dfg/DFGGraph.h:
+        * runtime/CommonIdentifiers.h:
+        * runtime/DatePrototype.cpp:
+        (JSC::DatePrototype::finishCreation):
+        (JSC::dateProtoFuncToPrimitiveSymbol):
+        * runtime/Error.cpp:
+        (JSC::throwTypeError):
+        * runtime/Error.h:
+        * runtime/JSCJSValueInlines.h:
+        (JSC::toPreferredPrimitiveType):
+        * runtime/JSObject.cpp:
+        (JSC::callToPrimitiveFunction):
+        (JSC::JSObject::ordinaryToPrimitive):
+        (JSC::JSObject::defaultValue):
+        (JSC::JSObject::toPrimitive):
+        (JSC::JSObject::getPrimitiveNumber):
+        (JSC::callDefaultValueFunction): Deleted.
+        (JSC::throwTypeError): Deleted.
+        * runtime/JSObject.h:
+        (JSC::JSObject::toPrimitive): Deleted.
+        * runtime/SmallStrings.h:
+        * runtime/SymbolPrototype.cpp:
+        (JSC::SymbolPrototype::finishCreation):
+        * runtime/SymbolPrototype.h:
+        (JSC::SymbolPrototype::create):
+        * tests/es6.yaml:
+        * tests/stress/date-symbol-toprimitive.js: Added.
+        * tests/stress/ropes-symbol-toprimitive.js: Added.
+        (ropify):
+        (String.prototype.Symbol.toPrimitive):
+        * tests/stress/symbol-toprimitive.js: Added.
+        (foo.Symbol.toPrimitive):
+        (catch):
+
 2016-03-03  Filip Pizlo  <fpizlo@apple.com>
 
         DFG should be able to compile StringReplace
index 1b92412..d570040 100644 (file)
@@ -349,6 +349,20 @@ ObjectPropertyConditionSet generateConditionsForPrototypePropertyHitCustom(
         });
 }
 
+ObjectPropertyConditionSet generateConditionsForPropertyMissConcurrently(
+    VM& vm, JSGlobalObject* globalObject, Structure* headStructure, UniquedStringImpl* uid)
+{
+    return generateConditions(
+        vm, globalObject, headStructure, nullptr,
+        [&] (Vector<ObjectPropertyCondition>& conditions, JSObject* object) -> bool {
+            ObjectPropertyCondition result = generateCondition(vm, nullptr, object, uid, PropertyCondition::Absence);
+            if (!result)
+                return false;
+            conditions.append(result);
+            return true;
+        }, Concurrent);
+}
+
 ObjectPropertyConditionSet generateConditionsForPropertySetterMissConcurrently(
     VM& vm, JSGlobalObject* globalObject, Structure* headStructure, UniquedStringImpl* uid)
 {
index 957eaac..76e8a9c 100644 (file)
@@ -166,6 +166,9 @@ ObjectPropertyConditionSet generateConditionsForPrototypePropertyHitCustom(
     VM&, JSCell* owner, ExecState*, Structure* headStructure, JSObject* prototype,
     UniquedStringImpl* uid);
 
+
+ObjectPropertyConditionSet generateConditionsForPropertyMissConcurrently(
+    VM&, JSGlobalObject*, Structure* headStructure, UniquedStringImpl* uid);
 ObjectPropertyConditionSet generateConditionsForPropertySetterMissConcurrently(
     VM&, JSGlobalObject*, Structure* headStructure, UniquedStringImpl* uid);
 
index 541c84e..fdf76a1 100644 (file)
@@ -890,6 +890,18 @@ bool Graph::watchCondition(const ObjectPropertyCondition& key)
     return true;
 }
 
+bool Graph::watchConditions(const ObjectPropertyConditionSet& keys)
+{
+    if (!keys.isValid())
+        return false;
+
+    for (const ObjectPropertyCondition& key : keys) {
+        if (!watchCondition(key))
+            return false;
+    }
+    return true;
+}
+
 bool Graph::isSafeToLoad(JSObject* base, PropertyOffset offset)
 {
     return m_safeToLoad.contains(std::make_pair(base, offset));
@@ -1518,6 +1530,9 @@ bool Graph::canOptimizeStringObjectAccess(const CodeOrigin& codeOrigin)
     if (stringPrototypeStructure->isDictionary())
         return false;
 
+    if (!watchConditions(generateConditionsForPropertyMissConcurrently(m_vm, globalObjectFor(codeOrigin), stringObjectStructure, m_vm.propertyNames->toPrimitiveSymbol.impl())))
+        return false;
+
     // We're being conservative here. We want DFG's ToString on StringObject to be
     // used in both numeric contexts (that would call valueOf()) and string contexts
     // (that would call toString()). We don't want the DFG to have to distinguish
index 1a4e2f7..a8b9666 100644 (file)
@@ -650,6 +650,7 @@ public:
     // this also makes it cheap to query if the condition holds. Also makes sure that the GC knows
     // what's going on.
     bool watchCondition(const ObjectPropertyCondition&);
+    bool watchConditions(const ObjectPropertyConditionSet&);
 
     // Checks if it's known that loading from the given object at the given offset is fine. This is
     // computed by tracking which conditions we track with watchCondition().
index b217a2b..15d488e 100644 (file)
     macro(match) \
     macro(replace) \
     macro(split) \
-    macro(toPrimitive)
 
 #define JSC_COMMON_PRIVATE_IDENTIFIERS_EACH_WELL_KNOWN_SYMBOL(macro) \
     macro(hasInstance) \
     macro(iterator) \
     macro(search) \
     macro(species) \
+    macro(toPrimitive) \
     macro(toStringTag) \
     macro(unscopables)
 
index b32cca4..4166124 100644 (file)
@@ -111,6 +111,7 @@ EncodedJSValue JSC_HOST_CALL dateProtoFuncToGMTString(ExecState*);
 EncodedJSValue JSC_HOST_CALL dateProtoFuncToLocaleDateString(ExecState*);
 EncodedJSValue JSC_HOST_CALL dateProtoFuncToLocaleString(ExecState*);
 EncodedJSValue JSC_HOST_CALL dateProtoFuncToLocaleTimeString(ExecState*);
+EncodedJSValue JSC_HOST_CALL dateProtoFuncToPrimitiveSymbol(ExecState*);
 EncodedJSValue JSC_HOST_CALL dateProtoFuncToString(ExecState*);
 EncodedJSValue JSC_HOST_CALL dateProtoFuncToTimeString(ExecState*);
 EncodedJSValue JSC_HOST_CALL dateProtoFuncToUTCString(ExecState*);
@@ -499,6 +500,7 @@ void DatePrototype::finishCreation(VM& vm, JSGlobalObject* globalObject)
     UNUSED_PARAM(globalObject);
 #endif // ENABLE(INTL)
 
+    JSC_NATIVE_FUNCTION(vm.propertyNames->toPrimitiveSymbol, dateProtoFuncToPrimitiveSymbol, DontEnum | ReadOnly, 1);
     // The constructor will be added later, after DateConstructor has been built.
 }
 
@@ -597,6 +599,27 @@ EncodedJSValue JSC_HOST_CALL dateProtoFuncToLocaleTimeString(ExecState* exec)
     return JSValue::encode(formatLocaleDate(exec, thisDateObj, thisDateObj->internalNumber(), LocaleTime));
 }
 
+EncodedJSValue JSC_HOST_CALL dateProtoFuncToPrimitiveSymbol(ExecState* exec)
+{
+    JSValue thisValue = exec->thisValue();
+    if (!thisValue.isObject())
+        return throwVMTypeError(exec, "Date.prototype[Symbol.toPrimitive] expected |this| to be an object.");
+    JSObject* thisObject = jsCast<JSObject*>(thisValue);
+
+    if (!exec->argumentCount())
+        return throwVMTypeError(exec, "Date.prototype[Symbol.toPrimitive] expected a first argument.");
+
+    JSValue hintValue = exec->uncheckedArgument(0);
+    PreferredPrimitiveType type = toPreferredPrimitiveType(exec, hintValue);
+    if (exec->hadException())
+        return JSValue::encode(JSValue());
+
+    if (type == NoPreference)
+        type = PreferString;
+
+    return JSValue::encode(thisObject->ordinaryToPrimitive(exec, type));
+}
+
 EncodedJSValue JSC_HOST_CALL dateProtoFuncGetTime(ExecState* exec)
 {
     JSValue thisValue = exec->thisValue();
index 56219c4..897f990 100644 (file)
@@ -219,6 +219,11 @@ JSObject* throwTypeError(ExecState* exec)
     return exec->vm().throwException(exec, createTypeError(exec));
 }
 
+JSObject* throwTypeError(ExecState* exec, const String& message)
+{
+    return exec->vm().throwException(exec, createTypeError(exec, message));
+}
+
 JSObject* throwSyntaxError(ExecState* exec)
 {
     return exec->vm().throwException(exec, createSyntaxError(exec, ASCIILiteral("Syntax error")));
index 0329869..a30c998 100644 (file)
@@ -74,6 +74,7 @@ JSObject* addErrorInfo(ExecState*, JSObject* error, int line, const SourceCode&)
 // Convenience wrappers, create an throw an exception with a default message.
 JS_EXPORT_PRIVATE JSObject* throwConstructorCannotBeCalledAsFunctionTypeError(ExecState*, const char* constructorName);
 JS_EXPORT_PRIVATE JSObject* throwTypeError(ExecState*);
+JS_EXPORT_PRIVATE JSObject* throwTypeError(ExecState*, const String& errorMessage);
 JS_EXPORT_PRIVATE JSObject* throwSyntaxError(ExecState*);
 JS_EXPORT_PRIVATE JSObject* throwSyntaxError(ExecState*, const String& errorMessage);
 inline JSObject* throwRangeError(ExecState* state, const String& errorMessage) { return state->vm().throwException(state, createRangeError(state, errorMessage)); }
index 3b2fdcf..0e92e07 100644 (file)
@@ -628,6 +628,28 @@ inline JSValue JSValue::toPrimitive(ExecState* exec, PreferredPrimitiveType pref
     return isCell() ? asCell()->toPrimitive(exec, preferredType) : asValue();
 }
 
+inline PreferredPrimitiveType toPreferredPrimitiveType(ExecState* exec, JSValue value)
+{
+    if (!value.isString()) {
+        throwTypeError(exec, "Primitive hint is not a string.");
+        return NoPreference;
+    }
+
+    StringImpl* hintString = jsCast<JSString*>(value)->value(exec).impl();
+    if (exec->hadException())
+        return NoPreference;
+
+    if (WTF::equal(hintString, "default"))
+        return NoPreference;
+    if (WTF::equal(hintString, "number"))
+        return PreferNumber;
+    if (WTF::equal(hintString, "string"))
+        return PreferString;
+
+    throwTypeError(exec, "Expected primitive hint to match one of 'default', 'number', 'string'.");
+    return NoPreference;
+}
+
 inline bool JSValue::getPrimitiveNumber(ExecState* exec, double& number, JSValue& value)
 {
     if (isInt32()) {
index 8b9d505..58e52bd 100644 (file)
@@ -1426,55 +1426,67 @@ bool JSObject::deletePropertyByIndex(JSCell* cell, ExecState* exec, unsigned i)
     }
 }
 
-static ALWAYS_INLINE JSValue callDefaultValueFunction(ExecState* exec, const JSObject* object, PropertyName propertyName)
+enum class TypeHintMode { TakesHint, DoesNotTakeHint };
+
+template<TypeHintMode mode = TypeHintMode::DoesNotTakeHint>
+static ALWAYS_INLINE JSValue callToPrimitiveFunction(ExecState* exec, const JSObject* object, PropertyName propertyName, PreferredPrimitiveType hint)
 {
     JSValue function = object->get(exec, propertyName);
+    if (exec->hadException())
+        return exec->exception();
+    if (function.isUndefined() && mode == TypeHintMode::TakesHint)
+        return JSValue();
     CallData callData;
     CallType callType = getCallData(function, callData);
     if (callType == CallTypeNone)
         return exec->exception();
 
-    // Prevent "toString" and "valueOf" from observing execution if an exception
-    // is pending.
-    if (exec->hadException())
-        return exec->exception();
+    MarkedArgumentBuffer callArgs;
+    if (mode == TypeHintMode::TakesHint) {
+        JSString* hintString;
+        switch (hint) {
+        case NoPreference:
+            hintString = exec->vm().smallStrings.defaultString();
+            break;
+        case PreferNumber:
+            hintString = exec->vm().smallStrings.numberString();
+            break;
+        case PreferString:
+            hintString = exec->vm().smallStrings.stringString();
+            break;
+        }
+        callArgs.append(hintString);
+    }
 
-    JSValue result = call(exec, function, callType, callData, const_cast<JSObject*>(object), exec->emptyList());
+    JSValue result = call(exec, function, callType, callData, const_cast<JSObject*>(object), callArgs);
     ASSERT(!result.isGetterSetter());
     if (exec->hadException())
         return exec->exception();
     if (result.isObject())
-        return JSValue();
+        return mode == TypeHintMode::DoesNotTakeHint ? JSValue() : throwTypeError(exec, "Symbol.toPrimitive returned an object");
     return result;
 }
 
-bool JSObject::getPrimitiveNumber(ExecState* exec, double& number, JSValue& result) const
-{
-    result = methodTable(exec->vm())->defaultValue(this, exec, PreferNumber);
-    number = result.toNumber(exec);
-    return !result.isString();
-}
-
-// ECMA 8.6.2.6
-JSValue JSObject::defaultValue(const JSObject* object, ExecState* exec, PreferredPrimitiveType hint)
+// ECMA 7.1.1
+inline JSValue JSObject::ordinaryToPrimitive(ExecState* exec, PreferredPrimitiveType hint) const
 {
     // Make sure that whatever default value methods there are on object's prototype chain are
     // being watched.
-    object->structure()->startWatchingInternalPropertiesIfNecessaryForEntireChain(exec->vm());
-    
-    // Must call toString first for Date objects.
-    if ((hint == PreferString) || (hint != PreferNumber && object->prototype() == exec->lexicalGlobalObject()->datePrototype())) {
-        JSValue value = callDefaultValueFunction(exec, object, exec->propertyNames().toString);
+    this->structure()->startWatchingInternalPropertiesIfNecessaryForEntireChain(exec->vm());
+
+    JSValue value;
+    if (hint == PreferString) {
+        value = callToPrimitiveFunction(exec, this, exec->propertyNames().toString, hint);
         if (value)
             return value;
-        value = callDefaultValueFunction(exec, object, exec->propertyNames().valueOf);
+        value = callToPrimitiveFunction(exec, this, exec->propertyNames().valueOf, hint);
         if (value)
             return value;
     } else {
-        JSValue value = callDefaultValueFunction(exec, object, exec->propertyNames().valueOf);
+        value = callToPrimitiveFunction(exec, this, exec->propertyNames().valueOf, hint);
         if (value)
             return value;
-        value = callDefaultValueFunction(exec, object, exec->propertyNames().toString);
+        value = callToPrimitiveFunction(exec, this, exec->propertyNames().toString, hint);
         if (value)
             return value;
     }
@@ -1484,6 +1496,27 @@ JSValue JSObject::defaultValue(const JSObject* object, ExecState* exec, Preferre
     return exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("No default value")));
 }
 
+JSValue JSObject::defaultValue(const JSObject* object, ExecState* exec, PreferredPrimitiveType hint)
+{
+    return object->ordinaryToPrimitive(exec, hint);
+}
+
+JSValue JSObject::toPrimitive(ExecState* exec, PreferredPrimitiveType preferredType) const
+{
+    JSValue value = callToPrimitiveFunction<TypeHintMode::TakesHint>(exec, this, exec->propertyNames().toPrimitiveSymbol, preferredType);
+    if (value)
+        return value;
+
+    return this->methodTable(exec->vm())->defaultValue(this, exec, preferredType);
+}
+
+bool JSObject::getPrimitiveNumber(ExecState* exec, double& number, JSValue& result) const
+{
+    result = toPrimitive(exec, PreferNumber);
+    number = result.toNumber(exec);
+    return !result.isString();
+}
+
 const HashTableValue* JSObject::findPropertyHashEntry(PropertyName propertyName) const
 {
     for (const ClassInfo* info = classInfo(); info; info = info->parentClass) {
@@ -2912,11 +2945,6 @@ bool JSObject::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName
     return object->defineOwnNonIndexProperty(exec, propertyName, descriptor, throwException);
 }
 
-JSObject* throwTypeError(ExecState* exec, const String& message)
-{
-    return exec->vm().throwException(exec, createTypeError(exec, message));
-}
-
 void JSObject::convertToDictionary(VM& vm)
 {
     DeferredStructureTransitionWatchpointFire deferredWatchpointFire;
index 8f34d4d..eeba197 100644 (file)
@@ -504,6 +504,7 @@ public:
     JS_EXPORT_PRIVATE static bool deletePropertyByIndex(JSCell*, ExecState*, unsigned propertyName);
 
     JS_EXPORT_PRIVATE static JSValue defaultValue(const JSObject*, ExecState*, PreferredPrimitiveType);
+    JS_EXPORT_PRIVATE JSValue ordinaryToPrimitive(ExecState*, PreferredPrimitiveType) const;
 
     JS_EXPORT_PRIVATE bool hasInstance(ExecState*, JSValue value, JSValue hasInstanceValue);
     bool hasInstance(ExecState*, JSValue);
@@ -517,7 +518,7 @@ public:
     JS_EXPORT_PRIVATE static void getStructurePropertyNames(JSObject*, ExecState*, PropertyNameArray&, EnumerationMode);
     JS_EXPORT_PRIVATE static void getGenericPropertyNames(JSObject*, ExecState*, PropertyNameArray&, EnumerationMode);
 
-    JSValue toPrimitive(ExecState*, PreferredPrimitiveType = NoPreference) const;
+    JS_EXPORT_PRIVATE JSValue toPrimitive(ExecState*, PreferredPrimitiveType = NoPreference) const;
     bool getPrimitiveNumber(ExecState*, double& number, JSValue&) const;
     JS_EXPORT_PRIVATE double toNumber(ExecState*) const;
     JS_EXPORT_PRIVATE JSString* toString(ExecState*) const;
@@ -1452,11 +1453,6 @@ inline void JSObject::putDirectWithoutTransition(VM& vm, PropertyName propertyNa
     putDirect(vm, offset, value);
 }
 
-inline JSValue JSObject::toPrimitive(ExecState* exec, PreferredPrimitiveType preferredType) const
-{
-    return methodTable()->defaultValue(this, exec, preferredType);
-}
-
 ALWAYS_INLINE JSObject* Register::object() const
 {
     return asObject(jsValue());
index b90394e..3d650e4 100644 (file)
@@ -31,6 +31,7 @@
 #include <wtf/Noncopyable.h>
 
 #define JSC_COMMON_STRINGS_EACH_NAME(macro) \
+    macro(default) \
     macro(boolean) \
     macro(false) \
     macro(function) \
index c15725f..46c0cc7 100644 (file)
@@ -56,11 +56,13 @@ SymbolPrototype::SymbolPrototype(VM& vm, Structure* structure)
 {
 }
 
-void SymbolPrototype::finishCreation(VM& vm)
+void SymbolPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject)
 {
     Base::finishCreation(vm);
     putDirectWithoutTransition(vm, vm.propertyNames->toStringTagSymbol, jsString(&vm, "Symbol"), DontEnum | ReadOnly);
     ASSERT(inherits(info()));
+
+    JSC_NATIVE_FUNCTION(vm.propertyNames->toPrimitiveSymbol, symbolProtoFuncValueOf, DontEnum | ReadOnly, 1);
 }
 
 bool SymbolPrototype::getOwnPropertySlot(JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot &slot)
index 9613787..3b40f00 100644 (file)
@@ -38,10 +38,10 @@ public:
     typedef JSNonFinalObject Base;
     static const unsigned StructureFlags = Base::StructureFlags | OverridesGetOwnPropertySlot;
 
-    static SymbolPrototype* create(VM& vm, JSGlobalObject*, Structure* structure)
+    static SymbolPrototype* create(VM& vm, JSGlobalObject* globalObject, Structure* structure)
     {
         SymbolPrototype* prototype = new (NotNull, allocateCell<SymbolPrototype>(vm.heap)) SymbolPrototype(vm, structure);
-        prototype->finishCreation(vm);
+        prototype->finishCreation(vm, globalObject);
         return prototype;
     }
 
@@ -54,7 +54,7 @@ public:
 
 protected:
     SymbolPrototype(VM&, Structure*);
-    void finishCreation(VM&);
+    void finishCreation(VM&, JSGlobalObject*);
 
 private:
     static bool getOwnPropertySlot(JSObject*, ExecState*, PropertyName, PropertySlot&);
index 3940537..021fd83 100644 (file)
 - path: es6/Proxy_internal_get_calls_CreateListFromArrayLike.js
   cmd: runES6 :normal
 - path: es6/Proxy_internal_get_calls_Date.prototype.toJSON.js
-  cmd: runES6 :fail
+  cmd: runES6 :normal
 - path: es6/Proxy_internal_get_calls_Error.prototype.toString.js
   cmd: runES6 :normal
 - path: es6/Proxy_internal_get_calls_Function.prototype.bind.js
 - path: es6/Proxy_internal_get_calls_String.prototype.replace.js
   cmd: runES6 :fail
 - path: es6/Proxy_internal_get_calls_String.prototype.search.js
-  cmd: runES6 :fail
+  cmd: runES6 :normal
 - path: es6/Proxy_internal_get_calls_String.prototype.split.js
   cmd: runES6 :fail
 - path: es6/Proxy_internal_get_calls_String.raw.js
   cmd: runES6 :normal
 - path: es6/Proxy_internal_get_calls_ToPrimitive.js
-  cmd: runES6 :fail
+  cmd: runES6 :normal
 - path: es6/Proxy_internal_get_calls_ToPropertyDescriptor.js
   cmd: runES6 :fail
 - path: es6/Proxy_internal_getOwnPropertyDescriptor_calls_[[Set]].js
 - path: es6/well-known_symbols_Symbol.split.js
   cmd: runES6 :fail
 - path: es6/well-known_symbols_Symbol.toPrimitive.js
-  cmd: runES6 :fail
+  cmd: runES6 :normal
 - path: es6/well-known_symbols_Symbol.toStringTag.js
   cmd: runES6 :normal
 - path: es6/well-known_symbols_Symbol.toStringTag_misc._built-ins.js
diff --git a/Source/JavaScriptCore/tests/stress/date-symbol-toprimitive.js b/Source/JavaScriptCore/tests/stress/date-symbol-toprimitive.js
new file mode 100644 (file)
index 0000000..fb5ea3d
--- /dev/null
@@ -0,0 +1,6 @@
+delete Date.prototype[Symbol.toPrimitive]
+
+let date = new Date();
+
+if (typeof (date + 1) !== "number")
+    throw "symbol was not deleted";
diff --git a/Source/JavaScriptCore/tests/stress/ropes-symbol-toprimitive.js b/Source/JavaScriptCore/tests/stress/ropes-symbol-toprimitive.js
new file mode 100644 (file)
index 0000000..f6dcf19
--- /dev/null
@@ -0,0 +1,28 @@
+function ropify(a,b,c) {
+    return a + b + c;
+}
+noInline(ropify);
+
+function ropify2(a,b,c) {
+    return a + b + c;
+}
+noInline(ropify2);
+
+let test = new String("test");
+
+for (let i = 0; i < 100000; i++) {
+    if (ropify("a", "b", test) !== "abtest")
+        throw "wrong on warmup";
+}
+
+String.prototype[Symbol.toPrimitive] = function() { return "changed"; }
+
+if (ropify("a", "b", test) !== "abchanged")
+    throw "watchpoint didn't fire";
+
+
+// Test we don't start doing the wrong thing if the prototype chain has been mucked with.
+for (let i = 0; i < 100000; i++) {
+    if (ropify2("a", "b", test) !== "abchanged")
+        throw "wrong on warmup";
+}
diff --git a/Source/JavaScriptCore/tests/stress/symbol-toprimitive.js b/Source/JavaScriptCore/tests/stress/symbol-toprimitive.js
new file mode 100644 (file)
index 0000000..fac7636
--- /dev/null
@@ -0,0 +1,18 @@
+// return object
+let foo = { }
+foo[Symbol.toPrimitive] = function() { return {} };
+
+for (i = 0; i < 100000; i++) {
+    let failed = true;
+    try {
+        foo >= 1;
+    } catch (e) {
+        if (e instanceof TypeError)
+            failed = false;
+    }
+
+    if (failed)
+        throw "should have thrown on return of object";
+}
+
+// The general use of Symbol.toPrimitive is covered in the ES6 tests.