[clang] [clang-tools-extra] [Clang] Treat line directives as header names. (PR #211512)
Corentin Jabot via cfe-commits
cfe-commits at lists.llvm.org
Thu Jul 23 07:51:12 PDT 2026
https://github.com/cor3ntin updated https://github.com/llvm/llvm-project/pull/211512
>From 5498ac5dac4b6753a224cff8df46896cd391c38b Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Thu, 23 Jul 2026 11:51:19 +0200
Subject: [PATCH 1/3] [Clang] Treat line directives as header names.
We treated line directives as being unevaluated string literal.
However
- The standard has no such restriction - or rather the standard has
different, unclear restriction (wg21.link/P2693)
- Because of Windows paths, it's common for "C:\foo\bar" to be used,
and these should not form escape sequences.
To remain consistent with other implementations, we do not allow `#line 1 <>`
at this time. We do however support `#line 1 ""` to avoid a breaking change.
Fix a regression introduced by #201413
---
clang/lib/Lex/PPDirectives.cpp | 70 ++++++++-----------
.../test/Frontend/linemarker-invalid-escape.c | 5 --
.../Parser/cxx11-user-defined-literals.cpp | 4 +-
.../test/Preprocessor/line-directive-output.c | 14 +++-
clang/test/Preprocessor/line-directive.c | 4 +-
5 files changed, 45 insertions(+), 52 deletions(-)
delete mode 100644 clang/test/Frontend/linemarker-invalid-escape.c
diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp
index c161f6a03593e..46649eae044b2 100644
--- a/clang/lib/Lex/PPDirectives.cpp
+++ b/clang/lib/Lex/PPDirectives.cpp
@@ -1599,6 +1599,21 @@ static bool GetLineValue(Token &DigitTok, unsigned &Val,
return false;
}
+// Unlike header-names, line directive only support filenames in double quotes
+// but does support empty filenames.
+static void GetFilenameSpellingForLineDirective(const Preprocessor &PP,
+ SourceLocation Loc,
+ StringRef &Buffer) {
+ // 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() != '"') {
+ PP.Diag(Loc, diag::err_pp_line_invalid_filename);
+ Buffer = StringRef();
+ return;
+ }
+ Buffer = Buffer.substr(1, Buffer.size() - 2);
+}
+
/// Handle a \#line directive: C99 6.10.4.
///
/// The two acceptable forms are:
@@ -1632,35 +1647,21 @@ void Preprocessor::HandleLineDirective() {
int FilenameID = -1;
Token StrTok;
- Lex(StrTok);
+ LexHeaderName(StrTok);
// If the StrTok is "eod", then it wasn't present. Otherwise, it must be a
// string followed by eod.
if (StrTok.is(tok::eod))
; // ok
- else if (StrTok.isNot(tok::string_literal)) {
+ else if (StrTok.isNot(tok::header_name)) {
Diag(StrTok, diag::err_pp_line_invalid_filename);
DiscardUntilEndOfDirective();
return;
- } else if (StrTok.hasUDSuffix()) {
- Diag(StrTok, diag::err_invalid_string_udl);
- DiscardUntilEndOfDirective();
- return;
} else {
- // Parse and validate the string, converting it into a unique ID.
- StringLiteralParser Literal(StrTok, *this,
- StringLiteralEvalMethod::Unevaluated);
- assert(Literal.isOrdinary() && "Didn't allow wide strings in");
- if (Literal.hadError) {
- DiscardUntilEndOfDirective();
- return;
- }
- if (Literal.Pascal) {
- Diag(StrTok, diag::err_pp_linemarker_invalid_filename);
- DiscardUntilEndOfDirective();
- return;
- }
- FilenameID = SourceMgr.getLineTableFilenameID(Literal.GetString());
+ SmallString<128> FilenameBuffer;
+ StringRef Filename = getSpelling(StrTok, FilenameBuffer);
+ GetFilenameSpellingForLineDirective(*this, StrTok.getLocation(), Filename);
+ FilenameID = SourceMgr.getLineTableFilenameID(Filename);
// Verify that there is nothing after the string, other than EOD. Because
// of C99 6.10.4p5, macros that expand to empty tokens are ok.
@@ -1778,7 +1779,7 @@ void Preprocessor::HandleDigitDirective(Token &DigitTok) {
return;
Token StrTok;
- Lex(StrTok);
+ LexHeaderName(StrTok);
bool IsFileEntry = false, IsFileExit = false;
int FilenameID = -1;
@@ -1790,29 +1791,14 @@ void Preprocessor::HandleDigitDirective(Token &DigitTok) {
Diag(StrTok, diag::ext_pp_gnu_line_directive);
// Treat this like "#line NN", which doesn't change file characteristics.
FileKind = SourceMgr.getFileCharacteristic(DigitTok.getLocation());
- } else if (StrTok.isNot(tok::string_literal)) {
+ } else if (StrTok.isNot(tok::header_name)) {
Diag(StrTok, diag::err_pp_linemarker_invalid_filename);
DiscardUntilEndOfDirective();
return;
- } else if (StrTok.hasUDSuffix()) {
- Diag(StrTok, diag::err_invalid_string_udl);
- DiscardUntilEndOfDirective();
- return;
} else {
- // Parse and validate the string, converting it into a unique ID.
- StringLiteralParser Literal(StrTok, *this,
- StringLiteralEvalMethod::Unevaluated);
- assert(Literal.isOrdinary() && "Didn't allow wide strings in");
- if (Literal.hadError) {
- DiscardUntilEndOfDirective();
- return;
- }
- if (Literal.Pascal) {
- Diag(StrTok, diag::err_pp_linemarker_invalid_filename);
- DiscardUntilEndOfDirective();
- return;
- }
-
+ SmallString<128> FilenameBuffer;
+ StringRef Filename = getSpelling(StrTok, FilenameBuffer);
+ GetFilenameSpellingForLineDirective(*this, StrTok.getLocation(), Filename);
// If a filename was present, read any flags that are present.
if (ReadLineMarkerFlags(IsFileEntry, IsFileExit, FileKind, *this))
return;
@@ -1821,8 +1807,8 @@ void Preprocessor::HandleDigitDirective(Token &DigitTok) {
// Exiting to an empty string means pop to the including file, so leave
// FilenameID as -1 in that case.
- if (!(IsFileExit && Literal.GetString().empty()))
- FilenameID = SourceMgr.getLineTableFilenameID(Literal.GetString());
+ if (!(IsFileExit && Filename.empty()))
+ FilenameID = SourceMgr.getLineTableFilenameID(Filename);
}
// Create a line note with this information.
diff --git a/clang/test/Frontend/linemarker-invalid-escape.c b/clang/test/Frontend/linemarker-invalid-escape.c
deleted file mode 100644
index a8239f81f152d..0000000000000
--- a/clang/test/Frontend/linemarker-invalid-escape.c
+++ /dev/null
@@ -1,5 +0,0 @@
-// RUN: %clang_cc1 -emit-llvm -o - -verify %s
-
-# 1 "original\x12source.c" // expected-error {{invalid escape sequence '\x12' in an unevaluated string literal}}
-
-int x = 0;
diff --git a/clang/test/Parser/cxx11-user-defined-literals.cpp b/clang/test/Parser/cxx11-user-defined-literals.cpp
index 968868509de19..6c15b8133b86f 100644
--- a/clang/test/Parser/cxx11-user-defined-literals.cpp
+++ b/clang/test/Parser/cxx11-user-defined-literals.cpp
@@ -3,8 +3,8 @@
// A ud-suffix cannot be used on string literals in a whole bunch of contexts:
#include "foo"_bar // expected-error {{expected "FILENAME" or <FILENAME>}}
-#line 1 "foo"_bar // expected-error {{user-defined suffix cannot be used here}}
-# 1 "foo"_bar 1 // expected-error {{user-defined suffix cannot be used here}}
+#line 1 "foo"_bar // expected-error {{invalid filename for #line directive}}
+# 1 "foo"_bar 1 // expected-error {{invalid filename for #line directive}}
#ident "foo"_bar // expected-error {{user-defined suffix cannot be used here}}
_Pragma("foo"_bar) // expected-error {{user-defined suffix cannot be used here}}
#pragma comment(lib, "foo"_bar) // expected-error {{user-defined suffix cannot be used here}}
diff --git a/clang/test/Preprocessor/line-directive-output.c b/clang/test/Preprocessor/line-directive-output.c
index 0e66487bd20c9..7774cc7c9cb31 100644
--- a/clang/test/Preprocessor/line-directive-output.c
+++ b/clang/test/Preprocessor/line-directive-output.c
@@ -75,9 +75,21 @@ extern int z;
# 44 "A.c"
# 49 "A.c"
-// CHECK: # 50 "a\n.c"
+// CHECK: # 50 "a\\n.c"
# 50 "a\n.c"
+// CHECK: # 1 "c:\\moo\\zar\\haz.h"
+#line 1 "c:\moo\zar\haz.h"
+
+// CHECK # 1 "original\x12source.c"
+# 1 "original\x12source.c"
+
+// CHECK # 1 "original\u1234"
+# 1 "original\u1234"
+
+// CHECK # 1 "original\u{1234}"
+# 1 "original\u{1234}"
+
# 1 "system.h" 3
# 2
void sys_foo(void);
diff --git a/clang/test/Preprocessor/line-directive.c b/clang/test/Preprocessor/line-directive.c
index 4fb05a4ea2f6e..530ddd686fdc8 100644
--- a/clang/test/Preprocessor/line-directive.c
+++ b/clang/test/Preprocessor/line-directive.c
@@ -127,6 +127,6 @@ undefined t; // expected-error {{unknown type name 'undefined'}}
// expected-error at -1{{MAIN2}}
#line 129 L"wide" // expected-error {{invalid filename for #line directive}}
-#line 130 "\x12" // expected-error {{invalid escape sequence '\x12' in an unevaluated string literal}}
+#line 130 "\x12"
# 131 U"hello" // expected-error {{invalid filename for line marker directive}}
-# 132 "\x13" // expected-error {{invalid escape sequence '\x13' in an unevaluated string literal}}
+# 132 "\x13" // expected-warning {{this style of line directive is a GNU extension}}
>From deaa9c245f445eea5c5e96913be8e6773e6f1663 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Thu, 23 Jul 2026 14:17:55 +0200
Subject: [PATCH 2/3] quotes are not escaped in header names
---
clang-tools-extra/clangd/unittests/PreambleTests.cpp | 7 -------
clang/test/Preprocessor/line-directive.c | 6 ++++++
2 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/clang-tools-extra/clangd/unittests/PreambleTests.cpp b/clang-tools-extra/clangd/unittests/PreambleTests.cpp
index 16a2f9448b1ec..a8d11bf681891 100644
--- a/clang-tools-extra/clangd/unittests/PreambleTests.cpp
+++ b/clang-tools-extra/clangd/unittests/PreambleTests.cpp
@@ -171,13 +171,6 @@ TEST(PreamblePatchTest, ContainsNewIncludes) {
Field(&Inclusion::HashLine, 4))));
}
-TEST(PreamblePatchTest, MainFileIsEscaped) {
- auto Includes = collectPatchedIncludes("#include <a.h>", "", "file\"name.cpp")
- .MainFileIncludes;
- EXPECT_THAT(Includes, ElementsAre(AllOf(Field(&Inclusion::Written, "<a.h>"),
- Field(&Inclusion::HashLine, 0))));
-}
-
TEST(PreamblePatchTest, PatchesPreambleIncludes) {
MockFS FS;
IgnoreDiagnostics Diags;
diff --git a/clang/test/Preprocessor/line-directive.c b/clang/test/Preprocessor/line-directive.c
index 530ddd686fdc8..97b9818d3d0aa 100644
--- a/clang/test/Preprocessor/line-directive.c
+++ b/clang/test/Preprocessor/line-directive.c
@@ -130,3 +130,9 @@ undefined t; // expected-error {{unknown type name 'undefined'}}
#line 130 "\x12"
# 131 U"hello" // expected-error {{invalid filename for line marker directive}}
# 132 "\x13" // expected-warning {{this style of line directive is a GNU extension}}
+
+#line 0 // expected-warning {{#line directive with zero argument is a GNU extension}}
+// #UNTERMINATED
+#line 1 "foo\""
+// expected-warning@#UNTERMINATED {{missing terminating '"' character}}
+// expected-warning@#UNTERMINATED {{extra tokens at end of #line directive}}
>From a003b63efd30b77f43586ce3107b51ab7138459b Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Thu, 23 Jul 2026 16:50:54 +0200
Subject: [PATCH 3/3] fix regex
---
clang/test/Frontend/rewrite-includes.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/test/Frontend/rewrite-includes.c b/clang/test/Frontend/rewrite-includes.c
index 7cca01c11e585..6aee1e0b0115d 100644
--- a/clang/test/Frontend/rewrite-includes.c
+++ b/clang/test/Frontend/rewrite-includes.c
@@ -268,5 +268,5 @@ static int unused;
// CHECKNL-NEXT: {{^}}static int unused;{{$}}
// CHECKNL-NEXT: {{^}}// ENDCOMPARE{{$}}
-// COMPILE: Inputs{{[/\\]}}rewrite-includes3.h:1:31: warning: implicit conversion changes signedness:
+// COMPILE: Inputs{{[/\\\\]}}rewrite-includes3.h:1:31: warning: implicit conversion changes signedness:
// COMPILE: rewrite-includes.c:25:12: warning: unused variable 'unused'
More information about the cfe-commits
mailing list