|
17 | 17 | #include "clang/AST/ExprCXX.h"
|
18 | 18 | #include "clang/AST/ExprObjC.h"
|
19 | 19 | #include "clang/AST/ParentMap.h"
|
| 20 | +#include "clang/AST/RecursiveASTVisitor.h" |
20 | 21 | #include "clang/AST/StmtCXX.h"
|
21 | 22 | #include "clang/Analysis/AnalysisDeclContext.h"
|
22 | 23 | #include "clang/Analysis/CFG.h"
|
@@ -453,26 +454,68 @@ bool DeadCodeScan::isDeadCodeRoot(const clang::CFGBlock *Block) {
|
453 | 454 | return isDeadRoot;
|
454 | 455 | }
|
455 | 456 |
|
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) { |
457 | 497 | if (S->getBeginLoc().isInvalid())
|
458 | 498 | return false;
|
459 | 499 | if (const BinaryOperator *BO = dyn_cast<BinaryOperator>(S))
|
460 | 500 | 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); |
462 | 505 | }
|
463 | 506 |
|
464 | 507 | const Stmt *DeadCodeScan::findDeadCode(const clang::CFGBlock *Block) {
|
465 | 508 | for (CFGBlock::const_iterator I = Block->begin(), E = Block->end(); I!=E; ++I)
|
466 | 509 | if (std::optional<CFGStmt> CS = I->getAs<CFGStmt>()) {
|
467 | 510 | const Stmt *S = CS->getStmt();
|
468 |
| - if (isValidDeadStmt(S)) |
| 511 | + if (isValidDeadStmt(S, Block)) |
469 | 512 | return S;
|
470 | 513 | }
|
471 | 514 |
|
472 | 515 | CFGTerminator T = Block->getTerminator();
|
473 | 516 | if (T.isStmtBranch()) {
|
474 | 517 | const Stmt *S = T.getStmt();
|
475 |
| - if (S && isValidDeadStmt(S)) |
| 518 | + if (S && isValidDeadStmt(S, Block)) |
476 | 519 | return S;
|
477 | 520 | }
|
478 | 521 |
|
|
0 commit comments