From 0c6d0a357362505802ef6fdae0fa56477ff16ae8 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 8 Sep 2026 21:50:01 -0400 Subject: [PATCH] shim: launder game-address reads past -Werror=array-bounds in ai_orders A fresh cross-build directory caught what a reused one had been hiding: gcc reasons about a reinterpret_cast from uintptr_t as an object of its own when the read is inlined at a constant offset. Same barrier idiom watchpoints.cpp already uses for the TIB reads. --- src/shim/hooks/ai_orders.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/shim/hooks/ai_orders.cpp b/src/shim/hooks/ai_orders.cpp index ea009f1..c24b964 100644 --- a/src/shim/hooks/ai_orders.cpp +++ b/src/shim/hooks/ai_orders.cpp @@ -51,11 +51,22 @@ inline bool Readable(std::uintptr_t p, std::size_t n) { return p != 0 && !IsBadReadPtr(reinterpret_cast(p), n); } +// A game address is an integer we were handed, not a pointer into any object gcc can see. When one +// of these is inlined at a site with a constant offset the optimizer reasons about it as an object +// of its own and trips -Werror=array-bounds. Launder it through a register the same way +// `watchpoints.cpp` reads the TIB: the barrier costs nothing and makes the provenance opaque. +template +inline volatile T* Opaque(std::uintptr_t p) { + auto* q = reinterpret_cast(p); + asm volatile("" : "+r"(q)); + return q; +} + inline std::uint32_t U32(std::uintptr_t p) { - return Readable(p, 4) ? *reinterpret_cast(p) : 0u; + return Readable(p, 4) ? *Opaque(p) : 0u; } inline std::uint8_t U8(std::uintptr_t p) { - return Readable(p, 1) ? *reinterpret_cast(p) : 0u; + return Readable(p, 1) ? *Opaque(p) : 0u; } inline float F32(std::uintptr_t p) { const std::uint32_t v = U32(p);