baloghadamsoftware created this revision.
baloghadamsoftware added a reviewer: dcoughlin.
baloghadamsoftware added subscribers: cfe-commits, o.gyorgy, xazax.hun.

Many classes (e.g. in common stl implementations) contain user-written copy and 
move constructors that are identical to the implicit ones. This far ExprEngine 
could only handle the official "trivial" case. This patch extends ExprEngine 
for copy and move constructors that are user provided but in all other aspects 
they are the same as trivial ones.

A typical example is the GNU implementation of the iterator of std::deque, 
where the user provided copy constructor of the non-const version also accepts 
an instance of the const version as parameter. This patch allows correct 
simulation of this iterator.

https://reviews.llvm.org/D22374

Files:
  lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
  test/Analysis/ctor.mm

Index: test/Analysis/ctor.mm
===================================================================
--- test/Analysis/ctor.mm
+++ test/Analysis/ctor.mm
@@ -144,11 +144,11 @@
 
     NonPOD() {}
     NonPOD(const NonPOD &Other)
-      : x(Other.x), y(Other.y) // expected-warning {{undefined}}
+      : x(Other.x), y(Other.y) // no-warning
     {
     }
     NonPOD(NonPOD &&Other)
-    : x(Other.x), y(Other.y) // expected-warning {{undefined}}
+    : x(Other.x), y(Other.y) // no-warning
     {
     }
 
@@ -174,11 +174,11 @@
 
       Inner() {}
       Inner(const Inner &Other)
-        : x(Other.x), y(Other.y) // expected-warning {{undefined}}
+        : x(Other.x), y(Other.y) // no-warning
       {
       }
       Inner(Inner &&Other)
-      : x(Other.x), y(Other.y) // expected-warning {{undefined}}
+      : x(Other.x), y(Other.y) // no-warning
       {
       }
 
@@ -224,25 +224,29 @@
   void testNonPOD() {
     NonPOD p;
     p.x = 1;
-    NonPOD p2 = p;
+    NonPOD p2 = p; // no-warning
+    clang_analyzer_eval(p2.x == 1); // expected-warning{{TRUE}}
   }
 
   void testNonPODMove() {
     NonPOD p;
     p.x = 1;
-    NonPOD p2 = move(p);
+    NonPOD p2 = move(p); // no-warning
+    clang_analyzer_eval(p2.x == 1); // expected-warning{{TRUE}}
   }
 
   void testNonPODWrapper() {
     NonPODWrapper w;
     w.p.y = 1;
-    NonPODWrapper w2 = w;
+    NonPODWrapper w2 = w; // no-warning
+    clang_analyzer_eval(w2.p.y == 1); // expected-warning{{TRUE}}
   }
 
   void testNonPODWrapperMove() {
     NonPODWrapper w;
     w.p.y = 1;
-    NonPODWrapper w2 = move(w);
+    NonPODWrapper w2 = move(w); // no-warning
+    clang_analyzer_eval(w2.p.y == 1); // expected-warning{{TRUE}}
   }
 
   // Not strictly about constructors, but trivial assignment operators should
Index: lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
===================================================================
--- lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
+++ lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
@@ -34,19 +34,132 @@
   Bldr.generateNode(ME, Pred, state);
 }
 
