From 760cb16da2fce632de0d210d397e51d0425729c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Sat, 3 Oct 2026 16:45:48 -0400 Subject: [PATCH 1/5] CI: Check positional printf arguments on Windows --- .github/scripts/positional_printf_check.c | 100 ++++++++++++++++++ .github/workflows/positional_printf_check.yml | 53 ++++++++++ 2 files changed, 153 insertions(+) create mode 100644 .github/scripts/positional_printf_check.c create mode 100644 .github/workflows/positional_printf_check.yml diff --git a/.github/scripts/positional_printf_check.c b/.github/scripts/positional_printf_check.c new file mode 100644 index 00000000000..7239535da6c --- /dev/null +++ b/.github/scripts/positional_printf_check.c @@ -0,0 +1,100 @@ +/* Throwaway check: do POSIX positional printf arguments work here? + * Results are printed as PASS/FAIL lines; the exit status is always 0. */ +#include +#include +#include + +#define BUFSZ 128 + +static void report(const char *func, const char *fmt, const char *result, + const char *expected) +{ + printf("%s: func=%s format=\"%s\" result=\"%s\" expected=\"%s\"\n", + strcmp(result, expected) == 0 ? "PASS" : "FAIL", func, fmt, result, + expected); +} + +/* Mimics G_vasprintf(): formats through vsnprintf with a va_list. */ +static int wrap_vsnprintf(char *buf, size_t n, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = vsnprintf(buf, n, fmt, ap); + va_end(ap); + return ret; +} + +#ifdef _MSC_VER +static int wrap_vsprintf_p(char *buf, size_t n, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = _vsprintf_p(buf, n, fmt, ap); + va_end(ap); + return ret; +} +#endif + +int main(void) +{ + char buf[BUFSZ]; + const char *fmt_s = "%2$s %1$s"; + const char *fmt_m = "%2$d %1$s"; + +#ifdef _MSC_VER + printf("compiler: MSVC _MSC_VER=%d\n", _MSC_VER); +#endif +#ifdef __GNUC__ + printf("compiler: GCC %d.%d\n", __GNUC__, __GNUC_MINOR__); +#endif +#ifdef _UCRT + printf("runtime: _UCRT defined\n"); +#else + printf("runtime: _UCRT not defined\n"); +#endif +#ifdef __USE_MINGW_ANSI_STDIO + printf("__USE_MINGW_ANSI_STDIO=%d\n", __USE_MINGW_ANSI_STDIO); +#else + printf("__USE_MINGW_ANSI_STDIO not defined\n"); +#endif + + memset(buf, 0, sizeof buf); + snprintf(buf, sizeof buf, fmt_s, "a", "b"); + report("snprintf", fmt_s, buf, "b a"); + + memset(buf, 0, sizeof buf); + snprintf(buf, sizeof buf, fmt_m, "x", 42); + report("snprintf", fmt_m, buf, "42 x"); + + memset(buf, 0, sizeof buf); + wrap_vsnprintf(buf, sizeof buf, fmt_s, "a", "b"); + report("vsnprintf(wrapper)", fmt_s, buf, "b a"); + + memset(buf, 0, sizeof buf); + wrap_vsnprintf(buf, sizeof buf, fmt_m, "x", 42); + report("vsnprintf(wrapper)", fmt_m, buf, "42 x"); + +#ifdef _MSC_VER + memset(buf, 0, sizeof buf); + _snprintf_p(buf, sizeof buf, fmt_s, "a", "b"); + report("_snprintf_p", fmt_s, buf, "b a"); + + memset(buf, 0, sizeof buf); + _snprintf_p(buf, sizeof buf, fmt_m, "x", 42); + report("_snprintf_p", fmt_m, buf, "42 x"); + + memset(buf, 0, sizeof buf); + wrap_vsprintf_p(buf, sizeof buf, fmt_s, "a", "b"); + report("_vsprintf_p(wrapper)", fmt_s, buf, "b a"); + + memset(buf, 0, sizeof buf); + wrap_vsprintf_p(buf, sizeof buf, fmt_m, "x", 42); + report("_vsprintf_p(wrapper)", fmt_m, buf, "42 x"); +#endif + + return 0; +} diff --git a/.github/workflows/positional_printf_check.yml b/.github/workflows/positional_printf_check.yml new file mode 100644 index 00000000000..9f3041d101f --- /dev/null +++ b/.github/workflows/positional_printf_check.yml @@ -0,0 +1,53 @@ +--- +name: Positional printf check + +# Throwaway check of POSIX positional printf arguments on Windows toolchains. +on: + workflow_dispatch: + pull_request: + paths: + - .github/workflows/positional_printf_check.yml + - .github/scripts/positional_printf_check.c + +permissions: {} + +jobs: + msvc: + name: MSVC + runs-on: windows-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: ilammy/msvc-dev-cmd@a102174a2b586eec2ea151a69e6fd14404a8ce7c # v1.13.0 + - name: Compile + shell: cmd + run: cl /nologo /W3 .github\scripts\positional_printf_check.c /Fe:check.exe + - name: Run + shell: cmd + run: check.exe + + mingw: + name: MinGW64 (MSYS2) + runs-on: windows-latest + defaults: + run: + shell: msys2 {0} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2.33.0 + with: + msystem: MINGW64 + update: false + install: >- + mingw-w64-x86_64-gcc + - name: Compile + run: | + gcc -Wall -o check_default.exe .github/scripts/positional_printf_check.c + gcc -Wall -D__USE_MINGW_ANSI_STDIO=1 -o check_ansi.exe .github/scripts/positional_printf_check.c + - name: Run without __USE_MINGW_ANSI_STDIO + run: ./check_default.exe + - name: Run with __USE_MINGW_ANSI_STDIO=1 + run: ./check_ansi.exe From 664c5fe288c2a06a035650cf938a4fb40da08378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Sat, 3 Oct 2026 16:54:04 -0400 Subject: [PATCH 2/5] CI: Use _sprintf_p in positional printf check --- .github/scripts/positional_printf_check.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/scripts/positional_printf_check.c b/.github/scripts/positional_printf_check.c index 7239535da6c..660ef6ef2bb 100644 --- a/.github/scripts/positional_printf_check.c +++ b/.github/scripts/positional_printf_check.c @@ -80,12 +80,12 @@ int main(void) #ifdef _MSC_VER memset(buf, 0, sizeof buf); - _snprintf_p(buf, sizeof buf, fmt_s, "a", "b"); - report("_snprintf_p", fmt_s, buf, "b a"); + _sprintf_p(buf, sizeof buf, fmt_s, "a", "b"); + report("_sprintf_p", fmt_s, buf, "b a"); memset(buf, 0, sizeof buf); - _snprintf_p(buf, sizeof buf, fmt_m, "x", 42); - report("_snprintf_p", fmt_m, buf, "42 x"); + _sprintf_p(buf, sizeof buf, fmt_m, "x", 42); + report("_sprintf_p", fmt_m, buf, "42 x"); memset(buf, 0, sizeof buf); wrap_vsprintf_p(buf, sizeof buf, fmt_s, "a", "b"); From 7c696cc3864ff26fa0810a14507ffa631ca152ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Sat, 3 Oct 2026 17:01:55 -0400 Subject: [PATCH 3/5] CI: Also test disabled MinGW ANSI stdio in printf check --- .github/workflows/positional_printf_check.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/positional_printf_check.yml b/.github/workflows/positional_printf_check.yml index 9f3041d101f..1dfeb35c3a8 100644 --- a/.github/workflows/positional_printf_check.yml +++ b/.github/workflows/positional_printf_check.yml @@ -47,7 +47,10 @@ jobs: run: | gcc -Wall -o check_default.exe .github/scripts/positional_printf_check.c gcc -Wall -D__USE_MINGW_ANSI_STDIO=1 -o check_ansi.exe .github/scripts/positional_printf_check.c - - name: Run without __USE_MINGW_ANSI_STDIO + gcc -Wall -D__USE_MINGW_ANSI_STDIO=0 -o check_msvcrt.exe .github/scripts/positional_printf_check.c + - name: Run with default __USE_MINGW_ANSI_STDIO run: ./check_default.exe - name: Run with __USE_MINGW_ANSI_STDIO=1 run: ./check_ansi.exe + - name: Run with __USE_MINGW_ANSI_STDIO=0 + run: ./check_msvcrt.exe From cfcc61a324182a9165e6623b784ad059d9ec529c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Sat, 3 Oct 2026 17:14:06 -0400 Subject: [PATCH 4/5] CI: Test libintl for positional printf arguments --- .github/scripts/positional_printf_check.c | 137 +++++++++++------- .github/workflows/positional_printf_check.yml | 33 +++++ 2 files changed, 116 insertions(+), 54 deletions(-) diff --git a/.github/scripts/positional_printf_check.c b/.github/scripts/positional_printf_check.c index 660ef6ef2bb..2f2b1cc5c86 100644 --- a/.github/scripts/positional_printf_check.c +++ b/.github/scripts/positional_printf_check.c @@ -1,11 +1,27 @@ /* Throwaway check: do POSIX positional printf arguments work here? - * Results are printed as PASS/FAIL lines; the exit status is always 0. */ + * Results are printed as PASS/FAIL lines; the exit status is always 0. + * + * Optional defines: + * USE_LIBINTL include first and report its redirects + * TEST_LIBINTL_DIRECT also call libintl_snprintf()/libintl_vsnprintf() + */ +#ifdef USE_LIBINTL +#include +#endif +#include #include +#include #include #include #define BUFSZ 128 +#ifdef TEST_LIBINTL_DIRECT +/* Declared here in case libintl.h only declares them conditionally. */ +extern int libintl_snprintf(char *, size_t, const char *, ...); +extern int libintl_vsnprintf(char *, size_t, const char *, va_list); +#endif + static void report(const char *func, const char *fmt, const char *result, const char *expected) { @@ -14,36 +30,47 @@ static void report(const char *func, const char *fmt, const char *result, expected); } -/* Mimics G_vasprintf(): formats through vsnprintf with a va_list. */ -static int wrap_vsnprintf(char *buf, size_t n, const char *fmt, ...) -{ - va_list ap; - int ret; - - va_start(ap, fmt); - ret = vsnprintf(buf, n, fmt, ap); - va_end(ap); - return ret; -} - +/* Mimics G_vasprintf(): formats through a va_list function. */ +#define DEFINE_WRAPPER(name, vfunc) \ + static int name(char *buf, size_t n, const char *fmt, ...) \ + { \ + va_list ap; \ + int ret; \ + \ + va_start(ap, fmt); \ + ret = vfunc(buf, n, fmt, ap); \ + va_end(ap); \ + return ret; \ + } + +DEFINE_WRAPPER(wrap_vsnprintf, vsnprintf) +#ifdef TEST_LIBINTL_DIRECT +DEFINE_WRAPPER(wrap_libintl_vsnprintf, libintl_vsnprintf) +#endif #ifdef _MSC_VER -static int wrap_vsprintf_p(char *buf, size_t n, const char *fmt, ...) -{ - va_list ap; - int ret; - - va_start(ap, fmt); - ret = _vsprintf_p(buf, n, fmt, ap); - va_end(ap); - return ret; -} +DEFINE_WRAPPER(wrap_vsprintf_p, _vsprintf_p) #endif +/* Runs all formats through fn(buf, size, fmt, args...). */ +#define RUN_CASES(label, fn) \ + do { \ + memset(buf, 0, sizeof buf); \ + fn(buf, sizeof buf, "%2$s %1$s", "a", "b"); \ + report(label, "%2$s %1$s", buf, "b a"); \ + memset(buf, 0, sizeof buf); \ + fn(buf, sizeof buf, "%2$d %1$s", "x", 42); \ + report(label, "%2$d %1$s", buf, "42 x"); \ + memset(buf, 0, sizeof buf); \ + fn(buf, sizeof buf, "%2$lu %1$llu", 5ULL, 7UL); \ + report(label, "%2$lu %1$llu", buf, "7 5"); \ + memset(buf, 0, sizeof buf); \ + fn(buf, sizeof buf, "%2$" PRIu64 " %1$s", "x", (uint64_t)123); \ + report(label, "%2$" PRIu64 " %1$s", buf, "123 x"); \ + } while (0) + int main(void) { char buf[BUFSZ]; - const char *fmt_s = "%2$s %1$s"; - const char *fmt_m = "%2$d %1$s"; #ifdef _MSC_VER printf("compiler: MSVC _MSC_VER=%d\n", _MSC_VER); @@ -60,40 +87,42 @@ int main(void) printf("__USE_MINGW_ANSI_STDIO=%d\n", __USE_MINGW_ANSI_STDIO); #else printf("__USE_MINGW_ANSI_STDIO not defined\n"); +#endif +#ifdef USE_LIBINTL + printf("libintl.h included first\n"); +#ifdef snprintf + printf("snprintf IS a macro after libintl.h\n"); +#else + printf("snprintf is NOT a macro after libintl.h\n"); +#endif +#ifdef vsnprintf + printf("vsnprintf IS a macro after libintl.h\n"); +#else + printf("vsnprintf is NOT a macro after libintl.h\n"); +#endif +#ifdef _INTL_REDIRECT_MACROS + printf("_INTL_REDIRECT_MACROS is defined\n"); +#else + printf("_INTL_REDIRECT_MACROS is not defined\n"); +#endif +#ifdef _INTL_NO_REDIRECT_PRINTF + printf("_INTL_NO_REDIRECT_PRINTF is defined\n"); +#else + printf("_INTL_NO_REDIRECT_PRINTF is not defined\n"); +#endif #endif - memset(buf, 0, sizeof buf); - snprintf(buf, sizeof buf, fmt_s, "a", "b"); - report("snprintf", fmt_s, buf, "b a"); - - memset(buf, 0, sizeof buf); - snprintf(buf, sizeof buf, fmt_m, "x", 42); - report("snprintf", fmt_m, buf, "42 x"); - - memset(buf, 0, sizeof buf); - wrap_vsnprintf(buf, sizeof buf, fmt_s, "a", "b"); - report("vsnprintf(wrapper)", fmt_s, buf, "b a"); + RUN_CASES("snprintf", snprintf); + RUN_CASES("vsnprintf(wrapper)", wrap_vsnprintf); - memset(buf, 0, sizeof buf); - wrap_vsnprintf(buf, sizeof buf, fmt_m, "x", 42); - report("vsnprintf(wrapper)", fmt_m, buf, "42 x"); +#ifdef TEST_LIBINTL_DIRECT + RUN_CASES("libintl_snprintf", libintl_snprintf); + RUN_CASES("libintl_vsnprintf(wrapper)", wrap_libintl_vsnprintf); +#endif #ifdef _MSC_VER - memset(buf, 0, sizeof buf); - _sprintf_p(buf, sizeof buf, fmt_s, "a", "b"); - report("_sprintf_p", fmt_s, buf, "b a"); - - memset(buf, 0, sizeof buf); - _sprintf_p(buf, sizeof buf, fmt_m, "x", 42); - report("_sprintf_p", fmt_m, buf, "42 x"); - - memset(buf, 0, sizeof buf); - wrap_vsprintf_p(buf, sizeof buf, fmt_s, "a", "b"); - report("_vsprintf_p(wrapper)", fmt_s, buf, "b a"); - - memset(buf, 0, sizeof buf); - wrap_vsprintf_p(buf, sizeof buf, fmt_m, "x", 42); - report("_vsprintf_p(wrapper)", fmt_m, buf, "42 x"); + RUN_CASES("_sprintf_p", _sprintf_p); + RUN_CASES("_vsprintf_p(wrapper)", wrap_vsprintf_p); #endif return 0; diff --git a/.github/workflows/positional_printf_check.yml b/.github/workflows/positional_printf_check.yml index 1dfeb35c3a8..275176a319d 100644 --- a/.github/workflows/positional_printf_check.yml +++ b/.github/workflows/positional_printf_check.yml @@ -26,6 +26,27 @@ jobs: - name: Run shell: cmd run: check.exe + - name: Install libintl with vcpkg + id: vcpkg + continue-on-error: true + shell: cmd + run: vcpkg install gettext-libintl:x64-windows + - name: Compile with libintl + if: steps.vcpkg.outcome == 'success' + continue-on-error: true + shell: cmd + run: | + set VPREFIX=%VCPKG_INSTALLATION_ROOT%\installed\x64-windows + dir %VPREFIX%\include\libintl.h %VPREFIX%\lib\*intl* %VPREFIX%\bin\*intl* + findstr /n /i "define.*printf _INTL_REDIRECT" %VPREFIX%\include\libintl.h + cl /nologo /W3 /DUSE_LIBINTL /DTEST_LIBINTL_DIRECT /I%VPREFIX%\include .github\scripts\positional_printf_check.c /Fe:check_intl.exe /link /LIBPATH:%VPREFIX%\lib intl.lib + - name: Run with libintl + if: steps.vcpkg.outcome == 'success' + continue-on-error: true + shell: cmd + run: | + set PATH=%VCPKG_INSTALLATION_ROOT%\installed\x64-windows\bin;%PATH% + check_intl.exe mingw: name: MinGW64 (MSYS2) @@ -43,8 +64,12 @@ jobs: update: false install: >- mingw-w64-x86_64-gcc + mingw-w64-x86_64-gettext-runtime + mingw-w64-x86_64-gettext-tools - name: Compile run: | + gcc -Wall -DUSE_LIBINTL -DTEST_LIBINTL_DIRECT -o check_intl.exe .github/scripts/positional_printf_check.c -lintl + gcc -Wall -D__USE_MINGW_ANSI_STDIO=0 -DUSE_LIBINTL -DTEST_LIBINTL_DIRECT -o check_intl_msvcrt.exe .github/scripts/positional_printf_check.c -lintl gcc -Wall -o check_default.exe .github/scripts/positional_printf_check.c gcc -Wall -D__USE_MINGW_ANSI_STDIO=1 -o check_ansi.exe .github/scripts/positional_printf_check.c gcc -Wall -D__USE_MINGW_ANSI_STDIO=0 -o check_msvcrt.exe .github/scripts/positional_printf_check.c @@ -54,3 +79,11 @@ jobs: run: ./check_ansi.exe - name: Run with __USE_MINGW_ANSI_STDIO=0 run: ./check_msvcrt.exe + - name: Show libintl.h printf redirects + run: | + ls -l /mingw64/include/libintl.h + grep -n -i "define.*printf\|_INTL_REDIRECT\|_INTL_NO_REDIRECT" /mingw64/include/libintl.h + - name: Run with libintl.h + run: ./check_intl.exe + - name: Run with libintl.h and __USE_MINGW_ANSI_STDIO=0 + run: ./check_intl_msvcrt.exe From 8c2fbeff1efc19e82b97121ea1ac97291b717c7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Sat, 3 Oct 2026 17:25:34 -0400 Subject: [PATCH 5/5] CI: Inspect vcpkg libintl exports in printf check --- .github/workflows/positional_printf_check.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/positional_printf_check.yml b/.github/workflows/positional_printf_check.yml index 275176a319d..5a251dcde45 100644 --- a/.github/workflows/positional_printf_check.yml +++ b/.github/workflows/positional_printf_check.yml @@ -39,7 +39,9 @@ jobs: set VPREFIX=%VCPKG_INSTALLATION_ROOT%\installed\x64-windows dir %VPREFIX%\include\libintl.h %VPREFIX%\lib\*intl* %VPREFIX%\bin\*intl* findstr /n /i "define.*printf _INTL_REDIRECT" %VPREFIX%\include\libintl.h - cl /nologo /W3 /DUSE_LIBINTL /DTEST_LIBINTL_DIRECT /I%VPREFIX%\include .github\scripts\positional_printf_check.c /Fe:check_intl.exe /link /LIBPATH:%VPREFIX%\lib intl.lib + dumpbin /exports %VPREFIX%\bin\intl-8.dll | findstr /i "printf" + cl /nologo /W3 /DUSE_LIBINTL /I%VPREFIX%\include .github\scripts\positional_printf_check.c /Fe:check_intl.exe /link /LIBPATH:%VPREFIX%\lib intl.lib + cl /nologo /W3 /DUSE_LIBINTL /DTEST_LIBINTL_DIRECT /I%VPREFIX%\include .github\scripts\positional_printf_check.c /Fe:check_intl_direct.exe /link /LIBPATH:%VPREFIX%\lib intl.lib - name: Run with libintl if: steps.vcpkg.outcome == 'success' continue-on-error: true