Skip to content

Commit a73baf6

Browse files
authored
[coroutine] Suppress unreachable-code warning on coroutine statements. (#77454)
This fixes #69219. Consider an example: ``` CoTask my_coroutine() { std::abort(); co_return 1; // unreachable code warning. } ``` Clang emits a CFG-based unreachable warning on the `co_return` statement (precisely the `1` subexpr). If we remove this statement, the program semantic is changed (my_coroutine is not a coroutine anymore). This patch fixes this issue by never considering coroutine statements as dead statements.
1 parent cfa0833 commit a73baf6

File tree

2 files changed

+123
-4
lines changed

2 files changed

+123
-4
lines changed

clang/lib/Analysis/ReachableCode.cpp

Lines changed: 47 additions & 4 deletions
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"
@@ -453,26 +454,68 @@ bool DeadCodeScan::isDeadCodeRoot(const clang::CFGBlock *Block) {
453454
return isDeadRoot;
454455
}
455456

456-
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. `Block` is the CFGBlock containing the `DeadStmt`.
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 after the DeadStmt in the block.
463+
bool AfterDeadStmt = false;
464+
for (CFGBlock::const_iterator I = Block->begin(), E = Block->end(); I != E;
465+
++I)
466+
if (std::optional<CFGStmt> CS = I->getAs<CFGStmt>()) {
467+
const Stmt *S = CS->getStmt();
468+
if (S == DeadStmt)
469+
AfterDeadStmt = true;
470+
if (AfterDeadStmt &&
471+
// For simplicity, we only check simple coroutine statements.
472+
(llvm::isa<CoreturnStmt>(S) || llvm::isa<CoroutineSuspendExpr>(S))) {
473+
CoroStmt = S;
474+
break;
475+
}
476+
}
477+
if (!CoroStmt)
478+
return false;
479+
struct Checker : RecursiveASTVisitor<Checker> {
480+
const Stmt *DeadStmt;
481+
bool CoroutineSubStmt = false;
482+
Checker(const Stmt *S) : DeadStmt(S) {}
483+
bool VisitStmt(const Stmt *S) {
484+
if (S == DeadStmt)
485+
CoroutineSubStmt = true;
486+
return true;
487+
}
488+
// Statements captured in the CFG can be implicit.
489+
bool shouldVisitImplicitCode() const { return true; }
490+
};
491+
Checker checker(DeadStmt);
492+
checker.TraverseStmt(const_cast<Stmt *>(CoroStmt));
493+
return checker.CoroutineSubStmt;
494+
}
495+
496+
static bool isValidDeadStmt(const Stmt *S, const clang::CFGBlock *Block) {
457497
if (S->getBeginLoc().isInvalid())
458498
return false;
459499
if (const BinaryOperator *BO = dyn_cast<BinaryOperator>(S))
460500
return BO->getOpcode() != BO_Comma;
461-
return true;
501+
// Coroutine statements are never considered dead statements, because removing
502+
// them may change the function semantic if it is the only coroutine statement
503+
// of the coroutine.
504+
return !isInCoroutineStmt(S, Block);
462505
}
463506

464507
const Stmt *DeadCodeScan::findDeadCode(const clang::CFGBlock *Block) {
465508
for (CFGBlock::const_iterator I = Block->begin(), E = Block->end(); I!=E; ++I)
466509
if (std::optional<CFGStmt> CS = I->getAs<CFGStmt>()) {
467510
const Stmt *S = CS->getStmt();
468-
if (isValidDeadStmt(S))
511+
if (isValidDeadStmt(S, Block))
469512
return S;
470513
}
471514

472515
CFGTerminator T = Block->getTerminator();
473516
if (T.isStmtBranch()) {
474517
const Stmt *S = T.getStmt();
475-
if (S && isValidDeadStmt(S))
518+
if (S && isValidDeadStmt(S, Block))
476519
return S;
477520
}
478521

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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+
struct Awaiter {
17+
bool await_ready();
18+
void await_suspend(auto);
19+
int await_resume();
20+
};
21+
auto await_transform(const int& x) { return Awaiter{}; }
22+
};
23+
};
24+
25+
task test1() {
26+
abort();
27+
co_yield 1;
28+
}
29+
30+
task test2() {
31+
abort();
32+
1; // expected-warning {{code will never be executed}}
33+
co_yield 1;
34+
}
35+
36+
task test3() {
37+
abort();
38+
co_return;
39+
}
40+
41+
task test4() {
42+
abort();
43+
1; // expected-warning {{code will never be executed}}
44+
co_return;
45+
}
46+
47+
task test5() {
48+
abort();
49+
co_await 1;
50+
}
51+
52+
task test6() {
53+
abort();
54+
1; // expected-warning {{code will never be executed}}
55+
co_await 3;
56+
}
57+
58+
task test7() {
59+
// coroutine statements are not considered unreachable.
60+
co_await 1;
61+
abort();
62+
co_await 2;
63+
}
64+
65+
task test8() {
66+
// coroutine statements are not considered unreachable.
67+
abort();
68+
co_return;
69+
1 + 1; // expected-warning {{code will never be executed}}
70+
}
71+
72+
task test9() {
73+
abort();
74+
// This warning is emitted on the declaration itself, rather the coroutine substmt.
75+
int x = co_await 1; // expected-warning {{code will never be executed}}
76+
}

0 commit comments

Comments
 (0)