[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