Skip to content

Pretty: fix Undefined Behavior with NaN floats #282

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 18, 2025

Conversation

niooss-ledger
Copy link
Contributor

When printing CBOR data containing NaN, function convertToUint64 does an undefined behavior. This can be reproduced using test cases from tests/parser/data.cpp:

$ make CC='clang -fsanitize=undefined'
$ printf "\xfb\x7f\xf8\0\0\0\0\0\0" | ./bin/cbordump
src/cborpretty.c:171:17: runtime error: nan is outside the range of representable values of type 'unsigned long'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior src/cborpretty.c:171:17 in
nan

$ printf "\xf9\x7e\x00" | ./bin/cbordump
src/cborpretty.c:171:17: runtime error: nan is outside the range of representable values of type 'unsigned long'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior src/cborpretty.c:171:17 in
nan

Fix this by checking whether the value to convert is not NaN.

Copy link
Member

@thiagomacieira thiagomacieira left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also looks ok, but a simpler change is to invert the comparison below:

    supremum = -2.0 * INT64_MIN;     /* -2 * (- 2^63) == 2^64 */
    if (!(v < supremum))
        return false;       /* out of range or NaN */

@niooss-ledger niooss-ledger force-pushed the fix-cborpretty-ub-nan branch from 5b0e304 to e6640b4 Compare March 18, 2025 08:58
When printing CBOR data containing NaN, function convertToUint64 does an
undefined behavior. This can be reproduced using test cases from
tests/parser/data.cpp:

    $ make CC='clang -fsanitize=undefined'
    $ printf "\xfb\x7f\xf8\0\0\0\0\0\0" | ./bin/cbordump
    src/cborpretty.c:171:17: runtime error: nan is outside the range of
    representable values of type 'unsigned long'
    SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior src/cborpretty.c:171:17 in
    nan

    $ printf "\xf9\x7e\x00" | ./bin/cbordump
    src/cborpretty.c:171:17: runtime error: nan is outside the range of
    representable values of type 'unsigned long'
    SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior src/cborpretty.c:171:17 in
    nan

Fix this by checking whether the value to convert is not NaN.
@niooss-ledger
Copy link
Contributor Author

In my humble opinion, both if (isnan(v)) and if (!(v < supremum)) work for me. I modified the PR to use the second option. Could you please take a look?

@thiagomacieira
Copy link
Member

In my humble opinion, both if (isnan(v)) and if (!(v < supremum)) work for me. I modified the PR to use the second option. Could you please take a look?

Done. Since this is tiny CBOR, I'd prefer the one that generates less code, even if slightly less expressive. Compilers ought to have been able to tell that an isnan and range comparison can be implemented in a single go... but most don't.
https://gcc.godbolt.org/z/69Thje93Y - compiled with -O3, Clang is the only one to do it. Nothing changes with -Os and similar. (I can barely read https://cross.godbolt.org/z/WfPrYb99Y). The "JA" and "SETA" instructions operate if CF = ZF = 0, which UCOMISD will not set if it's NaN. So it's showing the NaN check for this assembly as GCC generated is unnecessary.

Meanwhile, in https://gcc.godbolt.org/z/qPK8oehhW with the code I suggested, all four compilers generated optimal code. The "B" (or "C") condition is if CF = 1... wait, that doesn't look right.

Testing in https://gcc.godbolt.org/z/da3KqbbWr: the three compilers that inlined the function say "NaN" is in range.

Ok, this didn't work. Please change back to your original code. Sorry about the noise!

Time to file a missed optimisation report with GCC.

Copy link
Member

@thiagomacieira thiagomacieira left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, this didn't work. Please change back to your original idea.

@thiagomacieira
Copy link
Member

Meanwhile, in https://gcc.godbolt.org/z/qPK8oehhW with the code I suggested, all four compilers generated optimal code. The "B" (or "C") condition is if CF = 1... wait, that doesn't look right.

Testing in https://gcc.godbolt.org/z/da3KqbbWr: the three compilers that inlined the function say "NaN" is in range.

Ok, this didn't work. Please change back to your original code. Sorry about the noise!

Time to file a missed optimisation report with GCC.

Oops, we do want an out of range.
https://gcc.godbolt.org/z/qnn4E7T9h - isnan + range
https://gcc.godbolt.org/z/qzE8djjhz - range

Or, more specifically, what we actually want:
https://gcc.godbolt.org/z/4GEGjTzT5 - original proposal
https://gcc.godbolt.org/z/WMW58rfsE - this way
They're both the same.

@thiagomacieira thiagomacieira merged commit f965025 into intel:main Mar 18, 2025
6 checks passed
@niooss-ledger niooss-ledger deleted the fix-cborpretty-ub-nan branch March 18, 2025 17:57
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.

None yet

2 participants