Skip to content

Answer 0 for modulo by -1 instead of trapping on INT64_MIN - #348

Open
youdie006 wants to merge 1 commit into
pantor:mainfrom
youdie006:modulo-negative-one
Open

youdie006 wants to merge 1 commit into
pantor:mainfrom
youdie006:modulo-negative-one

Conversation

@youdie006

Copy link
Copy Markdown
Contributor

{{ a % b }} renders 0 for every a when b is -1, except a = -9223372036854775808, where the raw integer % overflows and raises SIGFPE. The host process dies; there is no exception to catch, unlike every other bad-input path here.

{{ 7 % -1 }}                    0
{{ -7 % -1 }}                   0
{{ min % -1 }}                  Floating point exception (core dumped)
{{ divisibleBy(min, -1) }}      Floating point exception (core dumped)

divisibleBy has the same hole: its divisor != 0 short circuit lets -1 through to the same raw % (renderer.hpp:364). Op::Division (:305-311) is safe because it converts to number_float_t first, so these two are the only sites.

Nothing that works today changes. The guard returns the 0 and the true that the same expression already returns for every other dividend, so there is no new exception and no new message. This follows #347, which covered the divisor 0 case; -1 is the other way that % traps.

Verification

g++ -std=c++17 -O1 -Wall -Wextra -Werror -D__TEST_DIR__=test -Iinclude -Ithird_party/include test/test.cpp. md5sum include/inja/renderer.hpp on every row; test/test-functions.cpp is constant across rows.

renderer.hpp md5 result
this PR 01f8a630 `assertions: 269
pristine HEAD c7d6e23f 40 passed, then CRASHED: SIGFPE
revert Op::Modulo only fed8786e 40 passed, then SIGFPE
revert Op::DivisibleBy only 47626360 71 passed, then SIGFPE
divisor <= -1 80167192 1 failed: {{ 7 % -2 }} gives 0, want 1
guard the dividend instead 9260e293 1 failed: {{ int_min % 3 }} gives 0, want -2

The two revert rows crash at different assertion counts, so each site is independently required. The two over-correction rows pin both axes: widening the divisor test breaks an existing row at test-functions.cpp:43, and guarding the dividend breaks the new int_min % 3 row.

Also run: the single_inja_test configuration against the regenerated single_include/inja/inja.hpp, 269 | 269 passed; and -fsanitize=address,undefined -fno-sanitize-recover=all, clean. single_include was regenerated with scripts/update_single_include.sh and differs from HEAD by exactly the fix lines.

Test rows go in test/test-functions.cpp, in the math and divisibleBy subcases next to the ones #347 added.

Not run here: cmake, and the clang / clang-tidy / Windows / macOS matrix rows.

Disclosure: this change was written with AI assistance (Claude). The measurements above were reproduced and checked by me before opening this PR.

a % -1 is 0 for every a, and inja already renders 0 for it. The one
exception is INT64_MIN, where the raw integer % overflows and raises
SIGFPE, killing the host process with no exception to catch.

divisibleBy has the same hole: its divisor != 0 short circuit lets -1
through to the same raw %. Op::Division is safe because it converts to
number_float_t first, so these are the only two sites.

No input that works today changes: the guard returns the 0 and the true
that the same expression already returns for every other dividend.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant