From bf1f7fbeff81170b3d0d3e061c21b89896808b3f Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sun, 20 Sep 2026 16:05:05 -0500 Subject: [PATCH 1/2] add a regression test for #5450 this tests for a regression on the bug in #5450 and fixed in #5451 --- library/LuaTools.test.cpp | 70 ++++++++++++++++++++++++++++++++++++ library/include/LuaWrapper.h | 2 +- 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 library/LuaTools.test.cpp diff --git a/library/LuaTools.test.cpp b/library/LuaTools.test.cpp new file mode 100644 index 0000000000..d5fce588c6 --- /dev/null +++ b/library/LuaTools.test.cpp @@ -0,0 +1,70 @@ +// Regression test for https://github.com/DFHack/dfhack/issues/5450 +// (fixed by https://github.com/DFHack/dfhack/pull/5451) +// +// df::identity_traits::get() returns a pointer to a *const* instance of +// a type_identity subclass for most types. When the df_object constraint +// required a pointer to non-const type_identity, Lua::Push rejected those +// types and fell back to the bool overload, so e.g. a pointer to a vector +// arrived in Lua as a boolean. + +#include "LuaTools.h" +#include "LuaWrapper.h" + +#include +#include + +#include +#include + +// The constraint must accept types whose identity is exposed through a +// pointer-to-const. This is the compile-time half of the regression check: +// it fails to build if df_object ever becomes too strict again. +static_assert(DFHack::Lua::df_object>); +static_assert(DFHack::Lua::df_object); +static_assert(DFHack::Lua::df_object); + +class LuaPushTest : public testing::Test { +protected: + lua_State *L = nullptr; + + void SetUp() override + { + L = luaL_newstate(); + ASSERT_NE(nullptr, L); + luaL_openlibs(L); + DFHack::LuaWrapper::AttachDFGlobals(L); + } + + void TearDown() override + { + if (L) + lua_close(L); + } +}; + +TEST_F(LuaPushTest, PushVectorPointer) +{ + std::vector vec{ 7, 42 }; + + DFHack::Lua::Push(L, &vec); + + // the pointer must arrive as a wrapped DF object, not a boolean + ASSERT_EQ(LUA_TUSERDATA, lua_type(L, -1)); + ASSERT_EQ(DFHack::Lua::OBJ_REF, DFHack::Lua::IsDFObject(L, -1)); + + // the same pointer must round-trip back out of Lua + EXPECT_EQ(&vec, DFHack::Lua::GetDFObject>(L, -1)); + + // and the pushed object must behave like the vector it wraps + lua_len(L, -1); + EXPECT_EQ(lua_Integer(vec.size()), lua_tointeger(L, -1)); + lua_pop(L, 1); + + lua_geti(L, -1, 0); + EXPECT_EQ(7, lua_tointeger(L, -1)); + lua_pop(L, 1); + + lua_geti(L, -1, 1); + EXPECT_EQ(42, lua_tointeger(L, -1)); + lua_pop(L, 1); +} diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h index 258d4d254a..c913d05893 100644 --- a/library/include/LuaWrapper.h +++ b/library/include/LuaWrapper.h @@ -236,7 +236,7 @@ namespace LuaWrapper { void IndexStatics(lua_State *state, int meta_idx, int ftable_idx, struct_identity *pstruct); - void AttachDFGlobals(lua_State *state); + DFHACK_EXPORT void AttachDFGlobals(lua_State *state); DFHACK_EXPORT void notify_deprecated(lua_State* state, std::string_view name, std::string_view message); }} From 2697439ab00b3edf586181f2f757f3f584937a14 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sun, 20 Sep 2026 16:15:06 -0500 Subject: [PATCH 2/2] improved test --- library/LuaTools.test.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/LuaTools.test.cpp b/library/LuaTools.test.cpp index d5fce588c6..304ebf8899 100644 --- a/library/LuaTools.test.cpp +++ b/library/LuaTools.test.cpp @@ -46,6 +46,9 @@ TEST_F(LuaPushTest, PushVectorPointer) { std::vector vec{ 7, 42 }; + // NB: intentionally avoids df_object-constrained calls below so that + // this test still compiles if the constraint regresses - in that case + // Push() falls back to the bool overload and the checks fail at runtime DFHack::Lua::Push(L, &vec); // the pointer must arrive as a wrapped DF object, not a boolean @@ -53,7 +56,8 @@ TEST_F(LuaPushTest, PushVectorPointer) ASSERT_EQ(DFHack::Lua::OBJ_REF, DFHack::Lua::IsDFObject(L, -1)); // the same pointer must round-trip back out of Lua - EXPECT_EQ(&vec, DFHack::Lua::GetDFObject>(L, -1)); + EXPECT_EQ(static_cast(&vec), + DFHack::Lua::GetDFObject(L, df::identity_traits>::get(), -1)); // and the pushed object must behave like the vector it wraps lua_len(L, -1);