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.

Reply via email to