-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
SafeInt.h
149 lines (129 loc) · 5.77 KB
/
SafeInt.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/*
*
* Copyright (c) 2020 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file
* Utilities for safely working with integer types.
*
*/
#pragma once
#include <limits>
#include <stdint.h>
#include <type_traits>
namespace chip {
/**
* A template function that determines whether it's safe to cast the given value
* of type U to the given type T. It does this by verifying that the value is
* in the range of valid values for T.
*/
template <typename T, typename U, std::enable_if_t<std::is_integral<T>::value, int> = 0>
bool CanCastTo(U arg)
{
using namespace std;
// U might be a reference to an integer type, if we're assigning from
// something passed by reference.
typedef typename remove_reference<U>::type V; // V for "value"
static_assert(is_integral<V>::value, "Must be assigning from an integral type");
// We want to check that "arg" can fit inside T but without doing any tests
// that are always true or always false due to the types involved, which
// would trigger compiler warnings themselves. So for example, we can't
// compare arg to max values for T if all U values are representable in T,
// etc, because those trigger warnings on some compilers.
// We also can't directly compare signed to unsigned values in general,
// because that will trigger sign conversion warnings. In fact, it will
// trigger them even on runtime-unreached codepaths, so for example we can't
// directly compare two min() values to each other!
// Oh, and some compilers warn on theoretical signed-to-unsigned compares
// even when those can't be reached, and that's known at compile time.
// Hence all the casts to intmax_t and uintmax_t below.
// A bunch of these tests could sure benefit from "if constexpr", but let's
// hope compilers just manage to optimize them properly anyway.
// We can't blindly compare "arg" to the minimal or maximal value of T one
// of T and V is signed and the other is unsigned: there might not be a
// single integer type that can represent _both_ the value of arg and the
// minimal/maximal value.
if (numeric_limits<T>::is_signed && numeric_limits<V>::is_signed)
{
if (static_cast<intmax_t>(numeric_limits<V>::max()) <= static_cast<intmax_t>(numeric_limits<T>::max()) &&
static_cast<intmax_t>(numeric_limits<V>::min()) >= static_cast<intmax_t>(numeric_limits<T>::min()))
{
// Any checks on arg would be trivially true; don't even do them, to
// avoid warnings.
return true;
}
return static_cast<intmax_t>(numeric_limits<T>::min()) <= static_cast<intmax_t>(arg) &&
static_cast<intmax_t>(arg) <= static_cast<intmax_t>(numeric_limits<T>::max());
}
if (!numeric_limits<T>::is_signed && !numeric_limits<V>::is_signed)
{
if (static_cast<uintmax_t>(numeric_limits<V>::max()) <= static_cast<uintmax_t>(numeric_limits<T>::max()))
{
// Any checks on arg would be trivially true; don't even do them, to
// avoid warnings.
return true;
}
return static_cast<uintmax_t>(arg) <= static_cast<uintmax_t>(numeric_limits<T>::max());
}
if (numeric_limits<T>::is_signed)
{
static_assert(numeric_limits<T>::max() >= 0, "What weird type is this?");
if (static_cast<uintmax_t>(numeric_limits<V>::max()) <= static_cast<uintmax_t>(numeric_limits<T>::max()))
{
return true;
}
return static_cast<uintmax_t>(arg) <= static_cast<uintmax_t>(numeric_limits<T>::max());
}
return 0 <= arg && static_cast<uintmax_t>(arg) <= static_cast<uintmax_t>(numeric_limits<T>::max());
}
template <typename T, typename U, std::enable_if_t<std::is_enum<T>::value, int> = 0>
bool CanCastTo(U arg)
{
return CanCastTo<std::underlying_type_t<T>>(arg);
}
/**
* A function to reverse the effects of a signed-to-unsigned integer cast.
*
* If the argument is small enough to be representable as a positive signed
* integer, returns that integer. Otherwise, returns a negative integer which
* would, if cast to the type of the argument, produce the given value.
*
* So for example, if a uint8_t with value 254 is passed in this function will
* return an int8_t with value -2.
*
* @note This function might become unnecessary if C++20 standardizes
* 2s-complement signed integers and defines casting of out-of-range values to
* signed types.
*/
template <typename T>
typename std::enable_if<std::is_unsigned<T>::value, typename std::make_signed<T>::type>::type CastToSigned(T arg)
{
using namespace std;
typedef typename make_signed<T>::type signed_type;
if (arg <= static_cast<T>(numeric_limits<signed_type>::max()))
{
return static_cast<signed_type>(arg);
}
// We want to return arg - (numeric_limits<T>::max() + 1), but do it without
// hitting overflow. We do this by rewriting it as:
//
// -(numeric_limits<T>::max() - arg) - 1
//
// then noting that both (numeric_limits<T>::max() - arg) and its negation
// are guaranteed to fit in signed_type.
signed_type diff = static_cast<signed_type>(numeric_limits<T>::max() - arg);
return static_cast<signed_type>(-diff - 1);
}
} // namespace chip