mirror of
https://github.com/openjdk/jdk.git
synced 2026-03-28 16:50:10 +00:00
8314258: Add integer_cast for checking conversions don't change the value
Reviewed-by: azafari, stefank, lkorinth
This commit is contained in:
parent
2afd7b8b14
commit
18fdbd2404
157
src/hotspot/share/utilities/integerCast.hpp
Normal file
157
src/hotspot/share/utilities/integerCast.hpp
Normal file
@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SHARE_UTILITIES_INTEGERCAST_HPP
|
||||
#define SHARE_UTILITIES_INTEGERCAST_HPP
|
||||
|
||||
#include "cppstdlib/limits.hpp"
|
||||
#include "cppstdlib/type_traits.hpp"
|
||||
#include "metaprogramming/enableIf.hpp"
|
||||
#include "utilities/debug.hpp"
|
||||
#include "utilities/macros.hpp"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Tests whether all values for the From type are within the range of values
|
||||
// for the To Type. From and To must be integral types. This is used by
|
||||
// integer_cast to test for tautological conversions.
|
||||
template<typename From, typename To,
|
||||
ENABLE_IF(std::is_integral_v<From>),
|
||||
ENABLE_IF(std::is_integral_v<To>)>
|
||||
constexpr bool is_always_integer_convertible() {
|
||||
if constexpr (std::is_signed_v<To> == std::is_signed_v<From>) {
|
||||
// signed => signed or unsigned => unsigned.
|
||||
return sizeof(To) >= sizeof(From);
|
||||
} else if constexpr (std::is_signed_v<From>) {
|
||||
// signed => unsigned is never tautological, because of negative values.
|
||||
return false;
|
||||
} else {
|
||||
// unsigned => signed.
|
||||
return sizeof(To) > sizeof(From);
|
||||
}
|
||||
}
|
||||
|
||||
// Tests whether the value of from is within the range of values for the To
|
||||
// type. To and From must be integral types. This is used by integer_cast
|
||||
// to test whether the conversion should be performed.
|
||||
template<typename To, typename From,
|
||||
ENABLE_IF(std::is_integral_v<To>),
|
||||
ENABLE_IF(std::is_integral_v<From>)>
|
||||
constexpr bool is_integer_convertible(From from) {
|
||||
if constexpr (is_always_integer_convertible<From, To>()) {
|
||||
// This clause simplifies direct calls and the implementation below. It
|
||||
// isn't needed by integer_cast, where a tautological call is discarded.
|
||||
return true;
|
||||
} else if constexpr (std::is_unsigned_v<From>) {
|
||||
// unsigned => signed or unsigned => unsigned.
|
||||
// Convert To::max to corresponding unsigned for compare.
|
||||
using U = std::make_unsigned_t<To>;
|
||||
return from <= static_cast<U>(std::numeric_limits<To>::max());
|
||||
} else if constexpr (std::is_signed_v<To>) {
|
||||
// signed => signed.
|
||||
return ((std::numeric_limits<To>::min() <= from) &&
|
||||
(from <= std::numeric_limits<To>::max()));
|
||||
} else {
|
||||
// signed => unsigned. Convert from to corresponding unsigned for compare.
|
||||
using U = std::make_unsigned_t<From>;
|
||||
return (0 <= from) && (static_cast<U>(from) <= std::numeric_limits<To>::max());
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the from value to the To type, after a debug-only check that the
|
||||
// value of from is within the range of values for the To type. To and From
|
||||
// must be integral types.
|
||||
//
|
||||
// permit_tautology determines the behavior when a conversion will always
|
||||
// succeed because the range of values for the From type is enclosed by the
|
||||
// range of values for the To type (is_always_integer_convertible<From, To>()
|
||||
// is true). If true, the conversion will be performed as requested. If
|
||||
// false, a compile-time error is produced. The default is false for 64bit
|
||||
// platforms, true for 32bit platforms. See integer_cast_permit_tautology as
|
||||
// the preferred way to override the default and always provide a true value.
|
||||
//
|
||||
// Unnecessary integer_casts make code harder to understand. Hence the
|
||||
// compile-time failure for tautological conversions, to alert that a code
|
||||
// change is making a integer_cast unnecessary. This can be suppressed on a
|
||||
// per-call basis, because there are cases where a conversion might only
|
||||
// sometimes be tautological. For example, the types involved may vary by
|
||||
// platform. Another case is if the operation is in a template with dependent
|
||||
// types, with the operation only being tautological for some instantiations.
|
||||
// Suppressing the tautology check is an alternative to possibly complex
|
||||
// metaprogramming to only perform the integer_cast when necessary.
|
||||
//
|
||||
// Despite that, for 32bit platforms the default is to not reject unnecessary
|
||||
// integer_casts. This is because 64bit platforms are the primary target, and
|
||||
// are likely to require conversions in some places. However, some of those
|
||||
// conversions will be tautological on 32bit platforms, such as size_t => uint.
|
||||
template<typename To,
|
||||
bool permit_tautology = LP64_ONLY(false) NOT_LP64(true),
|
||||
typename From,
|
||||
ENABLE_IF(std::is_integral_v<To>),
|
||||
ENABLE_IF(std::is_integral_v<From>)>
|
||||
constexpr To integer_cast(From from) {
|
||||
if constexpr (is_always_integer_convertible<From, To>()) {
|
||||
static_assert(permit_tautology, "tautological integer_cast");
|
||||
} else {
|
||||
#ifdef ASSERT
|
||||
if (!is_integer_convertible<To>(from)) {
|
||||
if constexpr (std::is_signed_v<From>) {
|
||||
fatal("integer_cast failed: %jd", static_cast<intmax_t>(from));
|
||||
} else {
|
||||
fatal("integer_cast failed: %ju", static_cast<uintmax_t>(from));
|
||||
}
|
||||
}
|
||||
#endif // ASSERT
|
||||
}
|
||||
return static_cast<To>(from);
|
||||
}
|
||||
|
||||
// Equivalent to "integer_cast<To, true>(from)", disabling the compile-time
|
||||
// check for tautological casts. Using this function is prefered to direct
|
||||
// use of the permit_tautology template parameter for integer_cast, unless the
|
||||
// choice is computed.
|
||||
template<typename To, typename From,
|
||||
ENABLE_IF(std::is_integral_v<To>),
|
||||
ENABLE_IF(std::is_integral_v<From>)>
|
||||
constexpr To integer_cast_permit_tautology(From from) {
|
||||
return integer_cast<To, true>(from);
|
||||
}
|
||||
|
||||
// Convert an enumerator to an integral value via static_cast, after a
|
||||
// debug-only check that the value is within the range for the destination
|
||||
// type. This is mostly for compatibility with old code. Class scoped enums
|
||||
// were used to work around ancient compilers that didn't implement class
|
||||
// scoped static integral constants properly, and HotSpot code still has many
|
||||
// examples of this. For others it might be sufficient to provide an explicit
|
||||
// underlying type and either permit implicit conversions or use
|
||||
// PrimitiveConversion::cast.
|
||||
template<typename To, typename From,
|
||||
ENABLE_IF(std::is_integral_v<To>),
|
||||
ENABLE_IF(std::is_enum_v<From>)>
|
||||
constexpr To integer_cast(From from) {
|
||||
using U = std::underlying_type_t<From>;
|
||||
return integer_cast<To, true /* permit_tautology */>(static_cast<U>(from));
|
||||
}
|
||||
|
||||
#endif // SHARE_UTILITIES_INTEGERCAST_HPP
|
||||
287
test/hotspot/gtest/utilities/test_integerCast.cpp
Normal file
287
test/hotspot/gtest/utilities/test_integerCast.cpp
Normal file
@ -0,0 +1,287 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include "cppstdlib/limits.hpp"
|
||||
#include "cppstdlib/type_traits.hpp"
|
||||
#include "utilities/integerCast.hpp"
|
||||
#include "utilities/globalDefinitions.hpp"
|
||||
|
||||
#include "unittest.hpp"
|
||||
|
||||
// Enable gcc warnings to verify we don't get any of these.
|
||||
// Eventually we plan to have these globally enabled, but not there yet.
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic warning "-Wconversion"
|
||||
#pragma GCC diagnostic warning "-Wsign-conversion"
|
||||
#endif
|
||||
|
||||
// Tautology tests for signed -> signed types.
|
||||
static_assert(is_always_integer_convertible<int32_t, int32_t>());
|
||||
static_assert(!is_always_integer_convertible<int64_t, int32_t>());
|
||||
static_assert(is_always_integer_convertible<int32_t, int64_t>());
|
||||
static_assert(is_always_integer_convertible<int64_t, int64_t>());
|
||||
|
||||
// Tautology tests for unsigned -> unsigned types.
|
||||
static_assert(is_always_integer_convertible<uint32_t, uint32_t>());
|
||||
static_assert(!is_always_integer_convertible<uint64_t, uint32_t>());
|
||||
static_assert(is_always_integer_convertible<uint32_t, uint64_t>());
|
||||
static_assert(is_always_integer_convertible<uint64_t, uint64_t>());
|
||||
|
||||
// Tautology tests for signed -> unsigned types.
|
||||
static_assert(!is_always_integer_convertible<int32_t, uint32_t>());
|
||||
static_assert(!is_always_integer_convertible<int64_t, uint32_t>());
|
||||
static_assert(!is_always_integer_convertible<int32_t, uint64_t>());
|
||||
static_assert(!is_always_integer_convertible<int64_t, uint64_t>());
|
||||
|
||||
// Tautology tests for unsigned -> signed types.
|
||||
static_assert(!is_always_integer_convertible<uint32_t, int32_t>());
|
||||
static_assert(!is_always_integer_convertible<uint64_t, int32_t>());
|
||||
static_assert(is_always_integer_convertible<uint32_t, int64_t>());
|
||||
static_assert(!is_always_integer_convertible<uint64_t, int64_t>());
|
||||
|
||||
template<typename T>
|
||||
struct TestIntegerCastValues {
|
||||
static TestIntegerCastValues values;
|
||||
|
||||
T minus_one = static_cast<T>(-1);
|
||||
T zero = static_cast<T>(0);
|
||||
T one = static_cast<T>(1);
|
||||
T min = std::numeric_limits<T>::min();
|
||||
T max = std::numeric_limits<T>::max();
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
TestIntegerCastValues<T> TestIntegerCastValues<T>::values{};
|
||||
|
||||
template<typename To, typename From>
|
||||
struct TestIntegerCastPairedValues {
|
||||
static TestIntegerCastPairedValues values;
|
||||
|
||||
From min = static_cast<From>(std::numeric_limits<To>::min());
|
||||
From max = static_cast<From>(std::numeric_limits<To>::max());
|
||||
};
|
||||
|
||||
template<typename To, typename From>
|
||||
TestIntegerCastPairedValues<To, From>
|
||||
TestIntegerCastPairedValues<To, From>::values{};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Integer casts between integral types of different sizes.
|
||||
// Test narrowing to verify checking.
|
||||
// Test widening to verify no compiler warnings for tautological comparisons.
|
||||
|
||||
template<typename To, typename From>
|
||||
struct TestIntegerCastIntegerValues {
|
||||
static TestIntegerCastIntegerValues values;
|
||||
|
||||
TestIntegerCastValues<To> to;
|
||||
TestIntegerCastValues<From> from;
|
||||
TestIntegerCastPairedValues<To, From> to_as_from;
|
||||
};
|
||||
|
||||
template<typename To, typename From>
|
||||
TestIntegerCastIntegerValues<To, From>
|
||||
TestIntegerCastIntegerValues<To, From>::values{};
|
||||
|
||||
template<typename To, typename From>
|
||||
static void good_integer_conversion(From from) {
|
||||
ASSERT_TRUE(is_integer_convertible<To>(from));
|
||||
EXPECT_EQ(static_cast<To>(from), integer_cast<To>(from));
|
||||
}
|
||||
|
||||
template<typename To, typename From>
|
||||
static void bad_integer_conversion(From from) {
|
||||
EXPECT_FALSE(is_integer_convertible<To>(from));
|
||||
}
|
||||
|
||||
// signed -> signed is tautological unless From is wider than To.
|
||||
|
||||
TEST(TestIntegerCast, wide_signed_to_narrow_signed_integers) {
|
||||
using To = int32_t;
|
||||
using From = int64_t;
|
||||
using Values = TestIntegerCastIntegerValues<To, From>;
|
||||
const Values& values = Values::values;
|
||||
|
||||
good_integer_conversion<To>(values.from.minus_one);
|
||||
good_integer_conversion<To>(values.from.zero);
|
||||
good_integer_conversion<To>(values.from.one);
|
||||
bad_integer_conversion<To>(values.from.min);
|
||||
bad_integer_conversion<To>(values.from.max);
|
||||
good_integer_conversion<To>(values.to_as_from.min);
|
||||
good_integer_conversion<To>(values.to_as_from.max);
|
||||
bad_integer_conversion<To>(values.to_as_from.min - 1);
|
||||
bad_integer_conversion<To>(values.to_as_from.max + 1);
|
||||
}
|
||||
|
||||
// unsigned -> unsigned is tautological unless From is wider than To.
|
||||
|
||||
TEST(TestIntegerCast, wide_unsigned_to_narrow_unsigned_integers) {
|
||||
using To = uint32_t;
|
||||
using From = uint64_t;
|
||||
using Values = TestIntegerCastIntegerValues<To, From>;
|
||||
const Values& values = Values::values;
|
||||
|
||||
bad_integer_conversion<To>(values.from.minus_one);
|
||||
good_integer_conversion<To>(values.from.zero);
|
||||
good_integer_conversion<To>(values.from.one);
|
||||
good_integer_conversion<To>(values.from.min);
|
||||
bad_integer_conversion<To>(values.from.max);
|
||||
good_integer_conversion<To>(values.to_as_from.min);
|
||||
good_integer_conversion<To>(values.to_as_from.min);
|
||||
bad_integer_conversion<To>(values.to_as_from.min - 1);
|
||||
bad_integer_conversion<To>(values.to_as_from.max + 1);
|
||||
}
|
||||
|
||||
TEST(TestIntegerCast, unsigned_to_signed_same_size_integers) {
|
||||
using To = int32_t;
|
||||
using From = uint32_t;
|
||||
using Values = TestIntegerCastIntegerValues<To, From>;
|
||||
const Values& values = Values::values;
|
||||
|
||||
good_integer_conversion<To>(values.from.zero);
|
||||
good_integer_conversion<To>(values.from.one);
|
||||
good_integer_conversion<To>(values.from.min);
|
||||
bad_integer_conversion<To>(values.from.max);
|
||||
bad_integer_conversion<To>(values.to_as_from.min);
|
||||
good_integer_conversion<To>(values.to_as_from.max);
|
||||
bad_integer_conversion<To>(values.to_as_from.max + 1);
|
||||
}
|
||||
|
||||
// Narrow unsigned to wide signed is tautological.
|
||||
|
||||
TEST(TestIntegerCast, wide_unsigned_to_narrow_signed_integers) {
|
||||
using To = int32_t;
|
||||
using From = uint64_t;
|
||||
using Values = TestIntegerCastIntegerValues<To, From>;
|
||||
const Values& values = Values::values;
|
||||
|
||||
bad_integer_conversion<To>(values.from.minus_one);
|
||||
good_integer_conversion<To>(values.from.zero);
|
||||
good_integer_conversion<To>(values.from.one);
|
||||
good_integer_conversion<To>(values.from.min);
|
||||
bad_integer_conversion<To>(values.from.max);
|
||||
bad_integer_conversion<To>(values.to_as_from.min);
|
||||
good_integer_conversion<To>(values.to_as_from.max);
|
||||
bad_integer_conversion<To>(values.to_as_from.min - 1);
|
||||
bad_integer_conversion<To>(values.to_as_from.max + 1);
|
||||
}
|
||||
|
||||
TEST(TestIntegerCast, signed_to_unsigned_same_size_integers) {
|
||||
using To = uint32_t;
|
||||
using From = int32_t;
|
||||
using Values = TestIntegerCastIntegerValues<To, From>;
|
||||
const Values& values = Values::values;
|
||||
|
||||
bad_integer_conversion<To>(values.from.minus_one);
|
||||
good_integer_conversion<To>(values.from.zero);
|
||||
good_integer_conversion<To>(values.from.one);
|
||||
bad_integer_conversion<To>(values.from.min);
|
||||
good_integer_conversion<To>(values.from.max);
|
||||
good_integer_conversion<To>(values.to_as_from.min);
|
||||
bad_integer_conversion<To>(values.to_as_from.max);
|
||||
}
|
||||
|
||||
TEST(TestIntegerCast, narrow_signed_to_wide_unsigned_integers) {
|
||||
using To = uint64_t;
|
||||
using From = int32_t;
|
||||
using Values = TestIntegerCastIntegerValues<To, From>;
|
||||
const Values& values = Values::values;
|
||||
|
||||
bad_integer_conversion<To>(values.from.minus_one);
|
||||
good_integer_conversion<To>(values.from.zero);
|
||||
good_integer_conversion<To>(values.from.one);
|
||||
bad_integer_conversion<To>(values.from.min);
|
||||
good_integer_conversion<To>(values.from.max);
|
||||
good_integer_conversion<To>(values.to_as_from.min);
|
||||
bad_integer_conversion<To>(values.to_as_from.max);
|
||||
}
|
||||
|
||||
TEST(TestIntegerCast, wide_signed_to_narrow_unsigned_integers) {
|
||||
using To = uint32_t;
|
||||
using From = int64_t;
|
||||
using Values = TestIntegerCastIntegerValues<To, From>;
|
||||
const Values& values = Values::values;
|
||||
|
||||
bad_integer_conversion<To>(values.from.minus_one);
|
||||
good_integer_conversion<To>(values.from.zero);
|
||||
good_integer_conversion<To>(values.from.one);
|
||||
bad_integer_conversion<To>(values.from.min);
|
||||
bad_integer_conversion<To>(values.from.max);
|
||||
good_integer_conversion<To>(values.to_as_from.min);
|
||||
good_integer_conversion<To>(values.to_as_from.max);
|
||||
}
|
||||
|
||||
TEST(TestIntegerCast, permit_tautology) {
|
||||
using From = uint32_t;
|
||||
using To = int64_t;
|
||||
using Values = TestIntegerCastIntegerValues<To, From>;
|
||||
const Values& values = Values::values;
|
||||
|
||||
static_assert(is_always_integer_convertible<From, To>());
|
||||
EXPECT_EQ(static_cast<To>(values.from.min),
|
||||
(integer_cast<To, true>(values.from.min)));
|
||||
EXPECT_EQ(static_cast<To>(values.from.min),
|
||||
(integer_cast_permit_tautology<To>(values.from.min)));
|
||||
EXPECT_EQ(static_cast<To>(values.from.max),
|
||||
(integer_cast<To, true>(values.from.max)));
|
||||
EXPECT_EQ(static_cast<To>(values.from.max),
|
||||
integer_cast_permit_tautology<To>(values.from.max));
|
||||
}
|
||||
|
||||
TEST(TestIntegerCast, check_constexpr) {
|
||||
using From = int64_t;
|
||||
using To = int32_t;
|
||||
constexpr From value = std::numeric_limits<To>::max();
|
||||
constexpr To converted = integer_cast<To>(value);
|
||||
EXPECT_EQ(static_cast<To>(value), converted);
|
||||
}
|
||||
|
||||
#ifdef ASSERT
|
||||
|
||||
TEST_VM_ASSERT(TestIntegerCast, cast_failure_signed_range) {
|
||||
using From = int64_t;
|
||||
using To = int32_t;
|
||||
using Values = TestIntegerCastIntegerValues<To, From>;
|
||||
const Values& values = Values::values;
|
||||
|
||||
From value = values.from.max;
|
||||
To expected = static_cast<To>(value); // Narrowing conversion.
|
||||
EXPECT_FALSE(is_integer_convertible<To>(value));
|
||||
// Should assert. If it doesn't, then shuld be equal, so fail.
|
||||
EXPECT_NE(static_cast<To>(value), integer_cast<To>(value));
|
||||
}
|
||||
|
||||
TEST_VM_ASSERT(TestIntegerCast, cast_failure_unsigned_range) {
|
||||
using From = uint64_t;
|
||||
using To = uint32_t;
|
||||
using Values = TestIntegerCastIntegerValues<To, From>;
|
||||
const Values& values = Values::values;
|
||||
|
||||
From value = values.from.max;
|
||||
To expected = static_cast<To>(value); // Narrowing conversion.
|
||||
EXPECT_FALSE(is_integer_convertible<To>(value));
|
||||
// Should assert. If it doesn't, then should be equal, so fail.
|
||||
EXPECT_NE(static_cast<To>(value), integer_cast<To>(value));
|
||||
}
|
||||
|
||||
#endif // ASSERT
|
||||
Loading…
x
Reference in New Issue
Block a user