diff options
author | Maxim Nikolenko <maximnproductengine@lindenlab.com> | 2024-02-27 18:51:32 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-27 18:51:32 +0200 |
commit | 777586a1865b496f8bfea9651afbd481ea23b4f7 (patch) | |
tree | 0cecaa9b4be0958dc7ff126c30382da3781f1f60 | |
parent | ad32c066691152e6a23f025d6aa5ead0e91b7be9 (diff) | |
parent | edf1a712a7ca8fd741514348e3304af6a766f4aa (diff) |
Merge pull request #843 from secondlife/luau-require-impl
Initial require implementation
-rw-r--r-- | indra/llcommon/lua_function.cpp | 35 | ||||
-rw-r--r-- | indra/llcommon/lua_function.h | 3 | ||||
-rw-r--r-- | indra/llfilesystem/lldir.cpp | 5 | ||||
-rw-r--r-- | indra/llfilesystem/lldir.h | 1 | ||||
-rw-r--r-- | indra/newview/llfloaterluadebug.cpp | 12 | ||||
-rw-r--r-- | indra/newview/llfloaterluadebug.h | 1 | ||||
-rw-r--r-- | indra/newview/llluamanager.cpp | 186 | ||||
-rw-r--r-- | indra/newview/llluamanager.h | 37 | ||||
-rw-r--r-- | indra/newview/skins/default/xui/en/floater_lua_debug.xml | 9 |
9 files changed, 276 insertions, 13 deletions
diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 906a3f379c..a5f1f582d9 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -41,16 +41,9 @@ namespace int lluau::dostring(lua_State* L, const std::string& desc, const std::string& text) { - { - size_t bytecodeSize = 0; - // The char* returned by luau_compile() must be freed by calling free(). - // Use unique_ptr so the memory will be freed even if luau_load() throws. - std::unique_ptr<char[], freer> bytecode{ - luau_compile(text.data(), text.length(), nullptr, &bytecodeSize)}; - auto r = luau_load(L, desc.data(), bytecode.get(), bytecodeSize, 0); - if (r != LUA_OK) - return r; - } // free bytecode + auto r = loadstring(L, desc, text); + if (r != LUA_OK) + return r; // It's important to pass LUA_MULTRET as the expected number of return // values: if we pass any fixed number, we discard any returned values @@ -58,6 +51,16 @@ int lluau::dostring(lua_State* L, const std::string& desc, const std::string& te return lua_pcall(L, 0, LUA_MULTRET, 0); } +int lluau::loadstring(lua_State *L, const std::string &desc, const std::string &text) +{ + size_t bytecodeSize = 0; + // The char* returned by luau_compile() must be freed by calling free(). + // Use unique_ptr so the memory will be freed even if luau_load() throws. + std::unique_ptr<char[], freer> bytecode{ + luau_compile(text.data(), text.length(), nullptr, &bytecodeSize)}; + return luau_load(L, desc.data(), bytecode.get(), bytecodeSize, 0); +} + /***************************************************************************** * Lua <=> C++ conversions *****************************************************************************/ @@ -426,8 +429,18 @@ void lua_pushllsd(lua_State* L, const LLSD& data) *****************************************************************************/ LuaState::LuaState(script_finished_fn cb): mCallback(cb), - mState(luaL_newstate()) + mState(nullptr) { + initLuaState(); +} + +void LuaState::initLuaState() +{ + if (mState) + { + lua_close(mState); + } + mState = luaL_newstate(); luaL_openlibs(mState); LuaFunction::init(mState); // Try to make print() write to our log. diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index eccc92ec09..08a2353d29 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -50,6 +50,7 @@ namespace lluau // rather than string_views because dostring() needs pointers to nul- // terminated char arrays. int dostring(lua_State* L, const std::string& desc, const std::string& text); + int loadstring(lua_State* L, const std::string& desc, const std::string& text); } // namespace lluau std::string lua_tostdstring(lua_State* L, int index); @@ -72,6 +73,8 @@ public: ~LuaState(); + void initLuaState(); + bool checkLua(const std::string& desc, int r); // expr() is for when we want to capture any results left on the stack diff --git a/indra/llfilesystem/lldir.cpp b/indra/llfilesystem/lldir.cpp index 69b23f9cf8..e3769c6afb 100644 --- a/indra/llfilesystem/lldir.cpp +++ b/indra/llfilesystem/lldir.cpp @@ -469,6 +469,7 @@ static std::string ELLPathToString(ELLPath location) ENT(LL_PATH_DEFAULT_SKIN) ENT(LL_PATH_FONTS) ENT(LL_PATH_LAST) + ENT(LL_PATH_SCRIPTS) ; #undef ENT @@ -588,6 +589,10 @@ std::string LLDir::getExpandedFilename(ELLPath location, const std::string& subd case LL_PATH_FONTS: prefix = add(getAppRODataDir(), "fonts"); break; + + case LL_PATH_SCRIPTS: + prefix = add(getAppRODataDir(), "scripts"); + break; default: llassert(0); diff --git a/indra/llfilesystem/lldir.h b/indra/llfilesystem/lldir.h index b9a046ba33..7d418159d1 100644 --- a/indra/llfilesystem/lldir.h +++ b/indra/llfilesystem/lldir.h @@ -49,6 +49,7 @@ typedef enum ELLPath LL_PATH_DEFAULT_SKIN = 17, LL_PATH_FONTS = 18, LL_PATH_DUMP = 19, + LL_PATH_SCRIPTS = 20, LL_PATH_LAST } ELLPath; diff --git a/indra/newview/llfloaterluadebug.cpp b/indra/newview/llfloaterluadebug.cpp index 3ebe40f80b..b63600e0e6 100644 --- a/indra/newview/llfloaterluadebug.cpp +++ b/indra/newview/llfloaterluadebug.cpp @@ -27,6 +27,7 @@ #include "llfloaterluadebug.h" +#include "llcheckboxctrl.h" #include "lllineeditor.h" #include "lltexteditor.h" #include "llviewermenufile.h" // LLFilePickerReplyThread @@ -79,6 +80,7 @@ void LLFloaterLUADebug::onExecuteClicked() mResultOutput->setValue(""); std::string cmd = mLineInput->getText(); + cleanLuaState(); LLLUAmanager::runScriptLine(mState, cmd, [this](int count, const LLSD& result) { completion(count, result); @@ -109,6 +111,7 @@ void LLFloaterLUADebug::runSelectedScript(const std::vector<std::string> &filena if (!filepath.empty()) { mScriptPath->setText(filepath); + cleanLuaState(); LLLUAmanager::runScriptFile(mState, filepath, [this](int count, const LLSD& result) { completion(count, result); @@ -140,3 +143,12 @@ void LLFloaterLUADebug::completion(int count, const LLSD& result) sep = ", "; } } + +void LLFloaterLUADebug::cleanLuaState() +{ + if(getChild<LLCheckBoxCtrl>("clean_lua_state")->get()) + { + //Reinit to clean lua_State + mState.initLuaState(); + } +} diff --git a/indra/newview/llfloaterluadebug.h b/indra/newview/llfloaterluadebug.h index 69b334ae2d..7418174570 100644 --- a/indra/newview/llfloaterluadebug.h +++ b/indra/newview/llfloaterluadebug.h @@ -58,6 +58,7 @@ class LLFloaterLUADebug : private: void completion(int count, const LLSD& result); + void cleanLuaState(); LLTempBoundListener mOutConnection; diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index 33b996ab50..0c39f6c8df 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -46,6 +46,7 @@ extern LLUIListener sUIListener; #endif // ! LL_TEST #include <boost/algorithm/string/replace.hpp> +#include <filesystem> #include "luau/luacode.h" #include "luau/lua.h" @@ -259,8 +260,7 @@ void LLLUAmanager::runScriptFile(LuaState& L, const std::string& filename, scrip if (in_file.is_open()) { - std::string text{std::istreambuf_iterator<char>(in_file), - std::istreambuf_iterator<char>()}; + std::string text{std::istreambuf_iterator<char>(in_file), {}}; auto [count, result] = L.expr(filename, text); if (cb) { @@ -353,3 +353,185 @@ void LLLUAmanager::runScriptOnLogin() runScriptFile(filename); #endif // ! LL_TEST } + +std::string read_file(const std::string &name) +{ + llifstream in_file; + in_file.open(name.c_str()); + + if (in_file.is_open()) + { + std::string text {std::istreambuf_iterator<char>(in_file), {}}; + return text; + } + + return std::string(); +} + +LLRequireResolver::LLRequireResolver(lua_State *L, const std::string& path) : mPathToResolve(path), L(L) +{ + //Luau lua_Debug and lua_getinfo() are different compared to default Lua: + //see https://github.com/luau-lang/luau/blob/80928acb92d1e4b6db16bada6d21b1fb6fa66265/VM/include/lua.h + lua_Debug ar; + lua_getinfo(L, 1, "s", &ar); + mSourceChunkname = ar.source; + + std::filesystem::path fs_path(mPathToResolve); + mPathToResolve = fs_path.lexically_normal().string(); + + if (fs_path.is_absolute()) + luaL_argerrorL(L, 1, "cannot require a full path"); +} + +[[nodiscard]] LLRequireResolver::ResolvedRequire LLRequireResolver::resolveRequire(lua_State *L, std::string path) +{ + LLRequireResolver resolver(L, std::move(path)); + ModuleStatus status = resolver.findModule(); + if (status != ModuleStatus::FileRead) + return ResolvedRequire {status}; + else + return ResolvedRequire {status, resolver.mAbsolutePath, resolver.mSourceCode}; +} + +LLRequireResolver::ModuleStatus LLRequireResolver::findModule() +{ + resolveAndStoreDefaultPaths(); + + // Put _MODULES table on stack for checking and saving to the cache + luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1); + + // Check if the module is already in _MODULES table, read from file otherwise + LLRequireResolver::ModuleStatus moduleStatus = findModuleImpl(); + + if (moduleStatus != LLRequireResolver::ModuleStatus::NotFound) + return moduleStatus; + + if (std::filesystem::path(mPathToResolve).is_absolute()) + return moduleStatus; + + std::vector<std::string> lib_paths {gDirUtilp->getExpandedFilename(LL_PATH_SCRIPTS, "lua")}; + + for (const auto& path : lib_paths) + { + std::string absolutePathOpt = (std::filesystem::path(path) / mPathToResolve).u8string(); + + if (absolutePathOpt.empty()) + luaL_errorL(L, "error requiring module"); + + mAbsolutePath = absolutePathOpt; + + moduleStatus = findModuleImpl(); + + if (moduleStatus != LLRequireResolver::ModuleStatus::NotFound) + return moduleStatus; + } + + return LLRequireResolver::ModuleStatus::NotFound; +} + +LLRequireResolver::ModuleStatus LLRequireResolver::findModuleImpl() +{ + std::string possibleSuffixedPaths[] = {mAbsolutePath + ".luau", mAbsolutePath + ".lua"}; + + for (auto suffixedPath : possibleSuffixedPaths) + { + // Check _MODULES cache for module + lua_getfield(L, -1, suffixedPath.c_str()); + if (!lua_isnil(L, -1)) + { + return ModuleStatus::Cached; + } + lua_pop(L, 1); + + // Try to read the matching file + std::string source = read_file(suffixedPath); + if (!source.empty()) + { + mAbsolutePath = suffixedPath; + mSourceCode = source; + return ModuleStatus::FileRead; + } + } + + return ModuleStatus::NotFound; +} + +void LLRequireResolver::resolveAndStoreDefaultPaths() +{ + if (!std::filesystem::path(mPathToResolve).is_absolute()) + { + mAbsolutePath = (std::filesystem::path((mSourceChunkname)).parent_path() / mPathToResolve).u8string();; + } + else + { + mAbsolutePath = mPathToResolve; + } +} + +static int finishrequire(lua_State *L) +{ + if (lua_isstring(L, -1)) + lua_error(L); + + return 1; +} + +lua_function(require, "require(module_name) : module_name can be fullpath or just the name, in both cases without .lua") +{ + std::string name = lua_tostdstring(L, 1); + + LLRequireResolver::ResolvedRequire resolvedRequire = LLRequireResolver::resolveRequire(L, name); + + if (resolvedRequire.status == LLRequireResolver::ModuleStatus::Cached) + { + // remove _MODULES from stack + lua_remove(L, -2); + return finishrequire(L); + } + else if (resolvedRequire.status == LLRequireResolver::ModuleStatus::NotFound) + luaL_errorL(L, "error requiring module"); + + // module needs to run in a new thread, isolated from the rest + // note: we create ML on main thread so that it doesn't inherit environment of L + lua_State *GL = lua_mainthread(L); + lua_State *ML = lua_newthread(GL); + lua_xmove(GL, L, 1); + + // new thread needs to have the globals sandboxed + luaL_sandboxthread(ML); + + { + if (lluau::loadstring(ML, resolvedRequire.absolutePath.c_str(), resolvedRequire.sourceCode.c_str()) == LUA_OK) + { + int status = lua_resume(ML, L, 0); + + if (status == LUA_OK) + { + if (lua_gettop(ML) == LUA_OK) + lua_pushstring(ML, "module must return a value"); + else if (!lua_istable(ML, -1) && !lua_isfunction(ML, -1)) + lua_pushstring(ML, "module must return a table or function"); + } + else if (status == LUA_YIELD) + { + lua_pushstring(ML, "module can not yield"); + } + else if (!lua_isstring(ML, -1)) + { + lua_pushstring(ML, "unknown error while running module"); + } + } + } + // there's now a return value on top of ML; L stack: _MODULES ML + lua_xmove(ML, L, 1); + lua_pushvalue(L, -1); + lua_setfield(L, -4, resolvedRequire.absolutePath.c_str()); + + //remove _MODULES from stack + lua_remove(L, -3); + // remove ML from stack + lua_remove(L, -2); + + // L stack: [module name] [result] + return finishrequire(L); +} diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h index 12284cd0e4..43950ccee4 100644 --- a/indra/newview/llluamanager.h +++ b/indra/newview/llluamanager.h @@ -33,6 +33,9 @@ #include <string> #include <utility> // std::pair +#include "luau/lua.h" +#include "luau/lualib.h" + class LuaState; class LLLUAmanager @@ -81,4 +84,38 @@ public: static void runScriptOnLogin(); }; +class LLRequireResolver +{ + public: + enum class ModuleStatus + { + Cached, + FileRead, + NotFound + }; + + struct ResolvedRequire + { + ModuleStatus status; + std::string absolutePath; + std::string sourceCode; + }; + + [[nodiscard]] ResolvedRequire static resolveRequire(lua_State *L, std::string path); + + std::string mAbsolutePath; + std::string mSourceCode; + + private: + std::string mPathToResolve; + std::string mSourceChunkname; + + LLRequireResolver(lua_State *L, const std::string& path); + + ModuleStatus findModule(); + lua_State *L; + + void resolveAndStoreDefaultPaths(); + ModuleStatus findModuleImpl(); +}; #endif diff --git a/indra/newview/skins/default/xui/en/floater_lua_debug.xml b/indra/newview/skins/default/xui/en/floater_lua_debug.xml index f03739f7c2..a028a1802c 100644 --- a/indra/newview/skins/default/xui/en/floater_lua_debug.xml +++ b/indra/newview/skins/default/xui/en/floater_lua_debug.xml @@ -25,6 +25,15 @@ width="100"> LUA string: </text> + <check_box + follows="left|bottom" + height="15" + label="Use clean lua_State" + layout="topleft" + top="10" + right ="-70" + name="clean_lua_state" + width="70"/> <line_editor border_style="line" border_thickness="1" |