Author: Max Winkler
Date: 2026-11-02T10:55:30Z
New Revision: 836f3e4a8c6551feb35cb7a48e005b653aa774ca

URL: 
https://github.com/llvm/llvm-project/commit/836f3e4a8c6551feb35cb7a48e005b653aa774ca
DIFF: 
https://github.com/llvm/llvm-project/commit/836f3e4a8c6551feb35cb7a48e005b653aa774ca.diff

LOG: [clang][Lex] Fix assertion on unclosed paren in PP directive (#234992)

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

Added: 
    

Modified: 
    clang/docs/ReleaseNotes.md
    clang/lib/Lex/PPExpressions.cpp
    clang/unittests/Lex/LexerTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 6d1dbbbb6eb77b..52fa928443f347 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 
diff erent address spaces for code and data. (#GH236285)
 

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..3a0ef9bf5c15c5 100644
--- a/clang/unittests/Lex/LexerTest.cpp
+++ b/clang/unittests/Lex/LexerTest.cpp
@@ -581,6 +581,23 @@ 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 (FOO\n").empty());
+}
+
+TEST_F(LexerTest, SingleFileUnclosedParenUndefinedId02) {
+  PPOpts.SingleFileParseMode = true;
+  CheckLex(R"cc(
+    #if (FOO
+    int x;
+    #endif
+  )cc",
+           {tok::kw_int, tok::identifier, tok::semi});
+}
+
 TEST_F(LexerTest, StringizingRasString) {
   // For "std::string Lexer::Stringify(StringRef Str, bool Charify)".
   std::string String1 = R"(foo


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

Reply via email to