Skip to content

Commit 092af69

Browse files
committed
[Analyzer][CFG] Correctly handle rebuilt default arg and default init expression
Signed-off-by: yronglin <[email protected]>
1 parent b6be53d commit 092af69

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
@@ -270,6 +270,10 @@ Code Completion
270270
Static Analyzer
271271
---------------
272272

273+
- Clang currently support extending lifetime of object bound to
274+
reference members of aggregates in CFG and ExprEngine, that are
275+
created from default member initializer.
276+
273277
New features
274278
^^^^^^^^^^^^
275279

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);
@@ -2263,16 +2267,10 @@ CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc,
22632267
asc, ExternallyDestructed);
22642268

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

22772275
case Stmt::CXXBindTemporaryExprClass:
22782276
return VisitCXXBindTemporaryExpr(cast<CXXBindTemporaryExpr>(S), asc);
@@ -2442,6 +2440,40 @@ CFGBlock *CFGBuilder::VisitChildren(Stmt *S) {
24422440
return B;
24432441
}
24442442

2443+
CFGBlock *CFGBuilder::VisitCXXDefaultArgExpr(CXXDefaultArgExpr *Arg,
2444+
AddStmtChoice asc) {
2445+
if (Arg->hasRewrittenInit()) {
2446+
if (asc.alwaysAdd(*this, Arg)) {
2447+
autoCreateBlock();
2448+
appendStmt(Block, Arg);
2449+
}
2450+
return VisitStmt(Arg->getExpr(), asc);
2451+
}
2452+
2453+
// We can't add the default argument if it's not rewritten because the
2454+
// expression inside a CXXDefaultArgExpr is owned by the called function's
2455+
// declaration, not by the caller, we could end up with the same expression
2456+
// appearing multiple times.
2457+
return VisitStmt(Arg, asc);
2458+
}
2459+
2460+
CFGBlock *CFGBuilder::VisitCXXDefaultInitExpr(CXXDefaultInitExpr *Init,
2461+
AddStmtChoice asc) {
2462+
if (Init->hasRewrittenInit()) {
2463+
if (asc.alwaysAdd(*this, Init)) {
2464+
autoCreateBlock();
2465+
appendStmt(Block, Init);
2466+
}
2467+
return VisitStmt(Init->getExpr(), asc);
2468+
}
2469+
2470+
// We can't add the default initializer if it's not rewritten because multiple
2471+
// CXXDefaultInitExprs for the same sub-expression to be used in the same
2472+
// function (through aggregate initialization). we could end up with the same
2473+
// expression appearing multiple times.
2474+
return VisitStmt(Init, asc);
2475+
}
2476+
24452477
CFGBlock *CFGBuilder::VisitInitListExpr(InitListExpr *ILE, AddStmtChoice asc) {
24462478
if (asc.alwaysAdd(*this, ILE)) {
24472479
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
@@ -1991,33 +1991,45 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
19911991
ExplodedNodeSet Tmp;
19921992
StmtNodeBuilder Bldr2(PreVisit, Tmp, *currBldrCtx);
19931993

1994-
const Expr *ArgE;
1995-
if (const auto *DefE = dyn_cast<CXXDefaultArgExpr>(S))
1994+
bool HasRebuiltInit = false;
1995+
const Expr *ArgE = nullptr;
1996+
if (const auto *DefE = dyn_cast<CXXDefaultArgExpr>(S)) {
19961997
ArgE = DefE->getExpr();
1997-
else if (const auto *DefE = dyn_cast<CXXDefaultInitExpr>(S))
1998+
HasRebuiltInit = DefE->hasRewrittenInit();
1999+
} else if (const auto *DefE = dyn_cast<CXXDefaultInitExpr>(S)) {
19982000
ArgE = DefE->getExpr();
1999-
else
2001+
HasRebuiltInit = DefE->hasRewrittenInit();
2002+
} else
20002003
llvm_unreachable("unknown constant wrapper kind");
20012004

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

2008-
std::optional<SVal> ConstantVal = svalBuilder.getConstantVal(ArgE);
2009-
if (!ConstantVal)
2010-
ConstantVal = UnknownVal();
2011-
2012-
const LocationContext *LCtx = Pred->getLocationContext();
2013-
for (const auto I : PreVisit) {
2014-
ProgramStateRef State = I->getState();
2015-
State = State->BindExpr(S, LCtx, *ConstantVal);
2016-
if (IsTemporary)
2017-
State = createTemporaryRegionIfNeeded(State, LCtx,
2018-
cast<Expr>(S),
2019-
cast<Expr>(S));
2020-
Bldr2.generateNode(S, I, State);
2031+
Bldr2.generateNode(S, I, State);
2032+
}
20212033
}
20222034

20232035
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)