diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -135,6 +135,10 @@ Miscellaneous Clang Crashes Fixed ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +- Fixed a crash when parsing top-level ObjC blocks that aren't properly + terminated. Clang should now also recover better when an @end is missing + between blocks. + `Issue 64065 `_ Target Specific Changes ----------------------- diff --git a/clang/lib/Parse/ParseObjc.cpp b/clang/lib/Parse/ParseObjc.cpp --- a/clang/lib/Parse/ParseObjc.cpp +++ b/clang/lib/Parse/ParseObjc.cpp @@ -613,6 +613,19 @@ /*mayBeProtocolList=*/false); } +static bool isTopLevelObjCKeyword(tok::ObjCKeywordKind DirectiveKind) { + switch (DirectiveKind) { + case tok::objc_class: + case tok::objc_compatibility_alias: + case tok::objc_interface: + case tok::objc_implementation: + case tok::objc_protocol: + return true; + default: + return false; + } +} + /// objc-interface-decl-list: /// empty /// objc-interface-decl-list objc-property-decl [OBJC2] @@ -705,27 +718,33 @@ continue; } - // Otherwise, we have an @ directive, eat the @. - SourceLocation AtLoc = ConsumeToken(); // the "@" - if (Tok.is(tok::code_completion)) { + // Otherwise, we have an @ directive, peak at the next token + SourceLocation AtLoc = Tok.getLocation(); + const auto &NextTok = NextToken(); + if (NextTok.is(tok::code_completion)) { cutOffParsing(); Actions.CodeCompleteObjCAtDirective(getCurScope()); return; } - tok::ObjCKeywordKind DirectiveKind = Tok.getObjCKeywordID(); - + tok::ObjCKeywordKind DirectiveKind = NextTok.getObjCKeywordID(); if (DirectiveKind == tok::objc_end) { // @end -> terminate list + ConsumeToken(); // the "@" AtEnd.setBegin(AtLoc); AtEnd.setEnd(Tok.getLocation()); break; } else if (DirectiveKind == tok::objc_not_keyword) { - Diag(Tok, diag::err_objc_unknown_at); + Diag(NextTok, diag::err_objc_unknown_at); SkipUntil(tok::semi); continue; } - // Eat the identifier. + // Bail out as if we saw an '@end' + if (isTopLevelObjCKeyword(DirectiveKind)) + break; + + // Otherwise parse it as part of the current declaration. Eat "@identifier". + ConsumeToken(); ConsumeToken(); switch (DirectiveKind) { @@ -739,15 +758,6 @@ SkipUntil(tok::r_brace, tok::at, StopAtSemi); break; - case tok::objc_implementation: - case tok::objc_interface: - Diag(AtLoc, diag::err_objc_missing_end) - << FixItHint::CreateInsertion(AtLoc, "@end\n"); - Diag(CDecl->getBeginLoc(), diag::note_objc_container_start) - << (int)Actions.getObjCContainerKind(); - ConsumeToken(); - break; - case tok::objc_required: case tok::objc_optional: // This is only valid on protocols. @@ -816,13 +826,10 @@ } } - // We break out of the big loop in two cases: when we see @end or when we see - // EOF. In the former case, eat the @end. In the later case, emit an error. - if (Tok.is(tok::code_completion)) { - cutOffParsing(); - Actions.CodeCompleteObjCAtDirective(getCurScope()); - return; - } else if (Tok.isObjCAtKeyword(tok::objc_end)) { + // We break out of the big loop in 3 cases: when we see @end or when we see + // top-level ObjC keyword or EOF. In the former case, eat the @end. In the + // later cases, emit an error. + if (Tok.isObjCAtKeyword(tok::objc_end)) { ConsumeToken(); // the "end" identifier } else { Diag(Tok, diag::err_objc_missing_end) diff --git a/clang/test/Parser/missing-end-1-gh64065-nocrash.m b/clang/test/Parser/missing-end-1-gh64065-nocrash.m new file mode 100644 --- /dev/null +++ b/clang/test/Parser/missing-end-1-gh64065-nocrash.m @@ -0,0 +1,5 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +@interface Roo // expected-note {{class started here}} +// expected-error@+1 {{missing '@end'}} +@interface // expected-error {{expected identifier}} diff --git a/clang/test/Parser/missing-end-2.m b/clang/test/Parser/missing-end-2.m --- a/clang/test/Parser/missing-end-2.m +++ b/clang/test/Parser/missing-end-2.m @@ -1,9 +1,10 @@ -// RUN: %clang_cc1 -fsyntax-only -verify %s +// RUN: %clang_cc1 -fsyntax-only -Wno-objc-root-class -verify %s // rdar: //7824372 @interface A // expected-note {{class started here}} --(void) im0; +-(void) im0; // expected-note {{method 'im0' declared here}} +// expected-warning@+1 {{method definition for 'im0' not found}} @implementation A // expected-error {{missing '@end'}} @end @@ -13,7 +14,8 @@ @implementation B // expected-error {{missing '@end'}} @end -@interface C // expected-note 2 {{class started here}} +@interface C // expected-note 1 {{class started here}} @property int P; +// expected-note@+1 {{implementation started here}} @implementation C // expected-error 2 {{missing '@end'}} diff --git a/clang/test/Parser/missing-end-3.m b/clang/test/Parser/missing-end-3.m --- a/clang/test/Parser/missing-end-3.m +++ b/clang/test/Parser/missing-end-3.m @@ -1,10 +1,12 @@ // RUN: %clang_cc1 -fsyntax-only -verify %s // rdar://8283484 +// expected-note@+1 {{previous definition is here}} @interface blah { // expected-note {{class started here}} @private } // since I forgot the @end here it should say something +// expected-error@+1 {{duplicate interface definition for class 'blah'}} @interface blah // expected-error {{missing '@end'}} -@end // and Unknown type name 'end' here +@end diff --git a/clang/test/Parser/missing-end-4.m b/clang/test/Parser/missing-end-4.m --- a/clang/test/Parser/missing-end-4.m +++ b/clang/test/Parser/missing-end-4.m @@ -37,10 +37,10 @@ - (C

*) MyMeth {} @end -@interface I2 {} -@protocol P2; // expected-error {{illegal interface qualifier}} -@class C2; // expected-error {{illegal interface qualifier}} -@end +@interface I2 {} // expected-note {{class started here}} +@protocol P2; // expected-error {{missing '@end'}} +@class C2; +@end // expected-error {{'@end' must appear in an Objective-C context}} @interface I3 @end