fix(lua) (#1697)
- sandbox stuff. - Fix lua scripts that could run when their lua state was destroyed, causing chaos.
This commit is contained in:
parent
f09b1cbda3
commit
f26514f8e4
@ -20,12 +20,20 @@ namespace lua::script
|
||||
// Class: script_util
|
||||
// Name: yield
|
||||
// Yield execution.
|
||||
int yield()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Lua API: Function
|
||||
// Class: script_util
|
||||
// Name: sleep
|
||||
// Param: ms: integer: The amount of time in milliseconds that we will sleep for.
|
||||
// Sleep for the given amount of time, time is in milliseconds.
|
||||
int sleep(int ms)
|
||||
{
|
||||
return ms;
|
||||
}
|
||||
};
|
||||
static script_util dummy_script_util;
|
||||
|
||||
@ -62,12 +70,16 @@ namespace lua::script
|
||||
// ENTITY.DELETE_ENTITY(spawnedVehicle)
|
||||
// end)
|
||||
// ```
|
||||
static void register_looped(const std::string& name, sol::coroutine func, sol::this_state state)
|
||||
static void register_looped(const std::string& name, sol::function func_, sol::this_state state)
|
||||
{
|
||||
auto module = sol::state_view(state)["!this"].get<big::lua_module*>();
|
||||
|
||||
module->m_registered_scripts.push_back(big::g_script_mgr.add_script(std::make_unique<big::script>(
|
||||
[func] () mutable {
|
||||
std::unique_ptr<big::script> lua_script = std::make_unique<big::script>(
|
||||
[func_, state]() mutable {
|
||||
|
||||
sol::thread t = sol::thread::create(state);
|
||||
sol::coroutine func = sol::coroutine(t.state(), func_);
|
||||
|
||||
while (big::g_running)
|
||||
{
|
||||
auto res = func(dummy_script_util);
|
||||
@ -75,17 +87,22 @@ namespace lua::script
|
||||
if (!res.valid())
|
||||
big::g_lua_manager->handle_error(res, res.lua_state());
|
||||
|
||||
if (func.runnable())
|
||||
{
|
||||
if (func.runnable())
|
||||
{
|
||||
big::script::get_current()->yield(std::chrono::milliseconds(res.return_count() ? res[0] : 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
big::script::get_current()->yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
name)));
|
||||
name
|
||||
);
|
||||
|
||||
const auto registered_script = big::g_script_mgr.add_script(std::move(lua_script));
|
||||
|
||||
module->m_registered_scripts.push_back(registered_script);
|
||||
}
|
||||
|
||||
// Lua API: Function
|
||||
@ -116,28 +133,42 @@ namespace lua::script
|
||||
// ENTITY.DELETE_ENTITY(spawnedVehicle)
|
||||
// end)
|
||||
// ```
|
||||
static void run_in_fiber(sol::coroutine func)
|
||||
static void run_in_fiber(sol::function func_, sol::this_state state)
|
||||
{
|
||||
big::g_fiber_pool->queue_job([func] () mutable {
|
||||
while (big::g_running)
|
||||
{
|
||||
auto res = func(dummy_script_util);
|
||||
auto module = sol::state_view(state)["!this"].get<big::lua_module*>();
|
||||
|
||||
if (!res.valid())
|
||||
big::g_lua_manager->handle_error(res, res.lua_state());
|
||||
static size_t name_i = 0;
|
||||
std::string job_name = module->module_name() + std::to_string(name_i++);
|
||||
|
||||
// Still runnable, meaning that the user function yielded
|
||||
// and that there is more code to run still.
|
||||
if (func.runnable())
|
||||
{
|
||||
big::script::get_current()->yield(std::chrono::milliseconds(res.return_count() ? res[0] : 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
// We make a new script for lua state destruction timing purposes, see lua_module dctor for more info.
|
||||
std::unique_ptr<big::script> lua_script = std::make_unique<big::script>(
|
||||
[func_, state]() mutable {
|
||||
sol::thread t = sol::thread::create(state);
|
||||
sol::coroutine func = sol::coroutine(t.state(), func_);
|
||||
|
||||
while (big::g_running)
|
||||
{
|
||||
auto res = func(dummy_script_util);
|
||||
|
||||
if (!res.valid())
|
||||
big::g_lua_manager->handle_error(res, res.lua_state());
|
||||
|
||||
if (func.runnable())
|
||||
{
|
||||
big::script::get_current()->yield(std::chrono::milliseconds(res.return_count() ? res[0] : 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
big::g_script_mgr.remove_script(big::script::get_current());
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
job_name);
|
||||
|
||||
const auto registered_script = big::g_script_mgr.add_script(std::move(lua_script));
|
||||
|
||||
module->m_registered_scripts.push_back(registered_script);
|
||||
}
|
||||
|
||||
static void bind(sol::state& state)
|
||||
@ -146,12 +177,9 @@ namespace lua::script
|
||||
ns["register_looped"] = register_looped;
|
||||
ns["run_in_fiber"] = run_in_fiber;
|
||||
|
||||
sol::function lua_coroutine_yield = state["coroutine"]["yield"];
|
||||
state.new_usertype<script_util>("script_util");
|
||||
|
||||
//clang-format off
|
||||
state.new_usertype<script_util>("script_util",
|
||||
"yield", lua_coroutine_yield,
|
||||
"sleep", lua_coroutine_yield);
|
||||
//clang-format on
|
||||
state["script_util"]["yield"] = sol::yielding(&script_util::yield);
|
||||
state["script_util"]["sleep"] = sol::yielding(&script_util::sleep);
|
||||
}
|
||||
}
|
@ -45,37 +45,36 @@ namespace big
|
||||
}
|
||||
|
||||
lua_module::lua_module(std::string module_name) :
|
||||
m_state(),
|
||||
m_module_name(module_name),
|
||||
m_module_id(rage::joaat(module_name))
|
||||
{
|
||||
m_state.open_libraries(
|
||||
m_state = std::make_unique<sol::state>();
|
||||
|
||||
auto& state = *m_state;
|
||||
|
||||
state.open_libraries(
|
||||
sol::lib::base,
|
||||
sol::lib::package,
|
||||
sol::lib::coroutine,
|
||||
sol::lib::string,
|
||||
sol::lib::os,
|
||||
sol::lib::math,
|
||||
sol::lib::table,
|
||||
sol::lib::debug,
|
||||
sol::lib::bit32,
|
||||
sol::lib::utf8
|
||||
);
|
||||
|
||||
const auto& scripts_folder = g_lua_manager->get_scripts_folder();
|
||||
|
||||
add_folder_to_require_available_paths(scripts_folder);
|
||||
|
||||
init_lua_api();
|
||||
|
||||
m_state["!module_name"] = module_name;
|
||||
m_state["!this"] = this;
|
||||
state["!module_name"] = module_name;
|
||||
state["!this"] = this;
|
||||
|
||||
m_state.set_exception_handler((sol::exception_handler_function)exception_handler);
|
||||
m_state.set_panic(panic_handler);
|
||||
state.set_exception_handler((sol::exception_handler_function)exception_handler);
|
||||
state.set_panic(panic_handler);
|
||||
|
||||
const auto script_file_path = scripts_folder.get_file(module_name).get_path();
|
||||
const auto script_file_path = g_lua_manager->get_scripts_folder().get_file(module_name).get_path();
|
||||
m_last_write_time = std::filesystem::last_write_time(script_file_path);
|
||||
auto result = m_state.load_file(script_file_path.string());
|
||||
auto result = state.load_file(script_file_path.string());
|
||||
|
||||
if (!result.valid())
|
||||
{
|
||||
@ -95,7 +94,17 @@ namespace big
|
||||
}
|
||||
|
||||
for (auto script : m_registered_scripts)
|
||||
{
|
||||
g_script_mgr.remove_script(script);
|
||||
}
|
||||
|
||||
// the lua state is about to be destroyed,
|
||||
// but we need to keep it around a little bit longer
|
||||
// until the script manager properly finish executing any potential lua script.
|
||||
// There are most likely much better ways of doing all this, feel free to refactor I guess.
|
||||
std::shared_ptr<sol::state> lua_state_shared = std::shared_ptr<sol::state>(std::move(m_state));
|
||||
g_script_mgr.add_on_script_batch_removed([lua_state_shared] {
|
||||
});
|
||||
|
||||
for (auto memory : m_allocated_memory)
|
||||
delete[] memory;
|
||||
@ -116,27 +125,73 @@ namespace big
|
||||
return m_last_write_time;
|
||||
}
|
||||
|
||||
void lua_module::add_folder_to_require_available_paths(const big::folder& scripts_folder)
|
||||
void lua_module::set_folder_for_lua_require()
|
||||
{
|
||||
const std::string package_path = m_state["package"]["path"];
|
||||
const auto scripts_search_path = scripts_folder.get_path() / "?.lua";
|
||||
m_state["package"]["path"] = package_path + (!package_path.empty() ? ";" : "") + scripts_search_path.string();
|
||||
auto& state = *m_state;
|
||||
|
||||
const auto scripts_search_path = g_lua_manager->get_scripts_folder().get_path() / "?.lua";
|
||||
state["package"]["path"] = scripts_search_path.string();
|
||||
}
|
||||
|
||||
void lua_module::sandbox_lua_os_library()
|
||||
{
|
||||
auto& state = *m_state;
|
||||
|
||||
const auto& os = state["os"];
|
||||
sol::table sandbox_os(state, sol::create);
|
||||
|
||||
sandbox_os["clock"] = os["clock"];
|
||||
sandbox_os["date"] = os["date"];
|
||||
sandbox_os["difftime"] = os["difftime"];
|
||||
sandbox_os["time"] = os["time"];
|
||||
|
||||
state["os"] = sandbox_os;
|
||||
}
|
||||
|
||||
void lua_module::sandbox_lua_loads()
|
||||
{
|
||||
auto& state = *m_state;
|
||||
|
||||
// That's from lua base lib, luaB
|
||||
state.set_function("load", nullptr);
|
||||
state.set_function("loadstring", nullptr);
|
||||
state.set_function("loadfile", nullptr);
|
||||
state.set_function("dofile", nullptr);
|
||||
|
||||
// That's from lua package lib.
|
||||
// We only allow dependencies between .lua files, no DLLs.
|
||||
state["package"]["loadlib"] = nullptr;
|
||||
state["package"]["cpath"] = "";
|
||||
|
||||
// 1 2 3 4
|
||||
// {searcher_preload, searcher_Lua, searcher_C, searcher_Croot, NULL};
|
||||
state["package"]["searchers"][3] = nullptr;
|
||||
state["package"]["searchers"][4] = nullptr;
|
||||
|
||||
set_folder_for_lua_require();
|
||||
}
|
||||
|
||||
void lua_module::init_lua_api()
|
||||
{
|
||||
lua::log::bind(m_state);
|
||||
lua::globals::bind(m_state);
|
||||
lua::script::bind(m_state);
|
||||
lua::native::bind(m_state);
|
||||
lua::memory::bind(m_state);
|
||||
lua::gui::bind(m_state);
|
||||
lua::network::bind(m_state);
|
||||
lua::command::bind(m_state);
|
||||
lua::tunables::bind(m_state);
|
||||
lua::locals::bind(m_state);
|
||||
lua::event::bind(m_state);
|
||||
lua::vector::bind(m_state);
|
||||
lua::global_table::bind(m_state);
|
||||
auto& state = *m_state;
|
||||
|
||||
// https://blog.rubenwardy.com/2020/07/26/sol3-script-sandbox/
|
||||
// https://www.lua.org/manual/5.4/manual.html#pdf-require
|
||||
sandbox_lua_os_library();
|
||||
sandbox_lua_loads();
|
||||
|
||||
lua::log::bind(state);
|
||||
lua::globals::bind(state);
|
||||
lua::script::bind(state);
|
||||
lua::native::bind(state);
|
||||
lua::memory::bind(state);
|
||||
lua::gui::bind(state);
|
||||
lua::network::bind(state);
|
||||
lua::command::bind(state);
|
||||
lua::tunables::bind(state);
|
||||
lua::locals::bind(state);
|
||||
lua::event::bind(state);
|
||||
lua::vector::bind(state);
|
||||
lua::global_table::bind(state);
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ namespace big
|
||||
|
||||
class lua_module
|
||||
{
|
||||
sol::state m_state;
|
||||
std::unique_ptr<sol::state> m_state;
|
||||
|
||||
std::string m_module_name;
|
||||
rage::joaat_t m_module_id;
|
||||
@ -37,8 +37,11 @@ namespace big
|
||||
const std::string& module_name() const;
|
||||
const std::chrono::time_point<std::chrono::file_clock> last_write_time() const;
|
||||
|
||||
// used for adding our own paths to the search paths of the lua require function
|
||||
void add_folder_to_require_available_paths(const big::folder& scripts_folder);
|
||||
// used for sandboxing and limiting to only our custom search path for the lua require function
|
||||
void set_folder_for_lua_require();
|
||||
|
||||
void sandbox_lua_os_library();
|
||||
void sandbox_lua_loads();
|
||||
|
||||
void init_lua_api();
|
||||
};
|
||||
|
@ -33,6 +33,11 @@ namespace big
|
||||
m_scripts.clear();
|
||||
}
|
||||
|
||||
void script_mgr::add_on_script_batch_removed(std::function<void()> f)
|
||||
{
|
||||
m_on_script_batch_removed.push(f);
|
||||
}
|
||||
|
||||
void script_mgr::tick()
|
||||
{
|
||||
gta_util::execute_as_script(RAGE_JOAAT("main_persistent"), std::mem_fn(&script_mgr::tick_internal), this);
|
||||
@ -62,6 +67,18 @@ namespace big
|
||||
return iter->m_should_be_deleted;
|
||||
});
|
||||
|
||||
while (m_on_script_batch_removed.size())
|
||||
{
|
||||
auto& f = m_on_script_batch_removed.front();
|
||||
|
||||
if (f)
|
||||
{
|
||||
f();
|
||||
}
|
||||
|
||||
m_on_script_batch_removed.pop();
|
||||
}
|
||||
|
||||
if (g_lua_manager->m_schedule_reload_modules)
|
||||
{
|
||||
g_lua_manager->load_all_modules();
|
||||
|
@ -16,6 +16,8 @@ namespace big
|
||||
void remove_script(script* script);
|
||||
void remove_all_scripts();
|
||||
|
||||
void add_on_script_batch_removed(std::function<void()> f);
|
||||
|
||||
inline void for_each_script(auto func)
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
@ -41,6 +43,7 @@ namespace big
|
||||
std::recursive_mutex m_mutex;
|
||||
script_list m_scripts;
|
||||
script_list m_scripts_to_add;
|
||||
std::queue<std::function<void()>> m_on_script_batch_removed;
|
||||
|
||||
bool m_can_tick = false;
|
||||
};
|
||||
|
Reference in New Issue
Block a user