Files
Goober5000andClaude Fable 5 9bf5b6acf4 harden move operations in w_bank and LuaThread
w_bank: add a move constructor.  object_copy_including_array_member
returns by value, and without a move constructor the return fell back
to the shallow copy constructor whenever NRVO was not applied (e.g.
MSVC debug builds), after which the local's destructor deleted the
freshly allocated arrays -- leaving the stored bank dangling and
double-deleting at model unload.  Also add noexcept to the move
operations, replace the destructor call in move-assignment with
explicit delete[]s (using an object after its lifetime has ended is
undefined behavior), guard against self-move, and document that the
defaulted copy constructor is intentionally shallow for
object_copy_including_array_member's copy-then-replace pattern.

LuaThread: declare the defaulted move operations noexcept.  This
exposed that LuaTable was not movable at all (its user-declared
destructor suppressed the implicit moves), so LuaThread's implicit
move spec was formally throwing -- a hard error on gcc 9, which
still requires the spec on a defaulted redeclaration to match the
implicit one.  Restore LuaTable's rule of five and declare the
LuaValue and LuaFunction copy operations noexcept (they only copy a
shared_ptr, a raw pointer, and an enum), making the noexcept claims
hold throughout the hierarchy.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-06 13:33:49 -04:00

254 lines
6.0 KiB
C++

#include "LuaFunction.h"
#include "LuaException.h"
#include "LuaHeaders.h"
namespace luacpp {
namespace {
int stdFunctionDestructor(lua_State* L)
{
// The userdata is the function object so we call the destructor here to free up the internal resources
const auto luaPtr = lua_touserdata(L, 1);
const auto funcPtr = static_cast<LuaFunctionObject*>(luaPtr);
funcPtr->~LuaFunctionObject();
return 0;
}
int stdFunctionCaller(lua_State* L)
{
const auto numArgs = lua_gettop(L);
// Push the actual function object on the stack
lua_pushvalue(L, lua_upvalueindex(1));
const auto funcObj = static_cast<LuaFunctionObject*>(lua_touserdata(L, -1));
lua_pop(L, 1); // We have the pointer so we can remove this from the stack again
LuaValueList params;
params.reserve(numArgs);
for (int i = 1; i <= numArgs; ++i) {
LuaValue val;
val.setReference(UniqueLuaReference::create(L, i));
params.push_back(val);
}
const auto ret = (*funcObj)(L, params);
// Prepare to return values. Clear the stack
lua_settop(L, 0);
for (const auto& retVal : ret)
{
retVal.pushValue(L);
}
// Done!
return static_cast<int>(ret.size());
}
}
LuaFunction LuaFunction::createFromCFunction(lua_State* L, lua_CFunction function, const LuaValueList& upvalues)
{
LuaFunction func;
for (auto& val : upvalues) {
val.pushValue(L);
}
lua_pushcclosure(L, function, (int)upvalues.size());
func.setReference(UniqueLuaReference::create(L));
lua_pop(L, 1);
return func;
}
LuaFunction LuaFunction::createFromStdFunction(lua_State* L, LuaFunctionObject function) {
auto userdataMetatable = LuaTable::create(L);
userdataMetatable.addValue("__gc", stdFunctionDestructor);
// Create storage for the function object in Lua and initialize our data there via placement new
auto functionStorage = lua_newuserdata(L, sizeof(function));
new (functionStorage) LuaFunctionObject(std::move(function));
userdataMetatable.pushValue(L);
lua_setmetatable(L, -2);
LuaValue funcValue;
funcValue.setReference(UniqueLuaReference::create(L));
lua_pop(L, 1); // Value is referenced. We can remove it from the stack now
return LuaFunction::createFromCFunction(L, stdFunctionCaller, { funcValue });
}
LuaFunction LuaFunction::createFromCode(lua_State* L, std::string const& code, std::string const& name) {
int load_err = luaL_loadbuffer(L, code.c_str(), code.length(), name.c_str());
if (!load_err) {
LuaFunction func;
func.setReference(UniqueLuaReference::create(L));
lua_pop(L, 1);
return func;
} else {
// Get the error message
size_t len;
const char* err = lua_tolstring(L, -1, &len);
lua_pop(L, 1);
throw LuaException(std::string(err, len));
}
}
LuaFunction::LuaFunction() : LuaValue(), _errorFunction(nullptr) {
}
LuaFunction::LuaFunction(const LuaFunction&) noexcept = default;
LuaFunction& LuaFunction::operator=(const LuaFunction&) noexcept = default;
LuaFunction::LuaFunction(LuaFunction&&) noexcept = default;
LuaFunction& LuaFunction::operator=(LuaFunction&&) noexcept = default;
LuaFunction::~LuaFunction() = default;
bool LuaFunction::setEnvironment(const LuaTable& table) {
if (!table.getReference()->isValid()) {
throw LuaException("Table reference is not valid!");
}
this->pushValue(_luaState);
table.pushValue(_luaState);
bool ret = lua_setfenv(_luaState, -2) != 0;
// Pop the function again
lua_pop(_luaState, 1);
return ret;
}
LuaValueList LuaFunction::operator()(lua_State* L, const LuaValueList& args) const {
return this->call(L, args);
}
void LuaFunction::setReference(const LuaReference& ref) {
if (ref == nullptr)
{
// If not a valid reference then let the base class handle everything
LuaValue::setReference(ref);
return;
}
lua_State* L = ref->getState();
ref->pushValue(L);
if (lua_type(L, -1) != LUA_TFUNCTION) {
lua_pop(L, 1);
throw LuaException("Reference does not refer to a function!");
} else {
lua_pop(L, 1);
LuaValue::setReference(ref);
}
}
LuaValueList LuaFunction::call(lua_State* L, const LuaValueList& args) const {
int err_idx = 0;
int stackTop;
if (_errorFunction) {
// push the error function
_errorFunction->pushValue(L);
err_idx = lua_gettop(L);
stackTop = err_idx;
} else {
stackTop = lua_gettop(L);
}
if(!lua_checkstack(L, (int)args.size() + 1))
throw LuaException("Lua Stack Overflow!");
// Push the function onto the stack
this->pushValue(L, true);
// Push the arguments onto the stack
for (const auto& arg : args) {
arg.pushValue(L, true);
}
// actually call the function now!
int err = lua_pcall(L, (int) args.size(), LUA_MULTRET, err_idx);
if (!err) {
int numReturn = lua_gettop(L) - stackTop;
LuaValueList values;
values.reserve(numReturn);
LuaValue val;
for (int i = 0; i < numReturn; ++i) {
if (convert::popValue(L, val)) {
// Add values at the begin as the last return value is on top
// of the stack.
values.insert(values.begin(), val);
}
}
if (err_idx != 0) {
// Remove the error function
lua_pop(L, 1);
}
return values;
} else {
// Make sure that there is exactly one parameter left on the stack
// If the error function didn't return anything then this will push nil
// If it pushed more than one value then this will discard all of them except the last one
lua_settop(L, stackTop + 1);
std::string err_msg;
if (!lua_isstring(L, -1)) {
err_msg = "Invalid lua value on stack!";
lua_pop(L, 1); // Remove the value on the stack
} else {
if (!convert::popValue(L, err_msg)) {
err_msg = "Failed to get error message from Lua stack!";
}
}
if (err_idx != 0) {
// Pop the error function
lua_pop(L, 1);
}
// Throw exception with generated message
throw LuaException(err_msg);
}
}
bool ::luacpp::convert::popValue(lua_State* luaState, LuaFunction& target, int stackposition, bool remove) {
if (!internal::isValidIndex(luaState, stackposition)) {
return false;
}
if (!lua_isfunction(luaState, stackposition)) {
return false;
} else {
target.setReference(UniqueLuaReference::create(luaState, stackposition));
if (remove) {
lua_remove(luaState, stackposition);
}
return true;
}
}
}