Fix some RELEASE_ASSERT failures caused by OutOfMemoryErrors.
authormark.lam@apple.com <mark.lam@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 28 Apr 2017 04:15:00 +0000 (04:15 +0000)
committermark.lam@apple.com <mark.lam@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 28 Apr 2017 04:15:00 +0000 (04:15 +0000)
https://bugs.webkit.org/show_bug.cgi?id=171404
<rdar://problem/31876178>

Reviewed by Saam Barati.

JSTests:

* stress/js-fixed-array-out-of-memory.js: Added.

Source/JavaScriptCore:

1. Added some tryAllocate() functions in JSCellInlines.h.
2. Consolidated the implementations of allocateCell() template functions into a
   single tryAllocateCellHelper() to reduce redundancy and eliminate needing to
   copy-paste for variations of allocateCell and tryAllocateCell.
3. Changed JSFixedArray::createFromArray() and constructEmptyArray() to check for
   allocation failure and throw an OutOfMemoryError.  It was already possible to
   throw errors from these functions for other reasons.  So, their clients are
   already ready to handle OOMEs.

* ftl/FTLOperations.cpp:
(JSC::FTL::operationMaterializeObjectInOSR):
* runtime/JSCInlines.h:
* runtime/JSCell.h:
* runtime/JSCellInlines.h:
(JSC::tryAllocateCellHelper):
(JSC::allocateCell):
(JSC::tryAllocateCell):
* runtime/JSFixedArray.h:
(JSC::JSFixedArray::createFromArray):
(JSC::JSFixedArray::tryCreate):
(JSC::JSFixedArray::create): Deleted.
* runtime/JSGlobalObject.h:
(JSC::constructEmptyArray):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@215919 268f45cc-cd09-0410-ab3c-d52691b4dbfc

JSTests/ChangeLog
JSTests/stress/js-fixed-array-out-of-memory.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/ftl/FTLOperations.cpp
Source/JavaScriptCore/runtime/JSCInlines.h
Source/JavaScriptCore/runtime/JSCell.h
Source/JavaScriptCore/runtime/JSCellInlines.h
Source/JavaScriptCore/runtime/JSFixedArray.h
Source/JavaScriptCore/runtime/JSGlobalObject.h

index abdc2b2..524de73 100644 (file)
@@ -1,3 +1,13 @@
+2017-04-27  Mark Lam  <mark.lam@apple.com>
+
+        Fix some RELEASE_ASSERT failures caused by OutOfMemoryErrors.
+        https://bugs.webkit.org/show_bug.cgi?id=171404
+        <rdar://problem/31876178>
+
+        Reviewed by Saam Barati.
+
+        * stress/js-fixed-array-out-of-memory.js: Added.
+
 2017-04-27  David Kilzer  <ddkilzer@apple.com>
 
         Enhance shouldBe()/shouldNotBe() to accept anonymous function arguments
