[ES6] Reflect.set with receiver
authorutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 16 Mar 2016 13:59:43 +0000 (13:59 +0000)
committerutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 16 Mar 2016 13:59:43 +0000 (13:59 +0000)
https://bugs.webkit.org/show_bug.cgi?id=155294

Reviewed by Saam Barati.

Source/JavaScriptCore:

This patch introduces the receiver parameter support for Reflect.set.
Reflect.set can alter the receiver with arbitrary values.
Each property descriptor uses the receiver in [[Set]].

1) In the accessor descriptor case, the receiver is used as |this| value for setter calls.
2) In the data descriptor case, the actual property will be set onto the receiver objects.

The current put operation does not support the receiver that is different from the base object.
In particular, (2) case is not supported.
The naive implementation adds one more [[GetOwnProperty]] for the receiver per [[Set]] (9.1.9.1-4-c [1]), and it is unacceptable.
To keep the fast path efficiently, we fall back to the slow but generic implementation (ordinarySetSlow)
only when the receiver is altered.

We need not to change any JIT part, because the JS code cannot alter the receiver without Reflect.set.
The property accesses generated by the JIT code always have the receiver that is the same to the base object.
ProxyObject can alter the receiver, but this situation has no problem because ProxyObject disables Inline Caching.
NOTE: Generating Inline Caching for JSProxy (that is used for the Window proxy) is already disabled before this change.

[1]: https://tc39.github.io/ecma262/#sec-ordinaryset

* jsc.cpp:
(functionCreateProxy):
* runtime/GenericArgumentsInlines.h:
(JSC::GenericArguments<Type>::put):
* runtime/JSArray.cpp:
(JSC::JSArray::put):
* runtime/JSArrayBuffer.cpp:
(JSC::JSArrayBuffer::put):
* runtime/JSArrayBufferView.cpp:
(JSC::JSArrayBufferView::put):
* runtime/JSCJSValue.h:
* runtime/JSCJSValueInlines.h:
(JSC::isThisValueAltered):
* runtime/JSDataView.cpp:
(JSC::JSDataView::put):
* runtime/JSFunction.cpp:
(JSC::JSFunction::put):
* runtime/JSGenericTypedArrayViewInlines.h:
(JSC::JSGenericTypedArrayView<Adaptor>::put):
* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::put):
* runtime/JSObject.cpp:
(JSC::ordinarySetSlow):
(JSC::JSObject::putInlineSlow):
* runtime/JSObject.h:
* runtime/JSObjectInlines.h:
(JSC::JSObject::putInline):
* runtime/JSProxy.h:
(JSC::JSProxy::createStructure):
* runtime/Lookup.h:
(JSC::putEntry):
* runtime/PropertySlot.h:
* runtime/ProxyObject.cpp:
(JSC::ProxyObject::put):
* runtime/PutPropertySlot.h:
(JSC::PutPropertySlot::PutPropertySlot):
(JSC::PutPropertySlot::isCacheablePut):
(JSC::PutPropertySlot::isCacheableSetter):
(JSC::PutPropertySlot::isCacheableCustom):
(JSC::PutPropertySlot::isCustomAccessor):
(JSC::PutPropertySlot::disableCaching):
(JSC::PutPropertySlot::isCacheable):
* runtime/ReflectObject.cpp:
(JSC::reflectObjectSet):
* runtime/RegExpObject.cpp:
(JSC::RegExpObject::put):
(JSC::reject): Deleted.
* runtime/StringObject.cpp:
(JSC::StringObject::put):
* tests/es6.yaml:
* tests/stress/ordinary-set-exceptions.js: Added.
(shouldBe):
(shouldThrow):
(shouldThrow.set get var):
* tests/stress/proxy-set.js:
* tests/stress/reflect-set-proxy-set.js: Copied from Source/JavaScriptCore/tests/stress/proxy-set.js.
(shouldBe):
(unreachable):
(assert):
(throw.new.Error.let.handler.set 45):
(throw.new.Error):
(let.target.set x):
(let.target.get x):
(set let):
* tests/stress/reflect-set-receiver-proxy-set.js: Added.
(shouldBe):
(unreachable):
(assert):
(let.handler.set 45):
(catch):
(let.target.set x):
(let.target.get x):
(set let):
* tests/stress/reflect-set-with-global-proxy.js: Added.
(shouldBe):
(unreachable):
(get shouldBe):
(set shouldBe):
(set test1):
(set test2):
(set test3):
* tests/stress/reflect-set.js:
(shouldThrow):
(unreachable):
(get shouldBe):
(set shouldBe):
(receiverTestIndexed):
(set get Uint8Array):
(receiverCase): Deleted.
(proxyCase): Deleted.
(stringObjectCase.set get shouldBe): Deleted.
(regExpLastIndex): Deleted.

LayoutTests:

Currently, putDelegate (JSLocation is special case) and CustomIndexedSetter work as special setters.

* js/dom/reflect-set-onto-dom-expected.txt:
* js/dom/script-tests/reflect-set-onto-dom.js:

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

33 files changed:
LayoutTests/ChangeLog
LayoutTests/js/dom/reflect-set-onto-dom-expected.txt
LayoutTests/js/dom/script-tests/reflect-set-onto-dom.js
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/jsc.cpp
Source/JavaScriptCore/runtime/GenericArgumentsInlines.h
Source/JavaScriptCore/runtime/JSArray.cpp
Source/JavaScriptCore/runtime/JSArrayBuffer.cpp
Source/JavaScriptCore/runtime/JSArrayBufferView.cpp
Source/JavaScriptCore/runtime/JSCJSValue.h
Source/JavaScriptCore/runtime/JSCJSValueInlines.h
Source/JavaScriptCore/runtime/JSDataView.cpp
Source/JavaScriptCore/runtime/JSFunction.cpp
Source/JavaScriptCore/runtime/JSGenericTypedArrayViewInlines.h
Source/JavaScriptCore/runtime/JSGlobalObject.cpp
Source/JavaScriptCore/runtime/JSObject.cpp
Source/JavaScriptCore/runtime/JSObject.h
Source/JavaScriptCore/runtime/JSObjectInlines.h
Source/JavaScriptCore/runtime/JSProxy.h
Source/JavaScriptCore/runtime/Lookup.h
Source/JavaScriptCore/runtime/PropertySlot.h
Source/JavaScriptCore/runtime/ProxyObject.cpp
Source/JavaScriptCore/runtime/PutPropertySlot.h
Source/JavaScriptCore/runtime/ReflectObject.cpp
Source/JavaScriptCore/runtime/RegExpObject.cpp
Source/JavaScriptCore/runtime/StringObject.cpp
Source/JavaScriptCore/tests/es6.yaml
Source/JavaScriptCore/tests/stress/ordinary-set-exceptions.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/proxy-set.js
Source/JavaScriptCore/tests/stress/reflect-set-proxy-set.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/reflect-set-receiver-proxy-set.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/reflect-set-with-global-proxy.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/reflect-set.js

index e22373a..8c96c76 100644 (file)
@@ -1,3 +1,15 @@
+2016-03-16  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        [ES6] Reflect.set with receiver
+        https://bugs.webkit.org/show_bug.cgi?id=155294
+
+        Reviewed by Saam Barati.
+
+        Currently, putDelegate (JSLocation is special case) and CustomIndexedSetter work as special setters.
+
+        * js/dom/reflect-set-onto-dom-expected.txt:
+        * js/dom/script-tests/reflect-set-onto-dom.js:
+
 2016-03-15  Zalan Bujtas  <zalan@apple.com>
 
         Remove overflow: -webkit-marquee
index dc6d48d..57d4e56 100644 (file)
@@ -8,21 +8,36 @@ PASS Reflect.set(document.body, "scrollTop", "#Hello") is true
 PASS Reflect.get(document.body, "scrollTop") is not "#Hello"
 PASS Reflect.set(document.body, "scrollTop", 0) is true
 PASS Reflect.get(document.body, "scrollTop") is 0
+PASS Reflect.set(document.body, "scrollTop", "Cocoa", receiver) threw exception TypeError: The Element.scrollTop setter can only be used on instances of Element.
+PASS Reflect.get(document.body, "scrollTop") is 0
+PASS Reflect.get(receiver, "scrollTop") is undefined
 CustomAccessor with ReadOnly
 PASS Reflect.set(document, "compatMode", false) is false
 PASS Reflect.get(document, "compatMode") is "BackCompat"
+PASS Reflect.set(document, "compatMode", false, receiver) is false
+PASS Reflect.get(document, "compatMode") is "BackCompat"
+PASS Reflect.get(receiver, "compatMode") is undefined
 CustomAccessor without setter for DOM objects
 PASS Reflect.set(window.history, "length", "http://www.example.com") is false
 PASS Reflect.get(window.history, "length") is not "http://www.example.com"
+PASS Reflect.set(window.history, "length", "http://www.example.com", receiver) is false
+PASS Reflect.get(window.history, "length") is not "http://www.example.com"
+PASS Reflect.get(receiver, "length") is undefined
 CustomValue setters.
 PASS Reflect.set(window, "Event", "http://www.example.com") is true
 PASS Reflect.get(window, "Event") is "http://www.example.com"
+PASS Reflect.set(window, "MessageEvent", "http://www.example.com", receiver) is true
+PASS Reflect.get(window, "MessageEvent") is not "http://www.example.com"
+PASS Reflect.get(receiver, "MessageEvent") is "http://www.example.com"
 CustomValue setters with writable=false.
 PASS Reflect.defineProperty(window, "MouseEvent", {
     writable: false
 }) is true
 PASS Reflect.set(window, "MouseEvent", "http://www.example.com") is false
 PASS Reflect.get(window, "MouseEvent") is originalMouseEvent
+PASS Reflect.set(window, "MouseEvent", "http://www.example.com", receiver) is false
+PASS Reflect.get(window, "MouseEvent") is originalMouseEvent
+PASS Reflect.get(receiver, "MouseEvent") is undefined
 putDelegate CustomAccessor setters for DOM objects.
 PASS Reflect.set(document.location, "hash", "#Hello") is true
 PASS Reflect.get(document.location, "hash") is "#Hello"
@@ -32,23 +47,45 @@ PASS Reflect.set(document.location, 0, "#Hello") is true
 PASS Reflect.get(document.location, 0) is "#Hello"
 PASS Reflect.set(document.location, 0, "#OUT") is true
 PASS Reflect.get(document.location, 0) is "#OUT"
+PASS Reflect.set(document.location, "hash", "#Hello", receiver) threw exception TypeError: The Location.hash setter can only be used on instances of Location.
+PASS Reflect.get(document.location, "hash") is "#OUT"
+PASS Reflect.get(receiver, "hash") is undefined
+PASS Reflect.set(document.location, 0, "#Hello", receiver) is true
+PASS Reflect.get(document.location, 0) is "#OUT"
+PASS Reflect.get(receiver, 0) is "#Hello"
 putDelegate CustomAccessor without setter for DOM objects
 PASS Reflect.set(document.location, "origin", "http://www.example.com") is false
+PASS Reflect.get(document.location, "origin") is not "http://www.example.com"
+PASS Reflect.set(document.location, "origin", "http://www.example.com", receiver) is false
+PASS Reflect.get(document.location, "origin") is not "http://www.example.com"
+PASS Reflect.get(receiver, "origin") is undefined
 NPAPI object with Reflect.set
 PASS Reflect.set(npobject, "Cocoa", "Sweet") is true
 PASS Reflect.get(npobject, "Cocoa") is "Sweet"
+PASS Reflect.set(npobject, "Cocoa", "OK", receiver) is true
+PASS Reflect.get(npobject, "Cocoa") is "Sweet"
+PASS Reflect.get(receiver, "Cocoa") is "OK"
 PASS Reflect.defineProperty(npobject, "Cocoa", {
     writable: false
 }) is true
 PASS Reflect.set(npobject, "Cocoa", "Hello") is false
 PASS Reflect.get(npobject, "Cocoa") is "Sweet"
+PASS Reflect.set(npobject, "Cocoa", "OK", receiver) is false
+PASS Reflect.get(npobject, "Cocoa") is "Sweet"
+PASS Reflect.get(receiver, "Cocoa") is undefined
 PASS Reflect.set(npobject, 0, "Sweet") is true
 PASS Reflect.get(npobject, 0) is "Sweet"
+PASS Reflect.set(npobject, 0, "OK", receiver) is true
+PASS Reflect.get(npobject, 0) is "Sweet"
+PASS Reflect.get(receiver, 0) is "OK"
 PASS Reflect.defineProperty(npobject, 0, {
     writable: false
 }) is true
 PASS Reflect.set(npobject, 0, "Hello") is false
 PASS Reflect.get(npobject, 0) is "Sweet"
+PASS Reflect.set(npobject, 0, "OK", receiver) is false
+PASS Reflect.get(npobject, 0) is "Sweet"
+PASS Reflect.get(receiver, 0) is undefined
 DOMStringMap
 PASS Reflect.get(document.body.dataset, "cocoa") is "cappuccino"
 PASS Reflect.set(document.body.dataset, "cocoa", "sweet") is true
@@ -56,6 +93,13 @@ PASS Reflect.get(document.body.dataset, "cocoa") is "sweet"
 PASS Reflect.set(document.body.dataset, "cocoa-cappuccino", "sweet") threw exception Error: SyntaxError: DOM Exception 12.
 PASS Reflect.set(document.body.dataset, 0, "sweet") is true
 PASS Reflect.get(document.body.dataset, 0) is "sweet"
+DOMStringMap ignores the receiver. These putDelegate only work with ::put (not ::defineOwnProperty). So they behave as the special setter, we should not fallback to the OrdinarySet.
+PASS Reflect.set(document.body.dataset, "cocoa", "ok", receiver) is true
+PASS Reflect.get(document.body.dataset, "cocoa") is "ok"
+PASS Reflect.get(receiver, "cocoa") is undefined
+PASS Reflect.set(document.body.dataset, 0, "ok", receiver) is true
+PASS Reflect.get(document.body.dataset, 0) is "ok"
+PASS Reflect.get(receiver, 0) is undefined
 CustomIndexedSetter
 PASS Reflect.get(select, 0).value is "cocoa"
 PASS Reflect.get(select, "length") is 3
@@ -67,6 +111,10 @@ PASS Reflect.get(select, "length") is 42
 PASS Reflect.set(select, 44, option2) is true
 PASS Reflect.get(select, 44).value is "kilimanjaro"
 PASS Reflect.set(select, 20, "Kilimanjaro") threw exception Error: TypeMismatchError: DOM Exception 17.
+CustomIndexedSetter ignores the receiver. These methods only work with ::put (not ::defineOwnProperty). So they behave as the special setter, we should not fallback to the OrdinarySet.
+PASS Reflect.set(select, 0, option3, receiver) is true
+PASS Reflect.get(receiver, 0) is undefined
+PASS Reflect.get(select, 0) is option3
 PASS successfullyParsed is true
 
 TEST COMPLETE
index e769644..28abea1 100644 (file)
@@ -5,18 +5,34 @@ shouldBe(`Reflect.set(document.body, "scrollTop", "#Hello")`, `true`);
 shouldNotBe(`Reflect.get(document.body, "scrollTop")`, `"#Hello"`);
 shouldBe(`Reflect.set(document.body, "scrollTop", 0)`, `true`);
 shouldBe(`Reflect.get(document.body, "scrollTop")`, `0`);
+var receiver = {};
+shouldThrow(`Reflect.set(document.body, "scrollTop", "Cocoa", receiver)`);
+shouldBe(`Reflect.get(document.body, "scrollTop")`, `0`);
+shouldBe(`Reflect.get(receiver, "scrollTop")`, `undefined`);
 
 debug("CustomAccessor with ReadOnly");
 shouldBe(`Reflect.set(document, "compatMode", false)`, `false`);
 shouldBe(`Reflect.get(document, "compatMode")`, `"BackCompat"`);
+var receiver = {};
+shouldBe(`Reflect.set(document, "compatMode", false, receiver)`, `false`);
+shouldBe(`Reflect.get(document, "compatMode")`, `"BackCompat"`);
+shouldBe(`Reflect.get(receiver, "compatMode")`, `undefined`);
 
 debug("CustomAccessor without setter for DOM objects");
 shouldBe(`Reflect.set(window.history, "length", "http://www.example.com")`, `false`);
 shouldNotBe(`Reflect.get(window.history, "length")`, `"http://www.example.com"`);
