Skip to content

quickjs.c fails to compile on targets where int32_t is long int (ESP-IDF / bare-metal newlib): -Wincompatible-pointer-types #1624

Description

@fantomc0der

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 countint 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions