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/MoveChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp --- a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp @@ -45,9 +45,8 @@ } // end of anonymous namespace namespace { -class MoveChecker - : public Checker { +class MoveChecker : public Checker { public: void checkPreCall(const CallEvent &MC, CheckerContext &C) const; void checkPostCall(const CallEvent &MC, CheckerContext &C) const; @@ -58,8 +57,8 @@ ArrayRef RequestedRegions, ArrayRef InvalidatedRegions, const LocationContext *LCtx, const CallEvent *Call) const; - void printState(raw_ostream &Out, ProgramStateRef State, - const char *NL, const char *Sep) const override; + void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, + const char *Sep) const override; private: enum MisuseKind { MK_FunCall, MK_Copy, MK_Move, MK_Dereference }; @@ -73,9 +72,7 @@ AK_NumKinds = AK_All }; - static bool misuseCausesCrash(MisuseKind MK) { - return MK == MK_Dereference; - } + static bool misuseCausesCrash(MisuseKind MK) { return MK == MK_Dereference; } struct ObjectKind { // Is this a local variable or a local rvalue reference? @@ -99,16 +96,9 @@ // TODO: We can still try to identify *unsafe* use after move, // like we did with smart pointers. const llvm::StringSet<> StdSafeClasses = { - "basic_filebuf", - "basic_ios", - "future", - "optional", - "packaged_task", - "promise", - "shared_future", - "shared_lock", - "thread", - "unique_lock", + "basic_filebuf", "basic_ios", "future", "optional", + "packaged_task", "promise", "shared_future", "shared_lock", + "thread", "unique_lock", }; // Should we bother tracking the state of the object? @@ -188,15 +178,15 @@ public: void setAggressiveness(StringRef Str, CheckerManager &Mgr) { - Aggressiveness = - llvm::StringSwitch(Str) - .Case("KnownsOnly", AK_KnownsOnly) - .Case("KnownsAndLocals", AK_KnownsAndLocals) - .Case("All", AK_All) - .Default(AK_Invalid); + Aggressiveness = llvm::StringSwitch(Str) + .Case("KnownsOnly", AK_KnownsOnly) + .Case("KnownsAndLocals", AK_KnownsAndLocals) + .Case("All", AK_All) + .Default(AK_Invalid); if (Aggressiveness == AK_Invalid) - Mgr.reportInvalidCheckerOptionValue(this, "WarnOn", + Mgr.reportInvalidCheckerOptionValue( + this, "WarnOn", "either \"KnownsOnly\", \"KnownsAndLocals\" or \"All\" string value"); }; @@ -299,28 +289,28 @@ ObjectKind OK = Chk.classifyObject(Region, RD); switch (OK.StdKind) { - case SK_SmartPtr: - if (MK == MK_Dereference) { - OS << "Smart pointer"; - Chk.explainObject(OS, Region, RD, MK); - OS << " is reset to null when moved from"; - break; - } - - // If it's not a dereference, we don't care if it was reset to null - // or that it is even a smart pointer. - [[fallthrough]]; - case SK_NonStd: - case SK_Safe: - OS << "Object"; - Chk.explainObject(OS, Region, RD, MK); - OS << " is moved"; - break; - case SK_Unsafe: - OS << "Object"; + case SK_SmartPtr: + if (MK == MK_Dereference) { + OS << "Smart pointer"; Chk.explainObject(OS, Region, RD, MK); - OS << " is left in a valid but unspecified state after move"; + OS << " is reset to null when moved from"; break; + } + + // If it's not a dereference, we don't care if it was reset to null + // or that it is even a smart pointer. + [[fallthrough]]; + case SK_NonStd: + case SK_Safe: + OS << "Object"; + Chk.explainObject(OS, Region, RD, MK); + OS << " is moved"; + break; + case SK_Unsafe: + OS << "Object"; + Chk.explainObject(OS, Region, RD, MK); + OS << " is left in a valid but unspecified state after move"; + break; } // Generate the extra diagnostic. @@ -358,8 +348,8 @@ if (MK == MK_Dereference && OK.StdKind != SK_SmartPtr) MK = MK_FunCall; - if (!RS || !shouldWarnAbout(OK, MK) - || isInMoveSafeContext(C.getLocationContext())) { + if (!RS || !shouldWarnAbout(OK, MK) || + isInMoveSafeContext(C.getLocationContext())) { // Finalize changes made by the caller. C.addTransition(State); return; @@ -403,25 +393,25 @@ // Creating the error message. llvm::SmallString<128> Str; llvm::raw_svector_ostream OS(Str); - switch(MK) { - case MK_FunCall: - OS << "Method called on moved-from object"; - explainObject(OS, Region, RD, MK); - break; - case MK_Copy: - OS << "Moved-from object"; - explainObject(OS, Region, RD, MK); - OS << " is copied"; - break; - case MK_Move: - OS << "Moved-from object"; - explainObject(OS, Region, RD, MK); - OS << " is moved"; - break; - case MK_Dereference: - OS << "Dereference of null smart pointer"; - explainObject(OS, Region, RD, MK); - break; + switch (MK) { + case MK_FunCall: + OS << "Method called on moved-from object"; + explainObject(OS, Region, RD, MK); + break; + case MK_Copy: + OS << "Moved-from object"; + explainObject(OS, Region, RD, MK); + OS << " is copied"; + break; + case MK_Move: + OS << "Moved-from object"; + explainObject(OS, Region, RD, MK); + OS << " is moved"; + break; + case MK_Dereference: + OS << "Dereference of null smart pointer"; + explainObject(OS, Region, RD, MK); + break; } auto R = std::make_unique( @@ -465,8 +455,9 @@ return; if (const auto *IC = dyn_cast(AFC)) - if (IC->getCXXThisVal().getAsRegion() == ArgRegion) - return; + if (const auto *IC = dyn_cast(AFC)) + if (IC->getCXXThisVal().getAsRegion() == ArgRegion) + return; const MemRegion *BaseRegion = ArgRegion->getBaseRegion(); // Skip temp objects because of their short lifetime. @@ -501,15 +492,15 @@ } // Function call `empty` can be skipped. return (MethodDec && MethodDec->getDeclName().isIdentifier() && - (MethodDec->getName().lower() == "empty" || - MethodDec->getName().lower() == "isempty")); + (MethodDec->getName().lower() == "empty" || + MethodDec->getName().lower() == "isempty")); } bool MoveChecker::isStateResetMethod(const CXXMethodDecl *MethodDec) const { if (!MethodDec) - return false; + return false; if (MethodDec->hasAttr()) - return true; + return true; if (MethodDec->getDeclName().isIdentifier()) { std::string MethodName = MethodDec->getName().lower(); // TODO: Some of these methods (eg., resize) are not always resetting @@ -552,19 +543,20 @@ // For the purposes of this checker, we classify move-safe STL types // as not-"STL" types, because that's how the checker treats them. MR = unwrapRValueReferenceIndirection(MR); - bool IsLocal = isa_and_nonnull(MR) && - isa(MR->getMemorySpace()); + bool IsLocal = + isa_and_nonnull(MR) && + isa(MR->getMemorySpace()); if (!RD || !RD->getDeclContext()->isStdNamespace()) - return { IsLocal, SK_NonStd }; + return {IsLocal, SK_NonStd}; if (belongsTo(RD, StdSmartPtrClasses)) - return { IsLocal, SK_SmartPtr }; + return {IsLocal, SK_SmartPtr}; if (belongsTo(RD, StdSafeClasses)) - return { IsLocal, SK_Safe }; + return {IsLocal, SK_Safe}; - return { IsLocal, SK_Unsafe }; + return {IsLocal, SK_Unsafe}; } void MoveChecker::explainObject(llvm::raw_ostream &OS, const MemRegion *MR, @@ -579,18 +571,18 @@ ObjectKind OK = classifyObject(MR, RD); switch (OK.StdKind) { - case SK_NonStd: - case SK_Safe: - break; - case SK_SmartPtr: - if (MK != MK_Dereference) - break; - - // We only care about the type if it's a dereference. - [[fallthrough]]; - case SK_Unsafe: - OS << " of type '" << RD->getQualifiedNameAsString() << "'"; + case SK_NonStd: + case SK_Safe: + break; + case SK_SmartPtr: + if (MK != MK_Dereference) break; + + // We only care about the type if it's a dereference. + [[fallthrough]]; + case SK_Unsafe: + OS << " of type '" << RD->getQualifiedNameAsString() << "'"; + break; }; } @@ -695,8 +687,8 @@ ProgramStateRef MoveChecker::checkRegionChanges( ProgramStateRef State, const InvalidatedSymbols *Invalidated, ArrayRef RequestedRegions, - ArrayRef InvalidatedRegions, - const LocationContext *LCtx, const CallEvent *Call) const { + ArrayRef InvalidatedRegions, const LocationContext *LCtx, + const CallEvent *Call) const { if (Call) { // Relax invalidation upon function calls: only invalidate parameters // that are passed directly via non-const pointers or non-const references @@ -732,7 +724,7 @@ if (!RS.isEmpty()) { Out << Sep << "Moved-from objects :" << NL; - for (auto I: RS) { + for (auto I : RS) { I.first->dumpToStream(Out); if (I.second.isMoved()) Out << ": moved"; @@ -748,6 +740,4 @@ mgr.getAnalyzerOptions().getCheckerStringOption(chk, "WarnOn"), mgr); } -bool ento::shouldRegisterMoveChecker(const CheckerManager &mgr) { - return true; -} +bool ento::shouldRegisterMoveChecker(const CheckerManager &mgr) { return true; } 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 { diff --git a/clang/test/Analysis/use-after-move.cpp b/clang/test/Analysis/use-after-move.cpp --- a/clang/test/Analysis/use-after-move.cpp +++ b/clang/test/Analysis/use-after-move.cpp @@ -613,6 +613,14 @@ } } +void lifeTimeExtendTest() { + A&& a = A{}; + A b = std::move(a); // peaceful-note {{Object is moved}} + + a.foo(); // peaceful-warning {{Method called on moved-from object}} + // peaceful-note@-1 {{Method called on moved-from object}} +} + void interFunTest1(A &a) { a.bar(); // peaceful-warning {{Method called on moved-from object 'a'}} // peaceful-note@-1 {{Method called on moved-from object 'a'}}