Revision: 12842
Author:   [email protected]
Date:     Fri Nov  2 07:46:57 2012
Log:      Handle edge cases in basic JSON.stringify.

BUG=

Review URL: https://chromiumcodereview.appspot.com/11315009
http://code.google.com/p/v8/source/detail?r=12842

Modified:
 /branches/bleeding_edge/src/json-stringifier.h
 /branches/bleeding_edge/src/json.js
 /branches/bleeding_edge/test/mjsunit/json2.js
 /branches/bleeding_edge/test/mjsunit/regress/regress-json-stringify-gc.js

=======================================
--- /branches/bleeding_edge/src/json-stringifier.h      Tue Oct 30 08:29:34 2012
+++ /branches/bleeding_edge/src/json-stringifier.h      Fri Nov  2 07:46:57 2012
@@ -46,7 +46,7 @@
   static const int kMaxPartLength = 16 * 1024;
   static const int kPartLengthGrowthFactor = 2;

-  enum Result { UNCHANGED, SUCCESS, BAILOUT, CIRCULAR, STACK_OVERFLOW };
+  enum Result { UNCHANGED, SUCCESS, EXCEPTION, CIRCULAR, STACK_OVERFLOW };

   template <bool is_ascii> void Extend();

@@ -79,27 +79,36 @@
   Handle<Object> GetProperty(Handle<JSObject> object,
                              Handle<String> key);

-  bool MayHaveToJsonFunction(Handle<JSObject> object);
+  Handle<Object> ApplyToJsonFunction(Handle<Object> object,
+                                     Handle<Object> key);

-  INLINE(Result Serialize(Handle<Object> object)) {
-    return Serialize_<false>(object);
+  // Entry point to serialize the object.
+  INLINE(Result SerializeObject(Handle<Object> obj)) {
+ return Serialize_<false>(obj, false, isolate_->factory()->empty_string());
   }

-  INLINE(Result SerializeDeferred(Handle<Object> object,
-                           bool deferred_comma,
-                           Handle<String> deferred_key)) {
+  // Serialize an array element.
+  // The index may serve as argument for the toJSON function.
+  INLINE(Result SerializeElement(Handle<Object> object, int i)) {
+ return Serialize_<false>(object, false, Handle<Object>(Smi::FromInt(i)));
+  }
+
+  // Serialize a object property.
+  // The key may or may not be serialized depending on the property.
+  // The key may also serve as argument for the toJSON function.
+  INLINE(Result SerializeProperty(Handle<Object> object,
+                                  bool deferred_comma,
+                                  Handle<String> deferred_key)) {
     ASSERT(!deferred_key.is_null());
     return Serialize_<true>(object, deferred_comma, deferred_key);
   }

-  template <bool deferred_key>
-  Result Serialize_(Handle<Object> object,
-                    bool comma = false,
-                    Handle<String> key = Handle<String>::null());
+  template <bool deferred_string_key>
+  Result Serialize_(Handle<Object> object, bool comma, Handle<Object> key);

- void SerializeDeferredKey(bool deferred_comma, Handle<String> deferred_key) { + void SerializeDeferredKey(bool deferred_comma, Handle<Object> deferred_key) {
     if (deferred_comma) Append(',');
-    SerializeString(deferred_key);
+    SerializeString(Handle<String>::cast(deferred_key));
     Append(':');
   }

@@ -110,8 +119,12 @@
     return SerializeDouble(object->value());
   }

-  INLINE(Result SerializeArray(Handle<JSArray> object));
-  INLINE(Result SerializeObject(Handle<JSObject> object));
+  Result SerializeJSValue(Handle<JSValue> object);
+
+  INLINE(Result SerializeJSArray(Handle<JSArray> object));
+  INLINE(Result SerializeJSObject(Handle<JSObject> object));
+
+  Result SerializeJSArraySlow(Handle<JSArray> object, int length);

   void SerializeString(Handle<String> object);

@@ -207,19 +220,19 @@


 MaybeObject* BasicJsonStringifier::Stringify(Handle<Object> object) {
-  switch (Serialize(object)) {
+  switch (SerializeObject(object)) {
+    case UNCHANGED:
+      return isolate_->heap()->undefined_value();
     case SUCCESS:
       ShrinkCurrentPart();
return *isolate_->factory()->NewConsString(accumulator(), current_part_);
-    case UNCHANGED:
-      return isolate_->heap()->undefined_value();
     case CIRCULAR:
       return isolate_->Throw(*isolate_->factory()->NewTypeError(
                  "circular_structure", HandleVector<Object>(NULL, 0)));
     case STACK_OVERFLOW:
       return isolate_->StackOverflow();
     default:
-      return Smi::FromInt(0);
+      return Failure::Exception();
   }
 }

@@ -261,36 +274,34 @@
     }
     case CONSTANT_FUNCTION:
       return Handle<Object>(lookup.GetConstantFunction());
-    case CALLBACKS:
-    case HANDLER:
-    case INTERCEPTOR:
-      return Handle<Object>::null();
-    case TRANSITION:
-    case NONEXISTENT:
-      UNREACHABLE();
-      break;
+    default: {
+      PropertyAttributes attr;
+      return Object::GetProperty(object, object, &lookup, key, &attr);
+    }
   }
   return Handle<Object>::null();
 }


