https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127319
Bug ID: 127319
Summary: libcpp truncates the system-header level to bool,
dropping the "4" flag from every system-header
linemarker
Product: gcc
Version: 16.2.1
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: preprocessor
Assignee: unassigned at gcc dot gnu.org
Reporter: vlad at petric dot cc
Target Milestone: ---
Created attachment 65557
--> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65557&action=edit
Straightforward fix (patch)
Summary: libcpp truncates the system-header level to bool, dropping the "4"
flag from every system-header linemarker
`_cpp_stack_file` computes the system-header level as an `int`:
int sysp = 0;
...
sysp = MAX (pfile->buffer->sysp, file->dir->sysp);
`cpp_dir::sysp` is documented as "One if a system header, two if a system
header that has extern \"C\" guards for C++", and gcc/incpath.cc sets it to
`1 + !cxx_aware`, so 2 is the normal value for a C system directory. That value
is passed to `_cpp_post_stack_file`, whose parameter is `bool`, and on to
`_cpp_do_file_change`, whose parameter is `unsigned int`. The 2 becomes 1.
So `# 1 "/usr/include/stdc-predef.h" 1 3 4` is emitted as `... 1 3`, for every
system header, in every translation unit. What that costs is spelled out in
"Why this is worth fixing" below.
Affected versions:
13, 14, 15 `_cpp_post_stack_file` does not exist; the int goes straight
to `_cpp_do_file_change`. Correct.
16.2.0 Broken.
trunk Still broken (checked 2026-09-09).
Introduced by the refactor that split `_cpp_post_stack_file` out of
`_cpp_stack_file`.
### Why this is worth fixing
It fixes no miscompilation -- see "Severity" below, and please read that before
weighing the rest. The case is about libcpp's own consistency, and about a
refactor having silently changed compiler output.
1. **libcpp contradicts itself.** `do_linemarker` in libcpp/directives.cc
still parses flag 4 and sets `new_sysp = 2` (16.2.0, around line 1631),
then
passes it on as `unsigned int`. The read path fully supports a value the
write path can no longer produce, so `-E` output no longer round-trips
through `-fpreprocessed`.
2. **A live-looking branch that never runs.** The `sysp == 2` arm in
gcc/c-family/c-ppoutput.cc that prints `" 3 4"` is unreachable for any file
entered through `_cpp_stack_file`.
3. **The current state is neither of the two coherent ones.** GCC has code to
emit flag 4, code to parse it, and documentation describing it, plus a
truncation that means it is never emitted. Either restore it (this patch),
or decide `sysp == 2` is dead and remove the emitter, the parser and the
documentation together. I have no stake in which one you pick; the point
is
that the middle state is the one nobody chose, and it cannot be chosen
while
the inconsistency is unnoticed.
4. **The parameter type is wrong independently of all of the above.** A
`bool`
sitting between an `int` caller and an `unsigned int` callee, carrying a
three-valued quantity.
5. **Nothing in the testsuite covered the 4 flag.** That is why the refactor
dropped it silently. The testcase here is arguably worth more than the
one-word fix, because it stops the next refactor doing the same thing.
6. **It is a release-to-release output regression, still present on trunk.**
The same source preprocesses differently under 15 and 16 for a reason
unrelated to the source, which matters to anything that compares or hashes
preprocessed text -- ccache, distcc, `-save-temps` diffs, build
reproducibility checks. That is how I found it. Left alone it will most
likely resurface as a puzzled report from one of those projects, without
the
diagnosis attached.
### Severity
Cosmetic, as far as I can establish, and I would rather understate this than
have you find the overstatement yourself. `sysp == 2` looks vestigial: a C++
TU
including a system header that declares `void legacy_function(int);` produces
the C++-mangled symbol both on a direct compile and through `-E`, with and
without the flag, so the implicit `extern "C"` that the flag documents does not
appear to happen any more. I did not find a program that behaves differently
with the patch than without it.
### Reproducing
$ cat > sysdir/h.h <<< 'extern int marker;'
$ echo '#include <h.h>' > t.c
$ gcc -isystem sysdir -E t.c | grep h.h
# 1 "sysdir/h.h" 1 3 4 <- gcc 13/14/15
# 1 "sysdir/h.h" 1 3 <- gcc 16, truncated
Both lines were observed here: the first from the distribution gcc 13.3.0, the
second from a gcc 16.2.0 built from the released tarball (C only,
--disable-bootstrap). `/usr/include/stdc-predef.h` loses the flag the same
way.