From cd997f21c272987e954f5890ed14fcc327eb734e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 4 Nov 2022 17:21:30 -0400 Subject: DRTVWR-575: Introduce llssize (signed size_t) and narrow() function. llssize is for a function parameter that should accept a size or index (derived from size_t, which is 64 bits in a 64-bit viewer) but might need to go negative for flag values. We've historically used S32 for that purpose, but Xcode 14.1 complains about trying to pass size_t to S32. narrow() is a template function that casts a wider type (e.g. size_t or llssize) to a narrower type (e.g. S32 or U32), with validation in RelWithDebInfo builds. It verifies (using assert()) that the value being truncated can in fact fit into the target type. --- indra/llcommon/stdtypes.h | 106 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 102 insertions(+), 4 deletions(-) (limited to 'indra') 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 #include #include +#include +#include -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::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(len) or + * S32(len) or (S32)len, but we can do better. + * + * For: + * @code + * std::vector 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 +class narrow_holder +{ +private: + FROM mValue; + +public: + narrow_holder(FROM value): mValue(value) {} + + /*---------------------- Narrowing unsigned to signed ----------------------*/ + template ::value && + std::is_signed::value, + bool>::type = true> + inline + operator TO() const + { + // The reason we skip the + // assert(value >= std::numeric_limits::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::max()); + return static_cast(mValue); + } + + /*----------------------- Narrowing all other cases ------------------------*/ + template ::value && + std::is_signed::value), + bool>::type = true> + inline + operator TO() const + { + // two different assert()s so we can tell which condition failed + assert(mValue <= std::numeric_limits::max()); + // Funny, with floating point types min() is "positive epsilon" rather + // than "largest negative" -- that's lowest(). + assert(mValue >= std::numeric_limits::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::min()); + return static_cast(mValue); + } +}; + +/// narrow() factory function returns a narrow_holder(), which can be +/// implicitly converted to the target type. +template +inline +narrow_holder narrow(FROM value) +{ + return { value }; +} + #endif -- cgit v1.2.3