[clang] [clang][Driver] Include stdc-predef.h by default on musl (PR #202919)
Jonathan Thackray via cfe-commits
cfe-commits at lists.llvm.org
Tue Jun 16 03:12:46 PDT 2026
https://github.com/jthackray updated https://github.com/llvm/llvm-project/pull/202919
>From d988b381e3461880d5b3b62b524d0de3aea75291 Mon Sep 17 00:00:00 2001
From: Jonathan Thackray <jonathan.thackray at arm.com>
Date: Wed, 10 Jun 2026 11:31:05 +0100
Subject: [PATCH 1/2] [clang][Driver] Include stdc-predef.h by default on musl
Match GCC behavior for musl-based Linux targets by implicitly passing
`-include stdc-predef.h` for non-freestanding compilations.
Unlike glibc, musl does not arrange for this header to be included through
its normal system headers. As a result, Clang may miss predefined C macros
provided by musl's stdc-predef.h unless users include it manually, while the
same code works with GCC.
Add Driver tests covering musl, non-musl, freestanding, preprocessed input,
missing-header diagnostics, and actual macro visibility.
This patch is based on: https://reviews.llvm.org/D137043 and
slightly tweaks it, as the code has drifted since 2023 when
that was authored.
Co-authored-by: Tao Liang <Origami404 at foxmail.com>
Co-authored-by: YingChi Long <me at inclyc.cn>
---
clang/docs/ReleaseNotes.rst | 3 +
clang/lib/Driver/ToolChains/Linux.cpp | 9 ++
.../Driver/Inputs/stdc-predef/preprocessed.i | 1 +
.../stdc-predef/usr/include/stdc-predef.h | 4 +
clang/test/Driver/stdc-predef.c | 95 +++++++++++++++++++
5 files changed, 112 insertions(+)
create mode 100644 clang/test/Driver/Inputs/stdc-predef/preprocessed.i
create mode 100644 clang/test/Driver/Inputs/stdc-predef/usr/include/stdc-predef.h
create mode 100644 clang/test/Driver/stdc-predef.c
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 7828135a6edbc..f28f0bea048e5 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -334,6 +334,9 @@ Non-comprehensive list of changes in this release
and ``__builtin___get_unsafe_stack_start`` are now deprecated. Use the
corresponding functions from ``<sanitizer/safestack_interface.h>`` instead.
+- Clang now implicitly includes ``stdc-predef.h`` for non-freestanding
+ compilations targeting musl-based Linux systems, matching GCC behavior.
+
New Compiler Flags
------------------
- New option ``-fms-anonymous-structs`` / ``-fno-ms-anonymous-structs`` added
diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp
index 512788d235fec..6709638fc7c28 100644
--- a/clang/lib/Driver/ToolChains/Linux.cpp
+++ b/clang/lib/Driver/ToolChains/Linux.cpp
@@ -825,6 +825,15 @@ void Linux::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
if (!DriverArgs.hasArg(options::OPT_nobuiltininc) && getTriple().isMusl())
addSystemInclude(DriverArgs, CC1Args, ResourceDirInclude);
+
+ // For GCC compatibility, add an implicit include for musl-based
+ // non-freestanding systems.
+ if (getTriple().isMusl() &&
+ !DriverArgs.hasFlag(options::OPT_ffreestanding, options::OPT_fhosted,
+ false)) {
+ CC1Args.push_back("-include");
+ CC1Args.push_back("stdc-predef.h");
+ }
}
void Linux::addLibStdCxxIncludePaths(const llvm::opt::ArgList &DriverArgs,
diff --git a/clang/test/Driver/Inputs/stdc-predef/preprocessed.i b/clang/test/Driver/Inputs/stdc-predef/preprocessed.i
new file mode 100644
index 0000000000000..048f715b4656f
--- /dev/null
+++ b/clang/test/Driver/Inputs/stdc-predef/preprocessed.i
@@ -0,0 +1 @@
+int i;
diff --git a/clang/test/Driver/Inputs/stdc-predef/usr/include/stdc-predef.h b/clang/test/Driver/Inputs/stdc-predef/usr/include/stdc-predef.h
new file mode 100644
index 0000000000000..0a8bef6e196ea
--- /dev/null
+++ b/clang/test/Driver/Inputs/stdc-predef/usr/include/stdc-predef.h
@@ -0,0 +1,4 @@
+#ifndef _STDC_PREDEF_H
+#define _STDC_PREDEF_H 1
+#define DUMMY_STDC_PREDEF 1
+#endif
diff --git a/clang/test/Driver/stdc-predef.c b/clang/test/Driver/stdc-predef.c
new file mode 100644
index 0000000000000..fd8d526eb817e
--- /dev/null
+++ b/clang/test/Driver/stdc-predef.c
@@ -0,0 +1,95 @@
+// Test that clang preincludes stdc-predef.h if we are using a libc that does
+// not pre-include it, e.g. musl.
+
+// Musl-based systems need this additional include.
+//
+// RUN: %clang %s -### -c 2>&1 \
+// RUN: -target x86_64-unknown-linux-musl \
+// RUN: --sysroot=%S/Inputs/stdc-predef \
+// RUN: | FileCheck -check-prefix CHECK-CPP-FLAG %s
+
+// GNU-based systems do not need this additional include.
+//
+// RUN: %clang %s -### -c 2>&1 \
+// RUN: -target x86_64-unknown-linux-gnu \
+// RUN: --sysroot=%S/Inputs/stdc-predef \
+// RUN: | FileCheck --implicit-check-not "stdc-predef.h" %s
+
+// Freestanding compilations do not need this additional include.
+//
+// RUN: %clang %s -### -c 2>&1 \
+// RUN: -target x86_64-unknown-linux-musl -ffreestanding \
+// RUN: --sysroot=%S/Inputs/stdc-predef \
+// RUN: | FileCheck --implicit-check-not "stdc-predef.h" %s
+
+// The last of -ffreestanding and -fhosted determines whether this additional
+// include is needed.
+//
+// RUN: %clang %s -### -c 2>&1 \
+// RUN: -target x86_64-unknown-linux-musl -ffreestanding -fhosted \
+// RUN: --sysroot=%S/Inputs/stdc-predef \
+// RUN: | FileCheck -check-prefix CHECK-CPP-FLAG %s
+//
+// RUN: %clang %s -### -c 2>&1 \
+// RUN: -target x86_64-unknown-linux-musl -fhosted -ffreestanding \
+// RUN: --sysroot=%S/Inputs/stdc-predef \
+// RUN: | FileCheck --implicit-check-not "stdc-predef.h" %s
+
+// Disabling standard include directories also disables this additional include.
+//
+// RUN: %clang %s -### -c 2>&1 \
+// RUN: -target x86_64-unknown-linux-musl -nostdinc \
+// RUN: --sysroot=%S/Inputs/stdc-predef \
+// RUN: | FileCheck --implicit-check-not "stdc-predef.h" %s
+//
+// RUN: %clang %s -### -c 2>&1 \
+// RUN: -target x86_64-unknown-linux-musl -nostdlibinc \
+// RUN: --sysroot=%S/Inputs/stdc-predef \
+// RUN: | FileCheck --implicit-check-not "stdc-predef.h" %s
+
+// Because this behavior is implemented by adding preprocessor flags in the
+// driver, disabling preprocessing means the include flag should not appear.
+//
+// RUN: %clang -x cpp-output %s -### -c 2>&1 \
+// RUN: -target x86_64-unknown-linux-musl \
+// RUN: --sysroot=%S/Inputs/stdc-predef \
+// RUN: | FileCheck --implicit-check-not "stdc-predef.h" %s
+
+// The automatic preinclude of stdc-predef.h should not occur if
+// the source filename indicates a preprocessed file.
+//
+// RUN: %clang %S/Inputs/stdc-predef/preprocessed.i -### -c 2>&1 \
+// RUN: --sysroot=%S/Inputs/stdc-predef \
+// RUN: -target x86_64-unknown-linux-musl \
+// RUN: | FileCheck --implicit-check-not "stdc-predef.h" %s
+
+// This behavior should appear in all files clang accepts that use the C
+// preprocessor, including C, C++ and Objective-C files, as long as the system
+// uses musl.
+//
+// RUN: %clang -x objective-c %s -### -c 2>&1 \
+// RUN: -target x86_64-unknown-linux-musl \
+// RUN: --sysroot=%S/Inputs/stdc-predef \
+// RUN: | FileCheck -check-prefix CHECK-CPP-FLAG %s
+
+// If a musl-based system does not have this header, give an error at line 1.
+//
+// RUN: %clang %s -c -Xclang -verify=expected 2>&1 \
+// RUN: -target x86_64-unknown-linux-musl \
+// RUN: --sysroot=%S/Inputs/basic_linux_tree
+// expected-error at 1 {{'stdc-predef.h' file not found}}
+
+// Check if the file is really included by macro.
+//
+// RUN: %clang %s -c -Xclang -verify=ok -DCHECK_DUMMY=1 \
+// RUN: -target x86_64-unknown-linux-musl \
+// RUN: --sysroot=%S/Inputs/stdc-predef
+// ok-no-diagnostics
+
+// CHECK-CPP-FLAG: "-include" "stdc-predef.h"
+int i;
+#if CHECK_DUMMY
+#if !DUMMY_STDC_PREDEF
+#error "Expected macro symbol DUMMY_STDC_PREDEF is not defined."
+#endif
+#endif
>From 2fd832d0115539f7e31b4a7779b0bfbe52b45c00 Mon Sep 17 00:00:00 2001
From: Jonathan Thackray <jonathan.thackray at arm.com>
Date: Tue, 16 Jun 2026 10:52:43 +0100
Subject: [PATCH 2/2] fixup! Address PR comments
---
clang/docs/ReleaseNotes.rst | 3 +-
clang/lib/Driver/ToolChains/Linux.cpp | 15 +++-
clang/test/Driver/stdc-predef.c | 106 +++++++++++---------------
3 files changed, 57 insertions(+), 67 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index f28f0bea048e5..2136cda29d3ca 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -335,7 +335,8 @@ Non-comprehensive list of changes in this release
corresponding functions from ``<sanitizer/safestack_interface.h>`` instead.
- Clang now implicitly includes ``stdc-predef.h`` for non-freestanding
- compilations targeting musl-based Linux systems, matching GCC behavior.
+ compilations targeting musl Linux systems, and for glibc Linux sysroots
+ that provide the header, matching GCC behavior.
New Compiler Flags
------------------
diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp
index 6709638fc7c28..38b8f8a943ad7 100644
--- a/clang/lib/Driver/ToolChains/Linux.cpp
+++ b/clang/lib/Driver/ToolChains/Linux.cpp
@@ -826,11 +826,18 @@ void Linux::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
if (!DriverArgs.hasArg(options::OPT_nobuiltininc) && getTriple().isMusl())
addSystemInclude(DriverArgs, CC1Args, ResourceDirInclude);
- // For GCC compatibility, add an implicit include for musl-based
- // non-freestanding systems.
- if (getTriple().isMusl() &&
+ // For GCC compatibility, add an implicit include for musl-based or
+ // non-freestanding GNU systems where the target libc provides stdc-predef.h.
+ bool HasGlibcStdcPredef =
+ getTriple().isGNUEnvironment() && !SysRoot.empty() &&
+ D.getVFS().exists(concat(SysRoot, "/usr/include/stdc-predef.h"));
+
+ bool ShouldPreincludeStdcPredef =
+ (getTriple().isMusl() || HasGlibcStdcPredef) &&
!DriverArgs.hasFlag(options::OPT_ffreestanding, options::OPT_fhosted,
- false)) {
+ false);
+
+ if (ShouldPreincludeStdcPredef) {
CC1Args.push_back("-include");
CC1Args.push_back("stdc-predef.h");
}
diff --git a/clang/test/Driver/stdc-predef.c b/clang/test/Driver/stdc-predef.c
index fd8d526eb817e..f1f116393cd7d 100644
--- a/clang/test/Driver/stdc-predef.c
+++ b/clang/test/Driver/stdc-predef.c
@@ -1,89 +1,71 @@
-// Test that clang preincludes stdc-predef.h if we are using a libc that does
-// not pre-include it, e.g. musl.
+// Test that clang preincludes stdc-predef.h for musl Linux targets and
+// glibc Linux sysroots that provide the header.
// Musl-based systems need this additional include.
-//
-// RUN: %clang %s -### -c 2>&1 \
-// RUN: -target x86_64-unknown-linux-musl \
-// RUN: --sysroot=%S/Inputs/stdc-predef \
-// RUN: | FileCheck -check-prefix CHECK-CPP-FLAG %s
+// RUN: %clang %s -### -c --target=x86_64-unknown-linux-musl \
+// RUN: --sysroot=%S/Inputs/stdc-predef 2>&1 \
+// RUN: | FileCheck --check-prefix=CHECK-CPP-FLAG %s
-// GNU-based systems do not need this additional include.
-//
-// RUN: %clang %s -### -c 2>&1 \
-// RUN: -target x86_64-unknown-linux-gnu \
-// RUN: --sysroot=%S/Inputs/stdc-predef \
-// RUN: | FileCheck --implicit-check-not "stdc-predef.h" %s
+// GNU-based sysroots with stdc-predef.h also need this additional include.
+// RUN: %clang %s -### -c --target=x86_64-unknown-linux-gnu \
+// RUN: --sysroot=%S/Inputs/stdc-predef 2>&1 \
+// RUN: | FileCheck --check-prefix=CHECK-CPP-FLAG %s
+
+// GNU-based sysroots without stdc-predef.h should not inject a host header.
+// RUN: %clang %s -### -c --target=x86_64-unknown-linux-gnu \
+// RUN: --sysroot=%S/Inputs/basic_linux_tree 2>&1 \
+// RUN: | FileCheck --implicit-check-not "stdc-predef.h" %s
// Freestanding compilations do not need this additional include.
-//
-// RUN: %clang %s -### -c 2>&1 \
-// RUN: -target x86_64-unknown-linux-musl -ffreestanding \
-// RUN: --sysroot=%S/Inputs/stdc-predef \
-// RUN: | FileCheck --implicit-check-not "stdc-predef.h" %s
+// RUN: %clang %s -### -c --target=x86_64-unknown-linux-musl -ffreestanding \
+// RUN: --sysroot=%S/Inputs/stdc-predef 2>&1 \
+// RUN: | FileCheck --implicit-check-not "stdc-predef.h" %s
// The last of -ffreestanding and -fhosted determines whether this additional
// include is needed.
-//
-// RUN: %clang %s -### -c 2>&1 \
-// RUN: -target x86_64-unknown-linux-musl -ffreestanding -fhosted \
-// RUN: --sysroot=%S/Inputs/stdc-predef \
-// RUN: | FileCheck -check-prefix CHECK-CPP-FLAG %s
-//
-// RUN: %clang %s -### -c 2>&1 \
-// RUN: -target x86_64-unknown-linux-musl -fhosted -ffreestanding \
-// RUN: --sysroot=%S/Inputs/stdc-predef \
-// RUN: | FileCheck --implicit-check-not "stdc-predef.h" %s
+// RUN: %clang %s -### -c --target=x86_64-unknown-linux-musl \
+// RUN: -ffreestanding -fhosted --sysroot=%S/Inputs/stdc-predef 2>&1 \
+// RUN: | FileCheck --check-prefix=CHECK-CPP-FLAG %s
+// RUN: %clang %s -### -c --target=x86_64-unknown-linux-musl \
+// RUN: -fhosted -ffreestanding --sysroot=%S/Inputs/stdc-predef 2>&1 \
+// RUN: | FileCheck --implicit-check-not "stdc-predef.h" %s
// Disabling standard include directories also disables this additional include.
-//
-// RUN: %clang %s -### -c 2>&1 \
-// RUN: -target x86_64-unknown-linux-musl -nostdinc \
-// RUN: --sysroot=%S/Inputs/stdc-predef \
-// RUN: | FileCheck --implicit-check-not "stdc-predef.h" %s
-//
-// RUN: %clang %s -### -c 2>&1 \
-// RUN: -target x86_64-unknown-linux-musl -nostdlibinc \
-// RUN: --sysroot=%S/Inputs/stdc-predef \
-// RUN: | FileCheck --implicit-check-not "stdc-predef.h" %s
+// RUN: %clang %s -### -c --target=x86_64-unknown-linux-musl -nostdinc \
+// RUN: --sysroot=%S/Inputs/stdc-predef 2>&1 \
+// RUN: | FileCheck --implicit-check-not "stdc-predef.h" %s
+// RUN: %clang %s -### -c --target=x86_64-unknown-linux-musl -nostdlibinc \
+// RUN: --sysroot=%S/Inputs/stdc-predef 2>&1 \
+// RUN: | FileCheck --implicit-check-not "stdc-predef.h" %s
// Because this behavior is implemented by adding preprocessor flags in the
// driver, disabling preprocessing means the include flag should not appear.
-//
-// RUN: %clang -x cpp-output %s -### -c 2>&1 \
-// RUN: -target x86_64-unknown-linux-musl \
-// RUN: --sysroot=%S/Inputs/stdc-predef \
-// RUN: | FileCheck --implicit-check-not "stdc-predef.h" %s
+// RUN: %clang -x cpp-output %s -### -c --target=x86_64-unknown-linux-musl \
+// RUN: --sysroot=%S/Inputs/stdc-predef 2>&1 \
+// RUN: | FileCheck --implicit-check-not "stdc-predef.h" %s
// The automatic preinclude of stdc-predef.h should not occur if
// the source filename indicates a preprocessed file.
-//
-// RUN: %clang %S/Inputs/stdc-predef/preprocessed.i -### -c 2>&1 \
-// RUN: --sysroot=%S/Inputs/stdc-predef \
-// RUN: -target x86_64-unknown-linux-musl \
-// RUN: | FileCheck --implicit-check-not "stdc-predef.h" %s
+// RUN: %clang %S/Inputs/stdc-predef/preprocessed.i -### -c \
+// RUN: --target=x86_64-unknown-linux-musl --sysroot=%S/Inputs/stdc-predef 2>&1 \
+// RUN: | FileCheck --implicit-check-not "stdc-predef.h" %s
// This behavior should appear in all files clang accepts that use the C
// preprocessor, including C, C++ and Objective-C files, as long as the system
-// uses musl.
-//
-// RUN: %clang -x objective-c %s -### -c 2>&1 \
-// RUN: -target x86_64-unknown-linux-musl \
-// RUN: --sysroot=%S/Inputs/stdc-predef \
-// RUN: | FileCheck -check-prefix CHECK-CPP-FLAG %s
+// uses musl or a glibc sysroot that provides the header.
+// RUN: %clang -x objective-c %s -### -c \
+// RUN: --target=x86_64-unknown-linux-musl --sysroot=%S/Inputs/stdc-predef 2>&1 \
+// RUN: | FileCheck --check-prefix=CHECK-CPP-FLAG %s
-// If a musl-based system does not have this header, give an error at line 1.
-//
-// RUN: %clang %s -c -Xclang -verify=expected 2>&1 \
-// RUN: -target x86_64-unknown-linux-musl \
-// RUN: --sysroot=%S/Inputs/basic_linux_tree
+// If a target system does not have this header, give an error at line 1.
+// RUN: %clang %s -c -Xclang -verify=expected \
+// RUN: --target=x86_64-unknown-linux-musl \
+// RUN: --sysroot=%S/Inputs/basic_linux_tree 2>&1
// expected-error at 1 {{'stdc-predef.h' file not found}}
// Check if the file is really included by macro.
-//
// RUN: %clang %s -c -Xclang -verify=ok -DCHECK_DUMMY=1 \
-// RUN: -target x86_64-unknown-linux-musl \
-// RUN: --sysroot=%S/Inputs/stdc-predef
+// RUN: --target=x86_64-unknown-linux-musl --sysroot=%S/Inputs/stdc-predef
// ok-no-diagnostics
// CHECK-CPP-FLAG: "-include" "stdc-predef.h"
More information about the cfe-commits
mailing list