// https://github.com/vinniefalco/LuaBridge
// Copyright 2019, Dmitry Tarakanov
// Copyright 2012, Vinnie Falco <vinnie.falco@gmail.com>
// SPDX-License-Identifier: MIT
#pragma once
#include <LuaBridge/detail/ClassInfo.h>
#include <LuaBridge/detail/TypeTraits.h>
#include <cassert>
#include <stdexcept>
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<Userdata*>(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<Userdata*>(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<class T>
static Userdata* getExact(lua_State* L, int index)
{
return getExactClass(L, index, detail::getClassRegistryKey<T>());
}
//--------------------------------------------------------------------------
/**
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<class T>
static T* get(lua_State* L, int index, bool canBeConst)
{
if (lua_isnil(L, index))
return 0;
return static_cast<T*>(getClass(L,
index,
detail::getConstRegistryKey<T>(),
detail::getClassRegistryKey<T>(),
canBeConst)
->getPointer());
}
template<class T>
static bool isInstance(lua_State* L, int index)
{
return isInstance(L, index, detail::getClassRegistryKey<T>());
}
};
//----------------------------------------------------------------------------
/**
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 T>
class UserdataValue : public Userdata
{
private:
UserdataValue(UserdataValue<T> const&);
UserdataValue<T> operator=(UserdataValue<T> 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<T>* place(lua_State* const L)
{
UserdataValue<T>* const ud =
new (lua_newuserdata(L, sizeof(UserdataValue<T>))) UserdataValue<T>();
lua_rawgetp(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey<T>());
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<class U>
static inline void push(lua_State* const L, U const& u)
{
UserdataValue<T>* 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<T*>(&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<void*>(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<class T>
static void push(lua_State* const L, T* const p)
{
if (p)
push(L, p, getClassRegistryKey<T>());
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<class T>
static void push(lua_State* const L, T const* const p)
{
if (p)
push(L, p, getConstRegistryKey<T>());
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 C>
class UserdataShared : public Userdata
{
private:
UserdataShared(UserdataShared<C> const&);
UserdataShared<C>& operator=(UserdataShared<C> const&);
typedef typename TypeTraits::removeConst<typename ContainerTraits<C>::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<class U>
explicit UserdataShared(U const& u) : m_c(u)
{
m_p = const_cast<void*>(reinterpret_cast<void const*>((ContainerTraits<C>::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<class U>
explicit UserdataShared(U* u) : m_c(u)
{
m_p = const_cast<void*>(reinterpret_cast<void const*>((ContainerTraits<C>::get(m_c))));
}
};
//----------------------------------------------------------------------------
//
// SFINAE helpers.
//
// non-const objects
template<class C, bool makeObjectConst>
struct UserdataSharedHelper
{
typedef typename TypeTraits::removeConst<typename ContainerTraits<C>::Type>::Type T;
static void push(lua_State* L, C const& c)
{
if (ContainerTraits<C>::get(c) != 0)
{
new (lua_newuserdata(L, sizeof(UserdataShared<C>))) UserdataShared<C>(c);
lua_rawgetp(L, LUA_REGISTRYINDEX, getClassRegistryKey<T>());
// 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<C>))) UserdataShared<C>(t);
lua_rawgetp(L, LUA_REGISTRYINDEX, getClassRegistryKey<T>());
// 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<class C>
struct UserdataSharedHelper<C, true>
{
typedef typename TypeTraits::removeConst<typename ContainerTraits<C>::Type>::Type T;
static void push(lua_State* L, C const& c)
{
if (ContainerTraits<C>::get(c) != 0)
{
new (lua_newuserdata(L, sizeof(UserdataShared<C>))) UserdataShared<C>(c);
lua_rawgetp(L, LUA_REGISTRYINDEX, getConstRegistryKey<T>());
// 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<C>))) UserdataShared<C>(t);
lua_rawgetp(L, LUA_REGISTRYINDEX, getConstRegistryKey<T>());
// 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<class C, bool byContainer>
struct StackHelper
{
static void push(lua_State* L, C const& c)
{
UserdataSharedHelper<C, TypeTraits::isConst<typename ContainerTraits<C>::Type>::value>::
push(L, c);
}
typedef typename TypeTraits::removeConst<typename ContainerTraits<C>::Type>::Type T;
static C get(lua_State* L, int index) { return Userdata::get<T>(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<class T>
struct StackHelper<T, false>
{
static inline void push(lua_State* L, T const& t) { UserdataValue<T>::push(L, t); }
static inline T const& get(lua_State* L, int index)
{
return *Userdata::get<T>(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<class C, bool byContainer>
struct RefStackHelper
{
typedef C return_type;
static inline void push(lua_State* L, C const& t)
{
UserdataSharedHelper<C, TypeTraits::isConst<typename ContainerTraits<C>::Type>::value>::
push(L, t);
}
typedef typename TypeTraits::removeConst<typename ContainerTraits<C>::Type>::Type T;
static return_type get(lua_State* L, int index) { return Userdata::get<T>(L, index, true); }
};
template<class T>
struct RefStackHelper<T, false>
{
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<T>(L, index, true);
if (!t)
luaL_error(L, "nil passed to reference");
return *t;
}
};
/**
* Voider class template. Used to force a comiler to instantiate
* an otherwise probably unused template parameter type T.
* See the C++20 std::void_t <> for details.
*/
template<class T>
struct Void
{
typedef void Type;
};
/**
* Trait class that selects whether to return a user registered
* class object by value or by reference.
*/
template<class T, class Enabler = void>
struct UserdataGetter
{
typedef T* ReturnType;
static ReturnType get(lua_State* L, int index) { return Userdata::get<T>(L, index, false); }
};
template<class T>
struct UserdataGetter<T, typename Void<T (*)()>::Type>
{
typedef T ReturnType;
static ReturnType get(lua_State* L, int index)
{
return StackHelper<T, TypeTraits::isContainer<T>::value>::get(L, index);
}
};
} // namespace detail
//==============================================================================
/**
Lua stack conversions for class objects passed by value.
*/
template<class T>
struct Stack
{
typedef void IsUserdata;
typedef detail::UserdataGetter<T> Getter;
typedef typename Getter::ReturnType ReturnType;
static void push(lua_State* L, T const& value)
{
using namespace detail;
StackHelper<T, TypeTraits::isContainer<T>::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<T>(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<class T, class Enable = void>
struct IsUserdata
{
static const bool value = false;
};
template<class T>
struct IsUserdata<T, typename Void<typename Stack<T>::IsUserdata>::Type>
{
static const bool value = true;
};
/**
* Trait class that selects a specific push/get implemenation.
*/
template<class T, bool isUserdata>
struct StackOpSelector;
// pointer
template<class T>
struct StackOpSelector<T*, true>
{
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<T>(L, index, false); }
static bool isInstance(lua_State* L, int index) { return Userdata::isInstance<T>(L, index); }
};
// pointer to const
template<class T>
struct StackOpSelector<const T*, true>
{
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<T>(L, index, true); }
static bool isInstance(lua_State* L, int index) { return Userdata::isInstance<T>(L, index); }
};
// reference
template<class T>
struct StackOpSelector<T&, true>
{
typedef RefStackHelper<T, TypeTraits::isContainer<T>::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<T>(L, index); }
};
// reference to const
template<class T>
struct StackOpSelector<const T&, true>
{
typedef RefStackHelper<T, TypeTraits::isContainer<T>::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<T>(L, index); }
};
} // namespace detail
} // namespace luabridge