[clang] feafbb9 - [analyzer] Differentiate lifetime extended temporaries

Tomasz Kamiński via cfe-commits cfe-commits at lists.llvm.org
Tue Jul 4 22:45:59 PDT 2023


Author: Tomasz Kamiński
Date: 2023-07-05T07:39:14+02:00
New Revision: feafbb9fda57dc77d5349a352b8495b6ef0f1bf5

URL: https://github.com/llvm/llvm-project/commit/feafbb9fda57dc77d5349a352b8495b6ef0f1bf5
DIFF: https://github.com/llvm/llvm-project/commit/feafbb9fda57dc77d5349a352b8495b6ef0f1bf5.diff

LOG: [analyzer] Differentiate lifetime extended temporaries

This patch introduces a new `CXXLifetimeExtendedObjectRegion` as a representation
of the memory for the temporary object that is lifetime extended by the reference
to which they are bound.

This separation provides an ability to detect the use of dangling pointers
(either binding or dereference) in a robust manner.
For example, the `ref` is conditionally dangling in the following example:
```
template<typename T>
T const& select(bool cond, T const& t, T const& u) { return cond ? t : u; }

int const& le = Composite{}.x;
auto&& ref = select(cond, le, 10);
```
Before the change, regardless of the value of `cond`, the `select()` call would
have returned a `temp_object` region.
With the proposed change we would produce a (non-dangling) `lifetime_extended_object`
region with lifetime bound to `le` or a `temp_object` region for the dangling case.

We believe that such separation is desired, as such lifetime extended temporaries
are closer to the variables. For example, they may have a static storage duration
(this patch removes a static temporary region, which was an abomination).
We also think that alternative approaches are not viable.

While for some cases it may be possible to determine if the region is lifetime
extended by searching the parents of the initializer expr, this quickly becomes
complex in the presence of the conditions operators like this one:
```
Composite cc;
// Ternary produces prvalue 'int' which is extended, as branches differ in value category
auto&& x = cond ? Composite{}.x : cc.x;

// Ternary produces xvalue, and extends the Composite object
auto&& y = cond ? Composite{}.x : std::move(cc).x;
```

Finally, the lifetime of the `CXXLifetimeExtendedObjectRegion` is tied to the lifetime of
the corresponding variables, however, the "liveness" (or reachability) of the extending
variable does not imply the reachability of all symbols in the region.
In conclusion `CXXLifetimeExtendedObjectRegion`, in contrast to `VarRegions`, does not
need any special handling in `SymReaper`.

RFC: https://discourse.llvm.org/t/rfc-detecting-uses-of-dangling-references/70731

Reviewed By: xazax.hun

Differential Revision: https://reviews.llvm.org/D151325

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

Modified: 
    clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h
    clang/include/clang/StaticAnalyzer/Core/PathSensitive/Regions.def
    clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
    clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp
    clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
    clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
    clang/lib/StaticAnalyzer/Core/MemRegion.cpp
    clang/lib/StaticAnalyzer/Core/Store.cpp
    clang/test/Analysis/stack-addr-ps.cpp
    clang/test/Analysis/use-after-move.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h