diff --git a/JSTests/stress/js-fixed-array-out-of-memory.js b/JSTests/stress/js-fixed-array-out-of-memory.js
new file mode 100644 (file)
index 0000000..b09babf
--- /dev/null
@@ -0,0 +1,25 @@
+//@ if $buildType == "debug" then runDefault("--maxSingleAllocationSize=1048576") else skip end
+
+var exception;
+
+function foo() { };
+
+function test(length) {
+    try {
+        foo([...new Array(length)].filter(() => { }));
+    } catch (e) {
+        exception = e;
+    }
+
+    if (exception && exception != "Error: Out of memory")
+        throw "ERROR: length " + length + ": unexpected exception " + exception;
+}
+
+var sizes = [
+    1, 10, 50, 100, 500, 1000, 5000, 10000, 50000, 100000, 500000,
+    1000000, 5000000, 10000000, 50000000, 100000000, 500000000, 1000000000
+];
+
+for (size of sizes)
+    test(size);
+
index f1b8343..118196b 100644 (file)
@@ -1,3 +1,35 @@
+2017-04-27  Mark Lam  <mark.lam@apple.com>
+
+        Fix some RELEASE_ASSERT failures caused by OutOfMemoryErrors.
+        https://bugs.webkit.org/show_bug.cgi?id=171404
+        <rdar://problem/31876178>
+
+        Reviewed by Saam Barati.
+
+        1. Added some tryAllocate() functions in JSCellInlines.h.
+        2. Consolidated the implementations of allocateCell() template functions into a
+           single tryAllocateCellHelper() to reduce redundancy and eliminate needing to
+           copy-paste for variations of allocateCell and tryAllocateCell.
+        3. Changed JSFixedArray::createFromArray() and constructEmptyArray() to check for
+           allocation failure and throw an OutOfMemoryError.  It was already possible to
+           throw errors from these functions for other reasons.  So, their clients are
+           already ready to handle OOMEs.
+
+        * ftl/FTLOperations.cpp:
+        (JSC::FTL::operationMaterializeObjectInOSR):
+        * runtime/JSCInlines.h:
+        * runtime/JSCell.h:
+        * runtime/JSCellInlines.h:
+        (JSC::tryAllocateCellHelper):
+        (JSC::allocateCell):
+        (JSC::tryAllocateCell):
+        * runtime/JSFixedArray.h:
+        (JSC::JSFixedArray::createFromArray):
+        (JSC::JSFixedArray::tryCreate):
+        (JSC::JSFixedArray::create): Deleted.
+        * runtime/JSGlobalObject.h:
+        (JSC::constructEmptyArray):
+
 2017-04-27  Joseph Pecoraro  <pecoraro@apple.com>
 
         Support for promise rejection events (unhandledrejection)
index 63e6be2..1940a08 100644 (file)
@@ -432,6 +432,7 @@ extern "C" JSCell* JIT_OPERATION operationMaterializeObjectInOSR(
         // that we only support PhantomSpread over CreateRest, which is an array we create.
         // Any attempts to put a getter on any indices on the rest array will escape the array.
         JSFixedArray* fixedArray = JSFixedArray::createFromArray(exec, vm, array);
+        RELEASE_ASSERT(fixedArray);
         return fixedArray;
     }
 
index cb3db3d..70330ca 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014, 2016 Apple Inc. All rights reserved.
+ * Copyright (C) 2014-2017 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -42,6 +42,7 @@
 #include "IdentifierInlines.h"
 #include "JSArrayBufferViewInlines.h"
 #include "JSCJSValueInlines.h"
+#include "JSCellInlines.h"
 #include "JSFunctionInlines.h"
 #include "JSGlobalObjectInlines.h"
 #include "JSObjectInlines.h"
index b5cf6c2..214b98a 100644 (file)
@@ -50,11 +50,20 @@ class PropertyDescriptor;
 class PropertyNameArray;
 class Structure;
 
-template<typename T> void* allocateCell(Heap&);
-template<typename T> void* allocateCell(Heap&, size_t);
+enum class AllocationFailureMode {
+    ShouldAssertOnFailure,
+    ShouldNotAssertOnFailure
+};
+
+enum class GCDeferralContextArgPresense {
+    HasArg,
+    DoesNotHaveArg
+};
 
-template<typename T> void* allocateCell(Heap&, GCDeferralContext*);
-template<typename T> void* allocateCell(Heap&, GCDeferralContext*, size_t);
+template<typename T> void* allocateCell(Heap&, size_t = sizeof(T));
+template<typename T> void* tryAllocateCell(Heap&, size_t = sizeof(T));
+template<typename T> void* allocateCell(Heap&, GCDeferralContext*, size_t = sizeof(T));
+template<typename T> void* tryAllocateCell(Heap&, GCDeferralContext*, size_t = sizeof(T));
 
 #define DECLARE_EXPORT_INFO                                                  \
     protected:                                                               \
