diff --git a/gpu/command_buffer/service/shared_image/shared_image_copy_manager_unittest.cc b/gpu/command_buffer/service/shared_image/shared_image_copy_manager_unittest.cc index 5f57d3a8557ee..471000f921fce 100644 --- a/gpu/command_buffer/service/shared_image/shared_image_copy_manager_unittest.cc +++ b/gpu/command_buffer/service/shared_image/shared_image_copy_manager_unittest.cc @@ -20,6 +20,8 @@ #if BUILDFLAG(SKIA_USE_DAWN) #include "gpu/command_buffer/service/dawn_context_provider.h" +#include "third_party/dawn/include/dawn/dawn_proc.h" // nogncheck +#include "third_party/dawn/include/dawn/native/DawnNative.h" // nogncheck #endif namespace gpu { @@ -153,4 +155,89 @@ TEST_F(SharedImageCopyManagerTestWithGraphiteDawn, CopyUsingDawnStrategy) { } #endif // BUILDFLAG(SKIA_USE_DAWN) +#if BUILDFLAG(SKIA_USE_DAWN) +// Regression test for stack-use-after-return in +// DawnCopyStrategy::CopyFromTextureToBacking. +// +// CopyFromTextureToBacking registers a wgpu::CallbackMode::WaitAnyOnly MapAsync +// callback whose userdata is the address of a stack-local `bool success`, then +// returns early when WaitAny() != Success without firing or untracking the +// future. The MapAsyncEvent (still holding the dead stack pointer) is later +// fired from ~EventManager() at instance teardown, writing through the dead +// stack slot. +// +// In production the WaitAny != Success branch is reached when WaitAny returns +// TimedOut after vkWaitForFences yields VK_ERROR_DEVICE_LOST (a renderer can +// induce TDR via a long-running shader). This test reaches the identical +// early-return branch by creating an Instance without the TimedWaitAny feature +// so that Instance::WaitAny(..., UINT64_MAX) returns wgpu::WaitStatus::Error. +// +// Run under ASAN with ASAN_OPTIONS=detect_stack_use_after_return=1. +TEST(DawnCopyStrategyStackUAR, MapAsyncCallbackOutlivesStackOnWaitAnyFailure) { + dawnProcSetProcs(&dawn::native::GetProcs()); + + // Intentionally do NOT request InstanceFeatureName::TimedWaitAny so that + // InstanceBase::APIWaitAny() returns wgpu::WaitStatus::Error for the + // UINT64_MAX-timeout WaitAny inside CopyFromTextureToBacking, taking the + // `return false` path at dawn_copy_strategy.cc:349. + wgpu::InstanceDescriptor instance_desc = {}; + auto dawn_instance = + std::make_unique(&instance_desc); + + wgpu::RequestAdapterOptions adapter_options; + adapter_options.backendType = wgpu::BackendType::Vulkan; + std::vector adapters = + dawn_instance->EnumerateAdapters(&adapter_options); + if (adapters.empty()) { + GTEST_SKIP() << "No Vulkan adapter; set " + "VK_ICD_FILENAMES=/vk_swiftshader_icd.json"; + } + + wgpu::FeatureName kFeatures[] = {wgpu::FeatureName::DawnInternalUsages}; + wgpu::DeviceDescriptor device_desc; + device_desc.requiredFeatureCount = 1; + device_desc.requiredFeatures = kFeatures; + wgpu::Device device = + wgpu::Device::Acquire(adapters[0].CreateDevice(&device_desc)); + ASSERT_TRUE(device); + + // Source texture sized to match the backing; CopySrc so the + // CopyTextureToBuffer encode succeeds and we reach MapAsync. + wgpu::TextureDescriptor tex_desc; + tex_desc.size = {4, 4, 1}; + tex_desc.format = wgpu::TextureFormat::RGBA8Unorm; + tex_desc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; + wgpu::Texture texture = device.CreateTexture(&tex_desc); + ASSERT_TRUE(texture); + + // Destination backing: only format()/size() are consulted before the + // early-return on WaitAny failure. + auto backing = std::make_unique( + Mailbox::Generate(), + SharedImageInfo(viz::SinglePlaneFormat::kRGBA_8888, gfx::Size(4, 4), + gfx::ColorSpace::CreateSRGB(), kTopLeft_GrSurfaceOrigin, + kPremul_SkAlphaType, SHARED_IMAGE_USAGE_CPU_WRITE_ONLY, + "DawnCopyStrategyStackUAR"), + /*estimated_size=*/64); + + // Hits dawn_copy_strategy.cc:337-350: stack-local `bool success` is captured + // by the WaitAnyOnly MapAsync callback; WaitAny returns Error; function + // returns false; the MapAsyncEvent retains the now-dead `&success` and stays + // tracked in EventManager::mEvents. + bool result = + DawnCopyStrategy::CopyFromTextureToBacking(texture, backing.get(), device); + EXPECT_FALSE(result) << "WaitAny was expected to fail (no TimedWaitAny)"; + + // Drop every external ref so InstanceBase is destroyed in-process. + // ~EventManager iterates mEvents and calls EnsureComplete(Shutdown) on the + // pending WaitAnyOnly MapAsyncEvent, whose callback writes + // `*success = (status == Success)` through the dead stack pointer. + backing.reset(); + texture = nullptr; + device = nullptr; + adapters.clear(); + dawn_instance.reset(); // ~InstanceBase -> ~EventManager -> stale callback. +} +#endif // BUILDFLAG(SKIA_USE_DAWN) + } // namespace gpu