-bool BasicJsonStringifier::MayHaveToJsonFunction(Handle<JSObject> object) {
+Handle<Object> BasicJsonStringifier::ApplyToJsonFunction(
+    Handle<Object> object, Handle<Object> key) {
   LookupResult lookup(isolate_);
-  object->LookupRealNamedProperty(*tojson_symbol_, &lookup);
-  if (!lookup.IsProperty()) return false;
-  Object* value;
-  switch (lookup.type()) {
-    case NORMAL:
-      value = lookup.holder()->GetNormalizedProperty(&lookup);
-      break;
-    case FIELD:
-      value = lookup.holder()->FastPropertyAt(lookup.GetFieldIndex());
-      break;
-    default:
-      return true;
-  }
-  ASSERT(!value->IsTheHole());
-  return value->IsSpecFunction();
+ JSObject::cast(*object)->LookupRealNamedProperty(*tojson_symbol_, &lookup);
+  if (!lookup.IsProperty()) return object;
+  PropertyAttributes attr;
+  Handle<Object> fun =
+      Object::GetProperty(object, object, &lookup, tojson_symbol_, &attr);
+  if (!fun->IsJSFunction()) return object;
+
+  // Call toJSON function.
+  if (key->IsSmi()) key = isolate_->factory()->NumberToString(key);
+  Handle<Object> argv[] = { key };
+  bool has_exception = false;
+  HandleScope scope(isolate_);
+  object = Execution::Call(fun, object, 1, argv, &has_exception);
+  // Return empty handle to signal an exception.
+  if (has_exception) return Handle<Object>::null();
+  return scope.CloseAndEscape(object);
 }


@@ -319,51 +330,49 @@
 }


