Implement ErrorScopes for synchronous errors

This patch implements Push/PopErrorScope except for asynchronous
or GPU commands. These commands, such as Queue::Submit will need
to hold onto the ErrorScope until GPU execution is complete.

Bug: dawn:153
Change-Id: I2d340b8b391d117a59497f35690993a9cd7503e6
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/10700
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index 7876bf3..0a3fe45 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -166,6 +166,8 @@
     "src/dawn_native/Error.h",
     "src/dawn_native/ErrorData.cpp",
     "src/dawn_native/ErrorData.h",
+    "src/dawn_native/ErrorScope.cpp",
+    "src/dawn_native/ErrorScope.h",
     "src/dawn_native/Extensions.cpp",
     "src/dawn_native/Extensions.h",
     "src/dawn_native/Fence.cpp",
@@ -767,6 +769,7 @@
     "src/tests/unittests/validation/DebugMarkerValidationTests.cpp",
     "src/tests/unittests/validation/DrawIndirectValidationTests.cpp",
     "src/tests/unittests/validation/DynamicStateCommandValidationTests.cpp",
+    "src/tests/unittests/validation/ErrorScopeValidationTests.cpp",
     "src/tests/unittests/validation/FenceValidationTests.cpp",
     "src/tests/unittests/validation/QueueSubmitValidationTests.cpp",
     "src/tests/unittests/validation/RenderBundleValidationTests.cpp",
diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp
index e667c12..4343911 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -24,6 +24,7 @@
 #include "dawn_native/ComputePipeline.h"
 #include "dawn_native/DynamicUploader.h"
 #include "dawn_native/ErrorData.h"
+#include "dawn_native/ErrorScope.h"
 #include "dawn_native/Fence.h"
 #include "dawn_native/FenceSignalTracker.h"
 #include "dawn_native/Instance.h"
@@ -35,6 +36,7 @@
 #include "dawn_native/ShaderModule.h"
 #include "dawn_native/SwapChain.h"
 #include "dawn_native/Texture.h"
+#include "dawn_native/ValidationUtils_autogen.h"
 
 #include <unordered_set>
 
@@ -61,7 +63,9 @@
     // DeviceBase
 
     DeviceBase::DeviceBase(AdapterBase* adapter, const DeviceDescriptor* descriptor)
-        : mAdapter(adapter) {
+        : mAdapter(adapter),
+          mRootErrorScope(AcquireRef(new ErrorScope())),
+          mCurrentErrorScope(mRootErrorScope.Get()) {
         mCaches = std::make_unique<DeviceBase::Caches>();
         mFenceSignalTracker = std::make_unique<FenceSignalTracker>(this);
         mDynamicUploader = std::make_unique<DynamicUploader>(this);
@@ -89,25 +93,32 @@
     }
 
     void DeviceBase::HandleError(dawn::ErrorType type, const char* message) {
-        if (mErrorCallback) {
-            mErrorCallback(static_cast<DawnErrorType>(type), message, mErrorUserdata);
-        }
+        mCurrentErrorScope->HandleError(type, message);
+    }
+
+    void DeviceBase::HandleError(ErrorData* data) {
+        mCurrentErrorScope->HandleError(data);
     }
 
     void DeviceBase::SetUncapturedErrorCallback(dawn::ErrorCallback callback, void* userdata) {
-        mErrorCallback = callback;
-        mErrorUserdata = userdata;
+        mRootErrorScope->SetCallback(callback, userdata);
     }
 
     void DeviceBase::PushErrorScope(dawn::ErrorFilter filter) {
-        // TODO(crbug.com/dawn/153): Implement error scopes.
-        HandleError(dawn::ErrorType::Validation, "Error scopes not implemented");
+        if (ConsumedError(ValidateErrorFilter(filter))) {
+            return;
+        }
+        mCurrentErrorScope = AcquireRef(new ErrorScope(filter, mCurrentErrorScope.Get()));
     }
 
     bool DeviceBase::PopErrorScope(dawn::ErrorCallback callback, void* userdata) {
-        // TODO(crbug.com/dawn/153): Implement error scopes.
-        HandleError(dawn::ErrorType::Validation, "Error scopes not implemented");
-        return false;
+        if (DAWN_UNLIKELY(mCurrentErrorScope.Get() == mRootErrorScope.Get())) {
+            return false;
+        }
+        mCurrentErrorScope->SetCallback(callback, userdata);
+        mCurrentErrorScope = Ref<ErrorScope>(mCurrentErrorScope->GetParent());
+
+        return true;
     }
 
     MaybeError DeviceBase::ValidateObject(const ObjectBase* object) const {
@@ -286,8 +297,7 @@
             return static_cast<AttachmentState*>(*iter);
         }
 
-        Ref<AttachmentState> attachmentState = new AttachmentState(this, *blueprint);
-        attachmentState->Release();
+        Ref<AttachmentState> attachmentState = AcquireRef(new AttachmentState(this, *blueprint));
         mCaches->attachmentStates.insert(attachmentState.Get());
         return attachmentState;
     }