index 9c4730a0b46f88..3b818327ead7b9 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h
@@ -1231,8 +1231,7 @@ class CXXTempObjectRegion : public TypedValueRegion {
   CXXTempObjectRegion(Expr const *E, MemSpaceRegion const *sReg)
       : TypedValueRegion(sReg, CXXTempObjectRegionKind), Ex(E) {
     assert(E);
-    assert(isa<StackLocalsSpaceRegion>(sReg) ||
-           isa<GlobalInternalSpaceRegion>(sReg));
+    assert(isa<StackLocalsSpaceRegion>(sReg));
   }
 
   static void ProfileRegion(llvm::FoldingSetNodeID &ID,
@@ -1242,6 +1241,9 @@ class CXXTempObjectRegion : public TypedValueRegion {
   LLVM_ATTRIBUTE_RETURNS_NONNULL
   const Expr *getExpr() const { return Ex; }
 
+  LLVM_ATTRIBUTE_RETURNS_NONNULL
+  const StackFrameContext *getStackFrame() const;
+
   QualType getValueType() const override { return Ex->getType(); }
 
   void dumpToStream(raw_ostream &os) const override;
@@ -1253,6 +1255,45 @@ class CXXTempObjectRegion : public TypedValueRegion {
   }
 };
 
+// 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<StackLocalsSpaceRegion, GlobalInternalSpaceRegion>(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 {
@@ -1485,6 +1526,19 @@ class MemRegionManager {
   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 
diff ers 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.
   ///
@@ -1523,11 +1577,6 @@ class MemRegionManager {
                                             const LocationContext *lc,
                                             unsigned blockCount);
 
-  /// Create a CXXTempObjectRegion for temporaries which are lifetime-extended
-  /// by static references. This 
diff ers from getCXXTempObjectRegion in the
-  /// super-region used.
-  const CXXTempObjectRegion *getCXXStaticTempObjectRegion(const Expr *Ex);
-
 private:
   template <typename RegionTy, typename SuperTy,
             typename Arg1Ty>

diff  --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/Regions.def b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/Regions.def
index 44ab31fc9f2ed0..245828a2fcc08b 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/Regions.def
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/Regions.def
@@ -69,6 +69,7 @@ ABSTRACT_REGION(SubRegion, MemRegion)
       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
index ada802394758d4..befcd0b891a139 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
@@ -552,8 +552,9 @@ MoveChecker::classifyObject(const MemRegion *MR,
   // 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<VarRegion>(MR) &&
-                 isa<StackSpaceRegion>(MR->getMemorySpace());
+  bool IsLocal =
+      isa_and_nonnull<VarRegion, CXXLifetimeExtendedObjectRegion>(MR) &&
+      isa<StackSpaceRegion>(MR->getMemorySpace());
 
   if (!RD || !RD->getDeclContext()->isStdNamespace())
     return { IsLocal, SK_NonStd };

diff  --git a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp
index c4b7411e940110..32f88728466aac 100644
--- a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp
@@ -96,6 +96,14 @@ SourceRange StackAddrEscapeChecker::genName(raw_ostream &os, const MemRegion *R,
     os << "stack memory associated with local variable '" << VR->getString()
        << '\'';
     range = VR->getDecl()->getSourceRange();
+  } else if (const auto *LER = dyn_cast<CXXLifetimeExtendedObjectRegion>(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<CXXTempObjectRegion>(R)) {
     QualType Ty = TOR->getValueType().getLocalUnqualifiedType();
     os << "stack memory associated with temporary object of type '";
@@ -376,7 +384,7 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS,
     llvm::raw_svector_ostream Out(Buf);
     const SourceRange Range = genName(Out, Referred, Ctx.getASTContext());
 
-    if (isa<CXXTempObjectRegion>(Referrer)) {
+    if (isa<CXXTempObjectRegion, CXXLifetimeExtendedObjectRegion>(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
index cbba7fac7b6fcf..03dcdfe031f6d7 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -386,15 +386,19 @@ ProgramStateRef ExprEngine::createTemporaryRegionIfNeeded(
       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 
diff erent 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
index e87639713991e7..bcedd856268967 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
@@ -284,7 +284,8 @@ SVal ExprEngine::computeObjectUnderConstruction(
       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 @@ SVal ExprEngine::computeObjectUnderConstruction(
             // 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 @@ void ExprEngine::handleConstructor(const Expr *E,
   StmtNodeBuilder Bldr(DstEvaluated, DstEvaluatedPostProcessed, *currBldrCtx);
   const AnalysisDeclContext *ADC = LCtx->getAnalysisDeclContext();
   if (!ADC->getCFGBuildOptions().AddTemporaryDtors) {
-    if (llvm::isa_and_nonnull<CXXTempObjectRegion>(TargetRegion) &&
+    if (llvm::isa_and_nonnull<CXXTempObjectRegion,
+                              CXXLifetimeExtendedObjectRegion>(TargetRegion) &&
         cast<CXXConstructorDecl>(Call->getDecl())
             ->getParent()
             ->isAnyDestructorNoReturn()) {

diff  --git a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp
index 5f054129c669ce..820938fe950ec4 100644
--- a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp
+++ b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp
@@ -158,6 +158,18 @@ const StackFrameContext *VarRegion::getStackFrame() const {
   return SSR ? SSR->getStackFrame() : nullptr;
 }
 
+const StackFrameContext *
+CXXLifetimeExtendedObjectRegion::getStackFrame() const {
+  const auto *SSR = dyn_cast<StackSpaceRegion>(getMemorySpace());
+  return SSR ? SSR->getStackFrame() : nullptr;
+}
+
+const StackFrameContext *CXXTempObjectRegion::getStackFrame() const {
+  assert(isa<StackSpaceRegion>(getMemorySpace()) &&
+         "A temporary object can only be allocated on the stack");
+  return cast<StackSpaceRegion>(getMemorySpace())->getStackFrame();
+}
+
 ObjCIvarRegion::ObjCIvarRegion(const ObjCIvarDecl *ivd, const SubRegion *sReg)
     : DeclRegion(sReg, ObjCIvarRegionKind), IVD(ivd) {
   assert(IVD);
@@ -389,6 +401,20 @@ void CXXTempObjectRegion::Profile(llvm::FoldingSetNodeID &ID) const {
   ProfileRegion(ID, Ex, getSuperRegion());
 }
 
+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,
@@ -483,6 +509,16 @@ void CXXTempObjectRegion::dumpToStream(raw_ostream &os) const {
      << "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() << '}';
 }
@@ -743,6 +779,7 @@ DefinedOrUnknownSVal MemRegionManager::getStaticSize(const MemRegion *MR,
   case MemRegion::CXXBaseObjectRegionKind:
   case MemRegion::CXXDerivedObjectRegionKind:
   case MemRegion::CXXTempObjectRegionKind:
+  case MemRegion::CXXLifetimeExtendedObjectRegionKind:
   case MemRegion::CXXThisRegionKind:
   case MemRegion::ObjCIvarRegionKind:
   case MemRegion::NonParamVarRegionKind:
@@ -1097,12 +1134,6 @@ MemRegionManager::getBlockDataRegion(const BlockCodeRegion *BC,
   return getSubRegion<BlockDataRegion>(BC, LC, blockCount, sReg);
 }
 
-const CXXTempObjectRegion *
-MemRegionManager::getCXXStaticTempObjectRegion(const Expr *Ex) {
-  return getSubRegion<CXXTempObjectRegion>(
-      Ex, getGlobalsRegion(MemRegion::GlobalInternalSpaceRegionKind, nullptr));
-}
-
 const CompoundLiteralRegion*
 MemRegionManager::getCompoundLiteralRegion(const CompoundLiteralExpr *CL,
                                            const LocationContext *LC) {
@@ -1184,6 +1215,23 @@ MemRegionManager::getCXXTempObjectRegion(Expr const *E,
   return getSubRegion<CXXTempObjectRegion>(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<CXXLifetimeExtendedObjectRegion>(
+      Ex, VD, getStackLocalsRegion(SFC));
+}
+
+const CXXLifetimeExtendedObjectRegion *
+MemRegionManager::getCXXStaticLifetimeExtendedObjectRegion(
+    const Expr *Ex, const ValueDecl *VD) {
+  return getSubRegion<CXXLifetimeExtendedObjectRegion>(
+      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,
@@ -1457,6 +1505,7 @@ static RegionOffset calculateOffset(const MemRegion *R) {
     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
index fe1fa22af7ab31..f343eba54f5962 100644
--- a/clang/lib/StaticAnalyzer/Core/Store.cpp
+++ b/clang/lib/StaticAnalyzer/Core/Store.cpp
@@ -144,6 +144,7 @@ std::optional<const MemRegion *> StoreManager::castRegion(const MemRegion *R,
     case MemRegion::NonParamVarRegionKind:
     case MemRegion::ParamVarRegionKind:
     case MemRegion::CXXTempObjectRegionKind:
+    case MemRegion::CXXLifetimeExtendedObjectRegionKind:
     case MemRegion::CXXBaseObjectRegionKind:
     case MemRegion::CXXDerivedObjectRegionKind:
       return MakeElementRegion(cast<SubRegion>(R), PointeeTy);

diff  --git a/clang/test/Analysis/lifetime-extended-regions.cpp b/clang/test/Analysis/lifetime-extended-regions.cpp
new file mode 100644
index 00000000000000..4e98bd4b0403eb
--- /dev/null
+++ b/clang/test/Analysis/lifetime-extended-regions.cpp
@@ -0,0 +1,171 @@
+// 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<typename T>
+void clang_analyzer_dump(T&&) {}
+
+template<typename T>
+T create() { return T{}; }
+
+template<typename T>
+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<typename T>
+struct Array {
+  T array[20];
+
+  T&& front() && { return static_cast<T&&>(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<int>{}; // extends `Array<int>`
+  clang_analyzer_dump(a); // expected-warning-re {{&lifetime_extended_object{Array<int>, 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<Composite>().y; // extends `Composite`
+  clang_analyzer_dump(y); // expected-warning-re {{&lifetime_extended_object{struct Composite, y, S{{[0-9]+}}}.y }}
+  int&& d = Array<int>{}.front(); // dangles `Array<int>`
+  clang_analyzer_dump(d); // expected-warning-re {{&Element{temp_object{Array<int>, S{{[0-9]+}}}.array,0 S64b,int} }}
+}
+
+void array_subscript() {
+  int&& i = Array<int>{}.array[0]; // extends `Array<int>`
+  clang_analyzer_dump(i); // expected-warning-re {{&Element{lifetime_extended_object{Array<int>, i, S{{[0-9]+}}}.array,0 S64b,int} }}
+  auto&& c = Array<Composite>{}.array[0]; // extends `Array<int>`
+  clang_analyzer_dump(c); // expected-warning-re {{&Element{lifetime_extended_object{Array<Composite>, c, S{{[0-9]+}}}.array,0 S64b,struct Composite} }}
+  auto&& x = Array<Composite>{}.array[0].x; // extends `Array<Composite>`
+  clang_analyzer_dump(x); // expected-warning-re {{&Element{lifetime_extended_object{Array<Composite>, 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<Composite&&>(cc).x; // extends `Composite` in true branch
+  clang_analyzer_dump(branchesExtended);
+  // expected-warning-re at -1 {{&lifetime_extended_object{Composite, branchesExtended, S{{[0-9]+}}}.x }}
+  // expected-warning at -2 {{&cc.x }}
+
+  // Object of 
diff erent types in branches are lifetime extended
+  auto&& extendingDifferentTypes = cond ? Composite{}.x : Array<int>{}.array[0]; // extends `Composite` or `Array<int>`
+  clang_analyzer_dump(extendingDifferentTypes);
+  // expected-warning-re at -1 {{&lifetime_extended_object{Composite, extendingDifferentTypes, S{{[0-9]+}}}.x }}
+  // expected-warning-re at -2 {{&Element{lifetime_extended_object{Array<int>, extendingDifferentTypes, S{{[0-9]+}}}.array,0 S64b,int} }}
+
+  Composite const& variableAndExtended = cond ? static_cast<Composite&&>(cc) : Array<Composite>{}.array[0]; // extends `Array<Composite>` in false branch
+  clang_analyzer_dump(variableAndExtended);
+  // expected-warning at -1 {{&cc }}
+  // expected-warning-re at -2 {{&Element{lifetime_extended_object{Array<Composite>, variableAndExtended, S{{[0-9]+}}}.array,0 S64b,struct Composite} }}
+
+  int const& extendAndDangling = cond ? Array<int>{}.array[0] : Array<int>{}.front(); // extends `Array<int>` only in true branch, false branch dangles
+  clang_analyzer_dump(extendAndDangling);
+  // expected-warning-re at -1 {{&Element{lifetime_extended_object{Array<int>, extendAndDangling, S{{[0-9]+}}}.array,0 S64b,int} }}
+  // expected-warning-re at -2 {{&Element{temp_object{Array<int>, 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<int>{}.front(), Composite{}}; // extends only `Composite`, `Array<int>` dangles
+  clang_analyzer_dump(danglingAndExtended.rx); // expected-warning-re {{&Element{temp_object{Array<int>, 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<Composite>()] {};
+  clang_analyzer_dump(lambdaRef); // expected-warning-re {{lifetime_extended_object{class (lambda at {{[^)]+}}), lambdaRef, S{{[0-9]+}}} }}
+
+  // The capture [&refCapture = create<Composite const>()] { ... } per [expr.prim.lambda.capture] p6 equivalent to:
+  //   auto& refCapture = create<Composite const>(); // Well-formed, deduces auto = Composite const, and performs lifetime extension
+  //   [&refCapture] { ... }
+  // Where 'refCapture' has the same lifetime as the lambda itself.
+  // However, compilers 
diff er: Clang lifetime-extends from C++17, GCC rejects the code, and MSVC dangles
+  // See also CWG2737 (https://cplusplus.github.io/CWG/issues/2737.html)
+  auto const refExtendingCapture = [&refCapture = create<Composite const>()] {
+     clang_analyzer_dump(refCapture);
+     // cpp14-warning-re at -1 {{&temp_object{const struct Composite, S{{[0-9]+}}} }}
+     // cpp17-warning-re at -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 at -1 {{&lifetime_extended_object{Composite, le, S{{[0-9]+}}}.x }}
+  // expected-warning-re at -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 at -1 {{&temp_object{int, S{{[0-9]+}}} }}
+  // expected-warning-re at -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
index 8f1cfc00690d99..91e41e1f00650f 100644
--- a/clang/test/Analysis/stack-addr-ps.cpp
+++ b/clang/test/Analysis/stack-addr-ps.cpp
@@ -30,13 +30,13 @@ const int &get_reference1() { return get_value(); } // expected-warning{{Address
 
 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 @@ int *f3() {
 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
index 8a8a89ba13ead4..33980e6ea2b8b9 100644
--- a/clang/test/Analysis/use-after-move.cpp
+++ b/clang/test/Analysis/use-after-move.cpp
@@ -613,6 +613,14 @@ void tempTest() {
   }
 }
 
+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 at -1 {{Method called on moved-from object}}
+}
+
 void interFunTest1(A &a) {
   a.bar(); // peaceful-warning {{Method called on moved-from object 'a'}}
            // peaceful-note at -1 {{Method called on moved-from object 'a'}}


        


More information about the cfe-commits mailing list