/* * Copyright (c) 2026, The Ladybird developers * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr u32 TEST_MAGIC = 0xCAFEF00D; constexpr int TARGET_MESSAGE_ID = 1; constexpr int OTHER_MESSAGE_ID = 2; class TestMessage final : public IPC::Message { public: explicit TestMessage(int id) : m_id(id) { } u32 endpoint_magic() const override { return TEST_MAGIC; } int message_id() const override { return m_id; } StringView message_name() const override { return "TestMessage"sv; } ErrorOr encode() const override { return IPC::MessageBuffer {}; } private: int m_id { 0 }; }; class CountingStub final : public IPC::Stub { public: u32 magic() const override { return TEST_MAGIC; } ByteString name() const override { return "CountingStub"; } ErrorOr> handle(NonnullOwnPtr) override { ++m_handle_count; return OwnPtr {}; } size_t handle_count() const { return m_handle_count; } private: size_t m_handle_count { 0 }; }; class TestConnection final : public IPC::ConnectionBase { C_OBJECT(TestConnection); public: void inject_unprocessed_message(NonnullOwnPtr message) { m_unprocessed_messages.append(move(message)); } OwnPtr call_wait_for_specific_endpoint_message_impl(u32 endpoint_magic, int message_id) { return wait_for_specific_endpoint_message_impl(endpoint_magic, message_id); } protected: OwnPtr try_parse_message(ReadonlyBytes, Queue&) override { return nullptr; } private: TestConnection(IPC::Stub& stub, NonnullOwnPtr transport) : IPC::ConnectionBase(stub, move(transport), TEST_MAGIC) { } }; } // Regression test for #9582. wait_for_specific_endpoint_message_impl is reachable from any sync IPC call, including // from inside a constructor whose members are still being initialized (e.g., PageHost's construction issues a sync // allocate_compositor_context_id IPC call before ConnectionFromClient::m_page_host has been assigned). If the peer // disconnects before responding, the failure path must not synchronously dispatch unrelated queued messages. That can // reenter arbitrary handlers from a context where caller state is only partially built. TEST_CASE(sync_wait_failure_does_not_dispatch_queued_messages) { Core::EventLoop loop; int fds[2] = {}; MUST(Core::System::socketpair(AF_LOCAL, SOCK_STREAM, 0, fds)); auto local_socket = TRY_OR_FAIL(Core::LocalSocket::adopt_fd(fds[0])); auto peer_socket = TRY_OR_FAIL(Core::LocalSocket::adopt_fd(fds[1])); MUST(local_socket->set_blocking(false)); MUST(peer_socket->set_blocking(false)); auto transport = make(move(local_socket)); CountingStub stub; auto connection = TestConnection::construct(stub, move(transport)); connection->inject_unprocessed_message(make(OTHER_MESSAGE_ID)); peer_socket->close(); auto response = connection->call_wait_for_specific_endpoint_message_impl(TEST_MAGIC, TARGET_MESSAGE_ID); EXPECT(!response); EXPECT_EQ(stub.handle_count(), 0u); }