-template <bool deferred_key>
+template <bool deferred_string_key>
 BasicJsonStringifier::Result BasicJsonStringifier::Serialize_(
-    Handle<Object> object, bool comma, Handle<String> key) {
+    Handle<Object> object, bool comma, Handle<Object> key) {
   if (object->IsJSObject()) {
-    // We don't deal with custom toJSON functions.
- if (MayHaveToJsonFunction(Handle<JSObject>::cast(object))) return BAILOUT;
+    object = ApplyToJsonFunction(object, key);
+    if (object.is_null()) return EXCEPTION;
+  }

-    if (object->IsJSFunction()) {
-      return UNCHANGED;
-    } else if (object->IsJSArray()) {
-      if (deferred_key) SerializeDeferredKey(comma, key);
-      return SerializeArray(Handle<JSArray>::cast(object));
+  if (object->IsJSObject()) {
+    if (object->IsJSFunction()) return UNCHANGED;
+    if (deferred_string_key) SerializeDeferredKey(comma, key);
+    if (object->IsJSArray()) {
+      return SerializeJSArray(Handle<JSArray>::cast(object));
     } else if (object->IsJSValue()) {
-      // JSValue with a custom prototype.
-      if (object->GetPrototype()->IsJSReceiver()) return BAILOUT;
-      // Unpack value wrapper and fall through.
-      object = Handle<Object>(JSValue::cast(*object)->value());
+      return SerializeJSValue(Handle<JSValue>::cast(object));
     } else {
-      if (deferred_key) SerializeDeferredKey(comma, key);
-      return SerializeObject(Handle<JSObject>::cast(object));
+      return SerializeJSObject(Handle<JSObject>::cast(object));
     }
   }

+  // Handle non-JSObject.
   if (object->IsString()) {
-    if (deferred_key) SerializeDeferredKey(comma, key);
+    if (deferred_string_key) SerializeDeferredKey(comma, key);
     SerializeString(Handle<String>::cast(object));
     return SUCCESS;
   } else if (object->IsSmi()) {
-    if (deferred_key) SerializeDeferredKey(comma, key);
+    if (deferred_string_key) SerializeDeferredKey(comma, key);
     return SerializeSmi(Smi::cast(*object));
   } else if (object->IsHeapNumber()) {
-    if (deferred_key) SerializeDeferredKey(comma, key);
+    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_key) SerializeDeferredKey(comma, key);
+        if (deferred_string_key) SerializeDeferredKey(comma, key);
         Append("false");
         return SUCCESS;
       case Oddball::kTrue:
-        if (deferred_key) SerializeDeferredKey(comma, key);
+        if (deferred_string_key) SerializeDeferredKey(comma, key);
         Append("true");
         return SUCCESS;
       case Oddball::kNull:
-        if (deferred_key) SerializeDeferredKey(comma, key);
+        if (deferred_string_key) SerializeDeferredKey(comma, key);
         Append("null");
         return SUCCESS;
     }
@@ -371,6 +380,29 @@

   return UNCHANGED;
 }
+
+
+BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSValue(
+    Handle<JSValue> object) {
+  bool has_exception = false;
+  String* class_name = object->class_name();
+  if (class_name == isolate_->heap()->String_symbol()) {
+    Handle<Object> value = Execution::ToString(object, &has_exception);
+    if (has_exception) return EXCEPTION;
+    SerializeString(Handle<String>::cast(value));
+  } else if (class_name == isolate_->heap()->Number_symbol()) {
+    Handle<Object> value = Execution::ToNumber(object, &has_exception);
+    if (has_exception) return EXCEPTION;
+    if (value->IsSmi()) return SerializeSmi(Smi::cast(*value));
+    SerializeHeapNumber(Handle<HeapNumber>::cast(value));
+  } else {
+    ASSERT(class_name == isolate_->heap()->Boolean_symbol());
+    Object* value = JSValue::cast(*object)->value();
+    ASSERT(value->IsBoolean());
+    Append(value->IsTrue() ? "true" : "false");
+  }
+  return SUCCESS;
+}


BasicJsonStringifier::Result BasicJsonStringifier::SerializeSmi(Smi* object) {
@@ -396,7 +428,7 @@
 }


