1
0
Files
envoy-apisix/envoy/lua.h
2023-04-14 17:12:13 +08:00

292 lines
9.7 KiB
C++

#pragma once
#include <vector>
#include <memory>
#include <lua.hpp>
#define ASSERT(...)
#define UNUSED(x) (void)(x)
#ifndef alignof
#define alignof(T) std::alignment_of<T>::value
#endif
namespace lua {
/**
* Base macro for declaring a Lua/C function. Any function declared will need to be exported via
* the exportedFunctions() function in BaseLuaObject. See BaseLuaObject below for more
* information. This macro declares a static "thunk" which checks the user data, optionally checks
* for object death (again see BaseLuaObject below for more info), and then invokes a normal
* object method. The actual object method needs to be implemented by the class.
* @param Class supplies the owning class name.
* @param Name supplies the function name.
* @param Index supplies the stack index where "this" (Lua/C userdata) is found.
*/
#define DECLARE_LUA_FUNCTION_EX(Class, Name, Index) \
static int static_##Name(lua_State* state) { \
Class* object = lua::alignAndCast<Class>( \
luaL_checkudata(state, Index, typeid(Class).name())); \
object->checkDead(state); \
return object->Name(state); \
} \
int Name(lua_State* state);
/**
* Declare a Lua function in which userdata is in stack slot 1. See DECLARE_LUA_FUNCTION_EX()
*/
#define DECLARE_LUA_FUNCTION(Class, Name) DECLARE_LUA_FUNCTION_EX(Class, Name, 1)
/**
* Declare a Lua function in which userdata is in upvalue slot 1. See DECLARE_LUA_FUNCTION_EX()
*/
#define DECLARE_LUA_CLOSURE(Class, Name) DECLARE_LUA_FUNCTION_EX(Class, Name, lua_upvalueindex(1))
/**
* Calculate the maximum space needed to be aligned.
*/
template <typename T> size_t maximumSpaceNeededToAlign() {
// The allocated memory can be misaligned up to `alignof(T) - 1` bytes. Adding it to the size to
// allocate.
return sizeof(T) + alignof(T) - 1;
}
template <typename T> inline T* alignAndCast(void* mem) {
size_t size = maximumSpaceNeededToAlign<T>();
return static_cast<T*>(std::align(alignof(T), sizeof(T), mem, size));
}
/**
* Create a new user data and assign its metatable.
*/
template <typename T> inline T* allocateLuaUserData(lua_State* state) {
void* mem = lua_newuserdata(state, maximumSpaceNeededToAlign<T>());
luaL_getmetatable(state, typeid(T).name());
ASSERT(lua_istable(state, -1));
lua_setmetatable(state, -2);
return alignAndCast<T>(mem);
}
/**
* This is the base class for all C++ objects that we expose out to Lua. The goal is to hide as
* much ugliness as possible. In general, to use this, do the following:
* 1) Make your class derive from BaseLuaObject<YourClass>
* 2) Define methods using DECLARE_LUA_FUNCTION* macros
* 3) Export your functions by declaring a static exportedFunctions() method in your class.
* 4) Optionally manage "death" status on your object. (See checkDead() and markDead() below).
* 5) Generally you will want to hold your objects inside a LuaRef or a LuaDeathRef. See below
* for more information on those containers.
*
* It's very important to understand the Lua memory model: Once an object is created, *it is
* owned by Lua*. Lua can GC it at any time. If you want to make sure that does not happen, you
* must hold a ref to it in C++, generally via LuaRef or LuaDeathRef.
*/
template <class T> class BaseLuaObject {
public:
using ExportedFunctions = std::vector<std::pair<const char*, lua_CFunction>>;
virtual ~BaseLuaObject() = default;
/**
* Create a new object of this type, owned by Lua. This type must have previously been registered
* via the registerType() routine below.
* @param state supplies the owning Lua state.
* @param args supplies the variadic constructor arguments for the object.
* @return a pair containing a pointer to the new object and the state it was created with. (This
* is done for convenience when passing a created object to a LuaRef or a LuaDeathRef.
*/
template <typename... ConstructorArgs>
static std::pair<T*, lua_State*> create(lua_State* state, ConstructorArgs&&... args) {
// Memory is allocated via Lua and it is raw. We use placement new to run the constructor.
T* mem = allocateLuaUserData<T>(state);
return {new (mem) T(std::forward<ConstructorArgs>(args)...), state};
}
/**
* Register a type with Lua.
* @param state supplies the state to register with.
*/
static void registerType(lua_State* state) {
std::vector<luaL_Reg> to_register;
// Fetch all of the functions to be exported to Lua so that we can register them in the
// metatable.
ExportedFunctions functions = T::exportedFunctions();
for (auto function : functions) {
to_register.push_back({function.first, function.second});
}
// Always register a __gc method so that we can run the object's destructor. We do this
// manually because the memory is raw and was allocated by Lua.
to_register.push_back(
{"__gc", [](lua_State* state) {
T* object = alignAndCast<T>(luaL_checkudata(state, 1, typeid(T).name()));
object->~T();
return 0;
}});
// Add the sentinel.
to_register.push_back({nullptr, nullptr});
// Register the type by creating a new metatable, setting __index to itself, and then
// performing the register.
luaL_newmetatable(state, typeid(T).name());
lua_pushvalue(state, -1);
lua_setfield(state, -2, "__index");
luaL_register(state, nullptr, to_register.data());
}
/**
* This function is called as part of the DECLARE_LUA_FUNCTION* macros. The idea here is that
* we cannot control when Lua destroys things. However, we may expose wrappers to a script that
* should not be used after some event. This allows us to mark objects as dead so that if they
* are used again they will throw a Lua error and not reach our code.
* @param state supplies the calling LuaState.
*/
int checkDead(lua_State* state) {
if (dead_) {
return luaL_error(state, "object used outside of proper scope");
}
return 0;
}
/**
* Mark an object as dead so that a checkDead() call will throw an error. See checkDead().
*/
void markDead() {
dead_ = true;
onMarkDead();
}
/**
* Mark an object as live so that a checkDead() call will not throw an error. See checkDead().
*/
void markLive() {
dead_ = false;
onMarkLive();
}
protected:
/**
* Called from markDead() when an object is marked dead. This is effectively a C++ destructor for
* Lua/C objects. Objects can perform inline cleanup or mark other objects as dead if needed. It
* can also be used to protect objects from use if they get assigned to a global variable and
* used across coroutines.
*/
virtual void onMarkDead() {}
/**
* Called from markLive() when an object is marked live. This is a companion to onMarkDead(). See
* the comments there.
*/
virtual void onMarkLive() {}
private:
bool dead_{};
};
/**
* This is basically a Lua smart pointer. The idea is that given a Lua object, if we want to
* guarantee that Lua won't destroy it, we need to reference it. This wraps the reference
* functionality. While a LuaRef owns an object it's guaranteed that Lua will not GC it.
* TODO(mattklein123): Add dedicated unit tests. This will require mocking a Lua state.
*/
template <typename T> class LuaRef {
public:
/**
* Create an empty LuaRef.
*/
LuaRef() { reset(); }
/**
* Create a LuaRef from an object.
* @param object supplies the object. Generally this is the return value from a Object::create()
* call. The object must be at the top of the Lua stack.
* @param leave_on_stack supplies whether to leave the object on the stack or not when the ref
* is constructed.
*/
LuaRef(const std::pair<T*, lua_State*>& object, bool leave_on_stack) {
reset(object, leave_on_stack);
}
~LuaRef() { unref(); }
T* get() { return object_.first; }
/**
* Same as the LuaRef non-default constructor, but post-construction.
*/
void reset(const std::pair<T*, lua_State*>& object, bool leave_on_stack) {
unref();
if (leave_on_stack) {
lua_pushvalue(object.second, -1);
}
object_ = object;
ref_ = luaL_ref(object_.second, LUA_REGISTRYINDEX);
ASSERT(ref_ != LUA_REFNIL);
}
/**
* Return a LuaRef to its default/empty state.
*/
void reset() {
unref();
object_ = std::pair<T*, lua_State*>{};
ref_ = LUA_NOREF;
}
/**
* Push the referenced object back onto the stack.
*/
void pushStack() {
ASSERT(object_.first);
lua_rawgeti(object_.second, LUA_REGISTRYINDEX, ref_);
}
protected:
void unref() {
if (object_.second != nullptr) {
luaL_unref(object_.second, LUA_REGISTRYINDEX, ref_);
}
}
std::pair<T*, lua_State*> object_;
int ref_;
};
/**
* This is a variant of LuaRef which also marks an object as dead during destruction. This is
* useful if an object should not be used after the scope of the pcall() or resume().
* TODO(mattklein123): Add dedicated unit tests. This will require mocking a Lua state.
*/
template <typename T> class LuaDeathRef : public LuaRef<T> {
public:
~LuaDeathRef() { markDead(); }
void markDead() {
if (this->object_.first) {
this->object_.first->markDead();
}
}
void markLive() {
if (this->object_.first) {
this->object_.first->markLive();
}
}
void reset(const std::pair<T*, lua_State*>& object, bool leave_on_stack) {
markDead();
LuaRef<T>::reset(object, leave_on_stack);
}
void reset() {
markDead();
LuaRef<T>::reset();
}
};
}