diff --git a/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc b/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc index 79f7c964b2493..3ced1005388c3 100644 --- a/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc +++ b/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc @@ -39,6 +39,7 @@ #include "ui/ozone/platform/wayland/host/wayland_connection.h" #include "ui/ozone/platform/wayland/host/wayland_frame_manager.h" #include "ui/ozone/platform/wayland/host/wayland_subsurface.h" +#include "ui/ozone/platform/wayland/host/wayland_window_manager.h" #include "ui/ozone/platform/wayland/host/wayland_zwp_linux_dmabuf.h" #include "ui/ozone/platform/wayland/test/mock_drm_syncobj_ioctl_wrapper.h" #include "ui/ozone/platform/wayland/test/mock_surface.h" @@ -2970,6 +2971,71 @@ TEST_P(WaylandBufferManagerViewportTest, ViewportDestinationInteger) { } } +// Regression test: WaylandBufferManagerHost::OnChannelDestroyed iterates a +// bare-pointer snapshot of all WaylandWindows and calls OnChannelDestroyed() +// on each. If processing one window synchronously destroys another (via the +// OnStateUpdate delegate callout, reachable when should_ack_swap_without_commit +// is set on a suspended window under video capture), the next iteration +// dereferences a freed WaylandWindow pointer. +TEST_P(WaylandBufferManagerTest, + OnChannelDestroyedSnapshotSurvivesWindowDeletion) { + // Create a second toplevel that will be destroyed synchronously from within + // the first window's OnChannelDestroyed() -> ... -> delegate()->OnStateUpdate + // call chain. + testing::NiceMock victim_delegate( + connection_.get()); + std::unique_ptr victim = CreateWaylandWindowWithParams( + PlatformWindowType::kWindow, gfx::Rect(0, 0, 100, 100), &victim_delegate); + ASSERT_TRUE(victim); + // Both windows must be tracked by the manager. + ASSERT_EQ(2u, connection_->window_manager()->GetAllWindows().size()); + // Iteration order in the flat_map is by widget id; window_ was created first. + ASSERT_LT(window_->GetWidget(), victim->GetWidget()); + + // Put |window_| into the state where WaylandWindow::OnChannelDestroyed -> + // WaylandFrameManager::MaybeProcessPendingFrame takes the + // should_ack_swap_without_commit_ branch and reaches delegate->OnStateUpdate. + // + // 1) Queue enough server-side configures to hit MAX_IN_FLIGHT_REQUESTS so + // the last one remains unapplied for MaybeApplyLatestStateRequest to pick + // up when OnSequencePoint(-1) runs. + WaylandWindow::WindowStates states; + states.is_activated = true; + for (int i = 1; i <= 4; ++i) { + window_->HandleToplevelConfigure(200 + i * 10, 200 + i * 10, states); + window_->HandleSurfaceConfigure(10 + i); + } + // 2) Video capture + suspended -> should_ack_swap_without_commit_ = true. + window_->SetVideoCapture(); + states.is_suspended = true; + window_->HandleToplevelConfigure(250, 250, states); + + // 3) Hook OnStateUpdate on |window_|'s delegate so that when it fires from + // inside the OnChannelDestroyed loop, it synchronously destroys the other + // window that is still referenced by the snapshot vector. + bool destroyed_from_callback = false; + delegate_.set_on_state_update_callback( + base::BindLambdaForTesting([&]() -> bool { + if (victim) { + victim.reset(); + destroyed_from_callback = true; + } + return false; + })); + + // Simulate GPU-process channel loss. The dangling snapshot loop will call + // OnChannelDestroyed() on the freed |victim| pointer without a per-iteration + // liveness check. + manager_host_->OnChannelDestroyed(); + + EXPECT_TRUE(destroyed_from_callback); + EXPECT_EQ(1u, connection_->window_manager()->GetAllWindows().size()); + + delegate_.set_on_state_update_callback({}); + manager_host_ = connection_->buffer_manager_host(); + DisableSyncOnTearDown(); +} + INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest, WaylandBufferManagerTest, Values(wl::ServerConfig{}));