Increase length for packed sealed object will transition to dictionary mode

Increase length of packed sealed array will create holes in packed array so transition to dictionary elements for now.
Later we can consider transitioning to holey sealed array.

Bug: chromium:952382
Change-Id: Ibe26ce56918859a114fccc1933f9c966c47c4112
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1566968
Commit-Queue: Z Nguyen-Huu <duongn@microsoft.com>
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Cr-Commit-Position: refs/heads/master@{#60884}
diff --git a/src/elements.cc b/src/elements.cc
index 0933631..31772c9 100644
--- a/src/elements.cc
+++ b/src/elements.cc
@@ -2799,13 +2799,43 @@
   static void SetLengthImpl(Isolate* isolate, Handle<JSArray> array,
                             uint32_t length,
                             Handle<FixedArrayBase> backing_store) {
-#ifdef DEBUG
-    // Can only go here if length equals old_length.
     uint32_t old_length = 0;
     CHECK(array->length()->ToArrayIndex(&old_length));
-    DCHECK_EQ(length, old_length);
-#endif
-    return;
+    if (length <= old_length) {
+      // Cannot delete entries so do nothing.
+      return;
+    }
+
+    // Transition to DICTIONARY_ELEMENTS.
+    // Convert to dictionary mode
+    Handle<NumberDictionary> new_element_dictionary =
+        old_length == 0 ? isolate->factory()->empty_slow_element_dictionary()
+                        : array->GetElementsAccessor()->Normalize(array);
+
+    // Migrate map.
+    Handle<Map> new_map = Map::Copy(isolate, handle(array->map(), isolate),
+                                    "SlowCopyForSetLengthImpl");
+    new_map->set_is_extensible(false);
+    new_map->set_elements_kind(DICTIONARY_ELEMENTS);
+    JSObject::MigrateToMap(array, new_map);
+
+    if (!new_element_dictionary.is_null()) {
+      array->set_elements(*new_element_dictionary);
+    }
+
+    if (array->elements() !=
+        ReadOnlyRoots(isolate).empty_slow_element_dictionary()) {
+      Handle<NumberDictionary> dictionary(array->element_dictionary(), isolate);
+      // Make sure we never go back to the fast case
+      array->RequireSlowElements(*dictionary);
+      JSObject::ApplyAttributesToDictionary(isolate, ReadOnlyRoots(isolate),
+                                            dictionary,
+                                            PropertyAttributes::SEALED);
+    }
+
+    // Set length
+    Handle<Object> length_obj = isolate->factory()->NewNumberFromUint(length);
+    array->set_length(*length_obj);
   }
 };
 
diff --git a/src/objects/js-objects.cc b/src/objects/js-objects.cc
index 72719b3..7587555 100644
--- a/src/objects/js-objects.cc
+++ b/src/objects/js-objects.cc
@@ -3771,12 +3771,10 @@
   return object->map()->is_extensible();
 }
 
-namespace {
-
 template <typename Dictionary>
-void ApplyAttributesToDictionary(Isolate* isolate, ReadOnlyRoots roots,
-                                 Handle<Dictionary> dictionary,
-                                 const PropertyAttributes attributes) {
+void JSObject::ApplyAttributesToDictionary(
+    Isolate* isolate, ReadOnlyRoots roots, Handle<Dictionary> dictionary,
+    const PropertyAttributes attributes) {
   int capacity = dictionary->Capacity();
   for (int i = 0; i < capacity; i++) {
     Object k;
@@ -3794,8 +3792,6 @@
   }
 }
 
-}  // namespace
-
 template <PropertyAttributes attrs>
 Maybe<bool> JSObject::PreventExtensionsWithTransition(
     Handle<JSObject> object, ShouldThrow should_throw) {
@@ -3911,11 +3907,13 @@
       if (object->IsJSGlobalObject()) {
         Handle<GlobalDictionary> dictionary(
             JSGlobalObject::cast(*object)->global_dictionary(), isolate);
-        ApplyAttributesToDictionary(isolate, roots, dictionary, attrs);
+        JSObject::ApplyAttributesToDictionary(isolate, roots, dictionary,
+                                              attrs);
       } else {
         Handle<NameDictionary> dictionary(object->property_dictionary(),
                                           isolate);
-        ApplyAttributesToDictionary(isolate, roots, dictionary, attrs);
+        JSObject::ApplyAttributesToDictionary(isolate, roots, dictionary,
+                                              attrs);
       }
     }
   }