+bool isCopyOrMoveLike(const CXXConstructorDecl *Constr) {
+  if (Constr->isCopyOrMoveConstructor())
+    return true;
+
+  if(Constr->getNumParams() != 1)
+    return false;
+
+  const auto ParamType =
+    Constr->getParamDecl(0)->getType()->getUnqualifiedDesugaredType();
+  if(!ParamType->isReferenceType())
+    return false;
+
+  const auto ParamPointeeType =
+    ParamType->getAs<ReferenceType>()->getPointeeType();
+  if(!ParamPointeeType->isRecordType())
+    return false;
+  
+  const auto *ParamRecDecl = ParamPointeeType->getAs<RecordType>()->getDecl();
+  const auto *ThisRecDecl = Constr->getParent();
+
+  if(ParamRecDecl!=ThisRecDecl)
+    return false;
+
+  return true;
+}
+
+bool isAlmostTrivial(const CXXMethodDecl *Met) {
+  if (Met->isTrivial())
+    return true;
+
+  if(!Met->hasTrivialBody())
+    return false;
+
+  if(Met->getNumParams() != 1)
+    return false;
+
+  const auto *Param = Met->getParamDecl(0);
+  const auto *ThisRecDecl = Met->getParent();
+
+  const auto *Constr = dyn_cast<CXXConstructorDecl>(Met);
+  if(!Constr)
+    return false;
+  
+  if(ThisRecDecl->getNumVBases()>0)
+    return false;
+
+  for(const auto Base: ThisRecDecl->bases()) {
+    if(Base.getType()->getAsCXXRecordDecl()->field_empty())
+      continue;
+    for(const auto *Initzer: Constr->inits()) {
+      if(Initzer->isBaseInitializer() &&
+         Initzer->getBaseClass() == &*Base.getType()) {
+        if(const auto *CtrCall = dyn_cast<CXXConstructExpr>(Initzer->getInit()->IgnoreParenImpCasts())) {
+          if(!isCopyOrMoveLike(CtrCall->getConstructor()) ||
+             !isAlmostTrivial(CtrCall->getConstructor()))
+            return false;
+          if(const auto *Init = dyn_cast<DeclRefExpr>(CtrCall->getArg(0)->IgnoreParenImpCasts())) {
+            if(Init->getDecl() != Param)
+              return false;
+          } else {
+            return false;
+          }
+        } else {
+          return false;
+        }
+        break;
+      }
+    }
+  }
+
+  for(const auto *Field: ThisRecDecl->fields()) {
+    if(!Field->getType()->isScalarType() &&
+       !Field->getType()->isRecordType())
+      return false;
+    bool found = false;
+    for(const auto *Initzer: Constr->inits()) {
+      if(Initzer->isMemberInitializer() && Initzer->getMember() == Field) {
+        found = true;
+        const MemberExpr *InitMem;
+        if(Field->getType()->isScalarType()) {
+          InitMem = dyn_cast<MemberExpr>(Initzer->getInit()->IgnoreParenImpCasts());
+        } else {
+          if(const auto *CtrCall = dyn_cast<CXXConstructExpr>(Initzer->getInit()->IgnoreParenImpCasts())) {
+            if(!isCopyOrMoveLike(CtrCall->getConstructor()) ||
+               !isAlmostTrivial(CtrCall->getConstructor()))
+              return false;
+            InitMem = dyn_cast<MemberExpr>(CtrCall->getArg(0)->IgnoreParenImpCasts());
+          } else {
+            return false;
+          } 
+        }
+        if(InitMem)  {
+          if(InitMem->getMemberDecl() != Field)
+            return false;
+          if(const auto *Base = dyn_cast<DeclRefExpr>(InitMem->getBase())) {
+            if(Base->getDecl() != Param)
+              return false;
+          } else {
+            return false;
+          }
+        } else {
+          return false;
+        }
+        break;
+      }
+    }
+    if(!found)
+      return false;
+  } 
+
+  return true;
+}
+
 // FIXME: This is the sort of code that should eventually live in a Core
 // checker rather than as a special case in ExprEngine.
 void ExprEngine::performTrivialCopy(NodeBuilder &Bldr, ExplodedNode *Pred,
                                     const CallEvent &Call) {
   SVal ThisVal;
   bool AlwaysReturnsLValue;
   if (const CXXConstructorCall *Ctor = dyn_cast<CXXConstructorCall>(&Call)) {
-    assert(Ctor->getDecl()->isTrivial());
-    assert(Ctor->getDecl()->isCopyOrMoveConstructor());
+    assert(isAlmostTrivial(Ctor->getDecl()));
+    assert(isCopyOrMoveLike(Ctor->getDecl()));
     ThisVal = Ctor->getCXXThisVal();
     AlwaysReturnsLValue = false;
   } else {
-    assert(cast<CXXMethodDecl>(Call.getDecl())->isTrivial());
+    assert(isAlmostTrivial(cast<CXXMethodDecl>(Call.getDecl())));
     assert(cast<CXXMethodDecl>(Call.getDecl())->getOverloadedOperator() ==
            OO_Equal);
     ThisVal = cast<CXXInstanceCall>(Call).getCXXThisVal();
@@ -332,8 +445,8 @@
   StmtNodeBuilder Bldr(DstPreCall, DstEvaluated, *currBldrCtx);
 
   bool IsArray = isa<ElementRegion>(Target);
-  if (CE->getConstructor()->isTrivial() &&
-      CE->getConstructor()->isCopyOrMoveConstructor() &&
+  if (isAlmostTrivial(CE->getConstructor()) &&
+      isCopyOrMoveLike(CE->getConstructor()) &&
       !IsArray) {
     // FIXME: Handle other kinds of trivial constructors as well.
     for (ExplodedNodeSet::iterator I = DstPreCall.begin(), E = DstPreCall.end();
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to