Diff
Modified: trunk/Source/_javascript_Core/ChangeLog (197917 => 197918)
--- trunk/Source/_javascript_Core/ChangeLog 2016-03-10 02:33:12 UTC (rev 197917)
+++ trunk/Source/_javascript_Core/ChangeLog 2016-03-10 02:41:34 UTC (rev 197918)
@@ -1,3 +1,35 @@
+2016-03-09 Saam barati <[email protected]>
+
+ Add proper JSON.stringify support for Proxy when the target is an array
+ https://bugs.webkit.org/show_bug.cgi?id=155180
+
+ Reviewed by Darin Adler.
+
+ This patch makes the following type of program true:
+ `JSON.stringify(new Proxy([25], {})) === "[25]"`
+
+ We need to change the JSON stringifier to use the IsArray test
+ in section 7.2.2 of ES6 spec instead of the JSC inherits(JSArray::info())
+ test.
+
+ This patch also adds tests for general JSON.stringify support
+ of Proxy.
+
+ * runtime/ArrayConstructor.cpp:
+ (JSC::arrayConstructorIsArray):
+ (JSC::arrayConstructorPrivateFuncIsArrayConstructor):
+ * runtime/ArrayConstructor.h:
+ (JSC::isArray):
+ * runtime/JSONObject.cpp:
+ (JSC::Stringifier::Holder::object):
+ (JSC::Stringifier::appendStringifiedValue):
+ (JSC::Stringifier::startNewLine):
+ (JSC::Stringifier::Holder::Holder):
+ * tests/es6.yaml:
+ * tests/stress/proxy-json.js: Added.
+ (assert):
+ (test):
+
2016-03-09 Saam Barati <[email protected]>
ES6: Implement lexical scoping for function definitions in strict mode
Modified: trunk/Source/_javascript_Core/runtime/ArrayConstructor.cpp (197917 => 197918)
--- trunk/Source/_javascript_Core/runtime/ArrayConstructor.cpp 2016-03-10 02:33:12 UTC (rev 197917)
+++ trunk/Source/_javascript_Core/runtime/ArrayConstructor.cpp 2016-03-10 02:41:34 UTC (rev 197918)
@@ -130,26 +130,7 @@
// https://tc39.github.io/ecma262/#sec-isarray
EncodedJSValue JSC_HOST_CALL arrayConstructorIsArray(ExecState* exec)
{
- JSValue argumentValue = exec->argument(0);
-
- if (!argumentValue.isObject())
- return JSValue::encode(jsBoolean(false));
-
- JSObject* argument = jsCast<JSObject*>(argumentValue);
- while (true) {
- if (argument->inherits(JSArray::info()))
- return JSValue::encode(jsBoolean(true));
-
- if (argument->type() != ProxyObjectType)
- return JSValue::encode(jsBoolean(false));
-
- ProxyObject* proxy = jsCast<ProxyObject*>(argument);
- if (proxy->isRevoked())
- return throwVMTypeError(exec, ASCIILiteral("Array.isArray can not be called on a Proxy that has been revoked."));
- argument = proxy->target();
- }
-
- ASSERT_NOT_REACHED();
+ return JSValue::encode(jsBoolean(isArray(exec, exec->argument(0))));
}
EncodedJSValue JSC_HOST_CALL arrayConstructorPrivateFuncIsArrayConstructor(ExecState* exec)
Modified: trunk/Source/_javascript_Core/runtime/ArrayConstructor.h (197917 => 197918)
--- trunk/Source/_javascript_Core/runtime/ArrayConstructor.h 2016-03-10 02:33:12 UTC (rev 197917)
+++ trunk/Source/_javascript_Core/runtime/ArrayConstructor.h 2016-03-10 02:41:34 UTC (rev 197918)
@@ -22,6 +22,7 @@
#define ArrayConstructor_h
#include "InternalFunction.h"
+#include "ProxyObject.h"
namespace JSC {
@@ -64,6 +65,32 @@
EncodedJSValue JSC_HOST_CALL arrayConstructorPrivateFuncIsArrayConstructor(ExecState*);
+// ES6 7.2.2
+// https://tc39.github.io/ecma262/#sec-isarray
+inline bool isArray(ExecState* exec, JSValue argumentValue)
+{
+ if (!argumentValue.isObject())
+ return false;
+
+ JSObject* argument = jsCast<JSObject*>(argumentValue);
+ while (true) {
+ if (argument->inherits(JSArray::info()))
+ return true;
+
+ if (argument->type() != ProxyObjectType)
+ return false;
+
+ ProxyObject* proxy = jsCast<ProxyObject*>(argument);
+ if (proxy->isRevoked()) {
+ throwTypeError(exec, ASCIILiteral("Array.isArray can not be called on a Proxy that has been revoked."));
+ return false;
+ }
+ argument = proxy->target();
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
} // namespace JSC
#endif // ArrayConstructor_h
Modified: trunk/Source/_javascript_Core/runtime/JSONObject.cpp (197917 => 197918)
--- trunk/Source/_javascript_Core/runtime/JSONObject.cpp 2016-03-10 02:33:12 UTC (rev 197917)
+++ trunk/Source/_javascript_Core/runtime/JSONObject.cpp 2016-03-10 02:41:34 UTC (rev 197918)
@@ -26,6 +26,7 @@
#include "config.h"
#include "JSONObject.h"
+#include "ArrayConstructor.h"
#include "BooleanObject.h"
#include "Error.h"
#include "ExceptionHelpers.h"
@@ -92,7 +93,7 @@
private:
class Holder {
public:
- Holder(VM&, JSObject*);
+ Holder(VM&, ExecState*, JSObject*);
JSObject* object() const { return m_object.get(); }
@@ -289,9 +290,10 @@
Stringifier::StringifyResult Stringifier::appendStringifiedValue(StringBuilder& builder, JSValue value, JSObject* holder, const PropertyNameForFunctionCall& propertyName)
{
+ VM& vm = m_exec->vm();
// Call the toJSON function.
value = toJSON(value, propertyName);
- if (m_exec->hadException())
+ if (vm.exception())
return StringifyFailed;
// Call the replacer function.
@@ -300,7 +302,7 @@
args.append(propertyName.value(m_exec));
args.append(value);
value = call(m_exec, m_replacer.get(), m_replacerCallType, m_replacerCallData, holder, args);
- if (m_exec->hadException())
+ if (vm.exception())
return StringifyFailed;
}
@@ -314,7 +316,7 @@
value = unwrapBoxedPrimitive(m_exec, value);
- if (m_exec->hadException())
+ if (vm.exception())
return StringifyFailed;
if (value.isBoolean()) {
@@ -364,14 +366,17 @@
return StringifyFailed;
}
}
+
bool holderStackWasEmpty = m_holderStack.isEmpty();
- m_holderStack.append(Holder(m_exec->vm(), object));
+ m_holderStack.append(Holder(vm, m_exec, object));
+ if (UNLIKELY(vm.exception()))
+ return StringifyFailed;
if (!holderStackWasEmpty)
return StringifySucceeded;
do {
while (m_holderStack.last().appendNextProperty(*this, builder)) {
- if (m_exec->hadException())
+ if (vm.exception())
return StringifyFailed;
}
m_holderStack.removeLast();
@@ -408,9 +413,9 @@
builder.append(m_indent);
}
-inline Stringifier::Holder::Holder(VM& vm, JSObject* object)
+inline Stringifier::Holder::Holder(VM& vm, ExecState* exec, JSObject* object)
: m_object(vm, object)
- , m_isArray(object->inherits(JSArray::info()))
+ , m_isArray(isArray(exec, object))
, m_index(0)
#ifndef NDEBUG
, m_size(0)
Modified: trunk/Source/_javascript_Core/tests/es6.yaml (197917 => 197918)
--- trunk/Source/_javascript_Core/tests/es6.yaml 2016-03-10 02:33:12 UTC (rev 197917)
+++ trunk/Source/_javascript_Core/tests/es6.yaml 2016-03-10 02:41:34 UTC (rev 197918)
@@ -1057,7 +1057,7 @@
- path: es6/Proxy_isExtensible_handler.js
cmd: runES6 :normal
- path: es6/Proxy_JSON.stringify_support.js
- cmd: runES6 :fail
+ cmd: runES6 :normal
- path: es6/Proxy_ownKeys_handler.js
cmd: runES6 :normal
- path: es6/Proxy_preventExtensions_handler.js
Added: trunk/Source/_javascript_Core/tests/stress/proxy-json.js (0 => 197918)
--- trunk/Source/_javascript_Core/tests/stress/proxy-json.js (rev 0)
+++ trunk/Source/_javascript_Core/tests/stress/proxy-json.js 2016-03-10 02:41:34 UTC (rev 197918)
@@ -0,0 +1,114 @@
+function assert(b) {
+ if (!b)
+ throw new Error("Bad assertion.");
+}
+
+function test(f) {
+ for (let i = 0; i < 500; i++)
+ f();
+}
+
+test(function() {
+ let proxy = new Proxy([], {});
+ assert(JSON.stringify(proxy) === "[]");
+});
+
+test(function() {
+ let target = ["foo"];
+ let proxy = new Proxy(target, {});
+ assert(JSON.stringify(proxy) === JSON.stringify(target));
+});
+
+test(function() {
+ let target = {
+ foo: 25,
+ bar: false,
+ 0: 45
+ };
+ let proxy = new Proxy(target, {});
+ assert(JSON.stringify(proxy) === JSON.stringify(target));
+});
+
+test(function() {
+ let target = {
+ foo: ["baz", {foo: 45}],
+ bar: false,
+ 0: 45,
+ baz: "hello world",
+ jaz: 4553.434
+ };
+ let proxy = new Proxy(target, {});
+ assert(JSON.stringify(proxy) === JSON.stringify(target));
+});
+
+test(function() {
+ let target = {
+ foo: ["baz", {foo: 45}],
+ bar: false,
+ 0: 45,
+ baz: "hello world",
+ jaz: 4553.434
+ };
+ let proxy = new Proxy(target, {});
+ for (let i = 0; i < 50; i++)
+ proxy = new Proxy(proxy, {});
+ assert(JSON.stringify(proxy) === JSON.stringify(target));
+});
+
+test(function() {
+ let target = [20, 30, "foo", {hello: "world"}];
+ let proxy = new Proxy(target, {});
+ for (let i = 0; i < 50; i++)
+ proxy = new Proxy(proxy, {});
+ assert(JSON.stringify(proxy) === JSON.stringify(target));
+});
+
+test(function() {
+ let target = {
+ foo: ["baz", {foo: 45}],
+ bar: false,
+ 0: 45,
+ baz: "hello world",
+ jaz: 4553.434
+ };
+ let {proxy, revoke} = Proxy.revocable(target, {});
+ assert(JSON.stringify(proxy) === JSON.stringify(target));
+
+ revoke();
+ JSON.stringify(target); // Things are ok.
+ let threw = false;
+ try {
+ JSON.stringify(proxy); // Things are not ok.
+ } catch(e) {
+ threw = true;
+ assert(e.toString() === "TypeError: Proxy has already been revoked. No more operations are allowed to be performed on it.");
+ }
+ assert(threw);
+});
+
+test(function() {
+ let target = ["foo", "bar", 25, false];
+ let proxy = new Proxy(target, {});
+ let revoke;
+ for (let i = 0; i < 50; i++) {
+ if (i === 25) {
+ let result = Proxy.revocable(proxy, {});
+ proxy = result.proxy;
+ revoke = result.revoke;
+ } else {
+ proxy = new Proxy(proxy, {});
+ }
+ }
+ assert(JSON.stringify(proxy) === JSON.stringify(target));
+
+ revoke();
+ JSON.stringify(target); // Things are ok.
+ let threw = false;
+ try {
+ JSON.stringify(proxy); // Things are not ok.
+ } catch(e) {
+ threw = true;
+ assert(e.toString() === "TypeError: Proxy has already been revoked. No more operations are allowed to be performed on it.");
+ }
+ assert(threw);
+});