Bug 865036 - Add a Casting.h header to hold various casting methods, right now including a SafeCast method that asserts in-rangeness. r=froydnj
authorJeff Walden <jwalden@mit.edu>
Tue, 23 Apr 2013 14:36:51 -0700
changeset 141623 f5adb2a4213572742ee737b50667acd8b87db333
parent 141622 441c2a4b1ddebbf17c9d23d37bc3d6c2229a3532
child 141624 8a9a40bfa8e3ad6c79333ec63a541dce0f503c44
push id350
push userbbajaj@mozilla.com
push dateMon, 29 Jul 2013 23:00:49 +0000
treeherdermozilla-release@064965b37dbd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs865036
milestone23.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 865036 - Add a Casting.h header to hold various casting methods, right now including a SafeCast method that asserts in-rangeness. r=froydnj
mfbt/Casting.h
mfbt/exported_headers.mk
mfbt/tests/Makefile.in
mfbt/tests/TestCasting.cpp
new file mode 100644
--- /dev/null
+++ b/mfbt/Casting.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Cast operations to supplement the built-in casting operations. */
+
+#ifndef mozilla_Casting_h_
+#define mozilla_Casting_h_
+
+#include "mozilla/Assertions.h"
+#include "mozilla/TypeTraits.h"
+
+#include <limits.h>
+
+namespace mozilla {
+
+namespace detail {
+
+enum ToSignedness { ToIsSigned, ToIsUnsigned };
+enum FromSignedness { FromIsSigned, FromIsUnsigned };
+
+template<typename From,
+         typename To,
+         FromSignedness = IsSigned<From>::value ? FromIsSigned : FromIsUnsigned,
+         ToSignedness = IsSigned<To>::value ? ToIsSigned : ToIsUnsigned>
+struct BoundsCheckImpl;
+
+// Implicit conversions on operands to binary operations make this all a bit
+// hard to verify.  Attempt to ease the pain below by *only* comparing values
+// that are obviously the same type (and will undergo no further conversions),
+// even when it's not strictly necessary, for explicitness.
+
+template<typename From, typename To>
+struct BoundsCheckImpl<From, To, FromIsUnsigned, ToIsUnsigned>
+{
+  public:
+    static bool check(const From from) {
+      typedef typename Conditional<sizeof(From) >= sizeof(To), From, To>::Type
+              LargerType;
+      return LargerType(from) <= LargerType(To(-1));
+    }
+};
+
+template<typename From, typename To>
+struct BoundsCheckImpl<From, To, FromIsSigned, ToIsUnsigned>
+{
+  public:
+    static bool check(const From from) {
+      if (from < 0)
+        return false;
+      if (sizeof(To) >= sizeof(From))
+        return To(from) <= To(-1);
+      return from <= From(To(-1));
+    }
+};
+
+template<typename From, typename To>
+struct BoundsCheckImpl<From, To, FromIsUnsigned, ToIsSigned>
+{
+  public:
+    static bool check(const From from) {
+      if (sizeof(From) < sizeof(To))
+        return true;
+      const To MaxValue = To((1ULL << (CHAR_BIT * sizeof(To) - 1)) - 1);
+      return from <= From(MaxValue);
+    }
+};
+
+template<typename From, typename To>
+struct BoundsCheckImpl<From, To, FromIsSigned, ToIsSigned>
+{
+  public:
+    static bool check(const From from) {
+      typedef typename Conditional<sizeof(To) >= sizeof(From), To, From>::Type
+              LargerType;
+      const To MaxValue = To((1ULL << (CHAR_BIT * sizeof(To) - 1)) - 1);
+      const To MinValue = -MaxValue - To(1);
+      return LargerType(MinValue) <= LargerType(from) &&
+             LargerType(from) <= LargerType(MaxValue);
+    }
+};
+
+template<typename From, typename To,
+         bool TypesAreIntegral = IsIntegral<From>::value && IsIntegral<To>::value>
+class BoundsChecker;
+
+template<typename From>
+class BoundsChecker<From, From, true>
+{
+  public:
+    static bool check(const From from) { return true; }
+};
+
+template<typename From, typename To>
+class BoundsChecker<From, To, true>
+{
+  public:
+    static bool check(const From from) {
+      return BoundsCheckImpl<From, To>::check(from);
+    }
+};
+
+template<typename From, typename To>
+inline bool
+IsInBounds(const From from)
+{
+  return BoundsChecker<From, To>::check(from);
+}
+
+} // namespace detail
+
+/**
+ * Cast a value of integral type |From| to a value of integral type |To|,
+ * asserting that the cast will be a safe cast per C++ (that is, that |to| is in
+ * the range of values permitted for the type |From|).
+ */
+template<typename To, typename From>
+inline To
+SafeCast(const From from)
+{
+  MOZ_ASSERT((detail::IsInBounds<From, To>(from)));
+  return static_cast<To>(from);
+}
+
+} // namespace mozilla
+
+#endif  /* mozilla_Casting_h_ */
--- a/mfbt/exported_headers.mk
+++ b/mfbt/exported_headers.mk
@@ -7,16 +7,17 @@
 # mfbt's exported headers itself.
 
 EXPORTS_NAMESPACES += mozilla
 
 EXPORTS_mozilla += \
   Assertions.h \
   Attributes.h \
   BloomFilter.h \
+  Casting.h \
   Char16.h \
   CheckedInt.h \
   Compiler.h \
   Constants.h \
   DebugOnly.h \
   Endian.h \
   EnumSet.h \
   FloatingPoint.h \
--- a/mfbt/tests/Makefile.in
+++ b/mfbt/tests/Makefile.in
@@ -8,16 +8,17 @@ srcdir = @srcdir@
 VPATH = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 STL_FLAGS =
 
 CPP_UNIT_TESTS = \
   TestBloomFilter.cpp \
+  TestCasting.cpp \
   TestCheckedInt.cpp \
   TestEndian.cpp \
   TestEnumSet.cpp \
   TestSHA1.cpp \
   TestTypeTraits.cpp \
   TestWeakPtr.cpp \
   $(NULL)
 
new file mode 100644
--- /dev/null
+++ b/mfbt/tests/TestCasting.cpp
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Casting.h"
+#include "mozilla/StandardInteger.h"
+
+using mozilla::detail::IsInBounds;
+
+static void
+TestSameSize()
+{
+  MOZ_ASSERT((IsInBounds<int16_t, int16_t>(int16_t(0))));
+  MOZ_ASSERT((IsInBounds<int16_t, int16_t>(int16_t(INT16_MIN))));
+  MOZ_ASSERT((IsInBounds<int16_t, int16_t>(int16_t(INT16_MAX))));
+  MOZ_ASSERT((IsInBounds<uint16_t, uint16_t>(uint16_t(UINT16_MAX))));
+  MOZ_ASSERT((IsInBounds<uint16_t, int16_t>(uint16_t(0))));
+  MOZ_ASSERT((!IsInBounds<uint16_t, int16_t>(uint16_t(-1))));
+  MOZ_ASSERT((!IsInBounds<int16_t, uint16_t>(int16_t(-1))));
+  MOZ_ASSERT((IsInBounds<int16_t, uint16_t>(int16_t(INT16_MAX))));
+  MOZ_ASSERT((!IsInBounds<int16_t, uint16_t>(int16_t(INT16_MIN))));
+  MOZ_ASSERT((IsInBounds<int32_t, uint32_t>(int32_t(INT32_MAX))));
+  MOZ_ASSERT((!IsInBounds<int32_t, uint32_t>(int32_t(INT32_MIN))));
+}
+
+static void
+TestToBiggerSize()
+{
+  MOZ_ASSERT((IsInBounds<int16_t, int32_t>(int16_t(0))));
+  MOZ_ASSERT((IsInBounds<int16_t, int32_t>(int16_t(INT16_MIN))));
+  MOZ_ASSERT((IsInBounds<int16_t, int32_t>(int16_t(INT16_MAX))));
+  MOZ_ASSERT((IsInBounds<uint16_t, uint32_t>(uint16_t(UINT16_MAX))));
+  MOZ_ASSERT((IsInBounds<uint16_t, int32_t>(uint16_t(0))));
+  MOZ_ASSERT((IsInBounds<uint16_t, int32_t>(uint16_t(-1))));
+  MOZ_ASSERT((!IsInBounds<int16_t, uint32_t>(int16_t(-1))));
+  MOZ_ASSERT((IsInBounds<int16_t, uint32_t>(int16_t(INT16_MAX))));
+  MOZ_ASSERT((!IsInBounds<int16_t, uint32_t>(int16_t(INT16_MIN))));
+  MOZ_ASSERT((IsInBounds<int32_t, uint64_t>(int32_t(INT32_MAX))));
+  MOZ_ASSERT((!IsInBounds<int32_t, uint64_t>(int32_t(INT32_MIN))));
+}
+
+static void
+TestToSmallerSize()
+{
+  MOZ_ASSERT((IsInBounds<int16_t, int8_t>(int16_t(0))));
+  MOZ_ASSERT((!IsInBounds<int16_t, int8_t>(int16_t(INT16_MIN))));
+  MOZ_ASSERT((!IsInBounds<int16_t, int8_t>(int16_t(INT16_MAX))));
+  MOZ_ASSERT((!IsInBounds<uint16_t, uint8_t>(uint16_t(UINT16_MAX))));
+  MOZ_ASSERT((IsInBounds<uint16_t, int8_t>(uint16_t(0))));
+  MOZ_ASSERT((!IsInBounds<uint16_t, int8_t>(uint16_t(-1))));
+  MOZ_ASSERT((!IsInBounds<int16_t, uint8_t>(int16_t(-1))));
+  MOZ_ASSERT((!IsInBounds<int16_t, uint8_t>(int16_t(INT16_MAX))));
+  MOZ_ASSERT((!IsInBounds<int16_t, uint8_t>(int16_t(INT16_MIN))));
+  MOZ_ASSERT((!IsInBounds<int32_t, uint16_t>(int32_t(INT32_MAX))));
+  MOZ_ASSERT((!IsInBounds<int32_t, uint16_t>(int32_t(INT32_MIN))));
+
+  // Boundary cases
+  MOZ_ASSERT((!IsInBounds<int64_t, int32_t>(int64_t(INT32_MIN) - 1)));
+  MOZ_ASSERT((IsInBounds<int64_t, int32_t>(int64_t(INT32_MIN))));
+  MOZ_ASSERT((IsInBounds<int64_t, int32_t>(int64_t(INT32_MIN) + 1)));
+  MOZ_ASSERT((IsInBounds<int64_t, int32_t>(int64_t(INT32_MAX) - 1)));
+  MOZ_ASSERT((IsInBounds<int64_t, int32_t>(int64_t(INT32_MAX))));
+  MOZ_ASSERT((!IsInBounds<int64_t, int32_t>(int64_t(INT32_MAX) + 1)));
+
+  MOZ_ASSERT((!IsInBounds<int64_t, uint32_t>(int64_t(-1))));
+  MOZ_ASSERT((IsInBounds<int64_t, uint32_t>(int64_t(0))));
+  MOZ_ASSERT((IsInBounds<int64_t, uint32_t>(int64_t(1))));
+  MOZ_ASSERT((IsInBounds<int64_t, uint32_t>(int64_t(UINT32_MAX) - 1)));
+  MOZ_ASSERT((IsInBounds<int64_t, uint32_t>(int64_t(UINT32_MAX))));
+  MOZ_ASSERT((!IsInBounds<int64_t, uint32_t>(int64_t(UINT32_MAX) + 1)));
+}
+
+int
+main()
+{
+  TestSameSize();
+  TestToBiggerSize();
+  TestToSmallerSize();
+}