https://github.com/rosefromthedead updated 
https://github.com/llvm/llvm-project/pull/186770

>From e2294efaabcc745bf6beacbefbd371595cf3e87f Mon Sep 17 00:00:00 2001
From: Rose Hudson <[email protected]>
Date: Wed, 25 Mar 2026 10:15:01 +0000
Subject: [PATCH] [clang][Lex] add -Wnonportable-include-path-separator

Emit a 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 +++++
 clang/include/clang/Basic/DiagnosticGroups.td |  3 +++
 .../include/clang/Basic/DiagnosticLexKinds.td |  5 ++++-
 clang/include/clang/Basic/SourceManager.h     |  9 ++++++++
 clang/lib/Lex/PPDirectives.cpp                | 11 ++++++++++
 clang/test/Lexer/backslash-include-win.c      | 22 +++++++++++++++++++
 6 files changed, 55 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/Lexer/backslash-include-win.c

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 08d7ffd826b41..bb83847d240c2 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -360,6 +360,12 @@ Improvements to Clang's diagnostics
 
 - Clang now emits an error when implicitly casting a complex type to a 
built-in vector type. (#GH186805)
 
+- 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/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index dc7280c66ea79..cc5626b76ad5f 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 NonportableIncludePathSeparator : 
DiagGroup<"nonportable-include-path-separator">;
+def NonportableIncludePath : DiagGroup<"nonportable-include-path",
+                                       [NonportableIncludePathSeparator]>;
 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 bea0aafac98cf..07afc69793499 100644
--- a/clang/include/clang/Basic/DiagnosticLexKinds.td
+++ b/clang/include/clang/Basic/DiagnosticLexKinds.td
@@ -377,9 +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<NonportableIncludePathSeparator>;
 
 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 b90c04776ff9e..172c2fa57db00 100644
--- a/clang/lib/Lex/PPDirectives.cpp
+++ b/clang/lib/Lex/PPDirectives.cpp
@@ -2731,6 +2731,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();
+      llvm::replace(SuggestedPath, '\\', '/');
+      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..a07325550093f
--- /dev/null
+++ b/clang/test/Lexer/backslash-include-win.c
@@ -0,0 +1,22 @@
+// 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 --check-prefixes=CHECK-ENABLED,CHECK-ALL %s
+// RUN: %clang_cc1 -fsyntax-only -Wnonportable-include-path 
-Wno-nonportable-include-path-separator -I%t %s 2>&1 | FileCheck 
--check-prefix=CHECK-DISABLED,CHECK-ALL %s
+
+#include "backslash\case-insensitive-include.h"
+// CHECK-ENABLED: non-portable path to file
+// CHECK-ENABLED: specified path contains backslashes
+// CHECK-ENABLED: "backslash/case-insensitive-include.h"
+// CHECK-DISABLED-NOT: non-portable path to file
+
+// 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-ALL: non-portable path to file
+// CHECK-ALL: specified path differs in case from file name on disk
+// CHECK-ALL: "backslash\case-insensitive-include.h"
+// CHECK-ENABLED: non-portable path to file
+// CHECK-ENABLED: specified path contains backslashes
+// CHECK-ENABLED: "backslash/CASE-insensitive-include.h"
+// CHECK-DISABLED-NOT: non-portable path to file

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to