r335800 - [analyzer] Add support for pre-C++17 copy elision.

Artem Dergachev via cfe-commits cfe-commits at lists.llvm.org
Wed Jun 27 17:30:18 PDT 2018


Author: dergachev
Date: Wed Jun 27 17:30:18 2018
New Revision: 335800

URL: http://llvm.org/viewvc/llvm-project?rev=335800&view=rev
Log:
[analyzer] Add support for pre-C++17 copy elision.

r335795 adds copy elision information to CFG. This commit allows static analyzer
to elide elidable copy constructors by constructing the objects that were
previously subject to elidable copy directly in the target region of the copy.

The chain of elided constructors may potentially be indefinitely long. This
only happens when the object is being returned from a function which in turn is
returned from another function, etc.

NRVO is not supported yet.

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

Modified:
    cfe/trunk/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
    cfe/trunk/lib/StaticAnalyzer/Core/ExprEngine.cpp
    cfe/trunk/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
    cfe/trunk/test/Analysis/cxx17-mandatory-elision.cpp
    cfe/trunk/test/Analysis/gtest.cpp
    cfe/trunk/test/Analysis/inlining/temp-dtors-path-notes.cpp
    cfe/trunk/test/Analysis/lifetime-extension.cpp
    cfe/trunk/test/Analysis/temporaries.cpp

Modified: cfe/trunk/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h?rev=335800&r1=335799&r2=335800&view=diff
==============================================================================
--- cfe/trunk/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h (original)
+++ cfe/trunk/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h Wed Jun 27 17:30:18 2018
@@ -780,9 +780,30 @@ private:
       llvm::PointerUnion<const Stmt *, const CXXCtorInitializer *> P,
       const LocationContext *LC);
 
+  /// If the given expression corresponds to a temporary that was used for
+  /// passing into an elidable copy/move constructor and that constructor
+  /// was actually elided, track that we also need to elide the destructor.
+  static ProgramStateRef elideDestructor(ProgramStateRef State,
+                                         const CXXBindTemporaryExpr *BTE,
+                                         const LocationContext *LC);
+
+  /// Stop tracking the destructor that corresponds to an elided constructor.
+  static ProgramStateRef
+  cleanupElidedDestructor(ProgramStateRef State,
+                          const CXXBindTemporaryExpr *BTE,
+                          const LocationContext *LC);
+
+  /// Returns true if the given expression corresponds to a temporary that
+  /// was constructed for passing into an elidable copy/move constructor
+  /// and that constructor was actually elided.
+  static bool isDestructorElided(ProgramStateRef State,
+                                 const CXXBindTemporaryExpr *BTE,
+                                 const LocationContext *LC);
+
   /// Check if all objects under construction have been fully constructed
   /// for the given context range (including FromLC, not including ToLC).
-  /// This is useful for assertions.
+  /// This is useful for assertions. Also checks if elided destructors
+  /// were cleaned up.
   static bool areAllObjectsFullyConstructed(ProgramStateRef State,
                                             const LocationContext *FromLC,
                                             const LocationContext *ToLC);

