Metal: Handle Buffer allocation failure

Bug: dawn:433
Change-Id: I6549c4a1e31171257761397b018090d0eb7471e6
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/22424
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Stephen White <senorblanco@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
diff --git a/src/dawn_native/metal/BufferMTL.mm b/src/dawn_native/metal/BufferMTL.mm
index 0f84467..6da0fd9 100644
--- a/src/dawn_native/metal/BufferMTL.mm
+++ b/src/dawn_native/metal/BufferMTL.mm
@@ -39,23 +39,38 @@
             storageMode = MTLResourceStorageModePrivate;
         }
 
-        if (GetSize() >
-            std::numeric_limits<uint64_t>::max() - kMinUniformOrStorageBufferAlignment) {
-            return DAWN_OUT_OF_MEMORY_ERROR("Buffer allocation is too large");
-        }
-
         // TODO(cwallez@chromium.org): Have a global "zero" buffer that can do everything instead
         // of creating a new 4-byte buffer?
-        uint32_t currentSize = std::max(GetSize(), uint64_t(4u));
+        if (GetSize() > std::numeric_limits<NSUInteger>::max()) {
+            return DAWN_OUT_OF_MEMORY_ERROR("Buffer allocation is too large");
+        }
+        NSUInteger currentSize = static_cast<NSUInteger>(std::max(GetSize(), uint64_t(4u)));
+
         // Metal validation layer requires the size of uniform buffer and storage buffer to be no
         // less than the size of the buffer block defined in shader, and the overall size of the
         // buffer must be aligned to the largest alignment of its members.
         if (GetUsage() & (wgpu::BufferUsage::Uniform | wgpu::BufferUsage::Storage)) {
+            if (currentSize >
+                std::numeric_limits<NSUInteger>::max() - kMinUniformOrStorageBufferAlignment) {
+                // Alignment would overlow.
+                return DAWN_OUT_OF_MEMORY_ERROR("Buffer allocation is too large");
+            }
             currentSize = Align(currentSize, kMinUniformOrStorageBufferAlignment);
         }
 
+        if (@available(iOS 12, macOS 10.14, *)) {
+            NSUInteger maxBufferSize = [ToBackend(GetDevice())->GetMTLDevice() maxBufferLength];
+            if (currentSize > maxBufferSize) {
+                return DAWN_OUT_OF_MEMORY_ERROR("Buffer allocation is too large");
+            }
+        }
+
         mMtlBuffer = [ToBackend(GetDevice())->GetMTLDevice() newBufferWithLength:currentSize
                                                                          options:storageMode];
+        if (mMtlBuffer == nil) {
+            return DAWN_OUT_OF_MEMORY_ERROR("Buffer allocation failed");
+        }
+
         return {};
     }
 
diff --git a/src/tests/end2end/BufferTests.cpp b/src/tests/end2end/BufferTests.cpp
index 504cf32..61f4e34 100644
--- a/src/tests/end2end/BufferTests.cpp
+++ b/src/tests/end2end/BufferTests.cpp
@@ -429,7 +429,7 @@
 // Test that creating a very large buffers fails gracefully.
 TEST_P(CreateBufferMappedTests, LargeBufferFails) {
     // TODO(http://crbug.com/dawn/27): Missing support.
-    DAWN_SKIP_TEST_IF(IsMetal() || IsOpenGL());
+    DAWN_SKIP_TEST_IF(IsOpenGL());
 
     wgpu::BufferDescriptor descriptor;
     descriptor.size = std::numeric_limits<uint64_t>::max();