[json] Replace JSArray with std::vector to track circular structures

This CL prepares JSON#stringify for improved error messages when
serializing circular structures. To this end, we also push the
key/index, in addition to the object itself, onto the stack that keeps
track of circular structures.

The stack itself is changed from a JSArray to a std::vector.

R=yangguo@chromium.org

Bug: v8:6513, v8:8698
Change-Id: I6dc4cb3be75a4514281411c654337f37c8798e55
Reviewed-on: https://chromium-review.googlesource.com/c/1424863
Reviewed-by: Yang Guo <yangguo@chromium.org>
Commit-Queue: Simon Zünd <szuend@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59019}
diff --git a/src/json-stringifier.cc b/src/json-stringifier.cc
index a92b5e2..1d3858f 100644
--- a/src/json-stringifier.cc
+++ b/src/json-stringifier.cc
@@ -74,12 +74,13 @@
     return SerializeDouble(object->value());
   }
 
-  Result SerializeJSValue(Handle<JSValue> object);
+  Result SerializeJSValue(Handle<JSValue> object, Handle<Object> key);
 
-  V8_INLINE Result SerializeJSArray(Handle<JSArray> object);
-  V8_INLINE Result SerializeJSObject(Handle<JSObject> object);
+  V8_INLINE Result SerializeJSArray(Handle<JSArray> object, Handle<Object> key);
+  V8_INLINE Result SerializeJSObject(Handle<JSObject> object,
+                                     Handle<Object> key);
 
-  Result SerializeJSProxy(Handle<JSProxy> object);
+  Result SerializeJSProxy(Handle<JSProxy> object, Handle<Object> key);
   Result SerializeJSReceiverSlow(Handle<JSReceiver> object);
   Result SerializeArrayLikeSlow(Handle<JSReceiver> object, uint32_t start,
                                 uint32_t length);
@@ -105,7 +106,7 @@
   Handle<JSReceiver> CurrentHolder(Handle<Object> value,
                                    Handle<Object> inital_holder);
 
-  Result StackPush(Handle<Object> object);
+  Result StackPush(Handle<Object> object, Handle<Object> key);
   void StackPop();
 
   Factory* factory() { return isolate_->factory(); }
@@ -113,12 +114,14 @@
   Isolate* isolate_;
   IncrementalStringBuilder builder_;
   Handle<String> tojson_string_;
-  Handle<JSArray> stack_;
   Handle<FixedArray> property_list_;
   Handle<JSReceiver> replacer_function_;
   uc16* gap_;
   int indent_;
 
+  using KeyObject = std::pair<Handle<Object>, Handle<Object>>;
+  std::vector<KeyObject> stack_;
+
   static const int kJsonEscapeTableEntrySize = 8;
   static const char* const JsonEscapeTable;
 };
@@ -198,9 +201,12 @@
     "\xFC\0      \xFD\0      \xFE\0      \xFF\0      ";
 
 JsonStringifier::JsonStringifier(Isolate* isolate)
