Summary
On any target whose stdint.h defines int32_t as long int rather than int, six call sites in quickjs.c pass an int * where an int32_t * is expected (and one in the reverse direction). Under GCC 14+ this is an error by default, so quickjs.c does not build at all. The most widely affected platform is ESP-IDF, i.e. every ESP32 board.
Both types are 32-bit signed, so there is no behavioural bug and no security impact. It is purely a build blocker, but a total one: the file does not compile, and the workaround is to suppress a real diagnostic across the whole engine.
Why it happens
int32_t and int are distinct, incompatible types whenever int32_t is a typedef for long int, even though both are 32-bit signed (C11 6.2.5p4, 6.7.6.1p2). Passing int * for a parameter of type int32_t * is a constraint violation that requires a diagnostic.
GCC's bare-metal newlib configuration picks long int for int32_t whenever long is 32 bits (gcc/config/newlib-stdint.h: #define STDINT_LONG32 (LONG_TYPE_SIZE == 32)). Hosted libcs (glibc, musl, bionic, Apple, mingw-w64) all override this and pin int32_t to int, which is why every target in CI is unaffected and why this has gone unnoticed.
Espressif made exactly this change in ESP-IDF v5.0 and documents it explicitly:
The types int32_t and uint32_t have been changed from the previous int and unsigned int to long and unsigned long respectively for the Xtensa compiler. [...] In common, int32_t and int, as well as uint32_t and unsigned int, are different types.
ESP-IDF Migration Guide 5.0, "int32_t and uint32_t for Xtensa Compiler"
Note that the ESP-IDF table in that section shows RISC-V was already long before the change, so this affects all ESP-IDF chips (ESP32, S2, S3, C3, C6, H2, P4), not just Xtensa.
Separately, GCC 14 promoted -Wincompatible-pointer-types from a warning to an error by default in C99 and later dialects. That is what turns this from noise into a hard failure. ESP-IDF 5.4/5.5 ship GCC 14.2.0 and 6.0 ships GCC 15.1.0, so current and future ESP-IDF releases are all on the error side of that change.
ESP-IDF also compiles every component, including third-party ones, with -Wall -Werror at -std=gnu23 by default (tools/cmake/build.cmake, __build_set_default_build_specifications), so there is no "just don't use -Werror" escape hatch short of per-file suppression.
Diagnostics
Six errors in quickjs.c, across five variables:
| Site |
Variable |
Callee |
Direction |
quickjs.c:8118, quickjs.c:8128 (find_line_num) |
v |
get_sleb128(int32_t *pval, ...) |
int * → int32_t * |
quickjs.c:46386 (js_parseInt) |
radix |
JS_ToInt32(..., int32_t *pres, ...) |
int * → int32_t * |
quickjs.c:55792 (remainingElementsCount_add) |
remainingElementsCount |
JS_ToInt32Free(..., int32_t *pres, ...) |
int * → int32_t * |
quickjs.c:55820 (js_promise_all_resolve_element) |
index |
JS_ToInt32(..., int32_t *pres, ...) |
int * → int32_t * |
quickjs.c:62393 (js_atomics_notify) |
count |
JS_ToInt32Clamp(..., int *pres, ...) |
int32_t * → int * |
The last one is the mirror image: count is declared int32_t while JS_ToInt32Clamp takes int *. 18 of the 19 JS_ToInt32Clamp call sites already pass an int, so count is the outlier rather than the signature.
Reproduction without ESP32 hardware
You do not need an Xtensa toolchain. The stdint model can be emulated on an ordinary x86-64 Linux box with a -include shim, which keeps libc's own typedefs self-consistent and then shadows the name for everything compiled afterwards:
/* xtensa_shim.h */
#include <stdint.h>
#include <inttypes.h>
typedef long int xtensa_int32_t;
#define int32_t xtensa_int32_t
$ gcc-14 -std=gnu11 -c quickjs.c -o /dev/null -I. -include xtensa_shim.h
Verbatim output (GCC 14.4.0; xtensa_int32_t is the shim typedef standing in for newlib's int32_t):
quickjs.c:8118:31: error: passing argument 1 of 'get_sleb128' from incompatible pointer type [-Wincompatible-pointer-types]
8118 | ret = get_sleb128(&v, p, p_end);
| ^~
| |
| int *
quickjs.c:8080:33: note: expected 'xtensa_int32_t *' {aka 'long int *'} but argument is of type 'int *'
8080 | static int get_sleb128(int32_t *pval, const uint8_t *buf,
quickjs.c:46386:25: error: passing argument 2 of 'JS_ToInt32' from incompatible pointer type [-Wincompatible-pointer-types]
46386 | if (JS_ToInt32(ctx, &radix, argv[1])) {
| ^~~~~~
| |
| int *
quickjs.c:14388:41: note: expected 'xtensa_int32_t *' {aka 'long int *'} but argument is of type 'int *'
14388 | int JS_ToInt32(JSContext *ctx, int32_t *pres, JSValueConst val)
quickjs.c:62393:34: error: passing argument 2 of 'JS_ToInt32Clamp' from incompatible pointer type [-Wincompatible-pointer-types]
62393 | if (JS_ToInt32Clamp(ctx, &count, argv[2], 0, INT32_MAX, 0))
| ^~~~~~
| |
| xtensa_int32_t * {aka long int *}
quickjs.c:14189:49: note: expected 'int *' but argument is of type 'xtensa_int32_t *' {aka 'long int *'}
14189 | static int JS_ToInt32Clamp(JSContext *ctx, int *pres, JSValueConst val,
(Elided: the two remaining errors at quickjs.c:55792 and quickjs.c:55820, same shape.)
Example of the shape of the fix
Each site is a one-word change to a local declaration; no signature, no cast, no logic change. For instance in js_parseInt:
- int radix, flags;
+ int32_t radix;
+ int flags;
radix is only ever compared against small signed literals and passed to js_atof, so widening its declared type to int32_t is a no-op on every platform. The js_atomics_notify site goes the other way (int32_t count → int count), matching what the other 18 JS_ToInt32Clamp callers already do.
I verified on x86-64 that the resulting -O2 -DNDEBUG object file is byte-identical to the unpatched one (same MD5), and that the native -Wall -Wextra warning count is unchanged at 672. So this is a pure type-correctness fix with no codegen impact on existing platforms.
Evidence this bites real users
Espressif's own official quickjs-ng component in esp-iot-solution suppresses this exact diagnostic on the engine sources:
# components/quickjs-ng/CMakeLists.txt
set(_qjs_vendor_warnings
-Wno-pointer-sign
-Wno-incompatible-pointer-types
-Wno-cast-function-type
...
)
# Apply vendor warnings only to upstream QuickJS files (not port/stub.c)
set_source_files_properties(
"${CMAKE_CURRENT_SOURCE_DIR}/quickjs-ng/quickjs.c"
PROPERTIES COMPILE_OPTIONS "${_qjs_vendor_warnings};-Wno-maybe-uninitialized"
)
(espressif/esp-iot-solution, components/quickjs-ng/CMakeLists.txt)
Blanket-suppressing -Wincompatible-pointer-types on the whole of quickjs.c is a fairly heavy hammer for what is five local variable declarations, and it also masks any future genuine pointer-type mistake in the engine.
Scope beyond quickjs.c
For completeness, the same class of mismatch exists outside the core engine and is not covered by the five sites above:
quickjs-libc.c: 28 sites (mostly int fd; passed to JS_ToInt32), including two that only compile under _WIN32
examples/fib.c: 1 site; examples/point.c: 3 sites (two of which are &s->x / &s->y on int struct members)
api-test.c: 1 site
Whether those are in scope for this issue is your call. quickjs-libc.c matters for anyone embedding the standard libc bindings; the examples and tests matter less.
Environment
- quickjs-ng
master
- GCC 14.4.0 (reproduction), matching the GCC 14.2.0 in ESP-IDF 5.4/5.5
-std=gnu11 (repro) / -std=gnu23 (ESP-IDF default); fails on any C99+ dialect, warns only under gnu89
- Workaround:
-Wno-error=incompatible-pointer-types
References
Summary
On any target whose
stdint.hdefinesint32_taslong intrather thanint, six call sites inquickjs.cpass anint *where anint32_t *is expected (and one in the reverse direction). Under GCC 14+ this is an error by default, soquickjs.cdoes not build at all. The most widely affected platform is ESP-IDF, i.e. every ESP32 board.Both types are 32-bit signed, so there is no behavioural bug and no security impact. It is purely a build blocker, but a total one: the file does not compile, and the workaround is to suppress a real diagnostic across the whole engine.
Why it happens
int32_tandintare distinct, incompatible types wheneverint32_tis a typedef forlong int, even though both are 32-bit signed (C11 6.2.5p4, 6.7.6.1p2). Passingint *for a parameter of typeint32_t *is a constraint violation that requires a diagnostic.GCC's bare-metal newlib configuration picks
long intforint32_twheneverlongis 32 bits (gcc/config/newlib-stdint.h:#define STDINT_LONG32 (LONG_TYPE_SIZE == 32)). Hosted libcs (glibc, musl, bionic, Apple, mingw-w64) all override this and pinint32_ttoint, which is why every target in CI is unaffected and why this has gone unnoticed.Espressif made exactly this change in ESP-IDF v5.0 and documents it explicitly:
Note that the ESP-IDF table in that section shows RISC-V was already
longbefore the change, so this affects all ESP-IDF chips (ESP32, S2, S3, C3, C6, H2, P4), not just Xtensa.Separately, GCC 14 promoted
-Wincompatible-pointer-typesfrom a warning to an error by default in C99 and later dialects. That is what turns this from noise into a hard failure. ESP-IDF 5.4/5.5 ship GCC 14.2.0 and 6.0 ships GCC 15.1.0, so current and future ESP-IDF releases are all on the error side of that change.ESP-IDF also compiles every component, including third-party ones, with
-Wall -Werrorat-std=gnu23by default (tools/cmake/build.cmake,__build_set_default_build_specifications), so there is no "just don't use-Werror" escape hatch short of per-file suppression.Diagnostics
Six errors in
quickjs.c, across five variables:quickjs.c:8118,quickjs.c:8128(find_line_num)vget_sleb128(int32_t *pval, ...)int *→int32_t *quickjs.c:46386(js_parseInt)radixJS_ToInt32(..., int32_t *pres, ...)int *→int32_t *quickjs.c:55792(remainingElementsCount_add)remainingElementsCountJS_ToInt32Free(..., int32_t *pres, ...)int *→int32_t *quickjs.c:55820(js_promise_all_resolve_element)indexJS_ToInt32(..., int32_t *pres, ...)int *→int32_t *quickjs.c:62393(js_atomics_notify)countJS_ToInt32Clamp(..., int *pres, ...)int32_t *→int *The last one is the mirror image:
countis declaredint32_twhileJS_ToInt32Clamptakesint *. 18 of the 19JS_ToInt32Clampcall sites already pass anint, socountis the outlier rather than the signature.Reproduction without ESP32 hardware
You do not need an Xtensa toolchain. The stdint model can be emulated on an ordinary x86-64 Linux box with a
-includeshim, which keeps libc's own typedefs self-consistent and then shadows the name for everything compiled afterwards:Verbatim output (GCC 14.4.0;
xtensa_int32_tis the shim typedef standing in for newlib'sint32_t):(Elided: the two remaining errors at
quickjs.c:55792andquickjs.c:55820, same shape.)Example of the shape of the fix
Each site is a one-word change to a local declaration; no signature, no cast, no logic change. For instance in
js_parseInt:radixis only ever compared against small signed literals and passed tojs_atof, so widening its declared type toint32_tis a no-op on every platform. Thejs_atomics_notifysite goes the other way (int32_t count→int count), matching what the other 18JS_ToInt32Clampcallers already do.I verified on x86-64 that the resulting
-O2 -DNDEBUGobject file is byte-identical to the unpatched one (same MD5), and that the native-Wall -Wextrawarning count is unchanged at 672. So this is a pure type-correctness fix with no codegen impact on existing platforms.Evidence this bites real users
Espressif's own official quickjs-ng component in
esp-iot-solutionsuppresses this exact diagnostic on the engine sources:(
espressif/esp-iot-solution,components/quickjs-ng/CMakeLists.txt)Blanket-suppressing
-Wincompatible-pointer-typeson the whole ofquickjs.cis a fairly heavy hammer for what is five local variable declarations, and it also masks any future genuine pointer-type mistake in the engine.Scope beyond quickjs.c
For completeness, the same class of mismatch exists outside the core engine and is not covered by the five sites above:
quickjs-libc.c: 28 sites (mostlyint fd;passed toJS_ToInt32), including two that only compile under_WIN32examples/fib.c: 1 site;examples/point.c: 3 sites (two of which are&s->x/&s->yonintstruct members)api-test.c: 1 siteWhether those are in scope for this issue is your call.
quickjs-libc.cmatters for anyone embedding the standard libc bindings; the examples and tests matter less.Environment
master-std=gnu11(repro) /-std=gnu23(ESP-IDF default); fails on any C99+ dialect, warns only undergnu89-Wno-error=incompatible-pointer-typesReferences
int32_tanduint32_tfor Xtensa Compiler"gcc/config/newlib-stdint.hespressif/esp-idf,tools/cmake/build.cmake(global-Wall -Werror,-std=gnu23)espressif/esp-iot-solution,components/quickjs-ng/CMakeLists.txt