summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2024-07-02 13:25:52 -0400
committerNat Goodspeed <nat@lindenlab.com>2024-07-02 13:25:52 -0400
commitb6a72ac2c4498ea52691d79b32d1bf763952d3ee (patch)
tree412142ca0a88f13ab81459354e7a5e6f395d17a4
parent3961bac0ef705775883a4b37f2b6a84e41b82c05 (diff)
parent14a05ec7ac2e2df5886e4dc7ae33ce57a4272b8d (diff)
Merge branch 'release/luau-scripting' into lua-appearance-listener
-rw-r--r--indra/llcommon/fsyspath.h5
-rw-r--r--indra/llcommon/lua_function.cpp2
-rw-r--r--indra/llcommon/lua_function.h231
-rw-r--r--indra/newview/tests/llluamanager_test.cpp35
-rw-r--r--indra/test/test.cpp32
5 files changed, 126 insertions, 179 deletions
diff --git a/indra/llcommon/fsyspath.h b/indra/llcommon/fsyspath.h
index aa4e0132bc..3c749d84de 100644
--- a/indra/llcommon/fsyspath.h
+++ b/indra/llcommon/fsyspath.h
@@ -69,6 +69,11 @@ public:
// shadow base-class string() method with UTF-8 aware method
std::string string() const { return super::u8string(); }
+ // On Posix systems, where value_type is already char, this operator
+ // std::string() method shadows the base class operator string_type()
+ // method. But on Windows, where value_type is wchar_t, the base class
+ // doesn't have operator std::string(). Provide it.
+ operator std::string() const { return string(); }
};
#endif /* ! defined(LL_FSYSPATH_H) */
diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp
index 255385b8c4..2d08de68c5 100644
--- a/indra/llcommon/lua_function.cpp
+++ b/indra/llcommon/lua_function.cpp
@@ -38,6 +38,8 @@ const S32 INTERRUPTS_SUSPEND_LIMIT = 100;
#define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n)))
#define lua_rawlen lua_objlen
+int DistinctInt::mValues{0};
+
/*****************************************************************************
* luau namespace
*****************************************************************************/
diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h
index 9cdd5665dc..c32a586d79 100644
--- a/indra/llcommon/lua_function.h
+++ b/indra/llcommon/lua_function.h
@@ -21,8 +21,9 @@
#include "stringize.h"
#include <exception> // std::uncaught_exceptions()
#include <memory> // std::shared_ptr
-#include <optional>
+#include <typeindex>
#include <typeinfo>
+#include <unordered_map>
#include <utility> // std::pair
class LuaListener;
@@ -194,30 +195,43 @@ int name##_luasub::call(lua_State* L)
/*****************************************************************************
* lua_emplace<T>(), lua_toclass<T>()
*****************************************************************************/
+// Every instance of DistinctInt has a different int value, barring int
+// wraparound.
+class DistinctInt
+{
+public:
+ DistinctInt(): mValue(++mValues) {}
+ int get() const { return mValue; }
+ operator int() const { return mValue; }
+private:
+ static int mValues;
+ int mValue;
+};
+
namespace {
-// this closure function retrieves its bound argument to pass to
-// lua_emplace_gc<T>()
-template <class T>
-int lua_emplace_call_gc(lua_State* L);
-// this will be the function called by the new userdata's metatable's __gc()
-template <class T>
-int lua_emplace_gc(lua_State* L);
-// name by which we'll store the new userdata's metatable in the Registry
-template <class T>
-std::string lua_emplace_metaname(const std::string& Tname = LLError::Log::classname<T>());
+template <typename T>
+struct TypeTag
+{
+ // For (std::is_same<T, U>), &TypeTag<T>::value == &TypeTag<U>::value.
+ // For (! std::is_same<T, U>), &TypeTag<T>::value != &TypeTag<U>::value.
+ // And every distinct instance of DistinctInt has a distinct value.
+ // Therefore, TypeTag<T>::value is an int uniquely associated with each
+ // distinct T.
+ static DistinctInt value;
+};
+
+template <typename T>
+DistinctInt TypeTag<T>::value;
} // anonymous namespace
/**
* On the stack belonging to the passed lua_State, push a Lua userdata object
- * with a newly-constructed C++ object std::optional<T>(args...). The new
- * userdata has a metadata table with a __gc() function to ensure that when
- * the userdata instance is garbage-collected, ~T() is called. Also call
- * LL.atexit(lua_emplace_call_gc<T>(object)) to make ~LuaState() call ~T().
- *
- * We wrap the userdata object as std::optional<T> so we can explicitly
- * destroy the contained T, and detect that we've done so.
+ * containing a newly-constructed C++ object T(args...). The userdata has a
+ * Luau destructor guaranteeing that the new T instance is destroyed when the
+ * userdata is garbage-collected, no later than when the LuaState is
+ * destroyed.
*
* Usage:
* lua_emplace<T>(L, T constructor args...);
@@ -226,178 +240,45 @@ std::string lua_emplace_metaname(const std::string& Tname = LLError::Log::classn
template <class T, typename... ARGS>
void lua_emplace(lua_State* L, ARGS&&... args)
{
- using optT = std::optional<T>;
- luaL_checkstack(L, 5, nullptr);
- auto ptr = lua_newuserdata(L, sizeof(optT));
+ luaL_checkstack(L, 1, nullptr);
+ int tag{ TypeTag<T>::value };
+ if (! lua_getuserdatadtor(L, tag))
+ {
+ // We haven't yet told THIS lua_State the destructor to use for this tag.
+ lua_setuserdatadtor(
+ L, tag,
+ [](lua_State*, void* ptr)
+ {
+ // destroy the contained T instance
+ static_cast<T*>(ptr)->~T();
+ });
+ }
+ auto ptr = lua_newuserdatatagged(L, sizeof(T), tag);
// stack is uninitialized userdata
// For now, assume (but verify) that lua_newuserdata() returns a
// conservatively-aligned ptr. If that turns out not to be the case, we
// might have to discard the new userdata, overallocate its successor and
// perform manual alignment -- but only if we must.
- llassert((uintptr_t(ptr) % alignof(optT)) == 0);
+ llassert((uintptr_t(ptr) % alignof(T)) == 0);
// Construct our T there using placement new
- new (ptr) optT(std::in_place, std::forward<ARGS>(args)...);
- // stack is now initialized userdata containing our T instance
-
- // Find or create the metatable shared by all userdata instances holding
- // C++ type T. We want it to be shared across instances, but it must be
- // type-specific because its __gc field is lua_emplace_gc<T>.
- auto Tname{ LLError::Log::classname<T>() };
- auto metaname{ lua_emplace_metaname<T>(Tname) };
- if (luaL_newmetatable(L, metaname.c_str()))
- {
- // just created it: populate it
- auto gcname{ stringize("lua_emplace_gc<", Tname, ">") };
- lua_pushcfunction(L, lua_emplace_gc<T>, gcname.c_str());
- // stack is userdata, metatable, lua_emplace_gc<T>
- lua_setfield(L, -2, "__gc");
- }
- // stack is userdata, metatable
- lua_setmetatable(L, -2);
- // Stack is now userdata, initialized with T(args),
- // with metatable.__gc pointing to lua_emplace_gc<T>.
-
- // But wait, there's more! Use our atexit() function to ensure that this
- // C++ object is eventually destroyed even if the garbage collector never
- // gets around to it.
- lua_getglobal(L, "LL");
- // stack contains userdata, LL
- lua_getfield(L, -1, "atexit");
- // stack contains userdata, LL, LL.atexit
- // ditch LL
- lua_replace(L, -2);
- // stack contains userdata, LL.atexit
-
- // We have a bit of a problem here. We want to allow the garbage collector
- // to collect the userdata if it must; but we also want to register a
- // cleanup function to destroy the value if (usual case) it has NOT been
- // garbage-collected. The problem is that if we bind into atexit()'s queue
- // a strong reference to the userdata, we ensure that the garbage
- // collector cannot collect it, making our metatable with __gc function
- // completely moot. And we must assume that lua_pushcclosure() binds a
- // strong reference to each value passed as a closure.
-
- // The solution is to use one more indirection: create a weak table whose
- // sole entry is the userdata. If all other references to the new userdata
- // are forgotten, so the only remaining reference is the weak table, the
- // userdata can be collected. Then we can bind that weak table as the
- // closure value for our cleanup function.
- // The new weak table will have at most 1 array value, 0 other keys.
- lua_createtable(L, 1, 0);
- // stack contains userdata, LL.atexit, weak_table
- if (luaL_newmetatable(L, "weak_values"))
- {
- // stack contains userdata, LL.atexit, weak_table, weak_values
- // just created "weak_values" metatable: populate it
- // Registry.weak_values = {__mode="v"}
- lua_pushliteral(L, "v");
- // stack contains userdata, LL.atexit, weak_table, weak_values, "v"
- lua_setfield(L, -2, "__mode");
- }
- // stack contains userdata, LL.atexit, weak_table, weak_values
- // setmetatable(weak_table, weak_values)
- lua_setmetatable(L, -2);
- // stack contains userdata, LL.atexit, weak_table
- lua_pushinteger(L, 1);
- // stack contains userdata, LL.atexit, weak_table, 1
- // duplicate userdata
- lua_pushvalue(L, -4);
- // stack contains userdata, LL.atexit, weak_table, 1, userdata
- // weak_table[1] = userdata
- lua_settable(L, -3);
- // stack contains userdata, LL.atexit, weak_table
-
- // push a closure binding (lua_emplace_call_gc<T>, weak_table)
- auto callgcname{ stringize("lua_emplace_call_gc<", Tname, ">") };
- lua_pushcclosure(L, lua_emplace_call_gc<T>, callgcname.c_str(), 1);
- // stack contains userdata, LL.atexit, closure
- // Call LL.atexit(closure)
- lua_call(L, 1, 0);
- // stack contains userdata -- return that
+ new (ptr) T(std::forward<ARGS>(args)...);
+ // stack is now initialized userdata containing our T instance -- return
+ // that
}
-namespace {
-
-// passed to LL.atexit(closure(lua_emplace_call_gc<T>, weak_table{userdata}));
-// retrieves bound userdata to pass to lua_emplace_gc<T>()
-template <class T>
-int lua_emplace_call_gc(lua_State* L)
-{
- luaL_checkstack(L, 2, nullptr);
- // retrieve the first (only) bound upvalue and push to stack top
- lua_pushvalue(L, lua_upvalueindex(1));
- // This is the weak_table bound by lua_emplace<T>(). Its one and only
- // entry should be the lua_emplace<T>() userdata -- unless userdata has
- // been garbage collected. Retrieve weak_table[1].
- lua_pushinteger(L, 1);
- // stack contains weak_table, 1
- lua_gettable(L, -2);
- // stack contains weak_table, weak_table[1]
- // If our userdata was garbage-collected, there is no weak_table[1],
- // and we just retrieved nil.
- if (lua_isnil(L, -1))
- {
- lua_pop(L, 2);
- return 0;
- }
- // stack contains weak_table, userdata
- // ditch weak_table
- lua_replace(L, -2);
- // pass userdata to lua_emplace_gc<T>()
- return lua_emplace_gc<T>(L);
-}
-
-// set as metatable(userdata).__gc to be called by the garbage collector
-template <class T>
-int lua_emplace_gc(lua_State* L)
-{
- using optT = std::optional<T>;
- // We're called with userdata on the stack holding an instance of type T.
- auto ptr = lua_touserdata(L, -1);
- llassert(ptr);
- // Destroy the T object contained in optT at the void* address ptr. If
- // in future lua_emplace() must manually align our optT* within the
- // Lua-provided void*, derive optT* from ptr.
- static_cast<optT*>(ptr)->reset();
- // pop the userdata
- lua_pop(L, 1);
- return 0;
-}
-
-template <class T>
-std::string lua_emplace_metaname(const std::string& Tname)
-{
- return stringize("lua_emplace_", Tname, "_meta");
-}
-
-} // anonymous namespace
-
/**
* If the value at the passed acceptable index is a full userdata created by
- * lua_emplace<T>() -- that is, the userdata contains a non-empty
- * std::optional<T> -- return a pointer to the contained T instance. Otherwise
- * (index is not a full userdata; userdata is not of type std::optional<T>;
- * std::optional<T> is empty) return nullptr.
+ * lua_emplace<T>(), return a pointer to the contained T instance. Otherwise
+ * (index is not a full userdata; userdata is not of type T) return nullptr.
*/
template <class T>
T* lua_toclass(lua_State* L, int index)
{
- using optT = std::optional<T>;
- // recreate the name lua_emplace<T>() uses for its metatable
- auto metaname{ lua_emplace_metaname<T>() };
// get void* pointer to userdata (if that's what it is)
- void* ptr{ luaL_checkudata(L, index, metaname.c_str()) };
- if (! ptr)
- return nullptr;
- // Derive the optT* from ptr. If in future lua_emplace() must manually
- // align our optT* within the Lua-provided void*, adjust accordingly.
- optT* tptr(static_cast<optT*>(ptr));
- // make sure our optT isn't empty
- if (! *tptr)
- return nullptr;
- // looks like we still have a non-empty optT: return the *address* of the
- // value() reference
- return &tptr->value();
+ void* ptr{ lua_touserdatatagged(L, index, TypeTag<T>::value) };
+ // Derive the T* from ptr. If in future lua_emplace() must manually
+ // align our T* within the Lua-provided void*, adjust accordingly.
+ return static_cast<T*>(ptr);
}
/*****************************************************************************
diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp
index 2d525f7913..d3fc70dfd5 100644
--- a/indra/newview/tests/llluamanager_test.cpp
+++ b/indra/newview/tests/llluamanager_test.cpp
@@ -465,4 +465,39 @@ namespace tut
ensure_equals(desc + " count: " + result.asString(), count, -1);
ensure_contains(desc + " result", result.asString(), "terminated");
}
+
+ template <typename T>
+ struct Visible
+ {
+ Visible(T name): name(name)
+ {
+ LL_INFOS() << "Visible<" << LLError::Log::classname<T>() << ">('" << name << "')" << LL_ENDL;
+ }
+ Visible(const Visible&) = delete;
+ Visible& operator=(const Visible&) = delete;
+ ~Visible()
+ {
+ LL_INFOS() << "~Visible<" << LLError::Log::classname<T>() << ">('" << name << "')" << LL_ENDL;
+ }
+ T name;
+ };
+
+ template<> template<>
+ void object::test<9>()
+ {
+ set_test_name("track distinct lua_emplace<T>() types");
+ LuaState L;
+ lua_emplace<Visible<std::string>>(L, "std::string 0");
+ int st0tag = lua_userdatatag(L, -1);
+ lua_emplace<Visible<const char*>>(L, "const char* 0");
+ int cp0tag = lua_userdatatag(L, -1);
+ lua_emplace<Visible<std::string>>(L, "std::string 1");
+ int st1tag = lua_userdatatag(L, -1);
+ lua_emplace<Visible<const char*>>(L, "const char* 1");
+ int cp1tag = lua_userdatatag(L, -1);
+ lua_settop(L, 0);
+ ensure_equals("lua_emplace<std::string>() tags diverge", st0tag, st1tag);
+ ensure_equals("lua_emplace<const char*>() tags diverge", cp0tag, cp1tag);
+ ensure_not_equals("lua_emplace<>() tags collide", st0tag, cp0tag);
+ }
} // namespace tut
diff --git a/indra/test/test.cpp b/indra/test/test.cpp
index 61a4eb07c5..0e863d8084 100644
--- a/indra/test/test.cpp
+++ b/indra/test/test.cpp
@@ -35,13 +35,14 @@
*/
#include "linden_common.h"
-#include "llerrorcontrol.h"
-#include "lltut.h"
#include "chained_callback.h"
-#include "stringize.h"
-#include "namedtempfile.h"
+#include "fsyspath.h"
+#include "llerrorcontrol.h"
#include "lltrace.h"
#include "lltracethreadrecorder.h"
+#include "lltut.h"
+#include "namedtempfile.h"
+#include "stringize.h"
#include "apr_pools.h"
#include "apr_getopt.h"
@@ -545,6 +546,29 @@ int main(int argc, char **argv)
// LOGTEST overrides default, but can be overridden by --debug.
const char* LOGTEST = getenv("LOGTEST");
+ // Sometimes we must rebuild much of the viewer before we get to the
+ // specific test we want to monitor, and some viewer integration tests are
+ // quite verbose. In addition to noticing plain LOGTEST= (for all tests),
+ // also notice LOGTEST_progname= (for a specific test).
+ // (Why doesn't MSVC notice fsyspath::operator std::string()?
+ // Why must we explicitly call fsyspath::string()?)
+ std::string basename(fsyspath(argv[0]).stem().string());
+ // don't make user set LOGTEST_INTEGRATION_TEST_progname or (worse)
+ // LOGTEST_PROJECT_foo_TEST_bar -- only LOGTEST_progname or LOGTEST_bar
+ auto _TEST_ = basename.find("_TEST_");
+ if (_TEST_ != std::string::npos)
+ {
+ basename.erase(0, _TEST_+6);
+ }
+ std::string LOGTEST_prog_key("LOGTEST_" + basename);
+ const char* LOGTEST_prog = getenv(LOGTEST_prog_key.c_str());
+// std::cout << LOGTEST_prog_key << "='" << (LOGTEST_prog? LOGTEST_prog : "") << "'" << std::endl;
+ if (LOGTEST_prog && *LOGTEST_prog)
+ {
+ LOGTEST = LOGTEST_prog;
+ std::cout << "LOGTEST='" << LOGTEST << "' from " << LOGTEST_prog_key << std::endl;
+ }
+
// values used for options parsing
apr_status_t apr_err;
const char* opt_arg = NULL;