summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArtem Dergachev <artem.dergachev@gmail.com>2018-07-31 20:45:53 +0000
committerArtem Dergachev <artem.dergachev@gmail.com>2018-07-31 20:45:53 +0000
commit4b1d99562879a0e3ff14137592345925f32a8482 (patch)
tree6b35e7ed18b5f9a7e00ce21c5e0accc41e69071d
parent90f288ce4978a49280fceff4cd7cc7a0f549ccca (diff)
[CFG] [analyzer] Implement function argument construction contexts.
In r330377 and r338425 we have already identified what constitutes function argument constructors and added stubs in order to prevent confusing them with other temporary object constructors. Now we implement a ConstructionContext sub-class to carry all the necessary information about the construction site, namely call expression and argument index. On the analyzer side, the patch interacts with the recently implemented pre-C++17 copy elision support in an interesting manner. If on the CFG side we didn't find a construction context for the elidable constructor, we build the CFG as if the elidable constructor is not elided, and the non-elided constructor within it is a simple temporary. But the same problem may occur in the analyzer: if the elidable constructor has a construction context but the analyzer doesn't implement such context yet, the analyzer should also try to skip copy elision and still inline the non-elided temporary constructor. This was implemented by adding a "roll back" mechanism: when elision fails, roll back the changes and proceed as if it's a simple temporary. The approach is wonky, but i'm fine with that as long as it's merely a defensive mechanism that should eventually go away once all construction contexts become supported. Differential Revision: https://reviews.llvm.org/D48681. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@338436 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r--include/clang/Analysis/CFG.h3
-rw-r--r--include/clang/Analysis/ConstructionContext.h47
-rw-r--r--lib/Analysis/CFG.cpp28
-rw-r--r--lib/Analysis/ConstructionContext.cpp22
-rw-r--r--lib/StaticAnalyzer/Core/ExprEngineCXX.cpp43
-rw-r--r--test/Analysis/cfg-rich-constructors.cpp52
-rw-r--r--test/Analysis/cfg-rich-constructors.mm8
7 files changed, 152 insertions, 51 deletions
diff --git a/include/clang/Analysis/CFG.h b/include/clang/Analysis/CFG.h
index 1655325db7..bf81d8358a 100644
--- a/include/clang/Analysis/CFG.h
+++ b/include/clang/Analysis/CFG.h
@@ -196,7 +196,8 @@ public:
// These are possible in C++17 due to mandatory copy elision.
isa<ReturnedValueConstructionContext>(C) ||
isa<VariableConstructionContext>(C) ||
- isa<ConstructorInitializerConstructionContext>(C)));
+ isa<ConstructorInitializerConstructionContext>(C) ||
+ isa<ArgumentConstructionContext>(C)));
Data2.setPointer(const_cast<ConstructionContext *>(C));
}
diff --git a/include/clang/Analysis/ConstructionContext.h b/include/clang/Analysis/ConstructionContext.h
index 40cb0e7e5d..c4a8a77d88 100644
--- a/include/clang/Analysis/ConstructionContext.h
+++ b/include/clang/Analysis/ConstructionContext.h
@@ -41,6 +41,12 @@ private:
/// new-expression triggers construction of the newly allocated object(s).
TriggerTy Trigger;
+ /// If a single trigger statement triggers multiple constructors, they are
+ /// usually being enumerated. This covers function argument constructors
+ /// triggered by a call-expression and items in an initializer list triggered
+ /// by an init-list-expression.
+ unsigned Index;
+
/// Sometimes a single trigger is not enough to describe the construction
/// site. In this case we'd have a chain of "partial" construction context
/// layers.
@@ -55,13 +61,13 @@ private:
/// Not all of these are currently supported.
const ConstructionContextLayer *Parent = nullptr;
- ConstructionContextLayer(TriggerTy Trigger,
- const ConstructionContextLayer *Parent)
- : Trigger(Trigger), Parent(Parent) {}
+ ConstructionContextLayer(TriggerTy Trigger, unsigned Index,
+ const ConstructionContextLayer *Parent)
+ : Trigger(Trigger), Index(Index), Parent(Parent) {}
public:
static const ConstructionContextLayer *
- create(BumpVectorContext &C, TriggerTy Trigger,
+ create(BumpVectorContext &C, TriggerTy Trigger, unsigned Index = 0,
const ConstructionContextLayer *Parent = nullptr);
const ConstructionContextLayer *getParent() const { return Parent; }
@@ -75,11 +81,13 @@ public:
return Trigger.dyn_cast<CXXCtorInitializer *>();
}
+ unsigned getIndex() const { return Index; }
+
/// Returns true if these layers are equal as individual layers, even if
/// their parents are different.
bool isSameLayer(const ConstructionContextLayer *Other) const {
assert(Other);
- return (Trigger == Other->Trigger);
+ return (Trigger == Other->Trigger && Index == Other->Index);
}
/// See if Other is a proper initial segment of this construction context
@@ -114,7 +122,8 @@ public:
SimpleReturnedValueKind,
CXX17ElidedCopyReturnedValueKind,
RETURNED_VALUE_BEGIN = SimpleReturnedValueKind,
- RETURNED_VALUE_END = CXX17ElidedCopyReturnedValueKind
+ RETURNED_VALUE_END = CXX17ElidedCopyReturnedValueKind,
+ ArgumentKind
};
protected:
@@ -469,6 +478,32 @@ public:
}
};
+class ArgumentConstructionContext : public ConstructionContext {
+ const Expr *CE; // The call of which the context is an argument.
+ unsigned Index; // Which argument we're constructing.
+ const CXXBindTemporaryExpr *BTE; // Whether the object needs to be destroyed.
+
+ friend class ConstructionContext; // Allows to create<>() itself.
+
+ explicit ArgumentConstructionContext(const Expr *CE, unsigned Index,
+ const CXXBindTemporaryExpr *BTE)
+ : ConstructionContext(ArgumentKind), CE(CE),
+ Index(Index), BTE(BTE) {
+ assert(isa<CallExpr>(CE) || isa<CXXConstructExpr>(CE) ||
+ isa<ObjCMessageExpr>(CE));
+ // BTE is optional.
+ }
+
+public:
+ const Expr *getCallLikeExpr() const { return CE; }
+ unsigned getIndex() const { return Index; }
+ const CXXBindTemporaryExpr *getCXXBindTemporaryExpr() const { return BTE; }
+
+ static bool classof(const ConstructionContext *CC) {
+ return CC->getKind() == ArgumentKind;
+ }
+};
+
} // end namespace clang
#endif // LLVM_CLANG_ANALYSIS_CONSTRUCTIONCONTEXT_H
diff --git a/lib/Analysis/CFG.cpp b/lib/Analysis/CFG.cpp
index cef75e91f4..d8081d0189 100644
--- a/lib/Analysis/CFG.cpp
+++ b/lib/Analysis/CFG.cpp
@@ -693,16 +693,13 @@ private:
std::is_same<CallLikeExpr, CXXConstructExpr>::value ||
std::is_same<CallLikeExpr, ObjCMessageExpr>::value>>
void findConstructionContextsForArguments(CallLikeExpr *E) {
- // A stub for the code that'll eventually be used for finding construction
- // contexts for constructors of C++ object-type arguments passed into
- // call-like expression E.
- // FIXME: Once actually implemented, this construction context layer should
- // include the index of the argument as well.
- for (auto Arg : E->arguments())
+ for (unsigned i = 0, e = E->getNumArgs(); i != e; ++i) {
+ Expr *Arg = E->getArg(i);
if (Arg->getType()->getAsCXXRecordDecl() && !Arg->isGLValue())
findConstructionContexts(
- ConstructionContextLayer::create(cfg->getBumpVectorContext(), E),
+ ConstructionContextLayer::create(cfg->getBumpVectorContext(), E, i),
Arg);
+ }
}
// Unset the construction context after consuming it. This is done immediately
@@ -1289,9 +1286,9 @@ void CFGBuilder::findConstructionContexts(
if (!Child)
return;
- auto withExtraLayer = [this, Layer](Stmt *S) {
+ auto withExtraLayer = [this, Layer](Stmt *S, unsigned Index = 0) {
return ConstructionContextLayer::create(cfg->getBumpVectorContext(), S,
- Layer);
+ Index, Layer);
};
switch(Child->getStmtClass()) {
@@ -5012,7 +5009,7 @@ static void print_construction_context(raw_ostream &OS,
OS << ", ";
const auto *SICC = cast<SimpleConstructorInitializerConstructionContext>(CC);
print_initializer(OS, Helper, SICC->getCXXCtorInitializer());
- break;
+ return;
}
case ConstructionContext::CXX17ElidedCopyConstructorInitializerKind: {
OS << ", ";
@@ -5063,6 +5060,17 @@ static void print_construction_context(raw_ostream &OS,
Stmts.push_back(TOCC->getConstructorAfterElision());
break;
}
+ case ConstructionContext::ArgumentKind: {
+ const auto *ACC = cast<ArgumentConstructionContext>(CC);
+ if (const Stmt *BTE = ACC->getCXXBindTemporaryExpr()) {
+ OS << ", ";
+ Helper.handledStmt(const_cast<Stmt *>(BTE), OS);
+ }
+ OS << ", ";
+ Helper.handledStmt(const_cast<Expr *>(ACC->getCallLikeExpr()), OS);
+ OS << "+" << ACC->getIndex();
+ return;
+ }
}
for (auto I: Stmts)
if (I) {
diff --git a/lib/Analysis/ConstructionContext.cpp b/lib/Analysis/ConstructionContext.cpp
index 3ffb0a6b35..9d255a0bcf 100644
--- a/lib/Analysis/ConstructionContext.cpp
+++ b/lib/Analysis/ConstructionContext.cpp
@@ -21,10 +21,11 @@ using namespace clang;
const ConstructionContextLayer *
ConstructionContextLayer::create(BumpVectorContext &C, TriggerTy Trigger,
+ unsigned Index,
const ConstructionContextLayer *Parent) {
ConstructionContextLayer *CC =
C.getAllocator().Allocate<ConstructionContextLayer>();
- return new (CC) ConstructionContextLayer(Trigger, Parent);
+ return new (CC) ConstructionContextLayer(Trigger, Index, Parent);
}
bool ConstructionContextLayer::isStrictlyMoreSpecificThan(
@@ -111,11 +112,14 @@ const ConstructionContext *ConstructionContext::createFromLayers(
}
assert(ParentLayer->isLast());
- // This is a constructor into a function argument. Not implemented yet.
+ // This is a constructor into a function argument.
if (isa<CallExpr>(ParentLayer->getTriggerStmt()) ||
isa<CXXConstructExpr>(ParentLayer->getTriggerStmt()) ||
- isa<ObjCMessageExpr>(ParentLayer->getTriggerStmt()))
- return nullptr;
+ isa<ObjCMessageExpr>(ParentLayer->getTriggerStmt())) {
+ return create<ArgumentConstructionContext>(
+ C, cast<Expr>(ParentLayer->getTriggerStmt()),
+ ParentLayer->getIndex(), BTE);
+ }
// This is C++17 copy-elided construction into return statement.
if (auto *RS = dyn_cast<ReturnStmt>(ParentLayer->getTriggerStmt())) {
assert(!RS->getRetValue()->getType().getCanonicalType()
@@ -175,11 +179,15 @@ const ConstructionContext *ConstructionContext::createFromLayers(
assert(TopLayer->isLast());
return create<SimpleReturnedValueConstructionContext>(C, RS);
}
- // This is a constructor into a function argument. Not implemented yet.
+ // This is a constructor into a function argument.
if (isa<CallExpr>(TopLayer->getTriggerStmt()) ||
isa<CXXConstructExpr>(TopLayer->getTriggerStmt()) ||
- isa<ObjCMessageExpr>(TopLayer->getTriggerStmt()))
- return nullptr;
+ isa<ObjCMessageExpr>(TopLayer->getTriggerStmt())) {
+ assert(TopLayer->isLast());
+ return create<ArgumentConstructionContext>(
+ C, cast<Expr>(TopLayer->getTriggerStmt()), TopLayer->getIndex(),
+ /*BTE=*/nullptr);
+ }
llvm_unreachable("Unexpected construction context with statement!");
} else if (const CXXCtorInitializer *I = TopLayer->getTriggerInit()) {
assert(TopLayer->isLast());
diff --git a/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
index dc124fc3ff..30d1e2413d 100644
--- a/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
+++ b/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
@@ -221,25 +221,42 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction(
// and construct directly into the final object. This call
// also sets the CallOpts flags for us.
SVal V;
+ // If the elided copy/move constructor is not supported, there's still
+ // benefit in trying to model the non-elided constructor.
+ // Stash our state before trying to elide, as it'll get overwritten.
+ ProgramStateRef PreElideState = State;
+ EvalCallOptions PreElideCallOpts = CallOpts;
+
std::tie(State, V) = prepareForObjectConstruction(
CE, State, LCtx, TCC->getConstructionContextAfterElision(), CallOpts);
- // Remember that we've elided the constructor.
- State = addObjectUnderConstruction(State, CE, LCtx, V);
+ // FIXME: This definition of "copy elision has not failed" is unreliable.
+ // It doesn't indicate that the constructor will actually be inlined
+ // later; it is still up to evalCall() to decide.
+ if (!CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion) {
+ // Remember that we've elided the constructor.
+ State = addObjectUnderConstruction(State, CE, LCtx, V);
- // Remember that we've elided the destructor.
- if (BTE)
- State = elideDestructor(State, BTE, LCtx);
+ // Remember that we've elided the destructor.
+ if (BTE)
+ State = elideDestructor(State, BTE, LCtx);
- // Instead of materialization, shamelessly return
- // the final object destination.
- if (MTE)
- State = addObjectUnderConstruction(State, MTE, LCtx, V);
+ // Instead of materialization, shamelessly return
+ // the final object destination.
+ if (MTE)
+ State = addObjectUnderConstruction(State, MTE, LCtx, V);
- return std::make_pair(State, V);
+ return std::make_pair(State, V);
+ } else {
+ // Copy elision failed. Revert the changes and proceed as if we have
+ // a simple temporary.
+ State = PreElideState;
+ CallOpts = PreElideCallOpts;
+ }
+ // FALL-THROUGH
}
case ConstructionContext::SimpleTemporaryObjectKind: {
- const auto *TCC = cast<SimpleTemporaryObjectConstructionContext>(CC);
+ const auto *TCC = cast<TemporaryObjectConstructionContext>(CC);
const CXXBindTemporaryExpr *BTE = TCC->getCXXBindTemporaryExpr();
const MaterializeTemporaryExpr *MTE = TCC->getMaterializedTemporaryExpr();
SVal V = UnknownVal();
@@ -274,6 +291,10 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction(
CallOpts.IsTemporaryCtorOrDtor = true;
return std::make_pair(State, V);
}
+ case ConstructionContext::ArgumentKind: {
+ // Function argument constructors. Not implemented yet.
+ break;
+ }
}
}
// If we couldn't find an existing region to construct into, assume we're
diff --git a/test/Analysis/cfg-rich-constructors.cpp b/test/Analysis/cfg-rich-constructors.cpp
index d66c9d3118..88b03788da 100644
--- a/test/Analysis/cfg-rich-constructors.cpp
+++ b/test/Analysis/cfg-rich-constructors.cpp
@@ -826,21 +826,49 @@ void useC(C c);
void useCByReference(const C &c);
void useD(D d);
void useDByReference(const D &d);
+void useCAndD(C c, D d);
-// FIXME: Find construction context for the argument.
// CHECK: void passArgument()
// CHECK: 1: useC
// CHECK-NEXT: 2: [B1.1] (ImplicitCastExpr, FunctionToPointerDecay, void (*)(class C))
-// CXX11-NEXT: 3: C() (CXXConstructExpr, [B1.4], class C)
+// CXX11-ELIDE-NEXT: 3: C() (CXXConstructExpr, [B1.4], [B1.5], class C)
+// CXX11-NOELIDE-NEXT: 3: C() (CXXConstructExpr, [B1.4], class C)
// CXX11-NEXT: 4: [B1.3]
-// CXX11-NEXT: 5: [B1.4] (CXXConstructExpr, class C)
+// CXX11-NEXT: 5: [B1.4] (CXXConstructExpr, [B1.6]+0, class C)
// CXX11-NEXT: 6: [B1.2]([B1.5])
-// CXX17-NEXT: 3: C() (CXXConstructExpr, class C)
+// CXX17-NEXT: 3: C() (CXXConstructExpr, [B1.4]+0, class C)
// CXX17-NEXT: 4: [B1.2]([B1.3])
void passArgument() {
useC(C());
}
+// CHECK: void passTwoArguments()
+// CHECK: [B1]
+// CHECK-NEXT: 1: useCAndD
+// CHECK-NEXT: 2: [B1.1] (ImplicitCastExpr, FunctionToPointerDecay, void (*)(class C, class argument_constructors::D))
+// CXX11-ELIDE-NEXT: 3: C() (CXXConstructExpr, [B1.4], [B1.5], class C)
+// CXX11-NOELIDE-NEXT: 3: C() (CXXConstructExpr, [B1.4], class C)
+// CXX11-NEXT: 4: [B1.3]
+// CXX11-NEXT: 5: [B1.4] (CXXConstructExpr, [B1.12]+0, class C)
+// CXX11-ELIDE-NEXT: 6: argument_constructors::D() (CXXConstructExpr, [B1.7], [B1.9], [B1.10], class argument_constructors::D)
+// CXX11-NOELIDE-NEXT: 6: argument_constructors::D() (CXXConstructExpr, [B1.7], [B1.9], class argument_constructors::D)
+// CXX11-NEXT: 7: [B1.6] (BindTemporary)
+// CXX11-NEXT: 8: [B1.7] (ImplicitCastExpr, NoOp, const class argument_constructors::D)
+// CXX11-NEXT: 9: [B1.8]
+// CXX11-NEXT: 10: [B1.9] (CXXConstructExpr, [B1.11], [B1.12]+1, class argument_constructors::D)
+// CXX11-NEXT: 11: [B1.10] (BindTemporary)
+// CXX11-NEXT: 12: [B1.2]([B1.5], [B1.11])
+// CXX11-NEXT: 13: ~argument_constructors::D() (Temporary object destructor)
+// CXX11-NEXT: 14: ~argument_constructors::D() (Temporary object destructor)
+// CXX17-NEXT: 3: C() (CXXConstructExpr, [B1.6]+0, class C)
+// CXX17-NEXT: 4: argument_constructors::D() (CXXConstructExpr, [B1.5], [B1.6]+1, class argument_co
+// CXX17-NEXT: 5: [B1.4] (BindTemporary)
+// CXX17-NEXT: 6: [B1.2]([B1.3], [B1.5])
+// CXX17-NEXT: 7: ~argument_constructors::D() (Temporary object destructor)
+void passTwoArguments() {
+ useCAndD(C(), D());
+}
+
// CHECK: void passArgumentByReference()
// CHECK: 1: useCByReference
// CHECK-NEXT: 2: [B1.1] (ImplicitCastExpr, FunctionToPointerDecay, void (*)(const class C &))
@@ -852,20 +880,20 @@ void passArgumentByReference() {
useCByReference(C());
}
-// FIXME: Find construction context for the argument.
// CHECK: void passArgumentWithDestructor()
// CHECK: 1: useD
// CHECK-NEXT: 2: [B1.1] (ImplicitCastExpr, FunctionToPointerDecay, void (*)(class argument_constructors::D))
-// CXX11-NEXT: 3: argument_constructors::D() (CXXConstructExpr, [B1.4], [B1.6], class argument_constructors::D)
+// CXX11-ELIDE-NEXT: 3: argument_constructors::D() (CXXConstructExpr, [B1.4], [B1.6], [B1.7], class argument_constructors::D)
+// CXX11-NOELIDE-NEXT: 3: argument_constructors::D() (CXXConstructExpr, [B1.4], [B1.6], class argument_constructors::D)
// CXX11-NEXT: 4: [B1.3] (BindTemporary)
// CXX11-NEXT: 5: [B1.4] (ImplicitCastExpr, NoOp, const class argument_constructors::D)
// CXX11-NEXT: 6: [B1.5]
-// CXX11-NEXT: 7: [B1.6] (CXXConstructExpr, class argument_constructors::D)
+// CXX11-NEXT: 7: [B1.6] (CXXConstructExpr, [B1.8], [B1.9]+0, class argument_constructors::D)
// CXX11-NEXT: 8: [B1.7] (BindTemporary)
// CXX11-NEXT: 9: [B1.2]([B1.8])
// CXX11-NEXT: 10: ~argument_constructors::D() (Temporary object destructor)
// CXX11-NEXT: 11: ~argument_constructors::D() (Temporary object destructor)
-// CXX17-NEXT: 3: argument_constructors::D() (CXXConstructExpr, class argument_constructors::D)
+// CXX17-NEXT: 3: argument_constructors::D() (CXXConstructExpr, [B1.4], [B1.5]+0, class argument_constructors::D)
// CXX17-NEXT: 4: [B1.3] (BindTemporary)
// CXX17-NEXT: 5: [B1.2]([B1.4])
// CXX17-NEXT: 6: ~argument_constructors::D() (Temporary object destructor)
@@ -886,13 +914,13 @@ void passArgumentWithDestructorByReference() {
useDByReference(D());
}
-// FIXME: Find construction context for the argument.
// CHECK: void passArgumentIntoAnotherConstructor()
-// CXX11: 1: argument_constructors::D() (CXXConstructExpr, [B1.2], [B1.4], class argument_constructors::D)
+// CXX11-ELIDE: 1: argument_constructors::D() (CXXConstructExpr, [B1.2], [B1.4], [B1.5], class argument_constructors::D)
+// CXX11-NOELIDE: 1: argument_constructors::D() (CXXConstructExpr, [B1.2], [B1.4], class argument_constructors::D)
// CXX11-NEXT: 2: [B1.1] (BindTemporary)
// CXX11-NEXT: 3: [B1.2] (ImplicitCastExpr, NoOp, const class argument_constructors::D)
// CXX11-NEXT: 4: [B1.3]
-// CXX11-NEXT: 5: [B1.4] (CXXConstructExpr, class argument_constructors::D)
+// CXX11-NEXT: 5: [B1.4] (CXXConstructExpr, [B1.6], [B1.7]+0, class argument_constructors::D)
// CXX11-NEXT: 6: [B1.5] (BindTemporary)
// CXX11-ELIDE-NEXT: 7: [B1.6] (CXXConstructExpr, [B1.9], [B1.10], class argument_constructors::E)
// CXX11-NOELIDE-NEXT: 7: [B1.6] (CXXConstructExpr, [B1.9], class argument_constructors::E)
@@ -902,7 +930,7 @@ void passArgumentWithDestructorByReference() {
// CXX11-NEXT: 11: argument_constructors::E e = argument_constructors::E(argument_constructors::D());
// CXX11-NEXT: 12: ~argument_constructors::D() (Temporary object destructor)
// CXX11-NEXT: 13: ~argument_constructors::D() (Temporary object destructor)
-// CXX17: 1: argument_constructors::D() (CXXConstructExpr, class argument_constructors::D)
+// CXX17: 1: argument_constructors::D() (CXXConstructExpr, [B1.2], [B1.3]+0, class argument_constructors::D)
// CXX17-NEXT: 2: [B1.1] (BindTemporary)
// CXX17-NEXT: 3: [B1.2] (CXXConstructExpr, [B1.5], class argument_constructors::E)
// CXX17-NEXT: 4: argument_constructors::E([B1.3]) (CXXFunctionalCastExpr, ConstructorConversion, class argument_constructors::E)
diff --git a/test/Analysis/cfg-rich-constructors.mm b/test/Analysis/cfg-rich-constructors.mm
index 0b81e274e9..289094293e 100644
--- a/test/Analysis/cfg-rich-constructors.mm
+++ b/test/Analysis/cfg-rich-constructors.mm
@@ -18,21 +18,21 @@ public:
-(D) bar;
@end
-// FIXME: Find construction context for the argument.
// CHECK: void passArgumentIntoMessage(E *e)
// CHECK: 1: e
// CHECK-NEXT: 2: [B1.1] (ImplicitCastExpr, LValueToRValue, E *)
-// CXX11-NEXT: 3: D() (CXXConstructExpr, [B1.4], [B1.6], class D)
+// CXX11-ELIDE-NEXT: 3: D() (CXXConstructExpr, [B1.4], [B1.6], [B1.7], class D)
+// CXX11-NOELIDE-NEXT: 3: D() (CXXConstructExpr, [B1.4], [B1.6], class D)
// CXX11-NEXT: 4: [B1.3] (BindTemporary)
// CXX11-NEXT: 5: [B1.4] (ImplicitCastExpr, NoOp, const class D)
// CXX11-NEXT: 6: [B1.5]
-// CXX11-NEXT: 7: [B1.6] (CXXConstructExpr, class D)
+// CXX11-NEXT: 7: [B1.6] (CXXConstructExpr, [B1.8], [B1.9]+0, class D)
// CXX11-NEXT: 8: [B1.7] (BindTemporary)
// Double brackets trigger FileCheck variables, escape.
// CXX11-NEXT: 9: {{\[}}[B1.2] foo:[B1.8]]
// CXX11-NEXT: 10: ~D() (Temporary object destructor)
// CXX11-NEXT: 11: ~D() (Temporary object destructor)
-// CXX17-NEXT: 3: D() (CXXConstructExpr, class D)
+// CXX17-NEXT: 3: D() (CXXConstructExpr, [B1.4], [B1.5]+0, class D)
// CXX17-NEXT: 4: [B1.3] (BindTemporary)
// Double brackets trigger FileCheck variables, escape.
// CXX17-NEXT: 5: {{\[}}[B1.2] foo:[B1.4]]