Fix JSON.stringify wrt harmony proxies.
authoryangguo@chromium.org <yangguo@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 5 Nov 2012 10:53:56 +0000 (10:53 +0000)
committeryangguo@chromium.org <yangguo@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 5 Nov 2012 10:53:56 +0000 (10:53 +0000)
BUG=

Review URL: https://chromiumcodereview.appspot.com/11312063

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@12851 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/json-stringifier.h
src/json.js
src/runtime.cc
test/mjsunit/harmony/proxies-json.js [new file with mode: 0644]
test/mjsunit/json2.js

index 1b4ab9a..728575b 100644 (file)
@@ -48,7 +48,7 @@ class BasicJsonStringifier BASE_EMBEDDED {
 
   enum Result { UNCHANGED, SUCCESS, EXCEPTION, CIRCULAR, STACK_OVERFLOW };
 
-  template <bool is_ascii> void Extend();
+  void Extend();
 
   void ChangeEncoding();
 
@@ -82,9 +82,14 @@ class BasicJsonStringifier BASE_EMBEDDED {
   Handle<Object> ApplyToJsonFunction(Handle<Object> object,
                                      Handle<Object> key);
 
+  Result SerializeGeneric(Handle<Object> object,
+                          Handle<Object> key,
+                          bool deferred_comma,
+                          bool deferred_key);
+
   // Entry point to serialize the object.
   INLINE(Result SerializeObject(Handle<Object> obj)) {
-    return Serialize_<false>(obj, false, isolate_->factory()->empty_string());
+    return Serialize_<false>(obj, false, factory_->empty_string());
   }
 
   // Serialize an array element.
@@ -147,7 +152,7 @@ class BasicJsonStringifier BASE_EMBEDDED {
   void StackPop();
 
   INLINE(Handle<String> accumulator()) {
-    return Handle<String>(String::cast(accumulator_store_->value()));
+    return Handle<String>(String::cast(accumulator_store_->value()), isolate_);
   }
 
   INLINE(void set_accumulator(Handle<String> string)) {
@@ -155,6 +160,7 @@ class BasicJsonStringifier BASE_EMBEDDED {
   }
 
   Isolate* isolate_;
+  Factory* factory_;
   // We use a value wrapper for the string accumulator to keep the
   // (indirect) handle to it in the outermost handle scope.
   Handle<JSValue> accumulator_store_;
@@ -209,13 +215,13 @@ const char* const BasicJsonStringifier::JsonEscapeTable =
 
 BasicJsonStringifier::BasicJsonStringifier(Isolate* isolate)
     : isolate_(isolate), current_index_(0), is_ascii_(true) {
+  factory_ = isolate_->factory();
   accumulator_store_ = Handle<JSValue>::cast(
-      isolate_->factory()->ToObject(isolate_->factory()->empty_string()));
+                           factory_->ToObject(factory_->empty_string()));
   part_length_ = kInitialPartLength;
-  current_part_ =
-      isolate_->factory()->NewRawAsciiString(kInitialPartLength);
-  tojson_symbol_ = isolate_->factory()->LookupAsciiSymbol("toJSON");
-  stack_ = isolate_->factory()->NewJSArray(8);
+  current_part_ = factory_->NewRawAsciiString(kInitialPartLength);
+  tojson_symbol_ = factory_->LookupAsciiSymbol("toJSON");
+  stack_ = factory_->NewJSArray(8);
 }
 
 
@@ -225,9 +231,9 @@ MaybeObject* BasicJsonStringifier::Stringify(Handle<Object> object) {
       return isolate_->heap()->undefined_value();
     case SUCCESS:
       ShrinkCurrentPart();
-      return *isolate_->factory()->NewConsString(accumulator(), current_part_);
+      return *factory_->NewConsString(accumulator(), current_part_);
     case CIRCULAR:
-      return isolate_->Throw(*isolate_->factory()->NewTypeError(
+      return isolate_->Throw(*factory_->NewTypeError(
                  "circular_structure", HandleVector<Object>(NULL, 0)));
     case STACK_OVERFLOW:
       return isolate_->StackOverflow();
@@ -246,7 +252,7 @@ void BasicJsonStringifier::Append_(Char c) {
     SeqTwoByteString::cast(*current_part_)->SeqTwoByteStringSet(
         current_index_++, c);
   }
-  if (current_index_ == part_length_) Extend<is_ascii>();
+  if (current_index_ == part_length_) Extend();
 }
 
 
@@ -260,20 +266,20 @@ Handle<Object> BasicJsonStringifier::GetProperty(Handle<JSObject> object,
                                                  Handle<String> key) {
   LookupResult lookup(isolate_);
   object->LocalLookupRealNamedProperty(*key, &lookup);
-  if (!lookup.IsProperty()) return isolate_->factory()->undefined_value();
+  if (!lookup.IsProperty()) return factory_->undefined_value();
   switch (lookup.type()) {
     case NORMAL: {
       Object* value = lookup.holder()->GetNormalizedProperty(&lookup);
       ASSERT(!value->IsTheHole());
-      return Handle<Object>(value);
+      return Handle<Object>(value, isolate_);
     }
     case FIELD: {
       Object* value = lookup.holder()->FastPropertyAt(lookup.GetFieldIndex());
       ASSERT(!value->IsTheHole());
-      return Handle<Object>(value);
+      return Handle<Object>(value, isolate_);
     }
     case CONSTANT_FUNCTION:
-      return Handle<Object>(lookup.GetConstantFunction());
+      return Handle<Object>(lookup.GetConstantFunction(), isolate_);
     default: {
       PropertyAttributes attr;
       return Object::GetProperty(object, object, &lookup, key, &attr);
@@ -294,7 +300,7 @@ Handle<Object> BasicJsonStringifier::ApplyToJsonFunction(
   if (!fun->IsJSFunction()) return object;
 
   // Call toJSON function.
-  if (key->IsSmi()) key = isolate_->factory()->NumberToString(key);
+  if (key->IsSmi()) key = factory_->NumberToString(key);
   Handle<Object> argv[] = { key };
   bool has_exception = false;
   HandleScope scope(isolate_);
@@ -338,47 +344,84 @@ BasicJsonStringifier::Result BasicJsonStringifier::Serialize_(
     if (object.is_null()) return EXCEPTION;
   }
 
-  if (object->IsJSObject()) {
-    if (object->IsJSFunction()) return UNCHANGED;
+  if (object->IsSmi()) {
     if (deferred_string_key) SerializeDeferredKey(comma, key);
-    if (object->IsJSArray()) {
-      return SerializeJSArray(Handle<JSArray>::cast(object));
-    } else if (object->IsJSValue()) {
-      return SerializeJSValue(Handle<JSValue>::cast(object));
-    } else {
-      return SerializeJSObject(Handle<JSObject>::cast(object));
-    }
+    return SerializeSmi(Smi::cast(*object));
   }
 
-  // Handle non-JSObject.
-  if (object->IsString()) {
-    if (deferred_string_key) SerializeDeferredKey(comma, key);
-    SerializeString(Handle<String>::cast(object));
-    return SUCCESS;
-  } else if (object->IsSmi()) {
-    if (deferred_string_key) SerializeDeferredKey(comma, key);
-    return SerializeSmi(Smi::cast(*object));
-  } else if (object->IsHeapNumber()) {
-    if (deferred_string_key) SerializeDeferredKey(comma, key);
-    return SerializeHeapNumber(Handle<HeapNumber>::cast(object));
-  } else if (object->IsOddball()) {
-    switch (Oddball::cast(*object)->kind()) {
-      case Oddball::kFalse:
-        if (deferred_string_key) SerializeDeferredKey(comma, key);
-        Append("false");
-        return SUCCESS;
-      case Oddball::kTrue:
+  switch (HeapObject::cast(*object)->map()->instance_type()) {
+    case HEAP_NUMBER_TYPE:
+      if (deferred_string_key) SerializeDeferredKey(comma, key);
+      return SerializeHeapNumber(Handle<HeapNumber>::cast(object));
+    case ODDBALL_TYPE:
+      switch (Oddball::cast(*object)->kind()) {
+        case Oddball::kFalse:
+          if (deferred_string_key) SerializeDeferredKey(comma, key);
+          Append("false");
+          return SUCCESS;
+        case Oddball::kTrue:
+          if (deferred_string_key) SerializeDeferredKey(comma, key);
+          Append("true");
+          return SUCCESS;
+        case Oddball::kNull:
+          if (deferred_string_key) SerializeDeferredKey(comma, key);
+          Append("null");
+          return SUCCESS;
+        default:
+          return UNCHANGED;
+      }
+    case JS_ARRAY_TYPE:
+      if (deferred_string_key) SerializeDeferredKey(comma, key);
+      return SerializeJSArray(Handle<JSArray>::cast(object));
+    case JS_VALUE_TYPE:
+      if (deferred_string_key) SerializeDeferredKey(comma, key);
+      return SerializeJSValue(Handle<JSValue>::cast(object));
+    case JS_FUNCTION_TYPE:
+      return UNCHANGED;
+    default:
+      if (object->IsString()) {
         if (deferred_string_key) SerializeDeferredKey(comma, key);
-        Append("true");
+        SerializeString(Handle<String>::cast(object));
         return SUCCESS;
-      case Oddball::kNull:
+      } else if (object->IsJSObject()) {
         if (deferred_string_key) SerializeDeferredKey(comma, key);
-        Append("null");
-        return SUCCESS;
-    }
+        return SerializeJSObject(Handle<JSObject>::cast(object));
+      } else {
+        return SerializeGeneric(object, key, comma, deferred_string_key);
+      }
   }
+}
+
 
-  return UNCHANGED;
+BasicJsonStringifier::Result BasicJsonStringifier::SerializeGeneric(
+    Handle<Object> object,
+    Handle<Object> key,
+    bool deferred_comma,
+    bool deferred_key) {
+  Handle<JSObject> builtins(isolate_->native_context()->builtins());
+  Handle<JSFunction> builtin = Handle<JSFunction>::cast(
+      v8::internal::GetProperty(builtins, "JSONSerializeAdapter"));
+
+  Handle<Object> argv[] = { key, object };
+  bool has_exception = false;
+  Handle<Object> result =
+      Execution::Call(builtin, object, 2, argv, &has_exception);
+  if (has_exception) return EXCEPTION;
+  if (result->IsUndefined()) return UNCHANGED;
+  if (deferred_key) {
+    if (key->IsSmi()) key = factory_->NumberToString(key);
+    SerializeDeferredKey(deferred_comma, key);
+  }
+
+  Handle<String> result_string = Handle<String>::cast(result);
+  // Shrink current part, attach it to the accumulator, also attach the result
+  // string to the accumulator, and allocate a new part.
+  ShrinkCurrentPart();  // Shrink.
+  part_length_ = kInitialPartLength;  // Allocate conservatively.
+  Extend();             // Attach current part and allocate new part.
+  // Attach result string to the accumulator.
+  set_accumulator(factory_->NewConsString(accumulator(), result_string));
+  return SUCCESS;
 }
 
 
@@ -437,7 +480,8 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSArray(
   Append('[');
   switch (object->GetElementsKind()) {
     case FAST_SMI_ELEMENTS: {
-      Handle<FixedArray> elements(FixedArray::cast(object->elements()));
+      Handle<FixedArray> elements(
+          FixedArray::cast(object->elements()), isolate_);
       for (int i = 0; i < length; i++) {
         if (i > 0) Append(',');
         SerializeSmi(Smi::cast(elements->get(i)));
@@ -446,7 +490,7 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSArray(
     }
     case FAST_DOUBLE_ELEMENTS: {
       Handle<FixedDoubleArray> elements(
-          FixedDoubleArray::cast(object->elements()));
+          FixedDoubleArray::cast(object->elements()), isolate_);
       for (int i = 0; i < length; i++) {
         if (i > 0) Append(',');
         SerializeDouble(elements->get_scalar(i));
@@ -454,10 +498,12 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSArray(
       break;
     }
     case FAST_ELEMENTS: {
-      Handle<FixedArray> elements(FixedArray::cast(object->elements()));
+      Handle<FixedArray> elements(
+          FixedArray::cast(object->elements()), isolate_);
       for (int i = 0; i < length; i++) {
         if (i > 0) Append(',');
-        Result result = SerializeElement(Handle<Object>(elements->get(i)), i);
+        Result result =
+            SerializeElement(Handle<Object>(elements->get(i), isolate_), i);
         if (result == SUCCESS) continue;
         if (result == UNCHANGED) {
           Append("null");
@@ -510,7 +556,8 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSObject(
   Result stack_push = StackPush(object);
   if (stack_push != SUCCESS) return stack_push;
   if (object->IsJSGlobalProxy()) {
-    object = Handle<JSObject>(JSObject::cast(object->GetPrototype()));
+    object = Handle<JSObject>(
+                 JSObject::cast(object->GetPrototype()), isolate_);
     ASSERT(object->IsGlobalObject());
   }
   bool has_exception = false;
@@ -524,11 +571,11 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSObject(
     Handle<String> key_handle;
     Handle<Object> property;
     if (key->IsString()) {
-      key_handle = Handle<String>(String::cast(key));
+      key_handle = Handle<String>(String::cast(key), isolate_);
       property = GetProperty(object, key_handle);
     } else {
       ASSERT(key->IsNumber());
-      key_handle = isolate_->factory()->NumberToString(Handle<Object>(key));
+      key_handle = factory_->NumberToString(Handle<Object>(key, isolate_));
       uint32_t index;
       if (key->IsSmi()) {
         property = Object::GetElement(object, Smi::cast(key)->value());
@@ -553,7 +600,7 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSObject(
 void BasicJsonStringifier::ShrinkCurrentPart() {
   ASSERT(current_index_ < part_length_);
   if (current_index_ == 0) {
-    current_part_ = isolate_->factory()->empty_string();
+    current_part_ = factory_->empty_string();
     return;
   }
 
@@ -580,19 +627,15 @@ void BasicJsonStringifier::ShrinkCurrentPart() {
 }
 
 
-template <bool is_ascii>
 void BasicJsonStringifier::Extend() {
-  set_accumulator(
-      isolate_->factory()->NewConsString(accumulator(), current_part_));
+  set_accumulator(factory_->NewConsString(accumulator(), current_part_));
   if (part_length_ <= kMaxPartLength / kPartLengthGrowthFactor) {
     part_length_ *= kPartLengthGrowthFactor;
   }
-  if (is_ascii) {
-    current_part_ =
-        isolate_->factory()->NewRawAsciiString(part_length_);
+  if (is_ascii_) {
+    current_part_ = factory_->NewRawAsciiString(part_length_);
   } else {
-    current_part_ =
-        isolate_->factory()->NewRawTwoByteString(part_length_);
+    current_part_ = factory_->NewRawTwoByteString(part_length_);
   }
   current_index_ = 0;
 }
@@ -600,10 +643,8 @@ void BasicJsonStringifier::Extend() {
 
 void BasicJsonStringifier::ChangeEncoding() {
   ShrinkCurrentPart();
-  set_accumulator(
-      isolate_->factory()->NewConsString(accumulator(), current_part_));
-  current_part_ =
-      isolate_->factory()->NewRawTwoByteString(part_length_);
+  set_accumulator(factory_->NewConsString(accumulator(), current_part_));
+  current_part_ = factory_->NewRawTwoByteString(part_length_);
   current_index_ = 0;
   is_ascii_ = false;
 }
index cc7fd7c..9ab1a31 100644 (file)
@@ -215,4 +215,12 @@ function SetUpJSON() {
   ));
 }
 
+
+function JSONSerializeAdapter(key, object) {
+  var holder = {};
+  holder[key] = object;
+  // No need to pass the actual holder since there is no replacer function.
+  return JSONSerialize(key, holder, void 0, new InternalArray(), "", "");
+}
+
 SetUpJSON();
index b0a6b5e..e90e3a9 100644 (file)
@@ -9188,7 +9188,7 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_AllocateInNewSpace) {
 RUNTIME_FUNCTION(MaybeObject*, Runtime_PushIfAbsent) {
   ASSERT(args.length() == 2);
   CONVERT_ARG_CHECKED(JSArray, array, 0);
-  CONVERT_ARG_CHECKED(JSObject, element, 1);
+  CONVERT_ARG_CHECKED(JSReceiver, element, 1);
   RUNTIME_ASSERT(array->HasFastSmiOrObjectElements());
   int length = Smi::cast(array->length())->value();
   FixedArray* elements = FixedArray::cast(array->elements());
diff --git a/test/mjsunit/harmony/proxies-json.js b/test/mjsunit/harmony/proxies-json.js
new file mode 100644 (file)
index 0000000..0a5be65
--- /dev/null
@@ -0,0 +1,156 @@
+// Copyright 2012 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Flags: --harmony
+
+function testStringify(expected, object) {
+  // Test fast case that bails out to slow case.
+  assertEquals(expected, JSON.stringify(object));
+  // Test slow case.
+  assertEquals(expected, JSON.stringify(object, undefined, 0));
+}
+
+// Test serializing a proxy, function proxy and objects that contain them.
+var handler1 = {
+  get: function(target, name) {
+    return name.toUpperCase();
+  },
+  enumerate: function(target) {
+    return ['a', 'b', 'c'];
+  },
+  getOwnPropertyDescriptor: function(target, name) {
+    return { enumerable: true };
+  }
+}
+
+var proxy1 = Proxy.create(handler1);
+testStringify('{"a":"A","b":"B","c":"C"}', proxy1);
+
+var proxy_fun = Proxy.createFunction(handler1, function() { return 1; });
+testStringify(undefined, proxy_fun);
+testStringify('[1,null]', [1, proxy_fun]);
+
+var parent1a = { b: proxy1 };
+testStringify('{"b":{"a":"A","b":"B","c":"C"}}', parent1a);
+
+var parent1b = { a: 123, b: proxy1, c: true };
+testStringify('{"a":123,"b":{"a":"A","b":"B","c":"C"},"c":true}', parent1b);
+
+var parent1c = [123, proxy1, true];
+testStringify('[123,{"a":"A","b":"B","c":"C"},true]', parent1c);
+
+// Proxy with side effect.
+var handler2 = {
+  get: function(target, name) {
+    delete parent2.c;
+    return name.toUpperCase();
+  },
+  enumerate: function(target) {
+    return ['a', 'b', 'c'];
+  },
+  getOwnPropertyDescriptor: function(target, name) {
+    return { enumerable: true };
+  }
+}
+
+var proxy2 = Proxy.create(handler2);
+var parent2 = { a: "delete", b: proxy2, c: "remove" };
+var expected2 = '{"a":"delete","b":{"a":"A","b":"B","c":"C"}}';
+assertEquals(expected2, JSON.stringify(parent2));
+parent2.c = "remove";  // Revert side effect.
+assertEquals(expected2, JSON.stringify(parent2, undefined, 0));
+
+// Proxy with a get function that uses the first argument.
+var handler3 = {
+  get: function(target, name) {
+    if (name == 'valueOf') return function() { return "proxy" };
+    return name + "(" + target + ")";
+  },
+  enumerate: function(target) {
+    return ['a', 'b', 'c'];
+  },
+  getOwnPropertyDescriptor: function(target, name) {
+    return { enumerable: true };
+  }
+}
+
+var proxy3 = Proxy.create(handler3);
+var parent3 = { x: 123, y: proxy3 }
+testStringify('{"x":123,"y":{"a":"a(proxy)","b":"b(proxy)","c":"c(proxy)"}}',
+              parent3);
+
+// Empty proxy.
+var handler4 = {
+  get: function(target, name) {
+    return 0;
+  },
+  enumerate: function(target) {
+    return [];
+  },
+  getOwnPropertyDescriptor: function(target, name) {
+    return { enumerable: false };
+  }
+}
+
+var proxy4 = Proxy.create(handler4);
+testStringify('{}', proxy4);
+testStringify('{"a":{}}', { a: proxy4 });
+
+// Proxy that provides a toJSON function that uses this.
+var handler5 = {
+  get: function(target, name) {
+    if (name == 'z') return 97000;
+    return function(key) { return key.charCodeAt(0) + this.z; };
+  },
+  enumerate: function(target) {
+    return ['toJSON', 'z'];
+  },
+  getOwnPropertyDescriptor: function(target, name) {
+    return { enumerable: true };
+  }
+}
+
+var proxy5 = Proxy.create(handler5);
+testStringify('{"a":97097}', { a: proxy5 });
+
+// Proxy that provides a toJSON function that returns undefined.
+var handler6 = {
+  get: function(target, name) {
+    return function(key) { return undefined; };
+  },
+  enumerate: function(target) {
+    return ['toJSON'];
+  },
+  getOwnPropertyDescriptor: function(target, name) {
+    return { enumerable: true };
+  }
+}
+
+var proxy6 = Proxy.create(handler6);
+testStringify('[1,null,true]', [1, proxy6, true]);
+testStringify('{"a":1,"c":true}', {a: 1, b: proxy6, c: true});
+
index 33e0aec..82ff572 100644 (file)
@@ -109,6 +109,10 @@ assertEquals('["00","11"]', JSON.stringify(tojson_with_key_2));
 var tojson_ex = { toJSON: function(key) { throw "123" } };
 assertThrows(function() { JSON.stringify(tojson_ex); });
 
+// Test toJSON with access to this.
+var obj = { toJSON: function(key) { return this.a + key; }, a: "x" };
+assertEquals('{"y":"xy"}', JSON.stringify({y: obj}));
+
 // Test holes in arrays.
 var fast_smi = [1, 2, 3, 4];
 fast_smi.__proto__ = [7, 7, 7, 7];