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