math: Avoid signed integer overflow when intrinsics are available

The `math(EXPR)` command documents that results must fit in a 64-bit
signed integer.  When built with compilers that offer the relevant
intrinsics, detect overflow and issue warnings.
This commit is contained in:
Brad King
2026-02-28 07:03:51 -05:00
parent 3c1f9d059e
commit 099c05dd04
16 changed files with 225 additions and 4 deletions
@@ -106,6 +106,7 @@ set(CMake_TEST_IPO_WORKS_C "ON" CACHE BOOL "")
set(CMake_TEST_IPO_WORKS_CXX "ON" CACHE BOOL "")
set(CMake_TEST_IPO_WORKS_Fortran "ON" CACHE BOOL "")
set(CMake_TEST_JQ "/usr/bin/jq" CACHE PATH "")
set(CMake_TEST_MATH_OVERFLOW ON CACHE BOOL "")
set(CMake_TEST_Qt5 "ON" CACHE BOOL "")
set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "")
set(CMake_TEST_TLS_VERIFY_URL_BAD "https://badtls-expired.kitware.com" CACHE STRING "")
@@ -106,6 +106,7 @@ set(CMake_TEST_IPO_WORKS_C "ON" CACHE BOOL "")
set(CMake_TEST_IPO_WORKS_CXX "ON" CACHE BOOL "")
set(CMake_TEST_IPO_WORKS_Fortran "ON" CACHE BOOL "")
set(CMake_TEST_JQ "/usr/bin/jq" CACHE PATH "")
set(CMake_TEST_MATH_OVERFLOW ON CACHE BOOL "")
set(CMake_TEST_Qt5 "ON" CACHE BOOL "")
set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "")
set(CMake_TEST_TLS_VERIFY_URL_BAD "https://badtls-expired.kitware.com" CACHE STRING "")
@@ -12,6 +12,7 @@ set(CMAKE_SKIP_BOOTSTRAP_TEST TRUE CACHE BOOL "")
set(ENV{KWSYS_TEST_PROCESS_1_COUNT} 11)
set(CMake_TEST_GUI "ON" CACHE BOOL "")
set(CMake_TEST_MATH_OVERFLOW ON CACHE BOOL "")
set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared,bmionly,build_database,import_std23" CACHE STRING "")
set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "")
set(CMake_TEST_TLS_VERIFY_URL_BAD "https://badtls-expired.kitware.com" CACHE STRING "")
@@ -1,4 +1,5 @@
set(CMake_TEST_GUI "ON" CACHE BOOL "")
set(CMake_TEST_MATH_OVERFLOW ON CACHE BOOL "")
set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared,bmionly,build_database,import_std23" CACHE STRING "")
set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "")
set(CMake_TEST_TLS_VERIFY_URL_BAD "https://badtls-expired.kitware.com" CACHE STRING "")
@@ -109,6 +109,7 @@ if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "")
set(CMake_TEST_ISPC "ON" CACHE STRING "")
endif()
set(CMake_TEST_LOCALE_CHARSET "ISO-8859-1" CACHE STRING "")
set(CMake_TEST_MATH_OVERFLOW ON CACHE BOOL "")
set(CMake_TEST_Qt5 "ON" CACHE BOOL "")
set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "")
set(CMake_TEST_TLS_VERIFY_URL_BAD "https://badtls-expired.kitware.com" CACHE STRING "")
@@ -5,6 +5,7 @@ if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "")
set(CMake_TEST_ISPC "ON" CACHE STRING "")
endif()
set(CMake_TEST_LOCALE_CHARSET "UTF-8" CACHE STRING "")
set(CMake_TEST_MATH_OVERFLOW ON CACHE BOOL "")
set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared,bmionly,build_database,import_std23" CACHE STRING "")
set(CMake_TEST_Rust "ON" CACHE STRING "")
set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "")
@@ -7,6 +7,7 @@ set(CMake_TEST_FindOpenMP_C "ON" CACHE BOOL "")
set(CMake_TEST_FindOpenMP_CXX "ON" CACHE BOOL "")
set(CMake_TEST_GUI "ON" CACHE BOOL "")
set(CMake_TEST_LOCALE_CHARSET "UTF-8" CACHE STRING "")
set(CMake_TEST_MATH_OVERFLOW ON CACHE BOOL "")
set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "")
set(CMake_TEST_TLS_VERIFY_URL_BAD "https://badtls-expired.kitware.com" CACHE STRING "")
set(CMake_TEST_TLS_VERSION "1.2" CACHE STRING "")
@@ -6,6 +6,7 @@ set(CMake_TEST_GUI "ON" CACHE BOOL "")
if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "")
set(CMake_TEST_ISPC "ON" CACHE STRING "")
endif()
set(CMake_TEST_MATH_OVERFLOW ON CACHE BOOL "")
set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "")
set(CMake_TEST_TLS_VERIFY_URL_BAD "https://badtls-expired.kitware.com" CACHE STRING "")
set(CMake_TEST_TLS_VERSION "1.2" CACHE STRING "")
@@ -10,6 +10,7 @@ if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "")
set(CMake_TEST_ISPC "ON" CACHE STRING "")
endif()
set(CMake_TEST_LOCALE_CHARSET "UTF-8" CACHE STRING "")
set(CMake_TEST_MATH_OVERFLOW ON CACHE BOOL "")
set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "")
set(CMake_TEST_TLS_VERIFY_URL_BAD "https://badtls-expired.kitware.com" CACHE STRING "")
set(CMake_TEST_TLS_VERSION "1.2" CACHE STRING "")
+115 -4
View File
@@ -100,6 +100,11 @@ void cmExprParserHelper::Error(char const* str)
this->ErrorString = ostr.str();
}
void cmExprParserHelper::Warning(std::string str)
{
this->WarningString = cmStrCat(this->WarningString, std::move(str), '\n');
}
void cmExprParserHelper::UnexpectedChar(char c)
{
unsigned long pos = static_cast<unsigned long>(this->InputBufferPos);
@@ -121,27 +126,63 @@ void cmExprParserHelper::SetError(std::string errorString)
std::int64_t cmExprParserHelper::ShL(std::int64_t l, std::int64_t r)
{
return l << r;
if (l < 0) {
this->Warning(
cmStrCat("left shift of negative value in:\n ", l, " << ", r));
}
if (r < 0) {
this->Warning(
cmStrCat("shift exponent is negative in:\n ", l, " << ", r));
r &= 0x3F;
}
if (r >= 64) {
this->Warning(
cmStrCat("shift exponent is too large in:\n ", l, " << ", r));
r &= 0x3F;
}
return static_cast<std::int64_t>(static_cast<std::uint64_t>(l) << r);
}
std::int64_t cmExprParserHelper::ShR(std::int64_t l, std::int64_t r)
{
if (r < 0) {
this->Warning(
cmStrCat("shift exponent is negative in:\n ", l, " >> ", r));
r &= 0x3F;
}
if (r >= 64) {
this->Warning(
cmStrCat("shift exponent is too large in:\n ", l, " >> ", r));
r &= 0x3F;
}
return l >> r;
}
std::int64_t cmExprParserHelper::Add(std::int64_t l, std::int64_t r)
{
return l + r;
std::int64_t sum;
if (this->AddOverflow(l, r, &sum)) {
this->Warning(cmStrCat("signed integer overflow in:\n ", l, " + ", r));
}
return sum;
}
std::int64_t cmExprParserHelper::Sub(std::int64_t l, std::int64_t r)
{
return l - r;
std::int64_t diff;
if (this->SubOverflow(l, r, &diff)) {
this->Warning(cmStrCat("signed integer overflow in:\n ", l, " - ", r));
}
return diff;
}
std::int64_t cmExprParserHelper::Mul(std::int64_t l, std::int64_t r)
{
return l * r;
std::int64_t prod;
if (this->MulOverflow(l, r, &prod)) {
this->Warning(cmStrCat("signed integer overflow in:\n ", l, " * ", r));
}
return prod;
}
std::int64_t cmExprParserHelper::Div(std::int64_t l, std::int64_t r)
@@ -159,3 +200,73 @@ std::int64_t cmExprParserHelper::Mod(std::int64_t l, std::int64_t r)
}
return l % r;
}
// The __has_builtin preprocessor check was added in Clang 2.6 and GCC 10.
// The __builtin_X_overflow intrinsics were added in Clang 3.4 and GCC 5.
#ifndef __has_builtin
# if defined(__GNUC__) && __GNUC__ >= 5
# define __has_builtin(x) 1
# else
# define __has_builtin(x) 0
# endif
#endif
bool cmExprParserHelper::AddOverflow(long l, long r, long* p)
{
#if __has_builtin(__builtin_saddl_overflow)
return __builtin_saddl_overflow(l, r, p);
#else
*p = l + r;
return false;
#endif
}
bool cmExprParserHelper::AddOverflow(long long l, long long r, long long* p)
{
#if __has_builtin(__builtin_saddll_overflow)
return __builtin_saddll_overflow(l, r, p);
#else
*p = l + r;
return false;
#endif
}
bool cmExprParserHelper::SubOverflow(long l, long r, long* p)
{
#if __has_builtin(__builtin_ssubl_overflow)
return __builtin_ssubl_overflow(l, r, p);
#else
*p = l - r;
return false;
#endif
}
bool cmExprParserHelper::SubOverflow(long long l, long long r, long long* p)
{
#if __has_builtin(__builtin_ssubll_overflow)
return __builtin_ssubll_overflow(l, r, p);
#else
*p = l - r;
return false;
#endif
}
bool cmExprParserHelper::MulOverflow(long l, long r, long* p)
{
#if __has_builtin(__builtin_smull_overflow)
return __builtin_smull_overflow(l, r, p);
#else
*p = l * r;
return false;
#endif
}
bool cmExprParserHelper::MulOverflow(long long l, long long r, long long* p)
{
#if __has_builtin(__builtin_smulll_overflow)
return __builtin_smulll_overflow(l, r, p);
#else
*p = l * r;
return false;
#endif
}
+8
View File
@@ -23,6 +23,7 @@ public:
int LexInput(char* buf, int maxlen);
void Error(char const* str);
void Warning(std::string str);
void SetResult(std::int64_t value);
@@ -42,6 +43,13 @@ public:
std::int64_t Div(std::int64_t l, std::int64_t r);
std::int64_t Mod(std::int64_t l, std::int64_t r);
static bool AddOverflow(long l, long r, long* p);
static bool AddOverflow(long long l, long long r, long long* p);
static bool SubOverflow(long l, long r, long* p);
static bool SubOverflow(long long l, long long r, long long* p);
static bool MulOverflow(long l, long r, long* p);
static bool MulOverflow(long long l, long long r, long long* p);
private:
std::string::size_type InputBufferPos;
std::string InputBuffer;
+3
View File
@@ -769,6 +769,9 @@ add_RunCMake_test(include_directories)
add_RunCMake_test(include_guard)
add_RunCMake_test(list)
add_RunCMake_test(load_cache)
if(DEFINED CMake_TEST_MATH_OVERFLOW)
list(APPEND math_ARGS -DCMake_TEST_MATH_OVERFLOW=${CMake_TEST_MATH_OVERFLOW})
endif()
add_RunCMake_test(math)
add_RunCMake_test(message)
add_RunCMake_test(option)
+63
View File
@@ -0,0 +1,63 @@
^CMake Warning \(dev\) at [^
]+/Tests/RunCMake/math/Overflow\.cmake:[0-9]+ \(math\):
left shift of negative value in:
-4 << 1
This warning is for project developers\. Use -Wno-dev to suppress it\.
+
CMake Warning \(dev\) at [^
]+/Tests/RunCMake/math/Overflow\.cmake:[0-9]+ \(math\):
shift exponent is negative in:
4 << -63
This warning is for project developers\. Use -Wno-dev to suppress it\.
+
CMake Warning \(dev\) at [^
]+/Tests/RunCMake/math/Overflow\.cmake:[0-9]+ \(math\):
shift exponent is negative in:
4 >> -63
This warning is for project developers\. Use -Wno-dev to suppress it\.
+
CMake Warning \(dev\) at [^
]+/Tests/RunCMake/math/Overflow\.cmake:[0-9]+ \(math\):
shift exponent is too large in:
4 << 65
This warning is for project developers\. Use -Wno-dev to suppress it\.
+
CMake Warning \(dev\) at [^
]+/Tests/RunCMake/math/Overflow\.cmake:[0-9]+ \(math\):
shift exponent is too large in:
4 >> 65
This warning is for project developers\. Use -Wno-dev to suppress it\.
+
CMake Warning \(dev\) at [^
]+/Tests/RunCMake/math/Overflow\.cmake:[0-9]+ \(math\):
signed integer overflow in:
9223372036854775807 \+ 1
This warning is for project developers\. Use -Wno-dev to suppress it\.
+
CMake Warning \(dev\) at [^
]+/Tests/RunCMake/math/Overflow\.cmake:[0-9]+ \(math\):
signed integer overflow in:
-9223372036854775807 - 2
This warning is for project developers\. Use -Wno-dev to suppress it\.
+
CMake Warning \(dev\) at [^
]+/Tests/RunCMake/math/Overflow\.cmake:[0-9]+ \(math\):
signed integer overflow in:
9223372036854775807 \* 2
This warning is for project developers\. Use -Wno-dev to suppress it\.$
+9
View File
@@ -0,0 +1,9 @@
^-- -4 << 1: -8
-- -4 >> 1: -2
-- 4 << -63: 8
-- 4 >> -63: 2
-- 4 << 65: 8
-- 4 >> 65: 2
-- 0x7FFFFFFFFFFFFFFF \+ 1: -9223372036854775808
-- -0x7FFFFFFFFFFFFFFF - 2: 9223372036854775807
-- 0x7FFFFFFFFFFFFFFF \* 2: -2$
+14
View File
@@ -0,0 +1,14 @@
foreach(expr IN ITEMS
"-4 << 1"
"-4 >> 1"
" 4 << -63"
" 4 >> -63"
" 4 << 65"
" 4 >> 65"
" 0x7FFFFFFFFFFFFFFF + 1"
"-0x7FFFFFFFFFFFFFFF - 2"
" 0x7FFFFFFFFFFFFFFF * 2"
)
math(EXPR result "${expr}")
message(STATUS "${expr}: ${result}")
endforeach()
+4
View File
@@ -8,3 +8,7 @@ run_cmake(MATH-InvalidExpression)
run_cmake(MATH-ToleratedExpression)
run_cmake(MATH-DivideByZero)
run_cmake(MATH-ModByZero)
if(CMake_TEST_MATH_OVERFLOW)
run_cmake_script(Overflow)
endif()