[clang] [Clang] Unescape doubled backslashes in #line filenames (PR #215992)
Hirotaka Monya via cfe-commits
cfe-commits at lists.llvm.org
Thu Aug 13 01:54:19 PDT 2026
https://github.com/catlover-bot created https://github.com/llvm/llvm-project/pull/215992
Fixes #213973
This change updates `#line` filename handling so that doubled backslashes (`\\`) are treated as a single backslash.
Importantly, it does not revert to the old string-literal parsing behavior. Only doubled backslashes are collapsed; single backslashes are preserved as written. This means filenames such as `C:\moo\zar\haz.h` remain unchanged, as do sequences such as `\x12`.
Regression coverage includes both forms of line directives:
* `#line ...`
* GNU line markers such as `# 40 "..."`
The tests cover the relevant doubled- and single-backslash cases for both forms.
Validation performed locally:
* Original reproducer after the fix: `exit code = 0`
* `clang/test/Preprocessor`: `380 passed / 12 unsupported / 0 failed`
* Related regression tests: `3/3 PASS`
AI tool usage: I do not use AI tools.
>From d04bbc09cc54db2cb96e28499919df4b2f2dfbd5 Mon Sep 17 00:00:00 2001
From: catlover-bot <catlover-bot at users.noreply.github.com>
Date: Thu, 13 Aug 2026 17:46:58 +0900
Subject: [PATCH] [Clang] Unescape doubled backslashes in #line filenames
---
clang/include/clang/Lex/Preprocessor.h | 5 +++-
clang/lib/Frontend/FrontendAction.cpp | 4 ++-
clang/lib/Lex/PPDirectives.cpp | 28 ++++++++++++++++---
.../Preprocessor/line-directive-backslash.c | 17 +++++++++++
4 files changed, 48 insertions(+), 6 deletions(-)
create mode 100644 clang/test/Preprocessor/line-directive-backslash.c
diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h
index e752010dd2062..61ad3e252087c 100644
--- a/clang/include/clang/Lex/Preprocessor.h
+++ b/clang/include/clang/Lex/Preprocessor.h
@@ -2556,7 +2556,10 @@ class Preprocessor {
/// the spelling of the filename, but is also expected to handle the case
/// when this method decides to use a different buffer.
///
- void GetLineDirectiveFilenameSpelling(SourceLocation Loc, StringRef &Buffer);
+ /// \p UnescapedBuffer provides storage when the filename needs unescaping.
+ ///
+ void GetLineDirectiveFilenameSpelling(SourceLocation Loc, StringRef &Buffer,
+ SmallVectorImpl<char> &UnescapedBuffer);
/// Given a "foo" or \<foo> reference, look up the indicated file.
///
diff --git a/clang/lib/Frontend/FrontendAction.cpp b/clang/lib/Frontend/FrontendAction.cpp
index c18c702d46cc7..d00ce06b99112 100644
--- a/clang/lib/Frontend/FrontendAction.cpp
+++ b/clang/lib/Frontend/FrontendAction.cpp
@@ -529,7 +529,9 @@ static SourceLocation ReadOriginalFileName(CompilerInstance &CI,
Preprocessor &PP = CI.getPreprocessor();
SmallString<128> HeaderNameBuffer;
StringRef HeaderName = PP.getSpelling(T, HeaderNameBuffer);
- PP.GetLineDirectiveFilenameSpelling(T.getLocation(), HeaderName);
+ SmallString<128> UnescapedHeaderNameBuffer;
+ PP.GetLineDirectiveFilenameSpelling(T.getLocation(), HeaderName,
+ UnescapedHeaderNameBuffer);
RawLexer->LexFromRawLexer(T);
if (T.isNot(tok::eof) && !T.isAtStartOfLine())
diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp
index ec387a4d582fc..e2a4c3d9db6b9 100644
--- a/clang/lib/Lex/PPDirectives.cpp
+++ b/clang/lib/Lex/PPDirectives.cpp
@@ -1645,7 +1645,9 @@ void Preprocessor::HandleLineDirective() {
} else {
SmallString<128> FilenameBuffer;
StringRef Filename = getSpelling(StrTok, FilenameBuffer);
- GetLineDirectiveFilenameSpelling(StrTok.getLocation(), Filename);
+ SmallString<128> UnescapedFilenameBuffer;
+ GetLineDirectiveFilenameSpelling(StrTok.getLocation(), Filename,
+ UnescapedFilenameBuffer);
FilenameID = SourceMgr.getLineTableFilenameID(Filename);
// Verify that there is nothing after the string, other than EOD. Because
@@ -1783,7 +1785,9 @@ void Preprocessor::HandleDigitDirective(Token &DigitTok) {
} else {
SmallString<128> FilenameBuffer;
StringRef Filename = getSpelling(StrTok, FilenameBuffer);
- GetLineDirectiveFilenameSpelling(StrTok.getLocation(), Filename);
+ SmallString<128> UnescapedFilenameBuffer;
+ GetLineDirectiveFilenameSpelling(StrTok.getLocation(), Filename,
+ UnescapedFilenameBuffer);
// If a filename was present, read any flags that are present.
if (ReadLineMarkerFlags(IsFileEntry, IsFileExit, FileKind, *this))
return;
@@ -1983,8 +1987,9 @@ bool Preprocessor::GetIncludeFilenameSpelling(SourceLocation Loc,
return isAngled;
}
-void Preprocessor::GetLineDirectiveFilenameSpelling(SourceLocation Loc,
- StringRef &Buffer) {
+void Preprocessor::GetLineDirectiveFilenameSpelling(
+ SourceLocation Loc, StringRef &Buffer,
+ SmallVectorImpl<char> &UnescapedBuffer) {
// Get the text form of the filename.
assert(!Buffer.empty() && "Can't have tokens with empty spellings!");
if (Buffer.size() < 2 || Buffer.front() != '"' || Buffer.back() != '"') {
@@ -1993,6 +1998,21 @@ void Preprocessor::GetLineDirectiveFilenameSpelling(SourceLocation Loc,
return;
}
Buffer = Buffer.substr(1, Buffer.size() - 2);
+
+ // A line directive filename is lexed as a header-name so that backslashes
+ // in paths are not interpreted as escape sequences. However, a doubled
+ // backslash represents a single backslash in the resulting filename.
+ if (!Buffer.contains("\\\\"))
+ return;
+
+ UnescapedBuffer.clear();
+ UnescapedBuffer.reserve(Buffer.size());
+ for (size_t I = 0; I < Buffer.size(); ++I) {
+ if (Buffer[I] == '\\' && I + 1 < Buffer.size() && Buffer[I + 1] == '\\')
+ ++I;
+ UnescapedBuffer.push_back(Buffer[I]);
+ }
+ Buffer = StringRef(UnescapedBuffer.data(), UnescapedBuffer.size());
}
/// Push a token onto the token stream containing an annotation.
diff --git a/clang/test/Preprocessor/line-directive-backslash.c b/clang/test/Preprocessor/line-directive-backslash.c
new file mode 100644
index 0000000000000..801c249eebcc7
--- /dev/null
+++ b/clang/test/Preprocessor/line-directive-backslash.c
@@ -0,0 +1,17 @@
+// RUN: %clang_cc1 -E -P %s | FileCheck %s
+
+#line 10 "not_a_\\tab"
+const char *escaped_backslash = __FILE__;
+// CHECK: const char *escaped_backslash = "not_a_\\tab";
+
+#line 20 "c:\moo\zar\haz.h"
+const char *windows_path = __FILE__;
+// CHECK: const char *windows_path = "c:\\moo\\zar\\haz.h";
+
+#line 30 "original\x12source.c"
+const char *non_escape = __FILE__;
+// CHECK: const char *non_escape = "original\\x12source.c";
+
+# 40 "gnu_\\path"
+const char *gnu_line_marker = __FILE__;
+// CHECK: const char *gnu_line_marker = "gnu_\\path";
More information about the cfe-commits
mailing list