]> granicus.if.org Git - clang/commitdiff
[analyzer] Better detect when C++ object was constructed into existing region.
authorDevin Coughlin <dcoughlin@apple.com>
Thu, 17 Dec 2015 00:28:33 +0000 (00:28 +0000)
committerDevin Coughlin <dcoughlin@apple.com>
Thu, 17 Dec 2015 00:28:33 +0000 (00:28 +0000)
When the analyzer evaluates a CXXConstructExpr, it looks ahead in the CFG for
the current block to detect what region the object should be constructed into.
If the constructor was directly constructed into a local variable or field
region then there is no need to explicitly bind the constructed value to
the local or field when analyzing the DeclStmt or CXXCtorInitializer that
called the constructor.

Unfortunately, there were situations in which the CXXConstructExpr was
constructed into a temporary region but when evaluating the corresponding
DeclStmt or CXXCtorInitializer the analyzer assumed the object was constructed
into the local or field. This led to spurious warnings about uninitialized
values (PR25777).

To avoid these false positives, this commit factors out the logic for
determining when a CXXConstructExpr will be directly constructed into existing
storage, adds the inverse logic to detect when the corresponding later bind can
be safely skipped, and adds assertions to make sure these two checks are in
sync.

rdar://problem/21947725

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@255859 91177308-0d34-0410-b5e6-96231b3b80d8

include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
lib/StaticAnalyzer/Core/ExprEngine.cpp
lib/StaticAnalyzer/Core/ExprEngineC.cpp
lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
test/Analysis/initializer.cpp

index 8337f770d4b9d41c5e2aee44acce4c312ce8a7bc..99083c9d6135b7d5638d228d6cd462579a565123 100644 (file)
@@ -604,6 +604,28 @@ private:
                                                 const LocationContext *LC,
                                                 const Expr *E,
                                                 const Expr *ResultE = nullptr);
+
+  /// For a DeclStmt or CXXInitCtorInitializer, walk backward in the current CFG
+  /// block to find the constructor expression that directly constructed into
+  /// the storage for this statement. Returns null if the constructor for this
+  /// statement created a temporary object region rather than directly
+  /// constructing into an existing region.
+  const CXXConstructExpr *findDirectConstructorForCurrentCFGElement();
+
+  /// For a CXXConstructExpr, walk forward in the current CFG block to find the
+  /// CFGElement for the DeclStmt or CXXInitCtorInitializer for which is
+  /// directly constructed by this constructor. Returns None if the current
+  /// constructor expression did not directly construct into an existing
+  /// region.
+  Optional<CFGElement> findElementDirectlyInitializedByCurrentConstructor();
+
+  /// For a given constructor, look forward in the current CFG block to
+  /// determine the region into which an object will be constructed by \p CE.
+  /// Returns either a field or local variable region if the object will be
+  /// directly constructed in an existing region or a temporary object region
+  /// if not.
+  const MemRegion *getRegionForConstructedObject(const CXXConstructExpr *CE,
+                                                 ExplodedNode *Pred);
 };
 
 /// Traits for storing the call processing policy inside GDM.
