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