Skip to content

Conversation

@footballhead
Copy link
Collaborator

@footballhead footballhead commented Dec 5, 2025

Running with asan produced a heap-use-after-free.

Building and running:

cmake . \
  -B build-asan \
  -G Ninja \
  -DCMAKE_BUILD_TYPE=Debug \
  -DCMAKE_CXX_FLAGS="-g -fsanitize=address -fno-omit-frame-pointer" \
  -DCMAKE_C_FLAGS="-g -fsanitize=address -fno-omit-frame-pointer" \
  -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address" \
  -DCMAKE_CXX_COMPILER=clang++ \
  -DCMAKE_C_COMPILER=clang

cmake --build build-asan --target vk_sample_04_cube

./build-asan/bin/vk_sample_04_cube --frame-count=2

Output:

==1385441==ERROR: AddressSanitizer: heap-use-after-free on address 0x511000204bc8 at pc 0x559f52c3fdb1 bp 0x7ffc93da0030 sp 0x7ffc93da0028
READ of size 4 at 0x511000204bc8 thread T0
    #0 0x559f52c3fdb0 in ppx::grfx::OwnershipTrait::GetOwnership() const /usr/local/google/home/hitchens/git/bigwheels/include/ppx/grfx/grfx_config.h:215:51
    #1 0x559f52c7edf7 in ppx::grfx::RenderPass::Destroy() /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_render_pass.cpp:519:30
    #2 0x559f52ba1c72 in void ppx::grfx::Device::DestroyAllObjects<ppx::grfx::RenderPass>(std::vector<ObjPtr<ppx::grfx::RenderPass>, std::allocator<ObjPtr<ppx::grfx::RenderPass>>>&) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_device.cpp:162:17
    #3 0x559f52b93c46 in ppx::grfx::Device::Destroy() /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_device.cpp:48:5
    #4 0x559f52c4f079 in void ppx::grfx::Instance::DestroyObject<ppx::grfx::Device, std::vector<ObjPtr<ppx::grfx::Device>, std::allocator<ObjPtr<ppx::grfx::Device>>>>(std::vector<ObjPtr<ppx::grfx::Device>, std::allocator<ObjPtr<ppx::grfx::Device>>>&, ppx::grfx::Device const*) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_instance.cpp:129:13
    #5 0x559f52c4cd19 in ppx::grfx::Instance::DestroyDevice(ppx::grfx::Device const*) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_instance.cpp:191:5
    #6 0x559f52a3b357 in ppx::Application::ShutdownGrfx() /usr/local/google/home/hitchens/git/bigwheels/src/ppx/application.cpp:389:24
    #7 0x559f52a4ad28 in ppx::Application::Run(int, char**) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/application.cpp:1368:5
    #8 0x559f52a326f6 in main /usr/local/google/home/hitchens/git/bigwheels/projects/sample_04_cube/main.cpp:17:1
    #9 0x7f3396a33ca7 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #10 0x7f3396a33d64 in __libc_start_main csu/../csu/libc-start.c:360:3
    #11 0x559f5292e6c0 in _start (/usr/local/google/home/hitchens/git/bigwheels/build-asan/bin/vk_sample_04_cube+0xf16c0) (BuildId: fbd6326cbe030704a398c228e623022d5d4a7629)