+var receiver = {};
+shouldBe(`Reflect.set(window.history, "length", "http://www.example.com", receiver)`, `false`);
+shouldNotBe(`Reflect.get(window.history, "length")`, `"http://www.example.com"`);
+shouldBe(`Reflect.get(receiver, "length")`, `undefined`);
 
 debug("CustomValue setters.");
 shouldBe(`Reflect.set(window, "Event", "http://www.example.com")`, `true`);
 shouldBe(`Reflect.get(window, "Event")`,  `"http://www.example.com"`);
+var receiver = {};
+shouldBe(`Reflect.set(window, "MessageEvent", "http://www.example.com", receiver)`, `true`);
+shouldNotBe(`Reflect.get(window, "MessageEvent")`, `"http://www.example.com"`);
+shouldBe(`Reflect.get(receiver, "MessageEvent")`, `"http://www.example.com"`);
 
 debug("CustomValue setters with writable=false.");
 var originalMouseEvent = MouseEvent;
@@ -25,6 +41,10 @@ shouldBe(`Reflect.defineProperty(window, "MouseEvent", {
 })`, `true`);
 shouldBe(`Reflect.set(window, "MouseEvent", "http://www.example.com")`, `false`);
 shouldBe(`Reflect.get(window, "MouseEvent")`, `originalMouseEvent`);
+var receiver = {};
+shouldBe(`Reflect.set(window, "MouseEvent", "http://www.example.com", receiver)`, `false`);
+shouldBe(`Reflect.get(window, "MouseEvent")`, `originalMouseEvent`);
+shouldBe(`Reflect.get(receiver, "MouseEvent")`, `undefined`);
 
 debug("putDelegate CustomAccessor setters for DOM objects.");
 shouldBe(`Reflect.set(document.location, "hash", "#Hello")`, `true`);
@@ -35,26 +55,57 @@ shouldBe(`Reflect.set(document.location, 0, "#Hello")`, `true`);
 shouldBe(`Reflect.get(document.location, 0)`, `"#Hello"`);
 shouldBe(`Reflect.set(document.location, 0, "#OUT")`, `true`);
 shouldBe(`Reflect.get(document.location, 0)`, `"#OUT"`);
+var receiver = {};
+shouldThrow(`Reflect.set(document.location, "hash", "#Hello", receiver)`);
+shouldBe(`Reflect.get(document.location, "hash")`, `"#OUT"`);
+shouldBe(`Reflect.get(receiver, "hash")`, `undefined`);
+shouldBe(`Reflect.set(document.location, 0, "#Hello", receiver)`, `true`);
+shouldBe(`Reflect.get(document.location, 0)`, `"#OUT"`);
+shouldBe(`Reflect.get(receiver, 0)`, `"#Hello"`);
 
 debug("putDelegate CustomAccessor without setter for DOM objects");
 shouldBe(`Reflect.set(document.location, "origin", "http://www.example.com")`, `false`);
+shouldNotBe(`Reflect.get(document.location, "origin")`, `"http://www.example.com"`);
+var receiver = {};
+shouldBe(`Reflect.set(document.location, "origin", "http://www.example.com", receiver)`, `false`);
+shouldNotBe(`Reflect.get(document.location, "origin")`, `"http://www.example.com"`);
+shouldBe(`Reflect.get(receiver, "origin")`, `undefined`);
 
 debug("NPAPI object with Reflect.set");
 var npobject = document.getElementById("testPlugin");
 shouldBe(`Reflect.set(npobject, "Cocoa", "Sweet")`, `true`);
 shouldBe(`Reflect.get(npobject, "Cocoa")`, `"Sweet"`);
+var receiver = {};
+shouldBe(`Reflect.set(npobject, "Cocoa", "OK", receiver)`, `true`);
+shouldBe(`Reflect.get(npobject, "Cocoa")`, `"Sweet"`);
+shouldBe(`Reflect.get(receiver, "Cocoa")`, `"OK"`);
+
 shouldBe(`Reflect.defineProperty(npobject, "Cocoa", {
     writable: false
 })`, `true`);
 shouldBe(`Reflect.set(npobject, "Cocoa", "Hello")`, `false`);
 shouldBe(`Reflect.get(npobject, "Cocoa")`, `"Sweet"`);
+var receiver = {};
+shouldBe(`Reflect.set(npobject, "Cocoa", "OK", receiver)`, `false`);
+shouldBe(`Reflect.get(npobject, "Cocoa")`, `"Sweet"`);
+shouldBe(`Reflect.get(receiver, "Cocoa")`, `undefined`);
+
 shouldBe(`Reflect.set(npobject, 0, "Sweet")`, `true`);
 shouldBe(`Reflect.get(npobject, 0)`, `"Sweet"`);
+var receiver = {};
+shouldBe(`Reflect.set(npobject, 0, "OK", receiver)`, `true`);
+shouldBe(`Reflect.get(npobject, 0)`, `"Sweet"`);
+shouldBe(`Reflect.get(receiver, 0)`, `"OK"`);
+
 shouldBe(`Reflect.defineProperty(npobject, 0, {
     writable: false
 })`, `true`);
 shouldBe(`Reflect.set(npobject, 0, "Hello")`, `false`);
 shouldBe(`Reflect.get(npobject, 0)`, `"Sweet"`);
+var receiver = {};
+shouldBe(`Reflect.set(npobject, 0, "OK", receiver)`, `false`);
+shouldBe(`Reflect.get(npobject, 0)`, `"Sweet"`);
+shouldBe(`Reflect.get(receiver, 0)`, `undefined`);
 
 debug("DOMStringMap");
 document.body.setAttribute("data-cocoa", "cappuccino");
@@ -65,6 +116,16 @@ shouldThrow(`Reflect.set(document.body.dataset, "cocoa-cappuccino", "sweet")`);
 shouldBe(`Reflect.set(document.body.dataset, 0, "sweet")`, `true`);
 shouldBe(`Reflect.get(document.body.dataset, 0)`, `"sweet"`);
 
+debug("DOMStringMap ignores the receiver. These putDelegate only work with ::put (not ::defineOwnProperty). So they behave as the special setter, we should not fallback to the OrdinarySet.");
+var receiver = {};
+shouldBe(`Reflect.set(document.body.dataset, "cocoa", "ok", receiver)`, `true`);
+shouldBe(`Reflect.get(document.body.dataset, "cocoa")`, `"ok"`);
+shouldBe(`Reflect.get(receiver, "cocoa")`, `undefined`);
+var receiver = {};
+shouldBe(`Reflect.set(document.body.dataset, 0, "ok", receiver)`, `true`);
+shouldBe(`Reflect.get(document.body.dataset, 0)`, `"ok"`);
+shouldBe(`Reflect.get(receiver, 0)`, `undefined`);
+
 debug("CustomIndexedSetter");
 var select = document.getElementById("testSelect");
 shouldBe(`Reflect.get(select, 0).value`, `"cocoa"`);
@@ -84,3 +145,12 @@ option2.textContent = "Kilimanjaro";
 shouldBe(`Reflect.set(select, 44, option2)`, `true`);
 shouldBe(`Reflect.get(select, 44).value`, `"kilimanjaro"`);
 shouldThrow(`Reflect.set(select, 20, "Kilimanjaro")`);
+
+debug("CustomIndexedSetter ignores the receiver. These methods only work with ::put (not ::defineOwnProperty). So they behave as the special setter, we should not fallback to the OrdinarySet.");
+var option3 = document.createElement("option");
+option3.value = "rabbit";
+option3.textContent = "Rabbit";
+var receiver = {};
+shouldBe(`Reflect.set(select, 0, option3, receiver)`, `true`);
+shouldBe(`Reflect.get(receiver, 0)`, `undefined`);
+shouldBe(`Reflect.get(select, 0)`, `option3`);
index cd559cd..39833fd 100644 (file)
@@ -1,3 +1,123 @@
+2016-03-16  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        [ES6] Reflect.set with receiver
+        https://bugs.webkit.org/show_bug.cgi?id=155294
+
+        Reviewed by Saam Barati.
+
+        This patch introduces the receiver parameter support for Reflect.set.
+        Reflect.set can alter the receiver with arbitrary values.
+        Each property descriptor uses the receiver in [[Set]].
+
+        1) In the accessor descriptor case, the receiver is used as |this| value for setter calls.
+        2) In the data descriptor case, the actual property will be set onto the receiver objects.
+
+        The current put operation does not support the receiver that is different from the base object.
+        In particular, (2) case is not supported.
+        The naive implementation adds one more [[GetOwnProperty]] for the receiver per [[Set]] (9.1.9.1-4-c [1]), and it is unacceptable.
+        To keep the fast path efficiently, we fall back to the slow but generic implementation (ordinarySetSlow)
+        only when the receiver is altered.
+
+        We need not to change any JIT part, because the JS code cannot alter the receiver without Reflect.set.
+        The property accesses generated by the JIT code always have the receiver that is the same to the base object.
+        ProxyObject can alter the receiver, but this situation has no problem because ProxyObject disables Inline Caching.
+        NOTE: Generating Inline Caching for JSProxy (that is used for the Window proxy) is already disabled before this change.
+
+        [1]: https://tc39.github.io/ecma262/#sec-ordinaryset
+
+        * jsc.cpp:
+        (functionCreateProxy):
+        * runtime/GenericArgumentsInlines.h:
+        (JSC::GenericArguments<Type>::put):
+        * runtime/JSArray.cpp:
+        (JSC::JSArray::put):
+        * runtime/JSArrayBuffer.cpp:
+        (JSC::JSArrayBuffer::put):
+        * runtime/JSArrayBufferView.cpp:
+        (JSC::JSArrayBufferView::put):
+        * runtime/JSCJSValue.h:
+        * runtime/JSCJSValueInlines.h:
+        (JSC::isThisValueAltered):
+        * runtime/JSDataView.cpp:
+        (JSC::JSDataView::put):
+        * runtime/JSFunction.cpp:
+        (JSC::JSFunction::put):
+        * runtime/JSGenericTypedArrayViewInlines.h:
+        (JSC::JSGenericTypedArrayView<Adaptor>::put):
+        * runtime/JSGlobalObject.cpp:
+        (JSC::JSGlobalObject::put):
+        * runtime/JSObject.cpp:
+        (JSC::ordinarySetSlow):
+        (JSC::JSObject::putInlineSlow):
+        * runtime/JSObject.h:
+        * runtime/JSObjectInlines.h:
+        (JSC::JSObject::putInline):
+        * runtime/JSProxy.h:
+        (JSC::JSProxy::createStructure):
+        * runtime/Lookup.h:
+        (JSC::putEntry):
+        * runtime/PropertySlot.h:
+        * runtime/ProxyObject.cpp:
+        (JSC::ProxyObject::put):
+        * runtime/PutPropertySlot.h:
+        (JSC::PutPropertySlot::PutPropertySlot):
+        (JSC::PutPropertySlot::isCacheablePut):
+        (JSC::PutPropertySlot::isCacheableSetter):
+        (JSC::PutPropertySlot::isCacheableCustom):
+        (JSC::PutPropertySlot::isCustomAccessor):
+        (JSC::PutPropertySlot::disableCaching):
+        (JSC::PutPropertySlot::isCacheable):
+        * runtime/ReflectObject.cpp:
+        (JSC::reflectObjectSet):
+        * runtime/RegExpObject.cpp:
+        (JSC::RegExpObject::put):
+        (JSC::reject): Deleted.
+        * runtime/StringObject.cpp:
+        (JSC::StringObject::put):
+        * tests/es6.yaml:
+        * tests/stress/ordinary-set-exceptions.js: Added.
+        (shouldBe):
+        (shouldThrow):
+        (shouldThrow.set get var):
+        * tests/stress/proxy-set.js:
+        * tests/stress/reflect-set-proxy-set.js: Copied from Source/JavaScriptCore/tests/stress/proxy-set.js.
+        (shouldBe):
+        (unreachable):
+        (assert):
+        (throw.new.Error.let.handler.set 45):
+        (throw.new.Error):
+        (let.target.set x):
+        (let.target.get x):
+        (set let):
+        * tests/stress/reflect-set-receiver-proxy-set.js: Added.
+        (shouldBe):
+        (unreachable):
+        (assert):
+        (let.handler.set 45):
+        (catch):
+        (let.target.set x):
+        (let.target.get x):
+        (set let):
+        * tests/stress/reflect-set-with-global-proxy.js: Added.
+        (shouldBe):
+        (unreachable):
+        (get shouldBe):
+        (set shouldBe):
+        (set test1):
+        (set test2):
+        (set test3):
+        * tests/stress/reflect-set.js:
+        (shouldThrow):
+        (unreachable):
+        (get shouldBe):
+        (set shouldBe):
+        (receiverTestIndexed):
+        (set get Uint8Array):
+        (receiverCase): Deleted.
+        (proxyCase): Deleted.
+        (stringObjectCase.set get shouldBe): Deleted.
+        (regExpLastIndex): Deleted.
+
 2016-03-15  Benjamin Poulain  <bpoulain@apple.com>
 
         [JSC] Remove hint from SlowCaseEntry
index a5cf5b0..b0481a4 100644 (file)
@@ -1229,7 +1229,7 @@ EncodedJSValue JSC_HOST_CALL functionCreateProxy(ExecState* exec)
     if (!target.isObject())
         return JSValue::encode(jsUndefined());
     JSObject* jsTarget = asObject(target.asCell());
-    Structure* structure = JSProxy::createStructure(exec->vm(), exec->lexicalGlobalObject(), jsTarget->getPrototypeDirect());
+    Structure* structure = JSProxy::createStructure(exec->vm(), exec->lexicalGlobalObject(), jsTarget->getPrototypeDirect(), ImpureProxyType);
     JSProxy* proxy = JSProxy::create(exec->vm(), structure, jsTarget);
     return JSValue::encode(proxy);
 }
index d2eb371..654fb54 100644 (file)
@@ -110,6 +110,11 @@ bool GenericArguments<Type>::put(JSCell* cell, ExecState* exec, PropertyName ide
         PutPropertySlot dummy = slot; // This put is not cacheable, so we shadow the slot that was given to us.
         return Base::put(thisObject, exec, ident, value, dummy);
     }
