Commit c1c090d3 authored by scottmg@chromium.org's avatar scottmg@chromium.org

Add numeric_cast for checked integral narrowing casts

In work on bringing up Windows x64, there are many places that need
to be safely narrowed to the types used for interacting with other
APIs (particularly when using containers). Rather than scatter these
CHECKs all over, numeric_cast<> CHECKs that the runtime value can be
safely converted to the target type.

BUG=8606, 167187, 166496

Review URL: https://codereview.chromium.org/11886037

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@177264 0039d316-1c4b-4281-b951-d872f2087c98
parent 6fe81d16
...@@ -543,6 +543,8 @@ ...@@ -543,6 +543,8 @@
'process_util_unittest_mac.mm', 'process_util_unittest_mac.mm',
'profiler/tracked_time_unittest.cc', 'profiler/tracked_time_unittest.cc',
'rand_util_unittest.cc', 'rand_util_unittest.cc',
'safe_numerics_unittest.cc',
'safe_numerics_unittest.nc',
'scoped_native_library_unittest.cc', 'scoped_native_library_unittest.cc',
'scoped_observer.h', 'scoped_observer.h',
'security_unittest.cc', 'security_unittest.cc',
......
...@@ -358,6 +358,7 @@ ...@@ -358,6 +358,7 @@
'rand_util_win.cc', 'rand_util_win.cc',
'run_loop.cc', 'run_loop.cc',
'run_loop.h', 'run_loop.h',
'safe_numerics.h',
'safe_strerror_posix.cc', 'safe_strerror_posix.cc',
'safe_strerror_posix.h', 'safe_strerror_posix.h',
'scoped_native_library.cc', 'scoped_native_library.cc',
......
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_SAFE_NUMERICS_H_
#define BASE_SAFE_NUMERICS_H_
#include <limits>
#include "base/logging.h"
namespace base {
namespace internal {
template <bool SameSize, bool DestLarger,
bool DestIsSigned, bool SourceIsSigned>
struct IsValidNumericCastImpl;
#define BASE_NUMERIC_CAST_CASE_SPECIALIZATION(A, B, C, D, Code) \
template <> struct IsValidNumericCastImpl<A, B, C, D> { \
template <class Source, class DestBounds> static inline bool Test( \
Source source, DestBounds min, DestBounds max) { \
return Code; \
} \
}
#define BASE_NUMERIC_CAST_CASE_SAME_SIZE(DestSigned, SourceSigned, Code) \
BASE_NUMERIC_CAST_CASE_SPECIALIZATION( \
true, true, DestSigned, SourceSigned, Code); \
BASE_NUMERIC_CAST_CASE_SPECIALIZATION( \
true, false, DestSigned, SourceSigned, Code)
#define BASE_NUMERIC_CAST_CASE_SOURCE_LARGER(DestSigned, SourceSigned, Code) \
BASE_NUMERIC_CAST_CASE_SPECIALIZATION( \
false, false, DestSigned, SourceSigned, Code); \
#define BASE_NUMERIC_CAST_CASE_DEST_LARGER(DestSigned, SourceSigned, Code) \
BASE_NUMERIC_CAST_CASE_SPECIALIZATION( \
false, true, DestSigned, SourceSigned, Code); \
// The three top level cases are:
// - Same size
// - Source larger
// - Dest larger
// And for each of those three cases, we handle the 4 different possibilities
// of signed and unsigned. This gives 12 cases to handle, which we enumerate
// below.
//
// The last argument in each of the macros is the actual comparison code. It
// has three arguments available, source (the value), and min/max which are
// the ranges of the destination.
// These are the cases where both types have the same size.
// Both signed.
BASE_NUMERIC_CAST_CASE_SAME_SIZE(true, true, true);
// Both unsigned.
BASE_NUMERIC_CAST_CASE_SAME_SIZE(false, false, true);
// Dest unsigned, Source signed.
BASE_NUMERIC_CAST_CASE_SAME_SIZE(false, true, source >= 0);
// Dest signed, Source unsigned.
// This cast is OK because Dest's max must be less than Source's.
BASE_NUMERIC_CAST_CASE_SAME_SIZE(true, false,
source <= static_cast<Source>(max));
// These are the cases where Source is larger.
// Both unsigned.
BASE_NUMERIC_CAST_CASE_SOURCE_LARGER(false, false, source <= max);
// Both signed.
BASE_NUMERIC_CAST_CASE_SOURCE_LARGER(true, true,
source >= min && source <= max);
// Dest is unsigned, Source is signed.
BASE_NUMERIC_CAST_CASE_SOURCE_LARGER(false, true,
source >= 0 && source <= max);
// Dest is signed, Source is unsigned.
// This cast is OK because Dest's max must be less than Source's.
BASE_NUMERIC_CAST_CASE_SOURCE_LARGER(true, false,
source <= static_cast<Source>(max));
// These are the cases where Dest is larger.
// Both unsigned.
BASE_NUMERIC_CAST_CASE_DEST_LARGER(false, false, true);
// Both signed.
BASE_NUMERIC_CAST_CASE_DEST_LARGER(true, true, true);
// Dest is unsigned, Source is signed.
BASE_NUMERIC_CAST_CASE_DEST_LARGER(false, true, source >= 0);
// Dest is signed, Source is unsigned.
BASE_NUMERIC_CAST_CASE_DEST_LARGER(true, false, true);
#undef BASE_NUMERIC_CAST_CASE_SPECIALIZATION
#undef BASE_NUMERIC_CAST_CASE_SAME_SIZE
#undef BASE_NUMERIC_CAST_CASE_SOURCE_LARGER
#undef BASE_NUMERIC_CAST_CASE_DEST_LARGER
// The main test for whether the conversion will under or overflow.
template <class Dest, class Source>
inline bool IsValidNumericCast(Source source) {
typedef std::numeric_limits<Source> SourceLimits;
typedef std::numeric_limits<Dest> DestLimits;
COMPILE_ASSERT(SourceLimits::is_specialized, argument_must_be_numeric);
COMPILE_ASSERT(SourceLimits::is_integer, argument_must_be_integral);
COMPILE_ASSERT(DestLimits::is_specialized, result_must_be_numeric);
COMPILE_ASSERT(DestLimits::is_integer, result_must_be_integral);
return IsValidNumericCastImpl<
sizeof(Dest) == sizeof(Source),
(sizeof(Dest) > sizeof(Source)),
DestLimits::is_signed,
SourceLimits::is_signed>::Test(
source,
DestLimits::min(),
DestLimits::max());
}
} // namespace internal
// numeric_cast<> is analogous to static_cast<> for numeric types, except that
// it CHECKs that the specified numeric conversion will not overflow or
// underflow. Floating point arguments are not currently allowed (this is
// COMPILE_ASSERTd), though this could be supported if necessary.
template <class Dest, class Source>
inline Dest checked_numeric_cast(Source source) {
CHECK(internal::IsValidNumericCast<Dest>(source));
return static_cast<Dest>(source);
}
} // namespace base
#endif // BASE_SAFE_NUMERICS_H_
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <gtest/gtest.h>
#include <sstream>
#include <vector>
#include "base/safe_numerics.h"
namespace base {
namespace internal {
// This is far (far, far) too slow to run normally, but if you're refactoring
// it might be useful.
// #define RUN_EXHAUSTIVE_TEST
#ifdef RUN_EXHAUSTIVE_TEST
template <class From, class To> void ExhaustiveCheckFromTo() {
fprintf(stderr, ".");
From i = std::numeric_limits<From>::min();
for (;;) {
std::ostringstream str_from, str_to;
str_from << i;
To to = static_cast<To>(i);
str_to << to;
bool strings_equal = str_from.str() == str_to.str();
EXPECT_EQ(IsValidNumericCast<To>(i), strings_equal);
fprintf(stderr, "\r%s vs %s\x1B[K",
str_from.str().c_str(), str_to.str().c_str());
++i;
// If we wrap, then we've tested everything.
if (i == std::numeric_limits<From>::min())
break;
}
}
template <class From> void ExhaustiveCheckFrom() {
ExhaustiveCheckFromTo<From, short>();
ExhaustiveCheckFromTo<From, unsigned short>();
ExhaustiveCheckFromTo<From, int>();
ExhaustiveCheckFromTo<From, unsigned int>();
ExhaustiveCheckFromTo<From, long long>();
ExhaustiveCheckFromTo<From, unsigned long long>();
ExhaustiveCheckFromTo<From, size_t>();
fprintf(stderr, "\n");
}
#endif
TEST(SafeNumerics, NumericCast) {
int small_positive = 1;
int small_negative = -1;
int large_positive = INT_MAX;
int large_negative = INT_MIN;
size_t size_t_small = 1;
size_t size_t_large = UINT_MAX;
// Narrow signed destination.
EXPECT_TRUE(IsValidNumericCast<signed char>(small_positive));
EXPECT_TRUE(IsValidNumericCast<signed char>(small_negative));
EXPECT_FALSE(IsValidNumericCast<signed char>(large_positive));
EXPECT_FALSE(IsValidNumericCast<signed char>(large_negative));
EXPECT_TRUE(IsValidNumericCast<signed short>(small_positive));
EXPECT_TRUE(IsValidNumericCast<signed short>(small_negative));
// Narrow unsigned destination.
EXPECT_TRUE(IsValidNumericCast<unsigned char>(small_positive));
EXPECT_FALSE(IsValidNumericCast<unsigned char>(small_negative));
EXPECT_FALSE(IsValidNumericCast<unsigned char>(large_positive));
EXPECT_FALSE(IsValidNumericCast<unsigned char>(large_negative));
EXPECT_FALSE(IsValidNumericCast<unsigned short>(small_negative));
EXPECT_FALSE(IsValidNumericCast<unsigned short>(large_negative));
// Same width signed destination.
EXPECT_TRUE(IsValidNumericCast<signed int>(small_positive));
EXPECT_TRUE(IsValidNumericCast<signed int>(small_negative));
EXPECT_TRUE(IsValidNumericCast<signed int>(large_positive));
EXPECT_TRUE(IsValidNumericCast<signed int>(large_negative));
// Same width unsigned destination.
EXPECT_TRUE(IsValidNumericCast<unsigned int>(small_positive));
EXPECT_FALSE(IsValidNumericCast<unsigned int>(small_negative));
EXPECT_TRUE(IsValidNumericCast<unsigned int>(large_positive));
EXPECT_FALSE(IsValidNumericCast<unsigned int>(large_negative));
// Wider signed destination.
EXPECT_TRUE(IsValidNumericCast<long long>(small_positive));
EXPECT_TRUE(IsValidNumericCast<long long>(large_negative));
EXPECT_TRUE(IsValidNumericCast<long long>(small_positive));
EXPECT_TRUE(IsValidNumericCast<long long>(large_negative));
// Wider unsigned destination.
EXPECT_TRUE(IsValidNumericCast<unsigned long long>(small_positive));
EXPECT_FALSE(IsValidNumericCast<unsigned long long>(small_negative));
EXPECT_TRUE(IsValidNumericCast<unsigned long long>(large_positive));
EXPECT_FALSE(IsValidNumericCast<unsigned long long>(large_negative));
// Negative to size_t.
EXPECT_FALSE(IsValidNumericCast<size_t>(small_negative));
EXPECT_FALSE(IsValidNumericCast<size_t>(large_negative));
// From unsigned.
// Small.
EXPECT_TRUE(IsValidNumericCast<signed char>(size_t_small));
EXPECT_TRUE(IsValidNumericCast<unsigned char>(size_t_small));
EXPECT_TRUE(IsValidNumericCast<short>(size_t_small));
EXPECT_TRUE(IsValidNumericCast<unsigned short>(size_t_small));
EXPECT_TRUE(IsValidNumericCast<int>(size_t_small));
EXPECT_TRUE(IsValidNumericCast<unsigned int>(size_t_small));
EXPECT_TRUE(IsValidNumericCast<long long>(size_t_small));
EXPECT_TRUE(IsValidNumericCast<unsigned long long>(size_t_small));
// Large.
EXPECT_FALSE(IsValidNumericCast<signed char>(size_t_large));
EXPECT_FALSE(IsValidNumericCast<unsigned char>(size_t_large));
EXPECT_FALSE(IsValidNumericCast<short>(size_t_large));
EXPECT_FALSE(IsValidNumericCast<unsigned short>(size_t_large));
EXPECT_FALSE(IsValidNumericCast<int>(size_t_large));
EXPECT_TRUE(IsValidNumericCast<unsigned int>(size_t_large));
EXPECT_TRUE(IsValidNumericCast<long long>(size_t_large));
EXPECT_TRUE(IsValidNumericCast<unsigned long long>(size_t_large));
// Various edge cases.
EXPECT_TRUE(IsValidNumericCast<int>(static_cast<short>(SHRT_MIN)));
EXPECT_FALSE(
IsValidNumericCast<unsigned short>(static_cast<short>(SHRT_MIN)));
EXPECT_FALSE(IsValidNumericCast<unsigned short>(SHRT_MIN));
// Confirm that numeric_cast<> actually compiles.
std::vector<int> v;
unsigned int checked_size =
base::checked_numeric_cast<unsigned int>(v.size());
EXPECT_EQ(0u, checked_size);
#ifdef RUN_EXHAUSTIVE_TEST
ExhaustiveCheckFrom<short>();
ExhaustiveCheckFrom<unsigned short>();
ExhaustiveCheckFrom<int>();
ExhaustiveCheckFrom<unsigned int>();
ExhaustiveCheckFrom<long long>();
ExhaustiveCheckFrom<unsigned long long>();
ExhaustiveCheckFrom<size_t>();
#endif
}
} // namespace internal
} // namespace base
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/safe_numerics.h"
namespace base {
namespace internal {
void NoFloatingPoint {
IsValidNumericCast<float>(0.0);
IsValidNumericCast<double>(0.0f);
IsValidNumericCast<int>(DBL_MAX);
}
} // namespace internal
} // namespace base
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment