diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h @@ -1233,8 +1233,7 @@ CXXTempObjectRegion(Expr const *E, MemSpaceRegion const *sReg) : TypedValueRegion(sReg, CXXTempObjectRegionKind), Ex(E) { assert(E); - assert(isa(sReg) || - isa(sReg)); + assert(isa(sReg)); } static void ProfileRegion(llvm::FoldingSetNodeID &ID, @@ -1255,6 +1254,45 @@ } }; +// C++ temporary object that have lifetime extended to lifetime of the +// variable. Usually they represent temporary bounds to reference variables. +class CXXLifetimeExtendedObjectRegion : public TypedValueRegion { + friend class MemRegionManager; + + Expr const *Ex; + ValueDecl const *ExD; + + CXXLifetimeExtendedObjectRegion(Expr const *E, ValueDecl const *D, + MemSpaceRegion const *sReg) + : TypedValueRegion(sReg, CXXLifetimeExtendedObjectRegionKind), Ex(E), + ExD(D) { + assert(E); + assert(D); + assert((isa(sReg))); + } + + static void ProfileRegion(llvm::FoldingSetNodeID &ID, Expr const *E, + ValueDecl const *D, const MemRegion *sReg); + +public: + LLVM_ATTRIBUTE_RETURNS_NONNULL + const Expr *getExpr() const { return Ex; } + LLVM_ATTRIBUTE_RETURNS_NONNULL + const ValueDecl *getExtendingDecl() const { return ExD; } + /// It might return null. + const StackFrameContext *getStackFrame() const; + + QualType getValueType() const override { return Ex->getType(); } + + void dumpToStream(raw_ostream &os) const override; + + void Profile(llvm::FoldingSetNodeID &ID) const override; + + static bool classof(const MemRegion *R) { + return R->getKind() == CXXLifetimeExtendedObjectRegionKind; + } +}; + // CXXBaseObjectRegion represents a base object within a C++ object. It is // identified by the base class declaration and the region of its parent object. class CXXBaseObjectRegion : public TypedValueRegion { @@ -1487,6 +1525,19 @@ const CXXTempObjectRegion *getCXXTempObjectRegion(Expr const *Ex, LocationContext const *LC); + /// Create a CXXLifetimeExtendedObjectRegion for temporaries which are + /// lifetime-extended by local references. + const CXXLifetimeExtendedObjectRegion * + getCXXLifetimeExtendedObjectRegion(Expr const *Ex, ValueDecl const *VD, + LocationContext const *LC); + + /// Create a CXXLifetimeExtendedObjectRegion for temporaries which are + /// lifetime-extended by *static* references. + /// This differs from \ref getCXXLifetimeExtendedObjectRegion(Expr const *, ValueDecl const *, LocationContext const *) in the super-region + /// used. + const CXXLifetimeExtendedObjectRegion * + getCXXStaticLifetimeExtendedObjectRegion(const Expr *Ex, ValueDecl const *VD); + /// Create a CXXBaseObjectRegion with the given base class for region /// \p Super. /// @@ -1525,11 +1576,6 @@ const LocationContext *lc, unsigned blockCount); - /// Create a CXXTempObjectRegion for temporaries which are lifetime-extended - /// by static references. This differs from getCXXTempObjectRegion in the - /// super-region used. - const CXXTempObjectRegion *getCXXStaticTempObjectRegion(const Expr *Ex); - private: template diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/Regions.def b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/Regions.def --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/Regions.def +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/Regions.def @@ -69,6 +69,7 @@ REGION(CXXBaseObjectRegion, TypedValueRegion) REGION(CXXDerivedObjectRegion, TypedValueRegion) REGION(CXXTempObjectRegion, TypedValueRegion) + REGION(CXXLifetimeExtendedObjectRegion, TypedValueRegion) REGION(CXXThisRegion, TypedValueRegion) ABSTRACT_REGION(DeclRegion, TypedValueRegion) REGION(FieldRegion, DeclRegion) diff --git a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp --- a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp @@ -96,6 +96,14 @@ os << "stack memory associated with local variable '" << VR->getString() << '\''; range = VR->getDecl()->getSourceRange(); + } else if (const auto *LER = dyn_cast(R)) { + QualType Ty = LER->getValueType().getLocalUnqualifiedType(); + os << "stack memory associated with temporary object of type '"; + Ty.print(os, Ctx.getPrintingPolicy()); + os << "' lifetime extended by local variable"; + if (const IdentifierInfo *ID = LER->getExtendingDecl()->getIdentifier()) + os << " '" << ID->getName() << '\''; + range = LER->getExpr()->getSourceRange(); } else if (const auto *TOR = dyn_cast(R)) { QualType Ty = TOR->getValueType().getLocalUnqualifiedType(); os << "stack memory associated with temporary object of type '"; @@ -376,7 +384,7 @@ llvm::raw_svector_ostream Out(Buf); const SourceRange Range = genName(Out, Referred, Ctx.getASTContext()); - if (isa(Referrer)) { + if (isa(Referrer)) { Out << " is still referred to by a temporary object on the stack " << CommonSuffix; auto Report = diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -386,15 +386,19 @@ State = finishObjectConstruction(State, MT, LC); State = State->BindExpr(Result, LC, *V); return State; - } else { + } else if (const ValueDecl *VD = MT->getExtendingDecl()) { StorageDuration SD = MT->getStorageDuration(); + assert(SD != SD_FullExpression); // If this object is bound to a reference with static storage duration, we // put it in a different region to prevent "address leakage" warnings. if (SD == SD_Static || SD == SD_Thread) { - TR = MRMgr.getCXXStaticTempObjectRegion(Init); + TR = MRMgr.getCXXStaticLifetimeExtendedObjectRegion(Init, VD); } else { - TR = MRMgr.getCXXTempObjectRegion(Init, LC); + TR = MRMgr.getCXXLifetimeExtendedObjectRegion(Init, VD, LC); } + } else { + assert(MT->getStorageDuration() == SD_FullExpression); + TR = MRMgr.getCXXTempObjectRegion(Init, LC); } } else { TR = MRMgr.getCXXTempObjectRegion(Init, LC); diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -284,7 +284,8 @@ CallOpts.IsTemporaryCtorOrDtor = true; if (MTE) { if (const ValueDecl *VD = MTE->getExtendingDecl()) { - assert(MTE->getStorageDuration() != SD_FullExpression); + StorageDuration SD = MTE->getStorageDuration(); + assert(SD != SD_FullExpression); if (!VD->getType()->isReferenceType()) { // We're lifetime-extended by a surrounding aggregate. // Automatic destructors aren't quite working in this case @@ -293,11 +294,15 @@ // the MaterializeTemporaryExpr? CallOpts.IsTemporaryLifetimeExtendedViaAggregate = true; } - } - if (MTE->getStorageDuration() == SD_Static || - MTE->getStorageDuration() == SD_Thread) - return loc::MemRegionVal(MRMgr.getCXXStaticTempObjectRegion(E)); + if (SD == SD_Static || SD == SD_Thread) + return loc::MemRegionVal( + MRMgr.getCXXStaticLifetimeExtendedObjectRegion(E, VD)); + + return loc::MemRegionVal( + MRMgr.getCXXLifetimeExtendedObjectRegion(E, VD, LCtx)); + } + assert(MTE->getStorageDuration() == SD_FullExpression); } return loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(E, LCtx)); @@ -799,7 +804,8 @@ StmtNodeBuilder Bldr(DstEvaluated, DstEvaluatedPostProcessed, *currBldrCtx); const AnalysisDeclContext *ADC = LCtx->getAnalysisDeclContext(); if (!ADC->getCFGBuildOptions().AddTemporaryDtors) { - if (llvm::isa_and_nonnull(TargetRegion) && + if (llvm::isa_and_nonnull(TargetRegion) && cast(Call->getDecl()) ->getParent() ->isAnyDestructorNoReturn()) { diff --git a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp --- a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp +++ b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp @@ -392,6 +392,25 @@ ProfileRegion(ID, Ex, getSuperRegion()); } +const StackFrameContext *CXXLifetimeExtendedObjectRegion::getStackFrame() const { + const auto *SSR = dyn_cast(getMemorySpace()); + return SSR ? SSR->getStackFrame() : nullptr; +} + +void CXXLifetimeExtendedObjectRegion::ProfileRegion(llvm::FoldingSetNodeID &ID, + const Expr *E, + const ValueDecl *D, + const MemRegion *sReg) { + ID.AddPointer(E); + ID.AddPointer(D); + ID.AddPointer(sReg); +} + +void CXXLifetimeExtendedObjectRegion::Profile( + llvm::FoldingSetNodeID &ID) const { + ProfileRegion(ID, Ex, ExD, getSuperRegion()); +} + void CXXBaseObjectRegion::ProfileRegion(llvm::FoldingSetNodeID &ID, const CXXRecordDecl *RD, bool IsVirtual, @@ -486,6 +505,16 @@ << "S" << Ex->getID(getContext()) << '}'; } +void CXXLifetimeExtendedObjectRegion::dumpToStream(raw_ostream &os) const { + os << "lifetime_extended_object{" << getValueType() << ", "; + if (const IdentifierInfo *ID = ExD->getIdentifier()) + os << ID->getName(); + else + os << "D" << ExD->getID(); + os << ", " + << "S" << Ex->getID(getContext()) << '}'; +} + void CXXBaseObjectRegion::dumpToStream(raw_ostream &os) const { os << "Base{" << superRegion << ',' << getDecl()->getName() << '}'; } @@ -750,6 +779,7 @@ case MemRegion::CXXBaseObjectRegionKind: case MemRegion::CXXDerivedObjectRegionKind: case MemRegion::CXXTempObjectRegionKind: + case MemRegion::CXXLifetimeExtendedObjectRegionKind: case MemRegion::CXXThisRegionKind: case MemRegion::ObjCIvarRegionKind: case MemRegion::NonParamVarRegionKind: @@ -1109,12 +1139,6 @@ return getSubRegion(BC, LC, blockCount, sReg); } -const CXXTempObjectRegion * -MemRegionManager::getCXXStaticTempObjectRegion(const Expr *Ex) { - return getSubRegion( - Ex, getGlobalsRegion(MemRegion::GlobalInternalSpaceRegionKind, nullptr)); -} - const CompoundLiteralRegion* MemRegionManager::getCompoundLiteralRegion(const CompoundLiteralExpr *CL, const LocationContext *LC) { @@ -1197,6 +1221,23 @@ return getSubRegion(E, getStackLocalsRegion(SFC)); } +const CXXLifetimeExtendedObjectRegion * +MemRegionManager::getCXXLifetimeExtendedObjectRegion( + const Expr *Ex, const ValueDecl *VD, const LocationContext *LC) { + const StackFrameContext *SFC = LC->getStackFrame(); + assert(SFC); + return getSubRegion( + Ex, VD, getStackLocalsRegion(SFC)); +} + +const CXXLifetimeExtendedObjectRegion * +MemRegionManager::getCXXStaticLifetimeExtendedObjectRegion( + const Expr *Ex, const ValueDecl *VD) { + return getSubRegion( + Ex, VD, + getGlobalsRegion(MemRegion::GlobalInternalSpaceRegionKind, nullptr)); +} + /// Checks whether \p BaseClass is a valid virtual or direct non-virtual base /// class of the type of \p Super. static bool isValidBaseClass(const CXXRecordDecl *BaseClass, @@ -1474,6 +1515,7 @@ case MemRegion::NonParamVarRegionKind: case MemRegion::ParamVarRegionKind: case MemRegion::CXXTempObjectRegionKind: + case MemRegion::CXXLifetimeExtendedObjectRegionKind: // Usual base regions. goto Finish; diff --git a/clang/lib/StaticAnalyzer/Core/Store.cpp b/clang/lib/StaticAnalyzer/Core/Store.cpp --- a/clang/lib/StaticAnalyzer/Core/Store.cpp +++ b/clang/lib/StaticAnalyzer/Core/Store.cpp @@ -144,6 +144,7 @@ case MemRegion::NonParamVarRegionKind: case MemRegion::ParamVarRegionKind: case MemRegion::CXXTempObjectRegionKind: + case MemRegion::CXXLifetimeExtendedObjectRegionKind: case MemRegion::CXXBaseObjectRegionKind: case MemRegion::CXXDerivedObjectRegionKind: return MakeElementRegion(cast(R), PointeeTy); diff --git a/clang/test/Analysis/lifetime-extended-regions.cpp b/clang/test/Analysis/lifetime-extended-regions.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/lifetime-extended-regions.cpp @@ -0,0 +1,170 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.core\ +// RUN: -analyzer-checker=debug.ExprInspection\ +// RUN: -Wno-dangling -Wno-c++1z-extensions\ +// RUN: -verify=expected,cpp14\ +// RUN: -x c++ -std=c++14 %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.core\ +// RUN: -analyzer-checker=debug.ExprInspection\ +// RUN: -analyzer-config elide-constructors=false\ +// RUN: -Wno-dangling -Wno-c++1z-extensions\ +// RUN: -verify=expected,cpp14\ +// RUN: -x c++ -std=c++14 %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.core\ +// RUN: -analyzer-checker=debug.ExprInspection\ +// RUN: -Wno-dangling -verify=expected,cpp17\ +// RUN: -x c++ -std=c++17 %s + +template +void clang_analyzer_dump(T&&) {} + +template +T create() { return T{}; } + +template +T const& select(bool cond, T const& t, T const& u) { return cond ? t : u; } + +struct Composite { + int x; + int y; +}; + +struct Derived : Composite { + int z; +}; + +template +struct Array { + T array[20]; + + T&& front() && { return static_cast(array[0]); } +}; + +void whole_object() { + int const& i = 10; // extends `int` + clang_analyzer_dump(i); // expected-warning-re {{&lifetime_extended_object{int, i, S{{[0-9]+}}} }} + Composite&& c = Composite{}; // extends `Composite` + clang_analyzer_dump(c); // expected-warning-re {{&lifetime_extended_object{Composite, c, S{{[0-9]+}}} }} + auto&& a = Array{}; // extends `Array` + clang_analyzer_dump(a); // expected-warning-re {{&lifetime_extended_object{Array, a, S{{[0-9]+}}} }} + Composite&& d = Derived{}; // extends `Derived` + clang_analyzer_dump(d); // expected-warning-re {{&Base{lifetime_extended_object{Derived, d, S{{[0-9]+}}},Composite} }} +} + +void member_access() { + int&& x = Composite{}.x; // extends `Composite` + clang_analyzer_dump(x); // expected-warning-re {{&lifetime_extended_object{Composite, x, S{{[0-9]+}}}.x }} + int&& y = create().y; // extends `Composite` + clang_analyzer_dump(y); // expected-warning-re {{&lifetime_extended_object{struct Composite, y, S{{[0-9]+}}}.y }} + int&& d = Array{}.front(); // dangles `Array` + clang_analyzer_dump(d); // expected-warning-re {{&Element{temp_object{Array, S{{[0-9]+}}}.array,0 S64b,int} }} +} + +void array_subscript() { + int&& i = Array{}.array[0]; // extends `Array` + clang_analyzer_dump(i); // expected-warning-re {{&Element{lifetime_extended_object{Array, i, S{{[0-9]+}}}.array,0 S64b,int} }} + auto&& c = Array{}.array[0]; // extends `Array` + clang_analyzer_dump(c); // expected-warning-re {{&Element{lifetime_extended_object{Array, c, S{{[0-9]+}}}.array,0 S64b,struct Composite} }} + auto&& x = Array{}.array[0].x; // extends `Array` + clang_analyzer_dump(x); // expected-warning-re {{&Element{lifetime_extended_object{Array, x, S{{[0-9]+}}}.array,0 S64b,struct Composite}.x }} +} + +void ternary(bool cond) { + Composite cc; + // Value category mismatch of the operands (lvalue and xvalue), ternary produces prvalue + auto&& ternaryProducesPRvalue = cond ? Composite{}.x : cc.x; // extends prvalue of 'int', `Composite` in true branch is destroyed + clang_analyzer_dump(ternaryProducesPRvalue); // expected-warning-re {{&lifetime_extended_object{int, ternaryProducesPRvalue, S{{[0-9]+}}} }} + + // Value category agrees (xvalues), lifetime extension is triggered + auto&& branchesExtended = cond ? Composite{}.x : static_cast(cc).x; // extends `Composite` in true branch + clang_analyzer_dump(branchesExtended); + // expected-warning-re@-1 {{&lifetime_extended_object{Composite, branchesExtended, S{{[0-9]+}}}.x }} + // expected-warning@-2 {{&cc.x }} + + // Object of different types in branches are lifetime extended + auto&& extendingDifferentTypes = cond ? Composite{}.x : Array{}.array[0]; // extends `Composite` or `Array` + clang_analyzer_dump(extendingDifferentTypes); + // expected-warning-re@-1 {{&lifetime_extended_object{Composite, extendingDifferentTypes, S{{[0-9]+}}}.x }} + // expected-warning-re@-2 {{&Element{lifetime_extended_object{Array, extendingDifferentTypes, S{{[0-9]+}}}.array,0 S64b,int} }} + + Composite const& variableAndExtended = cond ? static_cast(cc) : Array{}.array[0]; // extends `Array` in false branch + clang_analyzer_dump(variableAndExtended); + // expected-warning@-1 {{&cc }} + // expected-warning-re@-2 {{&Element{lifetime_extended_object{Array, variableAndExtended, S{{[0-9]+}}}.array,0 S64b,struct Composite} }} + + int const& extendAndDangling = cond ? Array{}.array[0] : Array{}.front(); // extends `Array` only in true branch, false branch dangles + clang_analyzer_dump(extendAndDangling); + // expected-warning-re@-1 {{&Element{lifetime_extended_object{Array, extendAndDangling, S{{[0-9]+}}}.array,0 S64b,int} }} + // expected-warning-re@-2 {{&Element{temp_object{Array, S{{[0-9]+}}}.array,0 S64b,int} }} +} + +struct RefAggregate { + int const& rx; + Composite&& ry = Composite{}; +}; + +void aggregateWithReferences() { + RefAggregate multipleExtensions = {10, Composite{}}; // extends `int` and `Composite` + clang_analyzer_dump(multipleExtensions.rx); // expected-warning-re {{&lifetime_extended_object{int, multipleExtensions, S{{[0-9]+}}} }} + clang_analyzer_dump(multipleExtensions.ry); // expected-warning-re {{&lifetime_extended_object{Composite, multipleExtensions, S{{[0-9]+}}} }} + + RefAggregate danglingAndExtended{Array{}.front(), Composite{}}; // extends only `Composite`, `Array` dangles + clang_analyzer_dump(danglingAndExtended.rx); // expected-warning-re {{&Element{temp_object{Array, S{{[0-9]+}}}.array,0 S64b,int} }} + clang_analyzer_dump(danglingAndExtended.ry); // expected-warning-re {{&lifetime_extended_object{Composite, danglingAndExtended, S{{[0-9]+}}} }} + + int i = 10; + RefAggregate varAndExtended{i, Composite{}}; // extends `Composite` + clang_analyzer_dump(varAndExtended.rx); // expected-warning {{&i }} + clang_analyzer_dump(varAndExtended.ry); // expected-warning-re {{&lifetime_extended_object{Composite, varAndExtended, S{{[0-9]+}}} }} + + auto const& viaReference = RefAggregate{10, Composite{}}; // extends `int`, `Composite`, and `RefAggregate` + clang_analyzer_dump(viaReference); // expected-warning-re {{&lifetime_extended_object{RefAggregate, viaReference, S{{[0-9]+}}} }} + clang_analyzer_dump(viaReference.rx); // expected-warning-re {{&lifetime_extended_object{int, viaReference, S{{[0-9]+}}} }} + clang_analyzer_dump(viaReference.ry); // expected-warning-re {{&lifetime_extended_object{Composite, viaReference, S{{[0-9]+}}} }} + + // clang does not currently implement extending lifetime of object bound to reference members of aggregates, + // that are created from default member initializer (see `warn_unsupported_lifetime_extension` from `-Wdangling`) + RefAggregate defaultInitExtended{i}; // clang-bug does not extend `Composite` + clang_analyzer_dump(defaultInitExtended.ry); // expected-warning {{Unknown }} +} + +void lambda() { + auto const& lambdaRef = [capture = create()] {}; + clang_analyzer_dump(lambdaRef); // expected-warning-re {{lifetime_extended_object{class (lambda at {{[^)]+}}), lambdaRef, S{{[0-9]+}}} }} + + // The capture [&refCapture = create()] { ... } per [expr.prim.lambda.capture] p6 equivalent to: + // auto& refCapture = create(); // Well-formed, deduces auto = Composite const, and performs lifetime extension + // [&refCapture] { ... } + // Where 'refCapture' has the same lifetime as the lambda itself. + // However, compilers differ: Clang lifetime-extends from C++17, GCC rejects the code, and MSVC dangles + auto const refExtendingCapture = [&refCapture = create()] { + clang_analyzer_dump(refCapture); + // cpp14-warning-re@-1 {{&temp_object{const struct Composite, S{{[0-9]+}}} }} + // cpp17-warning-re@-2 {{&lifetime_extended_object{const struct Composite, refExtendingCapture, S{{[0-9]+}}} }} + }; + refExtendingCapture(); +} + +void viaStructuredBinding() { + auto&& [x, y] = Composite{}; // extends `Composite` and binds it to unnamed decomposed object + clang_analyzer_dump(x); // expected-warning-re {{&lifetime_extended_object{Composite, D{{[0-9]+}}, S{{[0-9]+}}}.x }} + clang_analyzer_dump(y); // expected-warning-re {{&lifetime_extended_object{Composite, D{{[0-9]+}}, S{{[0-9]+}}}.y }} + + auto&& [rx, ry] = RefAggregate{10, Composite{}}; // extends `int`, `Composite`, and `RefAggregate`, and binds them to unnamed decomposed object + clang_analyzer_dump(rx); // expected-warning-re {{&lifetime_extended_object{int, D{{[0-9]+}}, S{{[0-9]+}}} }} + clang_analyzer_dump(ry); // expected-warning-re {{&lifetime_extended_object{Composite, D{{[0-9]+}}, S{{[0-9]+}}} }} +} + +void propagation(bool cond) { + int const& le = Composite{}.x; + // May return lifetime-extended region or dangling temporary + auto&& oneDangling = select(cond, le, 10); // does not extend lifetime of arguments + clang_analyzer_dump(oneDangling); + // expected-warning-re@-1 {{&lifetime_extended_object{Composite, le, S{{[0-9]+}}}.x }} + // expected-warning-re@-2 {{&temp_object{int, S{{[0-9]+}}} }} + + // Always dangles + auto&& bothDangling = select(cond, 10, 20); // does not extend lifetime of arguments + clang_analyzer_dump(bothDangling); + // expected-warning-re@-1 {{&temp_object{int, S{{[0-9]+}}} }} + // expected-warning-re@-2 {{&temp_object{int, S{{[0-9]+}}} }} +} diff --git a/clang/test/Analysis/stack-addr-ps.cpp b/clang/test/Analysis/stack-addr-ps.cpp --- a/clang/test/Analysis/stack-addr-ps.cpp +++ b/clang/test/Analysis/stack-addr-ps.cpp @@ -30,13 +30,13 @@ const int &get_reference2() { const int &x = get_value(); // expected-note {{binding reference variable 'x' here}} - return x; // expected-warning{{Address of stack memory associated with temporary object of type 'int' returned}} expected-warning {{returning reference to local temporary}} + return x; // expected-warning{{Address of stack memory associated with temporary object of type 'int' lifetime extended by local variable 'x' returned to caller}} expected-warning {{returning reference to local temporary}} } const int &get_reference3() { const int &x1 = get_value(); // expected-note {{binding reference variable 'x1' here}} const int &x2 = x1; // expected-note {{binding reference variable 'x2' here}} - return x2; // expected-warning{{Address of stack memory associated with temporary object of type 'int' returned}} expected-warning {{returning reference to local temporary}} + return x2; // expected-warning{{Address of stack memory associated with temporary object of type 'int' lifetime extended by local variable 'x1' returned to caller}} expected-warning {{returning reference to local temporary}} } int global_var; @@ -60,7 +60,7 @@ const int *f4() { const int &x1 = get_value(); // expected-note {{binding reference variable 'x1' here}} const int &x2 = x1; // expected-note {{binding reference variable 'x2' here}} - return &x2; // expected-warning{{Address of stack memory associated with temporary object of type 'int' returned}} expected-warning {{returning address of local temporary}} + return &x2; // expected-warning{{Address of stack memory associated with temporary object of type 'int' lifetime extended by local variable 'x1' returned to caller}} expected-warning {{returning address of local temporary}} } struct S {