Skip to content

Commit 9db9b1a

Browse files
committed
[coroutines][PR40978] Emit error for co_yield within catch block
Summary: As reported in https://bugs.llvm.org/show_bug.cgi?id=40978, it's an error to use the `co_yield` or `co_await` keywords outside of a valid "suspension context" as defined by [expr.await]p2 of http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/n4775.pdf. Whether or not the current scope was in a function-try-block's (https://en.cppreference.com/w/cpp/language/function-try-block) handler could be determined using scope flag `Scope::FnTryCatchScope`. No such flag existed for a simple C++ catch statement, so this commit adds one. Reviewers: GorNishanov, tks2103, rsmith Reviewed By: GorNishanov Subscribers: EricWF, jdoerfert, cfe-commits, lewissbaker Tags: #clang Differential Revision: https://reviews.llvm.org/D59076 llvm-svn: 356296
1 parent 0bb9b5b commit 9db9b1a

File tree

6 files changed

+119
-25
lines changed

6 files changed

+119
-25
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9251,6 +9251,8 @@ def err_coroutine_objc_method : Error<
92519251
"Objective-C methods as coroutines are not yet supported">;
92529252
def err_coroutine_unevaluated_context : Error<
92539253
"'%0' cannot be used in an unevaluated context">;
9254+
def err_coroutine_within_handler : Error<
9255+
"'%0' cannot be used in the handler of a try block">;
92549256
def err_coroutine_outside_function : Error<
92559257
"'%0' cannot be used outside a function">;
92569258
def err_coroutine_invalid_func_context : Error<

clang/include/clang/Sema/Scope.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ class Scope {
131131

132132
/// We are between inheritance colon and the real class/struct definition scope.
133133
ClassInheritanceScope = 0x800000,
134+
135+
/// This is the scope of a C++ catch statement.
136+
CatchScope = 0x1000000,
134137
};
135138

136139
private:

