Title: [276660] trunk
Revision
276660
Author
[email protected]
Date
2021-04-27 13:56:19 -0700 (Tue, 27 Apr 2021)

Log Message

[JSC] Remove defaultValue() from the method table
https://bugs.webkit.org/show_bug.cgi?id=225032

Reviewed by Darin Adler.

Source/_javascript_Core:

This patch not only removes the unnecessary method table entry, but also makes
the presence of custom ToPrimitive behavior observable to userland code.

To maintain object identity and (possibly) enable caching, Symbol.toPrimitive
method is stored on a structure. To avoid any potential breakage, it's made
replaceable and configurable, covering the case when its holder is a [[ProxyTarget]].

For JSCallbackObject, Symbol.toPrimitive method is created only if ConvertToType
callback is present, before initialization is performed.

Also, this change adds additional ordinaryToPrimitive() cast to fix the invariant
that toPrimitive() returns a primitive value, which was broken if ConvertToType
callback returned an object. The invariant is enforced by the spec [1][2] and is
validated via assertion in JSValue::toStringSlowCase().

[1]: https://tc39.es/ecma262/#sec-toprimitive (step 2.b.vi)
[2]: https://tc39.es/ecma262/#sec-ordinarytoprimitive (step 6)

* API/JSCallbackObject.h:
* API/JSCallbackObjectFunctions.h:
(JSC::JSCallbackObject<Parent>::init):
(JSC::JSCallbackObject<Parent>::customToPrimitive):
(JSC::JSCallbackObject<Parent>::defaultValue): Deleted.
* API/tests/testapiScripts/testapi.js:
* runtime/ClassInfo.h:
* runtime/JSCell.cpp:
(JSC::JSCell::defaultValue): Deleted.
* runtime/JSCell.h:
* runtime/JSObject.cpp:
(JSC::JSObject::toPrimitive const):
(JSC::JSObject::defaultValue): Deleted.
* runtime/JSObject.h:
* runtime/Operations.cpp:
(JSC::jsAddSlowCase):

Source/WebCore:

Test: platform/mac/fast/dom/objc-wrapper-toprimitive.html

* bindings/js/JSPluginElementFunctions.cpp:
(WebCore::pluginElementCustomGetOwnPropertySlot):
* bridge/objc/objc_runtime.h:
* bridge/objc/objc_runtime.mm:
(JSC::Bindings::ObjcFallbackObjectImp::finishCreation):
(JSC::Bindings::ObjcFallbackObjectImp::getOwnPropertySlot):
(JSC::Bindings::JSC_DEFINE_HOST_FUNCTION):
(JSC::Bindings::ObjcFallbackObjectImp::defaultValue): Deleted.
* bridge/runtime_object.cpp:
(JSC::Bindings::RuntimeObject::finishCreation):
(JSC::Bindings::RuntimeObject::getOwnPropertySlot):
(JSC::Bindings::JSC_DEFINE_HOST_FUNCTION):
(JSC::Bindings::RuntimeObject::defaultValue): Deleted.
* bridge/runtime_object.h:

LayoutTests:

* platform/mac/fast/dom/objc-wrapper-toprimitive-expected.txt: Added.
* platform/mac/fast/dom/objc-wrapper-toprimitive.html: Added.
* platform/wk2/TestExpectations:
* plugins/npruntime/tostring-expected.txt:
* plugins/npruntime/tostring.html:
* plugins/npruntime/valueof-expected.txt:
* plugins/npruntime/valueof.html:

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (276659 => 276660)


--- trunk/LayoutTests/ChangeLog	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/LayoutTests/ChangeLog	2021-04-27 20:56:19 UTC (rev 276660)
@@ -1,3 +1,18 @@
+2021-04-27  Alexey Shvayka  <[email protected]>
+
+        [JSC] Remove defaultValue() from the method table
+        https://bugs.webkit.org/show_bug.cgi?id=225032
+
+        Reviewed by Darin Adler.
+
+        * platform/mac/fast/dom/objc-wrapper-toprimitive-expected.txt: Added.
+        * platform/mac/fast/dom/objc-wrapper-toprimitive.html: Added.
+        * platform/wk2/TestExpectations:
+        * plugins/npruntime/tostring-expected.txt:
+        * plugins/npruntime/tostring.html:
+        * plugins/npruntime/valueof-expected.txt:
+        * plugins/npruntime/valueof.html:
+
 2021-04-27  Aditya Keerthi  <[email protected]>
 
         [iOS][FCR] Add borders for better control visibility

Added: trunk/LayoutTests/platform/mac/fast/dom/objc-wrapper-toprimitive-expected.txt (0 => 276660)


--- trunk/LayoutTests/platform/mac/fast/dom/objc-wrapper-toprimitive-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/platform/mac/fast/dom/objc-wrapper-toprimitive-expected.txt	2021-04-27 20:56:19 UTC (rev 276660)
@@ -0,0 +1,65 @@
+This tests ToPrimitive performed on Objective-C wrapper object.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+RuntimeObject
+
+PASS '' + objCController is '<ObjCController>'
+PASS +objCController is 0
+PASS `${objCController}` is '<ObjCController>'
+
+PASS objCController[Symbol.toPrimitive].length is 1
+PASS objCController[Symbol.toPrimitive]('default') is '<ObjCController>'
+PASS objCController[Symbol.toPrimitive]('number') is 0
+PASS objCController[Symbol.toPrimitive]('string') is '<ObjCController>'
+PASS objCController[Symbol.toPrimitive]('foo') threw exception TypeError: Expected primitive hint to match one of 'default', 'number', 'string'..
+PASS objCController[Symbol.toPrimitive].call({}, 'default') threw exception TypeError: RuntimeObject[Symbol.toPrimitive] method called on incompatible |this| value..
+PASS (0, objCController[Symbol.toPrimitive])() threw exception TypeError: RuntimeObject[Symbol.toPrimitive] method called on incompatible |this| value..
+
+RuntimeObject (redefined Symbol.toPrimitive)
+
+PASS typeof symbolToPrimitiveDescriptor is 'object'
+PASS symbolToPrimitiveDescriptor.value is object[Symbol.toPrimitive]
+PASS symbolToPrimitiveDescriptor.writable is true
+PASS symbolToPrimitiveDescriptor.enumerable is false
+PASS symbolToPrimitiveDescriptor.configurable is true
+
+PASS object[Symbol.toPrimitive]() is 'bar'
+PASS typeof symbolToPrimitiveDescriptor is 'object'
+PASS symbolToPrimitiveDescriptor.value is newToPrimitive
+PASS symbolToPrimitiveDescriptor.writable is false
+PASS symbolToPrimitiveDescriptor.enumerable is true
+PASS symbolToPrimitiveDescriptor.configurable is true
+
+PASS object[Symbol.toPrimitive] is 123
+
+ObjcFallbackObjectImp
+
+PASS '' + fallbackObject is 'undefined'
+PASS +fallbackObject is NaN
+PASS `${fallbackObject}` is 'undefined'
+
+PASS fallbackObject[Symbol.toPrimitive].length is 0
+PASS fallbackObject[Symbol.toPrimitive]() is undefined
+PASS fallbackObject[Symbol.toPrimitive]('foo') is undefined
+PASS fallbackObject[Symbol.toPrimitive].call({}) threw exception TypeError: ObjcFallbackObject[Symbol.toPrimitive] method called on incompatible |this| value..
+PASS (0, fallbackObject[Symbol.toPrimitive])() threw exception TypeError: ObjcFallbackObject[Symbol.toPrimitive] method called on incompatible |this| value..
+
+ObjcFallbackObjectImp (redefined Symbol.toPrimitive)
+
+PASS typeof symbolToPrimitiveDescriptor is 'object'
+PASS symbolToPrimitiveDescriptor.value is object[Symbol.toPrimitive]
+PASS symbolToPrimitiveDescriptor.writable is true
+PASS symbolToPrimitiveDescriptor.enumerable is false
+PASS symbolToPrimitiveDescriptor.configurable is true
+
+PASS object[Symbol.toPrimitive]() is 'bar'
+PASS typeof symbolToPrimitiveDescriptor is 'object'
+PASS symbolToPrimitiveDescriptor.value is newToPrimitive
+PASS symbolToPrimitiveDescriptor.writable is false
+PASS symbolToPrimitiveDescriptor.enumerable is true
+PASS symbolToPrimitiveDescriptor.configurable is true
+
+PASS object[Symbol.toPrimitive] is 123
+

Added: trunk/LayoutTests/platform/mac/fast/dom/objc-wrapper-toprimitive.html (0 => 276660)


--- trunk/LayoutTests/platform/mac/fast/dom/objc-wrapper-toprimitive.html	                        (rev 0)
+++ trunk/LayoutTests/platform/mac/fast/dom/objc-wrapper-toprimitive.html	2021-04-27 20:56:19 UTC (rev 276660)
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<script src=""
+<body>
+<p id="description"></p>
+<div id="console"></div>
+<script>
+if (window.testRunner)
+    testRunner.waitUntilDone();
+
+function runTest()
+{
+    description("This tests ToPrimitive performed on Objective-C wrapper object.");
+    if (!window.objCController) {
+        testFailed("window.objCController does not exist. Run with --dump-render-tree.");
+        return;
+    }
+
+    debug("RuntimeObject\n");
+
+    shouldBe("'' + objCController", "'<ObjCController>'");
+    shouldBe("+objCController", "0");
+    shouldBe("`${objCController}`", "'<ObjCController>'");
+
+    debug("");
+    shouldBe("objCController[Symbol.toPrimitive].length", "1");
+    shouldBe("objCController[Symbol.toPrimitive]('default')", "'<ObjCController>'");
+    shouldBe("objCController[Symbol.toPrimitive]('number')", "0");
+    shouldBe("objCController[Symbol.toPrimitive]('string')", "'<ObjCController>'");
+    shouldThrow("objCController[Symbol.toPrimitive]('foo')");
+    shouldThrow("objCController[Symbol.toPrimitive].call({}, 'default')");
+    shouldThrow("(0, objCController[Symbol.toPrimitive])()");
+
+    debug("\nRuntimeObject (redefined Symbol.toPrimitive)\n");
+    testDefineOwnProperty(objCController);
+
+    debug("\nObjcFallbackObjectImp\n");
+
+    fallbackObject = objCController.undefinedKey;
+
+    shouldBe("'' + fallbackObject", "'undefined'");
+    shouldBe("+fallbackObject", "NaN");
+    shouldBe("`${fallbackObject}`", "'undefined'");
+
+    debug("");
+    shouldBe("fallbackObject[Symbol.toPrimitive].length", "0");
+    shouldBe("fallbackObject[Symbol.toPrimitive]()", "undefined");
+    shouldBe("fallbackObject[Symbol.toPrimitive]('foo')", "undefined");
+    shouldThrow("fallbackObject[Symbol.toPrimitive].call({})");
+    shouldThrow("(0, fallbackObject[Symbol.toPrimitive])()");
+
+    debug("\nObjcFallbackObjectImp (redefined Symbol.toPrimitive)\n");
+    testDefineOwnProperty(fallbackObject);
+
+    if (window.testRunner)
+        testRunner.notifyDone();
+}
+
+function testDefineOwnProperty(_object)
+{
+    object = _object;
+
+    symbolToPrimitiveDescriptor = Object.getOwnPropertyDescriptor(object, Symbol.toPrimitive);
+    shouldBe("typeof symbolToPrimitiveDescriptor", "'object'");
+    shouldBe("symbolToPrimitiveDescriptor.value", "object[Symbol.toPrimitive]");
+    shouldBe("symbolToPrimitiveDescriptor.writable", "true");
+    shouldBe("symbolToPrimitiveDescriptor.enumerable", "false");
+    shouldBe("symbolToPrimitiveDescriptor.configurable", "true");
+
+    debug("");
+    newToPrimitive = () => "bar";
+    Object.defineProperty(object, Symbol.toPrimitive, { value: newToPrimitive, writable: false, enumerable: true });
+    shouldBe("object[Symbol.toPrimitive]()", "'bar'");
+
+    symbolToPrimitiveDescriptor = Object.getOwnPropertyDescriptor(object, Symbol.toPrimitive);
+    shouldBe("typeof symbolToPrimitiveDescriptor", "'object'");
+    shouldBe("symbolToPrimitiveDescriptor.value", "newToPrimitive");
+    shouldBe("symbolToPrimitiveDescriptor.writable", "false");
+    shouldBe("symbolToPrimitiveDescriptor.enumerable", "true");
+    shouldBe("symbolToPrimitiveDescriptor.configurable", "true");
+
+    debug("");
+    Object.defineProperty(object, Symbol.toPrimitive, { get: () => 123 });
+    shouldBe("object[Symbol.toPrimitive]", "123");
+}
+
+window._onload_ = runTest;
+</script>
+</body>

