https://github.com/max-winkler updated 
https://github.com/llvm/llvm-project/pull/234992

>From 3e99395507353db5a2b3a80faf6eafa1dc224d93 Mon Sep 17 00:00:00 2001
From: Max Winkler <[email protected]>
Date: Fri, 23 Oct 2026 14:54:14 +0200
Subject: [PATCH 1/2] [clang][Lex] Fix assertion on unclosed paren in PP
 directive

Clangd's preamble scan runs the preprocessor in single file parse mode.
If a directive contains an undefined identifier and ends inside a
subexpression (e.g. due to an unclosed parenthesis), PeekTok is already
eod when DiscardUntilEndOfDirective is called. That function lexes one
more token which is eof on the truncated buffer and triggers an
assertion.

Only discard if the current token is not eod, analogous to the other
callers of DiscardUntilEndOfDirective.

Fixes #226555
---
 clang/lib/Lex/PPExpressions.cpp   |  5 ++++-
 clang/unittests/Lex/LexerTest.cpp | 12 ++++++++++++
 2 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Lex/PPExpressions.cpp b/clang/lib/Lex/PPExpressions.cpp
index 731ee036f72627..ebf797c3ad6a1c 100644
--- a/clang/lib/Lex/PPExpressions.cpp
+++ b/clang/lib/Lex/PPExpressions.cpp
@@ -604,7 +604,10 @@ static bool EvaluateDirectiveSubExpr(PPValue &LHS, 
unsigned MinPrec,
     // identifier is undefined. If we've already seen one, there's no point in
     // continuing with the rest of the expression. Besides saving work, this
     // also prevents calling undefined function-like macros.
-    PP.DiscardUntilEndOfDirective(PeekTok);
+    // If the directive already ended (e.g. due to unclosed '('), there is
+    // nothing left to discard.
+    if (PeekTok.isNot(tok::eod))
+      PP.DiscardUntilEndOfDirective(PeekTok);
     return true;
   }
 
diff --git a/clang/unittests/Lex/LexerTest.cpp 
b/clang/unittests/Lex/LexerTest.cpp
index 8f632ccc5b9315..f808df7a9af546 100644
--- a/clang/unittests/Lex/LexerTest.cpp
+++ b/clang/unittests/Lex/LexerTest.cpp
@@ -581,6 +581,18 @@ TEST_F(LexerTest, AvoidPastEndOfStringDereference03) {
   EXPECT_TRUE(Lex("#include <\\\\\n").empty());
 }
 
+// Clangd uses single-file-parse mode in preamble scan on a truncated buffer.
+// An unclosed paren after an undefined identifier must not lex past the eod.
+TEST_F(LexerTest, SingleFileUnclosedParenUndefinedId01) {
+  PPOpts.SingleFileParseMode = true;
+  EXPECT_TRUE(Lex("#if (defined(FOO)\n").empty());
+}
+
+TEST_F(LexerTest, SingleFileUnclosedParenUndefinedId02) {
+  PPOpts.SingleFileParseMode = true;
+  EXPECT_TRUE(Lex("#if (FOO\n").empty());
+}
+
 TEST_F(LexerTest, StringizingRasString) {
   // For "std::string Lexer::Stringify(StringRef Str, bool Charify)".
   std::string String1 = R"(foo

>From 28e141d2b48d0d0ee23fee6e289078c3c4909973 Mon Sep 17 00:00:00 2001
From: Max Winkler <[email protected]>
Date: Thu, 29 Oct 2026 11:00:04 +0100
Subject: [PATCH 2/2] Address review comments: add CheckLex test and release
 note

Replaced one unit test with a CheckLex variant that also checks the
following line, and add a release note.
---
 clang/docs/ReleaseNotes.md        | 3 +++
 clang/unittests/Lex/LexerTest.cpp | 9 +++++++--
 2 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index d8762f8a8d54d5..6dee94cd5e60b5 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -1220,6 +1220,9 @@ documentation](CIR/index.md) for more information 
regarding ClangIR's status and
   parameter substituted through an alias template is used as the base of an
   unresolved name, such as `TT<T>::value` in a non-type template parameter's
   type. (#GH88592)
+- Fixed an assertion failure that caused clangd to crash on preprocessor 
+  directives that contain an undefined identifier and end inside an unfinished 
+  subexpression, e.g. due to unclosed parenthesis. (#GH226555)
 - Fixed an assertion when emitting base destructor as alias on targets (such
   as AVR) which have different address spaces for code and data. (#GH236285)
 
diff --git a/clang/unittests/Lex/LexerTest.cpp 
b/clang/unittests/Lex/LexerTest.cpp
index f808df7a9af546..3a0ef9bf5c15c5 100644
--- a/clang/unittests/Lex/LexerTest.cpp
+++ b/clang/unittests/Lex/LexerTest.cpp
@@ -585,12 +585,17 @@ TEST_F(LexerTest, AvoidPastEndOfStringDereference03) {
 // An unclosed paren after an undefined identifier must not lex past the eod.
 TEST_F(LexerTest, SingleFileUnclosedParenUndefinedId01) {
   PPOpts.SingleFileParseMode = true;
-  EXPECT_TRUE(Lex("#if (defined(FOO)\n").empty());
+  EXPECT_TRUE(Lex("#if (FOO\n").empty());
 }
 
 TEST_F(LexerTest, SingleFileUnclosedParenUndefinedId02) {
   PPOpts.SingleFileParseMode = true;
-  EXPECT_TRUE(Lex("#if (FOO\n").empty());
+  CheckLex(R"cc(
+    #if (FOO
+    int x;
+    #endif
+  )cc",
+           {tok::kw_int, tok::identifier, tok::semi});
 }
 
 TEST_F(LexerTest, StringizingRasString) {

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

Reply via email to