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 5947472 : 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 4065783 : 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 8131535 : return static_cast<intmax_t>(numeric_limits<T>::min()) <= static_cast<intmax_t>(arg) && 78 8131535 : 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 1520796 : 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 123 : return true; 88 : } 89 : 90 1520673 : 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 296671 : 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 296561 : return static_cast<uintmax_t>(arg) <= static_cast<uintmax_t>(numeric_limits<T>::max()); 102 : } 103 : 104 64222 : 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 5420179 : 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 5420179 : if (arg <= static_cast<T>(numeric_limits<signed_type>::max())) 134 : { 135 5415976 : 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 4203 : signed_type diff = static_cast<signed_type>(numeric_limits<T>::max() - arg); 146 4203 : return static_cast<signed_type>(-diff - 1); 147 : } 148 : 149 : } // namespace chip