@@ -680,12 +690,6 @@
 
     // Other implementation details
 
-    void DeviceBase::ConsumeError(ErrorData* error) {
-        ASSERT(error != nullptr);
-        HandleError(error->GetType(), error->GetMessage().c_str());
-        delete error;
-    }
-
     ResultOrError<DynamicUploader*> DeviceBase::GetDynamicUploader() const {
         if (mDynamicUploader->IsEmpty()) {
             DAWN_TRY(mDynamicUploader->CreateAndAppendBuffer());
diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h
index 60cdbba..fe52fd2 100644
--- a/src/dawn_native/Device.h
+++ b/src/dawn_native/Device.h
@@ -35,6 +35,7 @@
     class AdapterBase;
     class AttachmentState;
     class AttachmentStateBlueprint;
+    class ErrorScope;
     class FenceSignalTracker;
     class DynamicUploader;
     class StagingBufferBase;
@@ -45,10 +46,11 @@
         virtual ~DeviceBase();
 
         void HandleError(dawn::ErrorType type, const char* message);
+        void HandleError(ErrorData* error);
 
         bool ConsumedError(MaybeError maybeError) {
             if (DAWN_UNLIKELY(maybeError.IsError())) {
-                ConsumeError(maybeError.AcquireError());
+                HandleError(maybeError.AcquireError());
                 return true;
             }
             return false;
@@ -230,11 +232,13 @@
 
         void ApplyExtensions(const DeviceDescriptor* deviceDescriptor);
 
-        void ConsumeError(ErrorData* error);
         void SetDefaultToggles();
 
         AdapterBase* mAdapter = nullptr;
 
+        Ref<ErrorScope> mRootErrorScope;
+        Ref<ErrorScope> mCurrentErrorScope;
+
         // The object caches aren't exposed in the header as they would require a lot of
         // additional includes.
         struct Caches;
@@ -250,8 +254,6 @@
         std::unique_ptr<FenceSignalTracker> mFenceSignalTracker;
         std::vector<DeferredCreateBufferMappedAsync> mDeferredCreateBufferMappedAsyncResults;
 
-        dawn::ErrorCallback mErrorCallback = nullptr;
-        void* mErrorUserdata = 0;
         uint32_t mRefCount = 1;
 
         FormatTable mFormatTable;
diff --git a/src/dawn_native/ErrorScope.cpp b/src/dawn_native/ErrorScope.cpp
new file mode 100644
index 0000000..0b53216
--- /dev/null
+++ b/src/dawn_native/ErrorScope.cpp
@@ -0,0 +1,114 @@
+// Copyright 2019 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dawn_native/ErrorScope.h"
+
+#include "common/Assert.h"
+#include "dawn_native/ErrorData.h"
+
+namespace dawn_native {
+
+    ErrorScope::ErrorScope() = default;
+
+    ErrorScope::ErrorScope(dawn::ErrorFilter errorFilter, ErrorScope* parent)
+        : RefCounted(), mErrorFilter(errorFilter), mParent(parent) {
+        ASSERT(mParent.Get() != nullptr);
+    }
+
+    ErrorScope::~ErrorScope() {
+        if (mCallback == nullptr || IsRoot()) {
+            return;
+        }
+        mCallback(static_cast<DawnErrorType>(mErrorType), mErrorMessage.c_str(), mUserdata);
+    }
+
+    void ErrorScope::SetCallback(dawn::ErrorCallback callback, void* userdata) {
+        mCallback = callback;
+        mUserdata = userdata;
+    }
+
+    ErrorScope* ErrorScope::GetParent() {
+        return mParent.Get();
+    }
+
+    bool ErrorScope::IsRoot() const {
+        return mParent.Get() == nullptr;
+    }
+
+    void ErrorScope::HandleError(dawn::ErrorType type, const char* message) {
+        HandleErrorImpl(this, type, message);
+    }
+
+    void ErrorScope::HandleError(ErrorData* error) {
+        ASSERT(error != nullptr);
+        HandleErrorImpl(this, error->GetType(), error->GetMessage().c_str());
+    }
+
+    // static
+    void ErrorScope::HandleErrorImpl(ErrorScope* scope, dawn::ErrorType type, const char* message) {
+        ErrorScope* currentScope = scope;
+        for (; !currentScope->IsRoot(); currentScope = currentScope->GetParent()) {
+            ASSERT(currentScope != nullptr);
+
+            bool consumed = false;
+            switch (type) {
+                case dawn::ErrorType::Validation:
+                    if (currentScope->mErrorFilter != dawn::ErrorFilter::Validation) {
+                        // Error filter does not match. Move on to the next scope.
+                        continue;
+                    }
+                    consumed = true;
+                    break;
+
+                case dawn::ErrorType::OutOfMemory:
+                    if (currentScope->mErrorFilter != dawn::ErrorFilter::OutOfMemory) {
+                        // Error filter does not match. Move on to the next scope.
+                        continue;
+                    }
+                    consumed = true;
+                    break;
+
+                // Unknown and DeviceLost are fatal. All error scopes capture them.
+                // |consumed| is false because these should bubble to all scopes.
+                case dawn::ErrorType::Unknown:
+                case dawn::ErrorType::DeviceLost:
+                    consumed = false;
+                    break;
+
+                case dawn::ErrorType::NoError:
+                default:
+                    UNREACHABLE();
+                    return;
+            }
+
+            // Record the error if the scope doesn't have one yet.
+            if (currentScope->mErrorType == dawn::ErrorType::NoError) {
+                currentScope->mErrorType = type;
+                currentScope->mErrorMessage = message;
+            }
+
+            if (consumed) {
+                return;
+            }
+        }
+
+        // The root error scope captures all uncaptured errors.
+        ASSERT(currentScope->IsRoot());
+        if (currentScope->mCallback) {
+            currentScope->mCallback(static_cast<DawnErrorType>(type), message,
+                                    currentScope->mUserdata);
+        }
+    }
+
+}  // namespace dawn_native
diff --git a/src/dawn_native/ErrorScope.h b/src/dawn_native/ErrorScope.h
new file mode 100644
index 0000000..4fd57c9
--- /dev/null
+++ b/src/dawn_native/ErrorScope.h
@@ -0,0 +1,68 @@
+// Copyright 2019 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef DAWNNATIVE_ERRORSCOPE_H_
+#define DAWNNATIVE_ERRORSCOPE_H_
+
+#include "dawn_native/dawn_platform.h"
+
+#include "dawn_native/RefCounted.h"
+
+#include <string>
+
+namespace dawn_native {
+
+    class ErrorData;
+
+    // Errors can be recorded into an ErrorScope by calling |HandleError|.
+    // Because an error scope should not resolve until contained
+    // commands are complete, calling the callback is deferred until it is destructed.
+    // In-flight commands or asynchronous events should hold a reference to the
+    // ErrorScope for their duration.
+    //
+    // Because parent ErrorScopes should not resolve before child ErrorScopes,
+    // ErrorScopes hold a reference to their parent.
+    //
+    // To simplify ErrorHandling, there is a sentinel root error scope which has
+    // no parent. All uncaptured errors are handled by the root error scope. Its
+    // callback is called immediately once it encounters an error.
+    class ErrorScope : public RefCounted {
+      public:
+        ErrorScope();  // Constructor for the root error scope.
+        ErrorScope(dawn::ErrorFilter errorFilter, ErrorScope* parent);
+        ~ErrorScope();
+
+        void SetCallback(dawn::ErrorCallback callback, void* userdata);
+        ErrorScope* GetParent();
+
+        void HandleError(dawn::ErrorType type, const char* message);
+        void HandleError(ErrorData* error);
+
+      private:
+        bool IsRoot() const;
+        static void HandleErrorImpl(ErrorScope* scope, dawn::ErrorType type, const char* message);
+
+        dawn::ErrorFilter mErrorFilter = dawn::ErrorFilter::None;
+        Ref<ErrorScope> mParent = nullptr;
+
+        dawn::ErrorCallback mCallback = nullptr;
+        void* mUserdata = nullptr;
+
+        dawn::ErrorType mErrorType = dawn::ErrorType::NoError;
+        std::string mErrorMessage = "";
+    };
+
+}  // namespace dawn_native
+
+#endif  // DAWNNATIVE_ERRORSCOPE_H_
diff --git a/src/dawn_native/RefCounted.h b/src/dawn_native/RefCounted.h
index 6eb4ab0..5d21ced 100644
--- a/src/dawn_native/RefCounted.h
+++ b/src/dawn_native/RefCounted.h
@@ -120,6 +120,13 @@
         T* mPointee = nullptr;
     };
 
+    template <typename T>
+    Ref<T> AcquireRef(T* pointee) {
+        Ref<T> ref(pointee);
+        ref->Release();
+        return ref;
+    }
+
 }  // namespace dawn_native
 
 #endif  // DAWNNATIVE_REFCOUNTED_H_
diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp
index ab503d7..9ae41cf 100644
--- a/src/dawn_native/vulkan/CommandBufferVk.cpp
+++ b/src/dawn_native/vulkan/CommandBufferVk.cpp
@@ -330,12 +330,7 @@
         tempBufferDescriptor.usage = dawn::BufferUsage::CopySrc | dawn::BufferUsage::CopyDst;
 
         Device* device = ToBackend(GetDevice());
-        Ref<Buffer> tempBuffer = ToBackend(device->CreateBuffer(&tempBufferDescriptor));
-        // After device->CreateBuffer(&tempBufferDescriptor) is called, the ref count of the buffer
-        // object is 1, and after assigning it to a Ref<Buffer>, the ref count of it will be 2. To
-        // prevent memory leak, we must reduce the ref count here to ensure the ref count of this
-        // object to be 0 after all the Ref<> objects that contain the buffer object are released.
-        tempBuffer->Release();
+        Ref<Buffer> tempBuffer = AcquireRef(ToBackend(device->CreateBuffer(&tempBufferDescriptor)));
 
         BufferCopy tempBufferCopy;
         tempBufferCopy.buffer = tempBuffer.Get();
diff --git a/src/tests/unittests/validation/ErrorScopeValidationTests.cpp b/src/tests/unittests/validation/ErrorScopeValidationTests.cpp
new file mode 100644
index 0000000..4d9629e
--- /dev/null
+++ b/src/tests/unittests/validation/ErrorScopeValidationTests.cpp
@@ -0,0 +1,134 @@
+// Copyright 2019 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "tests/unittests/validation/ValidationTest.h"
+
+#include <gmock/gmock.h>
+
+using namespace testing;
+
+class MockDevicePopErrorScopeCallback {
+  public:
+    MOCK_METHOD3(Call, void(DawnErrorType type, const char* message, void* userdata));
+};
+
+static std::unique_ptr<MockDevicePopErrorScopeCallback> mockDevicePopErrorScopeCallback;
+static void ToMockDevicePopErrorScopeCallback(DawnErrorType type,
+                                              const char* message,
+                                              void* userdata) {
+    mockDevicePopErrorScopeCallback->Call(type, message, userdata);
+}
+
+class ErrorScopeValidationTest : public ValidationTest {
+  private:
+    void SetUp() override {
+        ValidationTest::SetUp();
+        mockDevicePopErrorScopeCallback = std::make_unique<MockDevicePopErrorScopeCallback>();
+    }
+
+    void TearDown() override {
+        // Delete mocks so that expectations are checked
+        mockDevicePopErrorScopeCallback = nullptr;
+        ValidationTest::TearDown();
+    }
+};
+
+// Test the simple success case.
+TEST_F(ErrorScopeValidationTest, Success) {
+    device.PushErrorScope(dawn::ErrorFilter::Validation);
+
+    EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_NO_ERROR, _, this)).Times(1);
+    device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this);
+}
+
+// Test the simple case where the error scope catches an error.
+TEST_F(ErrorScopeValidationTest, CatchesError) {
+    device.PushErrorScope(dawn::ErrorFilter::Validation);
+
+    dawn::BufferDescriptor desc = {};
+    desc.usage = static_cast<dawn::BufferUsage>(DAWN_BUFFER_USAGE_FORCE32);
+    device.CreateBuffer(&desc);
+
+    EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_VALIDATION, _, this))
+        .Times(1);
+    device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this);
+}
+
+// Test that errors bubble to the parent scope if not handled by the current scope.
+TEST_F(ErrorScopeValidationTest, ErrorBubbles) {
+    device.PushErrorScope(dawn::ErrorFilter::Validation);
+    device.PushErrorScope(dawn::ErrorFilter::OutOfMemory);
+
+    dawn::BufferDescriptor desc = {};
+    desc.usage = static_cast<dawn::BufferUsage>(DAWN_BUFFER_USAGE_FORCE32);
+    device.CreateBuffer(&desc);
+
+    // OutOfMemory does not match Validation error.
+    EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_NO_ERROR, _, this)).Times(1);
+    device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this);
+
+    // Parent validation error scope captures the error.
+    EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_VALIDATION, _, this + 1))
+        .Times(1);
+    device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this + 1);
+}
+
+// Test that if an error scope matches an error, it does not bubble to the parent scope.
+TEST_F(ErrorScopeValidationTest, HandledErrorsStopBubbling) {
+    device.PushErrorScope(dawn::ErrorFilter::OutOfMemory);
+    device.PushErrorScope(dawn::ErrorFilter::Validation);
+
+    dawn::BufferDescriptor desc = {};
+    desc.usage = static_cast<dawn::BufferUsage>(DAWN_BUFFER_USAGE_FORCE32);
+    device.CreateBuffer(&desc);
+
+    // Inner scope catches the error.
+    EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_VALIDATION, _, this))
+        .Times(1);
+    device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this);
+
+    // Parent scope does not see the error.
+    EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_NO_ERROR, _, this + 1))
+        .Times(1);
+    device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this + 1);
+}
+
+// Test that if no error scope handles an error, it goes to the device UncapturedError callback
+TEST_F(ErrorScopeValidationTest, UnhandledErrorsMatchUncapturedErrorCallback) {
+    device.PushErrorScope(dawn::ErrorFilter::OutOfMemory);
+
+    dawn::BufferDescriptor desc = {};
+    desc.usage = static_cast<dawn::BufferUsage>(DAWN_BUFFER_USAGE_FORCE32);
+    ASSERT_DEVICE_ERROR(device.CreateBuffer(&desc));
+
+    EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_NO_ERROR, _, this)).Times(1);
+    device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this);
+}
+
+// Check that push/popping error scopes must be balanced.
+TEST_F(ErrorScopeValidationTest, PushPopBalanced) {
+    // No error scopes to pop.
+    { EXPECT_FALSE(device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this)); }
+
+    // Too many pops
+    {
+        device.PushErrorScope(dawn::ErrorFilter::Validation);
+
+        EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_NO_ERROR, _, this + 1))
+            .Times(1);
+        device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this + 1);
+
+        EXPECT_FALSE(device.PopErrorScope(ToMockDevicePopErrorScopeCallback, this + 2));
+    }
+}