// Copyright 2023 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 #include #include "dawn/native/Instance.h" #include "dawn/utils/WireHelper.h" #include "dawn/webgpu.h" #include "gtest/gtest.h" static WGPUDevice lastBackendDevice = nullptr; class WireDeviceLifetimeTests : public testing::Test { public: WireDeviceLifetimeTests() : nativeProcs(BuildProcs()), wireHelper(utils::CreateWireHelper(nativeProcs, /* useWire */ true)) { WGPUInstanceDescriptor instanceDesc = {}; nativeInstance = std::make_unique(&instanceDesc); instance = wireHelper->RegisterInstance(nativeInstance->Get()); } protected: void SetUp() override { wgpu::RequestAdapterOptions options = {}; instance.RequestAdapter( &options, [](WGPURequestAdapterStatus status, WGPUAdapter cAdapter, const char*, void* userdata) { ASSERT_EQ(status, WGPURequestAdapterStatus_Success); *static_cast(userdata) = wgpu::Adapter::Acquire(cAdapter); }, &adapter); ASSERT_TRUE(wireHelper->FlushClient()); ASSERT_TRUE(wireHelper->FlushServer()); ASSERT_NE(adapter, nullptr); } const DawnProcTable nativeProcs; std::unique_ptr wireHelper; std::unique_ptr nativeInstance; wgpu::Instance instance; wgpu::Adapter adapter; private: // Set up the proc table which will save the latest WGPUDevice created from requestDevice in // static variable `lastBackendDevice`. This lets tests control the wire device and the native // backend device separately. DawnProcTable BuildProcs() { DawnProcTable nativeProcs = dawn::native::GetProcs(); nativeProcs.adapterRequestDevice = [](WGPUAdapter adapter, const WGPUDeviceDescriptor* desc, WGPURequestDeviceCallback callback, void* userdata) { using WrappedUserdata = std::pair; dawn::native::GetProcs().adapterRequestDevice( adapter, desc, [](WGPURequestDeviceStatus status, WGPUDevice device, char const* message, void* userdata) { lastBackendDevice = device; auto* wrappedUserdata = static_cast(userdata); wrappedUserdata->first(status, device, message, wrappedUserdata->second); delete wrappedUserdata; }, new WrappedUserdata(callback, userdata)); }; return nativeProcs; } }; // Drop the device from the wire, then call its uncaptured error callback. // This is a regression test for crbug.com/1430221 where these operations caused a use-after-free. TEST_F(WireDeviceLifetimeTests, DeviceDroppedFromWireThenUncapturedErrorCallback) { wgpu::Device device; wgpu::DeviceDescriptor deviceDesc = {}; adapter.RequestDevice( &deviceDesc, [](WGPURequestDeviceStatus, WGPUDevice cDevice, const char*, void* userdata) { *static_cast(userdata) = wgpu::Device::Acquire(cDevice); }, &device); ASSERT_TRUE(wireHelper->FlushClient()); ASSERT_TRUE(wireHelper->FlushServer()); ASSERT_NE(device, nullptr); wgpu::BufferDescriptor bufferDesc = {}; bufferDesc.size = 128; bufferDesc.usage = wgpu::BufferUsage::Uniform; // Destroy the device. device.Destroy(); // Inject an error. device.InjectError(wgpu::ErrorType::Validation, "injected error"); // Drop the device, but keep the server-side device alive. // This prevents the callbacks from being flushed yet. WGPUDevice oldDevice = lastBackendDevice; nativeProcs.deviceReference(oldDevice); device = nullptr; // Request a new device. This overrides the wire's device-related data. adapter.RequestDevice( &deviceDesc, [](WGPURequestDeviceStatus, WGPUDevice cDevice, const char*, void* userdata) { *static_cast(userdata) = wgpu::Device::Acquire(cDevice); }, &device); ASSERT_TRUE(wireHelper->FlushClient()); ASSERT_TRUE(wireHelper->FlushServer()); ASSERT_NE(device, nullptr); // Finally, release the old retained device which will flush any of its outstanding // callbacks. nativeProcs.deviceRelease(oldDevice); } // Drop the device from the wire, then call its logging callback. // This is a regression test for crbug.com/1430221 where these operations caused a use-after-free. TEST_F(WireDeviceLifetimeTests, DeviceDroppedFromWireThenLoggingCallback) { wgpu::Device device; wgpu::DeviceDescriptor deviceDesc = {}; adapter.RequestDevice( &deviceDesc, [](WGPURequestDeviceStatus, WGPUDevice cDevice, const char*, void* userdata) { *static_cast(userdata) = wgpu::Device::Acquire(cDevice); }, &device); ASSERT_TRUE(wireHelper->FlushClient()); ASSERT_TRUE(wireHelper->FlushServer()); ASSERT_NE(device, nullptr); wgpu::ShaderModuleDescriptor shaderModuleDesc = {}; wgpu::ShaderModuleWGSLDescriptor wgslDesc = {}; shaderModuleDesc.nextInChain = &wgslDesc; wgslDesc.code = "@compute @workgroup_size(64) fn main() {}"; // Create a shader module so the transformed shaders are dumped. device.CreateShaderModule(&shaderModuleDesc); // Destroy the device. device.Destroy(); // Drop the device, but keep the server-side device alive. // This prevents the callbacks from being flushed yet. WGPUDevice oldDevice = lastBackendDevice; nativeProcs.deviceReference(oldDevice); device = nullptr; // Request a new device. This overrides the wire's device-related data. adapter.RequestDevice( &deviceDesc, [](WGPURequestDeviceStatus, WGPUDevice cDevice, const char*, void* userdata) { *static_cast(userdata) = wgpu::Device::Acquire(cDevice); }, &device); ASSERT_TRUE(wireHelper->FlushClient()); ASSERT_TRUE(wireHelper->FlushServer()); ASSERT_NE(device, nullptr); // Finally, release the old retained device which will flush any of its outstanding // callbacks. nativeProcs.deviceRelease(oldDevice); } // Drop the device from the wire, then call its lost callback. // This is a regression test for crbug.com/1430221 where these operations caused a use-after-free. TEST_F(WireDeviceLifetimeTests, DeviceDroppedFromWireThenLostCallback) { wgpu::Device device; wgpu::DeviceDescriptor deviceDesc = {}; adapter.RequestDevice( &deviceDesc, [](WGPURequestDeviceStatus, WGPUDevice cDevice, const char*, void* userdata) { *static_cast(userdata) = wgpu::Device::Acquire(cDevice); }, &device); ASSERT_TRUE(wireHelper->FlushClient()); ASSERT_TRUE(wireHelper->FlushServer()); ASSERT_NE(device, nullptr); // Drop the device, but keep the server-side device alive. // This prevents the callbacks from being flushed yet. WGPUDevice oldDevice = lastBackendDevice; nativeProcs.deviceReference(oldDevice); device = nullptr; // Destroy the device to enqueue calling the lost callback. nativeProcs.deviceDestroy(oldDevice); // Request a new device. This overrides the wire's device-related data. adapter.RequestDevice( &deviceDesc, [](WGPURequestDeviceStatus, WGPUDevice cDevice, const char*, void* userdata) { *static_cast(userdata) = wgpu::Device::Acquire(cDevice); }, &device); ASSERT_TRUE(wireHelper->FlushClient()); ASSERT_TRUE(wireHelper->FlushServer()); ASSERT_NE(device, nullptr); // Finally, release the old retained device which will flush any of its outstanding // callbacks. nativeProcs.deviceRelease(oldDevice); }