diff options
-rw-r--r-- | indra/llcommon/lua_function.cpp | 3 | ||||
-rw-r--r-- | indra/llcommon/lua_function.h | 37 | ||||
-rw-r--r-- | indra/newview/llluamanager.cpp | 233 | ||||
-rw-r--r-- | indra/newview/llluamanager.h | 25 |
4 files changed, 164 insertions, 134 deletions
diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index a5f1f582d9..78abb8ba7e 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -789,7 +789,8 @@ std::ostream& operator<<(std::ostream& out, const lua_what& self) *****************************************************************************/ std::ostream& operator<<(std::ostream& out, const lua_stack& self) { - const char* sep = "stack: ["; + out << "stack: ["; + const char* sep = ""; for (int index = 1; index <= lua_gettop(self.L); ++index) { out << sep << lua_what(self.L, index); diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 08a2353d29..07848e38af 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -17,6 +17,7 @@ #include "luau/luaconf.h" #include "luau/lualib.h" #include "stringize.h" +#include <exception> // std::uncaught_exceptions() #include <memory> // std::shared_ptr #include <utility> // std::pair @@ -216,4 +217,40 @@ private: lua_State* L; }; +// adapted from indra/test/debug.h +// can't generalize Debug::operator() target because it's a variadic template +class LuaLog +{ +public: + template <typename... ARGS> + LuaLog(lua_State* L, ARGS&&... args): + L(L), + mBlock(stringize(std::forward<ARGS>(args)...)) + { + (*this)("entry ", lua_stack(L)); + } + + // non-copyable + LuaLog(const LuaLog&) = delete; + LuaLog& operator=(const LuaLog&) = delete; + + ~LuaLog() + { + auto exceptional{ std::uncaught_exceptions()? "exceptional " : "" }; + (*this)(exceptional, "exit ", lua_stack(L)); + } + + template <typename... ARGS> + void operator()(ARGS&&... args) + { + LL_INFOS("Lua") << mBlock << ' '; + stream_to(LL_CONT, std::forward<ARGS>(args)...); + LL_ENDL; + } + +private: + lua_State* L; + const std::string mBlock; +}; + #endif /* ! defined(LL_LUA_FUNCTION_H) */ diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index 0c39f6c8df..80d7eb4210 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -195,30 +195,6 @@ lua_function(get_event_next, return 2; } -lua_function(await_event, - "await_event(pumpname [, timeout [, value to return if timeout (default nil)]]):\n" - "pause the running Lua chunk until the next event on the named LLEventPump") -{ - auto pumpname{ lua_tostdstring(L, 1) }; - LLSD result; - if (lua_gettop(L) > 1) - { - auto timeout{ lua_tonumber(L, 2) }; - // with no 3rd argument, should be LLSD() - auto dftval{ lua_tollsd(L, 3) }; - lua_settop(L, 0); - result = llcoro::suspendUntilEventOnWithTimeout(pumpname, timeout, dftval); - } - else - { - // no timeout - lua_pop(L, 1); - result = llcoro::suspendUntilEventOn(pumpname); - } - lua_pushllsd(L, result); - return 1; -} - void LLLUAmanager::runScriptFile(const std::string& filename, script_finished_fn cb) { // A script_finished_fn is used to initialize the LuaState. @@ -361,14 +337,36 @@ std::string read_file(const std::string &name) if (in_file.is_open()) { - std::string text {std::istreambuf_iterator<char>(in_file), {}}; - return text; + return std::string{std::istreambuf_iterator<char>(in_file), {}}; } - return std::string(); + return {}; } -LLRequireResolver::LLRequireResolver(lua_State *L, const std::string& path) : mPathToResolve(path), L(L) +lua_function(require, "require(module_name) : load module_name.lua from known places") +{ + std::string name = lua_tostdstring(L, 1); + lua_pop(L, 1); + + // resolveRequire() does not return in case of error. + LLRequireResolver::resolveRequire(L, name); + + // resolveRequire() returned the newly-loaded module on the stack top. + // Return it. + return 1; +} + +// push loaded module or throw Lua error +void LLRequireResolver::resolveRequire(lua_State *L, std::string path) +{ + LLRequireResolver resolver(L, std::move(path)); + // findModule() pushes the loaded module or throws a Lua error. + resolver.findModule(); +} + +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 @@ -383,31 +381,59 @@ LLRequireResolver::LLRequireResolver(lua_State *L, const std::string& path) : mP luaL_argerrorL(L, 1, "cannot require a full path"); } -[[nodiscard]] LLRequireResolver::ResolvedRequire LLRequireResolver::resolveRequire(lua_State *L, std::string path) +/** + * Remove a particular stack index on exit from enclosing scope. + * If you pass a negative index (meaning relative to the current stack top), + * converts to an absolute index. The point of LuaRemover is to remove the + * entry at the specified index regardless of subsequent pushes to the stack. + */ +class LuaRemover { - 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}; -} +public: + LuaRemover(lua_State* L, int index): + mState(L), + mIndex(lua_absindex(L, index)) + {} + LuaRemover(const LuaRemover&) = delete; + LuaRemover& operator=(const LuaRemover&) = delete; + ~LuaRemover() + { + lua_remove(mState, mIndex); + } + +private: + lua_State* mState; + int mIndex; +}; -LLRequireResolver::ModuleStatus LLRequireResolver::findModule() +// push the loaded module or throw a Lua error +void LLRequireResolver::findModule() { - resolveAndStoreDefaultPaths(); + // If mPathToResolve is absolute, this replaces mSourceChunkname.parent_path. + auto absolutePath = (std::filesystem::path((mSourceChunkname)).parent_path() / mPathToResolve).u8string(); - // Put _MODULES table on stack for checking and saving to the cache + // Push _MODULES table on stack for checking and saving to the cache luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1); + // Remove that stack entry no matter how we exit + LuaRemover rm_MODULES(L, -1); + + // Check if the module is already in _MODULES table, read from file + // otherwise. + // findModuleImpl() pushes module if found, nothing if not, may throw Lua + // error. + if (findModuleImpl(absolutePath)) + return; - // Check if the module is already in _MODULES table, read from file otherwise - LLRequireResolver::ModuleStatus moduleStatus = findModuleImpl(); - - if (moduleStatus != LLRequireResolver::ModuleStatus::NotFound) - return moduleStatus; + // not already cached - prep error message just in case + auto fail{ + [L=L, path=mPathToResolve]() + { luaL_error(L, "could not find require('%s')", path.data()); }}; if (std::filesystem::path(mPathToResolve).is_absolute()) - return moduleStatus; + { + // no point searching known directories for an absolute path + fail(); + } std::vector<std::string> lib_paths {gDirUtilp->getExpandedFilename(LL_PATH_SCRIPTS, "lua")}; @@ -416,30 +442,31 @@ LLRequireResolver::ModuleStatus LLRequireResolver::findModule() std::string absolutePathOpt = (std::filesystem::path(path) / mPathToResolve).u8string(); if (absolutePathOpt.empty()) - luaL_errorL(L, "error requiring module"); - - mAbsolutePath = absolutePathOpt; - - moduleStatus = findModuleImpl(); + luaL_error(L, "error requiring module '%s'", mPathToResolve.data()); - if (moduleStatus != LLRequireResolver::ModuleStatus::NotFound) - return moduleStatus; + if (findModuleImpl(absolutePathOpt)) + return; } - return LLRequireResolver::ModuleStatus::NotFound; + // not found + fail(); } -LLRequireResolver::ModuleStatus LLRequireResolver::findModuleImpl() +// expects _MODULES table on stack top (and leaves it there) +// - if found, pushes loaded module and returns true +// - not found, pushes nothing and returns false +// - may throw Lua error +bool LLRequireResolver::findModuleImpl(const std::string& absolutePath) { - std::string possibleSuffixedPaths[] = {mAbsolutePath + ".luau", mAbsolutePath + ".lua"}; + std::string possibleSuffixedPaths[] = {absolutePath + ".luau", absolutePath + ".lua"}; - for (auto suffixedPath : possibleSuffixedPaths) + for (const auto& suffixedPath : possibleSuffixedPaths) { // Check _MODULES cache for module lua_getfield(L, -1, suffixedPath.c_str()); if (!lua_isnil(L, -1)) { - return ModuleStatus::Cached; + return true; } lua_pop(L, 1); @@ -447,91 +474,73 @@ LLRequireResolver::ModuleStatus LLRequireResolver::findModuleImpl() std::string source = read_file(suffixedPath); if (!source.empty()) { - mAbsolutePath = suffixedPath; - mSourceCode = source; - return ModuleStatus::FileRead; - } - } + // Try to run the loaded source. This will leave either a string + // error message or the module contents on the stack top. + runModule(suffixedPath, source); - return ModuleStatus::NotFound; -} + // If the stack top is an error message string, raise it. + if (lua_isstring(L, -1)) + lua_error(L); -void LLRequireResolver::resolveAndStoreDefaultPaths() -{ - if (!std::filesystem::path(mPathToResolve).is_absolute()) - { - mAbsolutePath = (std::filesystem::path((mSourceChunkname)).parent_path() / mPathToResolve).u8string();; - } - else - { - mAbsolutePath = mPathToResolve; - } -} + // duplicate the new module: _MODULES newmodule newmodule + lua_pushvalue(L, -1); + // store _MODULES[found path] = newmodule + lua_setfield(L, -3, source.data()); -static int finishrequire(lua_State *L) -{ - if (lua_isstring(L, -1)) - lua_error(L); + return true; + } + } - return 1; + return false; } -lua_function(require, "require(module_name) : module_name can be fullpath or just the name, in both cases without .lua") +// push string error message or new module +void LLRequireResolver::runModule(const std::string& desc, const std::string& code) { - 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 + // Here we just loaded a new module 'code', need to run it and get its result. + // 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_newthread() pushed the new thread object on GL's stack. Move to L's. 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) + // If loadstring() returns (! LUA_OK) then there's an error message on + // the stack. If it returns LUA_OK then the newly-loaded module code + // is on the stack. + if (lluau::loadstring(ML, desc, code) == LUA_OK) { + // luau uses Lua 5.3's version of lua_resume(): + // run the coroutine on ML, "from" L, passing no arguments. 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"); + if (lua_gettop(ML) == 0) + lua_pushfstring(ML, "module %s must return a value", desc.data()); else if (!lua_istable(ML, -1) && !lua_isfunction(ML, -1)) - lua_pushstring(ML, "module must return a table or function"); + lua_pushfstring(ML, "module %s must return a table or function, not %s", + desc.data(), lua_typename(ML, lua_type(ML, -1))); } else if (status == LUA_YIELD) { - lua_pushstring(ML, "module can not yield"); + lua_pushfstring(ML, "module %s can not yield", desc.data()); } else if (!lua_isstring(ML, -1)) { - lua_pushstring(ML, "unknown error while running module"); + lua_pushfstring(ML, "unknown error while running module %s", desc.data()); } } } - // there's now a return value on top of ML; L stack: _MODULES ML + // There's now a return value (string error message or module) on top of ML. + // Move return value to L's stack. 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 + // remove ML from L's stack lua_remove(L, -2); - - // L stack: [module name] [result] - return finishrequire(L); +// // DON'T call lua_close(ML)! Since ML is only a thread of L, corrupts L too! +// lua_close(ML); } diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h index 43950ccee4..fb9f1b8141 100644 --- a/indra/newview/llluamanager.h +++ b/indra/newview/llluamanager.h @@ -87,24 +87,7 @@ public: 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; + static void resolveRequire(lua_State *L, std::string path); private: std::string mPathToResolve; @@ -112,10 +95,10 @@ class LLRequireResolver LLRequireResolver(lua_State *L, const std::string& path); - ModuleStatus findModule(); + void findModule(); lua_State *L; - void resolveAndStoreDefaultPaths(); - ModuleStatus findModuleImpl(); + bool findModuleImpl(const std::string& absolutePath); + void runModule(const std::string& desc, const std::string& code); }; #endif |