Sync upstream into main - #4
Open
github-actions[bot] wants to merge 90 commits into
Open
Conversation
A handful of SolLua bindings still referenced sol::nil / sol::type::nil while the rest of the same files already use sol::lua_nil. sol::nil is a thin alias for lua_nil (see lib/sol2/sol.hpp) and collides with the `nil` macro that Objective-C headers define, so lua_nil is the portable spelling. This brings the remaining spots in line with the prevailing convention. Extracted from cc3cad9 in beyond-all-reason#2991; cross-platform, no behavior change.
float3.h included lib/streflop/streflop_cond.h directly, ahead of FastMath.h. FastMath.h defines MATH_SQRT_OVERRIDE before it includes streflop_cond.h (so streflop does not define its own math::sqrt(float) -- FastMath provides a faster one). The direct include meant streflop_cond.h could be processed before that override was set. Drop the direct include; FastMath.h pulls in streflop_cond.h transitively with the override in place. creg_cond.h keeps its original position -- it does not pull in streflop, so it does not need to move. Surfaced by the macOS port (beyond-all-reason#2991, commit cc3cad9). Co-authored-by: Mark Kropf <markkropf@gmail.com>
…son#3025) `SafeUtil.h` uses `<memory>` for `std::addressof`, and `<type_traits>` for `std::is_trivially_copyable` / `std::is_trivially_constructible_v`, but pulled them in only transitively. Include them directly so the header is self-contained and does not rely on include order elsewhere. Also use `std::is_trivially_default_constructible` for the default-construction. Extracted from cc3cad9 in beyond-all-reason#2991; cross-platform, no behavior change. Co-authored-by: Mark Kropf <markkropf@gmail.com> Co-authored-by: sprunk <spr.ng@o2.pl>
LuaTextures::Create returned an empty string on glTexImage failure with no diagnostics. Log target/size/format/dataFormat/dataType/glError so texture-creation failures can be diagnosed. Extracted from 1e75080 in beyond-all-reason#2991; cross-platform, no behavior change beyond the added log.
Engine crash thread: https://discordapp.com/channels/549281623154229250/1516994684591931535 Bugged Scenario: A reclaimer A is guarding another reclaimer B, while both reclaiming the same wreck. A can be notified of wreck death BEFORE the guarded unit B is notified, leading to trying to reclaim the same (actively-deleting) wreck because B's reference hasn't been cleaned up yet. This leads to an eventual segfault. So: the short term/easy fix is to skip reclaiming of a dead/dying target.
util_fileSelector was declared with a non-const struct dirent* on __APPLE__ and const elsewhere. macOS scandir() expects the selector argument as int(*)(const struct dirent*), so the non-const Apple variant failed to compile under GCC: Util.c:500: error: passing argument 3 of 'scandir' from incompatible pointer type Both branches were otherwise identical, so drop the __APPLE__ split and use const struct dirent* unconditionally (matches POSIX scandir). No effect on Linux/Windows. Assisted by Claude Code; verified by compiling the macOS headless build.
The legacy build did find_package(X11 REQUIRED) under a plain if(UNIX) guard. macOS is UNIX in CMake but does not use X11 (it uses Cocoa), so configuring the engine on macOS failed at find_package(X11). An if(APPLE) block already follows for Foundation, so exclude Apple from the X11 branch: if(UNIX AND NOT APPLE). Surfaced configuring the spring-headless target on macOS (which reuses the legacy Game target). No effect on Linux/Windows. Assisted by Claude Code; verified by configuring the macOS build.
Modern 7-Zip ships its CLI as '7zz' (Homebrew's 'sevenzip' formula installs /opt/homebrew/bin/7zz; recent Linux distros likewise package '7zz'). FindSevenZip only searched for '7z'/'7za', so configure failed with 'Could NOT find SevenZip (missing: SEVENZIP_BIN)' on such systems. Add '7zz' to the searched NAMES. No effect where 7z/7za already exist.
rts/System/Platform/Mac/SDLMain.m and SDLMain.h are the classic SDL 1.2 Cocoa main wrapper (the Darrell Walisser / Max Horn template). They are: - not listed in any CMakeLists (never compiled) - not #included by any source file - built on Carbon (<Carbon/Carbon.h>), which is 32-bit-only and unavailable on modern macOS / Apple Silicon SDL2 provides its own SDL_main, so this wrapper is obsolete. Remove the dead files so the macOS platform layer reflects what is actually built.
Updated the link for the SplinterFaction card to include the 'https://' prefix. This has been broken for a very long time. I've asked Skyrbunny to fix it multiple times, but it has never gotten fixed.
beyond-all-reason#3052) setMinLevel() only searched for an existing entry on the "set back to default" (erase) path. Setting a section to a *non-default* level appended a new row unconditionally, so repeatedly changing one section's level (e.g. via Spring.SetLogSectionFilterLevel) accumulated duplicate entries and eventually filled the fixed 64-slot sectionMinLevels table -- after which every section-level change silently failed with "too many section-levels". Fix: Look the section up before appending and, if it already has an entry, update it in place. This bounds the table at one entry per section. This likely never happens in practice but this was found while writing other tests Co-authored-by: Bruno Da Silva <Bruno-DaSilva@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The original code relied on implicit null-termination which is not guaranteed.
Aligns forward declarations with definitions to silence MSVC warnings.
SpringApp.cpp included <X11/Xlib.h> for every non-Windows platform, but macOS has no X11 headers by default, breaking the native build. The only user of it, XInitThreads(), is already excluded on __APPLE__ at its call site, so guard the include the same way.
glad_glx.c includes X11/X.h to provide the GLX windowing-system bindings,
but macOS has no X11/GLX. The Glad CMakeLists added glad_glx.c for every
UNIX-and-not-MinGW platform, so building any GL-enabled target (e.g.
engine-legacy) on macOS failed:
fatal error: X11/X.h: No such file or directory
macOS resolves GL entry points without GLX (the engine's glxHandler is
already #ifdef'd out on __APPLE__), so exclude glad_glx.c on Apple and
build only the core glad.c there.
…eyond-all-reason#2919) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These three headers use std:: algorithms but rely on <algorithm> being pulled in transitively. libstdc++ does so; libc++ (clang/macOS) does not, so they can fail to compile under libc++ depending on the version. Symbols that require the include: - rts/System/Matrix44f.h -> std::copy - rts/System/SpringHashMap.hpp -> std::fill_n - rts/System/SpringHashSet.hpp -> std::fill_n Adding the include is "include what you use" correctness and is a no-op on toolchains that already provide it transitively. No functional change. Cherry-picked from ExaDev/RecoilEngine (0ed29d5, 0b4bd10, 3adabd0). AI assistance: changes identified and applied with Claude (Anthropic); verified by a human (compiled under clang/libc++, no regression).
DemoTool compiles engine FileSystem sources (FileHandler, FileSystem) that include nowide/fstream.hpp and fmt/printf.h, but its target_link_libraries omitted the nowide and fmt targets. The engine builds obtain those include directories transitively through the nowide::nowide and fmt::fmt INTERFACE targets; DemoTool linked neither, so the build failed when the headers were not on a default search path (observed building the demotool target on macOS). Link nowide::nowide and fmt::fmt so their INTERFACE include directories propagate to the demotool target. Co-authored-by: Robert Burnham <burnhamrobertp@gmail.com>
* avoid parenthesised aggregate init and name the type explicitly (not supported everywhere) * avoid narrowing conversions.
* adds `Spring.TraceRayBetweenPositions(xA, yA, zA, xB, yB, zB, type)`
* adds `Spring.TraceRayInDirection(x, y, z, dx, dy, dz, length, type)`
* type is a string, "unit", "feature", or "both"
* both return an array of `{distance, objID, objType}` sorted by increasing distance
* these no longer did anything and just polluted the interface. * also removed example gadgetry using them from basecontent.
* blank_map_splatdetailtex * blank_map_splatdistr * blank_map_splattexscale1 .. 4 * blank_map_splattexmult1 .. 4 * blank_map_splatdetailnormaltex1 .. 4 * blank_map_splatdetailnormaldiffusealpha
…ason#3124) disable luaui, and pass an explicit start frame to the synctest command so the run anchors to a fixed sim frame instead of whenever the message reaches synced code.
…reason#3077) Every <simdjson.h> consumer already links simdjson::simdjson (GLTFParser via engineCommonLibraries; fastgltf links it privately), and that in-tree target exports the vendored include as a normal -I dir. DevIL's imported target is the only thing putting /opt/homebrew/include on the line, and as an imported target its include is emitted -isystem, which the compiler searches after every -I dir - so the vendored simdjson header wins on its own. The manual include_directories entry was redundant. Per review feedback (p2004a).
* Support alternate file extensions for replays --------- Co-authored-by: sprunk <spr.ng@o2.pl>
…beyond-all-reason#3139) * Fix Windows build: link test_DemoFileExtension against ZLIB::ZLIB The DemoFileExtension test target linked the bare library name `z`, which expands to `-lz`. That works on Linux where zlib sits in the default library search path, but the mingw cross-linker fails with "cannot find -lz" because on Windows zlib is supplied by mingwlibs64. Use the ZLIB::ZLIB imported target instead, matching how the engine, unitsync and DemoTool link zlib. find_package is needed here because imported targets are directory-scoped and test/ is a sibling of rts/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FuDx3RoNDdSDMTx7Nkw8ss * Apply suggestion from @sprunk Co-authored-by: sprunk <spr.ng@o2.pl> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: TarnishedKnight <lostsquirrel43@gmail.com> Co-authored-by: sprunk <spr.ng@o2.pl>
…nc) (beyond-all-reason#3075) * Sim/COB: avoid UB in float-to-short angle casts (fixes arm64/x86 desync) Casting a floating-point heading/pitch expression directly to `short` is undefined behaviour when the truncated value does not fit in `short`, and this is genuinely reached: `heading * RAD2TAANG` equals +-32768 at heading = +-pi, one past short's 32767 maximum. Because the result is UB, arm64 and x86 produced different values at the angle extremes, which was observed as an arm64/x86 multiplayer desync. x86 already lowers `short(float)` as float->int->short (cvttss2si into a 32-bit register, then narrow), so making the `int()` step explicit leaves the already-correct x86 result unchanged while pinning arm64 (whose fcvtzs saturated differently) to the same value. The int->short narrowing is the intended 16-bit TA-angle wraparound. Sites fixed in CobInstance.cpp: WindChanged, StartBuilding, AimWeapon. Sibling casts were checked and left untouched: UnitScript.cpp:1058 casts asin(...)*RAD2TAANG (range +-16384, always fits short, no UB) and lines 1095/1099 already route through int; CobThread.cpp has no such casts. Pure correctness fix to synced simulation code; it does not alter results on the platform that was already correct. Cross-arch sync validation is recommended. Origin: discussed in PR beyond-all-reason#2991's review (credit BambaDamba). AI assistance: implemented with Claude Code (Anthropic) from a written plan; reasoning and build verification reviewed by a human. * address review: extract RadAngleToCobShort helper with explanatory comment sprunk asked for a comment and a named helper around the float->short angle cast. Wrap the conversion in RadAngleToCobShort() and document why it exists: COB angles are circular 16-bit TA units (full turn == COBSCALE), so values past a half turn intentionally wrap modulo 2^16 via the int->short narrowing. The float->int step stays because the wrap is the desired behaviour and is deterministic across arm64/x86; clamping the range (as suggested) would break angles past a half turn rather than wrapping them. No behavioural change vs the previous short(int(...)) form.
…eason#3107) Ghost drawing in GLSL was inline in DrawAlphaUnit. Ghost drawing in GL4 was further inlined into DrawAlphaObjects. Both needed flags to pass around which makes the whole thing kinda awkward. This was ok in current world but the follow up PRs add more params so it's best to split them ahead of time. We pull the ghost drawing impl out into its own DrawGhostedBuildings function in both paths, then call them from DrawAlphaObjects. Simplifies the code and also makes the GLSL/GL4 paths more symmetric. Pure refactor, no behavior change. Co-authored-by: Bruno Da Silva <Bruno-DaSilva@users.noreply.github.com>
…een under (beyond-all-reason#3108) This fixes two bugs: 1. recently we added custom color palette functionality, but that was not extended to ghosts (so they are currently using just the team color). 2. Live ghosts read the color from the live CUnit at draw time, so if a unit changes team while in fog of war (eg. unit gifts), that information would be leaked. So, to fix, we now a) use paletteIndex for ghost color and b) snapshot the last seen paletteIndex for that unit. Co-authored-by: Bruno Da Silva <Bruno-DaSilva@users.noreply.github.com>
…ll-reason#3109) Extract the build + queue impl out of AddToSubmissionImpl into a private EmplaceInstance(), so in the next PR we can add another caller to EmplaceInstance(). Results in cleaner code without passing around a bunch of flags in the follow ups. Behavior should be unchanged. Co-authored-by: Bruno Da Silva <Bruno-DaSilva@users.noreply.github.com>
* refactor(GL4): replace matrix modes magic number with an enum
Add SetFlag("MATMODE_XXX", ...) alongside the existing GBUFFER_*_IDX flags so
the GL4 model shaders can reference the matrix-mode enum value by name instead
of hardcoding a literal.
Intended as a precursor for the ghost draw perf fixes, which add a third mode in the shader.
* refactor(GL4): add ARRAY_MATMODE static-instanced model draw path
* perf(GL4): batch ghost-building rendering via ARRAY_MATMODE
Ghost buildings are static (no animation, never move), so each gets a single
world-transform slot in the transforms SSBO, and is drawn batched through
ARRAY_MATMODE. One multidraw per (color bucket x texture type) instead of one
immediate draw per ghost.
---------
Co-authored-by: Bruno Da Silva <Bruno-DaSilva@users.noreply.github.com>
- Removed `lcstr` and `lcsub` arrays to save 64KB of BSS data per translation. - Removed unsafe inline implementation of `StrCaseStr` to eliminate multi-threaded race conditions.
…ownership (beyond-all-reason#2613) When bound through uikeys, Mouse4/5 (aka XButtons) try to assign ownership to gameInputReceiver. This causes either the mouse input or the X-button bind to be misrouted when pressed in combination. To prevent this, Mouse4/5 are routed like keybinds instead of sharing mouse ownership. This allows them to be pressed/released without stealing or confusing the activeReceiver.
…yond-all-reason#3026) smmalloc.h defines `#define INLINE inline`, a very generic token that collides with unrelated code (e.g. simdjson's layout_mode::INLINE enumerator) whenever both land in the same translation unit. Instead of undef-ing INLINE at each affected site, add System/recoil-smmalloc.h which includes smmalloc and drops the macro, and route the engine's single smmalloc include (MemPoolTypes.h) through it. The macro can no longer escape into engine code, so a future file mixing smmalloc with json/etc. won't reintroduce the collision. Surfaced by the macOS port (beyond-all-reason#2991, commit 16622a9). The upstream smmalloc leak is worth fixing there too. Co-authored-by: Mark Kropf <markkropf@gmail.com>
…ason#3033) Co-authored-by: 12345swordy <12345swordy@user.noreply.github.com>
…or (beyond-all-reason#2980) * Explicitly use InSonarJammer or InJammer depending on the tested sensor * Update IsUnitInJammer and GetPositionLosState to return either InJammer() or InSonarJammer() depending on pos/unitpos return args: IsUnitInJammer() and select(4, GetPositionLosState()) true -> complete jam = can't be sensed by anything false -> no jam or incomplete jam = can be sensed I picked this update because it's the most consistent with what we had. The main difference being that we check before calling InJammer() or InSonarJammer(), rather than having that pos check and jammerType dispatch within InJammer() * Revert "Update IsUnitInJammer and GetPositionLosState to return either InJammer() or InSonarJammer() depending on pos/unitpos" This reverts commit 93b8fb5.
* Lua: minor type annotation and doc fixes Series of minor EmmyLua annotation corrections and small documentation fixes that accumulated during the spring-split exploration. None of these depend on or relate to the spring-split refactor; pulled out as a standalone PR to keep the spring-split review focused. * expand check-lua to include Units/Scripts to capture UnitScriptTable in the shallow checkout
* Improve USE_ASAN configuration for all build types - Remove restriction to DEBUG builds only - ASAN works well with optimized builds - Add -fPIC for shared library compatibility with ASAN - Add -fno-omit-frame-pointer for better stack traces in error reports - Add -fno-optimize-sibling-calls to preserve full call chains - Set -O2 optimization level for better error detection than -O3 - Add explanatory comments for each flag 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix stack-use-after-scope in TextureCreationParams Replace std::initializer_list with std::array in wrapModes field. std::initializer_list is just a view into a temporary array, so storing it as a member variable creates a dangling pointer once the temporary is destroyed. ASAN error: ==559461==ERROR: AddressSanitizer: stack-use-after-scope on address 0x74d3039d3460 at pc 0x55c80ddb562f bp 0x7fffc14493c0 sp 0x7fffc14493b0 READ of size 4 at 0x74d3039d3460 thread T0 (recoil-main) #0 0x55c80ddb562e in GL::Impl::InitTexture(GL::TextureCreationParams const&, unsigned int, int) /build/src/rts/Rendering/Textures/Texture.cpp:33 #1 0x55c80ddb721c in GL::Texture2D::Texture2D(int, int, unsigned int, GL::TextureCreationParams const&, bool) /build/src/rts/Rendering/Textures/Texture.cpp:133 #2 0x55c80daf951c in CHeightTexture::CHeightTexture() /build/src/rts/Rendering/Map/InfoTexture/Modern/Height.cpp:49 #3 0x55c80db02149 in CInfoTextureHandler::CInfoTextureHandler() /build/src/rts/Rendering/Map/InfoTexture/Modern/InfoTextureHandler.cpp:26 #4 0x55c80daec745 in std::__detail::_MakeUniq<CInfoTextureHandler>::__single_object std::make_unique<CInfoTextureHandler>() /usr/include/c++/13/bits/unique_ptr.h:1070 #5 0x55c80daec745 in IInfoTextureHandler::Create() /build/src/rts/Rendering/Map/InfoTexture/IInfoTextureHandler.cpp:25 #6 0x55c80df3b1d1 in CWorldDrawer::InitPost() const /build/src/rts/Rendering/WorldDrawer.cpp:111 #7 0x55c8107955f2 in CGame::PostLoadRendering() /build/src/rts/Game/Game.cpp:756 #8 0x55c8107955f2 in CGame::Load(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) /build/src/rts/Game/Game.cpp:411 #9 0x55c81084498e in CLoadScreen::Init() /build/src/rts/Game/LoadScreen.cpp:146 #10 0x55c810845a80 in CLoadScreen::CreateInstance(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&, ILoadSaveHandler*) /build/src/rts/Game/LoadScreen.cpp:215 #11 0x55c810845a80 in CLoadScreen::CreateDeleteInstance(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&, ILoadSaveHandler*) /build/src/rts/Game/LoadScreen.cpp:200 #12 0x55c8108758d6 in CPreGame::UpdateClientNet() /build/src/rts/Game/PreGame.cpp:470 #13 0x55c810876b6f in CPreGame::Update() /build/src/rts/Game/PreGame.cpp:241 #14 0x55c80e883938 in SpringApp::Update() /build/src/rts/System/SpringApp.cpp:886 #15 0x55c80e890ffb in SpringApp::Run() /build/src/rts/System/SpringApp.cpp:927 #16 0x55c80e7fb939 in Run(int, char**) /build/src/rts/System/Main.cpp:51 #17 0x55c80cd63653 in main /build/src/rts/System/Main.cpp:104 #18 0x74d30642a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58 #19 0x74d30642a28a in __libc_start_main_impl ../csu/libc-start.c:360 #20 0x55c80ce28fc9 in _start (/home/gajop/projects/spring-projects/spring-bar/build-linux/install/spring+0x931fc9) (BuildId: 4378a67e1e8529ce2acbdc4e3f13182a8665a60b) Address 0x74d3039d3460 is located in stack of thread T0 (recoil-main) at offset 1120 in frame #0 0x55c80daf83cf in CHeightTexture::CHeightTexture() /build/src/rts/Rendering/Map/InfoTexture/Modern/Height.cpp:23 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix heap-use-after-free in SolLuaEventListener with deferred deletion Implement deferred deletion pattern for SolLuaEventListener to prevent use-after-free when OnDetach is called during ProcessEvent execution. The issue occurred when a Lua event handler called SetInnerRML, which destroyed elements and triggered OnDetach callbacks. OnDetach immediately deleted the listener with "delete this", but ProcessEvent was still on the call stack trying to invoke the Lua callback. The fix defers deletion by: 1. Setting m_detached flag in OnDetach instead of immediate deletion 2. Checking the flag at the start of ProcessEvent (handles queued events) 3. Checking the flag at the end of ProcessEvent (handles mid-execution detachment) This ensures the listener object is only deleted when it's safe to do so, after all its methods have returned. ASAN Error: ==704643==ERROR: AddressSanitizer: heap-use-after-free on address 0x5060002a98d8 at pc 0x618574f3991e bp 0x7ffe2f43de80 sp 0x7ffe2f43de70 READ of size 8 at 0x5060002a98d8 thread T0 (recoil-main) #0 in sol::basic_reference<false>::lua_state() const /build/src/rts/lib/sol2/sol.hpp:10244 #1 in sol::protected_function_result sol::basic_protected_function<>::invoke<false>() /build/src/rts/lib/sol2/sol.hpp:20676 #2 in decltype(auto) sol::basic_protected_function<>::call<>(Rml::Event&, Rml::Element*&, Rml::SolLua::SolLuaDocument*&) /build/src/rts/lib/sol2/sol.hpp:20569 #3 in Rml::SolLua::SolLuaEventListener::ProcessEvent(Rml::Event&) /build/src/rts/Rml/SolLua/plugin/SolLuaEventListener.cpp:144 #4 in Rml::EventDispatcher::DispatchEvent() /build/src/rts/lib/RmlUi/Source/Core/EventDispatcher.cpp:194 #5 in Rml::Element::DispatchEvent() /build/src/rts/lib/RmlUi/Source/Core/Element.cpp:1201 #6 in Rml::Context::ProcessMouseButtonUp(int, int) /build/src/rts/lib/RmlUi/Source/Core/Context.cpp:738 #7-53 [Mouse event handling chain up to main] 0x5060002a98d8 is located 24 bytes inside of 56-byte region freed by thread T0 (recoil-main) here: #0 operator delete(void*, unsigned long) #1 in Rml::SolLua::SolLuaEventListener::~SolLuaEventListener() /build/src/rts/Rml/SolLua/plugin/SolLuaEventListener.h:47 #2 in Rml::SolLua::SolLuaEventListener::OnDetach(Rml::Element*) /build/src/rts/Rml/SolLua/plugin/SolLuaEventListener.cpp:118 #3 in Rml::EventDispatcher::~EventDispatcher() /build/src/rts/lib/RmlUi/Source/Core/EventDispatcher.cpp:65 #4 in Rml::ElementMeta::~ElementMeta() /build/src/rts/lib/RmlUi/Source/Core/Element.cpp:94 #5 in Rml::Element::~Element() /build/src/rts/lib/RmlUi/Source/Core/Element.cpp:151 #6-14 [Element destruction chain] #15 in Rml::Element::SetInnerRML() /build/src/rts/lib/RmlUi/Source/Core/Element.cpp:1100 #16-45 [Lua/Sol2 call stack from event handler calling SetInnerRML] #46 in Rml::SolLua::SolLuaEventListener::ProcessEvent(Rml::Event&) /build/src/rts/Rml/SolLua/plugin/SolLuaEventListener.cpp:144 #47 in Rml::EventDispatcher::DispatchEvent() /build/src/rts/lib/RmlUi/Source/Core/EventDispatcher.cpp:194 #48-53 [Outer event handling that triggered the inner event] previously allocated by thread T0 (recoil-main) here: #0 operator new(unsigned long) #1 in Rml::SolLua::functions::addEventListener() /build/src/rts/Rml/SolLua/bind/Element.cpp:47 #2-53 [Lua binding and event registration chain] 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix heap-use-after-free in RmlUI PopLayer by wrapping Context::Render Root cause: Context::Render was exposed to Lua without proper frame management (BeginFrame/PresentFrame). When called from Lua for render-to-texture workflows, PushLayer/PopLayer would execute outside the frame lifecycle, causing the layer stack to underflow and access freed memory. Solution: Wrap the Lua binding for Context::Render with a lambda that calls BeginFrame before and PresentFrame after rendering, ensuring proper layer stack management for all callers including RTT use cases. ASAN error: ==37278==ERROR: AddressSanitizer: heap-use-after-free on address 0x503000460eec at pc 0x5c05ab21d77a bp 0x7ffcce4f73e0 sp 0x7ffcce4f73d0 READ of size 4 at 0x503000460eec thread T0 (recoil-main) #0 0x5c05ab21d779 in RenderInterface_GL3_Recoil::PopLayer() /build/src/rts/Rml/Backends/RmlUi_Renderer_GL3_Recoil.cpp:1727 #1 0x5c05ad40d908 in Rml::RenderManager::PopLayer() /build/src/rts/lib/RmlUi/Source/Core/RenderManager.cpp:326 #2 0x5c05ad2fac86 in Rml::ElementEffects::RenderEffects(Rml::RenderStage) /build/src/rts/lib/RmlUi/Source/Core/ElementEffects.cpp:336 #3 0x5c05ad2a037a in Rml::Element::Render() /build/src/rts/lib/RmlUi/Source/Core/Element.cpp:255 #4 0x5c05ad2a02eb in Rml::Element::Render() /build/src/rts/lib/RmlUi/Source/Core/Element.cpp:253 #5 0x5c05ad2a02eb in Rml::Element::Render() /build/src/rts/lib/RmlUi/Source/Core/Element.cpp:253 #6 0x5c05ad1e000a in Rml::Context::Render() /build/src/rts/lib/RmlUi/Source/Core/Context.cpp:221 #7 0x5c05ab3a434b in bool sol::member_function_wrapper<bool (Rml::Context::*)(), bool, Rml::Context>::call<bool (Rml::Context::*&)()>(bool (Rml::Context::*&)(), Rml::Context&) /build/src/rts/lib/sol2/sol.hpp:17338 #8 0x5c05ab3a434b in decltype(auto) sol::member_function_wrapper<bool (Rml::Context::*)(), bool, Rml::Context>::caller::operator()<bool (Rml::Context::*&)()>(bool (Rml::Context::*&)(), Rml::Context&) const /build/src/rts/lib/sol2/sol.hpp:17344 #9 0x5c05ab3a434b in eval<true, sol::argument_handler<sol::types<bool> >&, sol::member_function_wrapper<bool (Rml::Context::*)(), bool, Rml::Context>::caller, bool (Rml::Context::*&)(), Rml::Context&> /build/src/rts/lib/sol2/sol.hpp:16078 #10 0x5c05ab3a434b in decltype(auto) sol::stack::stack_detail::call<true, , bool, , sol::member_function_wrapper<bool (Rml::Context::*)(), bool, Rml::Context>::caller, bool (Rml::Context::*&)(), Rml::Context&>(sol::types<bool>, sol::types<>, std::integer_sequence<unsigned long>, lua_State*, int, sol::member_function_wrapper<bool (Rml::Context::*)(), bool, Rml::Context>::caller&&, bool (Rml::Context::*&)(), Rml::Context&) /build/src/rts/lib/sol2/sol.hpp:16131 #11 0x5c05ab3a434b in decltype(auto) sol::stack::call<true, bool, , sol::member_function_wrapper<bool (Rml::Context::*)(), bool, Rml::Context>::caller, bool (Rml::Context::*&)(), Rml::Context&>(sol::types<bool>, sol::types<>, lua_State*, int, sol::member_function_wrapper<bool (Rml::Context::*)(), bool, Rml::Context>::caller&&, bool (Rml::Context::*&)(), Rml::Context&) /build/src/rts/lib/sol2/sol.hpp:16150 #12 0x5c05ab3a434b in int sol::stack::call_into_lua<true, true, bool, , , sol::member_function_wrapper<bool (Rml::Context::*)(), bool, Rml::Context>::caller, bool (Rml::Context::*&)(), Rml::Context&>(sol::types<bool>, sol::types<>, lua_State*, int, sol::member_function_wrapper<bool (Rml::Context::*)(), bool, Rml::Context>::caller&&, bool (Rml::Context::*&)(), Rml::Context&) /build/src/rts/lib/sol2/sol.hpp:16198 #13 0x5c05ab3a434b in int sol::call_detail::lua_call_wrapper<Rml::Context, bool (Rml::Context::*)(), true, false, true, 0, true, void>::call<bool (Rml::Context::*&)(), Rml::Context&>(lua_State*, bool (Rml::Context::*&)(), Rml::Context&) /build/src/rts/lib/sol2/sol.hpp:18103 #14 0x5c05ab3a434b in int sol::call_detail::lua_call_wrapper<Rml::Context, bool (Rml::Context::*)(), true, false, true, 0, true, void>::call<bool (Rml::Context::*&)()>(lua_State*, bool (Rml::Context::*&)()) /build/src/rts/lib/sol2/sol.hpp:18093 #15 0x5c05ab3a434b in int sol::call_detail::call_wrapped<Rml::Context, true, false, 0, true, true, bool (Rml::Context::*&)()>(lua_State*, bool (Rml::Context::*&)()) /build/src/rts/lib/sol2/sol.hpp:18506 #16 0x5c05ab3a434b in int sol::u_detail::binding<char [7], bool (Rml::Context::*)(), Rml::Context>::call_with_<true, false>(lua_State*, void*) /build/src/rts/lib/sol2/sol.hpp:23023 #17 0x5c05ab3a434b in int sol::u_detail::binding<char [7], bool (Rml::Context::*)(), Rml::Context>::call_<true, false>(lua_State*) /build/src/rts/lib/sol2/sol.hpp:23029 #18 0x5c05aa5925ba in sol::detail::lua_cfunction_trampoline(lua_State*, int (*)(lua_State*)) /build/src/rts/lib/sol2/sol.hpp:8398 #19 0x5c05ab361ccf in int sol::detail::static_trampoline<&(int sol::u_detail::binding<char [7], bool (Rml::Context::*)(), Rml::Context>::call_<true, false>(lua_State*))>(lua_State*) /build/src/rts/lib/sol2/sol.hpp:8423 #20 0x5c05ab361ccf in int sol::detail::typed_static_trampoline<int (*)(lua_State*), &(int sol::u_detail::binding<char [7], bool (Rml::Context::*)(), Rml::Context>::call_<true, false>(lua_State*))>(lua_State*) /build/src/rts/lib/sol2/sol.hpp:8490 #21 0x5c05ab361ccf in int sol::u_detail::binding<char [7], bool (Rml::Context::*)(), Rml::Context>::call<true, false>(lua_State*) /build/src/rts/lib/sol2/sol.hpp:23034 #22 0x5c05ac11b57f in luaD_precall(lua_State*, lua_TValue*, int) /build/src/rts/lib/lua/src/ldo.cpp:320 #23 0x5c05ac1499a2 in luaV_execute(lua_State*, int) /build/src/rts/lib/lua/src/lvm.cpp:620 #24 0x5c05ac11c484 in luaD_call(lua_State*, lua_TValue*, int) /build/src/rts/lib/lua/src/ldo.cpp:378 #25 0x5c05ac1056b8 in f_call /build/src/rts/lib/lua/src/lapi.cpp:812 #26 0x5c05ac119bb8 in luaD_rawrunprotected(lua_State*, void (*)(lua_State*, void*), void*) /build/src/rts/lib/lua/src/ldo.cpp:116 #27 0x5c05ac11cdc2 in luaD_pcall(lua_State*, void (*)(lua_State*, void*), void*, long, long) /build/src/rts/lib/lua/src/ldo.cpp:464 #28 0x5c05ac10d695 in lua_pcall(lua_State*, int, int, int) /build/src/rts/lib/lua/src/lapi.cpp:833 #29 0x5c05ac11272f in luaB_pcall /build/src/rts/lib/lua/src/lbaselib.cpp:389 #30 0x5c05ac11b57f in luaD_precall(lua_State*, lua_TValue*, int) /build/src/rts/lib/lua/src/ldo.cpp:320 #31 0x5c05ac1499a2 in luaV_execute(lua_State*, int) /build/src/rts/lib/lua/src/lvm.cpp:620 #32 0x5c05ac11c484 in luaD_call(lua_State*, lua_TValue*, int) /build/src/rts/lib/lua/src/ldo.cpp:378 #33 0x5c05ac1056b8 in f_call /build/src/rts/lib/lua/src/lapi.cpp:812 #34 0x5c05ac119bb8 in luaD_rawrunprotected(lua_State*, void (*)(lua_State*, void*), void*) /build/src/rts/lib/lua/src/ldo.cpp:116 #35 0x5c05ac11cdc2 in luaD_pcall(lua_State*, void (*)(lua_State*, void*), void*, long, long) /build/src/rts/lib/lua/src/ldo.cpp:464 #36 0x5c05ac10d695 in lua_pcall(lua_State*, int, int, int) /build/src/rts/lib/lua/src/lapi.cpp:833 #37 0x5c05aa1e5175 in ScopedLuaCall /build/src/rts/Lua/LuaHandle.cpp:397 #38 0x5c05aa1e5175 in CLuaHandle::RunCallInTraceback(lua_State*, LuaHashString const*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*, int, int, int, bool) /build/src/rts/Lua/LuaHandle.cpp:483 #39 0x5c05aa1e6908 in CLuaHandle::RunCallInTraceback(lua_State*, LuaHashString const&, int, int, int, bool) /build/src/rts/Lua/LuaHandle.cpp:494 #40 0x5c05aa2102b2 in CLuaHandle::RunCallIn(lua_State*, LuaHashString const&, int, int) /build/src/rts/Lua/LuaHandle.h:425 #41 0x5c05aa2102b2 in CLuaHandle::DrawScreenCommon(LuaHashString const&) /build/src/rts/Lua/LuaHandle.cpp:2863 #42 0x5c05aa2102b2 in CLuaHandle::DrawScreen() /build/src/rts/Lua/LuaHandle.cpp:2881 #43 0x5c05ab9492b0 in CEventHandler::DrawScreen() /build/src/rts/System/EventHandler.cpp:708 #44 0x5c05aa24cdef in CLuaInputReceiver::Draw() /build/src/rts/Lua/LuaInputReceiver.cpp:71 #45 0x5c05ad971694 in CGame::DrawInputReceivers() /build/src/rts/Game/Game.cpp:1574 #46 0x5c05ad98b020 in CGame::Draw() /build/src/rts/Game/Game.cpp:1526 #47 0x5c05aba75cce in SpringApp::Update() /build/src/rts/System/SpringApp.cpp:889 #48 0x5c05aba8323b in SpringApp::Run() /build/src/rts/System/SpringApp.cpp:927 #49 0x5c05ab9edb79 in Run(int, char**) /build/src/rts/System/Main.cpp:51 #50 0x5c05a9f546d3 in main /build/src/rts/System/Main.cpp:104 #51 0x782b5082a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58 #52 0x782b5082a28a in __libc_start_main_impl ../csu/libc-start.c:360 beyond-all-reason#53 0x5c05aa01a049 in _start (/home/gajop/projects/spring-projects/spring-bar/build-linux/install/spring+0x931049) (BuildId: 0100fe9eb07611ab4faec887c8a97ffbdbc3df06) 0x503000460eec is located 28 bytes inside of 32-byte region [0x503000460ed0,0x503000460ef0) freed by thread T0 (recoil-main) here: #0 0x782b512fc4d8 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52 #1 0x782b40ef9d00 (/lib/x86_64-linux-gnu/libnvidia-glcore.so.535.274.02+0x14f9d00) (BuildId: 513f593c743a6a4f8ccb0183cb093aa171cef856) previously allocated by thread T0 (recoil-main) here: #0 0x782b512fd9c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69 #1 0x782b40ef9851 (/lib/x86_64-linux-gnu/libnvidia-glcore.so.535.274.02+0x14f9851) (BuildId: 513f593c743a6a4f8ccb0183cb093aa171cef856) SUMMARY: AddressSanitizer: heap-use-after-free /build/src/rts/Rml/Backends/RmlUi_Renderer_GL3_Recoil.cpp:1727 in RenderInterface_GL3_Recoil::PopLayer() 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix heap-use-after-free in Element::SetClass via deferred deletion When a Lua event handler calls element.inner_rml = "...", it destroys all child elements. If Lua holds references to those children and tries to use them later in the same event handler (e.g., calling SetClass), it causes a use-after-free. Solution: Wrap SetInnerRML in the Lua binding to manually remove children and store them in a deferred deletion list. Children are kept alive until the event processing completes, preventing use-after-free when Lua accesses them. Original ASAN error: ==41062==ERROR: AddressSanitizer: heap-use-after-free on address 0x506000057c28 READ of size 8 at 0x506000057c28 thread T0 (recoil-main) #0 std::__cxx11::basic_string::size() at /usr/include/c++/13/bits/basic_string.h:1060 #6 Rml::ElementStyle::SetClass() at ElementStyle.cpp:255 #7 Rml::Element::SetClass() at Element.cpp:297 #8 [Sol2/Lua binding call chain] 0x506000057c28 is located 8 bytes inside of 64-byte region freed by thread T0: #7 Rml::ElementStyle::~ElementStyle() at ElementStyle.h:51 #11 Rml::Element::~Element() at Element.cpp:151 #26 std::vector::clear() at stl_vector.h:1603 #41 Rml::Element::~Element() at Element.cpp:148 #49 Rml::Element::SetInnerRML() at Element.cpp:1100 #52 [Sol2/Lua binding call chain - SetInnerRML called from Lua] The element was previously allocated during Element::SetClass(): #9 Rml::ElementStyle::SetClass() at ElementStyle.cpp:262 #10 Rml::Element::SetClass() at Element.cpp:297 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: TarnishedKnight <lostsquirrel43@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated upstream sync hit conflicts. Merge
master(now level with beyond-all-reason/RecoilEngine) intomainand resolve here.