Index: include/clang/AST/StmtCXX.h =================================================================== --- include/clang/AST/StmtCXX.h +++ include/clang/AST/StmtCXX.h @@ -405,10 +405,13 @@ SourceLocation getLocStart() const LLVM_READONLY { return CoreturnLoc; } SourceLocation getLocEnd() const LLVM_READONLY { - return getOperand()->getLocEnd(); + return getOperand() ? getOperand()->getLocEnd() : getLocStart(); } child_range children() { + if (!getOperand()) + return child_range(SubStmts + SubStmt::PromiseCall, + SubStmts + SubStmt::Count); return child_range(SubStmts, SubStmts + SubStmt::Count); } Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -509,6 +509,12 @@ "control may reach end of non-void block">; def err_falloff_nonvoid_block : Error< "control reaches end of non-void block">; +def warn_maybe_falloff_nonvoid_coroutine : Warning< + "control may reach end of non-void coroutine">, + InGroup; +def warn_falloff_nonvoid_coroutine : Warning< + "control reaches end of non-void coroutine">, + InGroup; def warn_suggest_noreturn_function : Warning< "%select{function|method}0 %1 could be declared with attribute 'noreturn'">, InGroup, DefaultIgnore; @@ -8578,6 +8584,13 @@ def err_coroutine_traits_missing_specialization : Error< "this function cannot be a coroutine: missing definition of " "specialization %q0">; +def err_implied_std_current_exception_not_found : Error< + "you need to include before defining a coroutine that declares " + "'set_exception'">; +def err_malformed_std_current_exception : Error< + "'std::current_exception' must be a function">; +def err_coroutine_promise_return_ill_formed : Error< + "%0 declares both 'return_value' and 'return_void'">; } let CategoryName = "Documentation Issue" in { Index: lib/Sema/AnalysisBasedWarnings.cpp =================================================================== --- lib/Sema/AnalysisBasedWarnings.cpp +++ lib/Sema/AnalysisBasedWarnings.cpp @@ -365,7 +365,7 @@ CFGStmt CS = ri->castAs(); const Stmt *S = CS.getStmt(); - if (isa(S)) { + if (isa(S) || isa(S)) { HasLiveReturn = true; continue; } @@ -416,7 +416,7 @@ unsigned diag_AlwaysFallThrough_HasNoReturn; unsigned diag_AlwaysFallThrough_ReturnsNonVoid; unsigned diag_NeverFallThroughOrReturn; - enum { Function, Block, Lambda } funMode; + enum { Function, Block, Lambda, Coroutine } funMode; SourceLocation FuncLoc; static CheckFallThroughDiagnostics MakeForFunction(const Decl *Func) { @@ -452,6 +452,19 @@ return D; } + static CheckFallThroughDiagnostics MakeForCoroutine(const Decl *Func) { + CheckFallThroughDiagnostics D; + D.FuncLoc = Func->getLocation(); + D.diag_MaybeFallThrough_HasNoReturn = 0; + D.diag_MaybeFallThrough_ReturnsNonVoid = + diag::warn_maybe_falloff_nonvoid_coroutine; + D.diag_AlwaysFallThrough_HasNoReturn = 0; + D.diag_AlwaysFallThrough_ReturnsNonVoid = + diag::warn_falloff_nonvoid_coroutine; + D.funMode = Coroutine; + return D; + } + static CheckFallThroughDiagnostics MakeForBlock() { CheckFallThroughDiagnostics D; D.diag_MaybeFallThrough_HasNoReturn = @@ -494,7 +507,13 @@ (!ReturnsVoid || D.isIgnored(diag::warn_suggest_noreturn_block, FuncLoc)); } - + if (funMode == Coroutine) { + return (ReturnsVoid || + D.isIgnored(diag::warn_maybe_falloff_nonvoid_function, FuncLoc) || + D.isIgnored(diag::warn_maybe_falloff_nonvoid_coroutine, + FuncLoc)) && + (!HasNoReturn); + } // For blocks / lambdas. return ReturnsVoid && !HasNoReturn; } @@ -515,7 +534,10 @@ bool HasNoReturn = false; if (const FunctionDecl *FD = dyn_cast(D)) { - ReturnsVoid = FD->getReturnType()->isVoidType(); + if (const CoroutineBodyStmt *CBody = dyn_cast(Body)) + ReturnsVoid = CBody->getFallthroughHandler() != nullptr; + else + ReturnsVoid = FD->getReturnType()->isVoidType(); HasNoReturn = FD->isNoReturn(); } else if (const ObjCMethodDecl *MD = dyn_cast(D)) { @@ -1986,13 +2008,22 @@ // Warning: check missing 'return' if (P.enableCheckFallThrough) { + auto IsCoro = [&]() { + if (auto *FD = dyn_cast(D)) + if (FD->getBody() && isa(FD->getBody())) + return true; + return false; + }; const CheckFallThroughDiagnostics &CD = - (isa(D) ? CheckFallThroughDiagnostics::MakeForBlock() - : (isa(D) && - cast(D)->getOverloadedOperator() == OO_Call && - cast(D)->getParent()->isLambda()) - ? CheckFallThroughDiagnostics::MakeForLambda() - : CheckFallThroughDiagnostics::MakeForFunction(D)); + (isa(D) + ? CheckFallThroughDiagnostics::MakeForBlock() + : (isa(D) && + cast(D)->getOverloadedOperator() == OO_Call && + cast(D)->getParent()->isLambda()) + ? CheckFallThroughDiagnostics::MakeForLambda() + : (IsCoro() + ? CheckFallThroughDiagnostics::MakeForCoroutine(D) + : CheckFallThroughDiagnostics::MakeForFunction(D))); CheckFallThroughForBody(S, D, Body, blkExpr, CD, AC); } Index: lib/Sema/SemaCoroutine.cpp =================================================================== --- lib/Sema/SemaCoroutine.cpp +++ lib/Sema/SemaCoroutine.cpp @@ -378,6 +378,34 @@ return Res; } +static ExprResult buildStdCurrentExceptionCall(Sema &S, SourceLocation Loc) { + NamespaceDecl *Std = S.getStdNamespace(); + if (!Std) { + S.Diag(Loc, diag::err_implied_std_current_exception_not_found); + return ExprError(); + } + LookupResult Result(S, &S.PP.getIdentifierTable().get("current_exception"), + Loc, Sema::LookupOrdinaryName); + if (!S.LookupQualifiedName(Result, Std)) { + S.Diag(Loc, diag::err_implied_std_current_exception_not_found); + return ExprError(); + } + + // FIXME The STL is free to provide more than one overload. + FunctionDecl *FD = Result.getAsSingle(); + if (!FD) { + S.Diag(Loc, diag::err_malformed_std_current_exception); + return ExprError(); + } + ExprResult Res = S.BuildDeclRefExpr(FD, FD->getType(), VK_LValue, Loc); + Res = S.ActOnCallExpr(/*Scope*/ nullptr, Res.get(), Loc, None, Loc); + if (Res.isInvalid()) { + S.Diag(Loc, diag::err_malformed_std_current_exception); + return ExprError(); + } + return Res; +} + void Sema::CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body) { FunctionScopeInfo *Fn = getCurFunction(); assert(Fn && !Fn->CoroutineStmts.empty() && "not a coroutine"); @@ -432,10 +460,59 @@ if (FinalSuspend.isInvalid()) return FD->setInvalidDecl(); - // FIXME: Perform analysis of set_exception call. - - // FIXME: Try to form 'p.return_void();' expression statement to handle + // Try to form 'p.return_void();' expression statement to handle // control flowing off the end of the coroutine. + // Also try to form 'p.set_exception(std::current_exception());' to handle + // uncaught exceptions. + ExprResult SetException; + StmtResult Fallthrough; + if (Fn->CoroutinePromise && + !Fn->CoroutinePromise->getType()->isDependentType()) { + CXXRecordDecl *RD = Fn->CoroutinePromise->getType()->getAsCXXRecordDecl(); + assert(RD && "Type should have already been checked"); + // [dcl.fct.def.coroutine]/4 + // The unqualified-ids 'return_void' and 'return_value' are looked up in + // the scope of class P. If both are found, the program is ill-formed. + DeclarationName RVoidDN = PP.getIdentifierInfo("return_void"); + LookupResult RVoidResult(*this, RVoidDN, Loc, Sema::LookupMemberName); + const bool HasRVoid = LookupQualifiedName(RVoidResult, RD); + + DeclarationName RValueDN = PP.getIdentifierInfo("return_value"); + LookupResult RValueResult(*this, RValueDN, Loc, Sema::LookupMemberName); + const bool HasRValue = LookupQualifiedName(RValueResult, RD); + + if (HasRVoid && HasRValue) { + // FIXME Improve this diagnostic + Diag(FD->getLocation(), diag::err_coroutine_promise_return_ill_formed) + << RD; + return FD->setInvalidDecl(); + } else if (HasRVoid) { + // If the unqualified-id return_void is found, flowing off the end of a + // coroutine is equivalent to a co_return with no operand. Otherwise, + // flowing off the end of a coroutine results in undefined behavior. + Fallthrough = BuildCoreturnStmt(FD->getLocation(), nullptr); + Fallthrough = ActOnFinishFullStmt(Fallthrough.get()); + if (Fallthrough.isInvalid()) + return FD->setInvalidDecl(); + } + + // [dcl.fct.def.coroutine]/3 + // The unqualified-id set_exception is found in the scope of P by class + // member access lookup (3.4.5). + DeclarationName SetExDN = PP.getIdentifierInfo("set_exception"); + LookupResult SetExResult(*this, SetExDN, Loc, Sema::LookupMemberName); + if (LookupQualifiedName(SetExResult, RD)) { + // Form the call 'p.set_exception(std::current_exception())' + SetException = buildStdCurrentExceptionCall(*this, Loc); + if (SetException.isInvalid()) + return FD->setInvalidDecl(); + Expr *E = SetException.get(); + SetException = buildPromiseCall(*this, Fn, Loc, "set_exception", E); + SetException = ActOnFinishFullExpr(SetException.get(), Loc); + if (SetException.isInvalid()) + return FD->setInvalidDecl(); + } + } // Build implicit 'p.get_return_object()' expression and form initialization // of return type from it. @@ -462,6 +539,5 @@ // Build body for the coroutine wrapper statement. Body = new (Context) CoroutineBodyStmt( Body, PromiseStmt.get(), InitialSuspend.get(), FinalSuspend.get(), - /*SetException*/nullptr, /*Fallthrough*/nullptr, - ReturnObject.get(), ParamMoves); + SetException.get(), Fallthrough.get(), ReturnObject.get(), ParamMoves); } Index: lib/Sema/TreeTransform.h =================================================================== --- lib/Sema/TreeTransform.h +++ lib/Sema/TreeTransform.h @@ -6652,6 +6652,7 @@ StmtResult TreeTransform::TransformCoroutineBodyStmt(CoroutineBodyStmt *S) { // The coroutine body should be re-formed by the caller if necessary. + // FIXME: The coroutine body is always rebuilt by ActOnFinishFunctionBody return getDerived().TransformStmt(S->getBody()); } Index: test/SemaCXX/coreturn.cpp =================================================================== --- /dev/null +++ test/SemaCXX/coreturn.cpp @@ -0,0 +1,76 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++14 -fcoroutines-ts -fsyntax-only -Wignored-qualifiers -Wno-error=return-type -verify -fblocks -Wno-unreachable-code -Wno-unused-value + +struct awaitable { + bool await_ready(); + void await_suspend(); // FIXME: coroutine_handle + void await_resume(); +} a; + +struct suspend_always { + bool await_ready() { return false; } + void await_suspend() {} + void await_resume() {} +}; + +struct suspend_never { + bool await_ready() { return true; } + void await_suspend() {} + void await_resume() {} +}; + +namespace std { +namespace experimental { + +template +struct coroutine_traits { using promise_type = typename Ret::promise_type; }; + +template +struct coroutine_handle {}; +} +} + +struct promise_void { + void get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend(); + awaitable yield_value(int); + void return_void(); +}; + +struct promise_float { + float get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend(); + awaitable yield_value(int); + void return_void(); +}; + +struct promise_int { + int get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend(); + awaitable yield_value(int); + void return_value(int); +}; + +template +struct std::experimental::coroutine_traits { using promise_type = promise_void; }; + +template +struct std::experimental::coroutine_traits { using promise_type = promise_float; }; + +template +struct std::experimental::coroutine_traits { using promise_type = promise_int; }; + +void test0() { co_await a; } +float test1() { co_await a; } + +int test2() { + co_await a; +} // expected-warning {{control reaches end of non-void coroutine}} + +int test3() { + co_await a; +b: + goto b; +} Index: test/SemaCXX/coroutines.cpp =================================================================== --- test/SemaCXX/coroutines.cpp +++ test/SemaCXX/coroutines.cpp @@ -313,6 +313,47 @@ co_await a; } +struct bad_promise_6 { + coro get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend(); + void return_void(); + void return_value(int) const; + void return_value(int); +}; +coro bad_implicit_return() { // expected-error {{'bad_promise_6' declares both 'return_value' and 'return_void'}} + co_await a; +} + +struct bad_promise_7 { + coro get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend(); + void return_void(); + void set_exception(int *); +}; +coro no_std_current_exc() { + // expected-error@-1 {{you need to include before defining a coroutine that declares 'set_exception'}} + co_await a; +} + +namespace std { +int *current_exception(); +} + +struct bad_promise_8 { + coro get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend(); + void return_void(); + void set_exception(); // expected-note {{function not viable}} + void set_exception(int *) __attribute__((unavailable)); // expected-note {{explicitly made unavailable}} + void set_exception(void *); // expected-note {{candidate function}} +}; +coro calls_set_exception() { + // expected-error@-1 {{call to unavailable member function 'set_exception'}} + co_await a; +} template<> struct std::experimental::coroutine_traits { using promise_type = promise; };