index 7ef91d65385b5052dc249e835dd7e2a9f684e9d1..662b0a2dd7989ae86b71980e51cd22bbf99e3f27 100644 (file)
@@ -476,8 +476,12 @@ void ExprEngine::ProcessInitializer(const CFGInitializer Init,
   if (BMI->isAnyMemberInitializer()) {
     // Constructors build the object directly in the field,
     // but non-objects must be copied in from the initializer.
-    const Expr *Init = BMI->getInit()->IgnoreImplicit();
-    if (!isa<CXXConstructExpr>(Init)) {
+    if (auto *CtorExpr = findDirectConstructorForCurrentCFGElement()) {
+      assert(BMI->getInit()->IgnoreImplicit() == CtorExpr);
+      (void)CtorExpr;
+      // The field was directly constructed, so there is no need to bind.
+    } else {
+      const Expr *Init = BMI->getInit()->IgnoreImplicit();
       const ValueDecl *Field;
       if (BMI->isIndirectMemberInitializer()) {
         Field = BMI->getIndirectMember();
index 35af30552431e12ba2752c636edd0228140620da..a5b58710b215aec060bd73d362e9437a06a567d0 100644 (file)
@@ -492,7 +492,10 @@ void ExprEngine::VisitDeclStmt(const DeclStmt *DS, ExplodedNode *Pred,
       ExplodedNode *UpdatedN = N;
       SVal InitVal = state->getSVal(InitEx, LC);
 
-      if (isa<CXXConstructExpr>(InitEx->IgnoreImplicit())) {
+      assert(DS->isSingleDecl());
+      if (auto *CtorExpr = findDirectConstructorForCurrentCFGElement()) {
+        assert(InitEx->IgnoreImplicit() == CtorExpr);
+        (void)CtorExpr;
         // We constructed the object directly in the variable.
         // No need to bind anything.
         B.generateNode(DS, UpdatedN, state);
index 07ae41027d31ea2fefbb76ce432fa5c0022b71cf..556e2239abfb5acd43270b7e28ff098cfc287de8 100644 (file)
@@ -103,49 +103,32 @@ static SVal makeZeroElementRegion(ProgramStateRef State, SVal LValue,
 }
 
 
-static const MemRegion *getRegionForConstructedObject(
-    const CXXConstructExpr *CE, ExplodedNode *Pred, ExprEngine &Eng,
-    unsigned int CurrStmtIdx) {
+const MemRegion *
+ExprEngine::getRegionForConstructedObject(const CXXConstructExpr *CE,
+                                          ExplodedNode *Pred) {
   const LocationContext *LCtx = Pred->getLocationContext();
   ProgramStateRef State = Pred->getState();
-  const NodeBuilderContext &CurrBldrCtx = Eng.getBuilderContext();
 
   // See if we're constructing an existing region by looking at the next
   // element in the CFG.
-  const CFGBlock *B = CurrBldrCtx.getBlock();
-  unsigned int NextStmtIdx = CurrStmtIdx + 1;
-  if (NextStmtIdx < B->size()) {
-    CFGElement Next = (*B)[NextStmtIdx];
-
-    // Is this a destructor? If so, we might be in the middle of an assignment
-    // to a local or member: look ahead one more element to see what we find.
-    while (Next.getAs<CFGImplicitDtor>() && NextStmtIdx + 1 < B->size()) {
-      ++NextStmtIdx;
-      Next = (*B)[NextStmtIdx];
-    }
 
-    // Is this a constructor for a local variable?
-    if (Optional<CFGStmt> StmtElem = Next.getAs<CFGStmt>()) {
-      if (const DeclStmt *DS = dyn_cast<DeclStmt>(StmtElem->getStmt())) {
-        if (const VarDecl *Var = dyn_cast<VarDecl>(DS->getSingleDecl())) {
-          if (Var->getInit() && Var->getInit()->IgnoreImplicit() == CE) {
-            SVal LValue = State->getLValue(Var, LCtx);
-            QualType Ty = Var->getType();
-            LValue = makeZeroElementRegion(State, LValue, Ty);
-            return LValue.getAsRegion();
-          }
+  if (auto Elem = findElementDirectlyInitializedByCurrentConstructor()) {
+    if (Optional<CFGStmt> StmtElem = Elem->getAs<CFGStmt>()) {
+      auto *DS = cast<DeclStmt>(StmtElem->getStmt());
+      if (const auto *Var = dyn_cast<VarDecl>(DS->getSingleDecl())) {
+        if (Var->getInit() && Var->getInit()->IgnoreImplicit() == CE) {
+          SVal LValue = State->getLValue(Var, LCtx);
+          QualType Ty = Var->getType();
+          LValue = makeZeroElementRegion(State, LValue, Ty);
+          return LValue.getAsRegion();
         }
       }
-    }
-
-    // Is this a constructor for a member?
-    if (Optional<CFGInitializer> InitElem = Next.getAs<CFGInitializer>()) {
+    } else if (Optional<CFGInitializer> InitElem = Elem->getAs<CFGInitializer>()) {
       const CXXCtorInitializer *Init = InitElem->getInitializer();
       assert(Init->isAnyMemberInitializer());
-
       const CXXMethodDecl *CurCtor = cast<CXXMethodDecl>(LCtx->getDecl());
-      Loc ThisPtr = Eng.getSValBuilder().getCXXThis(CurCtor,
-          LCtx->getCurrentStackFrame());
+      Loc ThisPtr =
+      getSValBuilder().getCXXThis(CurCtor, LCtx->getCurrentStackFrame());
       SVal ThisVal = State->getSVal(ThisPtr);
 
       const ValueDecl *Field;
@@ -167,13 +150,86 @@ static const MemRegion *getRegionForConstructedObject(
     // Don't forget to update the pre-constructor initialization code in
     // ExprEngine::VisitCXXConstructExpr.
   }
-
   // If we couldn't find an existing region to construct into, assume we're
   // constructing a temporary.
-  MemRegionManager &MRMgr = Eng.getSValBuilder().getRegionManager();
+  MemRegionManager &MRMgr = getSValBuilder().getRegionManager();
   return MRMgr.getCXXTempObjectRegion(CE, LCtx);
 }
 
+/// Returns true if the initializer for \Elem can be a direct
+/// constructor.
+static bool canHaveDirectConstructor(CFGElement Elem){
+  // DeclStmts and CXXCtorInitializers for fields can be directly constructed.
+
+  if (Optional<CFGStmt> StmtElem = Elem.getAs<CFGStmt>()) {
+    if (isa<DeclStmt>(StmtElem->getStmt())) {
+      return true;
+    }
+  }
+
+  if (Elem.getKind() == CFGElement::Initializer) {
+    return true;
+  }
+
+  return false;
+}
+
+Optional<CFGElement>
+ExprEngine::findElementDirectlyInitializedByCurrentConstructor() {
+  const NodeBuilderContext &CurrBldrCtx = getBuilderContext();
+  // See if we're constructing an existing region by looking at the next
+  // element in the CFG.
+  const CFGBlock *B = CurrBldrCtx.getBlock();
+  assert(isa<CXXConstructExpr>(((*B)[currStmtIdx]).castAs<CFGStmt>().getStmt()));
+  unsigned int NextStmtIdx = currStmtIdx + 1;
+  if (NextStmtIdx >= B->size())
+    return None;
+
+  CFGElement Next = (*B)[NextStmtIdx];
+
+  // Is this a destructor? If so, we might be in the middle of an assignment
+  // to a local or member: look ahead one more element to see what we find.
+  while (Next.getAs<CFGImplicitDtor>() && NextStmtIdx + 1 < B->size()) {
+    ++NextStmtIdx;
+    Next = (*B)[NextStmtIdx];
+  }
+
+  if (canHaveDirectConstructor(Next))
+    return Next;
+
+  return None;
+}
+
+const CXXConstructExpr *
+ExprEngine::findDirectConstructorForCurrentCFGElement() {
+  // Go backward in the CFG to see if the previous element (ignoring
+  // destructors) was a CXXConstructExpr. If so, that constructor
+  // was constructed directly into an existing region.
+  // This process is essentially the inverse of that performed in
+  // findElementDirectlyInitializedByCurrentConstructor().
+  if (currStmtIdx == 0)
+    return nullptr;
+
+  const CFGBlock *B = getBuilderContext().getBlock();
+  assert(canHaveDirectConstructor((*B)[currStmtIdx]));
+
+  unsigned int PreviousStmtIdx = currStmtIdx - 1;
+  CFGElement Previous = (*B)[PreviousStmtIdx];
+
+  while (Previous.getAs<CFGImplicitDtor>() && PreviousStmtIdx > 0) {
+    --PreviousStmtIdx;
+    Previous = (*B)[PreviousStmtIdx];
+  }
+
+  if (Optional<CFGStmt> PrevStmtElem = Previous.getAs<CFGStmt>()) {
+    if (auto *CtorExpr = dyn_cast<CXXConstructExpr>(PrevStmtElem->getStmt())) {
+      return CtorExpr;
+    }
+  }
+
+  return nullptr;
+}
+
 void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE,
                                        ExplodedNode *Pred,
                                        ExplodedNodeSet &destNodes) {
@@ -188,7 +244,7 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE,
 
   switch (CE->getConstructionKind()) {
   case CXXConstructExpr::CK_Complete: {
-    Target = getRegionForConstructedObject(CE, Pred, *this, currStmtIdx);
+    Target = getRegionForConstructedObject(CE, Pred);
     break;
   }
   case CXXConstructExpr::CK_VirtualBase:
index a71e35dc63c6c75bd227818b787c568f394ddae5..b31c315ba524b2aa4580e449566301858d0670b4 100644 (file)
@@ -143,3 +143,57 @@ namespace DefaultMemberInitializers {
     clang_analyzer_eval(w.p[1] == 'y'); // expected-warning{{TRUE}}
   }
 }
+
+namespace ReferenceInitialization {
+  struct OtherStruct {
+    OtherStruct(int i);
+    ~OtherStruct();
+  };
+
+  struct MyStruct {
+    MyStruct(int i);
+    MyStruct(OtherStruct os);
+
+    void method() const;
+  };
+
+  void referenceInitializeLocal() {
+    const MyStruct &myStruct(5);
+    myStruct.method(); // no-warning
+  }
+
+  void referenceInitializeMultipleLocals() {
+    const MyStruct &myStruct1(5), myStruct2(5), &myStruct3(5);
+    myStruct1.method(); // no-warning
+    myStruct2.method(); // no-warning
+    myStruct3.method(); // no-warning
+  }
+
+  void referenceInitializeLocalWithCleanup() {
+    const MyStruct &myStruct(OtherStruct(5));
+    myStruct.method(); // no-warning
+  }
+
+  struct HasMyStruct {
+    const MyStruct &ms; // expected-note {{reference member declared here}}
+    const MyStruct &msWithCleanups; // expected-note {{reference member declared here}}
+
+    // clang's Sema issues a warning when binding a reference member to a
+    // temporary value.
+    HasMyStruct() : ms(5), msWithCleanups(OtherStruct(5)) {
+        // expected-warning@-1 {{binding reference member 'ms' to a temporary value}}
+        // expected-warning@-2 {{binding reference member 'msWithCleanups' to a temporary value}}
+
+      // At this point the members are not garbage so we should not expect an
+      // analyzer warning here even though binding a reference member
+      // to a member is a terrible idea.
+      ms.method(); // no-warning
+      msWithCleanups.method(); // no-warning
+    }
+  };
+
+  void referenceInitializeField() {
+    HasMyStruct hms;
+  }
+
+};