diff --git a/CMakeLists.txt b/CMakeLists.txt index 512d6ec..2ad9bbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,26 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") string(REGEX REPLACE "([/-])EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") endif() +# MSVC: force the MS STL off its checked-iterator debug mode in every +# target of this build (engine, tests, vendored dependencies). CMake's +# default MSVC Debug flags define _DEBUG, which makes the STL default to +# _ITERATOR_DEBUG_LEVEL=2: in that mode every STL container instance +# gets a heap-allocated iterator-proxy object in its constructor, so the +# engine's zero-allocation invariants (the M1-ECS-03 churn test, the +# M1-HEAD-01 headless run test) would measure a windows-msvc-only +# baseline that the other P0 platforms never enable — GCC/Clang/AppleClang +# compile without checked iterators by default. The define is build-wide +# (not per-target) because the MS STL enforces one _ITERATOR_DEBUG_LEVEL +# per linked binary: mixing engine targets compiled with 0 against +# GTest objects compiled with 2 fails the link (LNK2038). Disabling it +# build-wide makes the Windows Debug build measure the same allocation +# behavior as the rest of the P0 matrix. The proxies are an STL debug +# aid, not engine behavior — they are allocated in container setup, +# never on hot paths. +if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + add_compile_definitions(_ITERATOR_DEBUG_LEVEL=0) +endif() + # Both the static (default) and shared (LAIGE_BUILD_SHARED) library variants # are position-independent so they link identically into games and tools # (NFR-8.9). @@ -141,6 +161,9 @@ laige_deps_verify_lock() # yvals.h _TRY_BEGIN/_CATCH) on the _HAS_EXCEPTIONS macro, not on the /EH # flags — with /EHs- alone it would still emit catch blocks that the # compiler cannot unwind (cl C4530, fatal under /WX). +# _ITERATOR_DEBUG_LEVEL=0 (see the MSVC block above) keeps the Windows +# Debug build's STL container allocation behavior aligned with the other +# P0 platforms. # # The warning/language flags are PRIVATE: they govern this target's own # translation units and must not leak into consumers (CPP-010) — a game diff --git a/tools/run/laige-run.cpp b/tools/run/laige-run.cpp index 79e4176..d1d03fb 100644 --- a/tools/run/laige-run.cpp +++ b/tools/run/laige-run.cpp @@ -54,6 +54,10 @@ #include #include +#if defined(_MSC_VER) +#include // _SH_DENYNO: plain-fopen sharing for the _fsopen below +#endif + #include "laige/errors.h" #include "laige/json.h" #include "laige/logging.h" @@ -112,11 +116,30 @@ bool parseTicks(std::string_view text, std::uint64_t* out) { return true; } +// Portable config-file open (CPP-009 compile-time platform boundary — +// the logging.cpp precedent). MSVC's CRT deprecates plain `fopen` +// (C4996, fatal under the engine's /WX policy); `fopen_s` cannot be +// used here: it opens with the `_SH_SECURE` sharing mode, which denies +// re-opening of the file (the windows-msvc CI runs of M0-CORE-02 read +// back an empty file for exactly that reason). `_fsopen(path, mode, +// _SH_DENYNO)` is the CRT's documented way to open with plain-`fopen` +// sharing semantics, which every other supported compiler's `fopen` +// provides. +#if defined(_MSC_VER) +inline std::FILE* openConfigFile(const char* path, const char* mode) { + return ::_fsopen(path, mode, _SH_DENYNO); +} +#else +inline std::FILE* openConfigFile(const char* path, const char* mode) { + return std::fopen(path, mode); +} +#endif + // Reads the config file into a bounded buffer (the 1 MiB ADR 0003 // bound). Returns the error Status; on success the document is in // `out`. laige::Status readConfigFile(const std::string& path, std::string* out) { - std::FILE* file = std::fopen(path.c_str(), "rb"); + std::FILE* file = openConfigFile(path.c_str(), "rb"); if (file == nullptr) { return laige::ErrorCode::IoError; }