0x511000204bc8 is located 8 bytes inside of 240-byte region [0x511000204bc0,0x511000204cb0)
freed by thread T0 here:
    #0 0x559f52a108f6 in operator delete(void*, unsigned long) (/usr/local/google/home/hitchens/git/bigwheels/build-asan/bin/vk_sample_04_cube+0x1d38f6) (BuildId: fbd6326cbe030704a398c228e623022d5d4a7629)
    #1 0x559f530878b6 in ppx::grfx::vk::Image::~Image() /usr/local/google/home/hitchens/git/bigwheels/include/ppx/grfx/vk/vk_image.h:30:22
    #2 0x559f52bacfc5 in void ppx::grfx::Device::DestroyObject<ppx::grfx::Image, std::vector<ObjPtr<ppx::grfx::Image>, std::allocator<ObjPtr<ppx::grfx::Image>>>>(std::vector<ObjPtr<ppx::grfx::Image>, std::allocator<ObjPtr<ppx::grfx::Image>>>&, ppx::grfx::Image const*) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_device.cpp:151:5
    #3 0x559f52b994f9 in ppx::grfx::Device::DestroyImage(ppx::grfx::Image const*) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_device.cpp:418:5
    #4 0x559f52c8c7aa in ppx::grfx::Swapchain::DestroyColorImages() /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_swapchain.cpp:151:26
    #5 0x559f52c8bad8 in ppx::grfx::Swapchain::Destroy() /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_swapchain.cpp:126:5
    #6 0x559f52bb3ab9 in void ppx::grfx::Device::DestroyObject<ppx::grfx::Swapchain, std::vector<ObjPtr<ppx::grfx::Swapchain>, std::allocator<ObjPtr<ppx::grfx::Swapchain>>>>(std::vector<ObjPtr<ppx::grfx::Swapchain>, std::allocator<ObjPtr<ppx::grfx::Swapchain>>>&, ppx::grfx::Swapchain const*) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_device.cpp:148:13
    #7 0x559f52b9d609 in ppx::grfx::Device::DestroySwapchain(ppx::grfx::Swapchain const*) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_device.cpp:597:5
    #8 0x559f52a3ac93 in ppx::Application::DestroySwapchains() /usr/local/google/home/hitchens/git/bigwheels/src/ppx/application.cpp:329:18
    #9 0x559f52a3b311 in ppx::Application::ShutdownGrfx() /usr/local/google/home/hitchens/git/bigwheels/src/ppx/application.cpp:386:9
    #10 0x559f52a4ad28 in ppx::Application::Run(int, char**) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/application.cpp:1368:5
    #11 0x559f52a326f6 in main /usr/local/google/home/hitchens/git/bigwheels/projects/sample_04_cube/main.cpp:17:1
    #12 0x7f3396a33ca7 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

previously allocated by thread T0 here:
    #0 0x559f52a0fc71 in operator new(unsigned long) (/usr/local/google/home/hitchens/git/bigwheels/build-asan/bin/vk_sample_04_cube+0x1d2c71) (BuildId: fbd6326cbe030704a398c228e623022d5d4a7629)
    #1 0x559f53051709 in ppx::grfx::vk::Device::AllocateObject(ppx::grfx::Image**) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/vk/vk_device.cpp:939:26
    #2 0x559f52bac968 in ppx::Result ppx::grfx::Device::CreateObject<ppx::grfx::Image, ppx::grfx::ImageCreateInfo, std::vector<ObjPtr<ppx::grfx::Image>, std::allocator<ObjPtr<ppx::grfx::Image>>>>(ppx::grfx::ImageCreateInfo const*, std::vector<ObjPtr<ppx::grfx::Image>, std::allocator<ObjPtr<ppx::grfx::Image>>>&, ppx::grfx::Image**) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_device.cpp:106:24
    #3 0x559f52b9935c in ppx::grfx::Device::CreateImage(ppx::grfx::ImageCreateInfo const*, ppx::grfx::Image**) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_device.cpp:412:12
    #4 0x559f52cf0556 in ppx::grfx::vk::Swapchain::CreateApiObjects(ppx::grfx::SwapchainCreateInfo const*) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/vk/vk_swapchain.cpp:519:50
    #5 0x559f52c8e88e in ppx::grfx::CreateDestroyTraits<ppx::grfx::SwapchainCreateInfo>::Create(ppx::grfx::SwapchainCreateInfo const*) /usr/local/google/home/hitchens/git/bigwheels/include/ppx/grfx/grfx_config.h:240:30
    #6 0x559f52c875f3 in ppx::grfx::Swapchain::Create(ppx::grfx::SwapchainCreateInfo const*) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_swapchain.cpp:30:68
    #7 0x559f52bb356e in ppx::Result ppx::grfx::Device::CreateObject<ppx::grfx::Swapchain, ppx::grfx::SwapchainCreateInfo, std::vector<ObjPtr<ppx::grfx::Swapchain>, std::allocator<ObjPtr<ppx::grfx::Swapchain>>>>(ppx::grfx::SwapchainCreateInfo const*, std::vector<ObjPtr<ppx::grfx::Swapchain>, std::allocator<ObjPtr<ppx::grfx::Swapchain>>>&, ppx::grfx::Swapchain**) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_device.cpp:113:23
    #8 0x559f52b9d46c in ppx::grfx::Device::CreateSwapchain(ppx::grfx::SwapchainCreateInfo const*, ppx::grfx::Swapchain**) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_device.cpp:591:12
    #9 0x559f52a3a5e8 in ppx::Application::CreateSwapchains() /usr/local/google/home/hitchens/git/bigwheels/src/ppx/application.cpp:311:46
    #10 0x559f52a4a73f in ppx::Application::Run(int, char**) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/application.cpp:1316:14
    #11 0x559f52a326f6 in main /usr/local/google/home/hitchens/git/bigwheels/projects/sample_04_cube/main.cpp:17:1
    #12 0x7f3396a33ca7 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