+
+    // https://tc39.github.io/ecma262/#sec-arguments-exotic-objects-set-p-v-receiver
+    // Fall back to the OrdinarySet when the receiver is altered from the thisObject.
+    if (UNLIKELY(isThisValueAltered(slot, thisObject)))
+        return ordinarySetSlow(exec, thisObject, ident, value, slot.thisValue(), slot.isStrictMode());
     
     Optional<uint32_t> index = parseIndex(ident);
     if (index && thisObject->canAccessIndexQuickly(index.value())) {
index 403a0c9..47ca566 100644 (file)
@@ -194,6 +194,9 @@ bool JSArray::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSVa
 {
     JSArray* thisObject = jsCast<JSArray*>(cell);
 
+    if (UNLIKELY(isThisValueAltered(slot, thisObject)))
+        return ordinarySetSlow(exec, thisObject, propertyName, value, slot.thisValue(), slot.isStrictMode());
+
     if (propertyName == exec->propertyNames().length) {
         unsigned newLength = value.toUInt32(exec);
         if (value.toNumber(exec) != static_cast<double>(newLength)) {
index 6dde4c7..a762ad9 100644 (file)
@@ -84,6 +84,9 @@ bool JSArrayBuffer::put(
     PutPropertySlot& slot)
 {
     JSArrayBuffer* thisObject = jsCast<JSArrayBuffer*>(cell);
+
+    if (UNLIKELY(isThisValueAltered(slot, thisObject)))
+        return ordinarySetSlow(exec, thisObject, propertyName, value, slot.thisValue(), slot.isStrictMode());
     
     if (propertyName == exec->propertyNames().byteLength)
         return reject(exec, slot.isStrictMode(), "Attempting to write to a read-only array buffer property.");
index 1546a34..746448a 100644 (file)
@@ -169,6 +169,10 @@ bool JSArrayBufferView::put(
     PutPropertySlot& slot)
 {
     JSArrayBufferView* thisObject = jsCast<JSArrayBufferView*>(cell);
+
+    if (UNLIKELY(isThisValueAltered(slot, thisObject)))
+        return ordinarySetSlow(exec, thisObject, propertyName, value, slot.thisValue(), slot.isStrictMode());
+
     if (propertyName == exec->propertyNames().buffer)
         return reject(exec, slot.isStrictMode(), "Attempting to write to read-only typed array property.");
     
index 7b88822..ef4d66a 100644 (file)
@@ -585,6 +585,9 @@ inline bool operator==(const JSCell* a, const JSValue b) { return JSValue(a) ==
 inline bool operator!=(const JSValue a, const JSCell* b) { return a != JSValue(b); }
 inline bool operator!=(const JSCell* a, const JSValue b) { return JSValue(a) != b; }
 
+
+bool isThisValueAltered(const PutPropertySlot&, JSObject* baseObject);
+
 } // namespace JSC
 
 #endif // JSCJSValue_h
index c09ab58..91999f3 100644 (file)
@@ -1009,6 +1009,21 @@ ALWAYS_INLINE bool JSValue::requireObjectCoercible(ExecState* exec) const
     return false;
 }
 
+ALWAYS_INLINE bool isThisValueAltered(const PutPropertySlot& slot, JSObject* baseObject)
+{
+    JSValue thisValue = slot.thisValue();
+    if (LIKELY(thisValue == baseObject))
+        return false;
+
+    if (!thisValue.isObject())
+        return true;
+    JSObject* thisObject = asObject(thisValue);
+    // Only PureForwardingProxyType can be seen as the same to the original target object.
+    if (thisObject->type() == PureForwardingProxyType && jsCast<JSProxy*>(thisObject)->target() == baseObject)
+        return false;
+    return true;
+}
+
 } // namespace JSC
 
 #endif // JSValueInlines_h
index 90c64ed..09c5cdd 100644 (file)
@@ -116,6 +116,10 @@ bool JSDataView::put(
     PutPropertySlot& slot)
 {
     JSDataView* thisObject = jsCast<JSDataView*>(cell);
+
+    if (UNLIKELY(isThisValueAltered(slot, thisObject)))
+        return ordinarySetSlow(exec, thisObject, propertyName, value, slot.thisValue(), slot.isStrictMode());
+
     if (propertyName == exec->propertyNames().byteLength
         || propertyName == exec->propertyNames().byteOffset)
         return reject(exec, slot.isStrictMode(), "Attempting to write to read-only typed array property.");
index 52ca40d..1a4e70c 100644 (file)
@@ -427,6 +427,10 @@ void JSFunction::getOwnNonIndexPropertyNames(JSObject* object, ExecState* exec,
 bool JSFunction::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
 {
     JSFunction* thisObject = jsCast<JSFunction*>(cell);
+
+    if (UNLIKELY(isThisValueAltered(slot, thisObject)))
+        return ordinarySetSlow(exec, thisObject, propertyName, value, slot.thisValue(), slot.isStrictMode());
+
     if (thisObject->isHostOrBuiltinFunction())
         return Base::put(thisObject, exec, propertyName, value, slot);
 
index a0d8d19..131ba5f 100644 (file)
@@ -309,6 +309,9 @@ bool JSGenericTypedArrayView<Adaptor>::put(
 {
     JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(cell);
     
+    // https://tc39.github.io/ecma262/#sec-integer-indexed-exotic-objects-set-p-v-receiver
+    // Ignore the receiver even if the receiver is altered to non base value.
+    // 9.4.5.5-2-b-i Return ? IntegerIndexedElementSet(O, numericIndex, V).
     if (Optional<uint32_t> index = parseIndex(propertyName))
         return putByIndex(thisObject, exec, index.value(), value, slot.isStrictMode());
     
index 7875230..64060db 100644 (file)
@@ -635,6 +635,9 @@ bool JSGlobalObject::put(JSCell* cell, ExecState* exec, PropertyName propertyNam
     JSGlobalObject* thisObject = jsCast<JSGlobalObject*>(cell);
     ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(thisObject));
 
+    if (UNLIKELY(isThisValueAltered(slot, thisObject)))
+        return ordinarySetSlow(exec, thisObject, propertyName, value, slot.thisValue(), slot.isStrictMode());
+
     bool shouldThrowReadOnlyError = slot.isStrictMode();
     bool ignoreReadOnlyErrors = false;
     bool putResult = false;
index 3a841f8..8054f92 100644 (file)
@@ -417,6 +417,113 @@ bool JSObject::getOwnPropertySlotByIndex(JSObject* thisObject, ExecState* exec,
     return false;
 }
 
+// https://tc39.github.io/ecma262/#sec-ordinaryset
+bool ordinarySetSlow(ExecState* exec, JSObject* object, PropertyName propertyName, JSValue value, JSValue receiver, bool shouldThrow)
+{
+    // If we find the receiver is not the same to the object, we fall to this slow path.
+    // Currently, there are 3 candidates.
+    // 1. Reflect.set can alter the receiver with an arbitrary value.
+    // 2. Window Proxy.
+    // 3. ES6 Proxy.
+
+    VM& vm = exec->vm();
+    JSObject* current = object;
+    PropertyDescriptor ownDescriptor;
+    while (true) {
+        if (current->type() == ProxyObjectType && propertyName != vm.propertyNames->underscoreProto) {
+            ProxyObject* proxy = jsCast<ProxyObject*>(current);
+            PutPropertySlot slot(receiver, shouldThrow);
+            return proxy->ProxyObject::put(proxy, exec, propertyName, value, slot);
+        }
+
+        // 9.1.9.1-2 Let ownDesc be ? O.[[GetOwnProperty]](P).
+        bool ownDescriptorFound = current->getOwnPropertyDescriptor(exec, propertyName, ownDescriptor);
+        if (UNLIKELY(vm.exception()))
+            return false;
+
+        if (!ownDescriptorFound) {
+            // 9.1.9.1-3-a Let parent be ? O.[[GetPrototypeOf]]().
+            JSValue prototype = current->getPrototype(vm, exec);
+            if (UNLIKELY(vm.exception()))
+                return false;
+
+            // 9.1.9.1-3-b If parent is not null, then
+            if (!prototype.isNull()) {
+                // 9.1.9.1-3-b-i Return ? parent.[[Set]](P, V, Receiver).
+                current = asObject(prototype);
+                continue;
+            }
+            // 9.1.9.1-3-c-i Let ownDesc be the PropertyDescriptor{[[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}.
+            ownDescriptor = PropertyDescriptor(jsUndefined(), None);
+        }
+        break;
+    }
+
+    // 9.1.9.1-4 If IsDataDescriptor(ownDesc) is true, then
+    if (ownDescriptor.isDataDescriptor()) {
+        // 9.1.9.1-4-a If ownDesc.[[Writable]] is false, return false.
+        if (!ownDescriptor.writable())
+            return reject(exec, shouldThrow, StrictModeReadonlyPropertyWriteError);
+
+        // 9.1.9.1-4-b If Type(Receiver) is not Object, return false.
+        if (!receiver.isObject())
+            return reject(exec, shouldThrow, StrictModeReadonlyPropertyWriteError);
+
+        // In OrdinarySet, the receiver may not be the same to the object.
+        // So, we perform [[GetOwnProperty]] onto the receiver while we already perform [[GetOwnProperty]] onto the object.
+
+        // 9.1.9.1-4-c Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P).
+        JSObject* receiverObject = asObject(receiver);
+        PropertyDescriptor existingDescriptor;
+        bool existingDescriptorFound = receiverObject->getOwnPropertyDescriptor(exec, propertyName, existingDescriptor);
+        if (UNLIKELY(vm.exception()))
+            return false;
+
+        // 9.1.9.1-4-d If existingDescriptor is not undefined, then
+        if (existingDescriptorFound) {
+            // 9.1.9.1-4-d-i If IsAccessorDescriptor(existingDescriptor) is true, return false.
+            if (existingDescriptor.isAccessorDescriptor())
+                return reject(exec, shouldThrow, StrictModeReadonlyPropertyWriteError);
+
+            // 9.1.9.1-4-d-ii If existingDescriptor.[[Writable]] is false, return false.
+            if (!existingDescriptor.writable())
+                return reject(exec, shouldThrow, StrictModeReadonlyPropertyWriteError);
+
+            // 9.1.9.1-4-d-iii Let valueDesc be the PropertyDescriptor{[[Value]]: V}.
+            PropertyDescriptor valueDescriptor;
+            valueDescriptor.setValue(value);
+
+            // 9.1.9.1-4-d-iv Return ? Receiver.[[DefineOwnProperty]](P, valueDesc).
+            return receiverObject->methodTable(vm)->defineOwnProperty(receiverObject, exec, propertyName, valueDescriptor, shouldThrow);
+        }
+
+        // 9.1.9.1-4-e Else Receiver does not currently have a property P,
+        // 9.1.9.1-4-e-i Return ? CreateDataProperty(Receiver, P, V).
+        return receiverObject->methodTable(vm)->defineOwnProperty(receiverObject, exec, propertyName, PropertyDescriptor(value, None), shouldThrow);
+    }
+
+    // 9.1.9.1-5 Assert: IsAccessorDescriptor(ownDesc) is true.
+    ASSERT(ownDescriptor.isAccessorDescriptor());
+
+    // 9.1.9.1-6 Let setter be ownDesc.[[Set]].
+    // 9.1.9.1-7 If setter is undefined, return false.
+    JSValue setter = ownDescriptor.setter();
+    if (!setter.isObject())
+        return reject(exec, shouldThrow, StrictModeReadonlyPropertyWriteError);
+
+    // 9.1.9.1-8 Perform ? Call(setter, Receiver, << V >>).
+    JSObject* setterObject = asObject(setter);
+    MarkedArgumentBuffer args;
+    args.append(value);
+
+    CallData callData;
+    CallType callType = setterObject->methodTable(exec->vm())->getCallData(setterObject, callData);
+    call(exec, setterObject, callType, callData, receiver, args);
+
+    // 9.1.9.1-9 Return true.
+    return true;
+}
+
 // ECMA 8.6.2.2
 bool JSObject::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
 {
@@ -425,6 +532,8 @@ bool JSObject::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSV
 
 bool JSObject::putInlineSlow(ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
 {
+    ASSERT(!isThisValueAltered(slot, this));
+
     VM& vm = exec->vm();
 
     JSObject* obj = this;
@@ -434,14 +543,12 @@ bool JSObject::putInlineSlow(ExecState* exec, PropertyName propertyName, JSValue
         if (isValidOffset(offset)) {
             if (attributes & ReadOnly) {
                 ASSERT(structure(vm)->prototypeChainMayInterceptStoreTo(exec->vm(), propertyName) || obj == this);
-                if (slot.isStrictMode())
-                    exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral(StrictModeReadonlyPropertyWriteError)));
-                return false;
+                return reject(exec, slot.isStrictMode(), StrictModeReadonlyPropertyWriteError);
             }
 
             JSValue gs = obj->getDirect(offset);
             if (gs.isGetterSetter()) {
-                bool result = callSetter(exec, this, gs, value, slot.isStrictMode() ? StrictMode : NotStrictMode);
+                bool result = callSetter(exec, slot.thisValue(), gs, value, slot.isStrictMode() ? StrictMode : NotStrictMode);
                 if (!structure()->isDictionary())
                     slot.setCacheableSetter(obj, offset);
                 return result;
@@ -478,13 +585,10 @@ bool JSObject::putInlineSlow(ExecState* exec, PropertyName propertyName, JSValue
             break;
         obj = asObject(prototype);
     }
-    
+
     ASSERT(!structure(vm)->prototypeChainMayInterceptStoreTo(exec->vm(), propertyName) || obj == this);
-    if (!putDirectInternal<PutModePut>(vm, propertyName, value, 0, slot)) {
-        if (slot.isStrictMode())
-            throwTypeError(exec, ASCIILiteral(StrictModeReadonlyPropertyWriteError));
-        return false;
-    }
+    if (!putDirectInternal<PutModePut>(vm, propertyName, value, 0, slot))
+        return reject(exec, slot.isStrictMode(), StrictModeReadonlyPropertyWriteError);
     return true;
 }
 
index 0b7258a..547eb1f 100644 (file)
@@ -175,6 +175,7 @@ public:
     static bool putInline(JSCell*, ExecState*, PropertyName, JSValue, PutPropertySlot&);
     
     JS_EXPORT_PRIVATE static bool put(JSCell*, ExecState*, PropertyName, JSValue, PutPropertySlot&);
+    // putByIndex assumes that the receiver is this JSCell object.
     JS_EXPORT_PRIVATE static bool putByIndex(JSCell*, ExecState*, unsigned propertyName, JSValue, bool shouldThrow);
         
     ALWAYS_INLINE bool putByIndexInline(ExecState* exec, unsigned propertyName, JSValue value, bool shouldThrow)
@@ -1626,6 +1627,8 @@ void createListFromArrayLike(ExecState* exec, JSValue arrayLikeValue, RuntimeTyp
 bool validateAndApplyPropertyDescriptor(ExecState*, JSObject*, PropertyName, bool isExtensible,
     const PropertyDescriptor& descriptor, bool isCurrentDefined, const PropertyDescriptor& current, bool throwException);
 
+JS_EXPORT_PRIVATE NEVER_INLINE bool ordinarySetSlow(ExecState*, JSObject*, PropertyName, JSValue, JSValue receiver, bool shouldThrow);
+
 // Helper for defining native functions, if you're not using a static hash table.
 // Use this macro from within finishCreation() methods in prototypes. This assumes
 // you've defined variables called exec, globalObject, and vm, and they
index 2104417..aae127c 100644 (file)
@@ -59,7 +59,10 @@ ALWAYS_INLINE bool JSObject::putInline(JSCell* cell, ExecState* exec, PropertyNa
     ASSERT(value);
     ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(thisObject));
     VM& vm = exec->vm();
-    
+
+    if (UNLIKELY(isThisValueAltered(slot, thisObject)))
+        return ordinarySetSlow(exec, thisObject, propertyName, value, slot.thisValue(), slot.isStrictMode());
+
     // Try indexed put first. This is required for correctness, since loads on property names that appear like
     // valid indices will never look in the named property storage.
     if (Optional<uint32_t> index = parseIndex(propertyName))
@@ -74,6 +77,7 @@ ALWAYS_INLINE bool JSObject::putInline(JSCell* cell, ExecState* exec, PropertyNa
         }
         return true;
     }
+
     return thisObject->putInlineSlow(exec, propertyName, value, slot);
 }
 
index 570921b..7695dbb 100644 (file)
@@ -42,8 +42,9 @@ public:
         return proxy;
     }
 
-    static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype, JSType proxyType = ImpureProxyType)
+    static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype, JSType proxyType)
     {
+        ASSERT(proxyType == ImpureProxyType || proxyType == PureForwardingProxyType);
         return Structure::create(vm, globalObject, prototype, TypeInfo(proxyType, StructureFlags), info());
     }
 
index 6732394..8f0ac5a 100644 (file)
@@ -30,6 +30,7 @@
 #include "JSGlobalObject.h"
 #include "PropertySlot.h"
 #include "PutPropertySlot.h"
+#include "Reject.h"
 #include <wtf/Assertions.h>
 
 namespace JSC {
@@ -293,16 +294,11 @@ inline bool putEntry(ExecState* exec, const HashTableValue* entry, JSObject* bas
                 thisObject->putDirect(exec->vm(), propertyName, value);
             return true;
         }
-        if (slot.isStrictMode())
-            throwTypeError(exec, StrictModeReadonlyPropertyWriteError);
-        return false;
+        return reject(exec, slot.isStrictMode(), StrictModeReadonlyPropertyWriteError);
     }
 
-    if (entry->attributes() & Accessor) {
-        if (slot.isStrictMode())
-            throwTypeError(exec, StrictModeReadonlyPropertyWriteError);
-        return false;
-    }
+    if (entry->attributes() & Accessor)
+        return reject(exec, slot.isStrictMode(), StrictModeReadonlyPropertyWriteError);
 
     if (!(entry->attributes() & ReadOnly)) {
         bool isAccessor = entry->attributes() & CustomAccessor;
@@ -315,9 +311,7 @@ inline bool putEntry(ExecState* exec, const HashTableValue* entry, JSObject* bas
         return result;
     }
 
-    if (slot.isStrictMode())
-        throwTypeError(exec, StrictModeReadonlyPropertyWriteError);
-    return false;
+    return reject(exec, slot.isStrictMode(), StrictModeReadonlyPropertyWriteError);
 }
 
 /**
index 7e7ee05..4049977 100644 (file)
@@ -24,7 +24,6 @@
 #include "JSCJSValue.h"
 #include "PropertyName.h"
 #include "PropertyOffset.h"
-#include "Register.h"
 #include <wtf/Assertions.h>
 
 namespace JSC {
@@ -52,6 +51,11 @@ enum Attribute {
     BuiltinOrFunctionOrAccessorOrConstant = Builtin | Function | Accessor | ConstantInteger, // helper only used by static hashtables
 };
 
+enum CacheabilityType : uint8_t {
+    CachingDisallowed,
+    CachingAllowed
+};
+
 inline unsigned attributesForStructure(unsigned attributes)
 {
     // The attributes that are used just for the static hashtable are at bit 8 and higher.
@@ -66,11 +70,6 @@ class PropertySlot {
         TypeCustom
     };
 
-    enum CacheabilityType : uint8_t {
-        CachingDisallowed,
-        CachingAllowed
-    };
-
 public:
     enum class InternalMethodType : uint8_t {
         Get, // [[Get]] internal method in the spec.
index 2d57260..b62aca7 100644 (file)
@@ -421,6 +421,7 @@ bool ProxyObject::performPut(ExecState* exec, JSValue putValue, JSValue thisValu
 bool ProxyObject::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
 {
     VM& vm = exec->vm();
+    slot.disableCaching();
     if (propertyName == vm.propertyNames->underscoreProto)
         return Base::put(cell, exec, propertyName, value, slot);
 
index 13ad2d3..0d2d9c1 100644 (file)
@@ -27,7 +27,7 @@
 #define PutPropertySlot_h
 
 #include "JSCJSValue.h"
-#include "PropertyOffset.h"
+#include "PropertySlot.h"
 
 #include <wtf/Assertions.h>
 
@@ -50,6 +50,7 @@ public:
         , m_isStrictMode(isStrictMode)
         , m_isInitialization(isInitialization)
         , m_context(context)
+        , m_cacheability(CachingAllowed)
         , m_putFunction(nullptr)
     {
     }
@@ -112,10 +113,10 @@ public:
     JSValue thisValue() const { return m_thisValue; }
 
     bool isStrictMode() const { return m_isStrictMode; }
-    bool isCacheablePut() const { return m_type == NewProperty || m_type == ExistingProperty; }
-    bool isCacheableSetter() const { return m_type == SetterProperty; }
-    bool isCacheableCustom() const { return m_type == CustomValue || m_type == CustomAccessor; }
-    bool isCustomAccessor() const { return m_type == CustomAccessor; }
+    bool isCacheablePut() const { return isCacheable() && (m_type == NewProperty || m_type == ExistingProperty); }
+    bool isCacheableSetter() const { return isCacheable() && m_type == SetterProperty; }
+    bool isCacheableCustom() const { return isCacheable() && (m_type == CustomValue || m_type == CustomAccessor); }
+    bool isCustomAccessor() const { return isCacheable() && m_type == CustomAccessor; }
     bool isInitialization() const { return m_isInitialization; }
 
     PropertyOffset cachedOffset() const
@@ -123,7 +124,14 @@ public:
         return m_offset;
     }
 
+    void disableCaching()
+    {
+        m_cacheability = CachingDisallowed;
+    }
+
 private:
+    bool isCacheable() const { return m_cacheability == CachingAllowed; }
+
     Type m_type;
     JSObject* m_base;
     JSValue m_thisValue;
@@ -131,6 +139,7 @@ private:
     bool m_isStrictMode;
     bool m_isInitialization;
     uint8_t m_context;
+    CacheabilityType m_cacheability;
     PutValueFunc m_putFunction;
 };
 
index 099ac85..a5e187b 100644 (file)
@@ -256,8 +256,8 @@ EncodedJSValue JSC_HOST_CALL reflectObjectSet(ExecState* exec)
         receiver = exec->argument(3);
 
     // Do not raise any readonly errors that happen in strict mode.
-    bool isStrictMode = false;
-    PutPropertySlot slot(receiver, isStrictMode);
+    bool shouldThrowIfCantSet = false;
+    PutPropertySlot slot(receiver, shouldThrowIfCantSet);
     return JSValue::encode(jsBoolean(targetObject->methodTable(exec->vm())->put(targetObject, exec, propertyName, exec->argument(2), slot)));
 }
 
index d39f1ff..9632432 100644 (file)
@@ -104,13 +104,6 @@ void RegExpObject::getGenericPropertyNames(JSObject* object, ExecState* exec, Pr
     Base::getGenericPropertyNames(object, exec, propertyNames, mode);
 }
 
-static bool reject(ExecState* exec, bool throwException, const char* message)
-{
-    if (throwException)
-        throwTypeError(exec, ASCIILiteral(message));
-    return false;
-}
-
 bool RegExpObject::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow)
 {
     if (propertyName == exec->propertyNames().lastIndex) {
@@ -150,6 +143,11 @@ static bool regExpObjectSetLastIndexNonStrict(ExecState* exec, EncodedJSValue th
 
 bool RegExpObject::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
 {
+    RegExpObject* thisObject = jsCast<RegExpObject*>(cell);
+
+    if (UNLIKELY(isThisValueAltered(slot, thisObject)))
+        return ordinarySetSlow(exec, thisObject, propertyName, value, slot.thisValue(), slot.isStrictMode());
+
     if (propertyName == exec->propertyNames().lastIndex) {
         bool result = asRegExpObject(cell)->setLastIndex(exec, value, slot.isStrictMode());
         slot.setCustomValue(asRegExpObject(cell), slot.isStrictMode()
index 66fb6ff..52d4ec2 100644 (file)
@@ -63,6 +63,11 @@ bool StringObject::getOwnPropertySlotByIndex(JSObject* object, ExecState* exec,
 
 bool StringObject::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
 {
+    StringObject* thisObject = jsCast<StringObject*>(cell);
+
+    if (UNLIKELY(isThisValueAltered(slot, thisObject)))
+        return ordinarySetSlow(exec, thisObject, propertyName, value, slot.thisValue(), slot.isStrictMode());
+
     if (propertyName == exec->propertyNames().length) {
         if (slot.isStrictMode())
             throwTypeError(exec, StrictModeReadonlyPropertyWriteError);
index 61b0fa8..5087fd5 100644 (file)
 - path: es6/Proxy_has_handler_instances_of_proxies.js
   cmd: runES6 :normal
 - path: es6/Proxy_internal_defineProperty_calls_[[Set]].js
-  cmd: runES6 :fail
+  cmd: runES6 :normal
 - path: es6/Proxy_internal_defineProperty_calls_SetIntegrityLevel.js
   cmd: runES6 :normal
 - path: es6/Proxy_internal_deleteProperty_calls_Array.prototype.copyWithin.js
 - path: es6/Proxy_internal_get_calls_ToPropertyDescriptor.js
   cmd: runES6 :normal
 - path: es6/Proxy_internal_getOwnPropertyDescriptor_calls_[[Set]].js
-  cmd: runES6 :fail
+  cmd: runES6 :normal
 - path: es6/Proxy_internal_getOwnPropertyDescriptor_calls_Function.prototype.bind.js
   cmd: runES6 :normal
 - path: es6/Proxy_internal_getOwnPropertyDescriptor_calls_Object.assign.js
diff --git a/Source/JavaScriptCore/tests/stress/ordinary-set-exceptions.js b/Source/JavaScriptCore/tests/stress/ordinary-set-exceptions.js
new file mode 100644 (file)
index 0000000..1221b86
--- /dev/null
@@ -0,0 +1,100 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function shouldThrow(func, errorMessage) {
+    var errorThrown = false;
+    var error = null;
+    try {
+        func();
+    } catch (e) {
+        errorThrown = true;
+        error = e;
+    }
+    if (!errorThrown)
+        throw new Error('not thrown');
+    if (String(error) !== errorMessage)
+        throw new Error(`bad error: ${String(error)}`);
+}
+
+// 9.1.9.1 4-a
+shouldThrow(function () {
+    'use strict';
+    var target = {};
+    var handler = {};
+    var proxy = new Proxy(target, handler);
+    shouldBe(Reflect.defineProperty(target, 'cocoa', {
+        writable: false,
+        value: 42,
+    }), true);
+    proxy.cocoa = 'NG';
+}, `TypeError: Attempted to assign to readonly property.`);
+
+// 9.1.9.1 4-b
+(function () {
+    'use strict';
+    var target = {};
+    var handler = {};
+    var proxy = new Proxy(target, handler);
+    shouldBe(Reflect.defineProperty(target, 'cocoa', {
+        writable: false,
+        value: 42,
+    }), true);
+    shouldBe(Reflect.set(proxy, 'cocoa', 'NG', 'Cocoa'), false);
+}());
+
+// 9.1.9.1 4-d-i
+shouldThrow(function () {
+    'use strict';
+    var target = {};
+    var proxy = new Proxy(target, {
+        get set()
+        {
+            shouldBe(Reflect.defineProperty(receiver, 'cocoa', {
+                set() { }
+            }), true);
+            return undefined;
+        }
+    });
+    var receiver = { __proto__: proxy };
+    shouldBe(Reflect.defineProperty(target, 'cocoa', {
+        writable: true,
+        value: 42,
+    }), true);
+    receiver.cocoa = 'NG';
+}, `TypeError: Attempted to assign to readonly property.`);
+
+// 9.1.9.1 4-d-ii
+shouldThrow(function () {
+    'use strict';
+    var target = {};
+    var proxy = new Proxy(target, {
+        get set()
+        {
+            shouldBe(Reflect.defineProperty(receiver, 'cocoa', {
+                value: 'hello',
+                writable: false
+            }), true);
+            return undefined;
+        }
+    });
+    var receiver = { __proto__: proxy };
+    shouldBe(Reflect.defineProperty(target, 'cocoa', {
+        writable: true,
+        value: 42,
+    }), true);
+    receiver.cocoa = 'NG';
+}, `TypeError: Attempted to assign to readonly property.`);
+
+// 9.1.9.1 7
+shouldThrow(function () {
+    'use strict';
+    var target = {};
+    var proxy = new Proxy(target, {});
+    var receiver = { __proto__: proxy };
+    shouldBe(Reflect.defineProperty(target, 'cocoa', {
+        get() { }
+    }), true);
+    receiver.cocoa = 'NG';
+}, `TypeError: Attempted to assign to readonly property.`);
index 30eec8b..3eb536d 100644 (file)
@@ -90,7 +90,7 @@ function assert(b) {
 
     let called = false;
     let handler = {
-        set: function(theTarget, propName, value, reciever) {
+        set: function(theTarget, propName, value, receiver) {
             assert(theTarget === target);
             called = true;
             theTarget[propName] = value;
@@ -117,7 +117,7 @@ function assert(b) {
     });
 
     let handler = {
-        set: function(theTarget, propName, value, reciever) {
+        set: function(theTarget, propName, value, receiver) {
             assert(theTarget === target);
             theTarget[propName] = value;
             return true;
@@ -148,7 +148,7 @@ function assert(b) {
 
     let called = false;
     let handler = {
-        set: function(theTarget, propName, value, reciever) {
+        set: function(theTarget, propName, value, receiver) {
             assert(theTarget === target);
             called = true;
             theTarget[propName] = value;
@@ -176,7 +176,7 @@ function assert(b) {
 
     let called = false;
     let handler = {
-        set: function(theTarget, propName, value, reciever) {
+        set: function(theTarget, propName, value, receiver) {
             assert(theTarget === target);
             called = true;
             theTarget[propName] = value;
@@ -207,7 +207,7 @@ function assert(b) {
 
     let called = false;
     let handler = {
-        set: function(theTarget, propName, value, reciever) {
+        set: function(theTarget, propName, value, receiver) {
             assert(theTarget === target);
             called = true;
             theTarget[propName] = value;
@@ -232,9 +232,9 @@ function assert(b) {
 
     let called = false;
     let handler = {
-        set: function(theTarget, propName, value, reciever) {
+        set: function(theTarget, propName, value, receiver) {
             assert(target === theTarget);
-            assert(reciever === proxy);
+            assert(receiver === proxy);
             called = true;
             theTarget[propName] = value;
         }
@@ -263,9 +263,9 @@ function assert(b) {
 
     let called = false;
     let handler = {
-        set: function(theTarget, propName, value, reciever) {
+        set: function(theTarget, propName, value, receiver) {
             assert(target === theTarget);
-            assert(reciever === proxy);
+            assert(receiver === proxy);
             called = true;
             theTarget[propName] = value;
         }
@@ -306,9 +306,9 @@ function assert(b) {
 
     let called = false;
     let handler = {
-        set: function(theTarget, propName, value, reciever) {
+        set: function(theTarget, propName, value, receiver) {
             assert(target === theTarget);
-            assert(reciever === proxy);
+            assert(receiver === proxy);
             called = true;
             theTarget[propName] = value;
         }
@@ -329,9 +329,9 @@ function assert(b) {
 
     let called = false;
     let handler = {
-        set: function(theTarget, propName, value, reciever) {
+        set: function(theTarget, propName, value, receiver) {
             assert(target === theTarget);
-            assert(reciever === proxy);
+            assert(receiver === proxy);
             called = true;
             theTarget[propName] = value;
         }
@@ -362,9 +362,9 @@ function assert(b) {
     };
 
     let handler = {
-        set: function(theTarget, propName, value, reciever) {
+        set: function(theTarget, propName, value, receiver) {
             assert(target === theTarget);
-            assert(reciever === proxy);
+            assert(receiver === proxy);
             theTarget[propName] = value;
             return true;
         }
@@ -386,9 +386,9 @@ function assert(b) {
     let called = false;
     let target = {};
     let handler = {
-        set: function(theTarget, propName, value, reciever) {
+        set: function(theTarget, propName, value, receiver) {
             assert(target === theTarget);
-            assert(reciever === obj);
+            assert(receiver === obj);
             theTarget[propName] = value;
             called = true;
             return true;
@@ -435,8 +435,10 @@ function assert(b) {
         assert(proxy.own === undefined);
 
         obj.notOwn = i;
-        assert(target.notOwn === i);
-        assert(proxy.notOwn === i);
+        // The receiver is always |obj|.
+        // obj.[[Set]](P, V, obj) -> Proxy.[[Set]](P, V, obj) -> target.[[Set]](P, V, obj)
+        assert(target.notOwn === undefined);
+        assert(proxy.notOwn === undefined);
         assert(obj.notOwn === i);
     }
 }
@@ -445,9 +447,9 @@ function assert(b) {
     let called = false;
     let target = {};
     let handler = {
-        set: function(theTarget, propName, value, reciever) {
+        set: function(theTarget, propName, value, receiver) {
             assert(target === theTarget);
-            assert(reciever === obj);
+            assert(receiver === obj);
             theTarget[propName] = value;
             called = true;
             return true;
@@ -495,8 +497,10 @@ function assert(b) {
         assert(proxy[0] === undefined);
 
         obj[1] = i;
-        assert(target[1] === i);
-        assert(proxy[1] === i);
+        // The receiver is always |obj|.
+        // obj.[[Set]](P, V, obj) -> Proxy.[[Set]](P, V, obj) -> target.[[Set]](P, V, obj)
+        assert(target[1] === undefined);
+        assert(proxy[1] === undefined);
         assert(obj[1] === i);
     }
 }
@@ -505,9 +509,9 @@ function assert(b) {
     let called = false;
     let target = {};
     let handler = {
-        set: function(theTarget, propName, value, reciever) {
+        set: function(theTarget, propName, value, receiver) {
             assert(target === theTarget);
-            //assert(reciever === obj);
+            assert(receiver === obj);
             theTarget[propName] = value;
             called = true;
             return true;
@@ -541,9 +545,9 @@ function assert(b) {
     let called = false;
     let target = [25];
     let handler = {
-        set: function(theTarget, propName, value, reciever) {
+        set: function(theTarget, propName, value, receiver) {
             assert(target === theTarget);
-            //assert(reciever === obj);
+            assert(receiver === obj);
             theTarget[propName] = value;
             called = true;
             return true;
@@ -577,9 +581,9 @@ function assert(b) {
     let called = false;
     let ogTarget = {};
     let target = new Proxy(ogTarget, {
-        set: function(theTarget, propName, value, reciever) {
+        set: function(theTarget, propName, value, receiver) {
             assert(theTarget === ogTarget);
-            assert(reciever === obj);
+            assert(receiver === obj);
             called = true;
             theTarget[propName] = value;
         }
@@ -613,9 +617,9 @@ function assert(b) {
     let called = false;
     let ogTarget = [25];
     let target = new Proxy(ogTarget, {
-        set: function(theTarget, propName, value, reciever) {
+        set: function(theTarget, propName, value, receiver) {
             assert(theTarget === ogTarget);
-            assert(reciever === obj);
+            assert(receiver === obj);
             called = true;
             theTarget[propName] = value;
         }
diff --git a/Source/JavaScriptCore/tests/stress/reflect-set-proxy-set.js b/Source/JavaScriptCore/tests/stress/reflect-set-proxy-set.js
new file mode 100644 (file)
index 0000000..bc2e810
--- /dev/null
@@ -0,0 +1,665 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function unreachable()
+{
+    throw new Error('unreachable');
+}
+
+function assert(b) {
+    if (!b)
+        throw new Error("bad assertion");
+}
+
+{
+    let target = {
+        x: 30
+    };
+
+    let called = false;
+    let handler = {
+        set: 45
+    };
+
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 1000; i++) {
+        let threw = false;
+        try {
+            Reflect.set(proxy, 'x', 40);
+            unreachable();
+        } catch(e) {
+            assert(e.toString() === "TypeError: 'set' property of a Proxy's handler should be callable.");
+            threw = true;
+        }
+        assert(threw);
+    }
+}
+
+{
+    let target = {
+        x: 30
+    };
+
+    let error = null;
+    let handler = {
+        get set() {
+            error = new Error;
+            throw error;
+        }
+    };
+
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 1000; i++) {
+        let threw = false;
+        try {
+            Reflect.set(proxy, 'x', 40);
+            unreachable();
+        } catch(e) {
+            assert(e === error);
+            threw = true;
+        }
+        assert(threw);
+        error = null;
+    }
+}
+
+{
+    let target = {
+        x: 30
+    };
+
+    let error = null;
+    let handler = {
+        set: function() {
+            error = new Error;
+            throw error;
+        }
+    };
+
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 1000; i++) {
+        let threw = false;
+        try {
+            Reflect.set(proxy, 'x', 40);
+            unreachable();
+        } catch(e) {
+            assert(e === error);
+            threw = true;
+        }
+        assert(threw);
+        error = null;
+    }
+}
+
+{
+    let target = { };
+    Object.defineProperty(target, "x", {
+        configurable: false,
+        writable: false,
+        value: 500
+    });
+
+    let called = false;
+    let handler = {
+        set: function(theTarget, propName, value, receiver) {
+            assert(theTarget === target);
+            called = true;
+            theTarget[propName] = value;
+            return false;    
+        }
+    };
+
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 1000; i++) {
+        shouldBe(Reflect.set(proxy, 'x', 40), false);
+        assert(called);
+        assert(proxy.x === 500);
+        assert(target.x === 500);
+        called = false;
+    }
+}
+
+{
+    let target = { };
+    Object.defineProperty(target, "x", {
+        configurable: false,
+        writable: false,
+        value: 500
+    });
+
+    let handler = {
+        set: function(theTarget, propName, value, receiver) {
+            assert(theTarget === target);
+            theTarget[propName] = value;
+            return true;
+        }
+    };
+
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 1000; i++) {
+        let threw = false;
+        try {
+            Reflect.set(proxy, 'x', 40);
+            unreachable();
+        } catch(e) {
+            threw = true;
+            assert(e.toString() === "TypeError: Proxy handler's 'set' on a non-configurable and non-writable property on 'target' should either return false or be the same value already on the 'target'.");
+        }
+        assert(threw);
+    }
+}
+
+{
+    let target = { };
+    Object.defineProperty(target, "x", {
+        configurable: false,
+        get: function() {
+            return 25;
+        }
+    });
+
+    let called = false;
+    let handler = {
+        set: function(theTarget, propName, value, receiver) {
+            assert(theTarget === target);
+            called = true;
+            theTarget[propName] = value;
+            return false;
+        }
+    };
+
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 1000; i++) {
+        shouldBe(Reflect.set(proxy, 'x', 40), false);
+        assert(proxy.x === 25);
+        assert(called);
+        called = false;
+    }
+}
+
+{
+    let target = { };
+    Object.defineProperty(target, "x", {
+        configurable: false,
+        get: function() {
+            return 25;
+        }
+    });
+
+    let called = false;
+    let handler = {
+        set: function(theTarget, propName, value, receiver) {
+            assert(theTarget === target);
+            called = true;
+            theTarget[propName] = value;
+            return true;
+        }
+    };
+
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 1000; i++) {
+        let threw = false;
+        try {
+            Reflect.set(proxy, 'x', 40);
+            unreachable();
+        } catch(e) {
+            threw = true;
+            assert(e.toString() === "TypeError: Proxy handler's 'set' method on a non-configurable accessor property without a setter should return false.");
+        }
+        assert(threw);
+    }
+}
+
+{
+    let target = { };
+    Object.defineProperty(target, "x", {
+        configurable: false,
+        writable: true,
+        value: 50
+    });
+
+    let called = false;
+    let handler = {
+        set: function(theTarget, propName, value, receiver) {
+            assert(theTarget === target);
+            called = true;
+            theTarget[propName] = value;
+            return true;
+        }
+    };
+
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 1000; i++) {
+        shouldBe(Reflect.set(proxy, 'x', i), true);
+        assert(called);
+        assert(proxy.x === i);
+        assert(target.x === i);
+        called = false;
+    }
+}
+
+{
+    let target = {
+        x: 30
+    };
+
+    let called = false;
+    let handler = {
+        set: function(theTarget, propName, value, receiver) {
+            assert(target === theTarget);
+            assert(receiver === proxy);
+            called = true;
+            theTarget[propName] = value;
+        }
+    };
+
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 1000; i++) {
+        shouldBe(Reflect.set(proxy, 'x', i), false);
+        assert(called);
+        assert(proxy.x === i);
+        assert(target.x === i);
+        called = false;
+
+        shouldBe(Reflect.set(proxy, 'y', i), false);
+        assert(called);
+        assert(proxy.y === i);
+        assert(target.y === i);
+        called = false;
+    }
+}
+
+{
+    let target = {
+        x: 30
+    };
+
+    let called = false;
+    let handler = {
+        set: function(theTarget, propName, value, receiver) {
+            assert(target === theTarget);
+            assert(receiver === proxy);
+            called = true;
+            theTarget[propName] = value;
+        }
+    };
+
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 1000; i++) {
+        shouldBe(Reflect.set(proxy, 'x', i), false);
+        assert(called);
+        assert(proxy.x === i);
+        assert(target.x === i);
+        called = false;
+
+        shouldBe(Reflect.set(proxy, 'y', i), false);
+        assert(called);
+        assert(proxy.y === i);
+        assert(target.y === i);
+        called = false;
+    }
+}
+
+{
+    let target = [];
+
+    let called = false;
+    let handler = { };
+
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 1000; i++) {
+        shouldBe(Reflect.set(proxy, i, i), true);
+        assert(proxy[i] === i);
+        assert(target[i] === i);
+    }
+}
+
+{
+    let target = [];
+
+    let called = false;
+    let handler = {
+        set: function(theTarget, propName, value, receiver) {
+            assert(target === theTarget);
+            assert(receiver === proxy);
+            called = true;
+            theTarget[propName] = value;
+        }
+    };
+
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 1000; i++) {
+        shouldBe(Reflect.set(proxy, i, i), false);
+        assert(proxy[i] === i);
+        assert(target[i] === i);
+        assert(called);
+        called = false;
+    }
+}
+
+{
+    let target = [];
+
+    let called = false;
+    let handler = {
+        set: function(theTarget, propName, value, receiver) {
+            assert(target === theTarget);
+            assert(receiver === proxy);
+            called = true;
+            theTarget[propName] = value;
+        }
+    };
+
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 1000; i++) {
+        shouldBe(Reflect.set(proxy, i, i), false);
+        assert(proxy[i] === i);
+        assert(target[i] === i);
+        assert(called);
+        called = false;
+    }
+}
+
+{
+    let called = false;
+    let target = {
+        set x(v) {
+            assert(this === target);
+            this._x = v;
+            called = true;
+        },
+        get x() {
+            assert(this === target);
+            return this._x;
+        }
+    };
+
+    let handler = {
+        set: function(theTarget, propName, value, receiver) {
+            assert(target === theTarget);
+            assert(receiver === proxy);
+            theTarget[propName] = value;
+            return true;
+        }
+    };
+
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 1000; i++) {
+        shouldBe(Reflect.set(proxy, 'x', i), true);
+        assert(called);
+        assert(proxy.x === i);
+        assert(target.x === i);
+        assert(proxy._x === i);
+        assert(target._x === i);
+        called = false;
+    }
+}
+
+{
+    let called = false;
+    let target = {};
+    let handler = {
+        set: function(theTarget, propName, value, receiver) {
+            assert(target === theTarget);
+            assert(receiver === obj);
+            theTarget[propName] = value;
+            called = true;
+            return true;
+        }
+    };
+
+    let proxy = new Proxy(target, handler);
+    let obj = Object.create(proxy, {
+        own: {
+            writable: true,
+            configurable: true,
+            value: null
+        }
+    });
+    for (let i = 0; i < 1000; i++) {
+        shouldBe(Reflect.set(obj, 'own', i), true);
+        assert(!called);
+        assert(obj.own === i);
+
+        shouldBe(Reflect.set(obj, 'notOwn', i), true);
+        assert(target.notOwn === i);
+        assert(proxy.notOwn === i);
+        assert(obj.notOwn === i);
+        assert(called);
+        called = false;
+    }
+}
+
+{
+    let target = {};
+    let handler = { };
+
+    let proxy = new Proxy(target, handler);
+    let obj = Object.create(proxy, {
+        own: {
+            writable: true,
+            configurable: true,
+            value: null
+        }
+    });
+    for (let i = 0; i < 1000; i++) {
+        shouldBe(Reflect.set(obj, 'own', i), true);
+        assert(obj.own === i);
+        assert(proxy.own === undefined);
+
+        shouldBe(Reflect.set(obj, 'notOwn', i), true);
+        // The receiver is always |obj|.
+        // obj.[[Set]](P, V, obj) -> Proxy.[[Set]](P, V, obj) -> target.[[Set]](P, V, obj)
+        assert(target.notOwn === undefined);
+        assert(proxy.notOwn === undefined);
+        assert(obj.notOwn === i);
+    }
+}
+
+{
+    let called = false;
+    let target = {};
+    let handler = {
+        set: function(theTarget, propName, value, receiver) {
+            assert(target === theTarget);
+            assert(receiver === obj);
+            theTarget[propName] = value;
+            called = true;
+            return true;
+        }
+    };
+
+    let proxy = new Proxy(target, handler);
+    let obj = Object.create(proxy, {
+        [0]: {
+            writable: true,
+            configurable: true,
+            value: null
+        }
+    });
+    for (let i = 0; i < 1000; i++) {
+        shouldBe(Reflect.set(obj, 0, i), true);
+        assert(!called);
+        assert(obj[0] === i);
+        assert(proxy[0] === undefined);
+
+        shouldBe(Reflect.set(obj, 1, i), true);
+        assert(target[1] === i);
+        assert(proxy[1] === i);
+        assert(obj[1] === i);
+        assert(called);
+        called = false;
+    }
+}
+
+{
+    let target = {};
+    let handler = { };
+
+    let proxy = new Proxy(target, handler);
+    let obj = Object.create(proxy, {
+        [0]: {
+            writable: true,
+            configurable: true,
+            value: null
+        }
+    });
+    for (let i = 0; i < 1000; i++) {
+        shouldBe(Reflect.set(obj, 0, i), true);
+        assert(obj[0] === i);
+        assert(proxy[0] === undefined);
+
+        shouldBe(Reflect.set(obj, 1, i), true);
+        // The receiver is always |obj|.
+        // obj.[[Set]](P, V, obj) -> Proxy.[[Set]](P, V, obj) -> target.[[Set]](P, V, obj)
+        assert(target[1] === undefined);
+        assert(proxy[1] === undefined);
+        assert(obj[1] === i);
+    }
+}
+
+{
+    let called = false;
+    let target = {};
+    let handler = {
+        set: function(theTarget, propName, value, receiver) {
+            assert(target === theTarget);
+            assert(receiver === obj);
+            theTarget[propName] = value;
+            called = true;
+            return true;
+        }
+    };
+
+    let proxy = new Proxy(target, handler);
+    let obj = Object.create(proxy, {
+        [0]: {
+            writable: true,
+            configurable: true,
+            value: null
+        }
+    });
+    for (let i = 0; i < 1000; i++) {
+        shouldBe(Reflect.set(obj, 0, i), true);
+        assert(!called);
+        assert(obj[0] === i);
+        assert(proxy[0] === undefined);
+
+        shouldBe(Reflect.set(obj, 1, i), true);
+        assert(target[1] === i);
+        assert(proxy[1] === i);
+        assert(obj[1] === i);
+        assert(called);
+        called = false;
+    }
+}
+
+{
+    let called = false;
+    let target = [25];
+    let handler = {
+        set: function(theTarget, propName, value, receiver) {
+            assert(target === theTarget);
+            assert(receiver === obj);
+            theTarget[propName] = value;
+            called = true;
+            return true;
+        }
+    };
+
+    let proxy = new Proxy(target, handler);
+    let obj = Object.create(proxy, {
+        [0]: {
+            writable: true,
+            configurable: true,
+            value: null
+        }
+    });
+    for (let i = 0; i < 1000; i++) {
+        shouldBe(Reflect.set(obj, 0, i), true);
+        assert(!called);
+        assert(obj[0] === i);
+        assert(proxy[0] === 25);
+
+        shouldBe(Reflect.set(obj, 1, i), true);
+        assert(target[1] === i);
+        assert(proxy[1] === i);
+        assert(obj[1] === i);
+        assert(called);
+        called = false;
+    }
+}
+
+{
+    let called = false;
+    let ogTarget = {};
+    let target = new Proxy(ogTarget, {
+        set: function(theTarget, propName, value, receiver) {
+            assert(theTarget === ogTarget);
+            assert(receiver === obj);
+            called = true;
+            theTarget[propName] = value;
+        }
+    });
+    let handler = { };
+
+    let proxy = new Proxy(target, handler);
+    let obj = Object.create(proxy, {
+        own: {
+            writable: true,
+            configurable: true,
+            value: null
+        }
+    });
+    for (let i = 0; i < 1000; i++) {
+        shouldBe(Reflect.set(obj, 'own', i), true);
+        assert(!called);
+        assert(obj.own === i);
+        assert(proxy.own === undefined);
+
+        shouldBe(Reflect.set(obj, 'notOwn', i), false);
+        assert(target.notOwn === i);
+        assert(proxy.notOwn === i);
+        assert(obj.notOwn === i);
+        assert(called);
+        called = false;
+    }
+}
+
+{
+    let called = false;
+    let ogTarget = [25];
+    let target = new Proxy(ogTarget, {
+        set: function(theTarget, propName, value, receiver) {
+            assert(theTarget === ogTarget);
+            assert(receiver === obj);
+            called = true;
+            theTarget[propName] = value;
+        }
+    });
+    let handler = { };
+
+    let proxy = new Proxy(target, handler);
+    let obj = Object.create(proxy, {
+        [0]: {
+            writable: true,
+            configurable: true,
+            value: null
+        }
+    });
+    for (let i = 0; i < 1000; i++) {
+        shouldBe(Reflect.set(obj, 0, i), true);
+        assert(!called);
+        assert(obj[0] === i);
+        assert(proxy[0] === 25);
+
+        shouldBe(Reflect.set(obj, 1, i), false);
+        assert(target[1] === i);
+        assert(proxy[1] === i);
+        assert(obj[1] === i);
+        assert(called);
+        called = false;
+    }
+}
diff --git a/Source/JavaScriptCore/tests/stress/reflect-set-receiver-proxy-set.js b/Source/JavaScriptCore/tests/stress/reflect-set-receiver-proxy-set.js
new file mode 100644 (file)
index 0000000..5167179
--- /dev/null
@@ -0,0 +1,698 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function unreachable()
+{
+    throw new Error('unreachable');
+}
+
+function assert(b) {
+    if (!b)
+        throw new Error("bad assertion");
+}
+
+(function () {
+    {
+        let target = {
+            x: 30
+        };
+
+        let called = false;
+        let handler = {
+            set: 45
+        };
+
+        let theReceiver = {};
+        let proxy = new Proxy(target, handler);
+        for (let i = 0; i < 1000; i++) {
+            let threw = false;
+            try {
+                Reflect.set(proxy, 'x', 40, theReceiver);
+                unreachable();
+            } catch(e) {
+                assert(e.toString() === "TypeError: 'set' property of a Proxy's handler should be callable.");
+                threw = true;
+            }
+            assert(threw);
+        }
+    }
+
+    {
+        let target = {
+            x: 30
+        };
+
+        let error = null;
+        let handler = {
+            get set() {
+                error = new Error;
+                throw error;
+            }
+        };
+
+        let theReceiver = {};
+        let proxy = new Proxy(target, handler);
+        for (let i = 0; i < 1000; i++) {
+            let threw = false;
+            try {
+                Reflect.set(proxy, 'x', 40, theReceiver);
+                unreachable();
+            } catch(e) {
+                assert(e === error);
+                threw = true;
+            }
+            assert(threw);
+            error = null;
+        }
+    }
+
+    {
+        let target = {
+            x: 30
+        };
+
+        let error = null;
+        let handler = {
+            set: function(_1, _2, _3, receiver) {
+                shouldBe(receiver, theReceiver);
+                error = new Error;
+                throw error;
+            }
+        };
+
+        let theReceiver = {};
+        let proxy = new Proxy(target, handler);
+        for (let i = 0; i < 1000; i++) {
+            let threw = false;
+            try {
+                Reflect.set(proxy, 'x', 40, theReceiver);
+                unreachable();
+            } catch(e) {
+                assert(e === error);
+                threw = true;
+            }
+            assert(threw);
+            error = null;
+        }
+    }
+
+    {
+        let target = { };
+        Object.defineProperty(target, "x", {
+            configurable: false,
+            writable: false,
+            value: 500
+        });
+
+        let theReceiver = {};
+        let called = false;
+        let handler = {
+            set: function(theTarget, propName, value, receiver) {
+                assert(theTarget === target);
+                shouldBe(receiver, theReceiver);
+                called = true;
+                theTarget[propName] = value;
+                return false;
+            }
+        };
+
+        let proxy = new Proxy(target, handler);
+        for (let i = 0; i < 1000; i++) {
+            shouldBe(Reflect.set(proxy, 'x', 40, theReceiver), false);
+            assert(called);
+            assert(proxy.x === 500);
+            assert(target.x === 500);
+            called = false;
+        }
+    }
+
+    {
+        let target = { };
+        Object.defineProperty(target, "x", {
+            configurable: false,
+            writable: false,
+            value: 500
+        });
+
+        let handler = {
+            set: function(theTarget, propName, value, receiver) {
+                assert(theTarget === target);
+                shouldBe(receiver, theReceiver);
+                theTarget[propName] = value;
+                return true;
+            }
+        };
+
+        let theReceiver = {};
+        let proxy = new Proxy(target, handler);
+        for (let i = 0; i < 1000; i++) {
+            let threw = false;
+            try {
+                Reflect.set(proxy, 'x', 40, theReceiver);
+                unreachable();
+            } catch(e) {
+                threw = true;
+                assert(e.toString() === "TypeError: Proxy handler's 'set' on a non-configurable and non-writable property on 'target' should either return false or be the same value already on the 'target'.");
+            }
+            assert(threw);
+        }
+    }
+
+    {
+        let target = { };
+        Object.defineProperty(target, "x", {
+            configurable: false,
+            get: function() {
+                return 25;
+            }
+        });
+
+        let called = false;
+        let handler = {
+            set: function(theTarget, propName, value, receiver) {
+                assert(theTarget === target);
+                shouldBe(receiver, theReceiver);
+                called = true;
+                theTarget[propName] = value;
+                return false;
+            }
+        };
+
+        let theReceiver = {};
+        let proxy = new Proxy(target, handler);
+        for (let i = 0; i < 1000; i++) {
+            shouldBe(Reflect.set(proxy, 'x', 40, theReceiver), false);
+            assert(proxy.x === 25);
+            assert(theReceiver.x === undefined);
+            assert(called);
+            called = false;
+        }
+    }
+
+    {
+        let target = { };
+        Object.defineProperty(target, "x", {
+            configurable: false,
+            get: function() {
+                return 25;
+            }
+        });
+
+        let called = false;
+        let handler = {
+            set: function(theTarget, propName, value, receiver) {
+                assert(theTarget === target);
+                shouldBe(receiver, theReceiver);
+                called = true;
+                theTarget[propName] = value;
+                return true;
+            }
+        };
+
+        let theReceiver = {};
+        let proxy = new Proxy(target, handler);
+        for (let i = 0; i < 1000; i++) {
+            let threw = false;
+            try {
+                Reflect.set(proxy, 'x', 40, theReceiver);
+                unreachable();
+            } catch(e) {
+                threw = true;
+                assert(e.toString() === "TypeError: Proxy handler's 'set' method on a non-configurable accessor property without a setter should return false.");
+            }
+            assert(called);
+            assert(threw);
+        }
+    }
+
+    {
+        let target = { };
+        Object.defineProperty(target, "x", {
+            configurable: false,
+            writable: true,
+            value: 50
+        });
+
+        let called = false;
+        let handler = {
+            set: function(theTarget, propName, value, receiver) {
+                assert(theTarget === target);
+                shouldBe(receiver, theReceiver);
+                called = true;
+                theTarget[propName] = value;
+                return true;
+            }
+        };
+
+        let theReceiver = {};
+        let proxy = new Proxy(target, handler);
+        for (let i = 0; i < 1000; i++) {
+            shouldBe(Reflect.set(proxy, 'x', i, theReceiver), true);
+            assert(called);
+            assert(proxy.x === i);
+            assert(target.x === i);
+            assert(theReceiver.x === undefined);
+            called = false;
+        }
+    }
+
+    {
+        let target = {
+            x: 30
+        };
+
+        let called = false;
+        let handler = {
+            set: function(theTarget, propName, value, receiver) {
+                assert(target === theTarget);
+                shouldBe(receiver, theReceiver);
+                called = true;
+                theTarget[propName] = value;
+            }
+        };
+
+        let theReceiver = {};
+        let proxy = new Proxy(target, handler);
+        for (let i = 0; i < 1000; i++) {
+            shouldBe(Reflect.set(proxy, 'x', i, theReceiver), false);
+            assert(called);
+            assert(proxy.x === i);
+            assert(target.x === i);
+            assert(theReceiver.x === undefined);
+            called = false;
+
+            shouldBe(Reflect.set(proxy, 'y', i, theReceiver), false);
+            assert(called);
+            assert(proxy.y === i);
+            assert(target.y === i);
+            assert(theReceiver.y === undefined);
+            called = false;
+        }
+    }
+
+    {
+        let target = {
+            x: 30
+        };
+
+        let called = false;
+        let handler = {
+            set: function(theTarget, propName, value, receiver) {
+                assert(target === theTarget);
+                assert(receiver === theReceiver);
+                called = true;
+                theTarget[propName] = value;
+            }
+        };
+
+        let theReceiver = {};
+        let proxy = new Proxy(target, handler);
+        for (let i = 0; i < 1000; i++) {
+            shouldBe(Reflect.set(proxy, 'x', i, theReceiver), false);
+            assert(called);
+            assert(proxy.x === i);
+            assert(target.x === i);
+            assert(theReceiver.x === undefined);
+            called = false;
+
+            shouldBe(Reflect.set(proxy, 'y', i, theReceiver), false);
+            assert(called);
+            assert(proxy.y === i);
+            assert(target.y === i);
+            assert(theReceiver.y === undefined);
+            called = false;
+        }
+    }
+
+    {
+        let target = [];
+
+        let called = false;
+        let handler = { };
+
+        let theReceiver = {};
+        let proxy = new Proxy(target, handler);
+        for (let i = 0; i < 1000; i++) {
+            shouldBe(Reflect.set(proxy, i, i, theReceiver), true);
+            assert(proxy[i] === undefined);
+            assert(target[i] === undefined);
+            assert(theReceiver[i] === i);
+        }
+    }
+
+    {
+        let target = [];
+
+        let called = false;
+        let handler = {
+            set: function(theTarget, propName, value, receiver) {
+                assert(target === theTarget);
+                assert(receiver === theReceiver);
+                called = true;
+                theTarget[propName] = value;
+            }
+        };
+
+        let theReceiver = {};
+        let proxy = new Proxy(target, handler);
+        for (let i = 0; i < 1000; i++) {
+            shouldBe(Reflect.set(proxy, i, i, theReceiver), false);
+            assert(proxy[i] === i);
+            assert(target[i] === i);
+            assert(theReceiver[i] === undefined);
+            assert(called);
+            called = false;
+        }
+    }
+
+    {
+        let called = false;
+        let target = {
+            set x(v) {
+                assert(this === target);
+                this._x = v;
+                called = true;
+            },
+            get x() {
+                assert(this === target);
+                return this._x;
+            }
+        };
+
+        let handler = {
+            set: function(theTarget, propName, value, receiver) {
+                assert(target === theTarget);
+                assert(receiver === theReceiver);
+                theTarget[propName] = value;
+                return true;
+            }
+        };
+
+        let theReceiver = {};
+        let proxy = new Proxy(target, handler);
+        for (let i = 0; i < 1000; i++) {
+            shouldBe(Reflect.set(proxy, 'x', i, theReceiver), true);
+            assert(called);
+            assert(proxy.x === i);
+            assert(target.x === i);
+            assert(theReceiver.x === undefined);
+            assert(proxy._x === i);
+            assert(target._x === i);
+            assert(theReceiver._x === undefined);
+            called = false;
+        }
+    }
+
+    {
+        let called = false;
+        let target = {};
+        let handler = {
+            set: function(theTarget, propName, value, receiver) {
+                assert(target === theTarget);
+                assert(receiver === theReceiver);
+                theTarget[propName] = value;
+                called = true;
+                return true;
+            }
+        };
+
+        let theReceiver = {};
+        let proxy = new Proxy(target, handler);
+        let obj = Object.create(proxy, {
+            own: {
+                writable: true,
+                configurable: true,
+                value: null
+            }
+        });
+        for (let i = 0; i < 1000; i++) {
+            shouldBe(Reflect.set(obj, 'own', i, theReceiver), true);
+            assert(!called);
+            assert(obj.own === null);
+            assert(theReceiver.own === i);
+
+            shouldBe(Reflect.set(obj, 'notOwn', i, theReceiver), true);
+            assert(target.notOwn === i);
+            assert(proxy.notOwn === i);
+            assert(obj.notOwn === i);
+            assert(theReceiver.notOwn === undefined);
+            assert(called);
+            called = false;
+        }
+    }
+
+    {
+        let target = {};
+        let handler = { };
+
+        let theReceiver = {};
+        let proxy = new Proxy(target, handler);
+        let obj = Object.create(proxy, {
+            own: {
+                writable: true,
+                configurable: true,
+                value: null
+            }
+        });
+        for (let i = 0; i < 1000; i++) {
+            shouldBe(Reflect.set(obj, 'own', i, theReceiver), true);
+            assert(obj.own === null);
+            assert(proxy.own === undefined);
+            assert(theReceiver.own === i);
+
+            shouldBe(Reflect.set(obj, 'notOwn', i, theReceiver), true);
+            // The receiver is always |theReceiver|.
+            // obj.[[Set]](P, V, theReceiver) -> Proxy.[[Set]](P, V, theReceiver) -> target.[[Set]](P, V, theReceiver)
+            assert(target.notOwn === undefined);
+            assert(proxy.notOwn === undefined);
+            assert(obj.notOwn === undefined);
+            assert(theReceiver.notOwn === i);
+        }
+    }
+
+    {
+        let called = false;
+        let target = {};
+        let handler = {
+            set: function(theTarget, propName, value, receiver) {
+                assert(target === theTarget);
+                assert(receiver === theReceiver);
+                theTarget[propName] = value;
+                called = true;
+                return true;
+            }
+        };
+
+        let theReceiver = {};
+        let proxy = new Proxy(target, handler);
+        let obj = Object.create(proxy, {
+            [0]: {
+                writable: true,
+                configurable: true,
+                value: null
+            }
+        });
+        for (let i = 0; i < 1000; i++) {
+            shouldBe(Reflect.set(obj, 0, i, theReceiver), true);
+            assert(!called);
+            assert(obj[0] === null);
+            assert(proxy[0] === undefined);
+            assert(theReceiver[0] === i);
+
+            shouldBe(Reflect.set(obj, 1, i, theReceiver), true);
+            assert(target[1] === i);
+            assert(proxy[1] === i);
+            assert(obj[1] === i);
+            assert(theReceiver[1] === undefined);
+            assert(called);
+            called = false;
+        }
+    }
+
+    {
+        let target = {};
+        let handler = { };
+
+        let theReceiver = {};
+        let proxy = new Proxy(target, handler);
+        let obj = Object.create(proxy, {
+            [0]: {
+                writable: true,
+                configurable: true,
+                value: null
+            }
+        });
+        for (let i = 0; i < 1000; i++) {
+            shouldBe(Reflect.set(obj, 0, i, theReceiver), true);
+            assert(obj[0] === null);
+            assert(proxy[0] === undefined);
+            assert(theReceiver[0] === i);
+
+            shouldBe(Reflect.set(obj, 1, i, theReceiver), true);
+            // The receiver is always |theReceiver|.
+            // obj.[[Set]](P, V, theReceiver) -> Proxy.[[Set]](P, V, theReceiver) -> target.[[Set]](P, V, theReceiver)
+            assert(target[1] === undefined);
+            assert(proxy[1] === undefined);
+            assert(obj[1] === undefined);
+            assert(theReceiver[1] === i);
+        }
+    }
+
+    {
+        let called = false;
+        let target = {};
+        let handler = {
+            set: function(theTarget, propName, value, receiver) {
+                assert(target === theTarget);
+                assert(receiver === theReceiver);
+                theTarget[propName] = value;
+                called = true;
+                return true;
+            }
+        };
+
+        let theReceiver = {};
+        let proxy = new Proxy(target, handler);
+        let obj = Object.create(proxy, {
+            [0]: {
+                writable: true,
+                configurable: true,
+                value: null
+            }
+        });
+        for (let i = 0; i < 1000; i++) {
+            shouldBe(Reflect.set(obj, 0, i, theReceiver), true);
+            assert(!called);
+            assert(obj[0] === null);
+            assert(proxy[0] === undefined);
+            assert(theReceiver[0] === i);
+
+            shouldBe(Reflect.set(obj, 1, i, theReceiver), true);
+            assert(target[1] === i);
+            assert(proxy[1] === i);
+            assert(obj[1] === i);
+            assert(theReceiver[1] === undefined);
+            assert(called);
+            called = false;
+        }
+    }
+
+    {
+        let called = false;
+        let target = [25];
+        let handler = {
+            set: function(theTarget, propName, value, receiver) {
+                assert(target === theTarget);
+                assert(receiver === theReceiver);
+                theTarget[propName] = value;
+                called = true;
+                return true;
+            }
+        };
+
+        let theReceiver = {};
+        let proxy = new Proxy(target, handler);
+        let obj = Object.create(proxy, {
+            [0]: {
+                writable: true,
+                configurable: true,
+                value: null
+            }
+        });
+        for (let i = 0; i < 1000; i++) {
+            shouldBe(Reflect.set(obj, 0, i, theReceiver), true);
+            assert(!called);
+            assert(obj[0] === null);
+            assert(proxy[0] === 25);
+            assert(theReceiver[0] === i);
+
+            shouldBe(Reflect.set(obj, 1, i, theReceiver), true);
+            assert(target[1] === i);
+            assert(proxy[1] === i);
+            assert(obj[1] === i);
+            assert(theReceiver[1] === undefined);
+            assert(called);
+            called = false;
+        }
+    }
+
+    {
+        let called = false;
+        let ogTarget = {};
+        let target = new Proxy(ogTarget, {
+            set: function(theTarget, propName, value, receiver) {
+                assert(theTarget === ogTarget);
+                assert(receiver === theReceiver);
+                called = true;
+                theTarget[propName] = value;
+            }
+        });
+        let handler = { };
+
+        let theReceiver = {};
+        let proxy = new Proxy(target, handler);
+        let obj = Object.create(proxy, {
+            own: {
+                writable: true,
+                configurable: true,
+                value: null
+            }
+        });
+        for (let i = 0; i < 1000; i++) {
+            shouldBe(Reflect.set(obj, 'own', i, theReceiver), true);
+            assert(!called);
+            assert(obj.own === null);
+            assert(proxy.own === undefined);
+            assert(theReceiver.own === i);
+
+            shouldBe(Reflect.set(obj, 'notOwn', i, theReceiver), false);
+            assert(target.notOwn === i);
+            assert(proxy.notOwn === i);
+            assert(obj.notOwn === i);
+            assert(theReceiver.notOwn === undefined);
+            assert(called);
+            called = false;
+        }
+    }
+
+    {
+        let called = false;
+        let ogTarget = [25];
+        let target = new Proxy(ogTarget, {
+            set: function(theTarget, propName, value, receiver) {
+                assert(theTarget === ogTarget);
+                assert(receiver === theReceiver);
+                called = true;
+                theTarget[propName] = value;
+            }
+        });
+        let handler = { };
+
+        let theReceiver = {};
+        let proxy = new Proxy(target, handler);
+        let obj = Object.create(proxy, {
+            [0]: {
+                writable: true,
+                configurable: true,
+                value: null
+            }
+        });
+        for (let i = 0; i < 1000; i++) {
+            shouldBe(Reflect.set(obj, 0, i, theReceiver), true);
+            assert(!called);
+            assert(obj[0] === null);
+            assert(proxy[0] === 25);
+            assert(theReceiver[0] === i);
+
+            shouldBe(Reflect.set(obj, 1, i, theReceiver), false);
+            assert(target[1] === i);
+            assert(proxy[1] === i);
+            assert(obj[1] === i);
+            assert(theReceiver[1] === undefined);
+            assert(called);
+            called = false;
+        }
+    }
+}());
diff --git a/Source/JavaScriptCore/tests/stress/reflect-set-with-global-proxy.js b/Source/JavaScriptCore/tests/stress/reflect-set-with-global-proxy.js
new file mode 100644 (file)
index 0000000..1e44f23
--- /dev/null
@@ -0,0 +1,206 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function unreachable()
+{
+    throw new Error('unreachable');
+}
+
+function receiverTest(object, receiver)
+{
+    shouldBe(Reflect.set(object, 'Cocoa', 42, receiver), true);
+    shouldBe(Reflect.get(receiver, 'Cocoa'), 42);
+    shouldBe(Reflect.get(object, 'Cocoa'), undefined);
+
+    // Existing.
+    shouldBe(Reflect.set(object, 'Matcha', 40), true);
+    shouldBe(Reflect.get(object, 'Matcha'), 40);
+    shouldBe(Reflect.set(object, 'Matcha', 42, receiver), true);
+    shouldBe(Reflect.get(receiver, 'Matcha'), 42);
+    shouldBe(Reflect.get(object, 'Matcha'), 40);
+
+    // Existing non writable own descriptor.
+    Reflect.defineProperty(object, 'Cappuccino', {
+        value: 'nice',
+        writable: false
+    });
+    shouldBe(Reflect.set(object, 'Cappuccino', 42, receiver), false);
+    shouldBe(Reflect.get(receiver, 'Cappuccino'), undefined);
+    shouldBe(receiver.hasOwnProperty('Cappuccino'), false);
+    shouldBe(Reflect.get(object, 'Cappuccino'), 'nice');
+
+    // Existing non writable receiver descriptor.
+    Reflect.defineProperty(receiver, 'Kilimanjaro', {
+        value: 'good',
+        writable: false
+    });
+    shouldBe(Reflect.set(object, 'Kilimanjaro', 42, receiver), false);
+    shouldBe(Reflect.get(receiver, 'Kilimanjaro'), 'good');
+    shouldBe(receiver.hasOwnProperty('Kilimanjaro'), true);
+    shouldBe(Reflect.get(object, 'Kilimanjaro'), undefined);
+    shouldBe(object.hasOwnProperty('Kilimanjaro'), false);
+
+    shouldBe(Reflect.set(object, 'Kilimanjaro', 42, 'receiver'), false);
+
+    // Receiver accessors.
+    shouldBe(Reflect.defineProperty(receiver, 'Mocha', {
+        get()
+        {
+            return 42;
+        },
+        configurable: true
+    }), true);
+    shouldBe(Reflect.set(object, 'Mocha', 42, receiver), false);
+    shouldBe(Reflect.defineProperty(receiver, 'Mocha', {
+        set(value)
+        {
+            unreachable();
+        },
+        configurable: true
+    }), true);
+    shouldBe(Reflect.set(object, 'Mocha', 42, receiver), false);
+    shouldBe(receiver.value, undefined);
+    shouldBe(Reflect.defineProperty(object, 'Mocha', {
+        get(value)
+        {
+        },
+        configurable: true
+    }), true);
+    shouldBe(Reflect.set(object, 'Mocha', 42, receiver), false);
+    shouldBe(receiver.value, undefined);
+    shouldBe(Reflect.defineProperty(object, 'Mocha', {
+        set(value)
+        {
+            shouldBe(this, receiver);
+            this.value = value;
+        },
+        configurable: true
+    }), true);
+    shouldBe(Reflect.set(object, 'Mocha', 42, receiver), true);
+    shouldBe(receiver.value, 42);
+    shouldBe(object.value, undefined);
+}
+
+function receiverTestIndexed(object, receiver)
+{
+    shouldBe(Reflect.set(object, 11, 42, receiver), true);
+    shouldBe(Reflect.get(receiver, 11), 42);
+    shouldBe(Reflect.get(object, 11), undefined);
+
+    // Existing.
+    shouldBe(Reflect.set(object, 12, 40), true);
+    shouldBe(Reflect.get(object, 12), 40);
+    shouldBe(Reflect.set(object, 12, 42, receiver), true);
+    shouldBe(Reflect.get(receiver, 12), 42);
+    shouldBe(Reflect.get(object, 12), 40);
+
+    // Existing non writable own descriptor.
+    Reflect.defineProperty(object, 13, {
+        value: 'nice',
+        writable: false
+    });
+    shouldBe(Reflect.set(object, 13, 42, receiver), false);
+    shouldBe(Reflect.get(receiver, 13), undefined);
+    shouldBe(receiver.hasOwnProperty(13), false);
+    shouldBe(Reflect.get(object, 13), 'nice');
+
+    // Existing non writable receiver descriptor.
+    Reflect.defineProperty(receiver, 14, {
+        value: 'good',
+        writable: false
+    });
+    shouldBe(Reflect.set(object, 14, 42, receiver), false);
+    shouldBe(Reflect.get(receiver, 14), 'good');
+    shouldBe(receiver.hasOwnProperty(14), true);
+    shouldBe(Reflect.get(object, 14), undefined);
+    shouldBe(object.hasOwnProperty(14), false);
+
+    // Receiver is a primitive value.
+    shouldBe(Reflect.set(object, 14, 42, 'receiver'), false);
+
+    // Receiver accessors.
+    shouldBe(Reflect.defineProperty(receiver, 15, {
+        get()
+        {
+            return 42;
+        },
+        configurable: true
+    }), true);
+    shouldBe(Reflect.set(object, 15, 42, receiver), false);
+    shouldBe(Reflect.defineProperty(receiver, 15, {
+        set(value)
+        {
+            unreachable();
+        },
+        configurable: true
+    }), true);
+    shouldBe(Reflect.set(object, 15, 42, receiver), false);
+    shouldBe(receiver.value, undefined);
+    shouldBe(Reflect.defineProperty(object, 15, {
+        get(value)
+        {
+        },
+        configurable: true
+    }), true);
+    shouldBe(Reflect.set(object, 15, 42, receiver), false);
+    shouldBe(receiver.value, undefined);
+    shouldBe(Reflect.defineProperty(object, 15, {
+        set(value)
+        {
+            shouldBe(this, receiver);
+            this.value = value;
+        },
+        configurable: true
+    }), true);
+    shouldBe(Reflect.set(object, 15, 42, receiver), true);
+    shouldBe(receiver.value, 42);
+    shouldBe(object.value, undefined);
+}
+
+// The global object is wrapped with the JSProxy.
+var global = this;
+
+receiverTest(global, {});
+receiverTestIndexed(global, {});
+Reflect.defineProperty(global, 'OK1', {
+    set: function (value) {
+        shouldBe(this, global);
+    }
+});
+function test1()
+{
+    global.OK1 = 'Hello';
+}
+noInline(test1);
+for (var i = 0; i < 1e4; ++i)
+    test1();
+
+Reflect.defineProperty(global, 'OK2', {
+    set: function (value) {
+        'use strict';
+        shouldBe(this, global);
+    }
+});
+function test2()
+{
+    global.OK2 = 'Hello';
+}
+noInline(test2);
+for (var i = 0; i < 1e4; ++i)
+    test2();
+
+var receiver = {};
+Reflect.defineProperty(global, 'OK3', {
+    set: function (value) {
+        shouldBe(this, receiver);
+    }
+});
+function test3()
+{
+    shouldBe(Reflect.set(global, 'OK3', 'value', receiver), true);
+}
+noInline(test3);
+for (var i = 0; i < 1e4; ++i)
+    test3();
index 009f7cf..76f72f8 100644 (file)
@@ -11,28 +11,183 @@ function shouldThrow(func, message) {
         error = e;
     }
     if (!error)
-        throw new Error("not thrown.");
+        throw new Error('not thrown.');
     if (String(error) !== message)
-        throw new Error("bad error: " + String(error));
+        throw new Error('bad error: ' + String(error));
+}
+
+function unreachable()
+{
+    throw new Error('unreachable');
+}
+
+function receiverTest(object, receiver)
+{
+    shouldBe(Reflect.set(object, 'Cocoa', 42, receiver), true);
+    shouldBe(Reflect.get(receiver, 'Cocoa'), 42);
+    shouldBe(Reflect.get(object, 'Cocoa'), undefined);
+
+    // Existing.
+    shouldBe(Reflect.set(object, 'Matcha', 40), true);
+    shouldBe(Reflect.get(object, 'Matcha'), 40);
+    shouldBe(Reflect.set(object, 'Matcha', 42, receiver), true);
+    shouldBe(Reflect.get(receiver, 'Matcha'), 42);
+    shouldBe(Reflect.get(object, 'Matcha'), 40);
+
+    // Existing non writable own descriptor.
+    Reflect.defineProperty(object, 'Cappuccino', {
+        value: 'nice',
+        writable: false
+    });
+    shouldBe(Reflect.set(object, 'Cappuccino', 42, receiver), false);
+    shouldBe(Reflect.get(receiver, 'Cappuccino'), undefined);
+    shouldBe(receiver.hasOwnProperty('Cappuccino'), false);
+    shouldBe(Reflect.get(object, 'Cappuccino'), 'nice');
+
+    // Existing non writable receiver descriptor.
+    Reflect.defineProperty(receiver, 'Kilimanjaro', {
+        value: 'good',
+        writable: false
+    });
+    shouldBe(Reflect.set(object, 'Kilimanjaro', 42, receiver), false);
+    shouldBe(Reflect.get(receiver, 'Kilimanjaro'), 'good');
+    shouldBe(receiver.hasOwnProperty('Kilimanjaro'), true);
+    shouldBe(Reflect.get(object, 'Kilimanjaro'), undefined);
+    shouldBe(object.hasOwnProperty('Kilimanjaro'), false);
+
+    shouldBe(Reflect.set(object, 'Kilimanjaro', 42, 'receiver'), false);
+
+    // Receiver accessors.
+    shouldBe(Reflect.defineProperty(receiver, 'Mocha', {
+        get()
+        {
+            return 42;
+        },
+        configurable: true
+    }), true);
+    shouldBe(Reflect.set(object, 'Mocha', 42, receiver), false);
+    shouldBe(Reflect.defineProperty(receiver, 'Mocha', {
+        set(value)
+        {
+            unreachable();
+        },
+        configurable: true
+    }), true);
+    shouldBe(Reflect.set(object, 'Mocha', 42, receiver), false);
+    shouldBe(receiver.value, undefined);
+    shouldBe(Reflect.defineProperty(object, 'Mocha', {
+        get(value)
+        {
+        },
+        configurable: true
+    }), true);
+    shouldBe(Reflect.set(object, 'Mocha', 42, receiver), false);
+    shouldBe(receiver.value, undefined);
+    shouldBe(Reflect.defineProperty(object, 'Mocha', {
+        set(value)
+        {
+            shouldBe(this, receiver);
+            this.value = value;
+        },
+        configurable: true
+    }), true);
+    shouldBe(Reflect.set(object, 'Mocha', 42, receiver), true);
+    shouldBe(receiver.value, 42);
+    shouldBe(object.value, undefined);
+}
+
+function receiverTestIndexed(object, receiver)
+{
+    shouldBe(Reflect.set(object, 11, 42, receiver), true);
+    shouldBe(Reflect.get(receiver, 11), 42);
+    shouldBe(Reflect.get(object, 11), undefined);
+
+    // Existing.
+    shouldBe(Reflect.set(object, 12, 40), true);
+    shouldBe(Reflect.get(object, 12), 40);
+    shouldBe(Reflect.set(object, 12, 42, receiver), true);
+    shouldBe(Reflect.get(receiver, 12), 42);
+    shouldBe(Reflect.get(object, 12), 40);
+
+    // Existing non writable own descriptor.
+    Reflect.defineProperty(object, 13, {
+        value: 'nice',
+        writable: false
+    });
+    shouldBe(Reflect.set(object, 13, 42, receiver), false);
+    shouldBe(Reflect.get(receiver, 13), undefined);
+    shouldBe(receiver.hasOwnProperty(13), false);
+    shouldBe(Reflect.get(object, 13), 'nice');
+
+    // Existing non writable receiver descriptor.
+    Reflect.defineProperty(receiver, 14, {
+        value: 'good',
+        writable: false
+    });
+    shouldBe(Reflect.set(object, 14, 42, receiver), false);
+    shouldBe(Reflect.get(receiver, 14), 'good');
+    shouldBe(receiver.hasOwnProperty(14), true);
+    shouldBe(Reflect.get(object, 14), undefined);
+    shouldBe(object.hasOwnProperty(14), false);
+
+    // Receiver is a primitive value.
+    shouldBe(Reflect.set(object, 14, 42, 'receiver'), false);
+
+    // Receiver accessors.
+    shouldBe(Reflect.defineProperty(receiver, 15, {
+        get()
+        {
+            return 42;
+        },
+        configurable: true
+    }), true);
+    shouldBe(Reflect.set(object, 15, 42, receiver), false);
+    shouldBe(Reflect.defineProperty(receiver, 15, {
+        set(value)
+        {
+            unreachable();
+        },
+        configurable: true
+    }), true);
+    shouldBe(Reflect.set(object, 15, 42, receiver), false);
+    shouldBe(receiver.value, undefined);
+    shouldBe(Reflect.defineProperty(object, 15, {
+        get(value)
+        {
+        },
+        configurable: true
+    }), true);
+    shouldBe(Reflect.set(object, 15, 42, receiver), false);
+    shouldBe(receiver.value, undefined);
+    shouldBe(Reflect.defineProperty(object, 15, {
+        set(value)
+        {
+            shouldBe(this, receiver);
+            this.value = value;
+        },
+        configurable: true
+    }), true);
+    shouldBe(Reflect.set(object, 15, 42, receiver), true);
+    shouldBe(receiver.value, 42);
+    shouldBe(object.value, undefined);
 }
 
 shouldBe(Reflect.set.length, 3);
 
 shouldThrow(() => {
-    Reflect.set("hello", "hello", 42);
+    Reflect.set('hello', 'hello', 42);
 }, `TypeError: Reflect.set requires the first argument be an object`);
 
 var symbol = Symbol();
 
-(function receiverCase() {
-    // FIXME: Currently, receiver is not supported.
+(function simpleReceiverCase() {
     var receiver = {};
     var object = {
     };
 
     shouldBe(Reflect.set(object, 'Cocoa', 42, receiver), true);
-    // shouldBe(Reflect.get(object, 'Cocoa'), undefined);
-    // shouldBe(Reflect.get(receiver, 'Cocoa'), 42);
+    shouldBe(Reflect.get(object, 'Cocoa'), undefined);
+    shouldBe(Reflect.get(receiver, 'Cocoa'), 42);
 
     var object2 = {
         set Cocoa(value) {
@@ -42,11 +197,8 @@ var symbol = Symbol();
         }
     };
     shouldBe(Reflect.set(object, 'Cocoa', 42, receiver), true);
-    // shouldBe(Reflect.get(object, 'Cocoa'), undefined);
-    // shouldBe(Reflect.get(receiver, 'Cocoa'), 42);
-}());
-
-(function proxyCase() {
+    shouldBe(Reflect.get(object, 'Cocoa'), undefined);
+    shouldBe(Reflect.get(receiver, 'Cocoa'), 42);
 }());
 
 (function objectCase() {
@@ -142,6 +294,9 @@ var symbol = Symbol();
     shouldBe(Reflect.get(object, symbol), 'Cocoa');
     shouldBe(Reflect.set(object, symbol, 42), false);
     shouldBe(Reflect.get(object, symbol), 'Cocoa');
+
+    receiverTest({}, {});
+    receiverTestIndexed({}, {});
 }());
 
 (function arrayCase() {
@@ -156,7 +311,7 @@ var symbol = Symbol();
     shouldBe(Reflect.get(object, symbol), undefined);
     shouldBe(Reflect.set(object, symbol, 42), true);
     shouldBe(Reflect.get(object, symbol), 42);
-    object[1000000] = "Hello";
+    object[1000000] = 'Hello';
     shouldBe(Reflect.set(object, 0, 50), true);
     shouldBe(Reflect.get(object, 0), 50);
 
@@ -260,6 +415,26 @@ var symbol = Symbol();
     shouldBe(Reflect.get(object, symbol), 'Cocoa');
     shouldBe(Reflect.set(object, symbol, 42), false);
     shouldBe(Reflect.get(object, symbol), 'Cocoa');
+
+    receiverTest([], []);
+    receiverTest({}, []);
+    receiverTest([], {});
+    receiverTestIndexed([], []);
+    receiverTestIndexed({}, []);
+    receiverTestIndexed([], {});
+
+    var array = [0, 1, 2, 3];
+    var receiver = {};
+    shouldBe(Reflect.set(array, 'length', 'V', receiver), true);
+    shouldBe(Reflect.get(receiver, 'length'), 'V');
+    shouldBe(receiver.hasOwnProperty('length'), true);
+    shouldBe(Reflect.get(array, 'length'), 4);
+    Object.freeze(array);
+    var receiver = {};
+    shouldBe(Reflect.set(array, 'length', 'V', receiver), false);
+    shouldBe(Reflect.get(receiver, 'length'), undefined);
+    shouldBe(receiver.hasOwnProperty('length'), false);
+    shouldBe(Reflect.get(array, 'length'), 4);
 }());
 
 (function arrayBufferCase() {
@@ -274,7 +449,7 @@ var symbol = Symbol();
     shouldBe(Reflect.get(object, symbol), undefined);
     shouldBe(Reflect.set(object, symbol, 42), true);
     shouldBe(Reflect.get(object, symbol), 42);
-    object[1000000] = "Hello";
+    object[1000000] = 'Hello';
     shouldBe(Reflect.set(object, 0, 50), true);
     shouldBe(Reflect.get(object, 0), 50);
 
@@ -377,6 +552,13 @@ var symbol = Symbol();
     shouldBe(Reflect.get(object, symbol), 'Cocoa');
     shouldBe(Reflect.set(object, symbol, 42), false);
     shouldBe(Reflect.get(object, symbol), 'Cocoa');
+
+    receiverTest(new ArrayBuffer(64), new ArrayBuffer(64));
+    receiverTest({}, new ArrayBuffer(64));
+    receiverTest(new ArrayBuffer(64), {});
+    receiverTestIndexed(new ArrayBuffer(64), new ArrayBuffer(64));
+    receiverTestIndexed({}, new ArrayBuffer(64));
+    receiverTestIndexed(new ArrayBuffer(64), {});
 }());
 
 [
@@ -401,7 +583,7 @@ var symbol = Symbol();
     shouldBe(Reflect.get(object, symbol), undefined);
     shouldBe(Reflect.set(object, symbol, 42), true);
     shouldBe(Reflect.get(object, symbol), 42);
-    object[1000000] = "Hello";
+    object[1000000] = 'Hello';
     shouldBe(Reflect.set(object, 0, 50), true);
     shouldBe(Reflect.get(object, 0), 50);
 
@@ -496,6 +678,18 @@ var symbol = Symbol();
     shouldBe(Reflect.get(object, symbol), 'Cocoa');
     shouldBe(Reflect.set(object, symbol, 42), false);
     shouldBe(Reflect.get(object, symbol), 'Cocoa');
+
+    receiverTest(new TypedArray(64), new TypedArray(64));
+    receiverTest(new TypedArray(64), {});
+    receiverTest({}, new TypedArray(64));
+
+    var object = new TypedArray(64);
+    var receiver = {};
+    // The receiver is ignored when the property name is an indexed one.
+    // shouldBe(Reflect.set(object, 0, 42, receiver), true);
+    shouldBe(Reflect.set(object, 0, 42, receiver), true);
+    shouldBe(Reflect.get(object, 0), 42);
+    shouldBe(receiver.hasOwnProperty(0), false);
 });
 
 
@@ -616,6 +810,22 @@ var symbol = Symbol();
     test3();
     test4();
     test5();
+
+    function getArguments() { return arguments; }
+
+    receiverTest(getArguments(), getArguments());
+    receiverTest({}, getArguments());
+    receiverTest(getArguments(), {});
+    receiverTestIndexed(getArguments(), getArguments());
+    receiverTestIndexed({}, getArguments());
+    receiverTestIndexed(getArguments(), {});
+
+    var args = getArguments(0, 1, 2);
+    var receiver = {};
+    shouldBe(Reflect.set(args, 0, 'V', receiver), true);
+    shouldBe(Reflect.get(receiver, 0), 'V');
+    shouldBe(Reflect.set(args, 'length', 'V', receiver), true);
+    shouldBe(Reflect.get(receiver, 'length'), 'V');
 }());
 
 (function argumentStrictCase() {
@@ -740,11 +950,27 @@ var symbol = Symbol();
     test3();
     test4();
     test5();
+
+    function getArguments() { return arguments; }
+
+    receiverTest(getArguments(), getArguments());
+    receiverTest({}, getArguments());
+    receiverTest(getArguments(), {});
+    receiverTestIndexed(getArguments(), getArguments());
+    receiverTestIndexed({}, getArguments());
+    receiverTestIndexed(getArguments(), {});
+
+    var args = getArguments(0, 1, 2);
+    var receiver = {};
+    shouldBe(Reflect.set(args, 0, 'V', receiver), true);
+    shouldBe(Reflect.get(receiver, 0), 'V');
+    shouldBe(Reflect.set(args, 'length', 'V', receiver), true);
+    shouldBe(Reflect.get(receiver, 'length'), 'V');
 }());
 
 (function stringObjectCase() {
     'use strict';
-    var object = new String("Cocoa");
+    var object = new String('Cocoa');
     shouldBe(Reflect.get(object, 'hello'), undefined);
     shouldBe(Reflect.set(object, 'hello', 42), true);
     shouldBe(Reflect.get(object, 'hello'), 42);
@@ -754,11 +980,11 @@ var symbol = Symbol();
     shouldBe(Reflect.get(object, symbol), undefined);
     shouldBe(Reflect.set(object, symbol, 42), true);
     shouldBe(Reflect.get(object, symbol), 42);
-    object[1000000] = "Cocoa";
+    object[1000000] = 'Cocoa';
     shouldBe(Reflect.set(object, 0, 50), false);
     shouldBe(Reflect.get(object, 0), 'C');
 
-    var object = new String("Cocoa");
+    var object = new String('Cocoa');
     shouldBe(Reflect.defineProperty(object, 'hello', {
         value: 'Cocoa',
         writable: false
@@ -783,23 +1009,23 @@ var symbol = Symbol();
     shouldBe(Reflect.get(object, symbol), 'Cocoa');
 
     // Length.
-    var object = new String("Cocoa");
+    var object = new String('Cocoa');
     shouldBe(Reflect.get(object, 'length'), 5);
     shouldBe(Reflect.set(object, 'length', 'Cappuccino'), false);
     shouldBe(Reflect.get(object, 'length'), 5);
 
-    var object = new String("Cocoa");
+    var object = new String('Cocoa');
     shouldBe(Reflect.get(object, 'length'), 5);
     shouldBe(Reflect.set(object, 'length', 20), false);
     shouldBe(Reflect.get(object, 'length'), 5);
 
-    var object = new String("Cocoa");
+    var object = new String('Cocoa');
     Object.freeze(object);
     shouldBe(Reflect.get(object, 'length'), 5);
     shouldBe(Reflect.set(object, 'length', 20), false);
     shouldBe(Reflect.get(object, 'length'), 5);
 
-    var object = new String("Cocoa");
+    var object = new String('Cocoa');
     shouldBe(Reflect.defineProperty(object, 'hello', {
         get() {
             return 'Cocoa';
@@ -831,7 +1057,7 @@ var symbol = Symbol();
     shouldBe(Reflect.set(object, symbol, 42), true);  // Return true since the setter exists.
     shouldBe(Reflect.get(object, symbol), 'Cocoa');
 
-    var object = new String("Cocoa");
+    var object = new String('Cocoa');
     shouldBe(Reflect.defineProperty(object, 'hello', {
         get() {
             return 'Cocoa';
@@ -856,9 +1082,35 @@ var symbol = Symbol();
     shouldBe(Reflect.get(object, symbol), 'Cocoa');
     shouldBe(Reflect.set(object, symbol, 42), false);
     shouldBe(Reflect.get(object, symbol), 'Cocoa');
+
+    receiverTest(new String('Hello'), new String('World'));
+    receiverTest({}, new String('World'));
+    receiverTest(new String('Hello'), {});
+    // Tested indice should be out of range of the string object.
+    receiverTestIndexed(new String('Hello'), new String('World'));
+    receiverTestIndexed({}, new String('World'));
+    receiverTestIndexed(new String('Hello'), {});
+
+    var string = new String('Hello');
+    var receiver = {};
+    shouldBe(Reflect.set(string, 0, 'V', receiver), false);
+    shouldBe(Reflect.get(receiver, 0), undefined);
+    shouldBe(receiver.hasOwnProperty(0), false);
+    shouldBe(Reflect.set(string, 'length', 'V', receiver), false);
+    shouldBe(Reflect.get(receiver, 'length'), undefined);
+    shouldBe(receiver.hasOwnProperty('length'), false);
 }());
 
-(function customSetter() {
+(function regExpCase() {
+    receiverTest(/hello/, /world/);
+    receiverTest({}, /world/);
+    receiverTest(/hello/, {});
+    receiverTestIndexed(/hello/, /world/);
+    receiverTestIndexed({}, /world/);
+    receiverTestIndexed(/hello/, {});
+}());
+
+(function customValue() {
     // In this case, RegExp.multiline behaves like a setter because it coerce boolean type.
     // Anyway, it's OK, because RegExp.multiline is not specified in the spec.
 
@@ -867,6 +1119,11 @@ var symbol = Symbol();
         shouldBe(Reflect.get(RegExp, 'multiline'), true);
         shouldBe(Reflect.set(RegExp, 'multiline', 0), true);
         shouldBe(Reflect.get(RegExp, 'multiline'), false);
+
+        var receiver = {};
+        shouldBe(Reflect.set(RegExp, 'multiline', 'Cappuccino', receiver), true);
+        shouldBe(Reflect.get(receiver, 'multiline'), 'Cappuccino');
+        shouldBe(Reflect.get(RegExp, 'multiline'), false);
     }
 
     function test2() {
@@ -875,6 +1132,11 @@ var symbol = Symbol();
         shouldBe(Reflect.get(RegExp, 'multiline'), true);
         shouldBe(Reflect.set(RegExp, 'multiline', 0), true);
         shouldBe(Reflect.get(RegExp, 'multiline'), false);
+
+        var receiver = {};
+        shouldBe(Reflect.set(RegExp, 'multiline', 'Cappuccino', receiver), true);
+        shouldBe(Reflect.get(receiver, 'multiline'), 'Cappuccino');
+        shouldBe(Reflect.get(RegExp, 'multiline'), false);
     }
 
     function test3() {
@@ -887,6 +1149,10 @@ var symbol = Symbol();
         shouldBe(Reflect.get(RegExp, 'multiline'), false);
         shouldBe(Reflect.set(RegExp, 'multiline', 0), false);
         shouldBe(Reflect.get(RegExp, 'multiline'), false);
+
+        var receiver = {};
+        shouldBe(Reflect.set(RegExp, 'multiline', 'Cappuccino', receiver), false);
+        shouldBe(Reflect.get(receiver, 'multiline'), undefined);
     }
 
     test1();
@@ -904,6 +1170,11 @@ var symbol = Symbol();
     shouldBe(Reflect.set(regexp, 'lastIndex', 'Hello'), true);
     shouldBe(Reflect.get(regexp, 'lastIndex'), 'Hello');
 
+    var receiver = {};
+    shouldBe(Reflect.set(regexp, 'lastIndex', 'Cocoa', receiver), true);
+    shouldBe(Reflect.get(receiver, 'lastIndex'), 'Cocoa');
+    shouldBe(Reflect.get(regexp, 'lastIndex'), 'Hello');
+
     shouldBe(Reflect.defineProperty(regexp, 'lastIndex', {
         value: 42,
         writable: false
@@ -912,9 +1183,14 @@ var symbol = Symbol();
     shouldBe(Reflect.set(regexp, 'lastIndex', 'Hello'), false);
     shouldBe(Reflect.get(regexp, 'lastIndex'), 42);
 
+    var receiver = {};
+    shouldBe(Reflect.set(regexp, 'lastIndex', 'Cocoa', receiver), false);
+    shouldBe(Reflect.get(receiver, 'lastIndex'), undefined);
+    shouldBe(Reflect.get(regexp, 'lastIndex'), 42);
+
     shouldThrow(function () {
         'use strict';
-        regexp.lastIndex = "NG";
+        regexp.lastIndex = 'NG';
     }, `TypeError: Attempted to assign to readonly property.`);
 }());
 
@@ -927,4 +1203,21 @@ var symbol = Symbol();
     shouldBe(Reflect.get(func, 'caller'), null);
     shouldBe(Reflect.set(func, 'caller', 42), false);
     shouldBe(Reflect.get(func, 'caller'), null);
+
+    receiverTest(function () {}, function () {});
+    receiverTest({}, function () {});
+    receiverTest(function () {}, {});
+    receiverTestIndexed(function () {}, function () {});
+    receiverTestIndexed({}, function () {});
+    receiverTestIndexed(function () {}, {});
+
+    var receiver = {};
+    shouldBe(Reflect.set(func, 'arguments', 'V', receiver), false);
+    shouldBe(Reflect.get(receiver, 'arguments'), undefined);
+    shouldBe(receiver.hasOwnProperty('arguments'), false);
+    shouldBe(Reflect.get(func, 'arguments'), null);
+    shouldBe(Reflect.set(func, 'caller', 'V', receiver), false);
+    shouldBe(Reflect.get(receiver, 'caller'), undefined);
+    shouldBe(receiver.hasOwnProperty('caller'), false);
+    shouldBe(Reflect.get(func, 'caller'), null);
 }());