Modified: trunk/LayoutTests/platform/wk2/TestExpectations (276659 => 276660)


--- trunk/LayoutTests/platform/wk2/TestExpectations	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/LayoutTests/platform/wk2/TestExpectations	2021-04-27 20:56:19 UTC (rev 276660)
@@ -557,6 +557,7 @@
 
 # WebKitTestRunner doesn't have objCController
 platform/mac/fast/dom/objc-wrapper-identity.html
+platform/mac/fast/dom/objc-wrapper-toprimitive.html
 platform/mac/fast/dom/wrapper-classes-objc.html
 platform/mac/fast/dom/wrapper-round-tripping.html
 platform/mac/fast/objc/dom-html-select-activate.html

Modified: trunk/LayoutTests/plugins/npruntime/tostring-expected.txt (276659 => 276660)


--- trunk/LayoutTests/plugins/npruntime/tostring-expected.txt	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/LayoutTests/plugins/npruntime/tostring-expected.txt	2021-04-27 20:56:19 UTC (rev 276660)
@@ -3,6 +3,7 @@
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 
+PASS plugin[Symbol.toPrimitive] is undefined
 PASS plugin.toString() is "TestObject"
 PASS [plugin, ''].join('') is "TestObject"
 PASS successfullyParsed is true

Modified: trunk/LayoutTests/plugins/npruntime/tostring.html (276659 => 276660)


--- trunk/LayoutTests/plugins/npruntime/tostring.html	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/LayoutTests/plugins/npruntime/tostring.html	2021-04-27 20:56:19 UTC (rev 276660)
@@ -14,6 +14,7 @@
     plugin.setAttribute('test', 'to-string-and-value-of-object');
     document.body.appendChild(plugin);
 
+    shouldBe("plugin[Symbol.toPrimitive]", "undefined");
     shouldBeEqualToString("plugin.toString()", "TestObject");
     // Normal plugin.testObject + "" will call valueOf,
     // do some tricks to make a call to implicit toString.

Modified: trunk/LayoutTests/plugins/npruntime/valueof-expected.txt (276659 => 276660)


--- trunk/LayoutTests/plugins/npruntime/valueof-expected.txt	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/LayoutTests/plugins/npruntime/valueof-expected.txt	2021-04-27 20:56:19 UTC (rev 276660)
@@ -3,6 +3,7 @@
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 
+PASS plugin[Symbol.toPrimitive] is undefined
 PASS plugin.valueOf() is 123456789
 PASS plugin == 123456789 is true
 PASS successfullyParsed is true

Modified: trunk/LayoutTests/plugins/npruntime/valueof.html (276659 => 276660)


--- trunk/LayoutTests/plugins/npruntime/valueof.html	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/LayoutTests/plugins/npruntime/valueof.html	2021-04-27 20:56:19 UTC (rev 276660)
@@ -14,6 +14,7 @@
     plugin.setAttribute('test', 'to-string-and-value-of-object');
     document.body.appendChild(plugin);
 
+    shouldBe("plugin[Symbol.toPrimitive]", "undefined");
     shouldBe("plugin.valueOf()", "123456789");
     shouldBeTrue("plugin == 123456789");
     var successfullyParsed = true;

Modified: trunk/Source/_javascript_Core/API/JSCallbackObject.h (276659 => 276660)