In this case, CubeApp (vk_sample_04_cube) was creating a RenderPass object using Image objects from the Application's Swapchain. If the Application destroyed the swapchain first, Device still held the RenderPass objects that CubeApp made but with stale pointers to now-deleted Image objects.

Since projects and benchmarks don't clean up after themselves, I preferred to centralize cleanup into Device to align with current expectations.

if (mInstance) {
DestroySwapchains();
// Ensure resources are not being used
mDevice->WaitIdle();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was copied from DestroySwapchains(). I'm not sure if it's entirely necessary.

// Only reset the swapchains; let the device destroy them. Otherwise, we encounter
// heap-use-after-free when derived classes create RenderPass objects that use swapchain
// images but don't destroy them (with the assumption that cleanup will eventually happen).
mSwapchains.clear();
Copy link
Collaborator Author

@footballhead footballhead Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was looking at DestroySwapchains() and was debating keeping the mSwapchains[i].Reset(). However, no one takes SwapchainPtr* or ObjPtrRef<Swapchain>. And if they did, those objects would be invalidated by the mSwapchains.clear() anyway. It seemed unnecessary to keep.

@footballhead
Copy link
Collaborator Author

Seems like there are internal test failures and some Windows test failures. I'll look into them

@footballhead footballhead marked this pull request as draft December 5, 2025 22:49
Running with asan produced a heap-use-after-free

```
==1385441==ERROR: AddressSanitizer: heap-use-after-free on address 0x511000204bc8 at pc 0x559f52c3fdb1 bp 0x7ffc93da0030 sp 0x7ffc93da0028
READ of size 4 at 0x511000204bc8 thread T0
    #0 0x559f52c3fdb0 in ppx::grfx::OwnershipTrait::GetOwnership() const /usr/local/google/home/hitchens/git/bigwheels/include/ppx/grfx/grfx_config.h:215:51
    google#1 0x559f52c7edf7 in ppx::grfx::RenderPass::Destroy() /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_render_pass.cpp:519:30
    google#2 0x559f52ba1c72 in void ppx::grfx::Device::DestroyAllObjects<ppx::grfx::RenderPass>(std::vector<ObjPtr<ppx::grfx::RenderPass>, std::allocator<ObjPtr<ppx::grfx::RenderPass>>>&) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_device.cpp:162:17
    google#3 0x559f52b93c46 in ppx::grfx::Device::Destroy() /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_device.cpp:48:5
    google#4 0x559f52c4f079 in void ppx::grfx::Instance::DestroyObject<ppx::grfx::Device, std::vector<ObjPtr<ppx::grfx::Device>, std::allocator<ObjPtr<ppx::grfx::Device>>>>(std::vector<ObjPtr<ppx::grfx::Device>, std::allocator<ObjPtr<ppx::grfx::Device>>>&, ppx::grfx::Device const*) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_instance.cpp:129:13
    google#5 0x559f52c4cd19 in ppx::grfx::Instance::DestroyDevice(ppx::grfx::Device const*) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_instance.cpp:191:5
    google#6 0x559f52a3b357 in ppx::Application::ShutdownGrfx() /usr/local/google/home/hitchens/git/bigwheels/src/ppx/application.cpp:389:24
    google#7 0x559f52a4ad28 in ppx::Application::Run(int, char**) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/application.cpp:1368:5
    google#8 0x559f52a326f6 in main /usr/local/google/home/hitchens/git/bigwheels/projects/sample_04_cube/main.cpp:17:1
    google#9 0x7f3396a33ca7 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    google#10 0x7f3396a33d64 in __libc_start_main csu/../csu/libc-start.c:360:3
    google#11 0x559f5292e6c0 in _start (/usr/local/google/home/hitchens/git/bigwheels/build-asan/bin/vk_sample_04_cube+0xf16c0) (BuildId: fbd6326cbe030704a398c228e623022d5d4a7629)

0x511000204bc8 is located 8 bytes inside of 240-byte region [0x511000204bc0,0x511000204cb0)
freed by thread T0 here:
    #0 0x559f52a108f6 in operator delete(void*, unsigned long) (/usr/local/google/home/hitchens/git/bigwheels/build-asan/bin/vk_sample_04_cube+0x1d38f6) (BuildId: fbd6326cbe030704a398c228e623022d5d4a7629)
    google#1 0x559f530878b6 in ppx::grfx::vk::Image::~Image() /usr/local/google/home/hitchens/git/bigwheels/include/ppx/grfx/vk/vk_image.h:30:22
    google#2 0x559f52bacfc5 in void ppx::grfx::Device::DestroyObject<ppx::grfx::Image, std::vector<ObjPtr<ppx::grfx::Image>, std::allocator<ObjPtr<ppx::grfx::Image>>>>(std::vector<ObjPtr<ppx::grfx::Image>, std::allocator<ObjPtr<ppx::grfx::Image>>>&, ppx::grfx::Image const*) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_device.cpp:151:5
    google#3 0x559f52b994f9 in ppx::grfx::Device::DestroyImage(ppx::grfx::Image const*) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_device.cpp:418:5
    google#4 0x559f52c8c7aa in ppx::grfx::Swapchain::DestroyColorImages() /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_swapchain.cpp:151:26
    google#5 0x559f52c8bad8 in ppx::grfx::Swapchain::Destroy() /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_swapchain.cpp:126:5
    google#6 0x559f52bb3ab9 in void ppx::grfx::Device::DestroyObject<ppx::grfx::Swapchain, std::vector<ObjPtr<ppx::grfx::Swapchain>, std::allocator<ObjPtr<ppx::grfx::Swapchain>>>>(std::vector<ObjPtr<ppx::grfx::Swapchain>, std::allocator<ObjPtr<ppx::grfx::Swapchain>>>&, ppx::grfx::Swapchain const*) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_device.cpp:148:13
    google#7 0x559f52b9d609 in ppx::grfx::Device::DestroySwapchain(ppx::grfx::Swapchain const*) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_device.cpp:597:5
    google#8 0x559f52a3ac93 in ppx::Application::DestroySwapchains() /usr/local/google/home/hitchens/git/bigwheels/src/ppx/application.cpp:329:18
    google#9 0x559f52a3b311 in ppx::Application::ShutdownGrfx() /usr/local/google/home/hitchens/git/bigwheels/src/ppx/application.cpp:386:9
    google#10 0x559f52a4ad28 in ppx::Application::Run(int, char**) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/application.cpp:1368:5
    google#11 0x559f52a326f6 in main /usr/local/google/home/hitchens/git/bigwheels/projects/sample_04_cube/main.cpp:17:1
    google#12 0x7f3396a33ca7 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

previously allocated by thread T0 here:
    #0 0x559f52a0fc71 in operator new(unsigned long) (/usr/local/google/home/hitchens/git/bigwheels/build-asan/bin/vk_sample_04_cube+0x1d2c71) (BuildId: fbd6326cbe030704a398c228e623022d5d4a7629)
    google#1 0x559f53051709 in ppx::grfx::vk::Device::AllocateObject(ppx::grfx::Image**) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/vk/vk_device.cpp:939:26
    google#2 0x559f52bac968 in ppx::Result ppx::grfx::Device::CreateObject<ppx::grfx::Image, ppx::grfx::ImageCreateInfo, std::vector<ObjPtr<ppx::grfx::Image>, std::allocator<ObjPtr<ppx::grfx::Image>>>>(ppx::grfx::ImageCreateInfo const*, std::vector<ObjPtr<ppx::grfx::Image>, std::allocator<ObjPtr<ppx::grfx::Image>>>&, ppx::grfx::Image**) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_device.cpp:106:24
    google#3 0x559f52b9935c in ppx::grfx::Device::CreateImage(ppx::grfx::ImageCreateInfo const*, ppx::grfx::Image**) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_device.cpp:412:12
    google#4 0x559f52cf0556 in ppx::grfx::vk::Swapchain::CreateApiObjects(ppx::grfx::SwapchainCreateInfo const*) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/vk/vk_swapchain.cpp:519:50
    google#5 0x559f52c8e88e in ppx::grfx::CreateDestroyTraits<ppx::grfx::SwapchainCreateInfo>::Create(ppx::grfx::SwapchainCreateInfo const*) /usr/local/google/home/hitchens/git/bigwheels/include/ppx/grfx/grfx_config.h:240:30
    google#6 0x559f52c875f3 in ppx::grfx::Swapchain::Create(ppx::grfx::SwapchainCreateInfo const*) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_swapchain.cpp:30:68
    google#7 0x559f52bb356e in ppx::Result ppx::grfx::Device::CreateObject<ppx::grfx::Swapchain, ppx::grfx::SwapchainCreateInfo, std::vector<ObjPtr<ppx::grfx::Swapchain>, std::allocator<ObjPtr<ppx::grfx::Swapchain>>>>(ppx::grfx::SwapchainCreateInfo const*, std::vector<ObjPtr<ppx::grfx::Swapchain>, std::allocator<ObjPtr<ppx::grfx::Swapchain>>>&, ppx::grfx::Swapchain**) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_device.cpp:113:23
    google#8 0x559f52b9d46c in ppx::grfx::Device::CreateSwapchain(ppx::grfx::SwapchainCreateInfo const*, ppx::grfx::Swapchain**) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/grfx/grfx_device.cpp:591:12
    google#9 0x559f52a3a5e8 in ppx::Application::CreateSwapchains() /usr/local/google/home/hitchens/git/bigwheels/src/ppx/application.cpp:311:46
    google#10 0x559f52a4a73f in ppx::Application::Run(int, char**) /usr/local/google/home/hitchens/git/bigwheels/src/ppx/application.cpp:1316:14
    google#11 0x559f52a326f6 in main /usr/local/google/home/hitchens/git/bigwheels/projects/sample_04_cube/main.cpp:17:1
    google#12 0x7f3396a33ca7 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
```

In this case, CubeApp (vk_sample_04_cube) was creating RenderPass
objects using Image objects from the Application-owned swapchain. If the
Application destroyed the swapchain first, Device still held the
RenderPass objects that CubeApp made but with references to now-deleted
Image objects.

Since projects and benchmarks don't clean up after themselves, I
preferred to centralize cleanup into Device to align with current
expectations.
@footballhead footballhead force-pushed the fix_heap-use-after-free branch from 2c6d166 to b952c42 Compare January 16, 2026 20:42
@footballhead
Copy link
Collaborator Author

This breaks --headless for both VK and DX12

@footballhead
Copy link
Collaborator Author

I'm no longer convinced that this is the right approach. Will close while I think about this issue some more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant