Files
fs2open.github.com/code/scripting/lua/LuaValue.h
T
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

271 lines
6.3 KiB
C++

#ifndef LUAVALUE_H
#define LUAVALUE_H
#pragma once
#include "LuaConvert.h"
#include "LuaReference.h"
#include "LuaException.h"
#include "LuaHeaders.h"
#include <memory>
#include <cstdint>
#include <csetjmp>
namespace luacpp {
/**
* @brief The values of a LuaValue
*/
enum class ValueType {
NONE,
NIL,
BOOLEAN,
LIGHTUSERDATA,
STRING,
NUMBER,
TABLE,
FUNCTION,
USERDATA,
THREAD,
};
/**
* @brief Represents a Lua-value
*
* This class holds a reference to a lua value and provides type checking to ensure that the value is still the same.
*/
class LuaValue {
public:
/**
* @brief Creates a LuaValue.
* This is done by pushing the value onto the lua stack and creating a reference to it.
*
* @param state The lua state
* @param value The value to be referenced
* @return luacpp::LuaValue A new LuaValue instance which references the specified value
*/
template<class ValueType>
static LuaValue createValue(lua_State* state, ValueType&& value) {
LuaValue retVal(state);
convert::pushValue(state, std::forward<ValueType>(value));
retVal.setReference(UniqueLuaReference::create(state));
// Remove the value again
lua_pop(state, 1);
return retVal;
}
static LuaValue createNil(lua_State* L);
/**
* @brief Default constructor, creates an invalid LuaValue
*/
LuaValue();
/**
* @brief Initializes the lua value
*
* The instance does not point to a lua-value after the constructor has finished. To reference
* a value use #setReference(LuaReferencePtr)
*
* @param state The lua state
*/
LuaValue(lua_State* state);
/**
* @brief Copy-constructor
* @param other The other LuaValue.
* @note The noexcept is a promise that all members remain nothrow-copyable
* (currently a shared_ptr, a raw pointer, and an enum).
*/
LuaValue(const LuaValue& other) noexcept;
LuaValue& operator=(const LuaValue& other) noexcept;
/**
* @brief Move-constructor
* @param other The other LuaValue.
*/
LuaValue(LuaValue&& other) noexcept;
LuaValue& operator=(LuaValue&& other) noexcept;
/**
* @brief Releases the reference
*/
virtual ~LuaValue();
/**
* @brief Sets a new LuaReference.
*
* @param ref The new lua reference.
*/
virtual void setReference(const LuaReference& ref);
/**
* @brief Gets the LuaReference.
*
* This reference is used to reference the actual lua value.
*
* @return The LuaReference instance.
*/
virtual const LuaReference getReference() const;
/**
* @brief Gets the lua type of this value.
* @return One of the LUA_T* defines.
*/
virtual ValueType getValueType() const;
/**
* @brief Checks if the value is of the specified type.
*
* @return bool @c true when it is, @c false if it isn't.
*/
inline bool is(ValueType check) const { return _luaType == check; }
/**
* @brief Sets a new value, possible changing the type
*
* @param value The new value
*/
template<class Type>
void setValue(const Type& value) {
// Push the new value
convert::pushValue(_luaState, value);
// And create a reference for it
setReference(UniqueLuaReference::create(_luaState));
}
/**
* @brief Gets the value or throws an exception
*
* @return Type The value
*
* @exception LuaException Thrown when the conversion failed.
*/
template<class Type>
Type getValue() const {
_reference->pushValue(_luaState);
Type target;
if (!convert::popValue(_luaState, target)) {
lua_pop(_luaState, 1);
throw LuaException("Failed to pop value");
} else {
return target;
}
}
/**
* @brief Same as above but allows passing a reference to a value where to store the lua value
*
* @exception LuaException Thrown when the conversion failed.
*/
template<typename Type>
void getValue(Type&& od) const {
_reference->pushValue(_luaState);
if (!convert::popValue(_luaState, std::forward<Type>(od))) {
lua_pop(_luaState, 1);
throw LuaException("Failed to pop value");
}
}
/**
* @brief Specifies if the lua value is valid.
*
* @return bool @c true if it can be used and have an underlying reference, @c false otherwise.
*/
bool isValid() const;
/**
* @brief Pushes this lua value onto the stack.
* @param thread The thread stack onto which this value should be pushed. May be nullptr for the default state of
* this value
* @param manualStackAllocation Set to true if you manually allocate sufficient stack size before calling this function. Keep false unless you know what you are doing.
*/
bool pushValue(lua_State* thread, bool manualStackAllocation = false) const;
lua_State* getLuaState() const;
protected:
lua_State* _luaState{nullptr}; //!< The lua state of this value.
LuaReference _reference;
ValueType _luaType = ValueType::NONE;
};
/**
* @brief Checks for equality of the lua values.
* @param lhs The left value
* @param rhs The right lua value.
* @return `true` when the value are equal as specified by the lua "==" operator.
*/
template<typename Type>
bool operator==(const LuaValue& lhs, const Type& rhs) {
lhs.pushValue(lhs.getLuaState());
convert::pushValue(lhs.getLuaState(), rhs);
bool result = lua_equal(lhs.getLuaState(), -2, -1) != 0;
lua_pop(lhs.getLuaState(), 2);
return result;
}
/**
* @brief Checks if the other value is bigger than this value.
* @param lhs The left lua value.
* @param rhs The right lua value.
* @return `true` when the second value is bigger than this value as specified
* by lua.
*/
template<typename Type>
bool operator<(const LuaValue& lhs, const Type& rhs) {
lhs.pushValue(lhs.getLuaState());
convert::pushValue(lhs.getLuaState(), rhs);
bool result = lua_lessthan(lhs.getLuaState(), -2, -1) != 0;
lua_pop(lhs.getLuaState(), 2);
return result;
}
template<typename Type>
bool operator!=(const LuaValue& lhs, const Type& rhs) {
return !(lhs == rhs);
}
template<typename Type>
bool operator>(const LuaValue& lhs, const Type& rhs) {
return !(lhs <= rhs);
}
template<typename Type>
bool operator<=(const LuaValue& lhs, const Type& rhs) {
return (lhs == rhs) || (lhs < rhs);
}
template<typename Type>
bool operator>=(const LuaValue& lhs, const Type& rhs) {
return !(lhs < rhs);
}
namespace convert {
void pushValue(lua_State* luaState, const LuaValue& value);
bool popValue(lua_State* luaState, LuaValue& target, int stackposition = -1, bool remove = true);
}
}
#endif