-BasicJsonStringifier::Result BasicJsonStringifier::SerializeArray(
+BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSArray(
     Handle<JSArray> object) {
   HandleScope handle_scope(isolate_);
   Result stack_push = StackPush(object);
@@ -405,8 +437,7 @@
   Append('[');
   switch (object->GetElementsKind()) {
     case FAST_SMI_ELEMENTS: {
-      Handle<FixedArray> elements = Handle<FixedArray>(
-          FixedArray::cast(object->elements()));
+      Handle<FixedArray> elements(FixedArray::cast(object->elements()));
       for (int i = 0; i < length; i++) {
         if (i > 0) Append(',');
         SerializeSmi(Smi::cast(elements->get(i)));
@@ -414,7 +445,7 @@
       break;
     }
     case FAST_DOUBLE_ELEMENTS: {
-      Handle<FixedDoubleArray> elements = Handle<FixedDoubleArray>(
+      Handle<FixedDoubleArray> elements(
           FixedDoubleArray::cast(object->elements()));
       for (int i = 0; i < length; i++) {
         if (i > 0) Append(',');
@@ -423,11 +454,10 @@
       break;
     }
     case FAST_ELEMENTS: {
-      Handle<FixedArray> elements = Handle<FixedArray>(
-          FixedArray::cast(object->elements()));
+      Handle<FixedArray> elements(FixedArray::cast(object->elements()));
       for (int i = 0; i < length; i++) {
         if (i > 0) Append(',');
-        Result result = Serialize(Handle<Object>(elements->get(i)));
+ Result result = SerializeElement(Handle<Object>(elements->get(i)), i);
         if (result == SUCCESS) continue;
         if (result == UNCHANGED) {
           Append("null");
@@ -437,8 +467,14 @@
       }
       break;
     }
-    default:
-      return BAILOUT;
+ // TODO(yangguo): The FAST_HOLEY_* cases could be handled in a faster way. + // They resemble the non-holey cases except that a prototype chain lookup
+    // is necessary for holes.
+    default: {
+      Result result = SerializeJSArraySlow(object, length);
+      if (result != SUCCESS) return result;
+      break;
+    }
   }
   Append(']');
   StackPop();
@@ -447,16 +483,40 @@
 }


-BasicJsonStringifier::Result BasicJsonStringifier::SerializeObject(
+BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSArraySlow(
+    Handle<JSArray> object, int length) {
+  for (int i = 0; i < length; i++) {
+    if (i > 0) Append(',');
+    Handle<Object> element = Object::GetElement(object, i);
+    if (element->IsUndefined()) {
+      Append("null");
+    } else {
+      Result result = SerializeElement(element, i);
+      if (result == SUCCESS) continue;
+      if (result == UNCHANGED) {
+        Append("null");
+      } else {
+        return result;
+      }
+    }
+  }
+  return SUCCESS;
+}
+
+
+BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSObject(
     Handle<JSObject> object) {
   HandleScope handle_scope(isolate_);
   Result stack_push = StackPush(object);
   if (stack_push != SUCCESS) return stack_push;
-  if (object->IsJSGlobalProxy()) return BAILOUT;
-  bool threw = false;
+  if (object->IsJSGlobalProxy()) {
+    object = Handle<JSObject>(JSObject::cast(object->GetPrototype()));
+    ASSERT(object->IsGlobalObject());
+  }
+  bool has_exception = false;
   Handle<FixedArray> contents =
-      GetKeysInFixedArrayFor(object, LOCAL_ONLY, &threw);
-  if (threw) return BAILOUT;
+      GetKeysInFixedArrayFor(object, LOCAL_ONLY, &has_exception);
+  if (has_exception) return EXCEPTION;
   Append('{');
   bool comma = false;
   for (int i = 0; i < contents->length(); i++) {
@@ -478,10 +538,10 @@
         property = GetProperty(object, key_handle);
       }
     }
-    if (property.is_null()) return BAILOUT;
-    Result result = SerializeDeferred(property, comma, key_handle);
+    if (property.is_null()) return EXCEPTION;
+    Result result = SerializeProperty(property, comma, key_handle);
     if (!comma && result == SUCCESS) comma = true;
-    if (result >= BAILOUT) return result;
+    if (result >= EXCEPTION) return result;
   }
   Append('}');
   StackPop();
=======================================
--- /branches/bleeding_edge/src/json.js Mon Oct 22 07:22:58 2012
+++ /branches/bleeding_edge/src/json.js Fri Nov  2 07:46:57 2012
@@ -176,145 +176,11 @@
   // Undefined or a callable object.
   return void 0;
 }
-
-
-function BasicSerializeArray(value, stack, builder) {
-  var len = value.length;
-  if (len == 0) {
-    builder.push("[]");
-    return;
-  }
-  if (!%PushIfAbsent(stack, value)) {
-    throw MakeTypeError('circular_structure', $Array());
-  }
-  builder.push("[");
-  var val = value[0];
-  if (IS_STRING(val)) {
- // First entry is a string. Remaining entries are likely to be strings too.
-    var array_string = %QuoteJSONStringArray(value);
-    if (!IS_UNDEFINED(array_string)) {
-      // array_string also includes bracket characters so we are done.
-      builder[builder.length - 1] = array_string;
-      stack.pop();
-      return;
-    } else {
-      builder.push(%QuoteJSONString(val));
-      for (var i = 1; i < len; i++) {
-        val = value[i];
-        if (IS_STRING(val)) {
-          builder.push(%QuoteJSONStringComma(val));
-        } else {
-          builder.push(",");
-          var before = builder.length;
-          BasicJSONSerialize(i, val, stack, builder);
-          if (before == builder.length) builder[before - 1] = ",null";
-        }
-      }
-    }
-  } else if (IS_NUMBER(val)) {
- // First entry is a number. Remaining entries are likely to be numbers too.
-    builder.push(JSON_NUMBER_TO_STRING(val));
-    for (var i = 1; i < len; i++) {
-      builder.push(",");
-      val = value[i];
-      if (IS_NUMBER(val)) {
-        builder.push(JSON_NUMBER_TO_STRING(val));
-      } else {
-        var before = builder.length;
-        BasicJSONSerialize(i, val, stack, builder);
-        if (before == builder.length) builder[before - 1] = ",null";
-      }
-    }
-  } else {
-    var before = builder.length;
-    BasicJSONSerialize(0, val, stack, builder);
-    if (before == builder.length) builder.push("null");
-    for (var i = 1; i < len; i++) {
-      builder.push(",");
-      before = builder.length;
-      BasicJSONSerialize(i, value[i], stack, builder);
-      if (before == builder.length) builder[before - 1] = ",null";
-    }
-  }
-  stack.pop();
-  builder.push("]");
-}
-
-
-function BasicSerializeObject(value, stack, builder) {
-  if (!%PushIfAbsent(stack, value)) {
-    throw MakeTypeError('circular_structure', $Array());
-  }
-  builder.push("{");
-  var first = true;
-  var keys = %ObjectKeys(value);
-  var len = keys.length;
-  for (var i = 0; i < len; i++) {
-    var p = keys[i];
-    if (!first) {
-      builder.push(%QuoteJSONStringComma(p));
-    } else {
-      builder.push(%QuoteJSONString(p));
-    }
-    builder.push(":");
-    var before = builder.length;
-    BasicJSONSerialize(p, value[p], stack, builder);
-    if (before == builder.length) {
-      builder.pop();
-      builder.pop();
-    } else {
-      first = false;
-    }
-  }
-  stack.pop();
-  builder.push("}");
-}
-
-
-function BasicJSONSerialize(key, value, stack, builder) {
-  if (IS_SPEC_OBJECT(value)) {
-    var toJSON = value.toJSON;
-    if (IS_SPEC_FUNCTION(toJSON)) {
-      value = %_CallFunction(value, ToString(key), toJSON);
-    }
-  }
-  if (IS_STRING(value)) {
-    builder.push(value !== "" ? %QuoteJSONString(value) : '""');
-  } else if (IS_NUMBER(value)) {
-    builder.push(JSON_NUMBER_TO_STRING(value));
-  } else if (IS_BOOLEAN(value)) {
-    builder.push(value ? "true" : "false");
-  } else if (IS_NULL(value)) {
-    builder.push("null");
-  } else if (IS_SPEC_OBJECT(value) && !(typeof value == "function")) {
-    // Value is a non-callable object.
-    // Unwrap value if necessary
-    if (IS_NUMBER_WRAPPER(value)) {
-      value = ToNumber(value);
-      builder.push(JSON_NUMBER_TO_STRING(value));
-    } else if (IS_STRING_WRAPPER(value)) {
-      builder.push(%QuoteJSONString(ToString(value)));
-    } else if (IS_BOOLEAN_WRAPPER(value)) {
-      builder.push(%_ValueOf(value) ? "true" : "false");
-    } else if (IS_ARRAY(value)) {
-      BasicSerializeArray(value, stack, builder);
-    } else {
-      BasicSerializeObject(value, stack, builder);
-    }
-  }
-}


 function JSONStringify(value, replacer, space) {
   if (%_ArgumentsLength() == 1) {
-    var result = %BasicJSONStringify(value);
-    if (result != 0) return result;
-    var builder = new InternalArray();
-    BasicJSONSerialize('', value, new InternalArray(), builder);
-    if (builder.length == 0) return;
-    result = %_FastAsciiArrayJoin(builder, "");
-    if (!IS_UNDEFINED(result)) return result;
-    return %StringBuilderConcat(builder, builder.length, "");
+    return %BasicJSONStringify(value);
   }
   if (IS_OBJECT(space)) {
     // Unwrap 'space' if it is wrapped
@@ -339,6 +205,7 @@
   }
return JSONSerialize('', {'': value}, replacer, new InternalArray(), "", gap);
 }
+

 function SetUpJSON() {
   %CheckIsBootstrapping();
=======================================
--- /branches/bleeding_edge/test/mjsunit/json2.js       Tue Oct 30 08:29:34 2012
+++ /branches/bleeding_edge/test/mjsunit/json2.js       Fri Nov  2 07:46:57 2012
@@ -27,6 +27,89 @@

 // Flags: --allow-natives-syntax

+// Test JSON.stringify on the global object.
+var a = 12345;
+assertTrue(JSON.stringify(this).indexOf('"a":12345') > 0);
+
+// Test JSON.stringify of array in dictionary mode.
+var array_1 = [];
+var array_2 = [];
+array_1[100000] = 1;
+array_2[100000] = function() { return 1; };
+var nulls = "";
+for (var i = 0; i < 100000; i++) {
+  nulls += 'null,';
+}
+expected_1 = '[' + nulls + '1]';
+expected_2 = '[' + nulls + 'null]';
+assertEquals(expected_1, JSON.stringify(array_1));
+assertEquals(expected_2, JSON.stringify(array_2));
+
+// Test JSValue with custom prototype.
+var num_wrapper = Object(42);
+num_wrapper.__proto__ = { __proto__: null,
+                          toString: function() { return true; } };
+assertEquals('1', JSON.stringify(num_wrapper));
+
+var str_wrapper = Object('2');
+str_wrapper.__proto__ = { __proto__: null,
+                          toString: function() { return true; } };
+assertEquals('"true"', JSON.stringify(str_wrapper));
+
+var bool_wrapper = Object(false);
+bool_wrapper.__proto__ = { __proto__: null,
+                           toString: function() { return true; } };
+// Note that toString function is not evaluated here!
+assertEquals('false', JSON.stringify(bool_wrapper));
+
+// Test getters.
+var counter = 0;
+var getter_obj = { get getter() {
+                         counter++;
+                         return 123;
+                       } };
+assertEquals('{"getter":123}', JSON.stringify(getter_obj));
+assertEquals(1, counter);
+
+// Test toJSON function.
+var tojson_obj = { toJSON: function() {
+                             counter++;
+                             return [1, 2];
+                           },
+                   a: 1};
+assertEquals('[1,2]', JSON.stringify(tojson_obj));
+assertEquals(2, counter);
+
+// Test that we don't recursively look for the toJSON function.
+var tojson_proto_obj = { a: 'fail' };
+tojson_proto_obj.__proto__ = { toJSON: function() {
+                                         counter++;
+                                         return tojson_obj;
+                                       } };
+assertEquals('{"a":1}', JSON.stringify(tojson_proto_obj));
+
+// Test toJSON produced by a getter.
+var tojson_via_getter = { get toJSON() {
+                                return function(x) {
+                                         counter++;
+                                         return 321;
+                                       };
+                              },
+                          a: 1 };
+assertEquals('321', JSON.stringify(tojson_via_getter));
+
+// Test toJSON with key.
+tojson_obj = { toJSON: function(key) { return key + key; } };
+var tojson_with_key_1 = { a: tojson_obj, b: tojson_obj };
+assertEquals('{"a":"aa","b":"bb"}', JSON.stringify(tojson_with_key_1));
+var tojson_with_key_2 = [ tojson_obj, tojson_obj ];
+assertEquals('["00","11"]', JSON.stringify(tojson_with_key_2));
+
+// Test toJSON with exception.
+var tojson_ex = { toJSON: function(key) { throw "123" } };
+assertThrows(function() { JSON.stringify(tojson_ex); });
+
+// Test holes in arrays.
 var fast_smi = [1, 2, 3, 4];
 fast_smi.__proto__ = [7, 7, 7, 7];
 delete fast_smi[2];
=======================================
--- /branches/bleeding_edge/test/mjsunit/regress/regress-json-stringify-gc.js Mon Oct 22 07:22:58 2012 +++ /branches/bleeding_edge/test/mjsunit/regress/regress-json-stringify-gc.js Fri Nov 2 07:46:57 2012
@@ -37,5 +37,5 @@
 // screw up reading from the correct location.
 json1 = JSON.stringify(a);
 json2 = JSON.stringify(a);
-assertEquals(json1, json2);
+assertEquals(json1, json2, "GC caused JSON.stringify to fail.");

--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev

Reply via email to