--- trunk/Source/_javascript_Core/API/JSCallbackObject.h	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/Source/_javascript_Core/API/JSCallbackObject.h	2021-04-27 20:56:19 UTC (rev 276660)
@@ -199,7 +199,7 @@
     void finishCreation(VM&);
 
     static IsoSubspace* subspaceForImpl(VM&, SubspaceAccess);
-    static JSValue defaultValue(const JSObject*, JSGlobalObject*, PreferredPrimitiveType);
+    static JSC_HOST_CALL_ATTRIBUTES EncodedJSValue customToPrimitive(JSGlobalObject*, CallFrame*);
 
     static bool getOwnPropertySlot(JSObject*, JSGlobalObject*, PropertyName, PropertySlot&);
     static bool getOwnPropertySlotByIndex(JSObject*, JSGlobalObject*, unsigned propertyName, PropertySlot&);

Modified: trunk/Source/_javascript_Core/API/JSCallbackObjectFunctions.h (276659 => 276660)


--- trunk/Source/_javascript_Core/API/JSCallbackObjectFunctions.h	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/Source/_javascript_Core/API/JSCallbackObjectFunctions.h	2021-04-27 20:56:19 UTC (rev 276660)
@@ -112,13 +112,23 @@
 void JSCallbackObject<Parent>::init(JSGlobalObject* globalObject)
 {
     ASSERT(globalObject);
+    VM& vm = getVM(globalObject);
     
+    bool hasConvertToType = false;
     Vector<JSObjectInitializeCallback, 16> initRoutines;
     JSClassRef jsClass = classRef();
     do {
+        if (jsClass->convertToType)
+            hasConvertToType = true;
         if (JSObjectInitializeCallback initialize = jsClass->initialize)
             initRoutines.append(initialize);
     } while ((jsClass = jsClass->parentClass));
+
+    if (hasConvertToType) {
+        this->putDirect(vm, vm.propertyNames->toPrimitiveSymbol,
+            JSFunction::create(vm, globalObject, 1, "[Symbol.toPrimitive]"_s, customToPrimitive),
+            static_cast<unsigned>(PropertyAttribute::DontEnum));
+    }
     
     // initialize from base to derived
     for (int i = static_cast<int>(initRoutines.size()) - 1; i >= 0; i--) {
@@ -217,12 +227,17 @@
 }
 
 template <class Parent>
-JSValue JSCallbackObject<Parent>::defaultValue(const JSObject* object, JSGlobalObject* globalObject, PreferredPrimitiveType hint)
+EncodedJSValue JSCallbackObject<Parent>::customToPrimitive(JSGlobalObject* globalObject, CallFrame* callFrame)
 {
     VM& vm = getVM(globalObject);
     auto scope = DECLARE_THROW_SCOPE(vm);
 
-    const JSCallbackObject* thisObject = jsCast<const JSCallbackObject*>(object);
+    JSCallbackObject* thisObject = jsDynamicCast<JSCallbackObject*>(vm, callFrame->thisValue());
+    if (!thisObject)
+        return throwVMTypeError(globalObject, scope, "JSCallbackObject[Symbol.toPrimitive] method called on incompatible |this| value."_s);
+    PreferredPrimitiveType hint = toPreferredPrimitiveType(globalObject, callFrame->argument(0));
+    RETURN_IF_EXCEPTION(scope, { });
+
     JSContextRef ctx = toRef(globalObject);
     JSObjectRef thisRef = toRef(jsCast<const JSObject*>(thisObject));
     ::JSType jsHint = hint == PreferString ? kJSTypeString : kJSTypeNumber;
@@ -231,16 +246,18 @@
         if (JSObjectConvertToTypeCallback convertToType = jsClass->convertToType) {
             JSValueRef exception = nullptr;
             JSValueRef result = convertToType(ctx, thisRef, jsHint, &exception);
-            if (exception) {
-                throwException(globalObject, scope, toJS(globalObject, exception));
-                return jsUndefined();
+            if (exception)
+                return throwVMError(globalObject, scope, toJS(globalObject, exception));
+            if (result) {
+                JSValue jsResult = toJS(globalObject, result);
+                if (UNLIKELY(jsResult.isObject()))
+                    return JSValue::encode(asObject(jsResult)->ordinaryToPrimitive(globalObject, hint));
+                return JSValue::encode(jsResult);
             }
-            if (result)
-                return toJS(globalObject, result);
         }
     }
     
-    RELEASE_AND_RETURN(scope, Parent::defaultValue(object, globalObject, hint));
+    RELEASE_AND_RETURN(scope, JSValue::encode(thisObject->ordinaryToPrimitive(globalObject, hint)));
 }
 
 template <class Parent>

Modified: trunk/Source/_javascript_Core/API/tests/testapiScripts/testapi.js (276659 => 276660)


--- trunk/Source/_javascript_Core/API/tests/testapiScripts/testapi.js	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/Source/_javascript_Core/API/tests/testapiScripts/testapi.js	2021-04-27 20:56:19 UTC (rev 276660)
@@ -228,6 +228,24 @@
 shouldBe("globalObjectHeirGlobalStaticValue2Descriptor.configurable", true);
 shouldBe("this.globalStaticValue2", 3);
 
+var symbolToPrimitiveDescriptor = Object.getOwnPropertyDescriptor(MyObject, Symbol.toPrimitive);
+shouldBe("typeof symbolToPrimitiveDescriptor", "object");
+shouldBe("symbolToPrimitiveDescriptor.value", MyObject[Symbol.toPrimitive]);
+shouldBe("symbolToPrimitiveDescriptor.writable", true);
+shouldBe("symbolToPrimitiveDescriptor.enumerable", false);
+shouldBe("symbolToPrimitiveDescriptor.configurable", true);
+
+shouldBe("MyObject[Symbol.toPrimitive]('default')", 1);
+shouldBe("MyObject[Symbol.toPrimitive]('number')", 1);
+shouldBe("MyObject[Symbol.toPrimitive]('string')", "MyObjectAsString");
+
+shouldThrow("MyObject[Symbol.toPrimitive]('foo')");
+shouldThrow("MyObject[Symbol.toPrimitive].call({}, 'default')");
+shouldThrow("(0, MyObject[Symbol.toPrimitive])('default')");
+
+MyObject[Symbol.toPrimitive] = () => null;
+shouldBe("MyObject[Symbol.toPrimitive]('bar')", null);
+
 derived = new Derived();
 
 shouldBe("derived instanceof Derived", true);
@@ -307,6 +325,11 @@
 EvilExceptionObject.toStringExplicit = function f() { return f(); }
 shouldThrow("String(EvilExceptionObject)");
 
+EvilExceptionObject.toNumber = () => ({ valueOf: () => 4815 });
+shouldBe("Number(EvilExceptionObject)", 4815);
+EvilExceptionObject.toStringExplicit = () => ({ toString: () => "foobar" });
+shouldBe("`${EvilExceptionObject}`", "foobar");
+
 shouldBe("console", "[object console]");
 shouldBe("typeof console.log", "function");
 

Modified: trunk/Source/_javascript_Core/ChangeLog (276659 => 276660)


--- trunk/Source/_javascript_Core/ChangeLog	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/Source/_javascript_Core/ChangeLog	2021-04-27 20:56:19 UTC (rev 276660)
@@ -1,3 +1,45 @@
+2021-04-27  Alexey Shvayka  <[email protected]>
+
+        [JSC] Remove defaultValue() from the method table
+        https://bugs.webkit.org/show_bug.cgi?id=225032
+
+        Reviewed by Darin Adler.
+
+        This patch not only removes the unnecessary method table entry, but also makes
+        the presence of custom ToPrimitive behavior observable to userland code.
+
+        To maintain object identity and (possibly) enable caching, Symbol.toPrimitive
+        method is stored on a structure. To avoid any potential breakage, it's made
+        replaceable and configurable, covering the case when its holder is a [[ProxyTarget]].
+
+        For JSCallbackObject, Symbol.toPrimitive method is created only if ConvertToType
+        callback is present, before initialization is performed.
+
+        Also, this change adds additional ordinaryToPrimitive() cast to fix the invariant
+        that toPrimitive() returns a primitive value, which was broken if ConvertToType
+        callback returned an object. The invariant is enforced by the spec [1][2] and is
+        validated via assertion in JSValue::toStringSlowCase().
+
+        [1]: https://tc39.es/ecma262/#sec-toprimitive (step 2.b.vi)
+        [2]: https://tc39.es/ecma262/#sec-ordinarytoprimitive (step 6)
+
+        * API/JSCallbackObject.h:
+        * API/JSCallbackObjectFunctions.h:
+        (JSC::JSCallbackObject<Parent>::init):
+        (JSC::JSCallbackObject<Parent>::customToPrimitive):
+        (JSC::JSCallbackObject<Parent>::defaultValue): Deleted.
+        * API/tests/testapiScripts/testapi.js:
+        * runtime/ClassInfo.h:
+        * runtime/JSCell.cpp:
+        (JSC::JSCell::defaultValue): Deleted.
+        * runtime/JSCell.h:
+        * runtime/JSObject.cpp:
+        (JSC::JSObject::toPrimitive const):
+        (JSC::JSObject::defaultValue): Deleted.
+        * runtime/JSObject.h:
+        * runtime/Operations.cpp:
+        (JSC::jsAddSlowCase):
+
 2021-04-27  Keith Miller  <[email protected]>
 
         StructureStubInfo and PolymorphicAccess should account for their non-GC memory

Modified: trunk/Source/_javascript_Core/runtime/ClassInfo.h (276659 => 276660)


--- trunk/Source/_javascript_Core/runtime/ClassInfo.h	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/Source/_javascript_Core/runtime/ClassInfo.h	2021-04-27 20:56:19 UTC (rev 276660)
@@ -71,9 +71,6 @@
     using ToThisFunctionPtr = JSValue (*)(JSCell*, JSGlobalObject*, ECMAMode);
     ToThisFunctionPtr METHOD_TABLE_ENTRY(toThis);
 
-    using DefaultValueFunctionPtr = JSValue (*)(const JSObject*, JSGlobalObject*, PreferredPrimitiveType);
-    DefaultValueFunctionPtr METHOD_TABLE_ENTRY(defaultValue);
-
     using GetOwnPropertyNamesFunctionPtr = void (*)(JSObject*, JSGlobalObject*, PropertyNameArray&, DontEnumPropertiesMode);
     GetOwnPropertyNamesFunctionPtr METHOD_TABLE_ENTRY(getOwnPropertyNames);
     GetOwnPropertyNamesFunctionPtr METHOD_TABLE_ENTRY(getOwnSpecialPropertyNames);
@@ -156,7 +153,6 @@
         &ClassName::getOwnPropertySlot, \
         &ClassName::getOwnPropertySlotByIndex, \
         &ClassName::toThis, \
-        &ClassName::defaultValue, \
         &ClassName::getOwnPropertyNames, \
         &ClassName::getOwnSpecialPropertyNames, \
         &ClassName::customHasInstance, \

Modified: trunk/Source/_javascript_Core/runtime/JSCell.cpp (276659 => 276660)


--- trunk/Source/_javascript_Core/runtime/JSCell.cpp	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/Source/_javascript_Core/runtime/JSCell.cpp	2021-04-27 20:56:19 UTC (rev 276660)
@@ -183,12 +183,6 @@
     ASSERT_GC_OBJECT_LOOKS_VALID(cell);
 }
 
