Skip to content

<xlocnum>: Fix num_put::do_put() to avoid frexp()'s unspecified behavior for inf/nan#6168

Open
StephanTLavavej wants to merge 3 commits intomicrosoft:mainfrom
StephanTLavavej:frexp
Open

<xlocnum>: Fix num_put::do_put() to avoid frexp()'s unspecified behavior for inf/nan#6168
StephanTLavavej wants to merge 3 commits intomicrosoft:mainfrom
StephanTLavavej:frexp

Conversation

@StephanTLavavej
Copy link
Member

Background

This bug was reported to me by @Codiferous, reversing the natural order of things. (I'm supposed to report compiler bugs to him; he's not supposed to report library bugs to me! 😹) He found this while doing brain surgery to replace the UCRT's math functions with LLVM libc's math functions.

CRT frexp behavior

C23 WG14-N3220 7.12.6.7 "The frexp functions"/3:

If value is not a floating-point number or if the integral power is outside the range of int, the results are unspecified.

Microsoft UCRT

When frexp() is called with inf/nan, our behavior is that we always set the integer exponent to -1. (This is internal minkernel/crts/ucrt/src/appcrt/tran/frexp.c assigning INT_NAN which is defined in minkernel/crts/ucrt/inc/trans.h; we ship most UCRT sources but I don't believe we ship these ones, sorry.)

LLVM libc

Their behavior is that they don't assign anything to the integer exponent by default, although they do support a mode to control this behavior: libc/src/__support/FPUtil/ManipulationFunctions.h

STL

Bug

The problem was that num_put::do_put() was calling frexp() while calculating how much to increase its internal buffer size for fixed-precision formatting (which might need lots of characters for large positive or negative exponents). Unfortunately, this wasn't guarded for finite values only, so we were allowing inf/nan to go through this codepath when they didn't need it. This went unnoticed for decades because the Microsoft UCRT (and DevDiv's CRT before that, presumably) set the integer exponent to -1, which we then took the abs() of, with a final result of adding exactly 0 to _Bufsize.

However, because we were garbage-initing int _Ptwo;, if we replace frexp()'s implementation with LLVM libc's, we end up using that garbage integer while calculating how much to adjust _Bufsize, which is catastrophic.

Fix

From previous fixes (#4212), we're already determining whether the value is finite (i.e. neither inf nor nan). Move that up, so we'll call frexp() to perform the buffer size adjustment for finite values only. inf/nan don't need this because immediately afterwards, we add 50 as a "fudge factor"; this isn't pretty but it is absolutely enough for our possible inf/nan outputs:

_Buf.resize(_Bufsize + 50); // add fudge factor

Additionally, I have a commit to avoid garbage-init for frexp()'s integer exponent. While this isn't needed for correctness (the previous commit is the correctness fix), zero is a strict subset of garbage, and this may help avoid analysis warnings in the future. Note that this zero-init isn't sufficient for correctness either, since there's nothing stopping frexp()'s unspecified behavior from being "assign a billion".

Finally, #4212 introduced comprehensive test coverage for #4210, which is where @Codiferous actually found this bug. It was almost perfect to leave as-is, but I noticed that we weren't expecting specific strings. They're implementation-defined (C23 WG14-N3220 7.23.6.1 "The fprintf function"/8), so expecting these exact outputs will improve the strictness of the test.

@StephanTLavavej StephanTLavavej requested a review from a team as a code owner March 19, 2026 21:36
@StephanTLavavej StephanTLavavej added the bug Something isn't working label Mar 19, 2026
@github-project-automation github-project-automation bot moved this to Initial Review in STL Code Reviews Mar 19, 2026
@StephanTLavavej StephanTLavavej moved this from Initial Review to Final Review in STL Code Reviews Mar 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

Status: Final Review

Development

Successfully merging this pull request may close these issues.

1 participant