[clang] [clang][Parse] Fix stale `CurParsedObjCImpl` after an `@implementation` ends early (PR #219953)
Akira Hatanaka via cfe-commits
cfe-commits at lists.llvm.org
Wed Sep 16 09:03:00 PDT 2026
https://github.com/ahatanak updated https://github.com/llvm/llvm-project/pull/219953
>From 056d41605942f677106df91d2f88f38210759c8d Mon Sep 17 00:00:00 2001
From: Akash Manna <akash.manna.mymail at gmail.com>
Date: Mon, 31 Aug 2026 17:29:08 +0530
Subject: [PATCH 1/2] [clang][Parse] Fix stale CurParsedObjCImpl after an
@implementation ends early
ObjCImplParsingDataRAII only reset CurParsedObjCImpl in its destructor, but
an @implementation can be finished well before that: CheckNestedObjCContexts
ends it as soon as a nested @interface/@protocol/@implementation shows up,
and that container is then parsed inside the same frame. Anything defined in
there still looked like it was inside the already finished @implementation,
so ParseFunctionDefinition queued the body into a LateParsedObjCMethods that
nobody was going to drain again, and the destructor's
assert(LateParsedObjCMethods.empty()) fired.
finish() now restores the previous CurParsedObjCImpl the moment the
@implementation ends, instead of the destructor nulling it later. Restoring
rather than clearing also covers the mirror image: an @implementation nested
through a namespace inside another one used to wipe out the outer, still open
one when it ended, and the next method definition in the outer one tripped
the "Method out of @implementation" assert.
Fixes #209503
---
clang/docs/ReleaseNotes.md | 3 +++
clang/include/clang/Parse/Parser.h | 4 +++-
clang/lib/Parse/ParseObjc.cpp | 5 ++++-
clang/test/Parser/GH209503.mm | 27 +++++++++++++++++++++++++++
4 files changed, 37 insertions(+), 2 deletions(-)
create mode 100644 clang/test/Parser/GH209503.mm
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 3cca316a91d4d..1799c5c9ac23a 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -709,6 +709,9 @@ features cannot lower the translation-unit ABI level;
- Fixed an assertion when the `dim` argument to an OpenACC `gang` clause
evaluated to a value not representable by a signed integer, such as an
unsigned wrap around. (#GH221418)
+- Fixed an assertion failure when a method or function definition follows an
+ Objective-C `@implementation` that was ended by a nested `@interface`,
+ `@protocol` or `@implementation` before its `@end`. (#GH209503)
### OpenACC Specific Changes
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 6913c42884a36..9b2de3a10e988 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -5710,7 +5710,8 @@ class Parser : public CodeCompletionHandler {
LateParsedObjCMethodContainer LateParsedObjCMethods;
ObjCImplParsingDataRAII(Parser &parser, Decl *D)
- : P(parser), Dcl(D), HasCFunction(false) {
+ : P(parser), Dcl(D), HasCFunction(false),
+ PrevParsedObjCImpl(parser.CurParsedObjCImpl) {
P.CurParsedObjCImpl = this;
Finished = false;
}
@@ -5720,6 +5721,7 @@ class Parser : public CodeCompletionHandler {
bool isFinished() const { return Finished; }
private:
+ ObjCImplParsingDataRAII *PrevParsedObjCImpl;
bool Finished;
};
ObjCImplParsingDataRAII *CurParsedObjCImpl;
diff --git a/clang/lib/Parse/ParseObjc.cpp b/clang/lib/Parse/ParseObjc.cpp
index a70f6356ae013..7ca08cb2112e6 100644
--- a/clang/lib/Parse/ParseObjc.cpp
+++ b/clang/lib/Parse/ParseObjc.cpp
@@ -2035,7 +2035,6 @@ Parser::ObjCImplParsingDataRAII::~ObjCImplParsingDataRAII() {
<< SemaObjC::OCK_Implementation;
}
}
- P.CurParsedObjCImpl = nullptr;
assert(LateParsedObjCMethods.empty());
}
@@ -2061,6 +2060,10 @@ void Parser::ObjCImplParsingDataRAII::finish(SourceRange AtEnd) {
delete *I;
LateParsedObjCMethods.clear();
+ // Parsing may go on in the enclosing frame before this object is destroyed
+ // (e.g. a nested @interface ended the @implementation early), so stop being
+ // the current @implementation now rather than in the destructor.
+ P.CurParsedObjCImpl = PrevParsedObjCImpl;
Finished = true;
}
diff --git a/clang/test/Parser/GH209503.mm b/clang/test/Parser/GH209503.mm
new file mode 100644
index 0000000000000..43d14b242dbcc
--- /dev/null
+++ b/clang/test/Parser/GH209503.mm
@@ -0,0 +1,27 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Wno-objc-root-class %s
+
+// Method and function bodies are parsed when their @implementation ends; make
+// sure they are queued into the right @implementation (if any) after a nested
+// container has ended one.
+
+ at interface Z
+ at end
+ at interface A
+ at end
+
+ at implementation Z
+namespace N {
+ at implementation A // expected-error {{Objective-C declarations may only appear in global scope}}
+ at end
+}
+- (void)m {
+ undeclared(); // expected-error {{use of undeclared identifier 'undeclared'}}
+}
+ at end
+
+ at implementation NSArray // expected-warning {{cannot find interface declaration for 'NSArray'}} \
+ // expected-note {{implementation started here}}
+ at interface NSIndexSet // expected-error {{missing '@end'}} \
+ // expected-note {{class started here}}
+// expected-warning at +1 {{function definition inside an Objective-C container is deprecated}}
+void f(void) {} // expected-error {{missing '@end'}}
>From 6f1deae95075d7b7420c30b972cda0addf90b813 Mon Sep 17 00:00:00 2001
From: Akash Manna <akash.manna.mymail at gmail.com>
Date: Fri, 11 Sep 2026 08:05:23 +0530
Subject: [PATCH 2/2] Document `PrevParsedObjCImpl`
Explain that it is only ever non-null for invalid code, where an
@implementation starts while a previous one is still open.
---
clang/include/clang/Parse/Parser.h | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 9b2de3a10e988..960b3c2485db0 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -5721,6 +5721,11 @@ class Parser : public CodeCompletionHandler {
bool isFinished() const { return Finished; }
private:
+ /// The \@implementation that was still open when this one started; made
+ /// current again once this one finishes. Only invalid code has one: an
+ /// \@implementation that starts while a previous \@implementation is
+ /// still open (e.g. through an intervening namespace). For valid code
+ /// this is always null.
ObjCImplParsingDataRAII *PrevParsedObjCImpl;
bool Finished;
};
More information about the cfe-commits
mailing list