-JSValue JSCell::defaultValue(const JSObject*, JSGlobalObject*, PreferredPrimitiveType)
-{
-    RELEASE_ASSERT_NOT_REACHED();
-    return jsUndefined();
-}
-
 bool JSCell::getOwnPropertySlot(JSObject*, JSGlobalObject*, PropertyName, PropertySlot&)
 {
     RELEASE_ASSERT_NOT_REACHED();

Modified: trunk/Source/_javascript_Core/runtime/JSCell.h (276659 => 276660)


--- trunk/Source/_javascript_Core/runtime/JSCell.h	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/Source/_javascript_Core/runtime/JSCell.h	2021-04-27 20:56:19 UTC (rev 276660)
@@ -244,7 +244,6 @@
     void finishCreation(VM&, Structure*, CreatingEarlyCellTag);
 
     // Dummy implementations of override-able static functions for classes to put in their MethodTable
-    static JSValue defaultValue(const JSObject*, JSGlobalObject*, PreferredPrimitiveType);
     static NO_RETURN_DUE_TO_CRASH void getOwnPropertyNames(JSObject*, JSGlobalObject*, PropertyNameArray&, DontEnumPropertiesMode);
     static NO_RETURN_DUE_TO_CRASH void getOwnSpecialPropertyNames(JSObject*, JSGlobalObject*, PropertyNameArray&, DontEnumPropertiesMode);
 

Modified: trunk/Source/_javascript_Core/runtime/JSObject.cpp (276659 => 276660)


--- trunk/Source/_javascript_Core/runtime/JSObject.cpp	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/Source/_javascript_Core/runtime/JSObject.cpp	2021-04-27 20:56:19 UTC (rev 276660)
@@ -2310,11 +2310,6 @@
     return throwTypeError(globalObject, scope, "No default value"_s);
 }
 
