[clang] [clang-repl] Address error recovery fixing infinite loop while parsing (PR #127569)

Anutosh Bhat via cfe-commits cfe-commits at lists.llvm.org
Wed Feb 11 02:48:04 PST 2026


https://github.com/anutosh491 updated https://github.com/llvm/llvm-project/pull/127569

>From 5bda5cb946e8056a43e487e66487632729d37aee Mon Sep 17 00:00:00 2001
From: anutosh491 <andersonbhat491 at gmail.com>
Date: Tue, 18 Feb 2025 11:23:30 +0530
Subject: [PATCH 1/2] Address error recovery fixing infinite loop while parsing

---
 clang/lib/Parse/ParseStmt.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index cd4504630f871..a044c8acab927 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -1253,7 +1253,7 @@ StmtResult Parser::ParseCompoundStatementBody(bool isStmtExpr) {
 
   bool LastIsError = false;
   while (!tryParseMisplacedModuleImport() && Tok.isNot(tok::r_brace) &&
-         Tok.isNot(tok::eof)) {
+         Tok.isNot(tok::eof) && Tok.isNot(tok::annot_repl_input_end)) {
     if (Tok.is(tok::annot_pragma_unused)) {
       HandlePragmaUnused();
       continue;

>From 546dd5973e44e00c8ff9899d79b6014f6a304200 Mon Sep 17 00:00:00 2001
From: anutosh491 <andersonbhat491 at gmail.com>
Date: Wed, 11 Feb 2026 13:34:05 +0530
Subject: [PATCH 2/2] Apply discussed change as per
 https://github.com/llvm/llvm-project/pull/127569#issuecomment-3209476166 and
 added some tests

---
 clang/include/clang/Parse/Parser.h            | 12 ++++++++
 clang/lib/Parse/ParseCXXInlineMethods.cpp     | 16 +++++------
 clang/lib/Parse/ParseDecl.cpp                 |  8 +++---
 clang/lib/Parse/ParseDeclCXX.cpp              |  8 +++---
 clang/lib/Parse/ParseExprCXX.cpp              |  2 +-
 clang/lib/Parse/ParseHLSL.cpp                 |  2 +-
 clang/lib/Parse/ParseObjc.cpp                 |  2 +-
 clang/lib/Parse/ParseOpenMP.cpp               |  2 +-
 clang/lib/Parse/ParsePragma.cpp               | 20 ++++++-------
 clang/lib/Parse/ParseStmt.cpp                 |  2 +-
 clang/lib/Parse/Parser.cpp                    |  2 +-
 .../Interpreter/repl-input-end-recovery.cpp   | 28 +++++++++++++++++++
 12 files changed, 72 insertions(+), 32 deletions(-)
 create mode 100644 clang/test/Interpreter/repl-input-end-recovery.cpp

diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 5ae02e2b4e8ad..80353de0af32b 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -519,6 +519,14 @@ class Parser : public CodeCompletionHandler {
   bool SkipUntil(ArrayRef<tok::TokenKind> Toks,
                  SkipUntilFlags Flags = static_cast<SkipUntilFlags>(0));
 
+  /// Determine if the given token marks the end of the current partial
+  /// translation unit. In incremental (REPL) mode, this checks for
+  /// annot_repl_input_end. In normal compilation, this checks for EOF.
+  static bool isAtInputEnd(const Token &T, const LangOptions &LO) {
+    return T.is(tok::eof) ||
+           (LO.IncrementalExtensions && T.is(tok::annot_repl_input_end));
+  }
+
 private:
   Preprocessor &PP;
 
@@ -721,6 +729,10 @@ class Parser : public CodeCompletionHandler {
            Kind == tok::annot_repl_input_end;
   }
 
+  bool isAtInputEnd(const Token &T) const {
+    return isAtInputEnd(T, getLangOpts());
+  }
+
   static void setTypeAnnotation(Token &Tok, TypeResult T) {
     assert((T.isInvalid() || T.get()) &&
            "produced a valid-but-null type annotation?");
diff --git a/clang/lib/Parse/ParseCXXInlineMethods.cpp b/clang/lib/Parse/ParseCXXInlineMethods.cpp
index bc18881e89110..a83568be93056 100644
--- a/clang/lib/Parse/ParseCXXInlineMethods.cpp
+++ b/clang/lib/Parse/ParseCXXInlineMethods.cpp
@@ -426,7 +426,7 @@ void Parser::ParseLexedMethodDeclaration(LateParsedMethodDeclaration &LM) {
         Actions.ActOnParamDefaultArgumentError(Param, EqualLoc,
                                                /*DefaultArg=*/nullptr);
       } else {
-        if (Tok.isNot(tok::eof) || Tok.getEofData() != Param) {
+        if (!isAtInputEnd(Tok) || Tok.getEofData() != Param) {
           // The last two tokens are the terminator and the saved value of
           // Tok; the last token in the default argument is the one before
           // those.
@@ -441,7 +441,7 @@ void Parser::ParseLexedMethodDeclaration(LateParsedMethodDeclaration &LM) {
 
       // There could be leftover tokens (e.g. because of an error).
       // Skip through until we reach the 'end of default argument' token.
-      while (Tok.isNot(tok::eof))
+      while (!isAtInputEnd(Tok))
         ConsumeAnyToken();
 
       if (Tok.is(tok::eof) && Tok.getEofData() == Param)
@@ -534,7 +534,7 @@ void Parser::ParseLexedMethodDeclaration(LateParsedMethodDeclaration &LM) {
                                        DynamicExceptionRanges, NoexceptExpr,
                                        ExceptionSpecTokens);
 
-    if (Tok.isNot(tok::eof) || Tok.getEofData() != LM.Method)
+    if (!isAtInputEnd(Tok) || Tok.getEofData() != LM.Method)
       Diag(Tok.getLocation(), diag::err_except_spec_unparsed);
 
     // Attach the exception-specification to the method.
@@ -547,7 +547,7 @@ void Parser::ParseLexedMethodDeclaration(LateParsedMethodDeclaration &LM) {
 
     // There could be leftover tokens (e.g. because of an error).
     // Skip through until we reach the original token position.
-    while (Tok.isNot(tok::eof))
+    while (!isAtInputEnd(Tok))
       ConsumeAnyToken();
 
     // Clean up the remaining EOF token.
@@ -604,7 +604,7 @@ void Parser::ParseLexedMethodDef(LexedMethod &LM) {
   Actions.ActOnStartOfFunctionDef(getCurScope(), LM.D);
 
   llvm::scope_exit _([&]() {
-    while (Tok.isNot(tok::eof))
+    while (!isAtInputEnd(Tok))
       ConsumeAnyToken();
 
     if (Tok.is(tok::eof) && Tok.getEofData() == LM.D)
@@ -691,7 +691,7 @@ void Parser::ParseLexedMemberInitializer(LateParsedMemberInitializer &MI) {
   Actions.ActOnFinishCXXInClassMemberInitializer(MI.Field, EqualLoc, Init);
 
   // The next token should be our artificial terminating EOF token.
-  if (Tok.isNot(tok::eof)) {
+  if (!isAtInputEnd(Tok)) {
     if (!Init.isInvalid()) {
       SourceLocation EndLoc = PP.getLocForEndOfToken(PrevTokLocation);
       if (!EndLoc.isValid())
@@ -701,7 +701,7 @@ void Parser::ParseLexedMemberInitializer(LateParsedMemberInitializer &MI) {
     }
 
     // Consume tokens until we hit the artificial EOF.
-    while (Tok.isNot(tok::eof))
+    while (!isAtInputEnd(Tok))
       ConsumeAnyToken();
   }
   // Make sure this is *our* artificial EOF token.
@@ -797,7 +797,7 @@ void Parser::ParseLexedAttribute(LateParsedAttribute &LA,
 
   // Due to a parsing error, we either went over the cached tokens or
   // there are still cached tokens left, so we skip the leftover tokens.
-  while (Tok.isNot(tok::eof))
+  while (!isAtInputEnd(Tok))
     ConsumeAnyToken();
 
   if (Tok.is(tok::eof) && Tok.getEofData() == AttrEnd.getEofData())
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index df9e3878bffc0..8bea74976e312 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4832,7 +4832,7 @@ void Parser::ParseLexedCAttribute(LateParsedAttribute &LA, bool EnterScope,
 
   // Due to a parsing error, we either went over the cached tokens or
   // there are still cached tokens left, so we skip the leftover tokens.
-  while (Tok.isNot(tok::eof))
+  while (!isAtInputEnd(Tok))
     ConsumeAnyToken();
 
   // Consume the fake EOF token if it's there
@@ -4864,7 +4864,7 @@ void Parser::ParseStructUnionBody(SourceLocation RecordLoc,
 
   // While we still have something to read, read the declarations in the struct.
   while (!tryParseMisplacedModuleImport() && Tok.isNot(tok::r_brace) &&
-         Tok.isNot(tok::eof)) {
+         !isAtInputEnd(Tok)) {
     // Each iteration of this loop reads one struct-declaration.
 
     // Check for extraneous top-level semicolon.
@@ -8161,13 +8161,13 @@ TypeResult Parser::ParseTypeFromString(StringRef TypeStr, StringRef Context,
 
   // Check if we parsed the whole thing.
   if (Result.isUsable() &&
-      (Tok.isNot(tok::eof) || Tok.getEofData() != TypeStr.data())) {
+      (!isAtInputEnd(Tok) || Tok.getEofData() != TypeStr.data())) {
     Diag(Tok.getLocation(), diag::err_type_unparsed);
   }
 
   // There could be leftover tokens (e.g. because of an error).
   // Skip through until we reach the 'end of directive' token.
-  while (Tok.isNot(tok::eof))
+  while (!isAtInputEnd(Tok))
     ConsumeAnyToken();
 
   // Consume the end token.
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 9117a725843d9..749d13d7e81a0 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -229,7 +229,7 @@ void Parser::ParseInnerNamespace(const InnerNamespaceInfoList &InnerNSs,
                                  BalancedDelimiterTracker &Tracker) {
   if (index == InnerNSs.size()) {
     while (!tryParseMisplacedModuleImport() && Tok.isNot(tok::r_brace) &&
-           Tok.isNot(tok::eof)) {
+           !isAtInputEnd(Tok)) {
       ParsedAttributes DeclAttrs(AttrFactory);
       MaybeParseCXX11Attributes(DeclAttrs);
       ParsedAttributes EmptyDeclSpecAttrs(AttrFactory);
@@ -428,7 +428,7 @@ Decl *Parser::ParseExportDeclaration() {
   T.consumeOpen();
 
   while (!tryParseMisplacedModuleImport() && Tok.isNot(tok::r_brace) &&
-         Tok.isNot(tok::eof)) {
+         !isAtInputEnd(Tok)) {
     ParsedAttributes DeclAttrs(AttrFactory);
     MaybeParseCXX11Attributes(DeclAttrs);
     ParsedAttributes EmptyDeclSpecAttrs(AttrFactory);
@@ -3671,7 +3671,7 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc,
   if (TagDecl) {
     // While we still have something to read, read the member-declarations.
     while (!tryParseMisplacedModuleImport() && Tok.isNot(tok::r_brace) &&
-           Tok.isNot(tok::eof)) {
+           !isAtInputEnd(Tok)) {
       // Each iteration of this loop reads one member-declaration.
       ParseCXXClassMemberDeclarationWithPragmas(
           CurAS, AccessAttrs, static_cast<DeclSpec::TST>(TagType), TagDecl);
@@ -4441,7 +4441,7 @@ bool Parser::ParseCXX11AttributeArgs(
   if (LO.CPlusPlus) {
     TentativeParsingAction TPA(*this);
     bool HasInvalidArgument = false;
-    while (Tok.isNot(tok::r_paren) && Tok.isNot(tok::eof)) {
+    while (Tok.isNot(tok::r_paren) && !isAtInputEnd(Tok)) {
       if (Tok.isOneOf(tok::hash, tok::hashhash)) {
         Diag(Tok.getLocation(), diag::ext_invalid_attribute_argument)
             << PP.getSpelling(Tok);
diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp
index 842b52375eb14..2d2d8e1dcac81 100644
--- a/clang/lib/Parse/ParseExprCXX.cpp
+++ b/clang/lib/Parse/ParseExprCXX.cpp
@@ -3698,7 +3698,7 @@ Parser::ParseCXXAmbiguousParenExpression(ParenParseOption &ExprType,
 
   // Match the ')'.
   if (Result.isInvalid()) {
-    while (Tok.isNot(tok::eof))
+    while (!isAtInputEnd(Tok))
       ConsumeAnyToken();
     assert(Tok.getEofData() == AttrEnd.getEofData());
     ConsumeAnyToken();
diff --git a/clang/lib/Parse/ParseHLSL.cpp b/clang/lib/Parse/ParseHLSL.cpp
index c727ee3a1f1a6..a4a948ee10f9b 100644
--- a/clang/lib/Parse/ParseHLSL.cpp
+++ b/clang/lib/Parse/ParseHLSL.cpp
@@ -77,7 +77,7 @@ Decl *Parser::ParseHLSLBuffer(SourceLocation &DeclEnd,
                                             T.getOpenLocation());
   Actions.ProcessDeclAttributeList(Actions.CurScope, D, Attrs);
 
-  while (Tok.isNot(tok::r_brace) && Tok.isNot(tok::eof)) {
+  while (Tok.isNot(tok::r_brace) && !isAtInputEnd(Tok)) {
     // FIXME: support attribute on constants inside cbuffer/tbuffer.
     ParsedAttributes DeclAttrs(AttrFactory);
     ParsedAttributes EmptyDeclSpecAttrs(AttrFactory);
diff --git a/clang/lib/Parse/ParseObjc.cpp b/clang/lib/Parse/ParseObjc.cpp
index 0b9f113d9edc7..b59db1cb1f920 100644
--- a/clang/lib/Parse/ParseObjc.cpp
+++ b/clang/lib/Parse/ParseObjc.cpp
@@ -3324,7 +3324,7 @@ void Parser::ParseLexedObjCMethodDefs(LexedMethod &LM, bool parseMethod) {
     // expensive isBeforeInTranslationUnit call.
     if (PP.getSourceManager().isBeforeInTranslationUnit(Tok.getLocation(),
                                                      OrigLoc))
-      while (Tok.getLocation() != OrigLoc && Tok.isNot(tok::eof))
+      while (Tok.getLocation() != OrigLoc && !isAtInputEnd(Tok))
         ConsumeAnyToken();
   }
   // Clean up the remaining EOF token, only if it's inserted by us. Otherwise
diff --git a/clang/lib/Parse/ParseOpenMP.cpp b/clang/lib/Parse/ParseOpenMP.cpp
index b41803d23cb25..08237e69524ff 100644
--- a/clang/lib/Parse/ParseOpenMP.cpp
+++ b/clang/lib/Parse/ParseOpenMP.cpp
@@ -1973,7 +1973,7 @@ Parser::DeclGroupPtrTy Parser::ParseOpenMPDeclarativeDirectiveWithExtDecl(
       CachedTokens Toks;
       unsigned Cnt = 1;
       Toks.push_back(Tok);
-      while (Cnt && Tok.isNot(tok::eof)) {
+      while (Cnt && !isAtInputEnd(Tok)) {
         (void)ConsumeAnyToken();
         if (Tok.isOneOf(tok::annot_pragma_openmp, tok::annot_attr_openmp))
           ++Cnt;
diff --git a/clang/lib/Parse/ParsePragma.cpp b/clang/lib/Parse/ParsePragma.cpp
index def2817c930b2..3347d89762dbe 100644
--- a/clang/lib/Parse/ParsePragma.cpp
+++ b/clang/lib/Parse/ParsePragma.cpp
@@ -1063,7 +1063,7 @@ void Parser::HandlePragmaMSPragma() {
   if (!(this->*Handler)(PragmaName, PragmaLocation)) {
     // Pragma handling failed, and has been diagnosed.  Slurp up the tokens
     // until eof (really end of line) to prevent follow-on errors.
-    while (Tok.isNot(tok::eof))
+    while (!isAtInputEnd(Tok))
       PP.Lex(Tok);
     PP.Lex(Tok);
   }
@@ -1140,7 +1140,7 @@ bool Parser::HandlePragmaMSSection(StringRef PragmaName,
     return false;
   }
   PP.Lex(Tok); // )
-  if (Tok.isNot(tok::eof)) {
+  if (!isAtInputEnd(Tok)) {
     PP.Diag(PragmaLocation, diag::warn_pragma_extra_tokens_at_eol)
         << PragmaName;
     return false;
@@ -1222,7 +1222,7 @@ bool Parser::HandlePragmaMSSegment(StringRef PragmaName,
     return false;
   }
   PP.Lex(Tok); // )
-  if (Tok.isNot(tok::eof)) {
+  if (!isAtInputEnd(Tok)) {
     PP.Diag(PragmaLocation, diag::warn_pragma_extra_tokens_at_eol)
         << PragmaName;
     return false;
@@ -1596,7 +1596,7 @@ bool Parser::HandlePragmaLoopHint(LoopHint &Hint) {
       if (Toks.size() > 2) {
         Diag(Tok.getLocation(), diag::warn_pragma_extra_tokens_at_eol)
             << PragmaLoopHintString(Info->PragmaName, Info->Option);
-        while (Tok.isNot(tok::eof))
+        while (!isAtInputEnd(Tok))
           ConsumeAnyToken();
       }
 
@@ -1631,10 +1631,10 @@ bool Parser::HandlePragmaLoopHint(LoopHint &Hint) {
 
       // Tokens following an error in an ill-formed constant expression will
       // remain in the token stream and must be removed.
-      if (Tok.isNot(tok::eof)) {
+      if (!isAtInputEnd(Tok)) {
         Diag(Tok.getLocation(), diag::warn_pragma_extra_tokens_at_eol)
             << PragmaLoopHintString(Info->PragmaName, Info->Option);
-        while (Tok.isNot(tok::eof))
+        while (!isAtInputEnd(Tok))
           ConsumeAnyToken();
       }
 
@@ -1657,10 +1657,10 @@ bool Parser::HandlePragmaLoopHint(LoopHint &Hint) {
 
     // Tokens following an error in an ill-formed constant expression will
     // remain in the token stream and must be removed.
-    if (Tok.isNot(tok::eof)) {
+    if (!isAtInputEnd(Tok)) {
       Diag(Tok.getLocation(), diag::warn_pragma_extra_tokens_at_eol)
           << PragmaLoopHintString(Info->PragmaName, Info->Option);
-      while (Tok.isNot(tok::eof))
+      while (!isAtInputEnd(Tok))
         ConsumeAnyToken();
     }
 
@@ -2113,7 +2113,7 @@ void Parser::HandlePragmaAttribute() {
 
   // Tokens following an ill-formed attribute will remain in the token stream
   // and must be removed.
-  if (Tok.isNot(tok::eof)) {
+  if (!isAtInputEnd(Tok)) {
     Diag(Tok, diag::err_pragma_attribute_extra_tokens_after_attribute);
     SkipToEnd();
     return;
@@ -2823,7 +2823,7 @@ void PragmaSupportHandler<StartTok, EndTok, UnexpectedDiag>::HandlePragma(
   Tok.setKind(StartTok);
   Tok.setLocation(Introducer.Loc);
 
-  while (Tok.isNot(tok::eod) && Tok.isNot(tok::eof)) {
+  while (Tok.isNot(tok::eod) && !Parser::isAtInputEnd(Tok, PP.getLangOpts())) {
     Pragma.push_back(Tok);
     PP.Lex(Tok);
     if (Tok.is(StartTok)) {
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index 1f0f927053e51..2a3906fd5ca5e 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -1159,7 +1159,7 @@ StmtResult Parser::ParseCompoundStatementBody(bool isStmtExpr) {
 
   bool LastIsError = false;
   while (!tryParseMisplacedModuleImport() && Tok.isNot(tok::r_brace) &&
-         Tok.isNot(tok::eof) && Tok.isNot(tok::annot_repl_input_end)) {
+         !isAtInputEnd(Tok)) {
     if (Tok.is(tok::annot_pragma_unused)) {
       HandlePragmaUnused();
       continue;
diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp
index be9e0a39a3781..4f5aed482e552 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -310,7 +310,7 @@ bool Parser::SkipUntil(ArrayRef<tok::TokenKind> Toks, SkipUntilFlags Flags) {
     if (Toks.size() == 1 && Toks[0] == tok::eof &&
         !HasFlagsSet(Flags, StopAtSemi) &&
         !HasFlagsSet(Flags, StopAtCodeCompletion)) {
-      while (Tok.isNot(tok::eof))
+      while (!isAtInputEnd(Tok))
         ConsumeAnyToken();
       return true;
     }
diff --git a/clang/test/Interpreter/repl-input-end-recovery.cpp b/clang/test/Interpreter/repl-input-end-recovery.cpp
new file mode 100644
index 0000000000000..8512d999f00e6
--- /dev/null
+++ b/clang/test/Interpreter/repl-input-end-recovery.cpp
@@ -0,0 +1,28 @@
+// REQUIRES: host-supports-jit
+// RUN: cat %s | clang-repl -Xcc -Xclang -Xcc -verify -Xcc -Xclang -Xcc -verify-ignore-unexpected | FileCheck %s
+
+extern "C" int printf(const char *, ...);
+
+void foo() { int x = 5;
+
+// expected-error {{expected '}'}}
+int g1 = 0; void foo() { g1 = 5; } foo(); printf("g1 = %d\n", g1);
+// CHECK: g1 = 5
+
+void (*test)() = [](){ if } // expected-error {{expected '(' after 'if'}}
+
+int g2 = 0; void (*test)() = [](){ if (1) g2 = 7; }; test(); printf("g2 = %d\n", g2);
+// CHECK: g2 = 7
+
+namespace myspace {
+
+// expected-error {{expected '}'}}
+namespace myspace { int v = 11; } printf("v = %d\n", myspace::v);
+// CHECK: v = 11
+
+struct X { using type = int }; // expected-error {{expected ';' after alias declaration}}
+
+struct X { using type = int; }; X::type t = 3; printf("t = %d\n", t);
+// CHECK: t = 3
+
+%quit
\ No newline at end of file



More information about the cfe-commits mailing list