Skip to content

Commit 43810d2

Browse files
committed
[coroutine] Suppress unreachable-code warning on coroutine statements.
1 parent e3a38a7 commit 43810d2

File tree

2 files changed

+91
-1
lines changed

2 files changed

+91
-1
lines changed

clang/lib/Analysis/ReachableCode.cpp

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "clang/AST/ExprCXX.h"
1818
#include "clang/AST/ExprObjC.h"
1919
#include "clang/AST/ParentMap.h"
20+
#include "clang/AST/RecursiveASTVisitor.h"
2021
#include "clang/AST/StmtCXX.h"
2122
#include "clang/Analysis/AnalysisDeclContext.h"
2223
#include "clang/Analysis/CFG.h"
@@ -60,6 +61,45 @@ static bool isTrivialDoWhile(const CFGBlock *B, const Stmt *S) {
6061
return false;
6162
}
6263

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+
63103
static bool isBuiltinUnreachable(const Stmt *S) {
64104
if (const auto *DRE = dyn_cast<DeclRefExpr>(S))
65105
if (const auto *FDecl = dyn_cast<FunctionDecl>(DRE->getDecl()))
@@ -623,7 +663,7 @@ void DeadCodeScan::reportDeadCode(const CFGBlock *B,
623663
if (isa<BreakStmt>(S)) {
624664
UK = reachable_code::UK_Break;
625665
} else if (isTrivialDoWhile(B, S) || isBuiltinUnreachable(S) ||
626-
isBuiltinAssumeFalse(B, S, C)) {
666+
isBuiltinAssumeFalse(B, S, C) || isInCoroutineStmt(B, S)) {
627667
return;
628668
}
629669
else if (isDeadReturn(B, S)) {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-unknown %s -std=c++20 -fsyntax-only -verify -Wunreachable-code
2+
3+
#include "Inputs/std-coroutine.h"
4+
5+
extern void abort (void) __attribute__ ((__noreturn__));
6+
7+
struct task {
8+
struct promise_type {
9+
std::suspend_always initial_suspend();
10+
std::suspend_always final_suspend() noexcept;
11+
void return_void();
12+
std::suspend_always yield_value(int) { return {}; }
13+
task get_return_object();
14+
void unhandled_exception();
15+
};
16+
};
17+
18+
task test1() {
19+
abort();
20+
co_yield 1;
21+
}
22+
23+
task test2() {
24+
abort();
25+
1; // expected-warning {{code will never be executed}}
26+
co_yield 1;
27+
}
28+
29+
task test3() {
30+
abort();
31+
co_return;
32+
}
33+
34+
task test4() {
35+
abort();
36+
1; // expected-warning {{code will never be executed}}
37+
co_return;
38+
}
39+
40+
41+
task test5() {
42+
abort();
43+
co_await std::suspend_never{};
44+
}
45+
46+
task test6() {
47+
abort();
48+
1; // expected-warning {{code will never be executed}}
49+
co_await std::suspend_never{};
50+
}

0 commit comments

Comments
 (0)