Skip to content

Commit c8a1951

Browse files
committed
Never treat coroutine statements as valid dead statements.
1 parent 43810d2 commit c8a1951

File tree

2 files changed

+59
-45
lines changed

2 files changed

+59
-45
lines changed

clang/lib/Analysis/ReachableCode.cpp

Lines changed: 43 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -61,45 +61,6 @@ static bool isTrivialDoWhile(const CFGBlock *B, const Stmt *S) {
6161
return false;
6262
}
6363

64-
// Check if the block starts with a coroutine statement and see if the given
65-
// unreachable 'S' is the substmt of the coroutine statement.
66-
//
67-
// We suppress the unreachable warning for cases where an unreachable code is
68-
// a substmt of the coroutine statement, becase removing it will change the
69-
// function semantic if this is the only coroutine statement of the coroutine.
70-
static bool isInCoroutineStmt(const CFGBlock *Block, const Stmt* S) {
71-
// The coroutine statement, co_return, co_await, or co_yield.
72-
const Stmt* CoroStmt = nullptr;
73-
// Find the first coroutine statement in the block.
74-
for (CFGBlock::const_iterator I = Block->begin(), E = Block->end(); I != E;
75-
++I)
76-
if (std::optional<CFGStmt> CS = I->getAs<CFGStmt>()) {
77-
const Stmt *S = CS->getStmt();
78-
if (llvm::isa<CoreturnStmt>(S) || llvm::isa<CoroutineSuspendExpr>(S)) {
79-
CoroStmt = S ;
80-
break;
81-
}
82-
}
83-
if (!CoroStmt)
84-
return false;
85-
86-
struct Checker : RecursiveASTVisitor<Checker> {
87-
const Stmt *StmtToCheck;
88-
bool CoroutineSubStmt = false;
89-
Checker(const Stmt *S) : StmtToCheck(S) {}
90-
bool VisitStmt(const Stmt *S) {
91-
if (S == StmtToCheck)
92-
CoroutineSubStmt = true;
93-
return true;
94-
}
95-
// The 'S' stmt captured in the CFG can be implicit.
96-
bool shouldVisitImplicitCode() const { return true; }
97-
};
98-
Checker checker(S);
99-
checker.TraverseStmt(const_cast<Stmt *>(CoroStmt));
100-
return checker.CoroutineSubStmt;
101-
}
102-
10364
static bool isBuiltinUnreachable(const Stmt *S) {
10465
if (const auto *DRE = dyn_cast<DeclRefExpr>(S))
10566
if (const auto *FDecl = dyn_cast<FunctionDecl>(DRE->getDecl()))
@@ -493,26 +454,64 @@ bool DeadCodeScan::isDeadCodeRoot(const clang::CFGBlock *Block) {
493454
return isDeadRoot;
494455
}
495456

496-
static bool isValidDeadStmt(const Stmt *S) {
457+
// Check if the given `DeadStmt` is a coroutine statement and is a substmt of
458+
// the coroutine statement.
459+
static bool isInCoroutineStmt(const Stmt* DeadStmt, const CFGBlock *Block) {
460+
// The coroutine statement, co_return, co_await, or co_yield.
461+
const Stmt* CoroStmt = nullptr;
462+
// Find the first coroutine statement in the block.
463+
for (CFGBlock::const_iterator I = Block->begin(), E = Block->end(); I != E;
464+
++I)
465+
if (std::optional<CFGStmt> CS = I->getAs<CFGStmt>()) {
466+
const Stmt *S = CS->getStmt();
467+
if (llvm::isa<CoreturnStmt>(S) || llvm::isa<CoroutineSuspendExpr>(S)) {
468+
CoroStmt = S ;
469+
break;
470+
}
471+
}
472+
if (!CoroStmt)
473+
return false;
474+
475+
struct Checker : RecursiveASTVisitor<Checker> {
476+
const Stmt *StmtToCheck;
477+
bool CoroutineSubStmt = false;
478+
Checker(const Stmt *S) : StmtToCheck(S) {}
479+
bool VisitStmt(const Stmt *S) {
480+
if (S == StmtToCheck)
481+
CoroutineSubStmt = true;
482+
return true;
483+
}
484+
// Statements captured in the CFG can be implicit.
485+
bool shouldVisitImplicitCode() const { return true; }
486+
};
487+
Checker checker(DeadStmt);
488+
checker.TraverseStmt(const_cast<Stmt *>(CoroStmt));
489+
return checker.CoroutineSubStmt;
490+
}
491+
492+
static bool isValidDeadStmt(const Stmt *S, const clang::CFGBlock *Block) {
497493
if (S->getBeginLoc().isInvalid())
498494
return false;
499495
if (const BinaryOperator *BO = dyn_cast<BinaryOperator>(S))
500496
return BO->getOpcode() != BO_Comma;
501-
return true;
497+
// Coroutine statements are never considered dead statements, because removing
498+
// them may change the function semantic if it is the only coroutine statement
499+
// of the coroutine.
500+
return !isInCoroutineStmt(S, Block);
502501
}
503502

504503
const Stmt *DeadCodeScan::findDeadCode(const clang::CFGBlock *Block) {
505504
for (CFGBlock::const_iterator I = Block->begin(), E = Block->end(); I!=E; ++I)
506505
if (std::optional<CFGStmt> CS = I->getAs<CFGStmt>()) {
507506
const Stmt *S = CS->getStmt();
508-
if (isValidDeadStmt(S))
507+
if (isValidDeadStmt(S, Block))
509508
return S;
510509
}
511510

512511
CFGTerminator T = Block->getTerminator();
513512
if (T.isStmtBranch()) {
514513
const Stmt *S = T.getStmt();
515-
if (S && isValidDeadStmt(S))
514+
if (S && isValidDeadStmt(S, Block))
516515
return S;
517516
}
518517

@@ -663,7 +662,7 @@ void DeadCodeScan::reportDeadCode(const CFGBlock *B,
663662
if (isa<BreakStmt>(S)) {
664663
UK = reachable_code::UK_Break;
665664
} else if (isTrivialDoWhile(B, S) || isBuiltinUnreachable(S) ||
666-
isBuiltinAssumeFalse(B, S, C) || isInCoroutineStmt(B, S)) {
665+
isBuiltinAssumeFalse(B, S, C)) {
667666
return;
668667
}
669668
else if (isDeadReturn(B, S)) {

clang/test/SemaCXX/coroutine-unreachable-warning.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ struct task {
1010
std::suspend_always final_suspend() noexcept;
1111
void return_void();
1212
std::suspend_always yield_value(int) { return {}; }
13-
task get_return_object();
13+
task get_return_object();
1414
void unhandled_exception();
1515
};
1616
};
@@ -48,3 +48,18 @@ task test6() {
4848
1; // expected-warning {{code will never be executed}}
4949
co_await std::suspend_never{};
5050
}
51+
52+
task test7() {
53+
// coroutine statements are not considered unreachable.
54+
co_await std::suspend_never{};
55+
abort();
56+
co_await std::suspend_never{};
57+
}
58+
59+
task test8() {
60+
// coroutine statements are not considered unreachable.
61+
// co_await std::suspend_never{};
62+
abort();
63+
co_return;
64+
1 + 1; // expected-warning {{code will never be executed}}
65+
}

0 commit comments

Comments
 (0)