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
