Repository navigation
fix(libc): parse NaN payloads in strtod as measured on a PS5 - #7545
Conversation
|
I compared |
|
Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.
|
|
Thanks for the Windows test results after the rebase. The 464/465 local result with one environment skip, plus the passing |
In a payload process on a PS5 (firmware 12.70), the strtod and wcstod
of libSceLibcInternal.sprx keep the payload of a NAN(...) sequence,
read as FreeBSD 11's gdtoa hexnan reads it, under the quiet bit and
with the sign. strtof, wcstof, strtold and wcstold return the positive
default quiet NaN whatever the sign and the sequence, and consume a
sequence of letters, digits and underscores up to its closing
parenthesis. Here every NaN lost its payload, and strtof and strtold
kept the sign.
The model reproduces the 64 NaN inputs that p11, p14, p27 and p33
measured, including blanks and other characters inside the parentheses
and unterminated sequences, and the test now pins them. It also pins
two p71 cases measured through strtod: nan(1 2), and "nan( " followed
by NUL and ")". In hexnan, a blank after a payload digit, while a
32-bit word of the payload is still free, starts a blank skip that has
no NUL check, so when only blanks follow up to the terminating NUL the
console reads past it (p71); that case throws here. A blank right after
"nan(" is skipped with a NUL check, and the console stopped at the NUL
there.
AI-assisted: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ba2b7aa to
ae662c8
Compare
|
Rebase handoff — rebased onto current Verified on Linux (CachyOS, kernel 7.2.9, GCC 16.2.1, Ninja, Release, To pick it up: Head is |
|
Correction: my earlier push of |
|
Checked the |
What
strtodand the related conversions now parse aNAN(...)sequence the way the console'slibSceLibcInternal.sprxdoes when called from a payload process on a PS5 (firmware 12.70); whether a title's ownlibc.prxdoes the same is unverified.libSceLibcInternal.sprx, firmware 12.70)mainbefore this PRstrtod,wcstodstrtof,wcstof0x7fc00000; sign dropped, payload ignored0x7fc00000, or0xffc00000after a minus signstrtold,wcstold0x7fff, significand0xc000000000000000)atofcallsstrtodhere, so it changes with it; the console'satofwas not tried.How
strtodreads the sequence. It matches FreeBSD 11's gdtoahexnan, and the payload goes under the quiet bit as in itsstrtod:0x. Sonan(10)gives payload0x10.nan(1 )gives0x100000000.nanis consumed.HexNanPayloadis a direct translation ofhexnanfor a 52-bit payload, with one exception. Inhexnan, a blank after a payload digit, while a 32-bit word of the payload is still free, starts a blank skip that has no NUL check. When only blanks follow up to the terminating NUL, the console reads past it:nan(1followed by NUL and)consumes 8 characters, the)after the NUL included (p71 below). The result then depends on the memory after the string, soHexNanPayloadthrows for that case. A blank right afternan(is skipped with a NUL check, and there the console stopped at the NUL (open_blank_nul_paren).How
strtofandstrtoldread it. They consume a sequence of letters, digits and underscores up to its closing parenthesis, as C17 describes. The value is the fixed NaN in the table, whatever the sequence holds.Where the end pointer comes from. For NaN subjects the end pointer no longer comes from the host's parser. Other inputs still go through the host as before.
Tests: the old test asserted the inferred behaviour. It is replaced by a table of the 64 NaN inputs that p11, p14, p27 and p33 measured on the console, generated from the logs. All 64 go through
strtodandstrtof, and throughatof, which is checked against the console'sstrtodresults. 50 of them also go throughstrtoldandwcstod, and 18 throughwcstofandwcstold. The wide-nan(0x123)case inGuestWideStrings.cppnow expects the payload, and the positive NaN forwcstofandwcstold. Two more checks make sure thatnan(1andnan(12 \tthrow, and two pin p71 cases measured throughstrtod:nan(1 2)gives 0x7ff8000100000002 with 8 characters consumed, andnan(followed by NUL and)gives 0x7ff8000000000000 with 3, the)after the NUL not consumed.Debt: in
docs/dev/debt/libc.md, thestrtod, strtof, strtoldentry said that the sequence was ignored, and that this was inferred and not observed. It now describes what was measured, including the case that throws.How the console was measured
Setup:
Method:
libSceLibcInternal.sprxand called it through the returned pointer. p11 and p14 called the linkedstrtodandstrtof, and theirresolvelines show these are the exports oflibSceLibcInternal.sprx(same_as_called=yes).usedis the number of characters consumed.Checking the model: the attached
scripts/nan_model.pyis the model in Python. Run from the unpacked attachment, it checks all 64 inputs against the attached logs and finds no mismatch. LikeHexNanPayload, it raises wherehexnan's in-loop blank skip would read past the terminating NUL.Attachments: sources and the full logs are in the attached nan-probes.zip.
Console output (excerpt, lines copied unchanged)
The
pNN:prefix names the probe; the rest of each line is copied unchanged.Overlaps:
#if !defined(__aarch64__)around the long doubleQuietNan, which this PR removes. Whichever lands second moves the guard to the long double branch ofNanValueor drops it.main, and this PR adds no new conflict to it.lgamma) and fix(libc): return jrand48 and mrand48 zero-extended as measured on a PS5 #7546 (jrand48,mrand48), my other PRs, change neighbouring entries ofdocs/dev/debt/libc.md, and fix(libc): match lgamma's signgam for -0 and its errno at poles as measured on a PS5 #7544 alsoGuestMath.cpp. All three merge cleanly in any order.main(checked withgit merge-tree).Tested
Build: Windows 11 x86-64, full build with WinLibs MinGW-w64 GCC 15.2.0 (posix-seh, ucrt, r7) and
-DBUILD_TESTING=ON.Tests: the branch was rebased onto
main63e4a63. The rebase conflicted only indocs/dev/debt/libc.md, where #6345, now merged, had added two lines right after thestrtodentry this PR rewrites; the resolution keeps those two lines and this PR's entry. On the rebased branch,guest_math,guest_wide_stringsand the otherguest_*tests pass: 496 of 497 pass andguest_video_out_wqhd_detection_no_vulkanis skipped on this machine. In the first parallel run (ctest -j 6),guest_pthread_barrierandguest_pthread_rwlocktimed out; the whole second run passed. The currentmaindc54701 merges with this branch without a conflict.Old code, new test: on the rebased branch, with
Strings.cppreverted tomainand the new tests kept,guest_mathandguest_wide_stringsabort.Console: behaviour measured on a PS5 (firmware 12.70), as described above.
Not tested here: Linux (a reviewer reported a Linux run in the comments), and no title.
Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file.mdfiles or images added to the repository (attach them to this PR)🤖 Generated with Claude Code