Repository navigation
quickjs.c fails to compile on targets where int32_t is long int (ESP-IDF / bare-metal newlib): -Wincompatible-pointer-types #1624
Description
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_tanduint32_thave been changed from the previousintandunsigned inttolongandunsigned longrespectively for the Xtensa compiler. [...] In common,int32_tandint, as well asuint32_tandunsigned int, are different types.ESP-IDF Migration Guide 5.0, "
int32_tanduint32_tfor 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 (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 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 undergnu89- Workaround:
-Wno-error=incompatible-pointer-types
References
- ESP-IDF Migration Guide 5.0, "
int32_tanduint32_tfor Xtensa Compiler" - GCC 14 porting notes: incompatible pointer types are now errors
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- C11 6.2.5p4, 6.7.6.1p2 (type compatibility)
Activity
saghul commentedon Aug 1, 2026
Would you like send a PR?
Fix -Wincompatible-pointer-types warnings (#1657)