summaryrefslogtreecommitdiff
path: root/indra/llcommon
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon')
-rw-r--r--indra/llcommon/lldefs.h53
-rw-r--r--indra/llcommon/lua_function.cpp160
-rw-r--r--indra/llcommon/lua_function.h5
-rw-r--r--indra/llcommon/tests/llcond_test.cpp75
4 files changed, 232 insertions, 61 deletions
diff --git a/indra/llcommon/lldefs.h b/indra/llcommon/lldefs.h
index 2fbb26dc1a..d4b063f88c 100644
--- a/indra/llcommon/lldefs.h
+++ b/indra/llcommon/lldefs.h
@@ -28,6 +28,7 @@
#define LL_LLDEFS_H
#include "stdtypes.h"
+#include <cassert>
#include <type_traits>
// Often used array indices
@@ -169,6 +170,38 @@ constexpr U32 MAXADDRSTR = 17; // 123.567.901.345 = 15 chars + \0 +
// llclampb(a) // clamps a to [0 .. 255]
//
+// llless(d0, d1) safely compares d0 < d1 even if one is signed and the other
+// is unsigned. A simple (d0 < d1) expression converts the signed operand to
+// unsigned before comparing. If the signed operand is negative, that flips
+// the negative value to a huge positive value, producing the wrong answer!
+// llless() specifically addresses that case.
+template <typename T0, typename T1>
+inline bool llless(T0 d0, T1 d1)
+{
+ if constexpr (std::is_signed_v<T0> && ! std::is_signed_v<T1>)
+ {
+ // T0 signed, T1 unsigned: negative d0 is less than any unsigned d1
+ if (d0 < 0)
+ return true;
+ // both are non-negative: explicitly cast to avoid C4018
+ return std::make_unsigned_t<T0>(d0) < d1;
+ }
+ else if constexpr (! std::is_signed_v<T0> && std::is_signed_v<T1>)
+ {
+ // T0 unsigned, T1 signed: any unsigned d0 is greater than negative d1
+ if (d1 < 0)
+ return false;
+ // both are non-negative: explicitly cast to avoid C4018
+ return d0 < std::make_unsigned_t<T1>(d1);
+ }
+ else
+ {
+ // both T0 and T1 are signed, or both are unsigned:
+ // straightforward comparison works
+ return d0 < d1;
+ }
+}
+
// recursion tail
template <typename T>
inline auto llmax(T data)
@@ -180,7 +213,7 @@ template <typename T0, typename T1, typename... Ts>
inline auto llmax(T0 d0, T1 d1, Ts... rest)
{
auto maxrest = llmax(d1, rest...);
- return (d0 > maxrest)? d0 : maxrest;
+ return llless(maxrest, d0)? d0 : maxrest;
}
// recursion tail
@@ -194,12 +227,28 @@ template <typename T0, typename T1, typename... Ts>
inline auto llmin(T0 d0, T1 d1, Ts... rest)
{
auto minrest = llmin(d1, rest...);
- return (d0 < minrest) ? d0 : minrest;
+ return llless(d0, minrest) ? d0 : minrest;
}
template <typename A, typename MIN, typename MAX>
inline A llclamp(A a, MIN minval, MAX maxval)
{
+ // The only troublesome case is if A is unsigned and either minval or
+ // maxval is both signed and negative. Casting a negative number to
+ // unsigned flips it to a huge positive number, making this llclamp() call
+ // ineffective.
+ if constexpr (! std::is_signed_v<A>)
+ {
+ if constexpr (std::is_signed_v<MIN>)
+ {
+ assert(minval >= 0);
+ }
+ if constexpr (std::is_signed_v<MAX>)
+ {
+ assert(maxval >= 0);
+ }
+ }
+
A aminval{ static_cast<A>(minval) }, amaxval{ static_cast<A>(maxval) };
if ( a < aminval )
{
diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp
index f7876e4aaf..eefb1e62cf 100644
--- a/indra/llcommon/lua_function.cpp
+++ b/indra/llcommon/lua_function.cpp
@@ -24,6 +24,7 @@
#include <unordered_map>
// external library headers
// other Linden headers
+#include "commoncontrol.h"
#include "fsyspath.h"
#include "hexdump.h"
#include "llcoros.h"
@@ -529,9 +530,35 @@ int lua_metaipair(lua_State* L);
} // anonymous namespace
LuaState::LuaState(script_finished_fn cb):
- mCallback(cb),
- mState(luaL_newstate())
+ mCallback(cb)
{
+ /*---------------------------- feature flag ----------------------------*/
+ try
+ {
+ mFeature = LL::CommonControl::get("Global", "LuaFeature").asBoolean();
+ }
+ catch (const LL::CommonControl::NoListener&)
+ {
+ // If this program doesn't have an LLViewerControlListener,
+ // it's probably a test program; go ahead.
+ mFeature = true;
+ }
+ catch (const LL::CommonControl::ParamError&)
+ {
+ // We found LLViewerControlListener, but its settings do not include
+ // "LuaFeature". Hmm, fishy: that feature flag was introduced at the
+ // same time as this code.
+ mFeature = false;
+ }
+ // None of the rest of this is necessary if we're not going to run anything.
+ if (! mFeature)
+ {
+ mError = "Lua feature disabled";
+ return;
+ }
+ /*---------------------------- feature flag ----------------------------*/
+
+ mState = luaL_newstate();
// Ensure that we can always find this LuaState instance, given the
// lua_State we just created or any of its coroutines.
sLuaStateMap.emplace(mState, this);
@@ -682,75 +709,81 @@ int lua_metaipair(lua_State* L)
LuaState::~LuaState()
{
- // We're just about to destroy this lua_State mState. Did this Lua chunk
- // register any atexit() functions?
- lluau_checkstack(mState, 3);
- // look up Registry.atexit
- lua_getfield(mState, LUA_REGISTRYINDEX, "atexit");
- // stack contains Registry.atexit
- if (lua_istable(mState, -1))
+ /*---------------------------- feature flag ----------------------------*/
+ if (mFeature)
+ /*---------------------------- feature flag ----------------------------*/
{
- // We happen to know that Registry.atexit is built by appending array
- // entries using table.insert(). That's important because it means
- // there are no holes, and therefore lua_objlen() should be correct.
- // That's important because we walk the atexit table backwards, to
- // destroy last the things we created (passed to LL.atexit()) first.
- int len(lua_objlen(mState, -1));
- LL_DEBUGS("Lua") << LLCoros::getName() << ": Registry.atexit is a table with "
- << len << " entries" << LL_ENDL;
-
- // Push debug.traceback() onto the stack as lua_pcall()'s error
- // handler function. On error, lua_pcall() calls the specified error
- // handler function with the original error message; the message
- // returned by the error handler is then returned by lua_pcall().
- // Luau's debug.traceback() is called with a message to prepend to the
- // returned traceback string. Almost as if they'd been designed to
- // work together...
- lua_getglobal(mState, "debug");
- lua_getfield(mState, -1, "traceback");
- // ditch "debug"
- lua_remove(mState, -2);
- // stack now contains atexit, debug.traceback()
-
- for (int i(len); i >= 1; --i)
+ // We're just about to destroy this lua_State mState. Did this Lua chunk
+ // register any atexit() functions?
+ lluau_checkstack(mState, 3);
+ // look up Registry.atexit
+ lua_getfield(mState, LUA_REGISTRYINDEX, "atexit");
+ // stack contains Registry.atexit
+ if (lua_istable(mState, -1))
{
- lua_pushinteger(mState, i);
- // stack contains Registry.atexit, debug.traceback(), i
- lua_gettable(mState, -3);
- // stack contains Registry.atexit, debug.traceback(), atexit[i]
- // Call atexit[i](), no args, no return values.
- // Use lua_pcall() because errors in any one atexit() function
- // shouldn't cancel the rest of them. Pass debug.traceback() as
- // the error handler function.
- LL_DEBUGS("Lua") << LLCoros::getName()
- << ": calling atexit(" << i << ")" << LL_ENDL;
- if (lua_pcall(mState, 0, 0, -2) != LUA_OK)
+ // We happen to know that Registry.atexit is built by appending array
+ // entries using table.insert(). That's important because it means
+ // there are no holes, and therefore lua_objlen() should be correct.
+ // That's important because we walk the atexit table backwards, to
+ // destroy last the things we created (passed to LL.atexit()) first.
+ int len(lua_objlen(mState, -1));
+ LL_DEBUGS("Lua") << LLCoros::getName() << ": Registry.atexit is a table with "
+ << len << " entries" << LL_ENDL;
+
+ // Push debug.traceback() onto the stack as lua_pcall()'s error
+ // handler function. On error, lua_pcall() calls the specified error
+ // handler function with the original error message; the message
+ // returned by the error handler is then returned by lua_pcall().
+ // Luau's debug.traceback() is called with a message to prepend to the
+ // returned traceback string. Almost as if they'd been designed to
+ // work together...
+ lua_getglobal(mState, "debug");
+ lua_getfield(mState, -1, "traceback");
+ // ditch "debug"
+ lua_remove(mState, -2);
+ // stack now contains atexit, debug.traceback()
+
+ for (int i(len); i >= 1; --i)
{
- auto error{ lua_tostdstring(mState, -1) };
- LL_WARNS("Lua") << LLCoros::getName()
- << ": atexit(" << i << ") error: " << error << LL_ENDL;
- // pop error message
- lua_pop(mState, 1);
+ lua_pushinteger(mState, i);
+ // stack contains Registry.atexit, debug.traceback(), i
+ lua_gettable(mState, -3);
+ // stack contains Registry.atexit, debug.traceback(), atexit[i]
+ // Call atexit[i](), no args, no return values.
+ // Use lua_pcall() because errors in any one atexit() function
+ // shouldn't cancel the rest of them. Pass debug.traceback() as
+ // the error handler function.
+ LL_DEBUGS("Lua") << LLCoros::getName()
+ << ": calling atexit(" << i << ")" << LL_ENDL;
+ if (lua_pcall(mState, 0, 0, -2) != LUA_OK)
+ {
+ auto error{ lua_tostdstring(mState, -1) };
+ LL_WARNS("Lua") << LLCoros::getName()
+ << ": atexit(" << i << ") error: " << error << LL_ENDL;
+ // pop error message
+ lua_pop(mState, 1);
+ }
+ LL_DEBUGS("Lua") << LLCoros::getName() << ": atexit(" << i << ") done" << LL_ENDL;
+ // lua_pcall() has already popped atexit[i]:
+ // stack contains atexit, debug.traceback()
}
- LL_DEBUGS("Lua") << LLCoros::getName() << ": atexit(" << i << ") done" << LL_ENDL;
- // lua_pcall() has already popped atexit[i]:
- // stack contains atexit, debug.traceback()
+ // pop debug.traceback()
+ lua_pop(mState, 1);
}
- // pop debug.traceback()
+ // pop Registry.atexit (either table or nil)
lua_pop(mState, 1);
- }
- // pop Registry.atexit (either table or nil)
- lua_pop(mState, 1);
- lua_close(mState);
+ // with the demise of this LuaState, remove sLuaStateMap entry
+ sLuaStateMap.erase(mState);
+
+ lua_close(mState);
+ }
if (mCallback)
{
// mError potentially set by previous checkLua() call(s)
mCallback(mError);
}
- // with the demise of this LuaState, remove sLuaStateMap entry
- sLuaStateMap.erase(mState);
}
bool LuaState::checkLua(const std::string& desc, int r)
@@ -768,6 +801,14 @@ bool LuaState::checkLua(const std::string& desc, int r)
std::pair<int, LLSD> LuaState::expr(const std::string& desc, const std::string& text)
{
+ /*---------------------------- feature flag ----------------------------*/
+ if (! mFeature)
+ {
+ // fake an error
+ return { -1, stringize("Not running ", desc) };
+ }
+ /*---------------------------- feature flag ----------------------------*/
+
set_interrupts_counter(0);
lua_callbacks(mState)->interrupt = [](lua_State *L, int gc)
@@ -843,6 +884,9 @@ std::pair<int, LLSD> LuaState::expr(const std::string& desc, const std::string&
return result;
}
+// We think we don't need mFeature tests in the rest of these LuaState methods
+// because, if expr() isn't running code, nobody should be calling any of them.
+
LuaListener& LuaState::obtainListener(lua_State* L)
{
lluau_checkstack(L, 2);
diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h
index e28656c03b..967d8eaba1 100644
--- a/indra/llcommon/lua_function.h
+++ b/indra/llcommon/lua_function.h
@@ -115,8 +115,11 @@ public:
void check_interrupts_counter();
private:
+ /*---------------------------- feature flag ----------------------------*/
+ bool mFeature{ false };
+ /*---------------------------- feature flag ----------------------------*/
script_finished_fn mCallback;
- lua_State* mState;
+ lua_State* mState{ nullptr };
std::string mError;
S32 mInterrupts{ 0 };
};
diff --git a/indra/llcommon/tests/llcond_test.cpp b/indra/llcommon/tests/llcond_test.cpp
index f2a302ed13..7d1ac6edb9 100644
--- a/indra/llcommon/tests/llcond_test.cpp
+++ b/indra/llcommon/tests/llcond_test.cpp
@@ -19,6 +19,7 @@
// other Linden headers
#include "../test/lltut.h"
#include "llcoros.h"
+#include "lldefs.h" // llless()
/*****************************************************************************
* TUT
@@ -64,4 +65,78 @@ namespace tut
cond.set_all(2);
cond.wait_equal(3);
}
+
+ template <typename T0, typename T1>
+ struct compare
+ {
+ const char* desc;
+ T0 lhs;
+ T1 rhs;
+ bool expect;
+
+ void test() const
+ {
+ // fails
+// ensure_equals(desc, (lhs < rhs), expect);
+ ensure_equals(desc, llless(lhs, rhs), expect);
+ }
+ };
+
+ template<> template<>
+ void object::test<3>()
+ {
+ set_test_name("comparison");
+ // Try to construct signed and unsigned variables such that the
+ // compiler can't optimize away the code to compare at runtime.
+ std::istringstream input("-1 10 20 10 20");
+ int minus1, s10, s20;
+ input >> minus1 >> s10 >> s20;
+ unsigned u10, u20;
+ input >> u10 >> u20;
+ ensure_equals("minus1 wrong", minus1, -1);
+ ensure_equals("s10 wrong", s10, 10);
+ ensure_equals("s20 wrong", s20, 20);
+ ensure_equals("u10 wrong", u10, 10);
+ ensure_equals("u20 wrong", u20, 20);
+ // signed < signed should always work!
+ compare<int, int> ss[] =
+ { {"minus1 < s10", minus1, s10, true},
+ {"s10 < s10", s10, s10, false},
+ {"s20 < s10", s20, s20, false}
+ };
+ for (const auto& cmp : ss)
+ {
+ cmp.test();
+ }
+ // unsigned < unsigned should always work!
+ compare<unsigned, unsigned> uu[] =
+ { {"u10 < u20", u10, u20, true},
+ {"u20 < u20", u20, u20, false},
+ {"u20 < u10", u20, u10, false}
+ };
+ for (const auto& cmp : uu)
+ {
+ cmp.test();
+ }
+ // signed < unsigned ??
+ compare<int, unsigned> su[] =
+ { {"minus1 < u10", minus1, u10, true},
+ {"s10 < u10", s10, u10, false},
+ {"s20 < u10", s20, u10, false}
+ };
+ for (const auto& cmp : su)
+ {
+ cmp.test();
+ }
+ // unsigned < signed ??
+ compare<unsigned, int> us[] =
+ { {"u10 < minus1", u10, minus1, false},
+ {"u10 < s10", u10, s10, false},
+ {"u10 < s20", u10, s20, true}
+ };
+ for (const auto& cmp : us)
+ {
+ cmp.test();
+ }
+ }
} // namespace tut