@@ -71,10 +80,8 @@ template<typename T> void* allocateCell(Heap&, GCDeferralContext*, size_t);
 class JSCell : public HeapCell {
     friend class JSValue;
     friend class MarkedBlock;
-    template<typename T> friend void* allocateCell(Heap&);
-    template<typename T> friend void* allocateCell(Heap&, size_t);
-    template<typename T> friend void* allocateCell(Heap&, GCDeferralContext*);
-    template<typename T> friend void* allocateCell(Heap&, GCDeferralContext*, size_t);
+    template<typename T, AllocationFailureMode, GCDeferralContextArgPresense>
+    friend void* tryAllocateCellHelper(Heap&, GCDeferralContext*, size_t);
 
 public:
     static const unsigned StructureFlags = 0;
index 70bf1e8..c52985c 100644 (file)
@@ -142,12 +142,23 @@ Subspace* JSCell::subspaceFor(VM& vm)
     return &vm.cellSpace;
 }
 
-template<typename T>
-void* allocateCell(Heap& heap, size_t size)
+template<typename T, AllocationFailureMode mode, GCDeferralContextArgPresense deferralContextArgPresence>
+ALWAYS_INLINE void* tryAllocateCellHelper(Heap& heap, GCDeferralContext* deferralContext, size_t size)
 {
-    ASSERT(!DisallowGC::isInEffectOnCurrentThread());
+    ASSERT(deferralContext || !DisallowGC::isInEffectOnCurrentThread());
     ASSERT(size >= sizeof(T));
-    JSCell* result = static_cast<JSCell*>(subspaceFor<T>(*heap.vm())->allocate(size));
+    JSCell* result;
+    if (mode == AllocationFailureMode::ShouldAssertOnFailure) {
+        result = (deferralContextArgPresence == GCDeferralContextArgPresense::HasArg)
+            ? static_cast<JSCell*>(subspaceFor<T>(*heap.vm())->allocate(deferralContext, size))
+            : static_cast<JSCell*>(subspaceFor<T>(*heap.vm())->allocate(size));
+    } else {
+        result = (deferralContextArgPresence == GCDeferralContextArgPresense::HasArg)
+            ? static_cast<JSCell*>(subspaceFor<T>(*heap.vm())->tryAllocate(deferralContext, size))
+            : static_cast<JSCell*>(subspaceFor<T>(*heap.vm())->tryAllocate(size));
+        if (UNLIKELY(!result))
+            return nullptr;
+    }
 #if ENABLE(GC_VALIDATION)
     ASSERT(!heap.vm()->isInitializingObject());
     heap.vm()->setInitializingObjectClass(T::info());
@@ -155,32 +166,31 @@ void* allocateCell(Heap& heap, size_t size)
     result->clearStructure();
     return result;
 }
-    
+
+template<typename T>
+void* allocateCell(Heap& heap, size_t size)
+{
+    return tryAllocateCellHelper<T, AllocationFailureMode::ShouldAssertOnFailure, GCDeferralContextArgPresense::DoesNotHaveArg>(heap, nullptr, size);
+}
+
 template<typename T>
-void* allocateCell(Heap& heap)
+void* tryAllocateCell(Heap& heap, size_t size)
 {
-    return allocateCell<T>(heap, sizeof(T));
+    return tryAllocateCellHelper<T, AllocationFailureMode::ShouldNotAssertOnFailure, GCDeferralContextArgPresense::DoesNotHaveArg>(heap, nullptr, size);
 }
-    
+
 template<typename T>
 void* allocateCell(Heap& heap, GCDeferralContext* deferralContext, size_t size)
 {
-    ASSERT(size >= sizeof(T));
-    JSCell* result = static_cast<JSCell*>(subspaceFor<T>(*heap.vm())->allocate(deferralContext, size));
-#if ENABLE(GC_VALIDATION)
-    ASSERT(!heap.vm()->isInitializingObject());
-    heap.vm()->setInitializingObjectClass(T::info());
-#endif
-    result->clearStructure();
-    return result;
+    return tryAllocateCellHelper<T, AllocationFailureMode::ShouldAssertOnFailure, GCDeferralContextArgPresense::HasArg>(heap, deferralContext, size);
 }
-    
+
 template<typename T>
