Skip to content

Commit 5839b3d

Browse files
yronglinjvoung
andcommitted
Reapply [Analyzer][CFG] Correctly handle rebuilt default arg and default init expression
Co-authored-by: Jan Voung <jvoung@google.com> Signed-off-by: yronglin <yronglin777@gmail.com>
1 parent aeea062 commit 5839b3d

File tree

12 files changed

+284
-93
lines changed

12 files changed

+284
-93
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,6 +1064,9 @@ Static Analyzer
10641064
---------------
10651065
- Fixed a crash when C++20 parenthesized initializer lists are used. This issue
10661066
was causing a crash in clang-tidy. (#GH136041)
1067+
- Clang currently support extending lifetime of object bound to
1068+
reference members of aggregates in CFG and ExprEngine, that are
1069+
created from default member initializer.
10671070

10681071
New features
10691072
^^^^^^^^^^^^

clang/include/clang/AST/ParentMap.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#ifndef LLVM_CLANG_AST_PARENTMAP_H
1414
#define LLVM_CLANG_AST_PARENTMAP_H
1515

16+
#include "llvm/Support/Casting.h"
17+
1618
namespace clang {
1719
class Stmt;
1820
class Expr;
@@ -39,6 +41,25 @@ class ParentMap {
3941
Stmt *getParentIgnoreParenImpCasts(Stmt *) const;
4042
Stmt *getOuterParenParent(Stmt *) const;
4143

44+
template <typename... Ts> Stmt *getOuterMostAncestor(Stmt *S) const {
45+
Stmt *Res = nullptr;
46+
while (S) {
47+
if (llvm::isa<Ts...>(S))
48+
Res = S;
49+
S = getParent(S);
50+
}
51+
return Res;
52+
}
53+
54+
template <typename... Ts> Stmt *getInnerMostAncestor(Stmt *S) const {
55+
while (S) {
56+
if (llvm::isa<Ts...>(S))
57+
return S;
58+
S = getParent(S);
59+
}
60+
return nullptr;
61+
}
62+
4263
const Stmt *getParent(const Stmt* S) const {
4364
return getParent(const_cast<Stmt*>(S));
4465
}
@@ -51,6 +72,16 @@ class ParentMap {
5172
return getParentIgnoreParenCasts(const_cast<Stmt*>(S));
5273
}
5374

75+
template <typename... Ts>
76+
const Stmt *getOuterMostAncestor(const Stmt *S) const {
77+
return getOuterMostAncestor<Ts...>(const_cast<Stmt *>(S));
78+
}
79+
80+
template <typename... Ts>
81+
const Stmt *getInnerMostAncestor(const Stmt *S) const {
82+
return getInnerMostAncestor<Ts...>(const_cast<Stmt *>(S));
83+
}
84+
5485
bool hasParent(const Stmt *S) const { return getParent(S) != nullptr; }
5586

5687
bool isConsumedExpr(Expr *E) const;

clang/lib/AST/ParentMap.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "clang/AST/ParentMap.h"
1414
#include "clang/AST/Decl.h"
1515
#include "clang/AST/Expr.h"
16+
#include "clang/AST/ExprCXX.h"
1617
#include "clang/AST/StmtObjC.h"
1718
#include "llvm/ADT/DenseMap.h"
1819

@@ -103,6 +104,22 @@ static void BuildParentMap(MapTy& M, Stmt* S,
103104
BuildParentMap(M, SubStmt, OVMode);
104105
}
105106
break;
107+
case Stmt::CXXDefaultArgExprClass:
108+
if (auto *Arg = dyn_cast<CXXDefaultArgExpr>(S)) {
109+
if (Arg->hasRewrittenInit()) {
110+
M[Arg->getExpr()] = S;
111+
BuildParentMap(M, Arg->getExpr(), OVMode);
112+
}
113+
}
114+
break;
115+
case Stmt::CXXDefaultInitExprClass:
116+
if (auto *Init = dyn_cast<CXXDefaultInitExpr>(S)) {
117+
if (Init->hasRewrittenInit()) {
118+
M[Init->getExpr()] = S;
119+
BuildParentMap(M, Init->getExpr(), OVMode);
120+
}
121+
}
122+
break;
106123
default:
107124
for (Stmt *SubStmt : S->children()) {
108125
if (SubStmt) {

clang/lib/Analysis/CFG.cpp

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,10 @@ class CFGBuilder {
578578

579579
private:
580580
// Visitors to walk an AST and construct the CFG.
581+
CFGBlock *VisitCXXDefaultArgExpr(CXXDefaultArgExpr *Default,
582+
AddStmtChoice asc);
583+
CFGBlock *VisitCXXDefaultInitExpr(CXXDefaultInitExpr *Default,
584+
AddStmtChoice asc);
581585
CFGBlock *VisitInitListExpr(InitListExpr *ILE, AddStmtChoice asc);
582586
CFGBlock *VisitAddrLabelExpr(AddrLabelExpr *A, AddStmtChoice asc);
583587
CFGBlock *VisitAttributedStmt(AttributedStmt *A, AddStmtChoice asc);
@@ -2345,16 +2349,10 @@ CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc,
23452349
asc, ExternallyDestructed);
23462350

23472351
case Stmt::CXXDefaultArgExprClass:
2352+
return VisitCXXDefaultArgExpr(cast<CXXDefaultArgExpr>(S), asc);
2353+
23482354
case Stmt::CXXDefaultInitExprClass:
2349-
// FIXME: The expression inside a CXXDefaultArgExpr is owned by the
2350-
// called function's declaration, not by the caller. If we simply add
2351-
// this expression to the CFG, we could end up with the same Expr
2352-
// appearing multiple times (PR13385).
2353-
//
2354-
// It's likewise possible for multiple CXXDefaultInitExprs for the same
2355-
// expression to be used in the same function (through aggregate
2356-
// initialization).
2357-
return VisitStmt(S, asc);
2355+
return VisitCXXDefaultInitExpr(cast<CXXDefaultInitExpr>(S), asc);
23582356

23592357
case Stmt::CXXBindTemporaryExprClass:
23602358
return VisitCXXBindTemporaryExpr(cast<CXXBindTemporaryExpr>(S), asc);
@@ -2524,6 +2522,44 @@ CFGBlock *CFGBuilder::VisitChildren(Stmt *S) {
25242522
return B;
25252523
}
25262524

2525+
CFGBlock *CFGBuilder::VisitCXXDefaultArgExpr(CXXDefaultArgExpr *Arg,
2526+
AddStmtChoice asc) {
2527+
if (Arg->hasRewrittenInit()) {
2528+
if (asc.alwaysAdd(*this, Arg)) {
2529+
autoCreateBlock();
2530+
appendStmt(Block, Arg);
2531+
}
2532+
return VisitStmt(Arg->getExpr()->IgnoreParens(), asc);
2533+
}
2534+
2535+
// We can't add the default argument if it's not rewritten because the
2536+
// expression inside a CXXDefaultArgExpr is owned by the called function's
2537+
// declaration, not by the caller, we could end up with the same expression
2538+
// appearing multiple times.
2539+
return VisitStmt(Arg, asc);
2540+
}
2541+
2542+
CFGBlock *CFGBuilder::VisitCXXDefaultInitExpr(CXXDefaultInitExpr *Init,
2543+
AddStmtChoice asc) {
2544+
if (Init->hasRewrittenInit()) {
2545+
if (asc.alwaysAdd(*this, Init)) {
2546+
autoCreateBlock();
2547+
appendStmt(Block, Init);
2548+
}
2549+
2550+
// Unlike CXXDefaultArgExpr::getExpr stripped off the top level FullExpr and
2551+
// ConstantExpr, CXXDefaultInitExpr::getExpr does not do this, so we don't
2552+
// need to ignore ParenExprs, because the top level will not be a ParenExpr.
2553+
return VisitStmt(Init->getExpr(), asc);
2554+
}
2555+
2556+
// We can't add the default initializer if it's not rewritten because multiple
2557+
// CXXDefaultInitExprs for the same sub-expression to be used in the same
2558+
// function (through aggregate initialization). we could end up with the same
2559+
// expression appearing multiple times.
2560+
return VisitStmt(Init, asc);
2561+
}
2562+
25272563
CFGBlock *CFGBuilder::VisitInitListExpr(InitListExpr *ILE, AddStmtChoice asc) {
25282564
if (asc.alwaysAdd(*this, ILE)) {
25292565
autoCreateBlock();

clang/lib/Analysis/ReachableCode.cpp

Lines changed: 18 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "clang/Basic/SourceManager.h"
2626
#include "clang/Lex/Preprocessor.h"
2727
#include "llvm/ADT/BitVector.h"
28+
#include <cstddef>
2829
#include <optional>
2930

3031
using namespace clang;
@@ -396,17 +397,18 @@ namespace {
396397
SmallVector<const CFGBlock *, 10> WorkList;
397398
Preprocessor &PP;
398399
ASTContext &C;
400+
AnalysisDeclContext &AC;
399401

400402
typedef SmallVector<std::pair<const CFGBlock *, const Stmt *>, 12>
401403
DeferredLocsTy;
402404

403405
DeferredLocsTy DeferredLocs;
404406

405407
public:
406-
DeadCodeScan(llvm::BitVector &reachable, Preprocessor &PP, ASTContext &C)
407-
: Visited(reachable.size()),
408-
Reachable(reachable),
409-
PP(PP), C(C) {}
408+
DeadCodeScan(llvm::BitVector &reachable, Preprocessor &PP,
409+
AnalysisDeclContext &AC)
410+
: Visited(reachable.size()), Reachable(reachable), PP(PP),
411+
C(AC.getASTContext()), AC(AC) {}
410412

411413
void enqueue(const CFGBlock *block);
412414
unsigned scanBackwards(const CFGBlock *Start,
@@ -453,70 +455,36 @@ bool DeadCodeScan::isDeadCodeRoot(const clang::CFGBlock *Block) {
453455
return isDeadRoot;
454456
}
455457

456-
// Check if the given `DeadStmt` is a coroutine statement and is a substmt of
457-
// the coroutine statement. `Block` is the CFGBlock containing the `DeadStmt`.
458-
static bool isInCoroutineStmt(const Stmt *DeadStmt, const CFGBlock *Block) {
459-
// The coroutine statement, co_return, co_await, or co_yield.
460-
const Stmt *CoroStmt = nullptr;
461-
// Find the first coroutine statement after the DeadStmt in the block.
462-
bool AfterDeadStmt = false;
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 (S == DeadStmt)
468-
AfterDeadStmt = true;
469-
if (AfterDeadStmt &&
470-
// For simplicity, we only check simple coroutine statements.
471-
(llvm::isa<CoreturnStmt>(S) || llvm::isa<CoroutineSuspendExpr>(S))) {
472-
CoroStmt = S;
473-
break;
474-
}
475-
}
476-
if (!CoroStmt)
477-
return false;
478-
struct Checker : DynamicRecursiveASTVisitor {
479-
const Stmt *DeadStmt;
480-
bool CoroutineSubStmt = false;
481-
Checker(const Stmt *S) : DeadStmt(S) {
482-
// Statements captured in the CFG can be implicit.
483-
ShouldVisitImplicitCode = true;
484-
}
485-
486-
bool VisitStmt(Stmt *S) override {
487-
if (S == DeadStmt)
488-
CoroutineSubStmt = true;
489-
return true;
490-
}
491-
};
492-
Checker checker(DeadStmt);
493-
checker.TraverseStmt(const_cast<Stmt *>(CoroStmt));
494-
return checker.CoroutineSubStmt;
495-
}
496-
497-
static bool isValidDeadStmt(const Stmt *S, const clang::CFGBlock *Block) {
458+
static bool isValidDeadStmt(ParentMap &PM, const Stmt *S,
459+
const clang::CFGBlock *Block) {
498460
if (S->getBeginLoc().isInvalid())
499461
return false;
500462
if (const BinaryOperator *BO = dyn_cast<BinaryOperator>(S))
501463
return BO->getOpcode() != BO_Comma;
502464
// Coroutine statements are never considered dead statements, because removing
503465
// them may change the function semantic if it is the only coroutine statement
504466
// of the coroutine.
505-
return !isInCoroutineStmt(S, Block);
467+
return !PM.getInnerMostAncestor<CoreturnStmt, CoroutineSuspendExpr>(S);
506468
}
507469

508470
const Stmt *DeadCodeScan::findDeadCode(const clang::CFGBlock *Block) {
471+
auto &PM = AC.getParentMap();
472+
509473
for (CFGBlock::const_iterator I = Block->begin(), E = Block->end(); I!=E; ++I)
510474
if (std::optional<CFGStmt> CS = I->getAs<CFGStmt>()) {
511475
const Stmt *S = CS->getStmt();
512-
if (isValidDeadStmt(S, Block))
476+
auto *RewrittenParent =
477+
PM.getOuterMostAncestor<CXXDefaultArgExpr, CXXDefaultInitExpr>(S);
478+
if (RewrittenParent)
479+
S = RewrittenParent;
480+
if (isValidDeadStmt(AC.getParentMap(), S, Block))
513481
return S;
514482
}
515483

516484
CFGTerminator T = Block->getTerminator();
517485
if (T.isStmtBranch()) {
518486
const Stmt *S = T.getStmt();
519-
if (S && isValidDeadStmt(S, Block))
487+
if (S && isValidDeadStmt(AC.getParentMap(), S, Block))
520488
return S;
521489
}
522490

@@ -762,7 +730,7 @@ void FindUnreachableCode(AnalysisDeclContext &AC, Preprocessor &PP,
762730
if (reachable[block->getBlockID()])
763731
continue;
764732

765-
DeadCodeScan DS(reachable, PP, AC.getASTContext());
733+
DeadCodeScan DS(reachable, PP, AC);
766734
numReachable += DS.scanBackwards(block, CB);
767735

768736
if (numReachable == cfg->getNumBlockIDs())

clang/lib/Sema/SemaExpr.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5623,8 +5623,10 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc,
56235623
/*SkipImmediateInvocations=*/NestedDefaultChecking))
56245624
return ExprError();
56255625

5626+
Expr *RewrittenExpr = (Init == Param->getDefaultArg() ? nullptr : Init);
56265627
return CXXDefaultArgExpr::Create(Context, InitializationContext->Loc, Param,
5627-
Init, InitializationContext->Context);
5628+
RewrittenExpr,
5629+
InitializationContext->Context);
56285630
}
56295631

56305632
static FieldDecl *FindFieldDeclInstantiationPattern(const ASTContext &Ctx,
@@ -5742,10 +5744,11 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) {
57425744
return ExprError();
57435745
}
57445746
Init = Res.get();
5745-
5747+
Expr *RewrittenInit =
5748+
(Init == Field->getInClassInitializer() ? nullptr : Init);
57465749
return CXXDefaultInitExpr::Create(Context, InitializationContext->Loc,
57475750
Field, InitializationContext->Context,
5748-
Init);
5751+
RewrittenInit);
57495752
}
57505753

57515754
// DR1351:

clang/lib/StaticAnalyzer/Core/ExprEngine.cpp

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1995,33 +1995,45 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
19951995
ExplodedNodeSet Tmp;
19961996
StmtNodeBuilder Bldr2(PreVisit, Tmp, *currBldrCtx);
19971997

1998-
const Expr *ArgE;
1999-
if (const auto *DefE = dyn_cast<CXXDefaultArgExpr>(S))
1998+
bool HasRebuiltInit = false;
1999+
const Expr *ArgE = nullptr;
2000+
if (const auto *DefE = dyn_cast<CXXDefaultArgExpr>(S)) {
20002001
ArgE = DefE->getExpr();
2001-
else if (const auto *DefE = dyn_cast<CXXDefaultInitExpr>(S))
2002+
HasRebuiltInit = DefE->hasRewrittenInit();
2003+
} else if (const auto *DefE = dyn_cast<CXXDefaultInitExpr>(S)) {
20022004
ArgE = DefE->getExpr();
2003-
else
2005+
HasRebuiltInit = DefE->hasRewrittenInit();
2006+
} else
20042007
llvm_unreachable("unknown constant wrapper kind");
20052008

2006-
bool IsTemporary = false;
2007-
if (const auto *MTE = dyn_cast<MaterializeTemporaryExpr>(ArgE)) {
2008-
ArgE = MTE->getSubExpr();
2009-
IsTemporary = true;
2010-
}
2009+
if (HasRebuiltInit) {
2010+
for (auto *N : PreVisit) {
2011+
ProgramStateRef state = N->getState();
2012+
const LocationContext *LCtx = N->getLocationContext();
2013+
state = state->BindExpr(S, LCtx, state->getSVal(ArgE, LCtx));
2014+
Bldr2.generateNode(S, N, state);
2015+
}
2016+
} else {
2017+
// If it's not rewritten, the contents of these expressions are not
2018+
// actually part of the current function, so we fall back to constant
2019+
// evaluation.
2020+
bool IsTemporary = false;
2021+
if (const auto *MTE = dyn_cast<MaterializeTemporaryExpr>(ArgE)) {
2022+
ArgE = MTE->getSubExpr();
2023+
IsTemporary = true;
2024+
}
2025+
2026+
std::optional<SVal> ConstantVal = svalBuilder.getConstantVal(ArgE);
2027+
const LocationContext *LCtx = Pred->getLocationContext();
2028+
for (auto *I : PreVisit) {
2029+
ProgramStateRef State = I->getState();
2030+
State = State->BindExpr(S, LCtx, ConstantVal.value_or(UnknownVal()));
2031+
if (IsTemporary)
2032+
State = createTemporaryRegionIfNeeded(State, LCtx, cast<Expr>(S),
2033+
cast<Expr>(S));
20112034

2012-
std::optional<SVal> ConstantVal = svalBuilder.getConstantVal(ArgE);
2013-
if (!ConstantVal)
2014-
ConstantVal = UnknownVal();
2015-
2016-
const LocationContext *LCtx = Pred->getLocationContext();
2017-
for (const auto I : PreVisit) {
2018-
ProgramStateRef State = I->getState();
2019-
State = State->BindExpr(S, LCtx, *ConstantVal);
2020-
if (IsTemporary)
2021-
State = createTemporaryRegionIfNeeded(State, LCtx,
2022-
cast<Expr>(S),
2023-
cast<Expr>(S));
2024-
Bldr2.generateNode(S, I, State);
2035+
Bldr2.generateNode(S, I, State);
2036+
}
20252037
}
20262038

20272039
getCheckerManager().runCheckersForPostStmt(Dst, Tmp, S, *this);

clang/test/AST/ast-dump-recovery.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ union U {
294294
// CHECK-NEXT: `-DeclStmt {{.*}}
295295
// CHECK-NEXT: `-VarDecl {{.*}} g 'U':'GH112560::U' listinit
296296
// CHECK-NEXT: `-InitListExpr {{.*}} 'U':'GH112560::U' contains-errors field Field {{.*}} 'f' 'int'
297-
// CHECK-NEXT: `-CXXDefaultInitExpr {{.*}} 'int' contains-errors has rewritten init
297+
// CHECK-NEXT: `-CXXDefaultInitExpr {{.*}} 'int' contains-errors
298298
// CHECK-NEXT: `-RecoveryExpr {{.*}} 'int' contains-errors
299299
// DISABLED-NOT: -RecoveryExpr {{.*}} contains-errors
300300
void foo() {

0 commit comments

Comments
 (0)