crashappsec / crashappsec/ncc

ncc exits 0 when a constexpr helper compile fails (silent miscompile in cross-compile flows)

Open
#2 0 comments 0 reactions 0 assignees View on GitHub
P3 xcompile
Dominant language
C
Stars
0
Forks
0
Avg merge
5d 4h
Merged PRs (30d)
1

Description

## Summary

When a `constexpr_eval`/`constexpr_min`/`constexpr_max`/`constexpr_strlen`/etc. helper subprocess fails to compile, `ncc` emits a diagnostic but exits with status `0` and produces no output binary. Build systems treat this as success.

## Reproducer

On macOS arm64 with `llvm-mingw` available for cross-builds, using the README's documented validation flow from PR #1:

\`\`\`
\$ build-host/ncc --target=x86_64-w64-windows-gnu \\
-o /tmp/ncc-win-constexpr.exe test/test_constexpr.c
ncc: 25:18: constexpr: helper compile failed with exit status 1:
/var/folders/.../ncc_ce_XXXXXX/src.c:1:10: fatal error: 'stdio.h' file not found
1 | #include
| ^~~~~~~~~
1 error generated.
\$ echo \$?
0
\$ ls /tmp/ncc-win-constexpr.exe
ls: /tmp/ncc-win-constexpr.exe: No such file or directory
\`\`\`

The exit status from ncc is \`0\`, no \`.exe\` was written, and \`make\`/\`ninja\`/CI wrappers will not notice.

## Why this matters

Two distinct problems compound here:

1. **Helper subprocess sysroot is not propagated for cross-targets.** When the host ncc runs with \`--target=x86_64-w64-windows-gnu\`, the spawned helper compile inherits the \`--target\` flag but not the include path, library path, or any flags set by the surrounding compiler config (\`x86_64-w64-windows-gnu.cfg\` from llvm-mingw, \`-isysroot\`, \`--sysroot=\`, \`-I\`, etc.). The user has no obvious way to ensure helpers find their headers. This is the proximate cause of the reproducer above.

2. **Helper-compile-failure path returns success.** Even setting (1) aside, a *failed* constexpr helper should fail the parent compile. Today \`xform_constexpr.c\` prints \`helper compile failed with exit status N\` and continues (looks like it falls through to the substitution path with whatever default value, then never re-checks status before emitting). The compile finishes, but no output is produced — ncc still exits 0.

(2) is the more dangerous one: any future scenario where a helper genuinely fails — bad headers, missing dependency, clang ICE — will silently produce no binary while the build reports green.

## Suggested fix

- In \`xform_constexpr.c\`, treat any non-zero helper exit as fatal: emit a diagnostic and exit non-zero from the top-level driver. (Or set an error flag observed by \`ncc.c\`'s exit path.)
- For (1): forward the parent's \`-I\`, \`-isysroot\`, \`--sysroot\`, and any \`-L\` / library search flags into the helper invocation. Possibly also forward the parent's full argv subset that survives into clang, modulo \`-c\`/\`-o\` which the helper sets itself.

Both should land before the Windows port (#1) merges, since the README's own validation step documents the failing command but the user has no way to notice today that it's not actually working.

## Environment

- Host: macOS 25.3.0 arm64, Homebrew clang 21.1.8 as build-host CC
- Cross toolchain: llvm-mingw 20260421 (clang 22.1.4 + lld) targeting \`x86_64-w64-windows-gnu\`
- Repo: \`crashappsec/ncc\` at \`baron/windows@6455dbfa9a80\`

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.