[coroutines] Build fallthrough and set_exception statements.

Summary:
This patch adds semantic checking and building of the fall-through `co_return;` statement as well as the `p.set_exception(std::current_exception())` call for handling uncaught exceptions.

The fall-through statement is built and checked according to:
> [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. 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.

Similarly the `set_exception` call is only built when that unqualified-id is found in the scope of class P.

Additionally this patch adds fall-through warnings for non-void returning coroutines. Since it's surprising undefined behavior I thought it would be important to add the warning right away. 


Reviewers: majnemer, GorNishanov, rsmith

Subscribers: mehdi_amini, cfe-commits

Differential Revision: https://reviews.llvm.org/D25349

llvm-svn: 285271
This commit is contained in:
Eric Fiselier 2016-10-27 07:30:31 +00:00
parent ec47065795
commit 709d1b30ab
7 changed files with 256 additions and 21 deletions

View File

@ -405,10 +405,13 @@ public:
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);
}

View File

@ -512,6 +512,12 @@ def err_maybe_falloff_nonvoid_block : Error<
"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<ReturnType>;
def warn_falloff_nonvoid_coroutine : Warning<
"control reaches end of non-void coroutine">,
InGroup<ReturnType>;
def warn_suggest_noreturn_function : Warning<
"%select{function|method}0 %1 could be declared with attribute 'noreturn'">,
InGroup<MissingNoreturn>, DefaultIgnore;
@ -8643,6 +8649,13 @@ def err_implied_std_coroutine_traits_promise_type_not_class : Error<
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 <exception> before defining a coroutine that implicitly "
"uses '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 {

View File

@ -365,7 +365,7 @@ static ControlFlowKind CheckFallThrough(AnalysisDeclContext &AC) {
CFGStmt CS = ri->castAs<CFGStmt>();
const Stmt *S = CS.getStmt();
if (isa<ReturnStmt>(S)) {
if (isa<ReturnStmt>(S) || isa<CoreturnStmt>(S)) {
HasLiveReturn = true;
continue;
}
@ -416,7 +416,7 @@ struct CheckFallThroughDiagnostics {
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 @@ struct CheckFallThroughDiagnostics {
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 @@ struct CheckFallThroughDiagnostics {
(!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;
}
@ -514,11 +533,14 @@ static void CheckFallThroughForBody(Sema &S, const Decl *D, const Stmt *Body,
bool ReturnsVoid = false;
bool HasNoReturn = false;
if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
ReturnsVoid = FD->getReturnType()->isVoidType();
if (const auto *FD = dyn_cast<FunctionDecl>(D)) {
if (const auto *CBody = dyn_cast<CoroutineBodyStmt>(Body))
ReturnsVoid = CBody->getFallthroughHandler() != nullptr;
else
ReturnsVoid = FD->getReturnType()->isVoidType();
HasNoReturn = FD->isNoReturn();
}
else if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(D)) {
else if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) {
ReturnsVoid = MD->getReturnType()->isVoidType();
HasNoReturn = MD->hasAttr<NoReturnAttr>();
}
@ -1986,13 +2008,22 @@ AnalysisBasedWarnings::IssueWarnings(sema::AnalysisBasedWarnings::Policy P,
// Warning: check missing 'return'
if (P.enableCheckFallThrough) {
auto IsCoro = [&]() {
if (auto *FD = dyn_cast<FunctionDecl>(D))
if (FD->getBody() && isa<CoroutineBodyStmt>(FD->getBody()))
return true;
return false;
};
const CheckFallThroughDiagnostics &CD =
(isa<BlockDecl>(D) ? CheckFallThroughDiagnostics::MakeForBlock()
: (isa<CXXMethodDecl>(D) &&
cast<CXXMethodDecl>(D)->getOverloadedOperator() == OO_Call &&
cast<CXXMethodDecl>(D)->getParent()->isLambda())
? CheckFallThroughDiagnostics::MakeForLambda()
: CheckFallThroughDiagnostics::MakeForFunction(D));
(isa<BlockDecl>(D)
? CheckFallThroughDiagnostics::MakeForBlock()
: (isa<CXXMethodDecl>(D) &&
cast<CXXMethodDecl>(D)->getOverloadedOperator() == OO_Call &&
cast<CXXMethodDecl>(D)->getParent()->isLambda())
? CheckFallThroughDiagnostics::MakeForLambda()
: (IsCoro()
? CheckFallThroughDiagnostics::MakeForCoroutine(D)
: CheckFallThroughDiagnostics::MakeForFunction(D)));
CheckFallThroughForBody(S, D, Body, blkExpr, CD, AC);
}

View File

@ -378,6 +378,34 @@ StmtResult Sema::BuildCoreturnStmt(SourceLocation Loc, Expr *E) {
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<FunctionDecl>();
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 @@ void Sema::CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body) {
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 @@ void Sema::CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body) {
// 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);
}

View File

@ -6654,6 +6654,7 @@ template<typename Derived>
StmtResult
TreeTransform<Derived>::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());
}

View File

@ -0,0 +1,73 @@
// 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 <class Ret, typename... T>
struct coroutine_traits { using promise_type = typename Ret::promise_type; };
template <class Promise = void>
struct coroutine_handle {};
}
}
struct promise_void {
void get_return_object();
suspend_always initial_suspend();
suspend_always final_suspend();
void return_void();
};
struct promise_float {
float get_return_object();
suspend_always initial_suspend();
suspend_always final_suspend();
void return_void();
};
struct promise_int {
int get_return_object();
suspend_always initial_suspend();
suspend_always final_suspend();
void return_value(int);
};
template <typename... T>
struct std::experimental::coroutine_traits<void, T...> { using promise_type = promise_void; };
template <typename... T>
struct std::experimental::coroutine_traits<float, T...> { using promise_type = promise_float; };
template <typename... T>
struct std::experimental::coroutine_traits<int, T...> { 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;
}

View File

@ -118,9 +118,6 @@ struct promise_void {
void get_return_object();
suspend_always initial_suspend();
suspend_always final_suspend();
awaitable yield_value(int);
awaitable yield_value(yielded_thing);
not_awaitable yield_value(void());
void return_void();
};
@ -313,6 +310,47 @@ coro<bad_promise_5> bad_final_suspend() { // expected-error {{no member named 'a
co_await a;
}
struct bad_promise_6 {
coro<bad_promise_6> 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_promise_6> bad_implicit_return() { // expected-error {{'bad_promise_6' declares both 'return_value' and 'return_void'}}
co_await a;
}
struct bad_promise_7 {
coro<bad_promise_7> get_return_object();
suspend_always initial_suspend();
suspend_always final_suspend();
void return_void();
void set_exception(int *);
};
coro<bad_promise_7> no_std_current_exc() {
// expected-error@-1 {{you need to include <exception> before defining a coroutine that implicitly uses 'set_exception'}}
co_await a;
}
namespace std {
int *current_exception();
}
struct bad_promise_8 {
coro<bad_promise_8> 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<bad_promise_8> calls_set_exception() {
// expected-error@-1 {{call to unavailable member function 'set_exception'}}
co_await a;
}
template<> struct std::experimental::coroutine_traits<int, int, const char**>
{ using promise_type = promise; };