_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. 13, 14 and 15 are correct: _cpp_post_stack_file does not exist
there and the int goes straight to _cpp_do_file_change. 16.2 and trunk
are affected; the regression came in with the refactor that split
_cpp_post_stack_file out of _cpp_stack_file.
Severity, stated conservatively: I could not find a program that behaves
differently with this patch than without it. sysp == 2 looks vestigial
-- a C++ TU including a system header declaring void f(int); produces the
C++-mangled symbol with and without the flag -- so this is about output
contract and code health, not miscompilation. Please weigh the rest in
that light.
What is left over is an inconsistency inside libcpp. do_linemarker still
parses flag 4 and sets new_sysp = 2, passing it on as unsigned int, so
the read path fully supports a value the write path can no longer
produce and -E output no longer round-trips through -fpreprocessed. 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. And the
parameter type is wrong on its own terms: a bool between an int caller
and an unsigned int callee, carrying a three-valued quantity.
GCC therefore has code to emit flag 4, code to parse it, and
documentation describing it, plus a truncation meaning it is never
emitted. Either restore it, as here, or conclude sysp == 2 is dead and
remove the emitter, the parser and the documentation together. I have
no stake in which; the middle state is the one nobody chose. If you
prefer the second, say so and I will not pursue this patch.
I found it because 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.
Nothing in the testsuite covered the 4 flag, which is why the refactor
dropped it silently; the testcase may be worth more than the fix.
Testing. Against a gcc 16.2.0 built from the released tarball
(--enable-languages=c --disable-bootstrap --disable-multilib, make
all-gcc), with only libcpp/files.cc differing and cc1 relinked in
between:
unpatched FAIL: gcc.dg/cpp/sysp-externc.c scan-file ...
patched PASS: gcc.dg/cpp/sysp-externc.c scan-file ...
The same test passes on gcc 13.3.0, so it encodes the pre-16 behaviour
rather than this patch's. gcc.dg/cpp plus c-c++-common/cpp were run
both ways, 6136 tests; the complete diff of the two gcc.sum files is the
one line above. The 64 failures common to both runs are dg-do run tests
that cannot link because only all-gcc was built.
What I did not do: no bootstrap and no full make check. Only the
preprocessor testsuite was run, C only, native x86_64-pc-linux-gnu.
libcpp/ChangeLog:
PR preprocessor/127319
* files.cc (_cpp_post_stack_file): Take the system-header level as
unsigned int rather than bool, so a value of 2 (system header with
extern "C" guards) is not truncated to 1.
(_cpp_stack_file): Adjust caller.
gcc/testsuite/ChangeLog:
PR preprocessor/127319
* gcc.dg/cpp/sysp-externc.c: New test.
* gcc.dg/cpp/sysp-externc.h: New test header.
Signed-off-by: Vlad Petric <[email protected]>
---
--- a/libcpp/files.cc
+++ b/libcpp/files.cc
@@ -211,7 +211,8 @@
static int pchf_save_compare (const void *e1, const void *e2);
static int pchf_compare (const void *d_p, const void *e_p);
static bool check_file_against_entries (cpp_reader *, _cpp_file *, bool);
-static void _cpp_post_stack_file (cpp_reader *, _cpp_file *,
include_type, bool);
+static void _cpp_post_stack_file (cpp_reader *, _cpp_file *, include_type,
+ unsigned int);
/* Given a filename in FILE->PATH, with the empty string interpreted
as <stdin>, open it.
@@ -1030,7 +1031,7 @@
file->header_unit = +1;
_cpp_mark_file_once_only (pfile, file);
- _cpp_post_stack_file (pfile, file, type, false);
+ _cpp_post_stack_file (pfile, file, type, 0);
return true;
}
@@ -1038,7 +1039,7 @@
static void
_cpp_post_stack_file (cpp_reader *pfile, _cpp_file *file, include_type type,
- bool sysp)
+ unsigned int sysp)
{
/* In the case of a normal #include, we're now at the start of the
line *following* the #include. A separate location_t for this
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/cpp/sysp-externc.c
@@ -0,0 +1,13 @@
+/* Test that a header found in an -isystem directory is marked "3 4", not "3".
+
+ gcc/incpath.cc sets cpp_dir::sysp to 1 + !cxx_aware, so a C system
+ directory is 2, meaning "system header with extern \"C\" guards". libcpp
+ must carry that 2 through to the line map; truncating it to 1 silently drops
+ the 4 flag from every system-header linemarker.
+
+ { dg-do preprocess }
+ { dg-options "-isystem ${srcdir}/gcc.dg/cpp" } */
+
+#include <sysp-externc.h>
+
+/* { dg-final { scan-file sysp-externc.i "sysp-externc\\.h\" 1 3 4" } } */
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/cpp/sysp-externc.h
@@ -0,0 +1,2 @@
+/* A header reached through -isystem, for sysp-externc.c. */
+extern int sysp_externc_marker;