Skip to content

Handle signed overflow in checked_div and checked_mod - #2742

Open
ZainnQureshii wants to merge 1 commit into
meta:mainfrom
ZainnQureshii:fix/checked-div-mod-signed-overflow
Open

ZainnQureshii wants to merge 1 commit into
meta:mainfrom
ZainnQureshii:fix/checked-div-mod-signed-overflow

Conversation

@ZainnQureshii

Copy link
Copy Markdown

Summary

checked_div and checked_mod in folly/lang/CheckedMath.h only reject divisor == 0. For a signed built-in type at least as wide as int (no promotion), min / -1 and min % -1 are undefined behaviour (the quotient does not fit), so checked_div(&r, INT64_MIN, int64_t{-1}) runs the division instead of returning false. UBSan reports division of -9223372036854775808 by -1 cannot be represented in type 'long long' at both call sites.

Both functions now return false with *result = {} for that pair, the same way they handle a zero divisor. The check is under if constexpr for built-in signed integers only, using folly::is_integral_v and folly::is_signed_v so __int128 is covered on standard libraries whose std:: traits leave it out (libstdc++ in strict -std=c++XX mode). The minimum is computed from the sign bit via folly::make_unsigned_t rather than std::numeric_limits, so the guard also works where strict-mode libstdc++ (GCC 10.1 and 10.2) leaves numeric_limits<__int128> unspecialised, which would make min() return 0 and reject 0 / -1. Class types keep their old behaviour. checked_mod tests the promoted operand type, so int8_t(-128) % int8_t(-1) (computed in int) still returns true with 0.

One visible change for narrow types: checked_div on int8_t(-128) / int8_t(-1) or int16_t(-32768) / int16_t(-1) used to return true with the wrapped value (-128, -32768), because the division happens in int and only the store narrows. It now returns false, since the true quotient does not fit in T.

The test checkedModZeroDivisor was calling checked_div; it now calls checked_mod. Because the header now uses folly/Traits.h, checked_math lists //folly:traits in folly/lang/BUCK and folly_traits in folly/lang/CMakeLists.txt, and the test target lists it in folly/lang/test/BUCK.

Test plan

folly/lang/test/CheckedMathTest.cpp built with clang++ -std=c++20 -fsanitize=undefined against GoogleTest:

  • Original header, new tests kept: checkedDivSignedOverflow, checkedModSignedOverflow, checkedDivNarrowSignedOverflow and checkedDivModInt128SignedOverflow fail, with UBSan overflow reports at CheckedMath.h:185 and :195. Result: 41 tests pass and four fail, with six UBSan overflow reports.
  • Fixed header: all 45 tests pass with no UBSan reports (built with -DFOLLY_NO_CONFIG -DFOLLY_HAVE_INT128_T=1 so the __int128 tests run), including the new checkedDivModInt128ZeroDividend, which checks that 0 / -1 and 0 % -1 still succeed.

The full CMake/CTest build, Buck, native GCC builds and other platforms were not run. The GCC 10.1, 10.2, 10.3 and 11.1 <limits> headers were compiled with Clang in small probes (strict and GNU C++17/C++20) to check the __int128 specialisation, and libc++ probes passed in the same modes.

AI assistance

Prepared with AI assistance (Claude Code) and reviewed with GitHub Copilot.

🤖 Generated with Claude Code

checked_div and checked_mod only rejected a zero divisor, so for signed
built-in types min / -1 and min % -1 still ran and hit undefined
behaviour. Return false for that pair too, guarded by if constexpr so
class types are unchanged, and fix checkedModZeroDivisor to call
checked_mod.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@meta-cla meta-cla Bot added the CLA Signed label Nov 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant