build: check format strings, which nothing ever did - #44
Merged
Conversation
The segfault in issue #5 was a format string: `%I64d` is MSVC-only, glibc parses it as the GNU `I` flag with a width of 64 and an `int` conversion, the vararg run desynchronises, and the following `%s` dereferences an integer. It killed the node mid-sync at the first block carrying a real transaction, deterministically. GCC can catch this. It never got the chance, for two reasons that had to be fixed together: - `-w` sat in both makefiles and suppressed every diagnostic. It cannot be narrowed; it wins over any -W or -Werror= that follows, so it had to go. The four -Wno- entries replacing it are the categories this 2009-era tree actually produces (measured: 248 deprecated-declarations, 157 write-strings, 18 narrowing, 1 unused-result), silenced by name instead of wholesale. - Even with -Wformat on, nothing was checked. GCC only inspects format strings for functions it knows are printf-like, and every format string here goes through strprintf, error, my_snprintf, or OutputDebugStringF -- `printf` itself is #defined to the last of those. None carried a format attribute, so the entire tree was invisible to the check. They are annotated now. Annotating surfaced 77 warnings across 17 distinct sites, all fixed here. Most were size_t or int64 passed to %d, harmless on x86-64 but wrong. Two were not cosmetic: - CBlockIndex::ToString printed pprev/pnext with %08x, truncating both pointers to 32 bits, so the two fields that line exists to show were unreliable on every 64-bit build. - CInv::GetCommand threw "type=% unknown type", where the % ate the space as a flag and the u as its conversion. The message read "type= 3nknown type". PRId64 is now one spelling for every target. Its MSVCRT branch was what produced "I64d" in the first place, it is pointless against UCRT which takes the C99 forms, the rest of the tree already prints int64 with %lld on Windows, and its only user (i64tostr) is dead code. Collapsing it lets both builds share the gnu_printf archetype -- ms_printf would reject %zu, which this tree uses and UCRT supports, while waving through the %I64d that crashes. Verified on both platforms: clean build, and reintroducing the issue #5 specifier now fails compilation rather than shipping. main.h:410:50: error: unknown conversion type character 'I' in format main.h:410:70: error: format '%s' expects argument of type 'char*', but argument 3 has type 'long long int' That second line is the crash itself, caught at build time. Runtime checked on both: node syncs past the block from issue #5, and the repaired log lines read correctly -- before: CTxOut(nValue= 50.%08I64d, scriptPubKey=(null)) after: CTxOut(nValue=50.00000000, scriptPubKey=0xE1C7EF3E706002) Four '_WIN32_WINNT redefined' warnings remain on Windows. Silencing them means letting a system header pick the Windows API level instead of this tree, which is a separate decision from format checking.
Merged
Bitflash-sh
added a commit
that referenced
this pull request
Jul 30, 2026
Vic-Nas
added a commit
to Vic-Nas/bitflash-mod
that referenced
this pull request
Jul 30, 2026
…r no-op fix) My previous attempt (adding -Wformat -Werror=format after -w in CXXFLAGS) did nothing. Verified by direct test: a genuine format bug (%d given a long long) still compiles clean with -w present in the command line, regardless of where -Wformat/-Werror=format are placed relative to it. -w overrides them unconditionally. Ported and adapted Bitflash-sh#44 properly instead, which diagnosed this exact problem against the same root cause (the Bitflash-sh#5 segfault) and fixed it correctly: -w removed entirely, replaced with -Wno- deprecated-declarations/-Wno-write-strings/-Wno-narrowing/-Wno- unused-result (the categories this tree's own code legitimately triggers) plus -Wformat -Wformat-security -Werror=format. Also required BF_FORMAT annotations on strprintf/my_snprintf/ErrorImpl/ OutputDebugStringF -- printf itself is #defined to the last of those, and GCC only checks format strings for functions it knows are printf-like, so without the attributes -Wformat had nothing to inspect regardless of -w. gnu_printf archetype specifically: MinGW's alternative, ms_printf, models legacy msvcrt and would reject %zu (used here, supported by UCRT) while accepting %I64d (the specifier that actually crashes on glibc) -- exactly backwards for what this needs to catch. Collapsed PRId64/PRIu64/PRIx64 to one spelling (dropping the MSVCRT branch that produced %I64d in the first place), needed for both platforms to share one format archetype. Adapted rather than copied where our fork has diverged: PrintBlockTree doesn't exist here (removed earlier this session as dead code, so its fixes in the upstream diff don't apply), and `error` here is a macro over ErrorImpl (ours), not a plain function (theirs). Removing -w surfaced real bugs beyond what the upstream PR's diff covers, since this fork carries additional code: a SECP256K1_STATIC macro-redefinition warning in nostr.cpp/btfaddr.cpp (guarded the same way upstream did), and one more size_t-into-%d mismatch in util.cpp's AddTimeData logging that wasn't in their tree. Verified by actually compiling every source file in the tree with the real flags (not just the ones this diff touches) -- main/net/gui/ main_gui/util/db/rpc/script/update/market/dashboard.cpp all clean. nostr/btfaddr/compactblock/randomx_pow.cpp couldn't be verified this same way here (their C dependencies -- secp256k1, libsodium, RandomX -- aren't vendored in this sandbox, unrelated to this change), but nostr.cpp and btfaddr.cpp got the one warning each that could be checked without those headers (the macro redefinition, both fixed).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #5. That segfault was a format string, and the compiler could have caught it — this makes sure the next one is caught.
What happened
%I64dis MSVC-only. glibc reads it as the GNUIflag, a width of 64, and anintconversion; the vararg run desynchronises and the following%sdereferences an integer as a pointer. It killed the node mid-sync, deterministically, at the first block carrying a real transaction.GCC diagnoses this. It never got the chance, for two reasons that had to be fixed together — fixing either alone does nothing:
-win both makefiles suppressed every diagnostic. It cannot be narrowed: it wins over any-Wor-Werror=that follows, in any order. So it had to go. The four-Wno-entries replacing it are the categories this 2009-era tree actually produces — measured at 248deprecated-declarations, 157write-strings, 18narrowing, 1unused-result— silenced by name rather than wholesale.No format attributes. Even with
-Wformaton, GCC only inspects format strings for functions it knows are printf-like. Every format string in this project goes throughstrprintf,error,my_snprintforOutputDebugStringF— andprintfitself is#defined to the last of those. None carried an attribute, so the whole tree was invisible to the check. That is the real reason%I64dsurvived, and it is why enabling warnings alone would have changed nothing.What it found
77 warnings across 17 distinct sites (the same header lines multiplied across translation units), all fixed here. Most were
size_torint64passed to%d— harmless on x86-64, still wrong. Two were not cosmetic:CBlockIndex::ToStringprintedpprev/pnextwith%08x, truncating both pointers to 32 bits. The two fields that line exists to show were unreliable on every 64-bit build.CInv::GetCommandthrew"type=% unknown type"— the%ate the space as a flag and theuas its conversion, so the message readtype= 3nknown type.PRId64
Now one spelling for every target. Its MSVCRT branch is what produced
"I64d"in the first place; it is pointless against UCRT, which takes the C99 forms; the rest of the tree already printsint64with%lldon Windows; and its only user,i64tostr, is dead code. Collapsing it is what lets both builds share thegnu_printfarchetype —ms_printfwould reject%zu(used here, supported by UCRT) while waving through the%I64dthat crashes.Verification
Both platforms, clean build, then the specifier reintroduced on purpose:
The second line is the crash itself, caught at build time instead of shipped.
Runtime on both platforms — node syncs past the issue #5 block, and the repaired log lines read correctly:
%I64dreintroduced fails the build on bothKnown leftover
Four
'_WIN32_WINNT' redefinedwarnings remain on Windows. Silencing them means letting a system header choose the Windows API level instead of this tree, which is a separate decision from format checking and does not belong in this PR.