summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
authorMnikolenko Productengine <mnikolenko@productengine.com>2024-02-20 14:25:56 +0200
committerMnikolenko Productengine <mnikolenko@productengine.com>2024-02-20 14:25:56 +0200
commitb305fb6411939bf6afbe2ecf2c1bdf765c9247a9 (patch)
tree6421d6425b20c0848180b847785f7923be0acae9 /indra
parent9b1796df73f2d4fa19517f390258f798c10bff1e (diff)
Initial require implementation
Diffstat (limited to 'indra')
-rw-r--r--indra/llcommon/lua_function.cpp9
-rw-r--r--indra/llcommon/lua_function.h9
-rw-r--r--indra/newview/llluamanager.cpp207
-rw-r--r--indra/newview/llluamanager.h39
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