clang-tidy crashes with libcpp-cpp-version-check or libcpp-internal-ftms enabled, preprocessing `#if defined(FOO) && __has_include(<stddef.h>)`
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
### Summary
A vanilla `clang-tidy` binary (built from an unmodified `llvmorg-22.1.8` checkout,
no third-party checks beyond libc++'s own in-tree `libcpp-*` checks) crashes while
preprocessing a three-line reproducer when either of two `PPCallbacks`-registering
checks is enabled: `libcpp-cpp-version-check` or `libcpp-internal-ftms`.
### Minimal reproducer
```cpp
#if defined(FOO) && __has_include()
#endif
```
```
clang-tidy -checks='-*,libcpp-cpp-version-check' repro.cpp -- -std=c++20
```
Crashes identically with `-checks='-*,libcpp-internal-ftms'` instead. Both are the
only two of libc++'s nine in-tree `libcpp-*` checks that register `PPCallbacks`
(`proper_version_checks.cpp` / `internal_ftm_use.cpp` in
`libcxx/test/tools/clang_tidy_checks/`).
### Confirmation this is not a downstream/environment issue
Found while running libc++'s own `clang_tidy.gen.py`/`*.sh.py` test suite (145
crashing files, all going through one of these two checks) on a downstream fork
tracking `llvmorg-22.1.8`. Before reporting, ruled out every fork/environment-specific
explanation:
1. **Plugin/host ABI mismatch** — rebuilt `libcxx-tidy.plugin` fresh against the
exact `clang-tidy` binary being used (note: `ninja cxx-test-depends` does *not*
rebuild this plugin; the target is `libcxx-tidy.plugin`). No change.
2. **Wrong Clang found by `find_package(Clang ...)`** — confirmed
`libcxx/test/tools/clang_tidy_checks/CMakeLists.txt` can resolve to a
system-installed Clang instead of the tree being tested. Ruled out as the cause:
the crash reproduces identically with the 100% vanilla system `/usr/bin/clang-tidy`
(matching ABI, matching version) loading the same plugin.
3. **Fork modification of the check sources** — confirmed byte-identical to
upstream: `git diff llvmorg-22.1.8 -- libcxx/test/tools/clang_tidy_checks/{proper_version_checks,internal_ftm_use}.cpp libcxx/test/tools/clang_tidy_checks/CMakeLists.txt` is empty.
So: 100% vanilla `clang-tidy` binary, 100% vanilla check source, a plugin built the
normal way, crashing on ordinary system-header preprocessing
(`stddef.h`'s `__has_include_next` guard).
### Crash site
Varies by input (looks like corruption surfacing downstream of the real fault
rather than at it) — observed in `TokenLexer::PropagateLineStartLeadingSpaceInfo`
and `Preprocessor::LookupFile` across different repro shapes. Every function in
the observed call chain is unmodified from upstream
(`git diff llvmorg-22.1.8` empty for those files).
### Environment
- `llvmorg-22.1.8`, Release build, X86 target only, Ninja.
- Reproduced with both a locally-built `clang-tidy` and the distro's
`/usr/bin/clang-tidy`.
### Not yet done
Root cause inside `PPCallbacks` handling for `__has_include`/`__has_include_next`
during a `defined(...) && __has_include(...)` short-circuit was not traced to
completion — this write-up stops at "confirmed pure upstream, reproducible,
localized to two specific PPCallbacks-registering checks," not at the exact
faulting mechanism.
Contributor guide
Research direction
Start by running the three-line reproducer with each of the two checks, then inspect proper_version_checks.cpp and internal_ftm_use.cpp in libcxx/test/tools/clang_tidy_checks/. Trace their PPCallbacks handling around __has_include and __has_include_next, using the observed TokenLexer::PropagateLineStartLeadingSpaceInfo and Preprocessor::LookupFile crash paths as entry points. Done means identifying and fixing the upstream crash and confirming the reproducer and libc++ clang-tidy test suite no longer crash.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100