[runtime] Harden JSFunction::CalculateInstanceSizeHelper(...)


Bug: chromium:808192
Change-Id: I80136d291d5c21c311903bffc96d86d109f5cdc9
Reviewed-on: https://chromium-review.googlesource.com/902103
Commit-Queue: Jakob Kummerow <[email protected]>
Reviewed-by: Jakob Kummerow <[email protected]>
Cr-Commit-Position: refs/heads/master@{#51255}
diff --git a/src/objects.cc b/src/objects.cc
index e4a7e6f..71965e1 100644
--- a/src/objects.cc
+++ b/src/objects.cc
@@ -12985,6 +12985,56 @@
   map->StartInobjectSlackTracking();
 }
 
+namespace {
+bool FastInitializeDerivedMap(Isolate* isolate, Handle<JSFunction> new_target,
+                              Handle<JSFunction> constructor,
+                              Handle<Map> constructor_initial_map) {
+  // Check that |function|'s initial map still in sync with the |constructor|,
+  // otherwise we must create a new initial map for |function|.
+  if (new_target->has_initial_map() &&
+      new_target->initial_map()->GetConstructor() == *constructor) {
+    DCHECK(new_target->instance_prototype()->IsJSReceiver());
+    return true;
+  }
+  InstanceType instance_type = constructor_initial_map->instance_type();
+  DCHECK(CanSubclassHaveInobjectProperties(instance_type));
+  // Create a new map with the size and number of in-object properties
+  // suggested by |function|.
+
+  // Link initial map and constructor function if the new.target is actually a
+  // subclass constructor.
+  if (!IsDerivedConstructor(new_target->shared()->kind())) return false;
+
+  int instance_size;
+  int in_object_properties;
+  int embedder_fields =
+      JSObject::GetEmbedderFieldCount(*constructor_initial_map);
+  bool success = JSFunction::CalculateInstanceSizeForDerivedClass(
+      new_target, instance_type, embedder_fields, &instance_size,
+      &in_object_properties);
+
+  Handle<Map> map;
+  if (success) {
+    int pre_allocated = constructor_initial_map->GetInObjectProperties() -
+                        constructor_initial_map->UnusedPropertyFields();
+    CHECK_LE(constructor_initial_map->instance_size(), instance_size);
+    int unused_property_fields = in_object_properties - pre_allocated;
+    map = Map::CopyInitialMap(constructor_initial_map, instance_size,
+                              in_object_properties, unused_property_fields);
+  } else {
+    map = Map::CopyInitialMap(constructor_initial_map);
+  }
+  map->set_new_target_is_base(false);
+  Handle<Object> prototype(new_target->instance_prototype(), isolate);
+  JSFunction::SetInitialMap(new_target, map, prototype);
+  DCHECK(new_target->instance_prototype()->IsJSReceiver());
+  map->SetConstructor(*constructor);
+  map->set_construction_counter(Map::kNoSlackTracking);
+  map->StartInobjectSlackTracking();
+  return true;
+}
+
+}  // namespace
 
 // static
 MaybeHandle<Map> JSFunction::GetDerivedMap(Isolate* isolate,
@@ -12995,55 +13045,16 @@
   Handle<Map> constructor_initial_map(constructor->initial_map(), isolate);
   if (*new_target == *constructor) return constructor_initial_map;
 
+  Handle<Map> result_map;
   // Fast case, new.target is a subclass of constructor. The map is cacheable
   // (and may already have been cached). new.target.prototype is guaranteed to
   // be a JSReceiver.
   if (new_target->IsJSFunction()) {
     Handle<JSFunction> function = Handle<JSFunction>::cast(new_target);
-
-    // Check that |function|'s initial map still in sync with the |constructor|,
-    // otherwise we must create a new initial map for |function|.
-    if (function->has_initial_map() &&
-        function->initial_map()->GetConstructor() == *constructor) {
+    if (FastInitializeDerivedMap(isolate, function, constructor,
+                                 constructor_initial_map)) {
       return handle(function->initial_map(), isolate);
     }
-
-    // Create a new map with the size and number of in-object properties
-    // suggested by |function|.
-
-    // Link initial map and constructor function if the new.target is actually a
-    // subclass constructor.
-    if (IsDerivedConstructor(function->shared()->kind())) {
-      Handle<Object> prototype(function->instance_prototype(), isolate);
-      InstanceType instance_type = constructor_initial_map->instance_type();
-      DCHECK(CanSubclassHaveInobjectProperties(instance_type));
-      int embedder_fields =
-          JSObject::GetEmbedderFieldCount(*constructor_initial_map);
-      int pre_allocated = constructor_initial_map->GetInObjectProperties() -
-                          constructor_initial_map->UnusedPropertyFields();
-      int instance_size;
-      int in_object_properties;
-      bool success = CalculateInstanceSizeForDerivedClass(
-          function, instance_type, embedder_fields, &instance_size,
-          &in_object_properties);
-
-      int unused_property_fields = in_object_properties - pre_allocated;
-
-      Handle<Map> map;
-      if (success) {
-        map = Map::CopyInitialMap(constructor_initial_map, instance_size,
-                                  in_object_properties, unused_property_fields);
-      } else {
-        map = Map::CopyInitialMap(constructor_initial_map);
-      }
-      map->set_new_target_is_base(false);
-
-      JSFunction::SetInitialMap(function, map, prototype);
-      map->SetConstructor(*constructor);
-      map->set_construction_counter(Map::kNoSlackTracking);
-      map->StartInobjectSlackTracking();
-      return map;
-    }
   }
 
   // Slow path, new.target is either a proxy or can't cache the map.
@@ -13085,7 +13096,7 @@
 
   Handle<Map> map = Map::CopyInitialMap(constructor_initial_map);
   map->set_new_target_is_base(false);
-  DCHECK(prototype->IsJSReceiver());
+  CHECK(prototype->IsJSReceiver());
   if (map->prototype() != *prototype) Map::SetPrototype(map, prototype);
   map->SetConstructor(*constructor);
   return map;
@@ -13779,15 +13790,17 @@
                                              int* instance_size,
                                              int* in_object_properties) {
   int header_size = JSObject::GetHeaderSize(instance_type, has_prototype_slot);
-  DCHECK_LE(requested_embedder_fields,
-            (JSObject::kMaxInstanceSize - header_size) >> kPointerSizeLog2);
+  int max_nof_fields =
+      (JSObject::kMaxInstanceSize - header_size) >> kPointerSizeLog2;
+  CHECK_LE(max_nof_fields, JSObject::kMaxInObjectProperties);
+  *in_object_properties = Min(requested_in_object_properties, max_nof_fields);
+  CHECK_LE(requested_embedder_fields, max_nof_fields - *in_object_properties);
   *instance_size =
-      Min(header_size +
-              ((requested_embedder_fields + requested_in_object_properties)
-               << kPointerSizeLog2),
-          JSObject::kMaxInstanceSize);
-  *in_object_properties = ((*instance_size - header_size) >> kPointerSizeLog2) -
-                          requested_embedder_fields;
+      header_size +
+      ((requested_embedder_fields + *in_object_properties) << kPointerSizeLog2);
+  CHECK_EQ(*in_object_properties,
+           ((*instance_size - header_size) >> kPointerSizeLog2) -
+               requested_embedder_fields);
 }
 
 // static
@@ -13797,7 +13810,6 @@
     int* in_object_properties) {
   Isolate* isolate = function->GetIsolate();
   int expected_nof_properties = 0;
-  bool result = true;
   for (PrototypeIterator iter(isolate, function, kStartAtReceiver);
        !iter.IsAtEnd(); iter.Advance()) {
     Handle<JSReceiver> current =
@@ -13810,21 +13822,24 @@
     if (shared->is_compiled() ||
         Compiler::Compile(func, Compiler::CLEAR_EXCEPTION)) {
       DCHECK(shared->is_compiled());
-      expected_nof_properties += shared->expected_nof_properties();
+      int count = shared->expected_nof_properties();
+      // Check that the estimate is sane.
+      if (expected_nof_properties <= JSObject::kMaxInObjectProperties - count) {
+        expected_nof_properties += count;
+      } else {
+        expected_nof_properties = JSObject::kMaxInObjectProperties;
+      }
     } else if (!shared->is_compiled()) {
       // In case there was a compilation error for the constructor we will
       // throw an error during instantiation. Hence we directly return 0;
-      result = false;
-      break;
+      return false;
     }
-    if (!IsDerivedConstructor(shared->kind())) {
-      break;
-    }
+    if (!IsDerivedConstructor(shared->kind())) break;
   }
   CalculateInstanceSizeHelper(instance_type, true, requested_embedder_fields,
                               expected_nof_properties, instance_size,
                               in_object_properties);
-  return result;
+  return true;
 }
 
 
diff --git a/src/objects.h b/src/objects.h
index f242843..94059dae 100644
--- a/src/objects.h
+++ b/src/objects.h
@@ -2707,6 +2707,7 @@
   STATIC_ASSERT(kHeaderSize == Internals::kJSObjectHeaderSize);
   static const int kMaxInObjectProperties =
       (kMaxInstanceSize - kHeaderSize) >> kPointerSizeLog2;
+  STATIC_ASSERT(kMaxInObjectProperties <= kMaxNumberOfDescriptors);
 
   class BodyDescriptor;
   // No weak fields.
diff --git a/test/cctest/test-inobject-slack-tracking.cc b/test/cctest/test-inobject-slack-tracking.cc
index 48ec9e1..be6a71b 100644
--- a/test/cctest/test-inobject-slack-tracking.cc
+++ b/test/cctest/test-inobject-slack-tracking.cc
@@ -622,6 +622,12 @@
   TestClassHierarchy(hierarchy_desc, static_cast<int>(hierarchy_desc.size()));
 }
 
+TEST(Subclasses) {
+  std::vector<int> hierarchy_desc;
+  hierarchy_desc.push_back(50);
+  hierarchy_desc.push_back(128);
+  TestSubclassChain(hierarchy_desc);
+}
 
 TEST(LongSubclassChain1) {
   std::vector<int> hierarchy_desc;
diff --git a/test/mjsunit/es6/destructuring-assignment.js b/test/mjsunit/es6/destructuring-assignment.js
index 579c877..dee7a0b 100644
--- a/test/mjsunit/es6/destructuring-assignment.js
+++ b/test/mjsunit/es6/destructuring-assignment.js
@@ -513,25 +513,31 @@
   }
 
   function FakeNewTarget() {}