-void* allocateCell(Heap& heap, GCDeferralContext* deferralContext)
+void* tryAllocateCell(Heap& heap, GCDeferralContext* deferralContext, size_t size)
 {
-    return allocateCell<T>(heap, deferralContext, sizeof(T));
+    return tryAllocateCellHelper<T, AllocationFailureMode::ShouldNotAssertOnFailure, GCDeferralContextArgPresense::HasArg>(heap, deferralContext, size);
 }
-    
+
 inline bool JSCell::isObject() const
 {
     return TypeInfo::isObject(m_type);
index c0c7349..4a6477d 100644 (file)
@@ -45,9 +45,15 @@ public:
 
     ALWAYS_INLINE static JSFixedArray* createFromArray(ExecState* exec, VM& vm, JSArray* array)
     {
+        auto throwScope = DECLARE_THROW_SCOPE(vm);
+
         IndexingType indexingType = array->indexingType() & IndexingShapeMask;
         unsigned length = array->length();
-        JSFixedArray* result = JSFixedArray::create(vm, vm.fixedArrayStructure.get(), length);
+        JSFixedArray* result = JSFixedArray::tryCreate(vm, vm.fixedArrayStructure.get(), length);
+        if (UNLIKELY(!result)) {
+            throwOutOfMemoryError(exec, throwScope);
+            return nullptr;
+        }
 
         if (!length)
             return result;
@@ -70,8 +76,6 @@ public:
             return result;
         }
 
-
-        auto throwScope = DECLARE_THROW_SCOPE(vm);
         for (unsigned i = 0; i < length; i++) {
             JSValue value = array->getDirectIndex(exec, i);
             if (!value) {
@@ -116,9 +120,12 @@ public:
 private:
     unsigned m_size;
 
-    ALWAYS_INLINE static JSFixedArray* create(VM& vm, Structure* structure, unsigned size)
+    ALWAYS_INLINE static JSFixedArray* tryCreate(VM& vm, Structure* structure, unsigned size)
     {
-        JSFixedArray* result = new (NotNull, allocateCell<JSFixedArray>(vm.heap, allocationSize(size))) JSFixedArray(vm, structure, size);
+        void* buffer = tryAllocateCell<JSFixedArray>(vm.heap, allocationSize(size));
+        if (UNLIKELY(!buffer))
+            return nullptr;
+        JSFixedArray* result = new (NotNull, buffer) JSFixedArray(vm, structure, size);
         result->finishCreation(vm);
         return result;
     }
index bbebfd0..7e9fecb 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  Copyright (C) 2007 Eric Seidel <eric@webkit.org>
- *  Copyright (C) 2007-2009, 2014-2016 Apple Inc. All rights reserved.
+ *  Copyright (C) 2007-2017 Apple Inc. All rights reserved.
  *
  *  This library is free software; you can redistribute it and/or
  *  modify it under the terms of the GNU Library General Public
@@ -23,6 +23,7 @@
 
 #include "ArrayAllocationProfile.h"
 #include "ArrayBufferSharingMode.h"
+#include "ExceptionHelpers.h"
 #include "InternalFunction.h"
 #include "JSArray.h"
 #include "JSArrayBufferPrototype.h"
@@ -874,7 +875,12 @@ inline JSArray* constructEmptyArray(ExecState* exec, ArrayAllocationProfile* pro
         structure = globalObject->arrayStructureForProfileDuringAllocation(exec, profile, newTarget);
     RETURN_IF_EXCEPTION(scope, nullptr);
 
-    return ArrayAllocationProfile::updateLastAllocationFor(profile, JSArray::create(exec->vm(), structure, initialLength));
+    JSArray* result = JSArray::tryCreate(vm, structure, initialLength);
+    if (UNLIKELY(!result)) {
+        throwOutOfMemoryError(exec, scope);
+        return nullptr;
+    }
+    return ArrayAllocationProfile::updateLastAllocationFor(profile, result);
 }
 
 inline JSArray* constructEmptyArray(ExecState* exec, ArrayAllocationProfile* profile, unsigned initialLength = 0, JSValue newTarget = JSValue())