upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBitflash <btf01@proton.me>2026-07-30 10:38:12 -0300
committerGitHub <noreply@github.com>2026-07-30 10:38:12 -0300
commit2ed9aed2ea9e302b3b36b37806ec03f2cccaaed1 (patch)
treeab30eade9435c074ee08c70303b8a15c08770b4f
parentf30f45849ecd45be6a073b6b1d308e1c12b8b52b (diff)
build: check format strings, which nothing ever did (#44)
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.
-rw-r--r--src/Makefile21
-rw-r--r--src/btfaddr.cpp3
-rw-r--r--src/main.cpp16
-rw-r--r--src/main.h14
-rw-r--r--src/makefile.mingw9
-rw-r--r--src/net.cpp2
-rw-r--r--src/net.h4
-rw-r--r--src/nostr.cpp6
-rw-r--r--src/util.cpp4
-rw-r--r--src/util.h44
10 files changed, 92 insertions, 31 deletions
diff --git a/src/Makefile b/src/Makefile
index 9846d8e..f0c6706 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -9,7 +9,26 @@ RANDOMX_DIR ?= $(HOME)/RandomX
9 9
10IMGUI_DIR = imgui 10IMGUI_DIR = imgui
11 11
12CXXFLAGS = -std=gnu++14 -O2 -w -fpermissive -pthread \ 12# -w used to sit here and suppressed every diagnostic in the build, including
13# the one that mattered: `%I64d` is MSVC-only, glibc mis-parses it as the GNU
14# `I` flag with a width of 64 and an `int` conversion, and the resulting vararg
15# desynchronisation segfaulted the node while logging a received block (issue
16# #5, deterministic at height 1062 -- the first block carrying a real
17# transaction). GCC had been reporting it all along:
18#
19# warning: format '%d' expects argument of type 'int',
20# but argument 4 has type 'int64_t'
21#
22# -w cannot be narrowed: it wins over any -W or -Werror= that follows it, so it
23# has to go. The four -Wno- entries below are the categories this 2009-era tree
24# genuinely produces (measured: 248 deprecated-declarations, 157 write-strings,
25# 18 narrowing, 1 unused-result); silencing them by name instead of silencing
26# everything keeps the build quiet while leaving format errors fatal.
27WARNFLAGS = -Wformat -Werror=format \
28 -Wno-deprecated-declarations -Wno-write-strings \
29 -Wno-narrowing -Wno-unused-result
30
31CXXFLAGS = -std=gnu++14 -O2 $(WARNFLAGS) -fpermissive -pthread \
13 -DSECP256K1_STATIC \ 32 -DSECP256K1_STATIC \
14 -I$(SECP_INC) -I$(RANDOMX_DIR)/src \ 33 -I$(SECP_INC) -I$(RANDOMX_DIR)/src \
15 -I/usr/include/nlohmann \ 34 -I/usr/include/nlohmann \
diff --git a/src/btfaddr.cpp b/src/btfaddr.cpp
index 55bb6c5..6edcc91 100644
--- a/src/btfaddr.cpp
+++ b/src/btfaddr.cpp
@@ -7,7 +7,10 @@
7#include "btfaddr.h" 7#include "btfaddr.h"
8 8
9#include <openssl/sha.h> 9#include <openssl/sha.h>
10// See the note in nostr.cpp: the makefiles pass -DSECP256K1_STATIC too.
11#ifndef SECP256K1_STATIC
10#define SECP256K1_STATIC 12#define SECP256K1_STATIC
13#endif
11#include <secp256k1.h> 14#include <secp256k1.h>
12#include <secp256k1_schnorrsig.h> 15#include <secp256k1_schnorrsig.h>
13#include <secp256k1_extrakeys.h> 16#include <secp256k1_extrakeys.h>
diff --git a/src/main.cpp b/src/main.cpp
index bb317d2..c882b0b 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -898,7 +898,7 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, map<uint256, CTxIndex>& mapTestPoo
898 } 898 }
899 899
900 if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size()) 900 if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size())
901 return error("ConnectInputs() : %s prevout.n out of range %d %d %d", GetHash().ToString().substr(0,6).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size()); 901 return error("ConnectInputs() : %s prevout.n out of range %d %d %d", GetHash().ToString().substr(0,6).c_str(), prevout.n, (int)txPrev.vout.size(), (int)txindex.vSpent.size());
902 902
903 // If prev is coinbase, check that it's matured 903 // If prev is coinbase, check that it's matured
904 if (txPrev.IsCoinBase()) 904 if (txPrev.IsCoinBase())
@@ -1690,14 +1690,14 @@ void PrintBlockTree()
1690 pindex->nBlockPos, 1690 pindex->nBlockPos,
1691 block.GetHash().ToString().substr(0,14).c_str(), 1691 block.GetHash().ToString().substr(0,14).c_str(),
1692 DateTimeStr(block.nTime).c_str(), 1692 DateTimeStr(block.nTime).c_str(),
1693 block.vtx.size()); 1693 (int)block.vtx.size());
1694 1694
1695 CRITICAL_BLOCK(cs_mapWallet) 1695 CRITICAL_BLOCK(cs_mapWallet)
1696 { 1696 {
1697 if (mapWallet.count(block.vtx[0].GetHash())) 1697 if (mapWallet.count(block.vtx[0].GetHash()))
1698 { 1698 {
1699 CWalletTx& wtx = mapWallet[block.vtx[0].GetHash()]; 1699 CWalletTx& wtx = mapWallet[block.vtx[0].GetHash()];
1700 printf(" mine: %d %d %d", wtx.GetDepthInMainChain(), wtx.GetBlocksToMaturity(), wtx.GetCredit()); 1700 printf(" mine: %d %d %lld", wtx.GetDepthInMainChain(), wtx.GetBlocksToMaturity(), wtx.GetCredit());
1701 } 1701 }
1702 } 1702 }
1703 printf("\n"); 1703 printf("\n");
@@ -1759,7 +1759,7 @@ bool ProcessMessages(CNode* pfrom)
1759 CDataStream& vRecv = pfrom->vRecv; 1759 CDataStream& vRecv = pfrom->vRecv;
1760 if (vRecv.empty()) 1760 if (vRecv.empty())
1761 return true; 1761 return true;
1762 LogPrint("net", "ProcessMessages(%d bytes)\n", vRecv.size()); 1762 LogPrint("net", "ProcessMessages(%d bytes)\n", (int)vRecv.size());
1763 1763
1764 // 1764 //
1765 // Message format 1765 // Message format
@@ -1783,7 +1783,7 @@ bool ProcessMessages(CNode* pfrom)
1783 break; 1783 break;
1784 } 1784 }
1785 if (pstart - vRecv.begin() > 0) 1785 if (pstart - vRecv.begin() > 0)
1786 if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE SKIPPED %d BYTES\n\n", pstart - vRecv.begin()); 1786 if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE SKIPPED %d BYTES\n\n", (int)(pstart - vRecv.begin()));
1787 vRecv.erase(vRecv.begin(), pstart); 1787 vRecv.erase(vRecv.begin(), pstart);
1788 1788
1789 // Read header 1789 // Read header
@@ -1836,7 +1836,7 @@ bool ProcessMessages(CNode* pfrom)
1836bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) 1836bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
1837{ 1837{
1838 static map<unsigned int, vector<unsigned char> > mapReuseKey; 1838 static map<unsigned int, vector<unsigned char> > mapReuseKey;
1839 if (LogAcceptsCategory("net")) { printf("received: %-12s (%d bytes) ", strCommand.c_str(), vRecv.size()); for (int i = 0; i < min(vRecv.size(), (unsigned int)25); i++) printf("%02x ", vRecv[i] & 0xff); printf("\n"); } 1839 if (LogAcceptsCategory("net")) { printf("received: %-12s (%d bytes) ", strCommand.c_str(), (int)vRecv.size()); for (int i = 0; i < min(vRecv.size(), (unsigned int)25); i++) printf("%02x ", vRecv[i] & 0xff); printf("\n"); }
1840 if (nDropMessagesTest > 0 && GetRand(nDropMessagesTest) == 0) 1840 if (nDropMessagesTest > 0 && GetRand(nDropMessagesTest) == 0)
1841 { 1841 {
1842 if (LogAcceptsCategory("net")) printf("dropmessages DROPPING RECV MESSAGE\n"); 1842 if (LogAcceptsCategory("net")) printf("dropmessages DROPPING RECV MESSAGE\n");
@@ -2171,7 +2171,7 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
2171 2171
2172 2172
2173 if (!vRecv.empty()) 2173 if (!vRecv.empty())
2174 if (LogAcceptsCategory("net")) printf("ProcessMessage(%s) : %d extra bytes\n", strCommand.c_str(), vRecv.size()); 2174 if (LogAcceptsCategory("net")) printf("ProcessMessage(%s) : %d extra bytes\n", strCommand.c_str(), (int)vRecv.size());
2175 2175
2176 return true; 2176 return true;
2177} 2177}
@@ -2885,7 +2885,7 @@ bool BitcoinMiner()
2885 } 2885 }
2886 pblock->nBits = nBits; 2886 pblock->nBits = nBits;
2887 pblock->vtx[0].vout[0].nValue = pblock->GetBlockValue(nBestHeight + 1, nFees); 2887 pblock->vtx[0].vout[0].nValue = pblock->GetBlockValue(nBestHeight + 1, nFees);
2888 if (LogAcceptsCategory("net")) printf("\n\nRunning BitcoinMiner with %d transactions in block\n", pblock->vtx.size()); 2888 if (LogAcceptsCategory("net")) printf("\n\nRunning BitcoinMiner with %d transactions in block\n", (int)pblock->vtx.size());
2889 2889
2890 2890
2891 // 2891 //
diff --git a/src/main.h b/src/main.h
index fde3a4e..7e5bdf2 100644
--- a/src/main.h
+++ b/src/main.h
@@ -650,11 +650,11 @@ public:
650 string ToString() const 650 string ToString() const
651 { 651 {
652 string str; 652 string str;
653 str += strprintf("CTransaction(hash=%s, ver=%d, vin.size=%d, vout.size=%d, nLockTime=%d)\n", 653 str += strprintf("CTransaction(hash=%s, ver=%d, vin.size=%d, vout.size=%d, nLockTime=%u)\n",
654 GetHash().ToString().substr(0,6).c_str(), 654 GetHash().ToString().substr(0,6).c_str(),
655 nVersion, 655 nVersion,
656 vin.size(), 656 (int)vin.size(),
657 vout.size(), 657 (int)vout.size(),
658 nLockTime); 658 nLockTime);
659 for (int i = 0; i < vin.size(); i++) 659 for (int i = 0; i < vin.size(); i++)
660 str += " " + vin[i].ToString() + "\n"; 660 str += " " + vin[i].ToString() + "\n";
@@ -1081,7 +1081,7 @@ public:
1081 hashPrevBlock.ToString().substr(0,14).c_str(), 1081 hashPrevBlock.ToString().substr(0,14).c_str(),
1082 hashMerkleRoot.ToString().substr(0,6).c_str(), 1082 hashMerkleRoot.ToString().substr(0,6).c_str(),
1083 nTime, nBits, nNonce, 1083 nTime, nBits, nNonce,
1084 vtx.size()); 1084 (int)vtx.size());
1085 for (int i = 0; i < vtx.size(); i++) 1085 for (int i = 0; i < vtx.size(); i++)
1086 { 1086 {
1087 printf(" "); 1087 printf(" ");
@@ -1223,8 +1223,10 @@ public:
1223 1223
1224 string ToString() const 1224 string ToString() const
1225 { 1225 {
1226 return strprintf("CBlockIndex(nprev=%08x, pnext=%08x, nFile=%d, nBlockPos=%-6d nHeight=%d, merkle=%s, hashBlock=%s)", 1226 // pprev/pnext are pointers: %08x truncated them to 32 bits on a 64-bit
1227 pprev, pnext, nFile, nBlockPos, nHeight, 1227 // build, so the two fields this line exists to show were unreliable.
1228 return strprintf("CBlockIndex(nprev=%p, pnext=%p, nFile=%d, nBlockPos=%-6d nHeight=%d, merkle=%s, hashBlock=%s)",
1229 (void*)pprev, (void*)pnext, nFile, nBlockPos, nHeight,
1228 hashMerkleRoot.ToString().substr(0,6).c_str(), 1230 hashMerkleRoot.ToString().substr(0,6).c_str(),
1229 GetBlockHash().ToString().substr(0,14).c_str()); 1231 GetBlockHash().ToString().substr(0,14).c_str());
1230 } 1232 }
diff --git a/src/makefile.mingw b/src/makefile.mingw
index 7d35d92..01f7702 100644
--- a/src/makefile.mingw
+++ b/src/makefile.mingw
@@ -7,7 +7,14 @@ DEPS = ../deps
7RANDOMX = ../RandomX 7RANDOMX = ../RandomX
8IMGUI_DIR = imgui 8IMGUI_DIR = imgui
9 9
10CXXFLAGS = -std=gnu++14 -O2 -w -fpermissive -Wno-deprecated-declarations -pthread \ 10# See the long note in Makefile: -w hid the format bug that segfaulted the node
11# on glibc (issue #5), and -w cannot be narrowed, so format diagnostics are now
12# fatal and only the categories this tree actually produces are silenced.
13WARNFLAGS = -Wformat -Werror=format \
14 -Wno-deprecated-declarations -Wno-write-strings \
15 -Wno-narrowing -Wno-unused-result
16
17CXXFLAGS = -std=gnu++14 -O2 $(WARNFLAGS) -fpermissive -pthread \
11 -DWIN32 -DNOPCH \ 18 -DWIN32 -DNOPCH \
12 -DSECP256K1_STATIC \ 19 -DSECP256K1_STATIC \
13 -I$(DEPS)/include -I$(RANDOMX)/src \ 20 -I$(DEPS)/include -I$(RANDOMX)/src \
diff --git a/src/net.cpp b/src/net.cpp
index 08a3348..395092d 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -920,7 +920,7 @@ void ThreadSocketHandler2(void* parg)
920 920
921 if (pnodeExtra->GetRefCount() <= (pnodeExtra->fNetworkNode ? 1 : 0)) 921 if (pnodeExtra->GetRefCount() <= (pnodeExtra->fNetworkNode ? 1 : 0))
922 { 922 {
923 LogPrint("net", "(%d nodes) disconnecting duplicate: %s\n", vNodes.size(), pnodeExtra->addr.ToString().c_str()); 923 LogPrint("net", "(%d nodes) disconnecting duplicate: %s\n", (int)vNodes.size(), pnodeExtra->addr.ToString().c_str());
924 if (pnodeExtra->fNetworkNode && !pnode->fNetworkNode) 924 if (pnodeExtra->fNetworkNode && !pnode->fNetworkNode)
925 { 925 {
926 pnode->AddRef(); 926 pnode->AddRef();
diff --git a/src/net.h b/src/net.h
index d13de61..364fbfb 100644
--- a/src/net.h
+++ b/src/net.h
@@ -392,7 +392,9 @@ public:
392 const char* GetCommand() const 392 const char* GetCommand() const
393 { 393 {
394 if (!IsKnownType()) 394 if (!IsKnownType())
395 throw std::out_of_range(strprintf("CInv::GetCommand() : type=% unknown type", type)); 395 // Was "type=% unknown type": the % swallowed the space as a flag and
396 // the u as the conversion, so this read "type= 3nknown type".
397 throw std::out_of_range(strprintf("CInv::GetCommand() : type=%d unknown type", type));
396 return ppszTypeName[type]; 398 return ppszTypeName[type];
397 } 399 }
398 400
diff --git a/src/nostr.cpp b/src/nostr.cpp
index 482a835..088174a 100644
--- a/src/nostr.cpp
+++ b/src/nostr.cpp
@@ -6,8 +6,12 @@
6#include "headers.h" 6#include "headers.h"
7#include <openssl/ssl.h> 7#include <openssl/ssl.h>
8#include <openssl/err.h> 8#include <openssl/err.h>
9// We link libsecp256k1 statically (without this the header assumes DLL import) 9// We link libsecp256k1 statically (without this the header assumes DLL import).
10// Both makefiles also pass -DSECP256K1_STATIC; the guard keeps that from
11// warning while leaving this file correct if built without the flag.
12#ifndef SECP256K1_STATIC
10#define SECP256K1_STATIC 13#define SECP256K1_STATIC
14#endif
11#include <secp256k1.h> 15#include <secp256k1.h>
12#include <secp256k1_schnorrsig.h> 16#include <secp256k1_schnorrsig.h>
13#include <secp256k1_extrakeys.h> 17#include <secp256k1_extrakeys.h>
diff --git a/src/util.cpp b/src/util.cpp
index e4e314f..d52ca88 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -180,7 +180,7 @@ void RandAddSeed(bool fPerfmon)
180 RAND_add(&hash, sizeof(hash), min(nSize/500.0, (double)sizeof(hash))); 180 RAND_add(&hash, sizeof(hash), min(nSize/500.0, (double)sizeof(hash)));
181 hash = 0; 181 hash = 0;
182 memset(pdata, 0, nSize); 182 memset(pdata, 0, nSize);
183 printf("RandAddSeed() got %d bytes of performance data\n", nSize); 183 printf("RandAddSeed() got %lu bytes of performance data\n", nSize);
184 } 184 }
185 } 185 }
186#else 186#else
@@ -472,7 +472,7 @@ void AddTimeData(unsigned int ip, int64 nTime)
472 if (vTimeOffsets.empty()) 472 if (vTimeOffsets.empty())
473 vTimeOffsets.push_back(0); 473 vTimeOffsets.push_back(0);
474 vTimeOffsets.push_back(nOffsetSample); 474 vTimeOffsets.push_back(nOffsetSample);
475 printf("Added time data, samples %d, ip %08x, offset %+lld (%+lld minutes)\n", vTimeOffsets.size(), ip, vTimeOffsets.back(), vTimeOffsets.back()/60); 475 printf("Added time data, samples %d, ip %08x, offset %+lld (%+lld minutes)\n", (int)vTimeOffsets.size(), ip, vTimeOffsets.back(), vTimeOffsets.back()/60);
476 if (vTimeOffsets.size() >= 5 && vTimeOffsets.size() % 2 == 1) 476 if (vTimeOffsets.size() >= 5 && vTimeOffsets.size() % 2 == 1)
477 { 477 {
478 sort(vTimeOffsets.begin(), vTimeOffsets.end()); 478 sort(vTimeOffsets.begin(), vTimeOffsets.end());
diff --git a/src/util.h b/src/util.h
index b583248..797ed27 100644
--- a/src/util.h
+++ b/src/util.h
@@ -14,7 +14,9 @@ typedef unsigned long long uint64;
14#define for if (false) ; else for 14#define for if (false) ; else for
15#endif 15#endif
16 16
17#ifndef _MSC_VER 17// MinGW already provides __forceinline for MSVC compatibility, so defining it
18// unconditionally warned ten times per build once -w was removed.
19#if !defined(_MSC_VER) && !defined(__forceinline)
18#define __forceinline inline 20#define __forceinline inline
19#endif 21#endif
20 22
@@ -40,17 +42,17 @@ typedef unsigned long long uint64;
40#endif 42#endif
41#define snprintf my_snprintf 43#define snprintf my_snprintf
42 44
45// One definition for every target. The MSVCRT branch that used to sit here
46// produced "I64d", which is the specifier that segfaults on glibc (issue #5) --
47// and it was pointless besides: this build targets UCRT, which accepts the C99
48// forms, and the rest of the tree already prints int64 with %lld on Windows.
49// Keeping one spelling everywhere is also what lets the format attributes below
50// use a single archetype, so a bad specifier fails the build on both platforms.
43#ifndef PRId64 51#ifndef PRId64
44#if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__MSVCRT__)
45#define PRId64 "I64d"
46#define PRIu64 "I64u"
47#define PRIx64 "I64x"
48#else
49#define PRId64 "lld" 52#define PRId64 "lld"
50#define PRIu64 "llu" 53#define PRIu64 "llu"
51#define PRIx64 "llx" 54#define PRIx64 "llx"
52#endif 55#endif
53#endif
54 56
55// This is needed because the foreach macro can't get over the comma in pair<t1, t2> 57// This is needed because the foreach macro can't get over the comma in pair<t1, t2>
56#define PAIRTYPE(t1, t2) pair<t1, t2> 58#define PAIRTYPE(t1, t2) pair<t1, t2>
@@ -102,10 +104,30 @@ void LogPrintDedup(const char* category, const char* file, int line, const std::
102#define LogPrint(category, ...) \ 104#define LogPrint(category, ...) \
103 do { if (LogAcceptsCategory(category)) LogPrintDedup((category), __FILE__, __LINE__, strprintf(__VA_ARGS__)); } while (0) 105 do { if (LogAcceptsCategory(category)) LogPrintDedup((category), __FILE__, __LINE__, strprintf(__VA_ARGS__)); } while (0)
104 106
107// Every format string in this project goes through one of the four functions
108// below -- `printf` itself is remapped to OutputDebugStringF just above, and
109// snprintf to my_snprintf. None of them carried a format attribute, and GCC
110// only checks format strings for functions it knows are printf-like, so
111// -Wformat had nothing to inspect and the whole tree was unchecked. That is
112// how `%I64d` (MSVC-only, and a segfault on glibc) survived in
113// CTxOut::ToString until it crashed a node mid-sync -- see issue #5.
114//
115// gnu_printf on both platforms, deliberately. MinGW's other option, ms_printf,
116// models the legacy msvcrt: it rejects %zu, which this tree uses and UCRT
117// supports, and it accepts %I64d, which is the specifier that crashes on glibc.
118// It would give false positives where the code is right and stay silent where it
119// is dangerous. gnu_printf describes UCRT's C99 behaviour accurately, so one
120// archetype covers both builds and a bad specifier fails either one.
121#if defined(__GNUC__)
122#define BF_FORMAT(fmt, args) __attribute__((format(gnu_printf, fmt, args)))
123#else
124#define BF_FORMAT(fmt, args)
125#endif
126
105void RandAddSeed(bool fPerfmon=false); 127void RandAddSeed(bool fPerfmon=false);
106int my_snprintf(char* buffer, size_t limit, const char* format, ...); 128int my_snprintf(char* buffer, size_t limit, const char* format, ...) BF_FORMAT(3, 4);
107string strprintf(const char* format, ...); 129string strprintf(const char* format, ...) BF_FORMAT(1, 2);
108bool error(const char* format, ...); 130bool error(const char* format, ...) BF_FORMAT(1, 2);
109void PrintException(std::exception* pex, const char* pszThread); 131void PrintException(std::exception* pex, const char* pszThread);
110void ParseString(const string& str, char c, vector<string>& v); 132void ParseString(const string& str, char c, vector<string>& v);
111string FormatMoney(int64 n, bool fPlus=false); 133string FormatMoney(int64 n, bool fPlus=false);
@@ -266,6 +288,8 @@ void PrintHex(const T pbegin, const T pend, const char* pszFormat="%s", bool fSp
266// Forward declaration -- defined in main.cpp 288// Forward declaration -- defined in main.cpp
267string GetAppDir(); 289string GetAppDir();
268 290
291inline int OutputDebugStringF(const char* pszFormat, ...) BF_FORMAT(1, 2);
292
269inline int OutputDebugStringF(const char* pszFormat, ...) 293inline int OutputDebugStringF(const char* pszFormat, ...)
270{ 294{
271#if 1 // debug.log always enabled 295#if 1 // debug.log always enabled