-JSValue JSObject::defaultValue(const JSObject* object, JSGlobalObject* globalObject, PreferredPrimitiveType hint)
-{
-    return object->ordinaryToPrimitive(globalObject, hint);
-}
-
 JSValue JSObject::toPrimitive(JSGlobalObject* globalObject, PreferredPrimitiveType preferredType) const
 {
     VM& vm = globalObject->vm();
@@ -2325,7 +2320,7 @@
     if (value)
         return value;
 
-    RELEASE_AND_RETURN(scope, this->methodTable(vm)->defaultValue(this, globalObject, preferredType));
+    RELEASE_AND_RETURN(scope, ordinaryToPrimitive(globalObject, preferredType));
 }
 
 bool JSObject::getOwnStaticPropertySlot(VM& vm, PropertyName propertyName, PropertySlot& slot)

Modified: trunk/Source/_javascript_Core/runtime/JSObject.h (276659 => 276660)


--- trunk/Source/_javascript_Core/runtime/JSObject.h	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/Source/_javascript_Core/runtime/JSObject.h	2021-04-27 20:56:19 UTC (rev 276660)
@@ -664,7 +664,6 @@
     bool deleteProperty(JSGlobalObject*, uint32_t propertyName);
     bool deleteProperty(JSGlobalObject*, uint64_t propertyName);
 
-    JS_EXPORT_PRIVATE static JSValue defaultValue(const JSObject*, JSGlobalObject*, PreferredPrimitiveType);
     JSValue ordinaryToPrimitive(JSGlobalObject*, PreferredPrimitiveType) const;
 
     JS_EXPORT_PRIVATE bool hasInstance(JSGlobalObject*, JSValue value, JSValue hasInstanceValue);

Modified: trunk/Source/_javascript_Core/runtime/Operations.cpp (276659 => 276660)


