traintastic/server/src/lua/sandbox.cpp
Reinder Feenstra 246b27c434 moved ObjectProperty<> implementation to seperate file
world.hpp now uses forward decl. for many classes, this reduces built time!
see #43
2023-03-30 15:09:11 +02:00

303 Zeilen
7.7 KiB
C++

/**
* server/src/lua/sandbox.cpp - Lua sandbox
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2022 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "sandbox.hpp"
#include "push.hpp"
#include "object.hpp"
#include "method.hpp"
#include "event.hpp"
#include "eventhandler.hpp"
#include "log.hpp"
#include "class.hpp"
#include "to.hpp"
#include "type.hpp"
#include "enums.hpp"
#include "sets.hpp"
#include "getversion.hpp"
#include "script.hpp"
#include <version.hpp>
#include <traintastic/utils/str.hpp>
#include "../world/world.hpp"
#define LUA_SANDBOX "_sandbox"
#define LUA_SANDBOX_GLOBALS "_sandbox_globals"
constexpr std::array<std::string_view, 23> readOnlyGlobals = {{
// Lua baselib:
"assert",
"type",
"pairs",
"ipairs",
"next",
"tonumber",
"tostring",
"_G",
// Lua libs:
LUA_MATHLIBNAME,
LUA_STRLIBNAME,
LUA_TABLIBNAME,
// Constants:
"VERSION",
"VERSION_MAJOR",
"VERSION_MINOR",
"VERSION_PATCH",
"CODENAME",
"LUA_VERSION",
// Objects:
"world",
"log",
// Functions:
"is_instance",
// Type info:
"class",
"enum",
"set",
}};
static void addExtensions(lua_State* L, std::initializer_list<std::pair<const char*, lua_CFunction>> extensions)
{
assert(lua_istable(L, -1));
for(auto [name, func] : extensions)
{
lua_pushcfunction(L, func);
lua_setfield(L, -2, name);
}
}
static void addBaseLib(lua_State* L, std::initializer_list<const char*> names, std::initializer_list<std::pair<const char*, lua_CFunction>> extensions = {})
{
// load Lua baselib:
lua_pushcfunction(L, luaopen_base);
lua_pushliteral(L, "");
lua_call(L, 1, 0);
for(const char* name : names)
{
lua_getglobal(L, name);
assert(!lua_isnil(L, -1));
lua_setfield(L, -2, name);
}
addExtensions(L, extensions);
}
static void addLib(lua_State* L, const char* libraryName, lua_CFunction openFunction, std::initializer_list<const char*> names, std::initializer_list<std::pair<const char*, lua_CFunction>> extensions = {})
{
lua_createtable(L, 0, names.size() + extensions.size());
luaL_requiref(L, libraryName, openFunction, 1);
for(const char* name : names)
{
lua_getfield(L, -1, name);
assert(!lua_isnil(L, -1));
lua_setfield(L, -3, name);
}
lua_pop(L, 1); // pop lib
addExtensions(L, extensions);
Lua::ReadOnlyTable::wrap(L, -1);
lua_setfield(L, -2, libraryName);
}
namespace Lua {
void Sandbox::close(lua_State* L)
{
delete *static_cast<StateData**>(lua_getextraspace(L)); // free state data
lua_close(L);
}
int Sandbox::__index(lua_State* L)
{
lua_getglobal(L, LUA_SANDBOX_GLOBALS);
lua_replace(L, 1);
lua_rawget(L, 1);
return 1;
}
int Sandbox::__newindex(lua_State* L)
{
if(std::find(readOnlyGlobals.begin(), readOnlyGlobals.end(), to<std::string_view>(L, 2)) != readOnlyGlobals.end())
errorGlobalNIsReadOnly(L, lua_tostring(L, 2));
lua_getglobal(L, LUA_SANDBOX_GLOBALS);
lua_replace(L, 1);
lua_rawset(L, 1);
return 0;
}
SandboxPtr Sandbox::create(Script& script)
{
lua_State* L = luaL_newstate();
// create state data:
*static_cast<StateData**>(lua_getextraspace(L)) = new StateData(script);
// register types:
Enums::registerTypes<LUA_ENUMS>(L);
Sets::registerTypes<LUA_SETS>(L);
Object::registerType(L);
Method::registerType(L);
Event::registerType(L);
// setup sandbox:
lua_newtable(L);
luaL_newmetatable(L, LUA_SANDBOX);
lua_pushcfunction(L, __index);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, __newindex);
lua_setfield(L, -2, "__newindex");
lua_setmetatable(L, -2);
lua_setglobal(L, LUA_SANDBOX);
// setup globals:
lua_newtable(L);
// add standard Lua lib functions and extensions/replacements:
addBaseLib(L,
{
"assert", "pairs", "ipairs", "next", "tonumber", "tostring",
},
{
{"type", type},
});
addLib(L, LUA_MATHLIBNAME, luaopen_math, {
"abs", "acos", "asin", "atan", "ceil", "cos", "deg", "exp",
"floor", "fmod", "huge", "log", "max", "maxinteger", "min", "mininteger",
"modf", "pi", "rad", "random", "randomseed", "sin", "sqrt", "tan",
"tointeger", "type", "ult",
});
addLib(L, LUA_STRLIBNAME, luaopen_string, {
"byte", "char", "find", "format", "gmatch", "gsub", "len", "lower",
"match", "pack", "packsize", "rep", "reverse", "sub", "unpack", "upper",
});
addLib(L, LUA_TABLIBNAME, luaopen_table, {
"concat", "insert", "pack", "unpack", "remove", "move", "sort",
});
// set VERSION:
lua_pushstring(L, TRAINTASTIC_VERSION_FULL);
lua_setfield(L, -2, "VERSION");
lua_pushinteger(L, TRAINTASTIC_VERSION_MAJOR);
lua_setfield(L, -2, "VERSION_MAJOR");
lua_pushinteger(L, TRAINTASTIC_VERSION_MINOR);
lua_setfield(L, -2, "VERSION_MINOR");
lua_pushinteger(L, TRAINTASTIC_VERSION_PATCH);
lua_setfield(L, -2, "VERSION_PATCH");
// set LUA_VERSION
push(L, getVersion());
lua_setfield(L, -2, "LUA_VERSION");
// add world:
push(L, script.world().shared_from_this());
lua_setfield(L, -2, "world");
// add logger:
Log::push(L);
lua_setfield(L, -2, "log");
// add class types:
lua_newtable(L);
Class::registerValues(L);
ReadOnlyTable::wrap(L, -1);
lua_setfield(L, -2, "class");
// add enum values:
lua_newtable(L);
Enums::registerValues<LUA_ENUMS>(L);
ReadOnlyTable::wrap(L, -1);
lua_setfield(L, -2, "enum");
// add set values:
lua_newtable(L);
Sets::registerValues<LUA_SETS>(L);
ReadOnlyTable::wrap(L, -1);
lua_setfield(L, -2, "set");
// let global _G point to the sandbox:
lua_getglobal(L, LUA_SANDBOX);
lua_setfield(L, -2, "_G");
lua_setglobal(L, LUA_SANDBOX_GLOBALS);
return SandboxPtr(L, close);
}
Sandbox::StateData& Sandbox::getStateData(lua_State* L)
{
return **static_cast<StateData**>(lua_getextraspace(L));
}
int Sandbox::getGlobal(lua_State* L, const char* name)
{
lua_getglobal(L, LUA_SANDBOX_GLOBALS); // get the sandbox
lua_pushstring(L, name);
const int type = lua_gettable(L, -2); // get item
lua_insert(L, lua_gettop(L) - 1); // swap item and sandbox on the stack
lua_pop(L, 1); // remove sandbox from the stack
return type;
}
int Sandbox::pcall(lua_State* L, int nargs, int nresults, int errfunc)
{
// check if the function has _ENV as first upvalue
// if so, replace it by the sandbox
// NOTE: functions which don't use any globals, don't have an _ENV !!
assert(lua_isfunction(L, -(1 + nargs)));
const char* name = lua_getupvalue(L, -(1 + nargs), 1);
if(name)
lua_pop(L, 1); // remove upvalue from stack
if(name && strcmp(name, "_ENV") == 0)
{
lua_getglobal(L, LUA_SANDBOX); // get the sandbox
assert(lua_istable(L, -1));
if(!lua_setupvalue(L, -(2 + nargs), 1)) // change _ENV to the sandbox
{
assert(false); // should never happen
lua_pop(L, 2 + nargs); // clear stack
lua_pushliteral(L, "Internal error @ " __FILE__ ":" STR(__LINE__));
return LUA_ERRRUN;
}
}
return lua_pcall(L, nargs, nresults, errfunc);
}
Sandbox::StateData::~StateData()
{
while(!m_eventHandlers.empty())
{
auto handler = m_eventHandlers.begin()->second;
m_eventHandlers.erase(m_eventHandlers.begin());
handler->disconnect();
}
}
}