-    : isolate_(isolate), builder_(isolate), gap_(nullptr), indent_(0) {
+    : isolate_(isolate),
+      builder_(isolate),
+      gap_(nullptr),
+      indent_(0),
+      stack_() {
   tojson_string_ = factory()->toJSON_string();
-  stack_ = factory()->NewJSArray(8);
 }
 
 MaybeHandle<Object> JsonStringifier::Stringify(Handle<Object> object,
@@ -345,33 +351,30 @@
 
 Handle<JSReceiver> JsonStringifier::CurrentHolder(
     Handle<Object> value, Handle<Object> initial_holder) {
-  int length = Smi::ToInt(stack_->length());
-  if (length == 0) {
+  if (stack_.empty()) {
     Handle<JSObject> holder =
         factory()->NewJSObject(isolate_->object_function());
     JSObject::AddProperty(isolate_, holder, factory()->empty_string(),
                           initial_holder, NONE);
     return holder;
   } else {
-    FixedArray elements = FixedArray::cast(stack_->elements());
-    return Handle<JSReceiver>(JSReceiver::cast(elements->get(length - 1)),
+    return Handle<JSReceiver>(JSReceiver::cast(*stack_.back().second),
                               isolate_);
   }
 }
 
-JsonStringifier::Result JsonStringifier::StackPush(Handle<Object> object) {
+JsonStringifier::Result JsonStringifier::StackPush(Handle<Object> object,
+                                                   Handle<Object> key) {
   StackLimitCheck check(isolate_);
   if (check.HasOverflowed()) {
     isolate_->StackOverflow();
     return EXCEPTION;
   }
 
-  int length = Smi::ToInt(stack_->length());
   {
     DisallowHeapAllocation no_allocation;
-    FixedArray elements = FixedArray::cast(stack_->elements());
-    for (int i = 0; i < length; i++) {
-      if (elements->get(i) == *object) {
+    for (const KeyObject& key_object : stack_) {
+      if (*key_object.second == *object) {
         AllowHeapAllocation allow_to_return_error;
         Handle<Object> error =
             factory()->NewTypeError(MessageTemplate::kCircularStructure);
@@ -380,15 +383,11 @@
       }
     }
   }
-  JSArray::SetLength(stack_, length + 1);
-  FixedArray::cast(stack_->elements())->set(length, *object);
+  stack_.emplace_back(key, object);
   return SUCCESS;
 }
 
-void JsonStringifier::StackPop() {
-  int length = Smi::ToInt(stack_->length());
-  stack_->set_length(Smi::FromInt(length - 1));
-}
+void JsonStringifier::StackPop() { stack_.pop_back(); }
 
 template <bool deferred_string_key>
 JsonStringifier::Result JsonStringifier::Serialize_(Handle<Object> object,
@@ -443,10 +442,10 @@
       }
     case JS_ARRAY_TYPE:
       if (deferred_string_key) SerializeDeferredKey(comma, key);
-      return SerializeJSArray(Handle<JSArray>::cast(object));
+      return SerializeJSArray(Handle<JSArray>::cast(object), key);
     case JS_VALUE_TYPE:
       if (deferred_string_key) SerializeDeferredKey(comma, key);
-      return SerializeJSValue(Handle<JSValue>::cast(object));
+      return SerializeJSValue(Handle<JSValue>::cast(object), key);
     case SYMBOL_TYPE:
       return UNCHANGED;
     default:
@@ -460,9 +459,9 @@
         // Go to slow path for global proxy and objects requiring access checks.
         if (deferred_string_key) SerializeDeferredKey(comma, key);
         if (object->IsJSProxy()) {
-          return SerializeJSProxy(Handle<JSProxy>::cast(object));
+          return SerializeJSProxy(Handle<JSProxy>::cast(object), key);
         }
-        return SerializeJSObject(Handle<JSObject>::cast(object));
+        return SerializeJSObject(Handle<JSObject>::cast(object), key);
       }
   }
 
@@ -470,7 +469,7 @@
 }
 
 JsonStringifier::Result JsonStringifier::SerializeJSValue(
-    Handle<JSValue> object) {
+    Handle<JSValue> object, Handle<Object> key) {
   Object raw = object->value();
   if (raw->IsString()) {
     Handle<Object> value;
@@ -491,7 +490,7 @@
     builder_.AppendCString(raw->IsTrue(isolate_) ? "true" : "false");
   } else {
     // ES6 24.3.2.1 step 10.c, serialize as an ordinary JSObject.
-    return SerializeJSObject(object);
+    return SerializeJSObject(object, key);
   }
   return SUCCESS;
 }
@@ -517,9 +516,9 @@
 }
 
 JsonStringifier::Result JsonStringifier::SerializeJSArray(
-    Handle<JSArray> object) {
+    Handle<JSArray> object, Handle<Object> key) {
   HandleScope handle_scope(isolate_);
-  Result stack_push = StackPush(object);
+  Result stack_push = StackPush(object, key);
   if (stack_push != SUCCESS) return stack_push;
   uint32_t length = 0;
   CHECK(object->length()->ToArrayLength(&length));
@@ -632,9 +631,9 @@
 }
 
 JsonStringifier::Result JsonStringifier::SerializeJSObject(
-    Handle<JSObject> object) {
+    Handle<JSObject> object, Handle<Object> key) {
   HandleScope handle_scope(isolate_);
-  Result stack_push = StackPush(object);
+  Result stack_push = StackPush(object, key);
   if (stack_push != SUCCESS) return stack_push;
 
   if (property_list_.is_null() &&
@@ -711,9 +710,9 @@
 }
 
 JsonStringifier::Result JsonStringifier::SerializeJSProxy(
-    Handle<JSProxy> object) {
+    Handle<JSProxy> object, Handle<Object> key) {
   HandleScope scope(isolate_);
-  Result stack_push = StackPush(object);
+  Result stack_push = StackPush(object, key);
   if (stack_push != SUCCESS) return stack_push;
   Maybe<bool> is_array = Object::IsArray(object);
   if (is_array.IsNothing()) return EXCEPTION;