--- trunk/Source/_javascript_Core/runtime/Operations.cpp	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/Source/_javascript_Core/runtime/Operations.cpp	2021-04-27 20:56:19 UTC (rev 276660)
@@ -34,7 +34,6 @@
 
 NEVER_INLINE JSValue jsAddSlowCase(JSGlobalObject* globalObject, JSValue v1, JSValue v2)
 {
-    // exception for the Date exception in defaultValue()
     VM& vm = globalObject->vm();
     auto scope = DECLARE_THROW_SCOPE(vm);
     JSValue p1 = v1.toPrimitive(globalObject);

Modified: trunk/Source/WebCore/ChangeLog (276659 => 276660)


--- trunk/Source/WebCore/ChangeLog	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/Source/WebCore/ChangeLog	2021-04-27 20:56:19 UTC (rev 276660)
@@ -1,3 +1,27 @@
+2021-04-27  Alexey Shvayka  <[email protected]>
+
+        [JSC] Remove defaultValue() from the method table
+        https://bugs.webkit.org/show_bug.cgi?id=225032
+
+        Reviewed by Darin Adler.
+
+        Test: platform/mac/fast/dom/objc-wrapper-toprimitive.html
+
+        * bindings/js/JSPluginElementFunctions.cpp:
+        (WebCore::pluginElementCustomGetOwnPropertySlot):
+        * bridge/objc/objc_runtime.h:
+        * bridge/objc/objc_runtime.mm:
+        (JSC::Bindings::ObjcFallbackObjectImp::finishCreation):
+        (JSC::Bindings::ObjcFallbackObjectImp::getOwnPropertySlot):
+        (JSC::Bindings::JSC_DEFINE_HOST_FUNCTION):
+        (JSC::Bindings::ObjcFallbackObjectImp::defaultValue): Deleted.
+        * bridge/runtime_object.cpp:
+        (JSC::Bindings::RuntimeObject::finishCreation):
+        (JSC::Bindings::RuntimeObject::getOwnPropertySlot):
+        (JSC::Bindings::JSC_DEFINE_HOST_FUNCTION):
+        (JSC::Bindings::RuntimeObject::defaultValue): Deleted.
+        * bridge/runtime_object.h:
+
 2021-04-27  Chris Dumez  <[email protected]>
 
         Copy-on-write semantics should be an internal implementation detail of StorageMap

Modified: trunk/Source/WebCore/bindings/js/JSPluginElementFunctions.cpp (276659 => 276660)


--- trunk/Source/WebCore/bindings/js/JSPluginElementFunctions.cpp	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/Source/WebCore/bindings/js/JSPluginElementFunctions.cpp	2021-04-27 20:56:19 UTC (rev 276660)
@@ -114,10 +114,14 @@
 
 bool pluginElementCustomGetOwnPropertySlot(JSHTMLElement* element, JSGlobalObject* lexicalGlobalObject, PropertyName propertyName, PropertySlot& slot)
 {
+    VM& vm = lexicalGlobalObject->vm();
     slot.setIsTaintedByOpaqueObject();
 
+    if (propertyName.uid() == vm.propertyNames->toPrimitiveSymbol.impl())
+        return false;
+
     if (!element->globalObject()->world().isNormal()) {
-        JSC::JSValue proto = element->getPrototypeDirect(lexicalGlobalObject->vm());
+        JSValue proto = element->getPrototypeDirect(vm);
         if (proto.isObject() && JSC::jsCast<JSC::JSObject*>(asObject(proto))->hasProperty(lexicalGlobalObject, propertyName))
             return false;
     }

Modified: trunk/Source/WebCore/bridge/objc/objc_runtime.h (276659 => 276660)


--- trunk/Source/WebCore/bridge/objc/objc_runtime.h	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/Source/WebCore/bridge/objc/objc_runtime.h	2021-04-27 20:56:19 UTC (rev 276660)
@@ -124,6 +124,8 @@
         return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
     }
 
+    ObjcInstance* getInternalObjCInstance() const { return _instance.get(); }
+
 private:
     ObjcFallbackObjectImp(JSGlobalObject*, Structure*, ObjcInstance*, const String& propertyName);
     void finishCreation(JSGlobalObject*);
@@ -133,7 +135,6 @@
     static bool put(JSCell*, JSGlobalObject*, PropertyName, JSValue, PutPropertySlot&);
     static CallData getCallData(JSCell*);
     static bool deleteProperty(JSCell*, JSGlobalObject*, PropertyName, DeletePropertySlot&);
-    static JSValue defaultValue(const JSObject*, JSGlobalObject*, PreferredPrimitiveType);
 
     bool toBoolean(JSGlobalObject*) const; // FIXME: Currently this is broken because none of the superclasses are marked virtual. We need to solve this in the longer term.
 

Modified: trunk/Source/WebCore/bridge/objc/objc_runtime.mm (276659 => 276660)


--- trunk/Source/WebCore/bridge/objc/objc_runtime.mm	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/Source/WebCore/bridge/objc/objc_runtime.mm	2021-04-27 20:56:19 UTC (rev 276660)
@@ -45,6 +45,8 @@
 namespace JSC {
 namespace Bindings {
 
+static JSC_DECLARE_HOST_FUNCTION(convertObjCFallbackObjectToPrimitive);
+
 ClassStructPtr webScriptObjectClass()
 {
     static ClassStructPtr<WebScriptObject> webScriptObjectClass = NSClassFromString(@"WebScriptObject");
@@ -232,10 +234,16 @@
     VM& vm = globalObject->vm();
     Base::finishCreation(vm);
     ASSERT(inherits(vm, info()));
+    putDirect(vm, vm.propertyNames->toPrimitiveSymbol,
+        JSFunction::create(vm, globalObject, 0, "[Symbol.toPrimitive]"_s, convertObjCFallbackObjectToPrimitive),
+        static_cast<unsigned>(PropertyAttribute::DontEnum));
 }
 
-bool ObjcFallbackObjectImp::getOwnPropertySlot(JSObject*, JSGlobalObject*, PropertyName, PropertySlot& slot)
+bool ObjcFallbackObjectImp::getOwnPropertySlot(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot)
 {
+    VM& vm = globalObject->vm();
+    if (propertyName.uid() == vm.propertyNames->toPrimitiveSymbol.impl())
+        return JSObject::getOwnPropertySlot(object, globalObject, propertyName, slot);
     // keep the prototype from getting called instead of just returning false
     slot.setUndefined();
     return true;
@@ -301,11 +309,17 @@
     return false;
 }
 
-JSValue ObjcFallbackObjectImp::defaultValue(const JSObject* object, JSGlobalObject* lexicalGlobalObject, PreferredPrimitiveType)
+JSC_DEFINE_HOST_FUNCTION(convertObjCFallbackObjectToPrimitive, (JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame))
 {
     VM& vm = lexicalGlobalObject->vm();
-    const ObjcFallbackObjectImp* thisObject = jsCast<const ObjcFallbackObjectImp*>(object);
-    return thisObject->_instance->getValueOfUndefinedField(lexicalGlobalObject, Identifier::fromString(vm, thisObject->m_item));
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* thisObject = jsDynamicCast<ObjcFallbackObjectImp*>(vm, callFrame->thisValue());
+    if (!thisObject)
+        return throwVMTypeError(lexicalGlobalObject, scope, "ObjcFallbackObject[Symbol.toPrimitive] method called on incompatible |this| value."_s);
+
+    scope.release();
+    return JSValue::encode(thisObject->getInternalObjCInstance()->getValueOfUndefinedField(lexicalGlobalObject, Identifier::fromString(vm, thisObject->propertyName())));
 }
 
 bool ObjcFallbackObjectImp::toBoolean(JSGlobalObject*) const

Modified: trunk/Source/WebCore/bridge/runtime_object.cpp (276659 => 276660)


--- trunk/Source/WebCore/bridge/runtime_object.cpp	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/Source/WebCore/bridge/runtime_object.cpp	2021-04-27 20:56:19 UTC (rev 276660)
@@ -38,6 +38,7 @@
 
 WEBCORE_EXPORT const ClassInfo RuntimeObject::s_info = { "RuntimeObject", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(RuntimeObject) };
 
+static JSC_DECLARE_HOST_FUNCTION(convertRuntimeObjectToPrimitive);
 static JSC_DECLARE_HOST_FUNCTION(callRuntimeObject);
 static JSC_DECLARE_HOST_FUNCTION(callRuntimeConstructor);
 
@@ -55,6 +56,9 @@
 {
     Base::finishCreation(vm);
     ASSERT(inherits(vm, info()));
+    putDirect(vm, vm.propertyNames->toPrimitiveSymbol,
+        JSFunction::create(vm, globalObject(vm), 1, "[Symbol.toPrimitive]"_s, convertRuntimeObjectToPrimitive),
+        static_cast<unsigned>(PropertyAttribute::DontEnum));
 }
 
 void RuntimeObject::destroy(JSCell* cell)
@@ -143,6 +147,9 @@
         throwRuntimeObjectInvalidAccessError(lexicalGlobalObject, scope);
         return false;
     }
+
+    if (propertyName.uid() == vm.propertyNames->toPrimitiveSymbol.impl())
+        return JSObject::getOwnPropertySlot(thisObject, lexicalGlobalObject, propertyName, slot);
     
     RefPtr<Instance> instance = thisObject->m_instance;
 
@@ -213,21 +220,24 @@
     return false;
 }
 
-JSValue RuntimeObject::defaultValue(const JSObject* object, JSGlobalObject* lexicalGlobalObject, PreferredPrimitiveType hint)
+JSC_DEFINE_HOST_FUNCTION(convertRuntimeObjectToPrimitive, (JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame))
 {
     VM& vm = lexicalGlobalObject->vm();
     auto scope = DECLARE_THROW_SCOPE(vm);
 
-    const RuntimeObject* thisObject = jsCast<const RuntimeObject*>(object);
-    if (!thisObject->m_instance)
-        return throwRuntimeObjectInvalidAccessError(lexicalGlobalObject, scope);
-    
-    RefPtr<Instance> instance = thisObject->m_instance;
+    auto* thisObject = jsDynamicCast<RuntimeObject*>(vm, callFrame->thisValue());
+    if (!thisObject)
+        return throwVMTypeError(lexicalGlobalObject, scope, "RuntimeObject[Symbol.toPrimitive] method called on incompatible |this| value."_s);
+    auto instance = makeRefPtr(thisObject->getInternalInstance());
+    if (!instance)
+        return JSValue::encode(throwRuntimeObjectInvalidAccessError(lexicalGlobalObject, scope));
+    auto hint = toPreferredPrimitiveType(lexicalGlobalObject, callFrame->argument(0));
+    RETURN_IF_EXCEPTION(scope, { });
 
     instance->begin();
     JSValue result = instance->defaultValue(lexicalGlobalObject, hint);
     instance->end();
-    return result;
+    return JSValue::encode(result);
 }
 
 JSC_DEFINE_HOST_FUNCTION(callRuntimeObject, (JSGlobalObject* globalObject, CallFrame* callFrame))

Modified: trunk/Source/WebCore/bridge/runtime_object.h (276659 => 276660)


--- trunk/Source/WebCore/bridge/runtime_object.h	2021-04-27 20:55:02 UTC (rev 276659)
+++ trunk/Source/WebCore/bridge/runtime_object.h	2021-04-27 20:56:19 UTC (rev 276660)
@@ -60,7 +60,6 @@
     static bool getOwnPropertySlot(JSObject*, JSGlobalObject*, PropertyName, PropertySlot&);
     static bool put(JSCell*, JSGlobalObject*, PropertyName, JSValue, PutPropertySlot&);
     static bool deleteProperty(JSCell*, JSGlobalObject*, PropertyName, DeletePropertySlot&);
-    static JSValue defaultValue(const JSObject*, JSGlobalObject*, PreferredPrimitiveType);
     static CallData getCallData(JSCell*);
     static CallData getConstructData(JSCell*);
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to