Skip to content

Commit 44aa618

Browse files
authored
[Analyzer][CFG] Correctly handle rebuilt default arg and default init expression (llvm#117437)
Clang currently support extending lifetime of object bound to reference members of aggregates, that are created from default member initializer. This PR address this change and updaye CFG and ExprEngine. This PR reapply llvm#91879. Fixes llvm#93725. --------- Signed-off-by: yronglin <[email protected]>
1 parent 6990581 commit 44aa618

File tree

10 files changed

+168
-61
lines changed

10 files changed

+168
-61
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,10 @@ Code Completion
219219
Static Analyzer
220220
---------------
221221

222+
- Clang currently support extending lifetime of object bound to
223+
reference members of aggregates in CFG and ExprEngine, that are
224+
created from default member initializer.
225+
222226
New features
223227
^^^^^^^^^^^^
224228

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: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,10 @@ class CFGBuilder {
556556

557557
private:
558558
// Visitors to walk an AST and construct the CFG.
559+
CFGBlock *VisitCXXDefaultArgExpr(CXXDefaultArgExpr *Default,
560+
AddStmtChoice asc);
561+
CFGBlock *VisitCXXDefaultInitExpr(CXXDefaultInitExpr *Default,
562+
AddStmtChoice asc);
559563
CFGBlock *VisitInitListExpr(InitListExpr *ILE, AddStmtChoice asc);
560564
CFGBlock *VisitAddrLabelExpr(AddrLabelExpr *A, AddStmtChoice asc);
561565
CFGBlock *VisitAttributedStmt(AttributedStmt *A, AddStmtChoice asc);
@@ -2261,16 +2265,10 @@ CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc,
22612265
asc, ExternallyDestructed);
22622266

22632267
case Stmt::CXXDefaultArgExprClass:
2268+
return VisitCXXDefaultArgExpr(cast<CXXDefaultArgExpr>(S), asc);
2269+
22642270
case Stmt::CXXDefaultInitExprClass:
2265-
// FIXME: The expression inside a CXXDefaultArgExpr is owned by the
2266-
// called function's declaration, not by the caller. If we simply add
2267-
// this expression to the CFG, we could end up with the same Expr
2268-
// appearing multiple times (PR13385).
2269-
//
2270-
// It's likewise possible for multiple CXXDefaultInitExprs for the same
2271-
// expression to be used in the same function (through aggregate
2272-
// initialization).
2273-
return VisitStmt(S, asc);
2271+
return VisitCXXDefaultInitExpr(cast<CXXDefaultInitExpr>(S), asc);
22742272

22752273
case Stmt::CXXBindTemporaryExprClass:
22762274
return VisitCXXBindTemporaryExpr(cast<CXXBindTemporaryExpr>(S), asc);
@@ -2440,6 +2438,40 @@ CFGBlock *CFGBuilder::VisitChildren(Stmt *S) {
24402438
return B;
24412439
}
24422440

2441+
CFGBlock *CFGBuilder::VisitCXXDefaultArgExpr(CXXDefaultArgExpr *Arg,
2442+
AddStmtChoice asc) {
2443+
if (Arg->hasRewrittenInit()) {
2444+
if (asc.alwaysAdd(*this, Arg)) {
2445+
autoCreateBlock();
2446+
appendStmt(Block, Arg);
2447+
}
2448+
return VisitStmt(Arg->getExpr(), asc);
2449+
}
2450+
2451+
// We can't add the default argument if it's not rewritten because the
2452+
// expression inside a CXXDefaultArgExpr is owned by the called function's
2453+
// declaration, not by the caller, we could end up with the same expression
2454+
// appearing multiple times.
2455+
return VisitStmt(Arg, asc);
2456+
}
2457+
2458+
CFGBlock *CFGBuilder::VisitCXXDefaultInitExpr(CXXDefaultInitExpr *Init,
2459+
AddStmtChoice asc) {
2460+
if (Init->hasRewrittenInit()) {
2461+
if (asc.alwaysAdd(*this, Init)) {
2462+
autoCreateBlock();
2463+
appendStmt(Block, Init);
2464+
}
2465+
return VisitStmt(Init->getExpr(), asc);
2466+
}
2467+
2468+
// We can't add the default initializer if it's not rewritten because multiple
2469+
// CXXDefaultInitExprs for the same sub-expression to be used in the same
2470+
// function (through aggregate initialization). we could end up with the same
2471+
// expression appearing multiple times.
2472+
return VisitStmt(Init, asc);
2473+
}
2474+
24432475
CFGBlock *CFGBuilder::VisitInitListExpr(InitListExpr *ILE, AddStmtChoice asc) {
24442476
if (asc.alwaysAdd(*this, ILE)) {
24452477
autoCreateBlock();

clang/lib/Analysis/ReachableCode.cpp

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -454,11 +454,12 @@ bool DeadCodeScan::isDeadCodeRoot(const clang::CFGBlock *Block) {
454454
return isDeadRoot;
455455
}
456456

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) {
457+
// Check if the given `DeadStmt` is one of target statements or is a sub-stmt of
458+
// them. `Block` is the CFGBlock containing the `DeadStmt`.
459+
template <class... Ts>
460+
static bool isDeadStmtInOneOf(const Stmt *DeadStmt, const CFGBlock *Block) {
460461
// The coroutine statement, co_return, co_await, or co_yield.
461-
const Stmt *CoroStmt = nullptr;
462+
const Stmt *TargetStmt = nullptr;
462463
// Find the first coroutine statement after the DeadStmt in the block.
463464
bool AfterDeadStmt = false;
464465
for (CFGBlock::const_iterator I = Block->begin(), E = Block->end(); I != E;
@@ -467,32 +468,27 @@ static bool isInCoroutineStmt(const Stmt *DeadStmt, const CFGBlock *Block) {
467468
const Stmt *S = CS->getStmt();
468469
if (S == DeadStmt)
469470
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;
471+
if (AfterDeadStmt && llvm::isa<Ts...>(S)) {
472+
TargetStmt = S;
474473
break;
475474
}
476475
}
477-
if (!CoroStmt)
476+
if (!TargetStmt)
478477
return false;
479478
struct Checker : DynamicRecursiveASTVisitor {
480479
const Stmt *DeadStmt;
481-
bool CoroutineSubStmt = false;
482-
Checker(const Stmt *S) : DeadStmt(S) {
483-
// Statements captured in the CFG can be implicit.
484-
ShouldVisitImplicitCode = true;
485-
}
480+
bool IsSubStmtOfTargetStmt = false;
481+
Checker(const Stmt *S) : DeadStmt(S) { ShouldVisitImplicitCode = true; }
486482

487483
bool VisitStmt(Stmt *S) override {
488484
if (S == DeadStmt)
489-
CoroutineSubStmt = true;
485+
IsSubStmtOfTargetStmt = true;
490486
return true;
491487
}
492488
};
493489
Checker checker(DeadStmt);
494-
checker.TraverseStmt(const_cast<Stmt *>(CoroStmt));
495-
return checker.CoroutineSubStmt;
490+
checker.TraverseStmt(const_cast<Stmt *>(TargetStmt));
491+
return checker.IsSubStmtOfTargetStmt;
496492
}
497493

498494
static bool isValidDeadStmt(const Stmt *S, const clang::CFGBlock *Block) {
@@ -503,7 +499,12 @@ static bool isValidDeadStmt(const Stmt *S, const clang::CFGBlock *Block) {
503499
// Coroutine statements are never considered dead statements, because removing
504500
// them may change the function semantic if it is the only coroutine statement
505501
// of the coroutine.
506-
return !isInCoroutineStmt(S, Block);
502+
//
503+
// If the dead stmt is a sub-stmt of CXXDefaultInitExpr and CXXDefaultArgExpr,
504+
// we would rather expect to find CXXDefaultInitExpr and CXXDefaultArgExpr as
505+
// a valid dead stmt.
506+
return !isDeadStmtInOneOf<CoreturnStmt, CoroutineSuspendExpr,
507+
CXXDefaultArgExpr, CXXDefaultInitExpr>(S, Block);
507508
}
508509

509510
const Stmt *DeadCodeScan::findDeadCode(const clang::CFGBlock *Block) {

clang/lib/Sema/SemaExpr.cpp

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

5573+
Expr *RewrittenExpr = (Init == Param->getDefaultArg() ? nullptr : Init);
55735574
return CXXDefaultArgExpr::Create(Context, InitializationContext->Loc, Param,
5574-
Init, InitializationContext->Context);
5575+
RewrittenExpr,
5576+
InitializationContext->Context);
55755577
}
55765578

55775579
static FieldDecl *FindFieldDeclInstantiationPattern(const ASTContext &Ctx,
@@ -5689,10 +5691,11 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) {
56895691
return ExprError();
56905692
}
56915693
Init = Res.get();
5692-
5694+
Expr *RewrittenInit =
5695+
(Init == Field->getInClassInitializer() ? nullptr : Init);
56935696
return CXXDefaultInitExpr::Create(Context, InitializationContext->Loc,
56945697
Field, InitializationContext->Context,
5695-
Init);
5698+
RewrittenInit);
56965699
}
56975700

56985701
// DR1351:

clang/lib/StaticAnalyzer/Core/ExprEngine.cpp

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

1990-
const Expr *ArgE;
1991-
if (const auto *DefE = dyn_cast<CXXDefaultArgExpr>(S))
1990+
bool HasRebuiltInit = false;
1991+
const Expr *ArgE = nullptr;
1992+
if (const auto *DefE = dyn_cast<CXXDefaultArgExpr>(S)) {
19921993
ArgE = DefE->getExpr();
1993-
else if (const auto *DefE = dyn_cast<CXXDefaultInitExpr>(S))
1994+
HasRebuiltInit = DefE->hasRewrittenInit();
1995+
} else if (const auto *DefE = dyn_cast<CXXDefaultInitExpr>(S)) {
19941996
ArgE = DefE->getExpr();
1995-
else
1997+
HasRebuiltInit = DefE->hasRewrittenInit();
1998+
} else
19961999
llvm_unreachable("unknown constant wrapper kind");
19972000

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

2004-
std::optional<SVal> ConstantVal = svalBuilder.getConstantVal(ArgE);
2005-
if (!ConstantVal)
2006-
ConstantVal = UnknownVal();
2007-
2008-
const LocationContext *LCtx = Pred->getLocationContext();
2009-
for (const auto I : PreVisit) {
2010-
ProgramStateRef State = I->getState();
2011-
State = State->BindExpr(S, LCtx, *ConstantVal);
2012-
if (IsTemporary)
2013-
State = createTemporaryRegionIfNeeded(State, LCtx,
2014-
cast<Expr>(S),
2015-
cast<Expr>(S));
2016-
Bldr2.generateNode(S, I, State);
2027+
Bldr2.generateNode(S, I, State);
2028+
}
20172029
}
20182030

20192031
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
@@ -507,7 +507,7 @@ union U {
507507
// CHECK-NEXT: `-DeclStmt {{.*}}
508508
// CHECK-NEXT: `-VarDecl {{.*}} g 'U':'GH112560::U' listinit
509509
// CHECK-NEXT: `-InitListExpr {{.*}} 'U':'GH112560::U' contains-errors field Field {{.*}} 'f' 'int'
510-
// CHECK-NEXT: `-CXXDefaultInitExpr {{.*}} 'int' contains-errors has rewritten init
510+
// CHECK-NEXT: `-CXXDefaultInitExpr {{.*}} 'int' contains-errors
511511
// CHECK-NEXT: `-RecoveryExpr {{.*}} 'int' contains-errors
512512
// DISABLED-NOT: -RecoveryExpr {{.*}} contains-errors
513513
void foo() {

clang/test/Analysis/lifetime-extended-regions.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,10 @@ void aggregateWithReferences() {
121121
clang_analyzer_dump(viaReference.rx); // expected-warning-re {{&lifetime_extended_object{int, viaReference, S{{[0-9]+}}} }}
122122
clang_analyzer_dump(viaReference.ry); // expected-warning-re {{&lifetime_extended_object{Composite, viaReference, S{{[0-9]+}}} }}
123123

124-
// FIXME: clang currently support extending lifetime of object bound to reference members of aggregates,
125-
// that are created from default member initializer. But CFG and ExprEngine need to be updated to address this change.
126-
// The following expect warning: {{&lifetime_extended_object{Composite, defaultInitExtended, S{{[0-9]+}}} }}
124+
// The lifetime of object bound to reference members of aggregates,
125+
// that are created from default member initializer was extended.
127126
RefAggregate defaultInitExtended{i};
128-
clang_analyzer_dump(defaultInitExtended.ry); // expected-warning {{Unknown }}
127+
clang_analyzer_dump(defaultInitExtended.ry); // expected-warning-re {{&lifetime_extended_object{Composite, defaultInitExtended, S{{[0-9]+}}} }}
129128
}
130129

131130
void lambda() {

clang/test/SemaCXX/cxx2c-placeholder-vars.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,16 +274,16 @@ void f() {
274274
// CHECK: ClassTemplateSpecializationDecl {{.*}} struct A definition
275275
// CHECK: CXXConstructorDecl {{.*}} implicit used constexpr A 'void () noexcept'
276276
// CHECK-NEXT: CXXCtorInitializer Field {{.*}} '_' 'int'
277-
// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int' has rewritten init
277+
// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int'
278278
// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 1
279279
// CHECK-NEXT: CXXCtorInitializer Field {{.*}} '_' 'int'
280-
// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int' has rewritten init
280+
// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int'
281281
// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 2
282282
// CHECK-NEXT: CXXCtorInitializer Field {{.*}} 'a' 'int'
283-
// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int' has rewritten init
283+
// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int'
284284
// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 3
285285
// CHECK-NEXT: CXXCtorInitializer Field {{.*}} '_' 'int'
286-
// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int' has rewritten init
286+
// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int'
287287
// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 4
288288
// CHECK-NEXT: CompoundStmt {{.*}}
289289

clang/test/SemaCXX/warn-unreachable.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,3 +414,42 @@ void tautological_compare(bool x, int y) {
414414
calledFun();
415415

416416
}
417+
418+
namespace test_rebuilt_default_arg {
419+
struct A {
420+
explicit A(int = __builtin_LINE());
421+
};
422+
423+
int h(int a) {
424+
return 3;
425+
A(); // expected-warning {{will never be executed}}
426+
}
427+
428+
struct Temp {
429+
Temp();
430+
~Temp();
431+
};
432+
433+
struct B {
434+
explicit B(const Temp &t = Temp());
435+
};
436+
int f(int a) {
437+
return 3;
438+
B(); // expected-warning {{will never be executed}}
439+
}
440+
} // namespace test_rebuilt_default_arg
441+
namespace test_rebuilt_default_init {
442+
443+
struct A {
444+
A();
445+
~A();
446+
};
447+
448+
struct B {
449+
const A &t = A();
450+
};
451+
int f(int a) {
452+
return 3;
453+
A{}; // expected-warning {{will never be executed}}
454+
}
455+
} // namespace test_rebuilt_default_init

0 commit comments

Comments
 (0)