tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Casting.h (11763B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 /* Cast operations to supplement the built-in casting operations. */
      8 
      9 #ifndef mozilla_Casting_h
     10 #define mozilla_Casting_h
     11 
     12 #include "mozilla/Assertions.h"
     13 
     14 #include <cinttypes>
     15 #include <cmath>
     16 #include <limits>
     17 #include <type_traits>
     18 
     19 #ifndef __clang__
     20 #  include <cstring>
     21 #endif
     22 
     23 #ifdef DEBUG
     24 #  include "fmt/format.h"
     25 #  include <cstdio>
     26 #endif
     27 
     28 namespace mozilla {
     29 
     30 /**
     31 * Sets the outparam value of type |To| with the same underlying bit pattern of
     32 * |aFrom|.
     33 *
     34 * |To| and |From| must be types of the same size; be careful of cross-platform
     35 * size differences, or this might fail to compile on some but not all
     36 * platforms.
     37 *
     38 * There is also a variant that returns the value directly.  In most cases, the
     39 * two variants should be identical.  However, in the specific case of x86
     40 * chips, the behavior differs: returning floating-point values directly is done
     41 * through the x87 stack, and x87 loads and stores turn signaling NaNs into
     42 * quiet NaNs... silently.  Returning floating-point values via outparam,
     43 * however, is done entirely within the SSE registers when SSE2 floating-point
     44 * is enabled in the compiler, which has semantics-preserving behavior you would
     45 * expect.
     46 *
     47 * If preserving the distinction between signaling NaNs and quiet NaNs is
     48 * important to you, you should use the outparam version.  In all other cases,
     49 * you should use the direct return version.
     50 */
     51 template <typename To, typename From>
     52 inline void BitwiseCast(const From aFrom, To* aResult) {
     53  // FIXME: use std::bit_cast once we move to C++20
     54 #if defined(__clang__) || (defined(__GNUC__) && __GNUC__ >= 11)
     55  *aResult = __builtin_bit_cast(To, aFrom);
     56 #else
     57  static_assert(sizeof(From) == sizeof(To),
     58                "To and From must have the same size");
     59 
     60  // We could maybe downgrade these to std::is_trivially_copyable, but the
     61  // various STLs we use don't all provide it.
     62  static_assert(std::is_trivial<From>::value,
     63                "shouldn't bitwise-copy a type having non-trivial "
     64                "initialization");
     65  static_assert(std::is_trivial<To>::value,
     66                "shouldn't bitwise-copy a type having non-trivial "
     67                "initialization");
     68  std::memcpy(static_cast<void*>(aResult), static_cast<const void*>(&aFrom),
     69              sizeof(From));
     70 #endif
     71 }
     72 
     73 template <typename To, typename From>
     74 inline To BitwiseCast(const From aFrom) {
     75  To temp;
     76  BitwiseCast<To, From>(aFrom, &temp);
     77  return temp;
     78 }
     79 
     80 namespace detail {
     81 
     82 template <typename T>
     83 constexpr int64_t safe_integer() {
     84  static_assert(std::is_floating_point_v<T>);
     85  return std::pow(2, std::numeric_limits<T>::digits);
     86 }
     87 
     88 template <typename T>
     89 constexpr uint64_t safe_integer_unsigned() {
     90  static_assert(std::is_floating_point_v<T>);
     91  return std::pow(2, std::numeric_limits<T>::digits);
     92 }
     93 
     94 template <typename T>
     95 const char* TypeToStringFallback();
     96 
     97 template <typename T>
     98 inline constexpr const char* TypeToString() {
     99  return TypeToStringFallback<T>();
    100 }
    101 
    102 #define T2S(type)                                     \
    103  template <>                                         \
    104  inline constexpr const char* TypeToString<type>() { \
    105    return #type;                                     \
    106  }
    107 
    108 #define T2SF(type)                                            \
    109  template <>                                                 \
    110  inline constexpr const char* TypeToStringFallback<type>() { \
    111    return #type;                                             \
    112  }
    113 
    114 T2S(uint8_t);
    115 T2S(uint16_t);
    116 T2S(uint32_t);
    117 T2S(uint64_t);
    118 T2S(int8_t);
    119 T2S(int16_t);
    120 T2S(int32_t);
    121 T2S(int64_t);
    122 T2S(char16_t);
    123 T2S(char32_t);
    124 T2SF(int);
    125 T2SF(unsigned int);
    126 T2SF(long);
    127 T2SF(unsigned long);
    128 T2S(float);
    129 T2S(double);
    130 
    131 #undef T2S
    132 #undef T2SF
    133 
    134 #ifdef DEBUG
    135 template <typename In, typename Out>
    136 inline void DiagnosticMessage(In aIn, char aDiagnostic[1024]) {
    137  if constexpr (std::is_same_v<In, char> || std::is_same_v<In, wchar_t> ||
    138                std::is_same_v<In, char16_t> || std::is_same_v<In, char32_t>) {
    139    static_assert(sizeof(wchar_t) <= sizeof(int32_t));
    140    // Characters types are printed in hexadecimal for two reasons:
    141    // - to easily debug problems with non-printable characters.
    142    // - {fmt} refuses to format a string with mixed character type.
    143    // It's always possible to cast them to int64_t for lossless printing of the
    144    // value.
    145    auto [out, size] = fmt::format_to_n(
    146        aDiagnostic, 1023,
    147        FMT_STRING("Cannot cast {:x} from {} to {}: out of range"),
    148        static_cast<int64_t>(aIn), TypeToString<In>(), TypeToString<Out>());
    149    *out = 0;
    150  } else {
    151    auto [out, size] = fmt::format_to_n(
    152        aDiagnostic, 1023,
    153        FMT_STRING("Cannot cast {} from {} to {}: out of range"), aIn,
    154        TypeToString<In>(), TypeToString<Out>());
    155    *out = 0;
    156  }
    157 }
    158 #endif
    159 
    160 template <typename In, typename Out>
    161 bool IsInBounds(In aIn) {
    162  constexpr bool inSigned = std::is_signed_v<In>;
    163  constexpr bool outSigned = std::is_signed_v<Out>;
    164  constexpr bool bothSigned = inSigned && outSigned;
    165  constexpr bool bothUnsigned = !inSigned && !outSigned;
    166  constexpr bool inFloat = std::is_floating_point_v<In>;
    167  constexpr bool outFloat = std::is_floating_point_v<Out>;
    168  constexpr bool bothFloat = inFloat && outFloat;
    169  constexpr bool noneFloat = !inFloat && !outFloat;
    170  constexpr Out outMax = std::numeric_limits<Out>::max();
    171  constexpr Out outMin = std::numeric_limits<Out>::lowest();
    172 
    173  // This selects the widest of two types, and is used to cast throughout.
    174  using select_widest = std::conditional_t<(sizeof(In) > sizeof(Out)), In, Out>;
    175 
    176  if constexpr (bothFloat) {
    177    return select_widest(outMin) <= aIn && aIn <= select_widest(outMax);
    178  }
    179  // Normal casting applies, the floating point number is floored.
    180  else if constexpr (inFloat && !outFloat) {
    181    static_assert(sizeof(aIn) <= sizeof(int64_t));
    182    // Check if the input floating point is larger than the output bounds. This
    183    // catches situations where the input is a float larger than the max of the
    184    // output type.
    185    if (aIn < static_cast<double>(outMin) ||
    186        aIn > static_cast<double>(outMax)) {
    187      return false;
    188    }
    189    // At this point we know that the input can be converted to an integer.
    190    // Check if it's larger than the bounds of the target integer.
    191    if constexpr (outSigned) {
    192      int64_t asInteger = static_cast<int64_t>(aIn);
    193      return outMin <= asInteger && asInteger <= outMax;
    194    } else {
    195      uint64_t asInteger = static_cast<uint64_t>(aIn);
    196      return asInteger <= outMax;
    197    }
    198  }
    199 
    200  // Checks if the integer is representable exactly as a floating point value of
    201  // a specific width.
    202  else if constexpr (!inFloat && outFloat) {
    203    if constexpr (inSigned) {
    204      return -safe_integer<Out>() <= aIn && aIn <= safe_integer<Out>();
    205    } else {
    206      return aIn < safe_integer_unsigned<Out>();
    207    }
    208  }
    209 
    210  else if constexpr (noneFloat) {
    211    if constexpr (bothUnsigned) {
    212      return aIn <= select_widest(outMax);
    213    } else if constexpr (bothSigned) {
    214      return select_widest(outMin) <= aIn && aIn <= select_widest(outMax);
    215    } else if constexpr (inSigned && !outSigned) {
    216      return aIn >= 0 && std::make_unsigned_t<In>(aIn) <= outMax;
    217    } else if constexpr (!inSigned && outSigned) {
    218      return aIn <= select_widest(outMax);
    219    }
    220  }
    221 }
    222 
    223 }  // namespace detail
    224 
    225 /**
    226 * Cast a value of type |From| to a value of type |To|, asserting that the cast
    227 * will be a safe cast per C++ (that is, that |to| is in the range of values
    228 * permitted for the type |From|).
    229 * In particular, this will fail if a integer cannot be represented exactly as a
    230 * floating point value, because it's too large.
    231 */
    232 template <typename To, typename From>
    233 inline To AssertedCast(const From aFrom) {
    234  static_assert(std::is_arithmetic_v<To> && std::is_arithmetic_v<From>);
    235 #ifdef DEBUG
    236  if (!detail::IsInBounds<From, To>(aFrom)) {
    237    char buf[1024];
    238    detail::DiagnosticMessage<From, To>(aFrom, buf);
    239    fprintf(stderr, "AssertedCast error: %s\n", buf);
    240    MOZ_CRASH();
    241  }
    242 #endif
    243  return static_cast<To>(aFrom);
    244 }
    245 
    246 /**
    247 * Cast a value of numeric type |From| to a value of numeric type |To|, release
    248 * asserting that the cast will be a safe cast per C++ (that is, that |to| is in
    249 * the range of values permitted for the type |From|).
    250 * In particular, this will fail if a integer cannot be represented exactly as a
    251 * floating point value, because it's too large.
    252 */
    253 template <typename To, typename From>
    254 inline To ReleaseAssertedCast(const From aFrom) {
    255  static_assert(std::is_arithmetic_v<To> && std::is_arithmetic_v<From>);
    256  MOZ_RELEASE_ASSERT((detail::IsInBounds<From, To>(aFrom)));
    257  return static_cast<To>(aFrom);
    258 }
    259 
    260 /**
    261 * Cast from type From to type To, clamping to minimum and maximum value of the
    262 * destination type if needed.
    263 */
    264 template <typename To, typename From>
    265 inline To SaturatingCast(const From aFrom) {
    266  static_assert(std::is_arithmetic_v<To> && std::is_arithmetic_v<From>);
    267  // This implementation works up to 64-bits integers.
    268  static_assert(sizeof(From) <= 8 && sizeof(To) <= 8);
    269  constexpr bool fromFloat = std::is_floating_point_v<From>;
    270  constexpr bool toFloat = std::is_floating_point_v<To>;
    271 
    272  // It's not clear what the caller wants here, it could be round, truncate,
    273  // closest value, etc.
    274  static_assert((fromFloat && !toFloat) || (!fromFloat && !toFloat),
    275                "Handle manually depending on desired behaviour");
    276 
    277  // If the source is floating point and the destination isn't, it can be that
    278  // casting changes the value unexpectedly. Casting to double and clamping to
    279  // the max of the destination type is correct, this also handles infinity.
    280  if constexpr (fromFloat) {
    281    if (aFrom > static_cast<double>(std::numeric_limits<To>::max())) {
    282      return std::numeric_limits<To>::max();
    283    }
    284    if (aFrom < static_cast<double>(std::numeric_limits<To>::lowest())) {
    285      return std::numeric_limits<To>::lowest();
    286    }
    287    return static_cast<To>(aFrom);
    288  }
    289  // Source and destination are of opposite signedness
    290  if constexpr (std::is_signed_v<From> != std::is_signed_v<To>) {
    291    // Input is negative, output is unsigned, return 0
    292    if (std::is_signed_v<From> && aFrom < 0) {
    293      return 0;
    294    }
    295    // At this point the input is positive, cast everything to uint64_t for
    296    // simplicity and compare
    297    uint64_t inflated = AssertedCast<uint64_t>(aFrom);
    298    if (inflated > static_cast<uint64_t>(std::numeric_limits<To>::max())) {
    299      return std::numeric_limits<To>::max();
    300    }
    301    return static_cast<To>(aFrom);
    302  } else {
    303    // Regular case: clamp to destination type range
    304    if (aFrom > std::numeric_limits<To>::max()) {
    305      return std::numeric_limits<To>::max();
    306    }
    307    if (aFrom < std::numeric_limits<To>::lowest()) {
    308      return std::numeric_limits<To>::lowest();
    309    }
    310    return static_cast<To>(aFrom);
    311  }
    312 }
    313 
    314 namespace detail {
    315 
    316 template <typename From>
    317 class LazyAssertedCastT final {
    318  const From mVal;
    319 
    320 public:
    321  explicit LazyAssertedCastT(const From val) : mVal(val) {}
    322 
    323  template <typename To>
    324  operator To() const {
    325    return AssertedCast<To>(mVal);
    326  }
    327 };
    328 
    329 }  // namespace detail
    330 
    331 /**
    332 * Like AssertedCast, but infers |To| for AssertedCast lazily based on usage.
    333 * > uint8_t foo = LazyAssertedCast(1000);  // boom
    334 */
    335 template <typename From>
    336 inline auto LazyAssertedCast(const From val) {
    337  return detail::LazyAssertedCastT<From>(val);
    338 }
    339 
    340 }  // namespace mozilla
    341 
    342 #endif /* mozilla_Casting_h */