clang/lib/Parse/ParseStmt.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2260,8 +2260,10 @@ StmtResult Parser::ParseCXXCatchBlock(bool FnCatch) {
22602260
// C++ 3.3.2p3:
22612261
// The name in a catch exception-declaration is local to the handler and
22622262
// shall not be redeclared in the outermost block of the handler.
2263-
ParseScope CatchScope(this, Scope::DeclScope | Scope::ControlScope |
2264-
(FnCatch ? Scope::FnTryCatchScope : 0));
2263+
unsigned ScopeFlags = Scope::DeclScope | Scope::ControlScope |
2264+
Scope::CatchScope |
2265+
(FnCatch ? Scope::FnTryCatchScope : 0);
2266+
ParseScope CatchScope(this, ScopeFlags);
22652267

22662268
// exception-declaration is equivalent to '...' or a parameter-declaration
22672269
// without default arguments.
@@ -2290,7 +2292,7 @@ StmtResult Parser::ParseCXXCatchBlock(bool FnCatch) {
22902292
return StmtError(Diag(Tok, diag::err_expected) << tok::l_brace);
22912293

22922294
// FIXME: Possible draft standard bug: attribute-specifier should be allowed?
2293-
StmtResult Block(ParseCompoundStatement());
2295+
StmtResult Block(ParseCompoundStatement(/*isStmtExpr=*/false, ScopeFlags));
22942296
if (Block.isInvalid())
22952297
return Block;
22962298

clang/lib/Sema/Scope.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,9 @@ void Scope::dumpImpl(raw_ostream &OS) const {
166166
{SEHExceptScope, "SEHExceptScope"},
167167
{SEHFilterScope, "SEHFilterScope"},
168168
{CompoundStmtScope, "CompoundStmtScope"},
169-
{ClassInheritanceScope, "ClassInheritanceScope"}};
169+
{ClassInheritanceScope, "ClassInheritanceScope"},
170+
{CatchScope, "CatchScope"},
171+
};
170172

171173
for (auto Info : FlagInfo) {
172174
if (Flags & Info.first) {

clang/lib/Sema/SemaCoroutine.cpp

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -185,21 +185,8 @@ static QualType lookupCoroutineHandleType(Sema &S, QualType PromiseType,
185185

186186
static bool isValidCoroutineContext(Sema &S, SourceLocation Loc,
187187
StringRef Keyword) {
188-
// 'co_await' and 'co_yield' are not permitted in unevaluated operands,
189-
// such as subexpressions of \c sizeof.
190-
//
191-
// [expr.await]p2, emphasis added: "An await-expression shall appear only in
192-
// a *potentially evaluated* expression within the compound-statement of a
193-
// function-body outside of a handler [...] A context within a function where
194-
// an await-expression can appear is called a suspension context of the
195-
// function." And per [expr.yield]p1: "A yield-expression shall appear only
196-
// within a suspension context of a function."
197-
if (S.isUnevaluatedContext()) {
198-
S.Diag(Loc, diag::err_coroutine_unevaluated_context) << Keyword;
199-
return false;
200-
}
201-
202-
// Per [expr.await]p2, any other usage must be within a function.
188+
// [expr.await]p2 dictates that 'co_await' and 'co_yield' must be used within
189+
// a function body.
203190
// FIXME: This also covers [expr.await]p2: "An await-expression shall not
204191
// appear in a default argument." But the diagnostic QoI here could be
205192
// improved to inform the user that default arguments specifically are not
@@ -668,12 +655,57 @@ bool Sema::ActOnCoroutineBodyStart(Scope *SC, SourceLocation KWLoc,
668655
return true;
669656
}
670657

658+
// Recursively walks up the scope hierarchy until either a 'catch' or a function
659+
// scope is found, whichever comes first.
660+
static bool isWithinCatchScope(Scope *S) {
661+
// 'co_await' and 'co_yield' keywords are disallowed within catch blocks, but
662+
// lambdas that use 'co_await' are allowed. The loop below ends when a
663+
// function scope is found in order to ensure the following behavior:
664+
//
665+
// void foo() { // <- function scope
666+
// try { //
667+
// co_await x; // <- 'co_await' is OK within a function scope
668+
// } catch { // <- catch scope
669+
// co_await x; // <- 'co_await' is not OK within a catch scope
670+
// []() { // <- function scope
671+
// co_await x; // <- 'co_await' is OK within a function scope
672+
// }();
673+
// }
674+
// }
675+
while (S && !(S->getFlags() & Scope::FnScope)) {
676+
if (S->getFlags() & Scope::CatchScope)
677+
return true;
678+
S = S->getParent();
679+
}
680+
return false;
681+
}
682+
683+
// [expr.await]p2, emphasis added: "An await-expression shall appear only in
684+
// a *potentially evaluated* expression within the compound-statement of a
685+
// function-body *outside of a handler* [...] A context within a function
686+
// where an await-expression can appear is called a suspension context of the
687+
// function."
688+
static void checkSuspensionContext(Sema &S, SourceLocation Loc,
689+
StringRef Keyword) {
690+
// First emphasis of [expr.await]p2: must be a potentially evaluated context.
691+
// That is, 'co_await' and 'co_yield' cannot appear in subexpressions of
692+
// \c sizeof.
693+
if (S.isUnevaluatedContext())
694+
S.Diag(Loc, diag::err_coroutine_unevaluated_context) << Keyword;
695+
696+
// Second emphasis of [expr.await]p2: must be outside of an exception handler.
697+
if (isWithinCatchScope(S.getCurScope()))
698+
S.Diag(Loc, diag::err_coroutine_within_handler) << Keyword;
699+
}
700+
671701
ExprResult Sema::ActOnCoawaitExpr(Scope *S, SourceLocation Loc, Expr *E) {
672702
if (!ActOnCoroutineBodyStart(S, Loc, "co_await")) {
673703
CorrectDelayedTyposInExpr(E);
674704
return ExprError();
675705
}
676706

707+
checkSuspensionContext(*this, Loc, "co_await");
708+
677709
if (E->getType()->isPlaceholderType()) {
678710
ExprResult R = CheckPlaceholderExpr(E);
679711
if (R.isInvalid()) return ExprError();
@@ -771,6 +803,8 @@ ExprResult Sema::ActOnCoyieldExpr(Scope *S, SourceLocation Loc, Expr *E) {
771803
return ExprError();
772804
}
773805

806+
checkSuspensionContext(*this, Loc, "co_yield");
807+
774808
// Build yield_value call.
775809
ExprResult Awaitable = buildPromiseCall(
776810
*this, getCurFunction()->CoroutinePromise, Loc, "yield_value", E);

clang/test/SemaCXX/coroutines.cpp

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -314,20 +314,71 @@ struct CtorDtor {
314314
}
315315
};
316316

317+
namespace std { class type_info; }
318+
317319
void unevaluated() {
318-
decltype(co_await a); // expected-error {{cannot be used in an unevaluated context}}
319-
sizeof(co_await a); // expected-error {{cannot be used in an unevaluated context}}
320-
typeid(co_await a); // expected-error {{cannot be used in an unevaluated context}}
321-
decltype(co_yield a); // expected-error {{cannot be used in an unevaluated context}}
322-
sizeof(co_yield a); // expected-error {{cannot be used in an unevaluated context}}
323-
typeid(co_yield a); // expected-error {{cannot be used in an unevaluated context}}
320+
decltype(co_await a); // expected-error {{'co_await' cannot be used in an unevaluated context}}
321+
// expected-warning@-1 {{declaration does not declare anything}}
322+
sizeof(co_await a); // expected-error {{'co_await' cannot be used in an unevaluated context}}
323+
// expected-error@-1 {{invalid application of 'sizeof' to an incomplete type 'void'}}
324+
typeid(co_await a); // expected-error {{'co_await' cannot be used in an unevaluated context}}
325+
// expected-warning@-1 {{expression with side effects has no effect in an unevaluated context}}
326+
// expected-warning@-2 {{expression result unused}}
327+
decltype(co_yield 1); // expected-error {{'co_yield' cannot be used in an unevaluated context}}
328+
// expected-warning@-1 {{declaration does not declare anything}}
329+
sizeof(co_yield 2); // expected-error {{'co_yield' cannot be used in an unevaluated context}}
330+
// expected-error@-1 {{invalid application of 'sizeof' to an incomplete type 'void'}}
331+
typeid(co_yield 3); // expected-error {{'co_yield' cannot be used in an unevaluated context}}
332+
// expected-warning@-1 {{expression with side effects has no effect in an unevaluated context}}
333+
// expected-warning@-2 {{expression result unused}}
324334
}
325335

326336
// [expr.await]p2: "An await-expression shall not appear in a default argument."
327337
// FIXME: A better diagnostic would explicitly state that default arguments are
328338
// not allowed. A user may not understand that this is "outside a function."
329339
void default_argument(int arg = co_await 0) {} // expected-error {{'co_await' cannot be used outside a function}}
330340

341+
void await_in_catch_coroutine() {
342+
try {
343+
} catch (...) { // FIXME: Emit a note diagnostic pointing out the try handler on this line.
344+
[]() -> void { co_await a; }(); // OK
345+
co_await a; // expected-error {{'co_await' cannot be used in the handler of a try block}}
346+
}
347+
}
348+
349+
void await_nested_in_catch_coroutine() {
350+
try {
351+
} catch (...) { // FIXME: Emit a note diagnostic pointing out the try handler on this line.
352+
try {
353+
co_await a; // expected-error {{'co_await' cannot be used in the handler of a try block}}
354+
[]() -> void { co_await a; }(); // OK
355+
} catch (...) {
356+
co_return 123;
357+
}
358+
}
359+
}
360+
361+
void await_in_lambda_in_catch_coroutine() {
362+
try {
363+
} catch (...) {
364+
[]() -> void { co_await a; }(); // OK
365+
}
366+
}
367+
368+
void yield_in_catch_coroutine() {
369+
try {
370+
} catch (...) {
371+
co_yield 1; // expected-error {{'co_yield' cannot be used in the handler of a try block}}
372+
}
373+
}
374+
375+
void return_in_catch_coroutine() {
376+
try {
377+
} catch (...) {
378+
co_return 123; // OK
379+
}
380+
}
381+
331382
constexpr auto constexpr_deduced_return_coroutine() {
332383
co_yield 0; // expected-error {{'co_yield' cannot be used in a constexpr function}}
333384
// expected-error@-1 {{'co_yield' cannot be used in a function with a deduced return type}}

0 commit comments

Comments
 (0)