WebGPU error handling 1: Return error objects on errors.

Dawn used to return "nullptr" when an error happened while creating an
object. To match WebGPU we want to return valid pointers but to "error"
objects.

This commit implements WebGPU error handling for all "descriptorized"
objects and changes the nullptr error checks into "ValidateObject"
checks. This method is used both to check that the object isn't an
error, but also that all objects in a function call are from the same
device.

New validation is added to objects with methods (apart from Device) so
they check they aren't error objects.

A large number of ASSERTs were added to check that frontend objects
aren't accessed when they are errors, so that missing validation would
hit the asserts instead of crashing randomly.

The bind group validation tests were modified to test the behavior with
both nullptrs and error objects.

Future commits will change the CommandBufferBuilder to not be a builder
anymore but an "Encoder" instead with special-cased error handling. Then
once all objects are descriptorized, the notion of builders will be
removed from the code.

BUG=dawn:8

Change-Id: I8647712d5de3deb0e99e3bc58f34496f67710edd
Reviewed-on: https://dawn-review.googlesource.com/c/4360
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/BindGroup.cpp b/src/dawn_native/BindGroup.cpp
index 5a5a177..8732c06 100644
--- a/src/dawn_native/BindGroup.cpp
+++ b/src/dawn_native/BindGroup.cpp
@@ -28,12 +28,14 @@
 
         // Helper functions to perform binding-type specific validation
 
-        MaybeError ValidateBufferBinding(const BindGroupBinding& binding,
+        MaybeError ValidateBufferBinding(const DeviceBase* device,
+                                         const BindGroupBinding& binding,
                                          dawn::BufferUsageBit requiredUsage) {
             if (binding.buffer == nullptr || binding.sampler != nullptr ||
                 binding.textureView != nullptr) {
                 return DAWN_VALIDATION_ERROR("expected buffer binding");
             }
+            DAWN_TRY(device->ValidateObject(binding.buffer));
 
             uint32_t bufferSize = binding.buffer->GetSize();
             if (binding.size > bufferSize) {
@@ -58,12 +60,14 @@
             return {};
         }
 
-        MaybeError ValidateTextureBinding(const BindGroupBinding& binding,
+        MaybeError ValidateTextureBinding(const DeviceBase* device,
+                                          const BindGroupBinding& binding,
                                           dawn::TextureUsageBit requiredUsage) {
             if (binding.textureView == nullptr || binding.sampler != nullptr ||
                 binding.buffer != nullptr) {
                 return DAWN_VALIDATION_ERROR("expected texture binding");
             }
+            DAWN_TRY(device->ValidateObject(binding.textureView));
 
             if (!(binding.textureView->GetTexture()->GetUsage() & requiredUsage)) {
                 return DAWN_VALIDATION_ERROR("texture binding usage mismatch");
@@ -72,24 +76,26 @@
             return {};
         }
 
-        MaybeError ValidateSamplerBinding(const BindGroupBinding& binding) {
+        MaybeError ValidateSamplerBinding(const DeviceBase* device,
+                                          const BindGroupBinding& binding) {
             if (binding.sampler == nullptr || binding.textureView != nullptr ||
                 binding.buffer != nullptr) {
                 return DAWN_VALIDATION_ERROR("expected sampler binding");
             }
+            DAWN_TRY(device->ValidateObject(binding.sampler));
+
             return {};
         }
 
     }  // anonymous namespace
 
-    MaybeError ValidateBindGroupDescriptor(DeviceBase*, const BindGroupDescriptor* descriptor) {
+    MaybeError ValidateBindGroupDescriptor(DeviceBase* device,
+                                           const BindGroupDescriptor* descriptor) {
         if (descriptor->nextInChain != nullptr) {
             return DAWN_VALIDATION_ERROR("nextInChain must be nullptr");
         }
 
-        if (descriptor->layout == nullptr) {
-            return DAWN_VALIDATION_ERROR("layout cannot be null");
-        }
+        DAWN_TRY(device->ValidateObject(descriptor->layout));
 
         const BindGroupLayoutBase::LayoutBindingInfo& layoutInfo =
             descriptor->layout->GetBindingInfo();
@@ -120,16 +126,17 @@
             // Perform binding-type specific validation.
             switch (layoutInfo.types[bindingIndex]) {
                 case dawn::BindingType::UniformBuffer:
-                    DAWN_TRY(ValidateBufferBinding(binding, dawn::BufferUsageBit::Uniform));
+                    DAWN_TRY(ValidateBufferBinding(device, binding, dawn::BufferUsageBit::Uniform));
                     break;
                 case dawn::BindingType::StorageBuffer:
-                    DAWN_TRY(ValidateBufferBinding(binding, dawn::BufferUsageBit::Storage));
+                    DAWN_TRY(ValidateBufferBinding(device, binding, dawn::BufferUsageBit::Storage));
                     break;
                 case dawn::BindingType::SampledTexture:
-                    DAWN_TRY(ValidateTextureBinding(binding, dawn::TextureUsageBit::Sampled));
+                    DAWN_TRY(
+                        ValidateTextureBinding(device, binding, dawn::TextureUsageBit::Sampled));
                     break;
                 case dawn::BindingType::Sampler:
-                    DAWN_TRY(ValidateSamplerBinding(binding));
+                    DAWN_TRY(ValidateSamplerBinding(device, binding));
                     break;
             }
         }
@@ -179,11 +186,22 @@
         }
     }
 
+    BindGroupBase::BindGroupBase(DeviceBase* device, ObjectBase::ErrorTag tag)
+        : ObjectBase(device, tag) {
+    }
+
+    // static
+    BindGroupBase* BindGroupBase::MakeError(DeviceBase* device) {
+        return new BindGroupBase(device, ObjectBase::kError);
+    }
+
     const BindGroupLayoutBase* BindGroupBase::GetLayout() const {
+        ASSERT(!IsError());
         return mLayout.Get();
     }
 
     BufferBinding BindGroupBase::GetBindingAsBufferBinding(size_t binding) {
+        ASSERT(!IsError());
         ASSERT(binding < kMaxBindingsPerGroup);
         ASSERT(mLayout->GetBindingInfo().mask[binding]);
         ASSERT(mLayout->GetBindingInfo().types[binding] == dawn::BindingType::UniformBuffer ||
@@ -193,6 +211,7 @@
     }
 
     SamplerBase* BindGroupBase::GetBindingAsSampler(size_t binding) {
+        ASSERT(!IsError());
         ASSERT(binding < kMaxBindingsPerGroup);
         ASSERT(mLayout->GetBindingInfo().mask[binding]);
         ASSERT(mLayout->GetBindingInfo().types[binding] == dawn::BindingType::Sampler);
@@ -200,9 +219,11 @@
     }
 
     TextureViewBase* BindGroupBase::GetBindingAsTextureView(size_t binding) {
+        ASSERT(!IsError());
         ASSERT(binding < kMaxBindingsPerGroup);
         ASSERT(mLayout->GetBindingInfo().mask[binding]);
         ASSERT(mLayout->GetBindingInfo().types[binding] == dawn::BindingType::SampledTexture);
         return reinterpret_cast<TextureViewBase*>(mBindings[binding].Get());
     }
+
 }  // namespace dawn_native
diff --git a/src/dawn_native/BindGroup.h b/src/dawn_native/BindGroup.h
index 050747b..32988ea 100644
--- a/src/dawn_native/BindGroup.h
+++ b/src/dawn_native/BindGroup.h
@@ -42,12 +42,16 @@
       public:
         BindGroupBase(DeviceBase* device, const BindGroupDescriptor* descriptor);
 
+        static BindGroupBase* MakeError(DeviceBase* device);
+
         const BindGroupLayoutBase* GetLayout() const;
         BufferBinding GetBindingAsBufferBinding(size_t binding);
         SamplerBase* GetBindingAsSampler(size_t binding);
         TextureViewBase* GetBindingAsTextureView(size_t binding);
 
       private:
+        BindGroupBase(DeviceBase* device, ObjectBase::ErrorTag tag);
+
         Ref<BindGroupLayoutBase> mLayout;
         std::array<Ref<ObjectBase>, kMaxBindingsPerGroup> mBindings;
         std::array<uint32_t, kMaxBindingsPerGroup> mOffsets;
diff --git a/src/dawn_native/BindGroupLayout.cpp b/src/dawn_native/BindGroupLayout.cpp
index 0098fa6..9774340 100644
--- a/src/dawn_native/BindGroupLayout.cpp
+++ b/src/dawn_native/BindGroupLayout.cpp
@@ -92,14 +92,25 @@
         }
     }
 
+    BindGroupLayoutBase::BindGroupLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag)
+        : ObjectBase(device, tag), mIsBlueprint(true) {
+    }
+
     BindGroupLayoutBase::~BindGroupLayoutBase() {
         // Do not uncache the actual cached object if we are a blueprint
         if (!mIsBlueprint) {
+            ASSERT(!IsError());
             GetDevice()->UncacheBindGroupLayout(this);
         }
     }
 