@@ -3948,8 +3946,8 @@
     // Make sure we never go back to the fast case
     object->RequireSlowElements(*dictionary);
     if (attrs != NONE) {
-      ApplyAttributesToDictionary(isolate, ReadOnlyRoots(isolate), dictionary,
-                                  attrs);
+      JSObject::ApplyAttributesToDictionary(isolate, ReadOnlyRoots(isolate),
+                                            dictionary, attrs);
     }
   }
 
diff --git a/src/objects/js-objects.h b/src/objects/js-objects.h
index 4bb6a83..c67f70c 100644
--- a/src/objects/js-objects.h
+++ b/src/objects/js-objects.h
@@ -797,6 +797,11 @@
   static bool AllCanRead(LookupIterator* it);
   static bool AllCanWrite(LookupIterator* it);
 
+  template <typename Dictionary>
+  static void ApplyAttributesToDictionary(Isolate* isolate, ReadOnlyRoots roots,
+                                          Handle<Dictionary> dictionary,
+                                          const PropertyAttributes attributes);
+
  private:
   friend class JSReceiver;
   friend class Object;
diff --git a/test/mjsunit/object-freeze.js b/test/mjsunit/object-freeze.js
index e1a67db..7977659 100644
--- a/test/mjsunit/object-freeze.js
+++ b/test/mjsunit/object-freeze.js
@@ -561,3 +561,14 @@
 Object.freeze(arr);
 arr = arr.concat([1.5, 'b']);
 assertEquals(arr, ['a', 0.5, 1.5, 'b']);
+
+// Regression test with change length
+var arr = ['a', 'b'];
+Object.freeze(arr);
+assertEquals(arr.length, 2);
+arr.length = 3;
+assertEquals(arr.length, 2);
+arr[2] = 'c';
+assertEquals(arr[2], undefined);
+arr.length = 1;
+assertEquals(arr.length, 2);
diff --git a/test/mjsunit/object-prevent-extensions.js b/test/mjsunit/object-prevent-extensions.js
index 6f83a55..baae086 100644
--- a/test/mjsunit/object-prevent-extensions.js
+++ b/test/mjsunit/object-prevent-extensions.js
@@ -266,3 +266,15 @@
 Object.preventExtensions(arr);
 arr = arr.concat([1.5, 'b']);
 assertEquals(arr, ['a', 0.5, 1.5, 'b']);
+
+// Regression test with change length
+var arr = ['a', 'b'];
+Object.preventExtensions(arr);
+assertEquals(arr.length, 2);
+arr.length = 3;
+assertEquals(arr.length, 3);
+arr[2] = 'c';
+assertEquals(arr[2], undefined);
+arr.length = 1;
+assertEquals(arr.length, 1);
+assertEquals(arr[1], undefined);
diff --git a/test/mjsunit/object-seal.js b/test/mjsunit/object-seal.js
index 3ac880f..e83d874 100644
--- a/test/mjsunit/object-seal.js
+++ b/test/mjsunit/object-seal.js
@@ -537,3 +537,14 @@
 Object.seal(arr);
 arr = arr.concat([1.5, 'b']);
 assertEquals(arr, ['a', 0.5, 1.5, 'b']);
+
+// Regression test with change length
+var arr = ['a', 'b'];
+Object.seal(arr);
+assertEquals(arr.length, 2);
+arr.length = 3;
+assertEquals(arr.length, 3);
+arr[2] = 'c';
+assertEquals(arr[2], undefined);
+arr.length = 1;
+assertEquals(arr.length, 2);