Modified: cfe/trunk/lib/StaticAnalyzer/Core/ExprEngine.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Core/ExprEngine.cpp?rev=335800&r1=335799&r2=335800&view=diff
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Core/ExprEngine.cpp (original)
+++ cfe/trunk/lib/StaticAnalyzer/Core/ExprEngine.cpp Wed Jun 27 17:30:18 2018
@@ -139,7 +139,8 @@ public:
     // encountered. This list may be expanded when new actions are implemented.
     assert(getCXXCtorInitializer() || isa<DeclStmt>(getStmt()) ||
            isa<CXXNewExpr>(getStmt()) || isa<CXXBindTemporaryExpr>(getStmt()) ||
-           isa<MaterializeTemporaryExpr>(getStmt()));
+           isa<MaterializeTemporaryExpr>(getStmt()) ||
+           isa<CXXConstructExpr>(getStmt()));
   }
 
   const Stmt *getStmt() const {
@@ -183,6 +184,14 @@ typedef llvm::ImmutableMap<ConstructedOb
 REGISTER_TRAIT_WITH_PROGRAMSTATE(ObjectsUnderConstruction,
                                  ObjectsUnderConstructionMap)
 
+// Additionally, track a set of destructors that correspond to elided
+// constructors when copy elision occurs.
+typedef std::pair<const CXXBindTemporaryExpr *, const LocationContext *>
+    ElidedDestructorItem;
+typedef llvm::ImmutableSet<ElidedDestructorItem>
+    ElidedDestructorSet;
+REGISTER_TRAIT_WITH_PROGRAMSTATE(ElidedDestructors,
+                                 ElidedDestructorSet);
 
 //===----------------------------------------------------------------------===//
 // Engine construction and deletion.
@@ -358,14 +367,12 @@ ExprEngine::createTemporaryRegionIfNeede
   // a new temporary region out of thin air and copy the contents of the object
   // (which are currently present in the Environment, because Init is an rvalue)
   // into that region. This is not correct, but it is better than nothing.
-  bool FoundOriginalMaterializationRegion = false;
   const TypedValueRegion *TR = nullptr;
   if (const auto *MT = dyn_cast<MaterializeTemporaryExpr>(Result)) {
     if (Optional<SVal> V = getObjectUnderConstruction(State, MT, LC)) {
-      FoundOriginalMaterializationRegion = true;
-      TR = cast<CXXTempObjectRegion>(V->getAsRegion());
-      assert(TR);
       State = finishObjectConstruction(State, MT, LC);
+      State = State->BindExpr(Result, LC, *V);
+      return State;
     } else {
       StorageDuration SD = MT->getStorageDuration();
       // If this object is bound to a reference with static storage duration, we
@@ -402,35 +409,33 @@ ExprEngine::createTemporaryRegionIfNeede
     }
   }
 
-  if (!FoundOriginalMaterializationRegion) {
-    // What remains is to copy the value of the object to the new region.
-    // FIXME: In other words, what we should always do is copy value of the
-    // Init expression (which corresponds to the bigger object) to the whole
-    // temporary region TR. However, this value is often no longer present
-    // in the Environment. If it has disappeared, we instead invalidate TR.
-    // Still, what we can do is assign the value of expression Ex (which
-    // corresponds to the sub-object) to the TR's sub-region Reg. At least,
-    // values inside Reg would be correct.
-    SVal InitVal = State->getSVal(Init, LC);
-    if (InitVal.isUnknown()) {
-      InitVal = getSValBuilder().conjureSymbolVal(Result, LC, Init->getType(),
-                                                  currBldrCtx->blockCount());
-      State = State->bindLoc(BaseReg.castAs<Loc>(), InitVal, LC, false);
-
-      // Then we'd need to take the value that certainly exists and bind it
-      // over.
-      if (InitValWithAdjustments.isUnknown()) {
-        // Try to recover some path sensitivity in case we couldn't
-        // compute the value.
-        InitValWithAdjustments = getSValBuilder().conjureSymbolVal(
-            Result, LC, InitWithAdjustments->getType(),
-            currBldrCtx->blockCount());
-      }
-      State =
-          State->bindLoc(Reg.castAs<Loc>(), InitValWithAdjustments, LC, false);
-    } else {
-      State = State->bindLoc(BaseReg.castAs<Loc>(), InitVal, LC, false);
+  // What remains is to copy the value of the object to the new region.
+  // FIXME: In other words, what we should always do is copy value of the
+  // Init expression (which corresponds to the bigger object) to the whole
+  // temporary region TR. However, this value is often no longer present
+  // in the Environment. If it has disappeared, we instead invalidate TR.
+  // Still, what we can do is assign the value of expression Ex (which
+  // corresponds to the sub-object) to the TR's sub-region Reg. At least,
+  // values inside Reg would be correct.
+  SVal InitVal = State->getSVal(Init, LC);
+  if (InitVal.isUnknown()) {
+    InitVal = getSValBuilder().conjureSymbolVal(Result, LC, Init->getType(),
+                                                currBldrCtx->blockCount());
+    State = State->bindLoc(BaseReg.castAs<Loc>(), InitVal, LC, false);
+
+    // Then we'd need to take the value that certainly exists and bind it
+    // over.
+    if (InitValWithAdjustments.isUnknown()) {
+      // Try to recover some path sensitivity in case we couldn't
+      // compute the value.
+      InitValWithAdjustments = getSValBuilder().conjureSymbolVal(
+          Result, LC, InitWithAdjustments->getType(),
+          currBldrCtx->blockCount());
     }
+    State =
+        State->bindLoc(Reg.castAs<Loc>(), InitValWithAdjustments, LC, false);
+  } else {
+    State = State->bindLoc(BaseReg.castAs<Loc>(), InitVal, LC, false);
   }
 
   // The result expression would now point to the correct sub-region of the
@@ -438,10 +443,8 @@ ExprEngine::createTemporaryRegionIfNeede
   // correctly in case (Result == Init).
   State = State->BindExpr(Result, LC, Reg);
 
-  if (!FoundOriginalMaterializationRegion) {
-    // Notify checkers once for two bindLoc()s.
-    State = processRegionChange(State, TR, LC);
-  }
+  // Notify checkers once for two bindLoc()s.
+  State = processRegionChange(State, TR, LC);
 
   return State;
 }
@@ -475,6 +478,30 @@ ProgramStateRef ExprEngine::finishObject
   return State->remove<ObjectsUnderConstruction>(Key);
 }
 
+ProgramStateRef ExprEngine::elideDestructor(ProgramStateRef State,
+                                            const CXXBindTemporaryExpr *BTE,
+                                            const LocationContext *LC) {
+  ElidedDestructorItem I(BTE, LC);
+  assert(!State->contains<ElidedDestructors>(I));
+  return State->add<ElidedDestructors>(I);
+}
+
+ProgramStateRef
+ExprEngine::cleanupElidedDestructor(ProgramStateRef State,
+                                    const CXXBindTemporaryExpr *BTE,
+                                    const LocationContext *LC) {
+  ElidedDestructorItem I(BTE, LC);
+  assert(State->contains<ElidedDestructors>(I));
+  return State->remove<ElidedDestructors>(I);
+}
+
+bool ExprEngine::isDestructorElided(ProgramStateRef State,
+                                    const CXXBindTemporaryExpr *BTE,
+                                    const LocationContext *LC) {
+  ElidedDestructorItem I(BTE, LC);
+  return State->contains<ElidedDestructors>(I);
+}
+
 bool ExprEngine::areAllObjectsFullyConstructed(ProgramStateRef State,
                                                const LocationContext *FromLC,
                                                const LocationContext *ToLC) {
@@ -485,6 +512,10 @@ bool ExprEngine::areAllObjectsFullyConst
       if (I.first.getLocationContext() == LC)
         return false;
 
+    for (auto I: State->get<ElidedDestructors>())
+      if (I.second == LC)
+        return false;
+
     LC = LC->getParent();
   }
   return true;
@@ -529,6 +560,14 @@ static void printObjectsUnderConstructio
     Key.print(Out, nullptr, PP);
     Out << " : " << Value << NL;
   }
+
+  for (auto I : State->get<ElidedDestructors>()) {
+    if (I.second != LC)
+      continue;
+    Out << '(' << I.second << ',' << (const void *)I.first << ") ";
+    I.first->printPretty(Out, nullptr, PP);
+    Out << " : (constructor elided)" << NL;
+  }
 }
 
 void ExprEngine::printState(raw_ostream &Out, ProgramStateRef State,
@@ -1003,10 +1042,11 @@ void ExprEngine::ProcessMemberDtor(const
 void ExprEngine::ProcessTemporaryDtor(const CFGTemporaryDtor D,
                                       ExplodedNode *Pred,
                                       ExplodedNodeSet &Dst) {
-  ExplodedNodeSet CleanDtorState;
-  StmtNodeBuilder StmtBldr(Pred, CleanDtorState, *currBldrCtx);
+  const CXXBindTemporaryExpr *BTE = D.getBindTemporaryExpr();
   ProgramStateRef State = Pred->getState();
+  const LocationContext *LC = Pred->getLocationContext();
   const MemRegion *MR = nullptr;
+
   if (Optional<SVal> V =
           getObjectUnderConstruction(State, D.getBindTemporaryExpr(),
                                      Pred->getLocationContext())) {
@@ -1017,6 +1057,21 @@ void ExprEngine::ProcessTemporaryDtor(co
                                      Pred->getLocationContext());
     MR = V->getAsRegion();
   }
+
+  // If copy elision has occured, and the constructor corresponding to the
+  // destructor was elided, we need to skip the destructor as well.
+  if (isDestructorElided(State, BTE, LC)) {
+    State = cleanupElidedDestructor(State, BTE, LC);
+    NodeBuilder Bldr(Pred, Dst, *currBldrCtx);
+    PostImplicitCall PP(D.getDestructorDecl(getContext()),
+                        D.getBindTemporaryExpr()->getLocStart(),
+                        Pred->getLocationContext());
+    Bldr.generateNode(PP, State, Pred);
+    return;
+  }
+
+  ExplodedNodeSet CleanDtorState;
+  StmtNodeBuilder StmtBldr(Pred, CleanDtorState, *currBldrCtx);
   StmtBldr.generateNode(D.getBindTemporaryExpr(), Pred, State);
 
   QualType T = D.getBindTemporaryExpr()->getSubExpr()->getType();
@@ -2201,8 +2256,21 @@ void ExprEngine::processEndOfFunction(No
           // it must be a separate problem.
           assert(isa<CXXBindTemporaryExpr>(I.first.getStmt()));
           State = State->remove<ObjectsUnderConstruction>(I.first);
+          // Also cleanup the elided destructor info.
+          ElidedDestructorItem Item(
+              cast<CXXBindTemporaryExpr>(I.first.getStmt()),
+              I.first.getLocationContext());
+          State = State->remove<ElidedDestructors>(Item);
         }
 
+      // Also suppress the assertion for elided destructors when temporary
+      // destructors are not provided at all by the CFG, because there's no
+      // good place to clean them up.
+      if (!AMgr.getAnalyzerOptions().includeTemporaryDtorsInCFG())
+        for (auto I : State->get<ElidedDestructors>())
+          if (I.second == LC)
+            State = State->remove<ElidedDestructors>(I);
+
       LC = LC->getParent();
     }
     if (State != Pred->getState()) {

Modified: cfe/trunk/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp?rev=335800&r1=335799&r2=335800&view=diff
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp (original)
+++ cfe/trunk/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp Wed Jun 27 17:30:18 2018
@@ -209,12 +209,37 @@ std::pair<ProgramStateRef, SVal> ExprEng
       }
       llvm_unreachable("Unhandled return value construction context!");
     }
-    case ConstructionContext::ElidedTemporaryObjectKind:
+    case ConstructionContext::ElidedTemporaryObjectKind: {
       assert(AMgr.getAnalyzerOptions().shouldElideConstructors());
-      // FALL-THROUGH
+      const auto *TCC = cast<ElidedTemporaryObjectConstructionContext>(CC);
+      const CXXBindTemporaryExpr *BTE = TCC->getCXXBindTemporaryExpr();
+      const MaterializeTemporaryExpr *MTE = TCC->getMaterializedTemporaryExpr();
+      const CXXConstructExpr *CE = TCC->getConstructorAfterElision();
+
+      // Support pre-C++17 copy elision. We'll have the elidable copy
+      // constructor in the AST and in the CFG, but we'll skip it
+      // and construct directly into the final object. This call
+      // also sets the CallOpts flags for us.
+      SVal V;
+      std::tie(State, V) = prepareForObjectConstruction(
+          CE, State, LCtx, TCC->getConstructionContextAfterElision(), CallOpts);
+
+      // 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);
+
+      // Instead of materialization, shamelessly return
+      // the final object destination.
+      if (MTE)
+        State = addObjectUnderConstruction(State, MTE, LCtx, V);
+
+      return std::make_pair(State, V);
+    }
     case ConstructionContext::SimpleTemporaryObjectKind: {
-      // TODO: Copy elision implementation goes here.
-      const auto *TCC = cast<TemporaryObjectConstructionContext>(CC);
+      const auto *TCC = cast<SimpleTemporaryObjectConstructionContext>(CC);
       const CXXBindTemporaryExpr *BTE = TCC->getCXXBindTemporaryExpr();
       const MaterializeTemporaryExpr *MTE = TCC->getMaterializedTemporaryExpr();
       SVal V = UnknownVal();
@@ -266,6 +291,20 @@ void ExprEngine::VisitCXXConstructExpr(c
 
   SVal Target = UnknownVal();
 
+  if (Optional<SVal> ElidedTarget =
+          getObjectUnderConstruction(State, CE, LCtx)) {
+    // We've previously modeled an elidable constructor by pretending that it in
+    // fact constructs into the correct target. This constructor can therefore
+    // be skipped.
+    Target = *ElidedTarget;
+    StmtNodeBuilder Bldr(Pred, destNodes, *currBldrCtx);
+    State = finishObjectConstruction(State, CE, LCtx);
+    if (auto L = Target.getAs<Loc>())
+      State = State->BindExpr(CE, LCtx, State->getSVal(*L, CE->getType()));
+    Bldr.generateNode(CE, Pred, State);
+    return;
+  }
+
   // FIXME: Handle arrays, which run the same constructor for every element.
   // For now, we just run the first constructor (which should still invalidate
   // the entire array).

Modified: cfe/trunk/test/Analysis/cxx17-mandatory-elision.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/cxx17-mandatory-elision.cpp?rev=335800&r1=335799&r2=335800&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/cxx17-mandatory-elision.cpp (original)
+++ cfe/trunk/test/Analysis/cxx17-mandatory-elision.cpp Wed Jun 27 17:30:18 2018
@@ -1,5 +1,17 @@
 // RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++11 -verify %s
 // RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++17 -verify %s
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++11 -analyzer-config elide-constructors=false -DNO_ELIDE_FLAG -verify %s
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++17 -analyzer-config elide-constructors=false -DNO_ELIDE_FLAG -verify %s
+
+// Copy elision always occurs in C++17, otherwise it's under
+// an on-by-default flag.
+#if __cplusplus >= 201703L
+  #define ELIDE 1
+#else
+  #ifndef NO_ELIDE_FLAG
+    #define ELIDE 1
+  #endif
+#endif
 
 void clang_analyzer_eval(bool);
 
@@ -39,9 +51,9 @@ public:
   C() : t(T(4)) {
     S s = {1, 2, 3};
     t.s = s;
-    // FIXME: Should be TRUE in C++11 as well.
+    // FIXME: Should be TRUE regardless of copy elision.
     clang_analyzer_eval(t.w == 4);
-#if __cplusplus >= 201703L
+#ifdef ELIDE
     // expected-warning at -2{{TRUE}}
 #else
     // expected-warning at -4{{UNKNOWN}}
@@ -149,7 +161,7 @@ void testMultipleReturns() {
   AddressVector<ClassWithoutDestructor> v;
   ClassWithoutDestructor c = make3(v);
 
-#if __cplusplus >= 201703L
+#if ELIDE
   clang_analyzer_eval(v.len == 1); // expected-warning{{TRUE}}
   clang_analyzer_eval(v.buf[0] == &c); // expected-warning{{TRUE}}
 #else
@@ -184,13 +196,13 @@ void testVariable() {
     ClassWithDestructor c = ClassWithDestructor(v);
     // Check if the last destructor is an automatic destructor.
     // A temporary destructor would have fired by now.
-#if __cplusplus >= 201703L
+#if ELIDE
     clang_analyzer_eval(v.len == 1); // expected-warning{{TRUE}}
 #else
     clang_analyzer_eval(v.len == 3); // expected-warning{{TRUE}}
 #endif
   }
-#if __cplusplus >= 201703L
+#if ELIDE
   // 0. Construct the variable.
   // 1. Destroy the variable.
   clang_analyzer_eval(v.len == 2); // expected-warning{{TRUE}}
@@ -218,13 +230,13 @@ void testCtorInitializer() {
     TestCtorInitializer t(v);
     // Check if the last destructor is an automatic destructor.
     // A temporary destructor would have fired by now.
-#if __cplusplus >= 201703L
+#if ELIDE
     clang_analyzer_eval(v.len == 1); // expected-warning{{TRUE}}
 #else
     clang_analyzer_eval(v.len == 3); // expected-warning{{TRUE}}
 #endif
   }
-#if __cplusplus >= 201703L
+#if ELIDE
   // 0. Construct the member variable.
   // 1. Destroy the member variable.
   clang_analyzer_eval(v.len == 2); // expected-warning{{TRUE}}
@@ -257,14 +269,14 @@ void testMultipleReturnsWithDestructors(
     ClassWithDestructor c = make3(v);
     // Check if the last destructor is an automatic destructor.
     // A temporary destructor would have fired by now.
-#if __cplusplus >= 201703L
+#if ELIDE
     clang_analyzer_eval(v.len == 1); // expected-warning{{TRUE}}
 #else
     clang_analyzer_eval(v.len == 9); // expected-warning{{TRUE}}
 #endif
   }
 
-#if __cplusplus >= 201703L
+#if ELIDE
   // 0. Construct the variable. Yes, constructor in make1() constructs
   //    the variable 'c'.
   // 1. Destroy the variable.

Modified: cfe/trunk/test/Analysis/gtest.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/gtest.cpp?rev=335800&r1=335799&r2=335800&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/gtest.cpp (original)
+++ cfe/trunk/test/Analysis/gtest.cpp Wed Jun 27 17:30:18 2018
@@ -154,13 +154,11 @@ void testConstrainState(int p) {
 void testAssertSymbolicPtr(const bool *b) {
   ASSERT_TRUE(*b); // no-crash
 
-  // FIXME: Our solver doesn't handle this well yet.
-  clang_analyzer_eval(*b); // expected-warning{{UNKNOWN}}
+  clang_analyzer_eval(*b); // expected-warning{{TRUE}}
 }
 
 void testAssertSymbolicRef(const bool &b) {
   ASSERT_TRUE(b); // no-crash
 
-  // FIXME: Our solver doesn't handle this well yet.
-  clang_analyzer_eval(b); // expected-warning{{UNKNOWN}}
+  clang_analyzer_eval(b); // expected-warning{{TRUE}}
 }

Modified: cfe/trunk/test/Analysis/inlining/temp-dtors-path-notes.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/inlining/temp-dtors-path-notes.cpp?rev=335800&r1=335799&r2=335800&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/inlining/temp-dtors-path-notes.cpp (original)
+++ cfe/trunk/test/Analysis/inlining/temp-dtors-path-notes.cpp Wed Jun 27 17:30:18 2018
@@ -30,19 +30,14 @@ public:
 };
 
 void test(int coin) {
-  // We'd divide by zero in the temporary destructor that goes after the
-  // elidable copy-constructor from C(0) to the lifetime-extended temporary.
-  // So in fact this example has nothing to do with lifetime extension.
-  // Actually, it would probably be better to elide the constructor, and
-  // put the note for the destructor call at the closing brace after nop.
+  // We'd divide by zero in the automatic destructor for variable 'c'.
   const C &c = coin ? C(1) : C(0); // expected-note   {{Assuming 'coin' is 0}}
                                    // expected-note at -1{{'?' condition is false}}
                                    // expected-note at -2{{Passing the value 0 via 1st parameter 'x'}}
                                    // expected-note at -3{{Calling constructor for 'C'}}
                                    // expected-note at -4{{Returning from constructor for 'C'}}
-                                   // expected-note at -5{{Calling '~C'}}
   c.nop();
-}
+} // expected-note{{Calling '~C'}}
 } // end namespace test_lifetime_extended_temporary
 
 namespace test_bug_after_dtor {

Modified: cfe/trunk/test/Analysis/lifetime-extension.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/lifetime-extension.cpp?rev=335800&r1=335799&r2=335800&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/lifetime-extension.cpp (original)
+++ cfe/trunk/test/Analysis/lifetime-extension.cpp Wed Jun 27 17:30:18 2018
@@ -97,13 +97,10 @@ void f1() {
 
 void f2() {
   C *after, *before;
-  C c = C(1, &after, &before);
-  clang_analyzer_eval(after == before);
-#ifdef TEMPORARIES
-  // expected-warning at -2{{TRUE}}
-#else
-  // expected-warning at -4{{UNKNOWN}}
-#endif
+  {
+    C c = C(1, &after, &before);
+  }
+  clang_analyzer_eval(after == before); // expected-warning{{TRUE}}
 }
 
 void f3(bool coin) {
@@ -129,6 +126,7 @@ void f4(bool coin) {
   // operator. Ideally also add support for the binary conditional operator in
   // C++. Because for now it calls the constructor for the condition twice.
   if (coin) {
+    // FIXME: Should not warn.
     clang_analyzer_eval(after == before);
 #ifdef TEMPORARIES
   // expected-warning at -2{{The left operand of '==' is a garbage value}}
@@ -136,13 +134,12 @@ void f4(bool coin) {
   // expected-warning at -4{{UNKNOWN}}
 #endif
   } else {
+    // FIXME: Should be TRUE.
     clang_analyzer_eval(after == before);
 #ifdef TEMPORARIES
-    // Seems to work at the moment, but also seems accidental.
-    // Feel free to break.
-  // expected-warning at -4{{TRUE}}
+  // expected-warning at -2{{FALSE}}
 #else
-  // expected-warning at -6{{UNKNOWN}}
+  // expected-warning at -4{{UNKNOWN}}
 #endif
   }
 }
@@ -234,24 +231,25 @@ void f2() {
 } // end namespace maintain_original_object_address_on_move
 
 namespace maintain_address_of_copies {
+class C;
 
-template <typename T> struct AddressVector {
-  const T *buf[10];
+struct AddressVector {
+  C *buf[10];
   int len;
 
   AddressVector() : len(0) {}
 
-  void push(const T *t) {
-    buf[len] = t;
+  void push(C *c) {
+    buf[len] = c;
     ++len;
   }
 };
 
 class C {
-  AddressVector<C> &v;
+  AddressVector &v;
 
 public:
-  C(AddressVector<C> &v) : v(v) { v.push(this); }
+  C(AddressVector &v) : v(v) { v.push(this); }
   ~C() { v.push(this); }
 
 #ifdef MOVES
@@ -267,132 +265,70 @@ public:
 #endif
   } // no-warning
 
-  static C make(AddressVector<C> &v) { return C(v); }
+  static C make(AddressVector &v) { return C(v); }
 };
 
 void f1() {
-  AddressVector<C> v;
+  AddressVector v;
   {
     C c = C(v);
   }
-  // 0. Create the original temporary and lifetime-extend it into variable 'c'
-  //    construction argument.
-  // 1. Construct variable 'c' (elidable copy/move).
-  // 2. Destroy the temporary.
-  // 3. Destroy variable 'c'.
-  clang_analyzer_eval(v.len == 4);
-  clang_analyzer_eval(v.buf[0] == v.buf[2]);
-  clang_analyzer_eval(v.buf[1] == v.buf[3]);
-#ifdef TEMPORARIES
-  // expected-warning at -4{{TRUE}}
-  // expected-warning at -4{{TRUE}}
-  // expected-warning at -4{{TRUE}}
-#else
-  // expected-warning at -8{{UNKNOWN}}
-  // expected-warning at -8{{UNKNOWN}}
-  // expected-warning at -8{{UNKNOWN}}
-#endif
+  // 0. Construct variable 'c' (copy/move elided).
+  // 1. Destroy variable 'c'.
+  clang_analyzer_eval(v.len == 2); // expected-warning{{TRUE}}
+  clang_analyzer_eval(v.buf[0] == v.buf[1]); // expected-warning{{TRUE}}
 }
 
 void f2() {
-  AddressVector<C> v;
+  AddressVector v;
   {
     const C &c = C::make(v);
   }
-  // 0. Construct the original temporary within make(),
-  // 1. Construct the return value of make() (elidable copy/move) and
-  //    lifetime-extend it via reference 'c',
-  // 2. Destroy the temporary within make(),
-  // 3. Destroy the temporary lifetime-extended by 'c'.
-  clang_analyzer_eval(v.len == 4);
-  clang_analyzer_eval(v.buf[0] == v.buf[2]);
-  clang_analyzer_eval(v.buf[1] == v.buf[3]);
+  // 0. Construct the return value of make() (copy/move elided) and
+  //    lifetime-extend it directly via reference 'c',
+  // 1. Destroy the temporary lifetime-extended by 'c'.
+  clang_analyzer_eval(v.len == 2);
+  clang_analyzer_eval(v.buf[0] == v.buf[1]);
 #ifdef TEMPORARIES
-  // expected-warning at -4{{TRUE}}
-  // expected-warning at -4{{TRUE}}
-  // expected-warning at -4{{TRUE}}
+  // expected-warning at -3{{TRUE}}
+  // expected-warning at -3{{TRUE}}
 #else
-  // expected-warning at -8{{UNKNOWN}}
-  // expected-warning at -8{{UNKNOWN}}
-  // expected-warning at -8{{UNKNOWN}}
+  // expected-warning at -6{{UNKNOWN}}
+  // expected-warning at -6{{UNKNOWN}}
 #endif
 }
 
 void f3() {
-  AddressVector<C> v;
+  AddressVector v;
   {
     C &&c = C::make(v);
   }
-  // 0. Construct the original temporary within make(),
-  // 1. Construct the return value of make() (elidable copy/move) and
-  //    lifetime-extend it via reference 'c',
-  // 2. Destroy the temporary within make(),
-  // 3. Destroy the temporary lifetime-extended by 'c'.
-  clang_analyzer_eval(v.len == 4);
-  clang_analyzer_eval(v.buf[0] == v.buf[2]);
-  clang_analyzer_eval(v.buf[1] == v.buf[3]);
+  // 0. Construct the return value of make() (copy/move elided) and
+  //    lifetime-extend it directly via reference 'c',
+  // 1. Destroy the temporary lifetime-extended by 'c'.
+  clang_analyzer_eval(v.len == 2);
+  clang_analyzer_eval(v.buf[0] == v.buf[1]);
 #ifdef TEMPORARIES
-  // expected-warning at -4{{TRUE}}
-  // expected-warning at -4{{TRUE}}
-  // expected-warning at -4{{TRUE}}
+  // expected-warning at -3{{TRUE}}
+  // expected-warning at -3{{TRUE}}
 #else
-  // expected-warning at -8{{UNKNOWN}}
-  // expected-warning at -8{{UNKNOWN}}
-  // expected-warning at -8{{UNKNOWN}}
+  // expected-warning at -6{{UNKNOWN}}
+  // expected-warning at -6{{UNKNOWN}}
 #endif
 }
 
-C doubleMake(AddressVector<C> &v) {
+C doubleMake(AddressVector &v) {
   return C::make(v);
 }
 
 void f4() {
-  AddressVector<C> v;
+  AddressVector v;
   {
     C c = doubleMake(v);
   }
-  // 0. Construct the original temporary within make(),
-  // 1. Construct the return value of make() (elidable copy/move) and
-  //    lifetime-extend it into the return value constructor argument within
-  //    doubleMake(),
-  // 2. Destroy the temporary within make(),
-  // 3. Construct the return value of doubleMake() (elidable copy/move) and
-  //    lifetime-extend it into the variable 'c' constructor argument,
-  // 4. Destroy the return value of make(),
-  // 5. Construct variable 'c' (elidable copy/move),
-  // 6. Destroy the return value of doubleMake(),
-  // 7. Destroy variable 'c'.
-  clang_analyzer_eval(v.len == 8);
-  clang_analyzer_eval(v.buf[0] == v.buf[2]);
-  clang_analyzer_eval(v.buf[1] == v.buf[4]);
-  clang_analyzer_eval(v.buf[3] == v.buf[6]);
-  clang_analyzer_eval(v.buf[5] == v.buf[7]);
-#ifdef TEMPORARIES
-  // expected-warning at -6{{TRUE}}
-  // expected-warning at -6{{TRUE}}
-  // expected-warning at -6{{TRUE}}
-  // expected-warning at -6{{TRUE}}
-  // expected-warning at -6{{TRUE}}
-#else
-  // expected-warning at -12{{UNKNOWN}}
-  // expected-warning at -12{{UNKNOWN}}
-  // expected-warning at -12{{UNKNOWN}}
-  // expected-warning at -12{{UNKNOWN}}
-  // expected-warning at -12{{UNKNOWN}}
-#endif
-}
-
-class NoDtor {
-  AddressVector<NoDtor> &v;
-
-public:
-  NoDtor(AddressVector<NoDtor> &v) : v(v) { v.push(this); }
-};
-
-void f5() {
-  AddressVector<NoDtor> v;
-  const NoDtor &N = NoDtor(v);
-  clang_analyzer_eval(v.buf[0] == &N); // expected-warning{{TRUE}}
+  // 0. Construct variable 'c' (all copies/moves elided),
+  // 1. Destroy variable 'c'.
+  clang_analyzer_eval(v.len == 2); // expected-warning{{TRUE}}
+  clang_analyzer_eval(v.buf[0] == v.buf[1]); // expected-warning{{TRUE}}
 }
-
 } // end namespace maintain_address_of_copies

Modified: cfe/trunk/test/Analysis/temporaries.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/temporaries.cpp?rev=335800&r1=335799&r2=335800&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/temporaries.cpp (original)
+++ cfe/trunk/test/Analysis/temporaries.cpp Wed Jun 27 17:30:18 2018
@@ -612,107 +612,44 @@ void test() {
   clang_analyzer_eval(c3.getY() == 2); // expected-warning{{TRUE}}
 
   C c4 = returnTemporaryWithConstruction();
-  clang_analyzer_eval(c4.getX() == 1);
-  clang_analyzer_eval(c4.getY() == 2);
-#ifdef TEMPORARY_DTORS
-  // expected-warning at -3{{TRUE}}
-  // expected-warning at -3{{TRUE}}
-#else
-  // expected-warning at -6{{UNKNOWN}}
-  // expected-warning at -6{{UNKNOWN}}
-#endif
+  clang_analyzer_eval(c4.getX() == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(c4.getY() == 2); // expected-warning{{TRUE}}
 
   C c5 = returnTemporaryWithAnotherFunctionWithConstruction();
-  clang_analyzer_eval(c5.getX() == 1);
-  clang_analyzer_eval(c5.getY() == 2);
-#ifdef TEMPORARY_DTORS
-  // expected-warning at -3{{TRUE}}
-  // expected-warning at -3{{TRUE}}
-#else
-  // expected-warning at -6{{UNKNOWN}}
-  // expected-warning at -6{{UNKNOWN}}
-#endif
+  clang_analyzer_eval(c5.getX() == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(c5.getY() == 2); // expected-warning{{TRUE}}
 
   C c6 = returnTemporaryWithCopyConstructionWithConstruction();
-  clang_analyzer_eval(c5.getX() == 1);
-  clang_analyzer_eval(c5.getY() == 2);
-#ifdef TEMPORARY_DTORS
-  // expected-warning at -3{{TRUE}}
-  // expected-warning at -3{{TRUE}}
-#else
-  // expected-warning at -6{{UNKNOWN}}
-  // expected-warning at -6{{UNKNOWN}}
-#endif
+  clang_analyzer_eval(c5.getX() == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(c5.getY() == 2); // expected-warning{{TRUE}}
 
 #if __cplusplus >= 201103L
 
   C c7 = returnTemporaryWithBraces();
-  clang_analyzer_eval(c7.getX() == 1);
-  clang_analyzer_eval(c7.getY() == 2);
-#ifdef TEMPORARY_DTORS
-  // expected-warning at -3{{TRUE}}
-  // expected-warning at -3{{TRUE}}
-#else
-  // expected-warning at -6{{UNKNOWN}}
-  // expected-warning at -6{{UNKNOWN}}
-#endif
+  clang_analyzer_eval(c7.getX() == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(c7.getY() == 2); // expected-warning{{TRUE}}
 
   C c8 = returnTemporaryWithAnotherFunctionWithBraces();
-  clang_analyzer_eval(c8.getX() == 1);
-  clang_analyzer_eval(c8.getY() == 2);
-#ifdef TEMPORARY_DTORS
-  // expected-warning at -3{{TRUE}}
-  // expected-warning at -3{{TRUE}}
-#else
-  // expected-warning at -6{{UNKNOWN}}
-  // expected-warning at -6{{UNKNOWN}}
-#endif
+  clang_analyzer_eval(c8.getX() == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(c8.getY() == 2); // expected-warning{{TRUE}}
 
   C c9 = returnTemporaryWithCopyConstructionWithBraces();
-  clang_analyzer_eval(c9.getX() == 1);
-  clang_analyzer_eval(c9.getY() == 2);
-#ifdef TEMPORARY_DTORS
-  // expected-warning at -3{{TRUE}}
-  // expected-warning at -3{{TRUE}}
-#else
-  // expected-warning at -6{{UNKNOWN}}
-  // expected-warning at -6{{UNKNOWN}}
-#endif
+  clang_analyzer_eval(c9.getX() == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(c9.getY() == 2); // expected-warning{{TRUE}}
 
 #endif // C++11
 
   D d1 = returnTemporaryWithVariableAndNonTrivialCopy();
-  clang_analyzer_eval(d1.getX() == 1);
-  clang_analyzer_eval(d1.getY() == 2);
-#ifdef TEMPORARY_DTORS
-  // expected-warning at -3{{TRUE}}
-  // expected-warning at -3{{TRUE}}
-#else
-  // expected-warning at -6{{UNKNOWN}}
-  // expected-warning at -6{{UNKNOWN}}
-#endif
+  clang_analyzer_eval(d1.getX() == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(d1.getY() == 2); // expected-warning{{TRUE}}
 
   D d2 = returnTemporaryWithAnotherFunctionWithVariableAndNonTrivialCopy();
-  clang_analyzer_eval(d2.getX() == 1);
-  clang_analyzer_eval(d2.getY() == 2);
-#ifdef TEMPORARY_DTORS
-  // expected-warning at -3{{TRUE}}
-  // expected-warning at -3{{TRUE}}
-#else
-  // expected-warning at -6{{UNKNOWN}}
-  // expected-warning at -6{{UNKNOWN}}
-#endif
+  clang_analyzer_eval(d2.getX() == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(d2.getY() == 2); // expected-warning{{TRUE}}
 
   D d3 = returnTemporaryWithCopyConstructionWithVariableAndNonTrivialCopy();
-  clang_analyzer_eval(d3.getX() == 1);
-  clang_analyzer_eval(d3.getY() == 2);
-#ifdef TEMPORARY_DTORS
-  // expected-warning at -3{{TRUE}}
-  // expected-warning at -3{{TRUE}}
-#else
-  // expected-warning at -6{{UNKNOWN}}
-  // expected-warning at -6{{UNKNOWN}}
-#endif
+  clang_analyzer_eval(d3.getX() == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(d3.getY() == 2); // expected-warning{{TRUE}}
 }
 } // namespace test_return_temporary
 
@@ -767,19 +704,9 @@ void test(int coin) {
 
   C c2 = coin ? C(1) : C(2);
   if (coin) {
-    clang_analyzer_eval(c2.getX() == 1);
-#ifdef TEMPORARY_DTORS
-  // expected-warning at -2{{TRUE}}
-#else
-  // expected-warning at -4{{UNKNOWN}}
-#endif
+    clang_analyzer_eval(c2.getX() == 1); // expected-warning{{TRUE}}
   } else {
-    clang_analyzer_eval(c2.getX() == 2);
-#ifdef TEMPORARY_DTORS
-  // expected-warning at -2{{TRUE}}
-#else
-  // expected-warning at -4{{UNKNOWN}}
-#endif
+    clang_analyzer_eval(c2.getX() == 2); // expected-warning{{TRUE}}
   }
 }
 
@@ -816,16 +743,9 @@ void test_simple_temporary_with_copy() {
   {
     C c = C(x, y);
   }
-  // Two constructors (temporary object expr and copy) and two destructors.
-  clang_analyzer_eval(x == 2);
-  clang_analyzer_eval(y == 2);
-#ifdef TEMPORARY_DTORS
-  // expected-warning at -3{{TRUE}}
-  // expected-warning at -3{{TRUE}}
-#else
-  // expected-warning at -6{{UNKNOWN}}
-  // expected-warning at -6{{UNKNOWN}}
-#endif
+  // Only one constructor directly into the variable, and one destructor.
+  clang_analyzer_eval(x == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(y == 1); // expected-warning{{TRUE}}
 }
 
 void test_ternary_temporary(int coin) {
@@ -833,10 +753,10 @@ void test_ternary_temporary(int coin) {
   {
     const C &c = coin ? C(x, y) : C(z, w);
   }
-  // This time each branch contains an additional elidable copy constructor.
+  // Only one constructor on every branch, and one automatic destructor.
   if (coin) {
-    clang_analyzer_eval(x == 2);
-    clang_analyzer_eval(y == 2);
+    clang_analyzer_eval(x == 1);
+    clang_analyzer_eval(y == 1);
 #ifdef TEMPORARY_DTORS
     // expected-warning at -3{{TRUE}}
     // expected-warning at -3{{TRUE}}
@@ -850,8 +770,8 @@ void test_ternary_temporary(int coin) {
   } else {
     clang_analyzer_eval(x == 0); // expected-warning{{TRUE}}
     clang_analyzer_eval(y == 0); // expected-warning{{TRUE}}
-    clang_analyzer_eval(z == 2);
-    clang_analyzer_eval(w == 2);
+    clang_analyzer_eval(z == 1);
+    clang_analyzer_eval(w == 1);
 #ifdef TEMPORARY_DTORS
     // expected-warning at -3{{TRUE}}
     // expected-warning at -3{{TRUE}}
@@ -867,33 +787,18 @@ void test_ternary_temporary_with_copy(in
   {
     C c = coin ? C(x, y) : C(z, w);
   }
-  // Temporary expression, elidable copy within branch,
-  // constructor for variable - 3 total.
+  // On each branch the variable is constructed directly.
   if (coin) {
-    clang_analyzer_eval(x == 3);
-    clang_analyzer_eval(y == 3);
-#ifdef TEMPORARY_DTORS
-    // expected-warning at -3{{TRUE}}
-    // expected-warning at -3{{TRUE}}
-#else
-    // expected-warning at -6{{UNKNOWN}}
-    // expected-warning at -6{{UNKNOWN}}
-#endif
+    clang_analyzer_eval(x == 1); // expected-warning{{TRUE}}
+    clang_analyzer_eval(y == 1); // expected-warning{{TRUE}}
     clang_analyzer_eval(z == 0); // expected-warning{{TRUE}}
     clang_analyzer_eval(w == 0); // expected-warning{{TRUE}}
 
   } else {
     clang_analyzer_eval(x == 0); // expected-warning{{TRUE}}
     clang_analyzer_eval(y == 0); // expected-warning{{TRUE}}
-    clang_analyzer_eval(z == 3);
-    clang_analyzer_eval(w == 3);
-#ifdef TEMPORARY_DTORS
-    // expected-warning at -3{{TRUE}}
-    // expected-warning at -3{{TRUE}}
-#else
-    // expected-warning at -6{{UNKNOWN}}
-    // expected-warning at -6{{UNKNOWN}}
-#endif
+    clang_analyzer_eval(z == 1); // expected-warning{{TRUE}}
+    clang_analyzer_eval(w == 1); // expected-warning{{TRUE}}
   }
 }
 } // namespace test_match_constructors_and_destructors
@@ -928,12 +833,13 @@ void testLifetimeExtendedCall() {
 }
 
 void testCopiedCall() {
-  C c = make();
-  // Should have divided by zero in the temporary destructor.
-  clang_analyzer_warnIfReached();
-#ifndef TEMPORARY_DTORS
-    // expected-warning at -2{{REACHABLE}}
-#endif
+  {
+    C c = make();
+    // Should have elided the constructor/destructor for the temporary
+    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
+  }
+  // Should have divided by zero in the destructor.
+  clang_analyzer_warnIfReached(); // no-warning
 }
 } // namespace destructors_for_return_values
 
@@ -1018,20 +924,10 @@ void test() {
 #endif
 
   S s = 20;
-  clang_analyzer_eval(s.x == 20);
-#ifdef TEMPORARY_DTORS
-  // expected-warning at -2{{TRUE}}
-#else
-  // expected-warning at -4{{UNKNOWN}}
-#endif
+  clang_analyzer_eval(s.x == 20); // expected-warning{{TRUE}}
 
   C c2 = s;
-  clang_analyzer_eval(c2.getX() == 20);
-#ifdef TEMPORARY_DTORS
-  // expected-warning at -2{{TRUE}}
-#else
-  // expected-warning at -4{{UNKNOWN}}
-#endif
+  clang_analyzer_eval(c2.getX() == 20); // expected-warning{{TRUE}}
 }
 } // end namespace implicit_constructor_conversion
 




More information about the cfe-commits mailing list