summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaxim Nikolenko <maximnproductengine@lindenlab.com>2024-02-27 18:51:32 +0200
committerGitHub <noreply@github.com>2024-02-27 18:51:32 +0200
commit777586a1865b496f8bfea9651afbd481ea23b4f7 (patch)
tree0cecaa9b4be0958dc7ff126c30382da3781f1f60
parentad32c066691152e6a23f025d6aa5ead0e91b7be9 (diff)
parentedf1a712a7ca8fd741514348e3304af6a766f4aa (diff)
Merge pull request #843 from secondlife/luau-require-impl
Initial require implementation
-rw-r--r--indra/llcommon/lua_function.cpp35
-rw-r--r--indra/llcommon/lua_function.h3
-rw-r--r--indra/llfilesystem/lldir.cpp5
-rw-r--r--indra/llfilesystem/lldir.h1
-rw-r--r--indra/newview/llfloaterluadebug.cpp12
-rw-r--r--indra/newview/llfloaterluadebug.h1
-rw-r--r--indra/newview/llluamanager.cpp186
-rw-r--r--indra/newview/llluamanager.h37
-rw-r--r--indra/newview/skins/default/xui/en/floater_lua_debug.xml9
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"