diff options
author | Mnikolenko Productengine <mnikolenko@productengine.com> | 2024-02-20 14:25:56 +0200 |
---|---|---|
committer | Mnikolenko Productengine <mnikolenko@productengine.com> | 2024-02-20 14:25:56 +0200 |
commit | b305fb6411939bf6afbe2ecf2c1bdf765c9247a9 (patch) | |
tree | 6421d6425b20c0848180b847785f7923be0acae9 /indra | |
parent | 9b1796df73f2d4fa19517f390258f798c10bff1e (diff) |
Initial require implementation
Diffstat (limited to 'indra')
-rw-r--r-- | indra/llcommon/lua_function.cpp | 9 | ||||
-rw-r--r-- | indra/llcommon/lua_function.h | 9 | ||||
-rw-r--r-- | indra/newview/llluamanager.cpp | 207 | ||||
-rw-r--r-- | indra/newview/llluamanager.h | 39 |
4 files changed, 255 insertions, 9 deletions
diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 07e0c1fac2..17d81e8fc7 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -25,15 +25,6 @@ #include "llsdutil.h" #include "lualistener.h" -namespace -{ - // can't specify free function free() as a unique_ptr deleter - struct freer - { - void operator()(void* ptr){ free(ptr); } - }; -} // anonymous namespace - int lluau::dostring(lua_State* L, const std::string& desc, const std::string& text) { { diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index c23bf533ba..f549137c3e 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -22,6 +22,15 @@ #define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n))) #define lua_rawlen lua_objlen +namespace +{ + // can't specify free function free() as a unique_ptr deleter + struct freer + { + void operator()(void *ptr) { free(ptr); } + }; +} + namespace lluau { // luau defines luaL_error() as void, but we want to use the Lua idiom of diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index d58ee5ca5f..9b74060139 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -401,3 +401,210 @@ void LLLUAmanager::runScriptOnLogin() runScriptFile(filename); #endif // ! LL_TEST } + + +bool is_absolute_path(std::string_view path) +{ +#ifdef LL_WINDOWS + // Must either begin with "X:/", "X:\", "/", or "\", where X is a drive letter + return (path.size() >= 3 && isalpha(path[0]) && path[1] == ':' && (path[2] == '/' || path[2] == '\\')) || + (path.size() >= 1 && (path[0] == '/' || path[0] == '\\')); +#else + // Must begin with '/' + return path.size() >= 1 && path[0] == '/'; +#endif +} + +std::string join_paths(const std::string &lhs, const std::string &rhs) +{ + std::string result = lhs; + if (!result.empty() && result.back() != '/' && result.back() != '\\') + result += '/'; + result += rhs; + return result; +} + +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), std::istreambuf_iterator<char>()}; + return text; + } + + return std::string(); +} + +LLRequireResolver::LLRequireResolver(lua_State *L, std::string path) : mPathToResolve(std::move(path)), L(L) +{ + lua_Debug ar; + lua_getinfo(L, 1, "s", &ar); + mSourceChunkname = ar.source; + + std::replace(mPathToResolve.begin(), mPathToResolve.end(), '\\', '/'); +} + +[[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, std::move(resolver.mChunkname), std::move(resolver.mAbsolutePath), std::move(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); + + LLRequireResolver::ModuleStatus moduleStatus = findModuleImpl(); + + if (moduleStatus != LLRequireResolver::ModuleStatus::NotFound) + return moduleStatus; + + if (is_absolute_path(mPathToResolve)) + return moduleStatus; + + std::vector<std::string> lib_paths; + + lib_paths.push_back(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "")); + + for (size_t i = 0; i < lib_paths.size(); ++i) + { + std::string absolutePathOpt = join_paths(lib_paths[i], mPathToResolve); + + if (absolutePathOpt.empty()) + luaL_errorL(L, "error requiring module"); + + mChunkname = absolutePathOpt; + mAbsolutePath = absolutePathOpt; + + moduleStatus = findModuleImpl(); + + if (moduleStatus != LLRequireResolver::ModuleStatus::NotFound) + return moduleStatus; + } + + return LLRequireResolver::ModuleStatus::NotFound; +} + +LLRequireResolver::ModuleStatus LLRequireResolver::findModuleImpl() +{ + std::string possibleSuffixes[] = {".luau", ".lua"}; + + size_t unsuffixedAbsolutePathSize = mAbsolutePath.size(); + + for (auto possibleSuffix : possibleSuffixes) + { + mAbsolutePath += possibleSuffix; + + // Check cache for module + lua_getfield(L, -1, mAbsolutePath.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(mAbsolutePath); + if (!source.empty()) + { + mChunkname = "=" + mChunkname + possibleSuffix; + mSourceCode = source; + return ModuleStatus::FileRead; + } + + mAbsolutePath.resize(unsuffixedAbsolutePathSize); // truncate to remove suffix + } + + return ModuleStatus::NotFound; +} + +void LLRequireResolver::resolveAndStoreDefaultPaths() +{ + if (!is_absolute_path(mPathToResolve)) + { + std::string path = gDirUtilp->getDirName(mSourceChunkname); + std::replace(path.begin(), path.end(), '\\', '/'); + path = join_paths(path, mPathToResolve); + mAbsolutePath = path; + mChunkname = path; + } + else + { + mChunkname = mPathToResolve; + 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 = luaL_checkstring(L, 1); + + LLRequireResolver::ResolvedRequire resolvedRequire = LLRequireResolver::resolveRequire(L, std::move(name)); + + if (resolvedRequire.status == LLRequireResolver::ModuleStatus::Cached) + 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); + + { + // now we can compile & run module on the new thread + size_t bytecodeSize = 0; + std::unique_ptr<char[], freer> bytecode { + luau_compile(resolvedRequire.sourceCode.c_str(), resolvedRequire.sourceCode.length(), nullptr, &bytecodeSize)}; + + if (luau_load(ML, resolvedRequire.chunkName.c_str(), bytecode.get(), bytecodeSize, 0) == 0) + { + int status = lua_resume(ML, L, 0); + + if (status == 0) + { + if (lua_gettop(ML) == 0) + 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()); + + // L stack: _MODULES ML result + return finishrequire(L); +} diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h index 12284cd0e4..c382de3c62 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,40 @@ public: static void runScriptOnLogin(); }; +class LLRequireResolver +{ + public: + enum class ModuleStatus + { + Cached, + FileRead, + NotFound + }; + + struct ResolvedRequire + { + ModuleStatus status; + std::string chunkName; + std::string absolutePath; + std::string sourceCode; + }; + + [[nodiscard]] ResolvedRequire static resolveRequire(lua_State *L, std::string path); + + std::string mChunkname; + std::string mAbsolutePath; + std::string mSourceCode; + + private: + std::string mPathToResolve; + std::string mSourceChunkname; + + LLRequireResolver(lua_State *L, std::string path); + + ModuleStatus findModule(); + lua_State *L; + + void resolveAndStoreDefaultPaths(); + ModuleStatus findModuleImpl(); +}; #endif |