Add a test for creating a 0-sized buffer.

This is valid in WebGPU but causes validation errors in backends.

Also make it an OOM error on Metal to request a buffer close to
UINT32_MAX size because it would truncate the size, and could lead to
OOBs.

Bug: chromium:1069076
Change-Id: Ib961cb236cb7cabc0ae21203bf1d72ba82a56272
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/21060
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Stephen White <senorblanco@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/d3d12/BufferD3D12.cpp b/src/dawn_native/d3d12/BufferD3D12.cpp
index 63a1f82..fa68ef7 100644
--- a/src/dawn_native/d3d12/BufferD3D12.cpp
+++ b/src/dawn_native/d3d12/BufferD3D12.cpp
@@ -84,7 +84,9 @@
         D3D12_RESOURCE_DESC resourceDescriptor;
         resourceDescriptor.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
         resourceDescriptor.Alignment = 0;
-        resourceDescriptor.Width = GetSize();
+        // TODO(cwallez@chromium.org): Have a global "zero" buffer that can do everything instead
+        // of creating a new 4-byte buffer?
+        resourceDescriptor.Width = std::max(GetSize(), uint64_t(4u));
         resourceDescriptor.Height = 1;
         resourceDescriptor.DepthOrArraySize = 1;
         resourceDescriptor.MipLevels = 1;
diff --git a/src/dawn_native/metal/BufferMTL.h b/src/dawn_native/metal/BufferMTL.h
index effd077..081503b 100644
--- a/src/dawn_native/metal/BufferMTL.h
+++ b/src/dawn_native/metal/BufferMTL.h
@@ -26,13 +26,14 @@
 
     class Buffer : public BufferBase {
       public:
-        Buffer(Device* device, const BufferDescriptor* descriptor);
-
+        static ResultOrError<Buffer*> Create(Device* device, const BufferDescriptor* descriptor);
         id<MTLBuffer> GetMTLBuffer() const;
 
         void OnMapCommandSerialFinished(uint32_t mapSerial, bool isWrite);
 
       private:
+        using BufferBase::BufferBase;
+        MaybeError Initialize();
         ~Buffer() override;
         // Dawn API
         MaybeError MapReadAsyncImpl(uint32_t serial) override;
diff --git a/src/dawn_native/metal/BufferMTL.mm b/src/dawn_native/metal/BufferMTL.mm
index c61b029..5b57778 100644
--- a/src/dawn_native/metal/BufferMTL.mm
+++ b/src/dawn_native/metal/BufferMTL.mm
@@ -17,13 +17,21 @@
 #include "common/Math.h"
 #include "dawn_native/metal/DeviceMTL.h"
 
+#include <limits>
+
 namespace dawn_native { namespace metal {
     // The size of uniform buffer and storage buffer need to be aligned to 16 bytes which is the
     // largest alignment of supported data types
     static constexpr uint32_t kMinUniformOrStorageBufferAlignment = 16u;
 
-    Buffer::Buffer(Device* device, const BufferDescriptor* descriptor)
-        : BufferBase(device, descriptor) {
+    // static
+    ResultOrError<Buffer*> Buffer::Create(Device* device, const BufferDescriptor* descriptor) {
+        Ref<Buffer> buffer = AcquireRef(new Buffer(device, descriptor));
+        DAWN_TRY(buffer->Initialize());
+        return buffer.Detach();
+    }
+
+    MaybeError Buffer::Initialize() {
         MTLResourceOptions storageMode;
         if (GetUsage() & (wgpu::BufferUsage::MapRead | wgpu::BufferUsage::MapWrite)) {
             storageMode = MTLResourceStorageModeShared;
@@ -31,7 +39,14 @@
             storageMode = MTLResourceStorageModePrivate;
         }
 
-        uint32_t currentSize = GetSize();
+        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));
         // 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.
@@ -39,7 +54,9 @@
             currentSize = Align(currentSize, kMinUniformOrStorageBufferAlignment);
         }
 
-        mMtlBuffer = [device->GetMTLDevice() newBufferWithLength:currentSize options:storageMode];
+        mMtlBuffer = [ToBackend(GetDevice())->GetMTLDevice() newBufferWithLength:currentSize
+                                                                         options:storageMode];
+        return {};
     }
 
     Buffer::~Buffer() {
diff --git a/src/dawn_native/metal/DeviceMTL.mm b/src/dawn_native/metal/DeviceMTL.mm
index ba24d06..905832e 100644
--- a/src/dawn_native/metal/DeviceMTL.mm
+++ b/src/dawn_native/metal/DeviceMTL.mm
@@ -110,7 +110,7 @@
         return new BindGroupLayout(this, descriptor);
     }
     ResultOrError<BufferBase*> Device::CreateBufferImpl(const BufferDescriptor* descriptor) {
-        return new Buffer(this, descriptor);
+        return Buffer::Create(this, descriptor);
     }
     CommandBufferBase* Device::CreateCommandBuffer(CommandEncoder* encoder,
                                                    const CommandBufferDescriptor* descriptor) {
diff --git a/src/dawn_native/vulkan/BufferVk.cpp b/src/dawn_native/vulkan/BufferVk.cpp
index 55e15cc..4bc09e0 100644
--- a/src/dawn_native/vulkan/BufferVk.cpp
+++ b/src/dawn_native/vulkan/BufferVk.cpp
@@ -137,7 +137,9 @@
         createInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
         createInfo.pNext = nullptr;
         createInfo.flags = 0;
-        createInfo.size = GetSize();
+        // TODO(cwallez@chromium.org): Have a global "zero" buffer that can do everything instead
+        // of creating a new 4-byte buffer?
+        createInfo.size = std::max(GetSize(), uint64_t(4u));
         // Add CopyDst for non-mappable buffer initialization in CreateBufferMapped
         // and robust resource initialization.
         createInfo.usage = VulkanBufferUsage(GetUsage() | wgpu::BufferUsage::CopyDst);
diff --git a/src/tests/end2end/BufferTests.cpp b/src/tests/end2end/BufferTests.cpp
index d00aa74..04af878 100644
--- a/src/tests/end2end/BufferTests.cpp
+++ b/src/tests/end2end/BufferTests.cpp
@@ -581,3 +581,18 @@
                       MetalBackend(),
                       OpenGLBackend(),
                       VulkanBackend());
+
+class BufferTests : public DawnTest {};
+
+TEST_P(BufferTests, ZeroSizedBuffer) {
+    wgpu::BufferDescriptor desc;
+    desc.size = 0;
+    desc.usage = wgpu::BufferUsage::CopyDst;
+    device.CreateBuffer(&desc);
+}
+
+DAWN_INSTANTIATE_TEST(BufferTests,
+                      D3D12Backend(),
+                      MetalBackend(),
+                      OpenGLBackend(),
+                      VulkanBackend());