+    // static
+    BindGroupLayoutBase* BindGroupLayoutBase::MakeError(DeviceBase* device) {
+        return new BindGroupLayoutBase(device, ObjectBase::kError);
+    }
+
     const BindGroupLayoutBase::LayoutBindingInfo& BindGroupLayoutBase::GetBindingInfo() const {
+        ASSERT(!IsError());
         return mBindingInfo;
     }
 
diff --git a/src/dawn_native/BindGroupLayout.h b/src/dawn_native/BindGroupLayout.h
index 3a1f454..08f38fa 100644
--- a/src/dawn_native/BindGroupLayout.h
+++ b/src/dawn_native/BindGroupLayout.h
@@ -37,6 +37,8 @@
                             bool blueprint = false);
         ~BindGroupLayoutBase() override;
 
+        static BindGroupLayoutBase* MakeError(DeviceBase* device);
+
         struct LayoutBindingInfo {
             std::array<dawn::ShaderStageBit, kMaxBindingsPerGroup> visibilities;
             std::array<dawn::BindingType, kMaxBindingsPerGroup> types;
@@ -45,6 +47,8 @@
         const LayoutBindingInfo& GetBindingInfo() const;
 
       private:
+        BindGroupLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag);
+
         LayoutBindingInfo mBindingInfo;
         bool mIsBlueprint = false;
     };
