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