-  assertEquals(undefined, ReturnNewTarget1());
-  assertEquals(ReturnNewTarget1, new ReturnNewTarget1());
-  assertEquals(FakeNewTarget,
-               Reflect.construct(ReturnNewTarget1, [], FakeNewTarget));
 
-  assertEquals(undefined, ReturnNewTarget2());
-  assertEquals(ReturnNewTarget2, new ReturnNewTarget2());
-  assertEquals(FakeNewTarget,
-               Reflect.construct(ReturnNewTarget2, [], FakeNewTarget));
+  function construct() {
+    assertEquals(undefined, ReturnNewTarget1());
+    assertEquals(ReturnNewTarget1, new ReturnNewTarget1());
+    assertEquals(FakeNewTarget,
+                 Reflect.construct(ReturnNewTarget1, [], FakeNewTarget));
 
-  assertEquals(undefined, ReturnNewTarget3());
-  assertEquals(ReturnNewTarget3, new ReturnNewTarget3());
-  assertEquals(FakeNewTarget,
-               Reflect.construct(ReturnNewTarget3, [], FakeNewTarget));
+    assertEquals(undefined, ReturnNewTarget2());
+    assertEquals(ReturnNewTarget2, new ReturnNewTarget2());
+    assertEquals(FakeNewTarget,
+                 Reflect.construct(ReturnNewTarget2, [], FakeNewTarget));
 
-  assertEquals(undefined, ReturnNewTarget4());
-  assertEquals(ReturnNewTarget4, new ReturnNewTarget4());
-  assertEquals(FakeNewTarget,
-               Reflect.construct(ReturnNewTarget4, [], FakeNewTarget));
+    assertEquals(undefined, ReturnNewTarget3());
+    assertEquals(ReturnNewTarget3, new ReturnNewTarget3());
+    assertEquals(FakeNewTarget,
+                 Reflect.construct(ReturnNewTarget3, [], FakeNewTarget));
+
+    assertEquals(undefined, ReturnNewTarget4());
+    assertEquals(ReturnNewTarget4, new ReturnNewTarget4());
+    assertEquals(FakeNewTarget,
+                 Reflect.construct(ReturnNewTarget4, [], FakeNewTarget));
+  }
+  construct();
+  FakeNewTarget.prototype = 1;
+  construct();
 })();
 
 (function testSuperCall() {
diff --git a/test/mjsunit/mjsunit.status b/test/mjsunit/mjsunit.status
index 98ba88a..6410235 100644
--- a/test/mjsunit/mjsunit.status
+++ b/test/mjsunit/mjsunit.status
@@ -86,6 +86,9 @@
   # TODO(arm): This seems to flush out a bug on arm with simulator.
   'array-constructor': [PASS, SLOW, ['arch == arm and simulator == True', SKIP]],
 
+  # Very slow test
+  'regress/regress-crbug-808192' : [PASS, NO_VARIANTS, ['arch == arm or arch == arm64 or arch == android_arm or arch == android_arm64 or arch == mipsel or arch == mips64el or arch == mips64 or arch == mips', SKIP]],
+
   # Very slow on ARM and MIPS, contains no architecture dependent code.
   'unicode-case-overoptimization': [PASS, NO_VARIANTS, ['arch == arm or arch == arm64 or arch == android_arm or arch == android_arm64 or arch == mipsel or arch == mips64el or arch == mips64 or arch == mips', SKIP]],
   'regress/regress-3976': [PASS, NO_VARIANTS, ['arch == arm or arch == arm64 or arch == android_arm or arch == android_arm64 or arch == mipsel or arch == mips64el or arch == mips64 or arch == mips', SKIP]],
diff --git a/test/mjsunit/regress/regress-crbug-808192.js b/test/mjsunit/regress/regress-crbug-808192.js
new file mode 100644
index 0000000..3336c00
--- /dev/null
+++ b/test/mjsunit/regress/regress-crbug-808192.js
@@ -0,0 +1,32 @@
+// Copyright 2018 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TODO(cbruni): enable always opt once v8:7438
+// Flags: --expose-gc --no-always-opt
+
+const f = eval(`(function f(i) {
+  if (i == 0) {
+    class Derived extends Object {
+      constructor() {
+        super();
+        ${"this.a=1;".repeat(0x3fffe-8)}
+      }
+    }
+    return Derived;
+  }
+
+  class DerivedN extends f(i-1) {
+    constructor() {
+      super();
+      ${"this.a=1;".repeat(0x40000-8)}
+    }
+  }
+
+  return DerivedN;
+})`);
+
+let a = new (f(0x7ff))();
+a.a = 1;
+gc();
+assertEquals(1, a.a);