[clang] [clang][Lex] add -Wnonportable-include-path-separator (PR #186770)
Rose Hudson via cfe-commits
cfe-commits at lists.llvm.org
Wed Apr 1 08:20:55 PDT 2026
https://github.com/rosefromthedead updated https://github.com/llvm/llvm-project/pull/186770
>From 2f77ea965125e60481e81c9a14f3f0931340232a Mon Sep 17 00:00:00 2001
From: Rose Hudson <rose.hudson at sony.com>
Date: Wed, 25 Mar 2026 10:15:01 +0000
Subject: [PATCH 1/3] [clang][Lex] add -Wnonportable-include-path-separator
Emit an warning when #include paths contain backslashes, with a fixit to
convert them all to '/'. This can help users that build only on Windows
to automatically make their #includes more portable. The warning is off
by default due to being noisy and not always desirable.
---
clang/docs/ReleaseNotes.rst | 6 ++++++
.../include/clang/Basic/DiagnosticLexKinds.td | 3 +++
clang/include/clang/Basic/SourceManager.h | 9 +++++++++
clang/lib/Lex/PPDirectives.cpp | 11 +++++++++++
clang/test/Lexer/backslash-include-win.c | 19 +++++++++++++++++++
5 files changed, 48 insertions(+)
create mode 100644 clang/test/Lexer/backslash-include-win.c
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 0dbe667e4f07a..3ee154b7028d0 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -323,6 +323,12 @@ Improvements to Clang's diagnostics
``-Wunused-private-field`` no longer emits a warning for annotated private
fields.
+- Added ``-Wnonportable-include-path-separator`` (off by default) to catch
+ #include directives that use backslashes as a path separator. The warning
+ includes a FixIt to change all the backslashes to forward slashes, so that the
+ code can automatically be made portable to other host platforms that don't
+ support backslashes.
+
Improvements to Clang's time-trace
----------------------------------
diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td
index 5eceeced311f2..3525cc91d6f61 100644
--- a/clang/include/clang/Basic/DiagnosticLexKinds.td
+++ b/clang/include/clang/Basic/DiagnosticLexKinds.td
@@ -380,6 +380,9 @@ def pp_nonportable_path : NonportablePath,
InGroup<DiagGroup<"nonportable-include-path">>;
def pp_nonportable_system_path : NonportablePath, DefaultIgnore,
InGroup<DiagGroup<"nonportable-system-include-path">>;
+def pp_nonportable_path_separator : Warning<
+ "non-portable path to file '%0'; specified path contains backslashes">,
+ DefaultIgnore, InGroup<DiagGroup<"nonportable-include-path-separator">>;
def pp_pragma_once_in_main_file : Warning<"#pragma once in main file">,
InGroup<DiagGroup<"pragma-once-outside-header">>;
diff --git a/clang/include/clang/Basic/SourceManager.h b/clang/include/clang/Basic/SourceManager.h
index bc9e97863556d..4217b8683da1e 100644
--- a/clang/include/clang/Basic/SourceManager.h
+++ b/clang/include/clang/Basic/SourceManager.h
@@ -1526,6 +1526,15 @@ class SourceManager : public RefCountedBase<SourceManager> {
return Filename == "<scratch space>";
}
+ /// Returns whether \p Loc is located in a <module-includes> file.
+ bool isWrittenInModuleIncludes(SourceLocation Loc) const {
+ PresumedLoc Presumed = getPresumedLoc(Loc);
+ if (Presumed.isInvalid())
+ return false;
+ StringRef Filename(Presumed.getFilename());
+ return Filename == "<module-includes>";
+ }
+
/// Returns whether \p Loc is located in a built-in or command line source.
bool isInPredefinedFile(SourceLocation Loc) const {
PresumedLoc Presumed = getPresumedLoc(Loc);
diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp
index c89402fa137c0..65a7438eca130 100644
--- a/clang/lib/Lex/PPDirectives.cpp
+++ b/clang/lib/Lex/PPDirectives.cpp
@@ -2728,6 +2728,17 @@ Preprocessor::ImportAction Preprocessor::HandleHeaderIncludeOrImport(
Diag(FilenameTok, DiagId) << Path <<
FixItHint::CreateReplacement(FilenameRange, Path);
}
+
+ bool SuppressBackslashDiag =
+ FilenameLoc.isMacroID() ||
+ SourceMgr.isWrittenInBuiltinFile(FilenameLoc) ||
+ SourceMgr.isWrittenInModuleIncludes(FilenameLoc);
+ if (!SuppressBackslashDiag && OriginalFilename.contains('\\')) {
+ std::string SuggestedPath = OriginalFilename.str();
+ std::replace(SuggestedPath.begin(), SuggestedPath.end(), '\\', '/');
+ Diag(FilenameTok, diag::pp_nonportable_path_separator)
+ << Name << FixItHint::CreateReplacement(FilenameRange, SuggestedPath);
+ }
}
switch (Action) {
diff --git a/clang/test/Lexer/backslash-include-win.c b/clang/test/Lexer/backslash-include-win.c
new file mode 100644
index 0000000000000..31e282c48e69c
--- /dev/null
+++ b/clang/test/Lexer/backslash-include-win.c
@@ -0,0 +1,19 @@
+// REQUIRES: system-windows
+// RUN: mkdir -p %t/backslash
+// RUN: cp %S/Inputs/case-insensitive-include.h %t/backslash/case-insensitive-include.h
+// RUN: %clang_cc1 -fsyntax-only -Wnonportable-include-path-separator -I%t %s 2>&1 | FileCheck %s
+
+#include "backslash\case-insensitive-include.h"
+// CHECK: non-portable path to file
+// CHECK: specified path contains backslashes
+// CHECK: "backslash/case-insensitive-include.h"
+
+// Despite fixing the same span, nonportable-include-path is still a separate diagnostic
+// that can fire at the same time.
+#include "backslash\CASE-insensitive-include.h"
+// CHECK: non-portable path to file
+// CHECK: specified path differs in case from file name on disk
+// CHECK: "backslash\case-insensitive-include.h"
+// CHECK: non-portable path to file
+// CHECK: specified path contains backslashes
+// CHECK: "backslash/CASE-insensitive-include.h"
>From 5fcefb07ec2569535b78ab5e4234c39fc3478f79 Mon Sep 17 00:00:00 2001
From: Rose Hudson <rose.hudson at sony.com>
Date: Wed, 1 Apr 2026 13:53:51 +0100
Subject: [PATCH 2/3] make it a subgroup of -Wnonportable-include-path
---
clang/include/clang/Basic/DiagnosticGroups.td | 3 +++
clang/include/clang/Basic/DiagnosticLexKinds.td | 4 ++--
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index e440c9d2fb982..6035da0eba182 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -176,6 +176,9 @@ def NullConversion : DiagGroup<"null-conversion">;
def ImplicitConversionFloatingPointToBool :
DiagGroup<"implicit-conversion-floating-point-to-bool">;
def ObjCLiteralConversion : DiagGroup<"objc-literal-conversion">;
+def NonportableIncludePath : DiagGroup<"nonportable-include-path",
+ [NonportableIncludePathSeparator]>;
+def NonportableIncludePathSeparator : DiagGroup<"nonportable-include-path-separator">;
def MacroRedefined : DiagGroup<"macro-redefined">;
def BuiltinMacroRedefined : DiagGroup<"builtin-macro-redefined">;
def BuiltinRequiresHeader : DiagGroup<"builtin-requires-header">;
diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td
index 3525cc91d6f61..69f53e5a5e4ce 100644
--- a/clang/include/clang/Basic/DiagnosticLexKinds.td
+++ b/clang/include/clang/Basic/DiagnosticLexKinds.td
@@ -377,12 +377,12 @@ class NonportablePath : Warning<
"non-portable path to file '%0'; specified path differs in case from file"
" name on disk">;
def pp_nonportable_path : NonportablePath,
- InGroup<DiagGroup<"nonportable-include-path">>;
+ InGroup<NonportableIncludePath>;
def pp_nonportable_system_path : NonportablePath, DefaultIgnore,
InGroup<DiagGroup<"nonportable-system-include-path">>;
def pp_nonportable_path_separator : Warning<
"non-portable path to file '%0'; specified path contains backslashes">,
- DefaultIgnore, InGroup<DiagGroup<"nonportable-include-path-separator">>;
+ DefaultIgnore, InGroup<NonportableIncludePathSeparator>;
def pp_pragma_once_in_main_file : Warning<"#pragma once in main file">,
InGroup<DiagGroup<"pragma-once-outside-header">>;
>From a92feeb78686e4daecbe9066b24115ae6d98ff60 Mon Sep 17 00:00:00 2001
From: Rose Hudson <rose.hudson at sony.com>
Date: Wed, 1 Apr 2026 14:06:45 +0100
Subject: [PATCH 3/3] fix tablegen mistake
---
clang/include/clang/Basic/DiagnosticGroups.td | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 827d4ca86a6b3..130f85a4d8c35 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -176,9 +176,9 @@ def NullConversion : DiagGroup<"null-conversion">;
def ImplicitConversionFloatingPointToBool :
DiagGroup<"implicit-conversion-floating-point-to-bool">;
def ObjCLiteralConversion : DiagGroup<"objc-literal-conversion">;
+def NonportableIncludePathSeparator : DiagGroup<"nonportable-include-path-separator">;
def NonportableIncludePath : DiagGroup<"nonportable-include-path",
[NonportableIncludePathSeparator]>;
-def NonportableIncludePathSeparator : DiagGroup<"nonportable-include-path-separator">;
def MacroRedefined : DiagGroup<"macro-redefined">;
def BuiltinMacroRedefined : DiagGroup<"builtin-macro-redefined">;
def BuiltinRequiresHeader : DiagGroup<"builtin-requires-header">;
More information about the cfe-commits
mailing list