// https://github.com/vinniefalco/LuaBridge // Copyright 2019, Dmitry Tarakanov // Copyright 2012, Vinnie Falco // SPDX-License-Identifier: MIT #pragma once #include #include #include #include namespace luabridge { namespace detail { //============================================================================== /** Return the identity pointer for our lightuserdata tokens. Because of Lua's dynamic typing and our improvised system of imposing C++ class structure, there is the possibility that executing scripts may knowingly or unknowingly cause invalid data to get passed to the C functions created by LuaBridge. In particular, our security model addresses the following: 1. Scripts cannot create a userdata (ignoring the debug lib). 2. Scripts cannot create a lightuserdata (ignoring the debug lib). 3. Scripts cannot set the metatable on a userdata. */ /** Interface to a class pointer retrievable from a userdata. */ class Userdata { protected: void* m_p; // subclasses must set this Userdata() : m_p(0) {} //-------------------------------------------------------------------------- /** Get an untyped pointer to the contained class. */ void* getPointer() { return m_p; } private: //-------------------------------------------------------------------------- /** Validate and retrieve a Userdata on the stack. The Userdata must exactly match the corresponding class table or const table, or else a Lua error is raised. This is used for the __gc metamethod. */ static Userdata* getExactClass(lua_State* L, int index, void const* /*classKey*/) { return static_cast(lua_touserdata(L, lua_absindex(L, index))); } //-------------------------------------------------------------------------- /** Validate and retrieve a Userdata on the stack. The Userdata must be derived from or the same as the given base class, identified by the key. If canBeConst is false, generates an error if the resulting Userdata represents to a const object. We do the type check first so that the error message is informative. */ static Userdata* getClass(lua_State* L, int index, void const* registryConstKey, void const* registryClassKey, bool canBeConst) { index = lua_absindex(L, index); lua_getmetatable(L, index); // Stack: object metatable (ot) | nil if (!lua_istable(L, -1)) { lua_rawgetp( L, LUA_REGISTRYINDEX, registryClassKey); // Stack: registry metatable (rt) | nil return throwBadArg(L, index); } lua_rawgetp(L, -1, getConstKey()); // Stack: ot | nil, const table (co) | nil assert(lua_istable(L, -1) || lua_isnil(L, -1)); // If const table is NOT present, object is const. Use non-const registry table // if object cannot be const, so constness validation is done automatically. // E.g. nonConstFn (constObj) // -> canBeConst = false, isConst = true // -> 'Class' registry table, 'const Class' object table // -> 'expected Class, got const Class' bool isConst = lua_isnil(L, -1); // Stack: ot | nil, nil, rt if (isConst && canBeConst) { lua_rawgetp(L, LUA_REGISTRYINDEX, registryConstKey); // Stack: ot, nil, rt } else { lua_rawgetp(L, LUA_REGISTRYINDEX, registryClassKey); // Stack: ot, co, rt } lua_insert(L, -3); // Stack: rt, ot, co | nil lua_pop(L, 1); // Stack: rt, ot for (;;) { if (lua_rawequal(L, -1, -2)) // Stack: rt, ot { lua_pop(L, 2); // Stack: - return static_cast(lua_touserdata(L, index)); } // Replace current metatable with it's base class. lua_rawgetp(L, -1, getParentKey()); // Stack: rt, ot, parent ot (pot) | nil if (lua_isnil(L, -1)) // Stack: rt, ot, nil { // Drop the object metatable because it may be some parent metatable lua_pop(L, 2); // Stack: rt return throwBadArg(L, index); } lua_remove(L, -2); // Stack: rt, pot } // no return } static bool isInstance(lua_State* L, int index, void const* registryClassKey) { index = lua_absindex(L, index); int result = lua_getmetatable(L, index); // Stack: object metatable (ot) | nothing if (result == 0) { return false; // Nothing was pushed on the stack } if (!lua_istable(L, -1)) { lua_pop(L, 1); // Stack: - return false; } lua_rawgetp(L, LUA_REGISTRYINDEX, registryClassKey); // Stack: ot, rt lua_insert(L, -2); // Stack: rt, ot for (;;) { if (lua_rawequal(L, -1, -2)) // Stack: rt, ot { lua_pop(L, 2); // Stack: - return true; } // Replace current metatable with it's base class. lua_rawgetp(L, -1, getParentKey()); // Stack: rt, ot, parent ot (pot) | nil if (lua_isnil(L, -1)) // Stack: rt, ot, nil { lua_pop(L, 3); // Stack: - return false; } lua_remove(L, -2); // Stack: rt, pot } } static Userdata* throwBadArg(lua_State* L, int index) { assert(lua_istable(L, -1) || lua_isnil(L, -1)); // Stack: rt | nil const char* expected = 0; if (lua_isnil(L, -1)) // Stack: nil { expected = "unregistered class"; } else { lua_rawgetp(L, -1, getTypeKey()); // Stack: rt, registry type expected = lua_tostring(L, -1); } const char* got = 0; if (lua_isuserdata(L, index)) { lua_getmetatable(L, index); // Stack: ..., ot | nil if (lua_istable(L, -1)) // Stack: ..., ot { lua_rawgetp(L, -1, getTypeKey()); // Stack: ..., ot, object type | nil if (lua_isstring(L, -1)) { got = lua_tostring(L, -1); } } } if (!got) { got = lua_typename(L, lua_type(L, index)); } luaL_argerror(L, index, lua_pushfstring(L, "%s expected, got %s", expected, got)); return 0; } public: virtual ~Userdata() {} //-------------------------------------------------------------------------- /** Returns the Userdata* if the class on the Lua stack matches. If the class does not match, a Lua error is raised. @tparam T A registered user class. @param L A Lua state. @param index The index of an item on the Lua stack. @returns A userdata pointer if the class matches. */ template static Userdata* getExact(lua_State* L, int index) { return getExactClass(L, index, detail::getClassRegistryKey()); } //-------------------------------------------------------------------------- /** Get a pointer to the class from the Lua stack. If the object is not the class or a subclass, or it violates the const-ness, a Lua error is raised. @tparam T A registered user class. @param L A Lua state. @param index The index of an item on the Lua stack. @param canBeConst TBD @returns A pointer if the class and constness match. */ template static T* get(lua_State* L, int index, bool canBeConst) { if (lua_isnil(L, index)) return 0; return static_cast(getClass(L, index, detail::getConstRegistryKey(), detail::getClassRegistryKey(), canBeConst) ->getPointer()); } template static bool isInstance(lua_State* L, int index) { return isInstance(L, index, detail::getClassRegistryKey()); } }; //---------------------------------------------------------------------------- /** Wraps a class object stored in a Lua userdata. The lifetime of the object is managed by Lua. The object is constructed inside the userdata using placement new. */ template class UserdataValue : public Userdata { private: UserdataValue(UserdataValue const&); UserdataValue operator=(UserdataValue const&); char m_storage[sizeof(T)]; private: /** Used for placement construction. */ UserdataValue() { m_p = 0; } ~UserdataValue() { if (getPointer() != 0) { getObject()->~T(); } } public: /** Push a T via placement new. The caller is responsible for calling placement new using the returned uninitialized storage. @param L A Lua state. @returns An object referring to the newly created userdata value. */ static UserdataValue* place(lua_State* const L) { UserdataValue* const ud = new (lua_newuserdata(L, sizeof(UserdataValue))) UserdataValue(); lua_rawgetp(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); if (!lua_istable(L, -1)) { throw std::logic_error("The class is not registered in LuaBridge"); } lua_setmetatable(L, -2); return ud; } /** Push T via copy construction from U. @tparam U A container type. @param L A Lua state. @param u A container object reference. */ template static inline void push(lua_State* const L, U const& u) { UserdataValue* ud = place(L); new (ud->getObject()) U(u); ud->commit(); } /** Confirm object construction. */ void commit() { m_p = getObject(); } T* getObject() { // If this fails to compile it means you forgot to provide // a Container specialization for your container! // return reinterpret_cast(&m_storage[0]); } }; //---------------------------------------------------------------------------- /** Wraps a pointer to a class object inside a Lua userdata. The lifetime of the object is managed by C++. */ class UserdataPtr : public Userdata { private: UserdataPtr(UserdataPtr const&); UserdataPtr operator=(UserdataPtr const&); private: /** Push a pointer to object using metatable key. */ static void push(lua_State* L, const void* p, void const* const key) { new (lua_newuserdata(L, sizeof(UserdataPtr))) UserdataPtr(const_cast(p)); lua_rawgetp(L, LUA_REGISTRYINDEX, key); if (!lua_istable(L, -1)) { lua_pop(L, 1); // possibly: a nil throw std::logic_error("The class is not registered in LuaBridge"); } lua_setmetatable(L, -2); } explicit UserdataPtr(void* const p) { m_p = p; // Can't construct with a null pointer! // assert(m_p != 0); } public: /** Push non-const pointer to object. @tparam T A user registered class. @param L A Lua state. @param p A pointer to the user class instance. */ template static void push(lua_State* const L, T* const p) { if (p) push(L, p, getClassRegistryKey()); else lua_pushnil(L); } /** Push const pointer to object. @tparam T A user registered class. @param L A Lua state. @param p A pointer to the user class instance. */ template static void push(lua_State* const L, T const* const p) { if (p) push(L, p, getConstRegistryKey()); else lua_pushnil(L); } }; //============================================================================ /** Wraps a container that references a class object. The template argument C is the container type, ContainerTraits must be specialized on C or else a compile error will result. */ template class UserdataShared : public Userdata { private: UserdataShared(UserdataShared const&); UserdataShared& operator=(UserdataShared const&); typedef typename TypeTraits::removeConst::Type>::Type T; C m_c; private: ~UserdataShared() {} public: /** Construct from a container to the class or a derived class. @tparam U A container type. @param u A container object reference. */ template explicit UserdataShared(U const& u) : m_c(u) { m_p = const_cast(reinterpret_cast((ContainerTraits::get(m_c)))); } /** Construct from a pointer to the class or a derived class. @tparam U A container type. @param u A container object pointer. */ template explicit UserdataShared(U* u) : m_c(u) { m_p = const_cast(reinterpret_cast((ContainerTraits::get(m_c)))); } }; //---------------------------------------------------------------------------- // // SFINAE helpers. // // non-const objects template struct UserdataSharedHelper { typedef typename TypeTraits::removeConst::Type>::Type T; static void push(lua_State* L, C const& c) { if (ContainerTraits::get(c) != 0) { new (lua_newuserdata(L, sizeof(UserdataShared))) UserdataShared(c); lua_rawgetp(L, LUA_REGISTRYINDEX, getClassRegistryKey()); // If this goes off it means the class T is unregistered! assert(lua_istable(L, -1)); lua_setmetatable(L, -2); } else { lua_pushnil(L); } } static void push(lua_State* L, T* const t) { if (t) { new (lua_newuserdata(L, sizeof(UserdataShared))) UserdataShared(t); lua_rawgetp(L, LUA_REGISTRYINDEX, getClassRegistryKey()); // If this goes off it means the class T is unregistered! assert(lua_istable(L, -1)); lua_setmetatable(L, -2); } else { lua_pushnil(L); } } }; // const objects template struct UserdataSharedHelper { typedef typename TypeTraits::removeConst::Type>::Type T; static void push(lua_State* L, C const& c) { if (ContainerTraits::get(c) != 0) { new (lua_newuserdata(L, sizeof(UserdataShared))) UserdataShared(c); lua_rawgetp(L, LUA_REGISTRYINDEX, getConstRegistryKey()); // If this goes off it means the class T is unregistered! assert(lua_istable(L, -1)); lua_setmetatable(L, -2); } else { lua_pushnil(L); } } static void push(lua_State* L, T* const t) { if (t) { new (lua_newuserdata(L, sizeof(UserdataShared))) UserdataShared(t); lua_rawgetp(L, LUA_REGISTRYINDEX, getConstRegistryKey()); // If this goes off it means the class T is unregistered! assert(lua_istable(L, -1)); lua_setmetatable(L, -2); } else { lua_pushnil(L); } } }; /** Pass by container. The container controls the object lifetime. Typically this will be a lifetime shared by C++ and Lua using a reference count. Because of type erasure, containers like std::shared_ptr will not work. Containers must either be of the intrusive variety, or in the style of the RefCountedPtr type provided by LuaBridge (that uses a global hash table). */ template struct StackHelper { static void push(lua_State* L, C const& c) { UserdataSharedHelper::Type>::value>:: push(L, c); } typedef typename TypeTraits::removeConst::Type>::Type T; static C get(lua_State* L, int index) { return Userdata::get(L, index, true); } }; /** Pass by value. Lifetime is managed by Lua. A C++ function which accesses a pointer or reference to an object outside the activation record in which it was retrieved may result in undefined behavior if Lua garbage collected it. */ template struct StackHelper { static inline void push(lua_State* L, T const& t) { UserdataValue::push(L, t); } static inline T const& get(lua_State* L, int index) { return *Userdata::get(L, index, true); } }; //------------------------------------------------------------------------------ /** Lua stack conversions for pointers and references to class objects. Lifetime is managed by C++. Lua code which remembers a reference to the value may result in undefined behavior if C++ destroys the object. The handling of the const and volatile qualifiers happens in UserdataPtr. */ template struct RefStackHelper { typedef C return_type; static inline void push(lua_State* L, C const& t) { UserdataSharedHelper::Type>::value>:: push(L, t); } typedef typename TypeTraits::removeConst::Type>::Type T; static return_type get(lua_State* L, int index) { return Userdata::get(L, index, true); } }; template struct RefStackHelper { typedef T& return_type; static void push(lua_State* L, T const& t) { UserdataPtr::push(L, &t); } static return_type get(lua_State* L, int index) { T* t = Userdata::get(L, index, true); if (!t) luaL_error(L, "nil passed to reference"); return *t; } }; /** * Voider class template. Used to force a compiler to instantiate * an otherwise probably unused template parameter type T. * See the C++20 std::void_t <> for details. */ template struct Void { typedef void Type; }; /** * Trait class that selects whether to return a user registered * class object by value or by reference. */ template struct UserdataGetter { typedef T* ReturnType; static ReturnType get(lua_State* L, int index) { return Userdata::get(L, index, false); } }; template struct UserdataGetter::Type> { typedef T ReturnType; static ReturnType get(lua_State* L, int index) { return StackHelper::value>::get(L, index); } }; } // namespace detail //============================================================================== /** Lua stack conversions for class objects passed by value. */ template struct Stack { typedef void IsUserdata; typedef detail::UserdataGetter Getter; typedef typename Getter::ReturnType ReturnType; static void push(lua_State* L, T const& value) { using namespace detail; StackHelper::value>::push(L, value); } static ReturnType get(lua_State* L, int index) { return Getter::get(L, index); } static bool isInstance(lua_State* L, int index) { return detail::Userdata::isInstance(L, index); } }; namespace detail { /** * Trait class indicating whether the parameter type must be * a user registered class. The trait checks the existence of * member type Stack::IsUserdata specialization for detection. */ template struct IsUserdata { static const bool value = false; }; template struct IsUserdata::IsUserdata>::Type> { static const bool value = true; }; /** * Trait class that selects a specific push/get implementation. */ template struct StackOpSelector; // pointer template struct StackOpSelector { typedef T* ReturnType; static void push(lua_State* L, T* value) { UserdataPtr::push(L, value); } static T* get(lua_State* L, int index) { return Userdata::get(L, index, false); } static bool isInstance(lua_State* L, int index) { return Userdata::isInstance(L, index); } }; // pointer to const template struct StackOpSelector { typedef const T* ReturnType; static void push(lua_State* L, const T* value) { UserdataPtr::push(L, value); } static const T* get(lua_State* L, int index) { return Userdata::get(L, index, true); } static bool isInstance(lua_State* L, int index) { return Userdata::isInstance(L, index); } }; // reference template struct StackOpSelector { typedef RefStackHelper::value> Helper; typedef typename Helper::return_type ReturnType; static void push(lua_State* L, T& value) { UserdataPtr::push(L, &value); } static ReturnType get(lua_State* L, int index) { return Helper::get(L, index); } static bool isInstance(lua_State* L, int index) { return Userdata::isInstance(L, index); } }; // reference to const template struct StackOpSelector { typedef RefStackHelper::value> Helper; typedef typename Helper::return_type ReturnType; static void push(lua_State* L, const T& value) { Helper::push(L, value); } static ReturnType get(lua_State* L, int index) { return Helper::get(L, index); } static bool isInstance(lua_State* L, int index) { return Userdata::isInstance(L, index); } }; } // namespace detail } // namespace luabridge