diff --git a/src/boost_process_compat.h b/src/compat/boost_process.h similarity index 92% rename from src/boost_process_compat.h rename to src/compat/boost_process.h index d5441db3a12..47907e97695 100644 --- a/src/boost_process_compat.h +++ b/src/compat/boost_process.h @@ -1,5 +1,5 @@ /** - * @file src/boost_process_compat.h + * @file src/compat/boost_process.h * @brief Includes Boost.Process while isolating third-party deprecation diagnostics. */ #pragma once diff --git a/src/compat/moonlight.h b/src/compat/moonlight.h new file mode 100644 index 00000000000..e2896aa6cdc --- /dev/null +++ b/src/compat/moonlight.h @@ -0,0 +1,36 @@ +/** + * @file src/compat/moonlight.h + * @brief Import Moonlight's C declarations without changing C++ errno definitions. + */ +#pragma once + +// standard includes +// Load the CRT definitions before saving them, even when this is the first include. +#include + +// PlatformSockets.h maps these names to Winsock values for Moonlight's C code. +// Let that code see its mappings, but do not leak them into Boost or the C++ library. +#ifdef _WIN32 + #pragma push_macro("EAGAIN") + #pragma push_macro("EINTR") + #pragma push_macro("EWOULDBLOCK") + #pragma push_macro("EINPROGRESS") + #pragma push_macro("ETIMEDOUT") + #pragma push_macro("ECONNREFUSED") + #pragma push_macro("EMSGSIZE") +#endif + +// lib includes +extern "C" { +#include +} + +#ifdef _WIN32 + #pragma pop_macro("EMSGSIZE") + #pragma pop_macro("ECONNREFUSED") + #pragma pop_macro("ETIMEDOUT") + #pragma pop_macro("EINPROGRESS") + #pragma pop_macro("EWOULDBLOCK") + #pragma pop_macro("EINTR") + #pragma pop_macro("EAGAIN") +#endif diff --git a/src/platform/common.h b/src/platform/common.h index 62961d7a310..7919f49d105 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -20,7 +20,7 @@ // local includes #ifndef _WIN32 - #include "src/boost_process_compat.h" + #include "src/compat/boost_process.h" #endif #include "src/config.h" #include "src/logging.h" diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index b89b9b97dd0..143075b534c 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -59,7 +59,7 @@ // local includes #include "graphics.h" #include "misc.h" -#include "src/boost_process_compat.h" +#include "src/compat/boost_process.h" #include "src/config.h" #include "src/entry_handler.h" #include "src/globals.h" diff --git a/src/platform/macos/misc.mm b/src/platform/macos/misc.mm index 9fde3784788..e4ed3c27872 100644 --- a/src/platform/macos/misc.mm +++ b/src/platform/macos/misc.mm @@ -38,7 +38,7 @@ // local includes #include "misc.h" -#include "src/boost_process_compat.h" +#include "src/compat/boost_process.h" #include "src/config.h" #include "src/entry_handler.h" #include "src/logging.h" diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 2a013367107..f97b6933d7a 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -13,7 +13,7 @@ // local includes required before library includes // Boost.Process must be included before MinHook pulls in Windows.h so Boost.Asio sees WinSock2.h first. -#include "src/boost_process_compat.h" +#include "src/compat/boost_process.h" // lib includes #include diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index 9556275828d..d68b928d0ed 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -30,7 +30,7 @@ #include // local includes required before platform includes -#include "src/boost_process_compat.h" +#include "src/compat/boost_process.h" // platform includes // prevent clang format from "optimizing" the header include order diff --git a/src/process.h b/src/process.h index 132178be709..774b0bacd26 100644 --- a/src/process.h +++ b/src/process.h @@ -20,7 +20,7 @@ #include "config.h" #include "platform/common.h" #include "rtsp.h" -#include "src/boost_process_compat.h" +#include "src/compat/boost_process.h" #include "utility.h" /** diff --git a/src/rtsp.cpp b/src/rtsp.cpp index 71253733ce7..7f8c97d792d 100644 --- a/src/rtsp.cpp +++ b/src/rtsp.cpp @@ -4,8 +4,9 @@ */ #define BOOST_BIND_GLOBAL_PLACEHOLDERS +#include "compat/moonlight.h" + extern "C" { -#include #include } diff --git a/src/stream.cpp b/src/stream.cpp index 2f2db0c2fc9..be458d50161 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -17,11 +17,8 @@ #include #include -extern "C" { - // clang-format off -#include - // clang-format on -} +// Keep this separate so clang-format preserves the include order. +#include "compat/moonlight.h" // local includes #include "config.h" diff --git a/tests/integration/test_external_commands.cpp b/tests/integration/test_external_commands.cpp index adf730a9041..997ae166c08 100644 --- a/tests/integration/test_external_commands.cpp +++ b/tests/integration/test_external_commands.cpp @@ -13,7 +13,7 @@ #include // local includes -#include "src/boost_process_compat.h" +#include "src/compat/boost_process.h" #include "src/platform/common.h" // Test data structure for parameterized testing diff --git a/tests/unit/moonlight_common_first_include.cpp b/tests/unit/moonlight_common_first_include.cpp new file mode 100644 index 00000000000..e12018bc13c --- /dev/null +++ b/tests/unit/moonlight_common_first_include.cpp @@ -0,0 +1,69 @@ +/** + * @file tests/unit/moonlight_common_first_include.cpp + * @brief Capture errno when the Moonlight wrapper is the first include in its translation unit. + */ + +// local includes +#include + +namespace { + // Capture before any other include. Missing macros use zero only to keep a + // broken wrapper testable at runtime; the separate completeness flag must pass. + constexpr int errno_after_first_include[7] /* NOSONAR(cpp:S5945): No extra headers before the errno snapshot. */ { +#ifdef EAGAIN + EAGAIN, +#else + 0, +#endif +#ifdef EINTR + EINTR, +#else + 0, +#endif +#ifdef EWOULDBLOCK + EWOULDBLOCK, +#else + 0, +#endif +#ifdef EINPROGRESS + EINPROGRESS, +#else + 0, +#endif +#ifdef ETIMEDOUT + ETIMEDOUT, +#else + 0, +#endif +#ifdef ECONNREFUSED + ECONNREFUSED, +#else + 0, +#endif +#ifdef EMSGSIZE + EMSGSIZE +#else + 0 +#endif + }; ///< Errno values captured immediately after the first include. + +#if defined(EAGAIN) && defined(EINTR) && defined(EWOULDBLOCK) && defined(EINPROGRESS) && defined(ETIMEDOUT) && defined(ECONNREFUSED) && defined(EMSGSIZE) + constexpr bool errno_complete = true; ///< Every required errno name was defined. +#else + constexpr bool errno_complete = false; ///< At least one required errno name was absent. +#endif +} // namespace + +namespace moonlight_common_tests { + /** + * @brief Copy the first-include errno snapshot without sharing library types across translation units. + * @param values Destination for seven errno values. + * @return Whether all seven errno names were defined. + */ + bool first_include_errno(int *values) { + for (int index = 0; index < 7; ++index) { + values[index] = errno_after_first_include[index]; + } + return errno_complete; + } +} // namespace moonlight_common_tests diff --git a/tests/unit/test_moonlight_common.cpp b/tests/unit/test_moonlight_common.cpp new file mode 100644 index 00000000000..de69c82e311 --- /dev/null +++ b/tests/unit/test_moonlight_common.cpp @@ -0,0 +1,111 @@ +/** + * @file tests/unit/test_moonlight_common.cpp + * @brief Verify that importing Moonlight does not change C++ errno constants. + */ + +// The shared test header includes Boost. Keep it after the errno snapshots and +// the explicit Boost include so it cannot mask this regression. + +// standard includes +#include +#include + +namespace { + constexpr std::array errno_before_moonlight { + EAGAIN, + EINTR, + EWOULDBLOCK, + EINPROGRESS, + ETIMEDOUT, + ECONNREFUSED, + EMSGSIZE + }; ///< CRT errno values before including Moonlight. +} + +// local includes +// Capture errno immediately after including Moonlight. +#include // NOSONAR(cpp:S954): The baseline must precede this include. + +namespace { + constexpr std::array errno_after_moonlight { + EAGAIN, + EINTR, + EWOULDBLOCK, + EINPROGRESS, + ETIMEDOUT, + ECONNREFUSED, + EMSGSIZE + }; ///< Errno values immediately after including Moonlight. +} + +// lib includes +// Boost must be parsed after the wrapper for this regression test. +#include + +// test includes +#include "../tests_common.h" + +namespace moonlight_common_tests { + /** + * @brief Copy the first-include errno snapshot without sharing library types across translation units. + * @param values Destination for seven errno values in the baseline array's order. + * @return Whether all seven errno names were defined. + */ + bool first_include_errno(int *values); +} // namespace moonlight_common_tests + +/** + * @brief Including Moonlight preserves the CRT errno values. + */ +TEST(MoonlightCommonTests, PreservesCrtErrno) { + EXPECT_EQ(errno_before_moonlight, errno_after_moonlight); +} + +/** + * @brief Boost's error constants match the original CRT errno values. + */ +TEST(MoonlightCommonTests, PreservesBoostErrno) { + const std::array boost_errno { + boost::system::errc::resource_unavailable_try_again, + boost::system::errc::interrupted, + boost::system::errc::operation_would_block, + boost::system::errc::operation_in_progress, + boost::system::errc::timed_out, + boost::system::errc::connection_refused, + boost::system::errc::message_size + }; + EXPECT_EQ(errno_before_moonlight, boost_errno); +} + +/** + * @brief The wrapper initializes errno even when it is the first include. + */ +TEST(MoonlightCommonTests, FirstIncludePreservesCrtErrno) { + std::array first_include_values {}; + EXPECT_TRUE(moonlight_common_tests::first_include_errno(first_include_values.data())); + EXPECT_EQ(errno_before_moonlight, first_include_values); +} + +/** + * @brief Native connection-refused errors match Boost's generic condition. + */ +TEST(MoonlightCommonTests, ConnectionRefusedCondition) { +#ifdef _WIN32 + const boost::system::error_code ec {WSAECONNREFUSED, boost::system::system_category()}; +#else + const boost::system::error_code ec {ECONNREFUSED, boost::system::system_category()}; +#endif + EXPECT_EQ(ec, boost::system::errc::connection_refused); +} + +/** + * @brief Native timeouts match Boost's generic condition. + */ +TEST(MoonlightCommonTests, TimedOutCondition) { +#ifdef _WIN32 + const boost::system::error_code ec {WSAETIMEDOUT, boost::system::system_category()}; +#else + const boost::system::error_code ec {ETIMEDOUT, boost::system::system_category()}; +#endif + EXPECT_EQ(ec, boost::system::errc::timed_out); +}