Repository navigation
fix(libc): stop the strtol family at a binary prefix on Linux - #1459
Conversation
b951a3a to
0541c12
Compare
|
the wide versions have the same problem and aren't covered here. the rest checks out on Linux, Ubuntu 24.04 in Docker with glibc 2.39 and GCC 14.2. |
0541c12 to
b709647
Compare
|
Thank you @Zheorgyan! The wide versions now go through the same check: I also rebased onto current main to clear the TechnicalDebt conflict with #1278. |
b709647 to
74dde55
Compare
|
Rebased onto current main ( The earlier red Linux run was |
|
One related issue surfaced while reviewing the Windows This PR also changes |
C17 has no binary prefix: with base 0 or 2, strtol("0b101", &end, base)
reads the subject "0", returns 0 and leaves end at the 'b'. With glibc
2.38 or later, g++ (which defines _GNU_SOURCE) binds std::strtoll,
strtoull, strtoul, strtoimax and strtoumax to the C23 __isoc23_*
functions, which accept 0b/0B: the Linux exports returned 5 and
consumed the whole string. The Windows UCRT already follows C17, so the
same guest call gave different results on the two hosts.
strtol, strtoul, strtoll, strtoull, _Stoull, strtoimax, strtoumax and
_Stoul now return 0 with the end pointer after the '0' when, after
white space and an optional sign, the text starts with 0b or 0B and a
binary digit, in base 0 or 2. Everything else still goes to the host
function. The scanf family's %i has the same glibc behaviour; it is
recorded in TechnicalDebt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wcstol, wcstoll, wcstoul and wcstoull parse the ASCII prefix of the guest's UTF-16 text with std::strtoll and std::strtoull, which glibc 2.38 and later bind to the C23 __isoc23_* functions: wcstol(u"0b101", &end, 0) returned 5 with the end pointer after the whole string. They now check StopAtBinaryPrefix_nid_no_patch first, like the narrow functions, and the end pointer maps back through ParseAsciiPrefix as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
74dde55 to
30ebbed
Compare
The guest's unsigned long is 64 bits. _Stoul was declared with the host's unsigned long and returned std::strtoul, which are 32 bits on Windows (LLP64): "4294967296" clamped to 4294967295 with ERANGE and "-1" gave 0xffffffff, so a guest std::stoul threw out_of_range on values the console accepts. It now returns std::uint64_t from std::strtoull, as strtoul_nid_postfix and _Stoull already do; on Linux nothing changes. The binary-prefix check stays in front of the host call. Reported in boykopovar#2424 by AchrafReyani; the same change was proposed in boykopovar#2442 by SurajKhanna, whose two guest_math cases are taken here, with the "-1" case from boykopovar#1579 by ewraj. Folded in at MarcZX100's suggestion on boykopovar#1459. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The guest's unsigned long is 64 bits. _Stoul was declared with the host's unsigned long and returned std::strtoul, which are 32 bits on Windows (LLP64): "4294967296" clamped to 4294967295 with ERANGE and "-1" gave 0xffffffff, so a guest std::stoul threw out_of_range on values the console accepts. It now returns std::uint64_t from std::strtoull, as strtoul_nid_postfix and _Stoull already do; on Linux nothing changes. The binary-prefix check stays in front of the host call. Reported in boykopovar#2424 by AchrafReyani; the same change was proposed in boykopovar#2442 by SurajKhanna, whose two guest_math cases are taken here. Folded in at MarcZX100's suggestion on boykopovar#1459. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
30ebbed to
2243e17
Compare
|
Thanks @MarcZX100 for connecting the dots, and thanks @AchrafReyani for the report in #2424 and @SurajKhanna for the patch in #2442. Folded in as a third commit: |
|
Checked on Linux (glibc 2.39, GCC): |
The guest's unsigned long is 64 bits. _Stoul was declared with the host's unsigned long and returned std::strtoul, which are 32 bits on Windows (LLP64): "4294967296" clamped to 4294967295 with ERANGE and "-1" gave 0xffffffff, so a guest std::stoul threw out_of_range on values the console accepts. It now returns std::uint64_t from std::strtoull, as strtoul_nid_postfix and _Stoull already do; on Linux nothing changes. The binary-prefix check stays in front of the host call. Reported in boykopovar#2424 by AchrafReyani; the same change was proposed in boykopovar#2442 by SurajKhanna, whose two guest_math cases are taken here. Folded in at MarcZX100's suggestion on boykopovar#1459. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
fix(libc): stop the strtol family at a binary prefix on Linux
What
On Linux, the narrow integer converters
strtol,strtoul,strtoll,strtoull,_Stoull,strtoimax,strtoumaxand_Stoulpass the guest's text and base straight to glibc. The wide converterswcstol,wcstoll,wcstoulandwcstoullpass the ASCII prefix of the guest's text. Since glibc 2.38, code built as GNU C++ calls the C23__isoc23_*functions (g++ defines_GNU_SOURCE). Those accept a0b/0Bbinary prefix in base 0 and 2. C17 has no binary prefix (7.22.1.4p3): the subject sequence of"0b101"is"0", so the result is 0 and the end pointer stops at theb.Results on Linux with the code on main:
strtol("0b101", &end, 0)strtol("0b101", &end, 2)strtol(" -0B1", &end, 0)strtoul("0b11", &end, 2)strtoimax("0b101", &end, 0)strtoumax("0b101", &end, 0)_Stoul("0b101", &end, 0)wcstol(u"0b101", &end, 0)Separately, on Windows,
_Stoulwas declared with the host'sunsigned longand returnedstd::strtoul, both 32-bit in the LLP64 build, while the guest'sunsigned longis 64-bit (#2424)."4294967296"clamped to 4294967295 withERANGEand"-1"gave0xffffffff, so a gueststd::stoulthrewout_of_rangeon values the console accepts.Changes, one commit each:
StopAtBinaryPrefix_nid_no_patch(Strings.cpp, declared inGeneral.hpp) handles text in base 0 or 2. After C-locale white space and an optional sign, if the text starts with0bor0Band a binary digit, it returns 0 and sets the end pointer after the0. The eight narrow exports check it first. Every other input still goes to the host function, so errno, overflow and the0/0xprefixes are unchanged.guest_mathgets rows for base 0 and base 2, a sign with leading white space,0b2(where glibc already stops after the0) and0b101in base 16 (still the hex value0xb101), and checksstrtoll,strtoull,strtoumax,_Stoulland_Stoulon" -0B11".ParseAsciiPrefixmaps the end pointer back to the guest's UTF-16 text as before.guest_wide_stringschecks them onu" -0b101"in base 0 and base 2, andu"0b101"in base 16._Stoul_nid_postfixreturnsstd::uint64_tfromstd::strtoull, asstrtoul_nid_postfixand_Stoullalready do, with the prefix check still in front.guest_mathchecks"4294967296","18446744073709551615"and"-1". On Linux this changes nothing.%ireaches glibc's C23 conversion the same way:sscanf("0b101", "%i", &value)stores 5. Because the format is handed to glibc, this is recorded in TechnicalDebt rather than changed here.The Windows behaviour of the prefix check is unchanged: the UCRT functions accept only the
0and0xprefixes, so the new check returns what they already return.Thanks to @Zheorgyan for catching the wide functions, @AchrafReyani for #2424, @SurajKhanna for #2442, and @MarcZX100 for suggesting the fold.
Tested
-DBUILD_TESTING=ON, this branch on d724cad (current main).guest_mathandguest_wide_stringspass;guest_mathfails on main with "Guest strtol failed for '0b101' in base 0", andguest_wide_stringsfails on the first commit, before the wide change. The table above comes from main's function bodies, built with the same compiler and glibc._Stoulchange is the one fix(libc): return 64-bit values from _Stoul on Windows #2442 tested on Windows 11, GCC 16.2.0 (UCRT64), whereguest_mathaborted on the first new case before the change. The CI Windows run on the previous push (419/419) predates that commit.python3 tools/check_conventions.py --base upstream/mainpasses.Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt.mdfiles or images added to the repository (attach them to this PR)