diff --git a/src/dawn_native/Buffer.cpp b/src/dawn_native/Buffer.cpp
index 543d62e..97ba85a 100644
--- a/src/dawn_native/Buffer.cpp
+++ b/src/dawn_native/Buffer.cpp
@@ -23,6 +23,33 @@
 
 namespace dawn_native {
 
+    namespace {
+
+        class ErrorBuffer : public BufferBase {
+          public:
+            ErrorBuffer(DeviceBase* device) : BufferBase(device, ObjectBase::kError) {
+            }
+
+          private:
+            MaybeError SetSubDataImpl(uint32_t start,
+                                      uint32_t count,
+                                      const uint8_t* data) override {
+                UNREACHABLE();
+                return {};
+            }
+            void MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t size) override {
+                UNREACHABLE();
+            }
+            void MapWriteAsyncImpl(uint32_t serial, uint32_t start, uint32_t size) override {
+                UNREACHABLE();
+            }
+            void UnmapImpl() override {
+                UNREACHABLE();
+            }
+        };
+
+    }  // anonymous namespace
+
     MaybeError ValidateBufferDescriptor(DeviceBase*, const BufferDescriptor* descriptor) {
         if (descriptor->nextInChain != nullptr) {
             return DAWN_VALIDATION_ERROR("nextInChain must be nullptr");
@@ -53,22 +80,35 @@
         : ObjectBase(device), mSize(descriptor->size), mUsage(descriptor->usage) {
     }
 
+    BufferBase::BufferBase(DeviceBase* device, ObjectBase::ErrorTag tag) : ObjectBase(device, tag) {
+    }
+
     BufferBase::~BufferBase() {
         if (mIsMapped) {
+            ASSERT(!IsError());
             CallMapReadCallback(mMapSerial, DAWN_BUFFER_MAP_ASYNC_STATUS_UNKNOWN, nullptr);
             CallMapWriteCallback(mMapSerial, DAWN_BUFFER_MAP_ASYNC_STATUS_UNKNOWN, nullptr);
         }
     }
 
+    // static
+    BufferBase* BufferBase::MakeError(DeviceBase* device) {
+        return new ErrorBuffer(device);
+    }
+
     uint32_t BufferBase::GetSize() const {
+        ASSERT(!IsError());
         return mSize;
     }
 
     dawn::BufferUsageBit BufferBase::GetUsage() const {
+        ASSERT(!IsError());
         return mUsage;
     }
 
     MaybeError BufferBase::ValidateCanUseInSubmitNow() const {
+        ASSERT(!IsError());
+
         if (mIsMapped) {
             return DAWN_VALIDATION_ERROR("Buffer used in a submit while mapped");
         }
@@ -78,6 +118,8 @@
     void BufferBase::CallMapReadCallback(uint32_t serial,
                                          dawnBufferMapAsyncStatus status,
                                          const void* pointer) {
+        ASSERT(!IsError());
+
         if (mMapReadCallback != nullptr && serial == mMapSerial) {
             ASSERT(mMapWriteCallback == nullptr);
             // Tag the callback as fired before firing it, otherwise it could fire a second time if
@@ -91,6 +133,8 @@
     void BufferBase::CallMapWriteCallback(uint32_t serial,
                                           dawnBufferMapAsyncStatus status,
                                           void* pointer) {
+        ASSERT(!IsError());
+
         if (mMapWriteCallback != nullptr && serial == mMapSerial) {
             ASSERT(mMapReadCallback == nullptr);
             // Tag the callback as fired before firing it, otherwise it could fire a second time if
@@ -105,6 +149,7 @@
         if (GetDevice()->ConsumedError(ValidateSetSubData(start, count))) {
             return;
         }
+        ASSERT(!IsError());
 
         if (GetDevice()->ConsumedError(SetSubDataImpl(start, count, data))) {
             return;
@@ -119,6 +164,7 @@
             callback(DAWN_BUFFER_MAP_ASYNC_STATUS_ERROR, nullptr, userdata);
             return;
         }
+        ASSERT(!IsError());
 
         ASSERT(mMapWriteCallback == nullptr);
 
@@ -139,6 +185,7 @@
             callback(DAWN_BUFFER_MAP_ASYNC_STATUS_ERROR, nullptr, userdata);
             return;
         }
+        ASSERT(!IsError());
 
         ASSERT(mMapReadCallback == nullptr);
 
@@ -155,6 +202,7 @@
         if (GetDevice()->ConsumedError(ValidateUnmap())) {
             return;
         }
+        ASSERT(!IsError());
 
         // A map request can only be called once, so this will fire only if the request wasn't
         // completed before the Unmap
@@ -168,6 +216,8 @@
     }
 
     MaybeError BufferBase::ValidateSetSubData(uint32_t start, uint32_t count) const {
+        DAWN_TRY(GetDevice()->ValidateObject(this));
+
         if (count > GetSize()) {
             return DAWN_VALIDATION_ERROR("Buffer subdata with too much data");
         }
@@ -187,6 +237,8 @@
     MaybeError BufferBase::ValidateMap(uint32_t start,
                                        uint32_t size,
                                        dawn::BufferUsageBit requiredUsage) const {
+        DAWN_TRY(GetDevice()->ValidateObject(this));
+
         if (size > GetSize()) {
             return DAWN_VALIDATION_ERROR("Buffer mapping with too big a region");
         }
@@ -208,6 +260,8 @@
     }
 
     MaybeError BufferBase::ValidateUnmap() const {
+        DAWN_TRY(GetDevice()->ValidateObject(this));
+
         if (!mIsMapped) {
             return DAWN_VALIDATION_ERROR("Buffer wasn't mapped");
         }
diff --git a/src/dawn_native/Buffer.h b/src/dawn_native/Buffer.h
index 4472cec..8f6bc7c 100644
--- a/src/dawn_native/Buffer.h
+++ b/src/dawn_native/Buffer.h
@@ -39,6 +39,8 @@
         BufferBase(DeviceBase* device, const BufferDescriptor* descriptor);
         ~BufferBase();
 
+        static BufferBase* MakeError(DeviceBase* device);
+
         uint32_t GetSize() const;
         dawn::BufferUsageBit GetUsage() const;
 
@@ -57,6 +59,8 @@
         void Unmap();
 
       protected:
+        BufferBase(DeviceBase* device, ObjectBase::ErrorTag tag);
+
         void CallMapReadCallback(uint32_t serial,
                                  dawnBufferMapAsyncStatus status,
                                  const void* pointer);
@@ -74,7 +78,7 @@
                                dawn::BufferUsageBit requiredUsage) const;
         MaybeError ValidateUnmap() const;
 
-        uint32_t mSize;
+        uint32_t mSize = 0;
         dawn::BufferUsageBit mUsage = dawn::BufferUsageBit::None;
 
         dawnBufferMapReadCallback mMapReadCallback = nullptr;
diff --git a/src/dawn_native/CommandBuffer.cpp b/src/dawn_native/CommandBuffer.cpp
index 97b5678..db88ab3 100644
--- a/src/dawn_native/CommandBuffer.cpp
+++ b/src/dawn_native/CommandBuffer.cpp
@@ -387,6 +387,8 @@
                 case Command::CopyBufferToBuffer: {
                     CopyBufferToBufferCmd* copy = mIterator.NextCommand<CopyBufferToBufferCmd>();
 
+                    DAWN_TRY(GetDevice()->ValidateObject(copy->source.buffer.Get()));
+                    DAWN_TRY(GetDevice()->ValidateObject(copy->destination.buffer.Get()));
                     DAWN_TRY(ValidateCopySizeFitsInBuffer(copy->source, copy->size));
                     DAWN_TRY(ValidateCopySizeFitsInBuffer(copy->destination, copy->size));
 
@@ -402,6 +404,9 @@
                 case Command::CopyBufferToTexture: {
                     CopyBufferToTextureCmd* copy = mIterator.NextCommand<CopyBufferToTextureCmd>();
 
+                    DAWN_TRY(GetDevice()->ValidateObject(copy->source.buffer.Get()));
+                    DAWN_TRY(GetDevice()->ValidateObject(copy->destination.texture.Get()));
+
                     uint32_t bufferCopySize = 0;
                     DAWN_TRY(ValidateRowPitch(copy->destination.texture->GetFormat(),
                                               copy->copySize, copy->source.rowPitch));
@@ -427,6 +432,9 @@
                 case Command::CopyTextureToBuffer: {
                     CopyTextureToBufferCmd* copy = mIterator.NextCommand<CopyTextureToBufferCmd>();
 
+                    DAWN_TRY(GetDevice()->ValidateObject(copy->source.texture.Get()));
+                    DAWN_TRY(GetDevice()->ValidateObject(copy->destination.buffer.Get()));
+
                     uint32_t bufferCopySize = 0;
                     DAWN_TRY(ValidateRowPitch(copy->source.texture->GetFormat(), copy->copySize,
                                               copy->destination.rowPitch));
diff --git a/src/dawn_native/ComputePassEncoder.cpp b/src/dawn_native/ComputePassEncoder.cpp
index bc2510c..27a31ce 100644
--- a/src/dawn_native/ComputePassEncoder.cpp
+++ b/src/dawn_native/ComputePassEncoder.cpp
@@ -17,6 +17,7 @@
 #include "dawn_native/CommandBuffer.h"
 #include "dawn_native/Commands.h"
 #include "dawn_native/ComputePipeline.h"
+#include "dawn_native/Device.h"
 
 namespace dawn_native {
 
@@ -39,12 +40,8 @@
     }
 
     void ComputePassEncoderBase::SetPipeline(ComputePipelineBase* pipeline) {
-        if (mTopLevelBuilder->ConsumedError(ValidateCanRecordCommands())) {
-            return;
-        }
-
-        if (pipeline == nullptr) {
-            mTopLevelBuilder->HandleError("Pipeline cannot be null");
+        if (mTopLevelBuilder->ConsumedError(ValidateCanRecordCommands()) ||
+            mTopLevelBuilder->ConsumedError(GetDevice()->ValidateObject(pipeline))) {
             return;
         }
 
diff --git a/src/dawn_native/ComputePipeline.cpp b/src/dawn_native/ComputePipeline.cpp
index fb7c861..c95115b 100644
--- a/src/dawn_native/ComputePipeline.cpp
+++ b/src/dawn_native/ComputePipeline.cpp
@@ -18,19 +18,14 @@
 
 namespace dawn_native {
 
-    MaybeError ValidateComputePipelineDescriptor(DeviceBase*,
+    MaybeError ValidateComputePipelineDescriptor(DeviceBase* device,
                                                  const ComputePipelineDescriptor* descriptor) {
         if (descriptor->nextInChain != nullptr) {
             return DAWN_VALIDATION_ERROR("nextInChain must be nullptr");
         }
 
-        if (descriptor->module == nullptr) {
-            return DAWN_VALIDATION_ERROR("module cannot be null");
-        }
-
-        if (descriptor->layout == nullptr) {
-            return DAWN_VALIDATION_ERROR("layout cannot be null");
-        }
+        DAWN_TRY(device->ValidateObject(descriptor->module));
+        DAWN_TRY(device->ValidateObject(descriptor->layout));
 
         if (descriptor->entryPoint != std::string("main")) {
             return DAWN_VALIDATION_ERROR("Currently the entry point has to be main()");
@@ -55,4 +50,13 @@
         ExtractModuleData(dawn::ShaderStage::Compute, descriptor->module);
     }
 
+    ComputePipelineBase::ComputePipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag)
+        : PipelineBase(device, tag) {
+    }
+
+    // static
+    ComputePipelineBase* ComputePipelineBase::MakeError(DeviceBase* device) {
+        return new ComputePipelineBase(device, ObjectBase::kError);
+    }
+
 }  // namespace dawn_native
diff --git a/src/dawn_native/ComputePipeline.h b/src/dawn_native/ComputePipeline.h
index 1cea676..c1450d3 100644
--- a/src/dawn_native/ComputePipeline.h
+++ b/src/dawn_native/ComputePipeline.h
@@ -27,6 +27,11 @@
     class ComputePipelineBase : public PipelineBase {
       public:
         ComputePipelineBase(DeviceBase* device, const ComputePipelineDescriptor* descriptor);
+
+        static ComputePipelineBase* MakeError(DeviceBase* device);
+
+      private:
+        ComputePipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag);
     };
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp
index 8b33268..095fb0a 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -70,6 +70,16 @@
         mErrorUserdata = userdata;
     }
 
+    MaybeError DeviceBase::ValidateObject(const ObjectBase* object) const {
+        if (DAWN_UNLIKELY(object->GetDevice() != this)) {
+            return DAWN_VALIDATION_ERROR("Object from a different device.");
+        }
+        if (DAWN_UNLIKELY(object->IsError())) {
+            return DAWN_VALIDATION_ERROR("Object is an error.");
+        }
+        return {};
+    }
+
     AdapterBase* DeviceBase::GetAdapter() const {
         return mAdapter;
     }
@@ -108,7 +118,7 @@
         BindGroupBase* result = nullptr;
 
         if (ConsumedError(CreateBindGroupInternal(&result, descriptor))) {
-            return nullptr;
+            return BindGroupBase::MakeError(this);
         }
 
         return result;
@@ -118,7 +128,7 @@
         BindGroupLayoutBase* result = nullptr;
 
         if (ConsumedError(CreateBindGroupLayoutInternal(&result, descriptor))) {
-            return nullptr;
+            return BindGroupLayoutBase::MakeError(this);
         }
 
         return result;
@@ -127,7 +137,7 @@
         BufferBase* result = nullptr;
 
         if (ConsumedError(CreateBufferInternal(&result, descriptor))) {
-            return nullptr;
+            return BufferBase::MakeError(this);
         }
 
         return result;
@@ -140,7 +150,7 @@
         ComputePipelineBase* result = nullptr;
 
         if (ConsumedError(CreateComputePipelineInternal(&result, descriptor))) {
-            return nullptr;
+            return ComputePipelineBase::MakeError(this);
         }
 
         return result;
@@ -149,7 +159,7 @@
         FenceBase* result = nullptr;
 
         if (ConsumedError(CreateFenceInternal(&result, descriptor))) {
-            return nullptr;
+            return FenceBase::MakeError(this);
         }
 
         return result;
@@ -162,7 +172,7 @@
         PipelineLayoutBase* result = nullptr;
 
         if (ConsumedError(CreatePipelineLayoutInternal(&result, descriptor))) {
-            return nullptr;
+            return PipelineLayoutBase::MakeError(this);
         }
 
         return result;
@@ -171,6 +181,9 @@
         QueueBase* result = nullptr;
 
         if (ConsumedError(CreateQueueInternal(&result))) {
+            // If queue creation failure ever becomes possible, we should implement MakeError and
+            // friends for them.
+            UNREACHABLE();
             return nullptr;
         }
 
@@ -183,7 +196,7 @@
         SamplerBase* result = nullptr;
 
         if (ConsumedError(CreateSamplerInternal(&result, descriptor))) {
-            return nullptr;
+            return SamplerBase::MakeError(this);
         }
 
         return result;
@@ -193,7 +206,7 @@
         RenderPipelineBase* result = nullptr;
 
         if (ConsumedError(CreateRenderPipelineInternal(&result, descriptor))) {
-            return nullptr;
+            return RenderPipelineBase::MakeError(this);
         }
 
         return result;
@@ -202,7 +215,7 @@
         ShaderModuleBase* result = nullptr;
 
         if (ConsumedError(CreateShaderModuleInternal(&result, descriptor))) {
-            return nullptr;
+            return ShaderModuleBase::MakeError(this);
         }
 
         return result;
@@ -214,8 +227,9 @@
         TextureBase* result = nullptr;
 
         if (ConsumedError(CreateTextureInternal(&result, descriptor))) {
-            return nullptr;
+            return TextureBase::MakeError(this);
         }
+
         return result;
     }
     TextureViewBase* DeviceBase::CreateTextureView(TextureBase* texture,
@@ -223,8 +237,9 @@
         TextureViewBase* result = nullptr;
 
         if (ConsumedError(CreateTextureViewInternal(&result, texture, descriptor))) {
-            return nullptr;
+            return TextureViewBase::MakeError(this);
         }
+
         return result;
     }
 
diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h
index 83c40c4..94f91d0 100644
--- a/src/dawn_native/Device.h
+++ b/src/dawn_native/Device.h
@@ -49,6 +49,8 @@
             return false;
         }
 
+        MaybeError ValidateObject(const ObjectBase* object) const;
+
         AdapterBase* GetAdapter() const;
 
         // Used by autogenerated code, returns itself
diff --git a/src/dawn_native/Error.h b/src/dawn_native/Error.h
index f76e638..9e073c2 100644
--- a/src/dawn_native/Error.h
+++ b/src/dawn_native/Error.h
@@ -43,7 +43,8 @@
     //
     // but shorthand version for specific error types are preferred:
     //   return DAWN_VALIDATION_ERROR("My error message");
-#define DAWN_MAKE_ERROR(TYPE, MESSAGE) MakeError(TYPE, MESSAGE, __FILE__, __func__, __LINE__)
+#define DAWN_MAKE_ERROR(TYPE, MESSAGE) \
+    ::dawn_native::MakeError(TYPE, MESSAGE, __FILE__, __func__, __LINE__)
 #define DAWN_VALIDATION_ERROR(MESSAGE) DAWN_MAKE_ERROR(ErrorType::Validation, MESSAGE)
 #define DAWN_CONTEXT_LOST_ERROR(MESSAGE) DAWN_MAKE_ERROR(ErrorType::ContextLost, MESSAGE)
 #define DAWN_UNIMPLEMENTED_ERROR(MESSAGE) DAWN_MAKE_ERROR(ErrorType::Unimplemented, MESSAGE)
@@ -55,31 +56,31 @@
     // When Errors aren't handled explicitly, calls to functions returning errors should be
     // wrapped in an DAWN_TRY. It will return the error if any, otherwise keep executing
     // the current function.
-#define DAWN_TRY(EXPR)                                            \
-    {                                                             \
-        auto DAWN_LOCAL_VAR = EXPR;                               \
-        if (DAWN_UNLIKELY(DAWN_LOCAL_VAR.IsError())) {            \
-            ErrorData* error = DAWN_LOCAL_VAR.AcquireError();     \
-            AppendBacktrace(error, __FILE__, __func__, __LINE__); \
-            return {std::move(error)};                            \
-        }                                                         \
-    }                                                             \
-    for (;;)                                                      \
+#define DAWN_TRY(EXPR)                                                           \
+    {                                                                            \
+        auto DAWN_LOCAL_VAR = EXPR;                                              \
+        if (DAWN_UNLIKELY(DAWN_LOCAL_VAR.IsError())) {                           \
+            ErrorData* error = DAWN_LOCAL_VAR.AcquireError();                    \
+            ::dawn_native::AppendBacktrace(error, __FILE__, __func__, __LINE__); \
+            return {std::move(error)};                                           \
+        }                                                                        \
+    }                                                                            \
+    for (;;)                                                                     \
     break
 
     // DAWN_TRY_ASSIGN is the same as DAWN_TRY for ResultOrError and assigns the success value, if
     // any, to VAR.
-#define DAWN_TRY_ASSIGN(VAR, EXPR)                                \
-    {                                                             \
-        auto DAWN_LOCAL_VAR = EXPR;                               \
-        if (DAWN_UNLIKELY(DAWN_LOCAL_VAR.IsError())) {            \
-            ErrorData* error = DAWN_LOCAL_VAR.AcquireError();     \
-            AppendBacktrace(error, __FILE__, __func__, __LINE__); \
-            return {std::move(error)};                            \
-        }                                                         \
-        VAR = DAWN_LOCAL_VAR.AcquireSuccess();                    \
-    }                                                             \
-    for (;;)                                                      \
+#define DAWN_TRY_ASSIGN(VAR, EXPR)                                               \
+    {                                                                            \
+        auto DAWN_LOCAL_VAR = EXPR;                                              \
+        if (DAWN_UNLIKELY(DAWN_LOCAL_VAR.IsError())) {                           \
+            ErrorData* error = DAWN_LOCAL_VAR.AcquireError();                    \
+            ::dawn_native::AppendBacktrace(error, __FILE__, __func__, __LINE__); \
+            return {std::move(error)};                                           \
+        }                                                                        \
+        VAR = DAWN_LOCAL_VAR.AcquireSuccess();                                   \
+    }                                                                            \
+    for (;;)                                                                     \
     break
 
     // Implementation detail of DAWN_TRY and DAWN_TRY_ASSIGN's adding to the Error's backtrace.
diff --git a/src/dawn_native/Fence.cpp b/src/dawn_native/Fence.cpp
index 54ed9a9..4ab4c7b 100644
--- a/src/dawn_native/Fence.cpp
+++ b/src/dawn_native/Fence.cpp
@@ -38,14 +38,26 @@
           mCompletedValue(descriptor->initialValue) {
     }
 
+    FenceBase::FenceBase(DeviceBase* device, ObjectBase::ErrorTag tag) : ObjectBase(device, tag) {
+    }
+
     FenceBase::~FenceBase() {
         for (auto& request : mRequests.IterateAll()) {
+            ASSERT(!IsError());
             request.completionCallback(DAWN_FENCE_COMPLETION_STATUS_UNKNOWN, request.userdata);
         }
         mRequests.Clear();
     }
 
+    // static
+    FenceBase* FenceBase::MakeError(DeviceBase* device) {
+        return new FenceBase(device, ObjectBase::kError);
+    }
+
     uint64_t FenceBase::GetCompletedValue() const {
+        if (IsError()) {
+            return 0;
+        }
         return mCompletedValue;
     }
 
@@ -56,6 +68,7 @@
             callback(DAWN_FENCE_COMPLETION_STATUS_ERROR, userdata);
             return;
         }
+        ASSERT(!IsError());
 
         if (value <= mCompletedValue) {
             callback(DAWN_FENCE_COMPLETION_STATUS_SUCCESS, userdata);
@@ -69,15 +82,18 @@
     }
 
     uint64_t FenceBase::GetSignaledValue() const {
+        ASSERT(!IsError());
         return mSignalValue;
     }
 
     void FenceBase::SetSignaledValue(uint64_t signalValue) {
+        ASSERT(!IsError());
         ASSERT(signalValue > mSignalValue);
         mSignalValue = signalValue;
     }
 
     void FenceBase::SetCompletedValue(uint64_t completedValue) {
+        ASSERT(!IsError());
         ASSERT(completedValue <= mSignalValue);
         ASSERT(completedValue > mCompletedValue);
         mCompletedValue = completedValue;
@@ -89,6 +105,7 @@
     }
 
     MaybeError FenceBase::ValidateOnCompletion(uint64_t value) const {
+        DAWN_TRY(GetDevice()->ValidateObject(this));
         if (value > mSignalValue) {
             return DAWN_VALIDATION_ERROR("Value greater than fence signaled value");
         }
diff --git a/src/dawn_native/Fence.h b/src/dawn_native/Fence.h
index e2f4c40..0b1c7d9 100644
--- a/src/dawn_native/Fence.h
+++ b/src/dawn_native/Fence.h
@@ -33,12 +33,15 @@
         FenceBase(DeviceBase* device, const FenceDescriptor* descriptor);
         ~FenceBase();
 
+        static FenceBase* MakeError(DeviceBase* device);
+
+        uint64_t GetSignaledValue() const;
+
         // Dawn API
         uint64_t GetCompletedValue() const;
         void OnCompletion(uint64_t value,
                           dawn::FenceOnCompletionCallback callback,
                           dawn::CallbackUserdata userdata);
-        uint64_t GetSignaledValue() const;
 
       protected:
         friend class QueueBase;
@@ -47,6 +50,8 @@
         void SetCompletedValue(uint64_t completedValue);
 
       private:
+        FenceBase(DeviceBase* device, ObjectBase::ErrorTag tag);
+
         MaybeError ValidateOnCompletion(uint64_t value) const;
 
         struct OnCompletionData {
diff --git a/src/dawn_native/ObjectBase.cpp b/src/dawn_native/ObjectBase.cpp
index 2d6b56f..3e40af4 100644
--- a/src/dawn_native/ObjectBase.cpp
+++ b/src/dawn_native/ObjectBase.cpp
@@ -16,7 +16,10 @@
 
 namespace dawn_native {
 
-    ObjectBase::ObjectBase(DeviceBase* device) : mDevice(device) {
+    ObjectBase::ObjectBase(DeviceBase* device) : mDevice(device), mIsError(false) {
+    }
+
+    ObjectBase::ObjectBase(DeviceBase* device, ErrorTag) : mDevice(device), mIsError(true) {
     }
 
     ObjectBase::~ObjectBase() {
@@ -26,4 +29,8 @@
         return mDevice;
     }
 
+    bool ObjectBase::IsError() const {
+        return mIsError;
+    }
+
 }  // namespace dawn_native
diff --git a/src/dawn_native/ObjectBase.h b/src/dawn_native/ObjectBase.h
index 019938a..02dd7ec 100644
--- a/src/dawn_native/ObjectBase.h
+++ b/src/dawn_native/ObjectBase.h
@@ -23,13 +23,22 @@
 
     class ObjectBase : public RefCounted {
       public:
+        struct ErrorTag {};
+        static constexpr ErrorTag kError = {};
+
         ObjectBase(DeviceBase* device);
+        ObjectBase(DeviceBase* device, ErrorTag tag);
         virtual ~ObjectBase();
 
         DeviceBase* GetDevice() const;
+        bool IsError() const;
 
       private:
         DeviceBase* mDevice;
+        // TODO(cwallez@chromium.org): This most likely adds 4 bytes to most Dawn objects, see if
+        // that bit can be hidden in the refcount once it is a single 64bit refcount.
+        // See https://bugs.chromium.org/p/dawn/issues/detail?id=105
+        bool mIsError;
     };
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/Pipeline.cpp b/src/dawn_native/Pipeline.cpp
index fb2a311..6ddb742 100644
--- a/src/dawn_native/Pipeline.cpp
+++ b/src/dawn_native/Pipeline.cpp
@@ -26,10 +26,16 @@
     PipelineBase::PipelineBase(DeviceBase* device,
                                PipelineLayoutBase* layout,
                                dawn::ShaderStageBit stages)
-        : ObjectBase(device), mStageMask(stages), mLayout(layout), mDevice(device) {
+        : ObjectBase(device), mStageMask(stages), mLayout(layout) {
+    }
+
+    PipelineBase::PipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag)
+        : ObjectBase(device, tag) {
     }
 
     void PipelineBase::ExtractModuleData(dawn::ShaderStage stage, ShaderModuleBase* module) {
+        ASSERT(!IsError());
+
         PushConstantInfo* info = &mPushConstants[stage];
 
         const auto& moduleInfo = module->GetPushConstants();
@@ -50,19 +56,18 @@
 
     const PipelineBase::PushConstantInfo& PipelineBase::GetPushConstants(
         dawn::ShaderStage stage) const {
+        ASSERT(!IsError());
         return mPushConstants[stage];
     }
 
     dawn::ShaderStageBit PipelineBase::GetStageMask() const {
+        ASSERT(!IsError());
         return mStageMask;
     }
 
     PipelineLayoutBase* PipelineBase::GetLayout() {
+        ASSERT(!IsError());
         return mLayout.Get();
     }
 
-    DeviceBase* PipelineBase::GetDevice() const {
-        return mDevice;
-    }
-
 }  // namespace dawn_native
diff --git a/src/dawn_native/Pipeline.h b/src/dawn_native/Pipeline.h
index 47c1079..cc1c542 100644
--- a/src/dawn_native/Pipeline.h
+++ b/src/dawn_native/Pipeline.h
@@ -37,26 +37,24 @@
 
     class PipelineBase : public ObjectBase {
       public:
-        PipelineBase(DeviceBase* device, PipelineLayoutBase* layout, dawn::ShaderStageBit stages);
-
         struct PushConstantInfo {
             std::bitset<kMaxPushConstants> mask;
             std::array<PushConstantType, kMaxPushConstants> types;
         };
         const PushConstantInfo& GetPushConstants(dawn::ShaderStage stage) const;
         dawn::ShaderStageBit GetStageMask() const;
-
         PipelineLayoutBase* GetLayout();
-        DeviceBase* GetDevice() const;
 
       protected:
+        PipelineBase(DeviceBase* device, PipelineLayoutBase* layout, dawn::ShaderStageBit stages);
+        PipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag);
+
         void ExtractModuleData(dawn::ShaderStage stage, ShaderModuleBase* module);
 
       private:
         dawn::ShaderStageBit mStageMask;
         Ref<PipelineLayoutBase> mLayout;
         PerStage<PushConstantInfo> mPushConstants;
-        DeviceBase* mDevice;
     };
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/PipelineLayout.cpp b/src/dawn_native/PipelineLayout.cpp
index 96772bd..720273a 100644
--- a/src/dawn_native/PipelineLayout.cpp
+++ b/src/dawn_native/PipelineLayout.cpp
@@ -20,7 +20,7 @@
 
 namespace dawn_native {
 
-    MaybeError ValidatePipelineLayoutDescriptor(DeviceBase*,
+    MaybeError ValidatePipelineLayoutDescriptor(DeviceBase* device,
                                                 const PipelineLayoutDescriptor* descriptor) {
         if (descriptor->nextInChain != nullptr) {
             return DAWN_VALIDATION_ERROR("nextInChain must be nullptr");
@@ -31,9 +31,7 @@
         }
 
         for (uint32_t i = 0; i < descriptor->numBindGroupLayouts; ++i) {
-            if (descriptor->bindGroupLayouts[i] == nullptr) {
-                return DAWN_VALIDATION_ERROR("bind group layouts may not be null");
-            }
+            DAWN_TRY(device->ValidateObject(descriptor->bindGroupLayouts[i]));
         }
         return {};
     }
@@ -50,22 +48,36 @@
         }
     }
 
+    PipelineLayoutBase::PipelineLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag)
+        : ObjectBase(device, tag) {
+    }
+
+    // static
+    PipelineLayoutBase* PipelineLayoutBase::MakeError(DeviceBase* device) {
+        return new PipelineLayoutBase(device, ObjectBase::kError);
+    }
+
     const BindGroupLayoutBase* PipelineLayoutBase::GetBindGroupLayout(size_t group) const {
+        ASSERT(!IsError());
         ASSERT(group < kMaxBindGroups);
         ASSERT(mMask[group]);
         return mBindGroupLayouts[group].Get();
     }
 
     const std::bitset<kMaxBindGroups> PipelineLayoutBase::GetBindGroupLayoutsMask() const {
+        ASSERT(!IsError());
         return mMask;
     }
 
     std::bitset<kMaxBindGroups> PipelineLayoutBase::InheritedGroupsMask(
         const PipelineLayoutBase* other) const {
+        ASSERT(!IsError());
         return {(1 << GroupsInheritUpTo(other)) - 1u};
     }
 
     uint32_t PipelineLayoutBase::GroupsInheritUpTo(const PipelineLayoutBase* other) const {
+        ASSERT(!IsError());
+
         for (uint32_t i = 0; i < kMaxBindGroups; ++i) {
             if (!mMask[i] || mBindGroupLayouts[i].Get() != other->mBindGroupLayouts[i].Get()) {
                 return i;
diff --git a/src/dawn_native/PipelineLayout.h b/src/dawn_native/PipelineLayout.h
index 0d5e932..aa1e3e5 100644
--- a/src/dawn_native/PipelineLayout.h
+++ b/src/dawn_native/PipelineLayout.h
@@ -36,6 +36,8 @@
       public:
         PipelineLayoutBase(DeviceBase* device, const PipelineLayoutDescriptor* descriptor);
 
+        static PipelineLayoutBase* MakeError(DeviceBase* device);
+
         const BindGroupLayoutBase* GetBindGroupLayout(size_t group) const;
         const std::bitset<kMaxBindGroups> GetBindGroupLayoutsMask() const;
 
@@ -48,6 +50,8 @@
         uint32_t GroupsInheritUpTo(const PipelineLayoutBase* other) const;
 
       protected:
+        PipelineLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag);
+
         BindGroupLayoutArray mBindGroupLayouts;
         std::bitset<kMaxBindGroups> mMask;
     };
diff --git a/src/dawn_native/ProgrammablePassEncoder.cpp b/src/dawn_native/ProgrammablePassEncoder.cpp
index fdfcf42..bc9f19e 100644
--- a/src/dawn_native/ProgrammablePassEncoder.cpp
+++ b/src/dawn_native/ProgrammablePassEncoder.cpp
@@ -17,6 +17,7 @@
 #include "dawn_native/BindGroup.h"
 #include "dawn_native/CommandBuffer.h"
 #include "dawn_native/Commands.h"
+#include "dawn_native/Device.h"
 
 #include <string.h>
 
@@ -38,12 +39,8 @@
     }
 
     void ProgrammablePassEncoder::SetBindGroup(uint32_t groupIndex, BindGroupBase* group) {
-        if (mTopLevelBuilder->ConsumedError(ValidateCanRecordCommands())) {
-            return;
-        }
-
-        if (group == nullptr) {
-            mTopLevelBuilder->HandleError("BindGroup cannot be null");
+        if (mTopLevelBuilder->ConsumedError(ValidateCanRecordCommands()) ||
+            mTopLevelBuilder->ConsumedError(GetDevice()->ValidateObject(group))) {
             return;
         }
 
diff --git a/src/dawn_native/Queue.cpp b/src/dawn_native/Queue.cpp
index 426c963..6fc9af9 100644
--- a/src/dawn_native/Queue.cpp
+++ b/src/dawn_native/Queue.cpp
@@ -32,6 +32,7 @@
         if (GetDevice()->ConsumedError(ValidateSubmit(numCommands, commands))) {
             return;
         }
+        ASSERT(!IsError());
 
         SubmitImpl(numCommands, commands);
     }
@@ -40,13 +41,20 @@
         if (GetDevice()->ConsumedError(ValidateSignal(fence, signalValue))) {
             return;
         }
+        ASSERT(!IsError());
 
         fence->SetSignaledValue(signalValue);
         GetDevice()->GetFenceSignalTracker()->UpdateFenceOnComplete(fence, signalValue);
     }
 
     MaybeError QueueBase::ValidateSubmit(uint32_t numCommands, CommandBufferBase* const* commands) {
+        DAWN_TRY(GetDevice()->ValidateObject(this));
+
         for (uint32_t i = 0; i < numCommands; ++i) {
+            DAWN_TRY(GetDevice()->ValidateObject(commands[i]));
+
+            // TODO(cwallez@chromium.org): Remove this once CommandBufferBuilder doesn't use the
+            // builder mechanism anymore.
             if (commands[i] == nullptr) {
                 return DAWN_VALIDATION_ERROR("Command buffers cannot be null");
             }
@@ -74,9 +82,8 @@
     }
 
     MaybeError QueueBase::ValidateSignal(const FenceBase* fence, uint64_t signalValue) {
-        if (fence == nullptr) {
-            return DAWN_VALIDATION_ERROR("Fence cannot be null");
-        }
+        DAWN_TRY(GetDevice()->ValidateObject(this));
+        DAWN_TRY(GetDevice()->ValidateObject(fence));
 
         if (signalValue <= fence->GetSignaledValue()) {
             return DAWN_VALIDATION_ERROR("Signal value less than or equal to fence signaled value");
diff --git a/src/dawn_native/RenderPassDescriptor.cpp b/src/dawn_native/RenderPassDescriptor.cpp
index 3a40a83..99730ba 100644
--- a/src/dawn_native/RenderPassDescriptor.cpp
+++ b/src/dawn_native/RenderPassDescriptor.cpp
@@ -164,6 +164,10 @@
                 continue;
             }
 
+            // TODO(cwallez@chromium.org): Once RenderPassDescriptor doesn't use a builder, check
+            // that the textureView is a valid object.
+            // See https://bugs.chromium.org/p/dawn/issues/detail?id=6
+
             if (!IsColorRenderableTextureFormat(textureView->GetFormat())) {
                 HandleError(
                     "The format of the texture view used as color attachment is not color "
@@ -190,6 +194,10 @@
     void RenderPassDescriptorBuilder::SetDepthStencilAttachment(
         const RenderPassDepthStencilAttachmentDescriptor* attachment) {
         TextureViewBase* textureView = attachment->attachment;
+
+        // TODO(cwallez@chromium.org): Once RenderPassDescriptor doesn't use a builder, check that
+        // the textureView is a valid object.
+        // See https://bugs.chromium.org/p/dawn/issues/detail?id=6
         if (textureView == nullptr) {
             HandleError("Texture view cannot be nullptr");
             return;
diff --git a/src/dawn_native/RenderPassEncoder.cpp b/src/dawn_native/RenderPassEncoder.cpp
index f48b053..c392bb3 100644
--- a/src/dawn_native/RenderPassEncoder.cpp
+++ b/src/dawn_native/RenderPassEncoder.cpp
@@ -17,6 +17,7 @@
 #include "dawn_native/Buffer.h"
 #include "dawn_native/CommandBuffer.h"
 #include "dawn_native/Commands.h"
+#include "dawn_native/Device.h"
 #include "dawn_native/RenderPipeline.h"
 
 #include <string.h>
@@ -64,12 +65,8 @@
     }
 
     void RenderPassEncoderBase::SetPipeline(RenderPipelineBase* pipeline) {
-        if (mTopLevelBuilder->ConsumedError(ValidateCanRecordCommands())) {
-            return;
-        }
-
-        if (pipeline == nullptr) {
-            mTopLevelBuilder->HandleError("Pipeline cannot be null");
+        if (mTopLevelBuilder->ConsumedError(ValidateCanRecordCommands()) ||
+            mTopLevelBuilder->ConsumedError(GetDevice()->ValidateObject(pipeline))) {
             return;
         }
 
@@ -117,12 +114,8 @@
     }
 
     void RenderPassEncoderBase::SetIndexBuffer(BufferBase* buffer, uint32_t offset) {
-        if (mTopLevelBuilder->ConsumedError(ValidateCanRecordCommands())) {
-            return;
-        }
-
-        if (buffer == nullptr) {
-            mTopLevelBuilder->HandleError("Buffer cannot be null");
+        if (mTopLevelBuilder->ConsumedError(ValidateCanRecordCommands()) ||
+            mTopLevelBuilder->ConsumedError(GetDevice()->ValidateObject(buffer))) {
             return;
         }
 
@@ -141,8 +134,7 @@
         }
 
         for (size_t i = 0; i < count; ++i) {
-            if (buffers[i] == nullptr) {
-                mTopLevelBuilder->HandleError("Buffers cannot be null");
+            if (mTopLevelBuilder->ConsumedError(GetDevice()->ValidateObject(buffers[i]))) {
                 return;
             }
         }
diff --git a/src/dawn_native/RenderPipeline.cpp b/src/dawn_native/RenderPipeline.cpp
index b760939..19b9bd5 100644
--- a/src/dawn_native/RenderPipeline.cpp
+++ b/src/dawn_native/RenderPipeline.cpp
@@ -25,9 +25,12 @@
     // Helper functions
     namespace {
 
-        MaybeError ValidatePipelineStageDescriptor(const PipelineStageDescriptor* descriptor,
+        MaybeError ValidatePipelineStageDescriptor(DeviceBase* device,
+                                                   const PipelineStageDescriptor* descriptor,
                                                    const PipelineLayoutBase* layout,
                                                    dawn::ShaderStage stage) {
+            DAWN_TRY(device->ValidateObject(descriptor->module));
+
             if (descriptor->entryPoint != std::string("main")) {
                 return DAWN_VALIDATION_ERROR("Entry point must be \"main\"");
             }
@@ -110,28 +113,22 @@
             return DAWN_VALIDATION_ERROR("nextInChain must be nullptr");
         }
 
-        if (descriptor->layout == nullptr) {
-            return DAWN_VALIDATION_ERROR("Layout must not be null");
-        }
+        DAWN_TRY(device->ValidateObject(descriptor->layout));
 
         if (descriptor->inputState == nullptr) {
             return DAWN_VALIDATION_ERROR("Input state must not be null");
         }
 
-        if (descriptor->depthStencilState == nullptr) {
-            return DAWN_VALIDATION_ERROR("Depth stencil state must not be null");
-        }
-
         for (uint32_t i = 0; i < descriptor->numBlendStates; ++i) {
             DAWN_TRY(ValidateBlendStateDescriptor(&descriptor->blendStates[i]));
         }
 
         DAWN_TRY(ValidateIndexFormat(descriptor->indexFormat));
         DAWN_TRY(ValidatePrimitiveTopology(descriptor->primitiveTopology));
-        DAWN_TRY(ValidatePipelineStageDescriptor(descriptor->vertexStage, descriptor->layout,
-                                                 dawn::ShaderStage::Vertex));
-        DAWN_TRY(ValidatePipelineStageDescriptor(descriptor->fragmentStage, descriptor->layout,
-                                                 dawn::ShaderStage::Fragment));
+        DAWN_TRY(ValidatePipelineStageDescriptor(device, descriptor->vertexStage,
+                                                 descriptor->layout, dawn::ShaderStage::Vertex));
+        DAWN_TRY(ValidatePipelineStageDescriptor(device, descriptor->fragmentStage,
+                                                 descriptor->layout, dawn::ShaderStage::Fragment));
         DAWN_TRY(ValidateAttachmentsStateDescriptor(descriptor->attachmentsState));
 
         if ((descriptor->vertexStage->module->GetUsedVertexAttributes() &
@@ -206,46 +203,65 @@
         // attachment are set?
     }
 
+    RenderPipelineBase::RenderPipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag)
+        : PipelineBase(device, tag) {
+    }
+
+    // static
+    RenderPipelineBase* RenderPipelineBase::MakeError(DeviceBase* device) {
+        return new RenderPipelineBase(device, ObjectBase::kError);
+    }
+
     const BlendStateDescriptor* RenderPipelineBase::GetBlendStateDescriptor(
         uint32_t attachmentSlot) {
+        ASSERT(!IsError());
         ASSERT(attachmentSlot < mBlendStates.size());
         return &mBlendStates[attachmentSlot];
     }
 
     const DepthStencilStateDescriptor* RenderPipelineBase::GetDepthStencilStateDescriptor() {
+        ASSERT(!IsError());
         return &mDepthStencilState;
     }
 
     dawn::IndexFormat RenderPipelineBase::GetIndexFormat() const {
+        ASSERT(!IsError());
         return mIndexFormat;
     }
 
     InputStateBase* RenderPipelineBase::GetInputState() {
+        ASSERT(!IsError());
         return mInputState.Get();
     }
 
     dawn::PrimitiveTopology RenderPipelineBase::GetPrimitiveTopology() const {
+        ASSERT(!IsError());
         return mPrimitiveTopology;
     }
 
     std::bitset<kMaxColorAttachments> RenderPipelineBase::GetColorAttachmentsMask() const {
+        ASSERT(!IsError());
         return mColorAttachmentsSet;
     }
 
     bool RenderPipelineBase::HasDepthStencilAttachment() const {
+        ASSERT(!IsError());
         return mHasDepthStencilAttachment;
     }
 
     dawn::TextureFormat RenderPipelineBase::GetColorAttachmentFormat(uint32_t attachment) const {
+        ASSERT(!IsError());
         return mColorAttachmentFormats[attachment];
     }
 
     dawn::TextureFormat RenderPipelineBase::GetDepthStencilFormat() const {
+        ASSERT(!IsError());
         ASSERT(mHasDepthStencilAttachment);
         return mDepthStencilFormat;
     }
 
     bool RenderPipelineBase::IsCompatibleWith(const RenderPassDescriptorBase* renderPass) const {
+        ASSERT(!IsError());
         // TODO(cwallez@chromium.org): This is called on every SetPipeline command. Optimize it for
         // example by caching some "attachment compatibility" object that would make the
         // compatibility check a single pointer comparison.
diff --git a/src/dawn_native/RenderPipeline.h b/src/dawn_native/RenderPipeline.h
index 03d7df8..ab2da1b 100644
--- a/src/dawn_native/RenderPipeline.h
+++ b/src/dawn_native/RenderPipeline.h
@@ -36,6 +36,8 @@
       public:
         RenderPipelineBase(DeviceBase* device, const RenderPipelineDescriptor* descriptor);
 
+        static RenderPipelineBase* MakeError(DeviceBase* device);
+
         const BlendStateDescriptor* GetBlendStateDescriptor(uint32_t attachmentSlot);
         const DepthStencilStateDescriptor* GetDepthStencilStateDescriptor();
         dawn::IndexFormat GetIndexFormat() const;
@@ -52,6 +54,8 @@
         bool IsCompatibleWith(const RenderPassDescriptorBase* renderPass) const;
 
       private:
+        RenderPipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag);
+
         DepthStencilStateDescriptor mDepthStencilState;
         dawn::IndexFormat mIndexFormat;
         Ref<InputStateBase> mInputState;
diff --git a/src/dawn_native/Sampler.cpp b/src/dawn_native/Sampler.cpp
index 3e68894..4d462a5 100644
--- a/src/dawn_native/Sampler.cpp
+++ b/src/dawn_native/Sampler.cpp
@@ -49,4 +49,13 @@
     SamplerBase::SamplerBase(DeviceBase* device, const SamplerDescriptor*) : ObjectBase(device) {
     }
 
+    SamplerBase::SamplerBase(DeviceBase* device, ObjectBase::ErrorTag tag)
+        : ObjectBase(device, tag) {
+    }
+
+    // static
+    SamplerBase* SamplerBase::MakeError(DeviceBase* device) {
+        return new SamplerBase(device, ObjectBase::kError);
+    }
+
 }  // namespace dawn_native
diff --git a/src/dawn_native/Sampler.h b/src/dawn_native/Sampler.h
index a9d547a..cde32dd 100644
--- a/src/dawn_native/Sampler.h
+++ b/src/dawn_native/Sampler.h
@@ -29,6 +29,11 @@
     class SamplerBase : public ObjectBase {
       public:
         SamplerBase(DeviceBase* device, const SamplerDescriptor* descriptor);
+
+        static SamplerBase* MakeError(DeviceBase* device);
+
+      private:
+        SamplerBase(DeviceBase* device, ObjectBase::ErrorTag tag);
     };
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/ShaderModule.cpp b/src/dawn_native/ShaderModule.cpp
index 7a0e49e..d32899f 100644
--- a/src/dawn_native/ShaderModule.cpp
+++ b/src/dawn_native/ShaderModule.cpp
@@ -69,7 +69,18 @@
         : ObjectBase(device) {
     }
 
+    ShaderModuleBase::ShaderModuleBase(DeviceBase* device, ObjectBase::ErrorTag tag)
+        : ObjectBase(device, tag) {
+    }
+
+    // static
+    ShaderModuleBase* ShaderModuleBase::MakeError(DeviceBase* device) {
+        return new ShaderModuleBase(device, ObjectBase::kError);
+    }
+
     void ShaderModuleBase::ExtractSpirvInfo(const spirv_cross::Compiler& compiler) {
+        ASSERT(!IsError());
+
         DeviceBase* device = GetDevice();
         // TODO(cwallez@chromium.org): make errors here builder-level
         // currently errors here do not prevent the shadermodule from being used
@@ -211,22 +222,28 @@
     }
 
     const ShaderModuleBase::PushConstantInfo& ShaderModuleBase::GetPushConstants() const {
+        ASSERT(!IsError());
         return mPushConstants;
     }
 
     const ShaderModuleBase::ModuleBindingInfo& ShaderModuleBase::GetBindingInfo() const {
+        ASSERT(!IsError());
         return mBindingInfo;
     }
 
     const std::bitset<kMaxVertexAttributes>& ShaderModuleBase::GetUsedVertexAttributes() const {
+        ASSERT(!IsError());
         return mUsedVertexAttributes;
     }
 
     dawn::ShaderStage ShaderModuleBase::GetExecutionModel() const {
+        ASSERT(!IsError());
         return mExecutionModel;
     }
 
     bool ShaderModuleBase::IsCompatibleWithPipelineLayout(const PipelineLayoutBase* layout) {
+        ASSERT(!IsError());
+
         for (uint32_t group : IterateBitSet(layout->GetBindGroupLayoutsMask())) {
             if (!IsCompatibleWithBindGroupLayout(group, layout->GetBindGroupLayout(group))) {
                 return false;
@@ -246,6 +263,8 @@
 
     bool ShaderModuleBase::IsCompatibleWithBindGroupLayout(size_t group,
                                                            const BindGroupLayoutBase* layout) {
+        ASSERT(!IsError());
+
         const auto& layoutInfo = layout->GetBindingInfo();
         for (size_t i = 0; i < kMaxBindingsPerGroup; ++i) {
             const auto& moduleInfo = mBindingInfo[group][i];
diff --git a/src/dawn_native/ShaderModule.h b/src/dawn_native/ShaderModule.h
index 1d0fcf1..dbeeb7b 100644
--- a/src/dawn_native/ShaderModule.h
+++ b/src/dawn_native/ShaderModule.h
@@ -40,6 +40,8 @@
       public:
         ShaderModuleBase(DeviceBase* device, const ShaderModuleDescriptor* descriptor);
 
+        static ShaderModuleBase* MakeError(DeviceBase* device);
+
         void ExtractSpirvInfo(const spirv_cross::Compiler& compiler);
 
         struct PushConstantInfo {
@@ -68,6 +70,8 @@
         bool IsCompatibleWithPipelineLayout(const PipelineLayoutBase* layout);
 
       private:
+        ShaderModuleBase(DeviceBase* device, ObjectBase::ErrorTag tag);
+
         bool IsCompatibleWithBindGroupLayout(size_t group, const BindGroupLayoutBase* layout);
 
         PushConstantInfo mPushConstants = {};
diff --git a/src/dawn_native/SwapChain.cpp b/src/dawn_native/SwapChain.cpp
index c2ea095..fd1cb51 100644
--- a/src/dawn_native/SwapChain.cpp
+++ b/src/dawn_native/SwapChain.cpp
@@ -72,6 +72,7 @@
     }
 
     void SwapChainBase::Present(TextureBase* texture) {
+        // This also checks that the texture is valid since mLastNextTexture is always valid.
         if (texture != mLastNextTexture) {
             GetDevice()->HandleError("Tried to present something other than the last NextTexture");
             return;
diff --git a/src/dawn_native/Texture.cpp b/src/dawn_native/Texture.cpp
index 44b4160..74cc696 100644
--- a/src/dawn_native/Texture.cpp
+++ b/src/dawn_native/Texture.cpp
@@ -163,13 +163,14 @@
         return {};
     }
 
-    MaybeError ValidateTextureViewDescriptor(const DeviceBase*,
+    MaybeError ValidateTextureViewDescriptor(const DeviceBase* device,
                                              const TextureBase* texture,
                                              const TextureViewDescriptor* descriptor) {
         if (descriptor->nextInChain != nullptr) {
             return DAWN_VALIDATION_ERROR("nextInChain must be nullptr");
         }
 
+        DAWN_TRY(device->ValidateObject(texture));
         DAWN_TRY(ValidateTextureViewDimension(descriptor->dimension));
         DAWN_TRY(ValidateTextureFormat(descriptor->format));
 
@@ -292,31 +293,52 @@
           mUsage(descriptor->usage) {
     }
 
+    TextureBase::TextureBase(DeviceBase* device, ObjectBase::ErrorTag tag)
+        : ObjectBase(device, tag) {
+    }
+
+    // static
+    TextureBase* TextureBase::MakeError(DeviceBase* device) {
+        return new TextureBase(device, ObjectBase::kError);
+    }
+
     dawn::TextureDimension TextureBase::GetDimension() const {
+        ASSERT(!IsError());
         return mDimension;
     }
     dawn::TextureFormat TextureBase::GetFormat() const {
+        ASSERT(!IsError());
         return mFormat;
     }
     const Extent3D& TextureBase::GetSize() const {
+        ASSERT(!IsError());
         return mSize;
     }
     uint32_t TextureBase::GetArrayLayers() const {
+        ASSERT(!IsError());
         return mArrayLayers;
     }
     uint32_t TextureBase::GetNumMipLevels() const {
+        ASSERT(!IsError());
         return mNumMipLevels;
     }
     dawn::TextureUsageBit TextureBase::GetUsage() const {
+        ASSERT(!IsError());
         return mUsage;
     }
 
     MaybeError TextureBase::ValidateCanUseInSubmitNow() const {
+        ASSERT(!IsError());
         return {};
     }
 
     TextureViewBase* TextureBase::CreateDefaultTextureView() {
-        TextureViewDescriptor descriptor = MakeDefaultTextureViewDescriptor(this);
+        TextureViewDescriptor descriptor = {};
+
+        if (!IsError()) {
+            descriptor = MakeDefaultTextureViewDescriptor(this);
+        }
+
         return GetDevice()->CreateTextureView(this, &descriptor);
     }
 
@@ -336,31 +358,47 @@
           mLayerCount(descriptor->layerCount) {
     }
 
+    TextureViewBase::TextureViewBase(DeviceBase* device, ObjectBase::ErrorTag tag)
+        : ObjectBase(device, tag) {
+    }
+
+    // static
+    TextureViewBase* TextureViewBase::MakeError(DeviceBase* device) {
+        return new TextureViewBase(device, ObjectBase::kError);
+    }
+
     const TextureBase* TextureViewBase::GetTexture() const {
+        ASSERT(!IsError());
         return mTexture.Get();
     }
 
     TextureBase* TextureViewBase::GetTexture() {
+        ASSERT(!IsError());
         return mTexture.Get();
     }
 
     dawn::TextureFormat TextureViewBase::GetFormat() const {
+        ASSERT(!IsError());
         return mFormat;
     }
 
     uint32_t TextureViewBase::GetBaseMipLevel() const {
+        ASSERT(!IsError());
         return mBaseMipLevel;
     }
 
     uint32_t TextureViewBase::GetLevelCount() const {
+        ASSERT(!IsError());
         return mLevelCount;
     }
 
     uint32_t TextureViewBase::GetBaseArrayLayer() const {
+        ASSERT(!IsError());
         return mBaseArrayLayer;
     }
 
     uint32_t TextureViewBase::GetLayerCount() const {
+        ASSERT(!IsError());
         return mLayerCount;
     }
 }  // namespace dawn_native
diff --git a/src/dawn_native/Texture.h b/src/dawn_native/Texture.h
index 12edb3c..5967cde 100644
--- a/src/dawn_native/Texture.h
+++ b/src/dawn_native/Texture.h
@@ -47,6 +47,8 @@
       public:
         TextureBase(DeviceBase* device, const TextureDescriptor* descriptor);
 
+        static TextureBase* MakeError(DeviceBase* device);
+
         dawn::TextureDimension GetDimension() const;
         dawn::TextureFormat GetFormat() const;
         const Extent3D& GetSize() const;
@@ -61,6 +63,8 @@
         TextureViewBase* CreateTextureView(const TextureViewDescriptor* descriptor);
 
       private:
+        TextureBase(DeviceBase* device, ObjectBase::ErrorTag tag);
+
         dawn::TextureDimension mDimension;
         dawn::TextureFormat mFormat;
         Extent3D mSize;
@@ -73,6 +77,8 @@
       public:
         TextureViewBase(TextureBase* texture, const TextureViewDescriptor* descriptor);
 
+        static TextureViewBase* MakeError(DeviceBase* device);
+
         const TextureBase* GetTexture() const;
         TextureBase* GetTexture();
 
@@ -83,6 +89,8 @@
         uint32_t GetLayerCount() const;
 
       private:
+        TextureViewBase(DeviceBase* device, ObjectBase::ErrorTag tag);
+
         Ref<TextureBase> mTexture;
 
         dawn::TextureFormat mFormat;
diff --git a/src/tests/unittests/validation/BindGroupValidationTests.cpp b/src/tests/unittests/validation/BindGroupValidationTests.cpp
index c76c46d..e78ccb5 100644
--- a/src/tests/unittests/validation/BindGroupValidationTests.cpp
+++ b/src/tests/unittests/validation/BindGroupValidationTests.cpp
@@ -161,6 +161,19 @@
     binding.buffer = mUBO;
     ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor));
     binding.buffer = nullptr;
+
+    // Setting the sampler to an error sampler is an error.
+    {
+        dawn::SamplerDescriptor samplerDesc = utils::GetDefaultSamplerDescriptor();
+        samplerDesc.minFilter = static_cast<dawn::FilterMode>(0xFFFFFFFF);
+
+        dawn::Sampler errorSampler;
+        ASSERT_DEVICE_ERROR(errorSampler = device.CreateSampler(&samplerDesc));
+
+        binding.sampler = errorSampler;
+        ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor));
+        binding.sampler = nullptr;
+    }
 }
 
 // Check that a texture binding must contain exactly a texture view
@@ -198,6 +211,24 @@
     binding.buffer = mUBO;
     ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor));
     binding.buffer = nullptr;
+
+    // Setting the texture view to an error texture view is an error.
+    {
+        dawn::TextureViewDescriptor viewDesc;
+        viewDesc.format = dawn::TextureFormat::R8G8B8A8Unorm;
+        viewDesc.dimension = dawn::TextureViewDimension::e2D;
+        viewDesc.baseMipLevel = 0;
+        viewDesc.levelCount = 0;
+        viewDesc.baseArrayLayer = 0;
+        viewDesc.layerCount = 0;
+
+        dawn::TextureView errorView;
+        ASSERT_DEVICE_ERROR(errorView = mSampledTexture.CreateTextureView(&viewDesc));
+
+        binding.textureView = errorView;
+        ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor));
+        binding.textureView = nullptr;
+    }
 }
 
 // Check that a buffer binding must contain exactly a buffer
@@ -235,6 +266,20 @@
     binding.sampler = mSampler;
     ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor));
     binding.sampler = nullptr;
+
+    // Setting the buffer to an error buffer is an error.
+    {
+        dawn::BufferDescriptor bufferDesc;
+        bufferDesc.size = 1024;
+        bufferDesc.usage = static_cast<dawn::BufferUsageBit>(0xFFFFFFFF);
+
+        dawn::Buffer errorBuffer;
+        ASSERT_DEVICE_ERROR(errorBuffer = device.CreateBuffer(&bufferDesc));
+
+        binding.buffer = errorBuffer;
+        ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor));
+        binding.buffer = nullptr;
+    }
 }
 
 // Check that a texture must have the correct usage
@@ -338,6 +383,25 @@
     ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, layout, {{0, buffer, 256, uint32_t(0) - uint32_t(256)}}));
 }
 
+// Test what happens when the layout is an error.
+TEST_F(BindGroupValidationTest, ErrorLayout) {
+    dawn::BindGroupLayout goodLayout = utils::MakeBindGroupLayout(device, {
+        {0, dawn::ShaderStageBit::Vertex, dawn::BindingType::UniformBuffer},
+    });
+
+    dawn::BindGroupLayout errorLayout;
+    ASSERT_DEVICE_ERROR(errorLayout = utils::MakeBindGroupLayout(device, {
+        {0, dawn::ShaderStageBit::Vertex, dawn::BindingType::UniformBuffer},
+        {0, dawn::ShaderStageBit::Vertex, dawn::BindingType::UniformBuffer},
+    }));
+
+    // Control case, creating with the good layout works
+    utils::MakeBindGroup(device, goodLayout, {{0, mUBO, 0, 256}});
+
+    // Control case, creating with the good layout works
+    ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, errorLayout, {{0, mUBO, 0, 256}}));
+}
+
 class BindGroupLayoutValidationTest : public ValidationTest {
 };