summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indra/llcommon/stdtypes.h106
1 files changed, 102 insertions, 4 deletions
diff --git a/indra/llcommon/stdtypes.h b/indra/llcommon/stdtypes.h
index b07805b628..a030017e3b 100644
--- a/indra/llcommon/stdtypes.h
+++ b/indra/llcommon/stdtypes.h
@@ -26,16 +26,23 @@
#ifndef LL_STDTYPES_H
#define LL_STDTYPES_H
+#include <cassert>
#include <cfloat>
#include <climits>
+#include <limits>
+#include <type_traits>
-typedef signed char S8;
+typedef signed char S8;
typedef unsigned char U8;
typedef signed short S16;
typedef unsigned short U16;
-typedef signed int S32;
+typedef signed int S32;
typedef unsigned int U32;
+// to express an index that might go negative
+// (ssize_t is provided by SOME compilers, don't collide)
+typedef typename std::make_signed<size_t>::type llssize;
+
#if LL_WINDOWS
// https://docs.microsoft.com/en-us/cpp/build/reference/zc-wchar-t-wchar-t-is-native-type
// https://docs.microsoft.com/en-us/cpp/cpp/fundamental-types-cpp
@@ -45,7 +52,7 @@ typedef unsigned int U32;
// The version of clang available with VS 2019 also defines wchar_t as __wchar_t
// which is also 16 bits.
// In any case, llwchar should be a UTF-32 type.
-typedef U32 llwchar;
+typedef U32 llwchar;
#else
typedef wchar_t llwchar;
// What we'd actually want is a simple module-scope 'if constexpr' to test
@@ -76,7 +83,7 @@ typedef double F64;
typedef S32 BOOL;
typedef U8 KEY;
typedef U32 MASK;
-typedef U32 TPACKETID;
+typedef U32 TPACKETID;
// Use #define instead of consts to avoid conversion headaches
#define S8_MAX (SCHAR_MAX)
@@ -118,4 +125,95 @@ typedef U8 LLPCode;
typedef int intptr_t;
#endif
+/*****************************************************************************
+* Narrowing
+*****************************************************************************/
+/**
+ * narrow() is used to cast a wider type to a narrower type with validation.
+ *
+ * In many cases we take the size() of a container and try to pass it to an
+ * S32 or a U32 parameter. We used to be able to assume that the size of
+ * anything we could fit into memory could be expressed as a 32-bit int. With
+ * 64-bit viewers, though, size_t as returned by size() and length() and so
+ * forth is 64 bits, and the compiler is unhappy about stuffing such values
+ * into 32-bit types.
+ *
+ * It works to force the compiler to truncate, e.g. static_cast<S32>(len) or
+ * S32(len) or (S32)len, but we can do better.
+ *
+ * For:
+ * @code
+ * std::vector<Object> container;
+ * void somefunc(S32 size);
+ * @endcode
+ * call:
+ * @code
+ * somefunc(narrow(container.size()));
+ * @endcode
+ *
+ * narrow() truncates but, in RelWithDebInfo builds, it validates (using
+ * assert()) that the passed value can validly be expressed by the destination
+ * type.
+ */
+// narrow_holder is a struct that accepts the passed value as its original
+// type and provides templated conversion functions to other types. Once we're
+// building with compilers that support Class Template Argument Deduction, we
+// can rename this class template 'narrow' and eliminate the narrow() factory
+// function below.
+template <typename FROM>
+class narrow_holder
+{
+private:
+ FROM mValue;
+
+public:
+ narrow_holder(FROM value): mValue(value) {}
+
+ /*---------------------- Narrowing unsigned to signed ----------------------*/
+ template <typename TO,
+ typename std::enable_if<std::is_unsigned<FROM>::value &&
+ std::is_signed<TO>::value,
+ bool>::type = true>
+ inline
+ operator TO() const
+ {
+ // The reason we skip the
+ // assert(value >= std::numeric_limits<TO>::lowest());
+ // in the overload below is that to perform the above comparison, the
+ // compiler promotes the signed lowest() to the unsigned FROM type,
+ // making it hugely positive -- so a reasonable 'value' will always
+ // fail the assert().
+ assert(mValue <= std::numeric_limits<TO>::max());
+ return static_cast<TO>(mValue);
+ }
+
+ /*----------------------- Narrowing all other cases ------------------------*/
+ template <typename TO,
+ typename std::enable_if<! (std::is_unsigned<FROM>::value &&
+ std::is_signed<TO>::value),
+ bool>::type = true>
+ inline
+ operator TO() const
+ {
+ // two different assert()s so we can tell which condition failed
+ assert(mValue <= std::numeric_limits<TO>::max());
+ // Funny, with floating point types min() is "positive epsilon" rather
+ // than "largest negative" -- that's lowest().
+ assert(mValue >= std::numeric_limits<TO>::lowest());
+ // Do we really expect to use this with floating point types?
+ // If so, does it matter if a very small value truncates to zero?
+ //assert(fabs(mValue) >= std::numeric_limits<TO>::min());
+ return static_cast<TO>(mValue);
+ }
+};
+
+/// narrow() factory function returns a narrow_holder<FROM>(), which can be
+/// implicitly converted to the target type.
+template <typename FROM>
+inline
+narrow_holder<FROM> narrow(FROM value)
+{
+ return { value };
+}
+
#endif