Index: include/clang/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- include/clang/StaticAnalyzer/Checkers/Checkers.td +++ include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -303,9 +303,8 @@ HelpText<"Check for use of iterators of different containers where iterators " "of the same container are expected">; -def MisusedMovedObjectChecker: Checker<"MisusedMovedObject">, - HelpText<"Method calls on a moved-from object and copying a moved-from " - "object will be reported">; +def MoveChecker: Checker<"Move">, + HelpText<"Find use-after-move bugs in C++">; def UninitializedObjectChecker: Checker<"UninitializedObject">, HelpText<"Reports uninitialized fields after object construction">; Index: lib/StaticAnalyzer/Checkers/CMakeLists.txt =================================================================== --- lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -52,7 +52,7 @@ MallocOverflowSecurityChecker.cpp MallocSizeofChecker.cpp MmapWriteExecChecker.cpp - MisusedMovedObjectChecker.cpp + MoveChecker.cpp MPI-Checker/MPIBugReporter.cpp MPI-Checker/MPIChecker.cpp MPI-Checker/MPIFunctionClassifier.cpp Index: lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp =================================================================== --- lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp +++ lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp @@ -1,519 +0,0 @@ -// MisusedMovedObjectChecker.cpp - Check use of moved-from objects. - C++ -===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -// -// This defines checker which checks for potential misuses of a moved-from -// object. That means method calls on the object or copying it in moved-from -// state. -// -//===----------------------------------------------------------------------===// - -#include "ClangSACheckers.h" -#include "clang/AST/ExprCXX.h" -#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" -#include "clang/StaticAnalyzer/Core/Checker.h" -#include "clang/StaticAnalyzer/Core/CheckerManager.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" - -using namespace clang; -using namespace ento; - -namespace { - -struct RegionState { -private: - enum Kind { Moved, Reported } K; - RegionState(Kind InK) : K(InK) {} - -public: - bool isReported() const { return K == Reported; } - bool isMoved() const { return K == Moved; } - - static RegionState getReported() { return RegionState(Reported); } - static RegionState getMoved() { return RegionState(Moved); } - - bool operator==(const RegionState &X) const { return K == X.K; } - void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); } -}; - -class MisusedMovedObjectChecker - : public Checker { -public: - void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; - void checkPreCall(const CallEvent &MC, CheckerContext &C) const; - void checkPostCall(const CallEvent &MC, CheckerContext &C) const; - void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; - ProgramStateRef - checkRegionChanges(ProgramStateRef State, - const InvalidatedSymbols *Invalidated, - ArrayRef ExplicitRegions, - ArrayRef Regions, - const LocationContext *LCtx, const CallEvent *Call) const; - void printState(raw_ostream &Out, ProgramStateRef State, - const char *NL, const char *Sep) const override; - -private: - enum MisuseKind {MK_FunCall, MK_Copy, MK_Move}; - class MovedBugVisitor : public BugReporterVisitor { - public: - MovedBugVisitor(const MemRegion *R) : Region(R), Found(false) {} - - void Profile(llvm::FoldingSetNodeID &ID) const override { - static int X = 0; - ID.AddPointer(&X); - ID.AddPointer(Region); - } - - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; - - private: - // The tracked region. - const MemRegion *Region; - bool Found; - }; - - mutable std::unique_ptr BT; - ExplodedNode *reportBug(const MemRegion *Region, const CallEvent &Call, - CheckerContext &C, MisuseKind MK) const; - bool isInMoveSafeContext(const LocationContext *LC) const; - bool isStateResetMethod(const CXXMethodDecl *MethodDec) const; - bool isMoveSafeMethod(const CXXMethodDecl *MethodDec) const; - const ExplodedNode *getMoveLocation(const ExplodedNode *N, - const MemRegion *Region, - CheckerContext &C) const; -}; -} // end anonymous namespace - -REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, RegionState) - -// If a region is removed all of the subregions needs to be removed too. -static ProgramStateRef removeFromState(ProgramStateRef State, - const MemRegion *Region) { - if (!Region) - return State; - for (auto &E : State->get()) { - if (E.first->isSubRegionOf(Region)) - State = State->remove(E.first); - } - return State; -} - -static bool isAnyBaseRegionReported(ProgramStateRef State, - const MemRegion *Region) { - for (auto &E : State->get()) { - if (Region->isSubRegionOf(E.first) && E.second.isReported()) - return true; - } - return false; -} - -std::shared_ptr -MisusedMovedObjectChecker::MovedBugVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &) { - // We need only the last move of the reported object's region. - // The visitor walks the ExplodedGraph backwards. - if (Found) - return nullptr; - ProgramStateRef State = N->getState(); - ProgramStateRef StatePrev = N->getFirstPred()->getState(); - const RegionState *TrackedObject = State->get(Region); - const RegionState *TrackedObjectPrev = - StatePrev->get(Region); - if (!TrackedObject) - return nullptr; - if (TrackedObjectPrev && TrackedObject) - return nullptr; - - // Retrieve the associated statement. - const Stmt *S = PathDiagnosticLocation::getStmt(N); - if (!S) - return nullptr; - Found = true; - - std::string ObjectName; - if (const auto DecReg = Region->getAs()) { - const auto *RegionDecl = dyn_cast(DecReg->getDecl()); - ObjectName = RegionDecl->getNameAsString(); - } - std::string InfoText; - if (ObjectName != "") - InfoText = "'" + ObjectName + "' became 'moved-from' here"; - else - InfoText = "Became 'moved-from' here"; - - // Generate the extra diagnostic. - PathDiagnosticLocation Pos(S, BRC.getSourceManager(), - N->getLocationContext()); - return std::make_shared(Pos, InfoText, true); -} - -const ExplodedNode *MisusedMovedObjectChecker::getMoveLocation( - const ExplodedNode *N, const MemRegion *Region, CheckerContext &C) const { - // Walk the ExplodedGraph backwards and find the first node that referred to - // the tracked region. - const ExplodedNode *MoveNode = N; - - while (N) { - ProgramStateRef State = N->getState(); - if (!State->get(Region)) - break; - MoveNode = N; - N = N->pred_empty() ? nullptr : *(N->pred_begin()); - } - return MoveNode; -} - -ExplodedNode *MisusedMovedObjectChecker::reportBug(const MemRegion *Region, - const CallEvent &Call, - CheckerContext &C, - MisuseKind MK) const { - if (ExplodedNode *N = C.generateNonFatalErrorNode()) { - if (!BT) - BT.reset(new BugType(this, "Usage of a 'moved-from' object", - "C++ move semantics")); - - // Uniqueing report to the same object. - PathDiagnosticLocation LocUsedForUniqueing; - const ExplodedNode *MoveNode = getMoveLocation(N, Region, C); - - if (const Stmt *MoveStmt = PathDiagnosticLocation::getStmt(MoveNode)) - LocUsedForUniqueing = PathDiagnosticLocation::createBegin( - MoveStmt, C.getSourceManager(), MoveNode->getLocationContext()); - - // Creating the error message. - std::string ErrorMessage; - switch(MK) { - case MK_FunCall: - ErrorMessage = "Method call on a 'moved-from' object"; - break; - case MK_Copy: - ErrorMessage = "Copying a 'moved-from' object"; - break; - case MK_Move: - ErrorMessage = "Moving a 'moved-from' object"; - break; - } - if (const auto DecReg = Region->getAs()) { - const auto *RegionDecl = dyn_cast(DecReg->getDecl()); - ErrorMessage += " '" + RegionDecl->getNameAsString() + "'"; - } - - auto R = - llvm::make_unique(*BT, ErrorMessage, N, LocUsedForUniqueing, - MoveNode->getLocationContext()->getDecl()); - R->addVisitor(llvm::make_unique(Region)); - C.emitReport(std::move(R)); - return N; - } - return nullptr; -} - -// Removing the function parameters' MemRegion from the state. This is needed -// for PODs where the trivial destructor does not even created nor executed. -void MisusedMovedObjectChecker::checkEndFunction(const ReturnStmt *RS, - CheckerContext &C) const { - auto State = C.getState(); - TrackedRegionMapTy Objects = State->get(); - if (Objects.isEmpty()) - return; - - auto LC = C.getLocationContext(); - - const auto LD = dyn_cast_or_null(LC->getDecl()); - if (!LD) - return; - llvm::SmallSet InvalidRegions; - - for (auto Param : LD->parameters()) { - auto Type = Param->getType().getTypePtrOrNull(); - if (!Type) - continue; - if (!Type->isPointerType() && !Type->isReferenceType()) { - InvalidRegions.insert(State->getLValue(Param, LC).getAsRegion()); - } - } - - if (InvalidRegions.empty()) - return; - - for (const auto &E : State->get()) { - if (InvalidRegions.count(E.first->getBaseRegion())) - State = State->remove(E.first); - } - - C.addTransition(State); -} - -void MisusedMovedObjectChecker::checkPostCall(const CallEvent &Call, - CheckerContext &C) const { - const auto *AFC = dyn_cast(&Call); - if (!AFC) - return; - - ProgramStateRef State = C.getState(); - const auto MethodDecl = dyn_cast_or_null(AFC->getDecl()); - if (!MethodDecl) - return; - - const auto *ConstructorDecl = dyn_cast(MethodDecl); - - const auto *CC = dyn_cast_or_null(&Call); - // Check if an object became moved-from. - // Object can become moved from after a call to move assignment operator or - // move constructor . - if (ConstructorDecl && !ConstructorDecl->isMoveConstructor()) - return; - - if (!ConstructorDecl && !MethodDecl->isMoveAssignmentOperator()) - return; - - const auto ArgRegion = AFC->getArgSVal(0).getAsRegion(); - if (!ArgRegion) - return; - - // Skip moving the object to itself. - if (CC && CC->getCXXThisVal().getAsRegion() == ArgRegion) - return; - if (const auto *IC = dyn_cast(AFC)) - if (IC->getCXXThisVal().getAsRegion() == ArgRegion) - return; - - const MemRegion *BaseRegion = ArgRegion->getBaseRegion(); - // Skip temp objects because of their short lifetime. - if (BaseRegion->getAs() || - AFC->getArgExpr(0)->isRValue()) - return; - // If it has already been reported do not need to modify the state. - - if (State->get(ArgRegion)) - return; - // Mark object as moved-from. - State = State->set(ArgRegion, RegionState::getMoved()); - C.addTransition(State); -} - -bool MisusedMovedObjectChecker::isMoveSafeMethod( - const CXXMethodDecl *MethodDec) const { - // We abandon the cases where bool/void/void* conversion happens. - if (const auto *ConversionDec = - dyn_cast_or_null(MethodDec)) { - const Type *Tp = ConversionDec->getConversionType().getTypePtrOrNull(); - if (!Tp) - return false; - if (Tp->isBooleanType() || Tp->isVoidType() || Tp->isVoidPointerType()) - return true; - } - // Function call `empty` can be skipped. - return (MethodDec && MethodDec->getDeclName().isIdentifier() && - (MethodDec->getName().lower() == "empty" || - MethodDec->getName().lower() == "isempty")); -} - -bool MisusedMovedObjectChecker::isStateResetMethod( - const CXXMethodDecl *MethodDec) const { - if (!MethodDec) - return false; - if (MethodDec->hasAttr()) - return true; - if (MethodDec->getDeclName().isIdentifier()) { - std::string MethodName = MethodDec->getName().lower(); - if (MethodName == "reset" || MethodName == "clear" || - MethodName == "destroy") - return true; - } - return false; -} - -// Don't report an error inside a move related operation. -// We assume that the programmer knows what she does. -bool MisusedMovedObjectChecker::isInMoveSafeContext( - const LocationContext *LC) const { - do { - const auto *CtxDec = LC->getDecl(); - auto *CtorDec = dyn_cast_or_null(CtxDec); - auto *DtorDec = dyn_cast_or_null(CtxDec); - auto *MethodDec = dyn_cast_or_null(CtxDec); - if (DtorDec || (CtorDec && CtorDec->isCopyOrMoveConstructor()) || - (MethodDec && MethodDec->isOverloadedOperator() && - MethodDec->getOverloadedOperator() == OO_Equal) || - isStateResetMethod(MethodDec) || isMoveSafeMethod(MethodDec)) - return true; - } while ((LC = LC->getParent())); - return false; -} - -void MisusedMovedObjectChecker::checkPreCall(const CallEvent &Call, - CheckerContext &C) const { - ProgramStateRef State = C.getState(); - const LocationContext *LC = C.getLocationContext(); - ExplodedNode *N = nullptr; - - // Remove the MemRegions from the map on which a ctor/dtor call or assignment - // happened. - - // Checking constructor calls. - if (const auto *CC = dyn_cast(&Call)) { - State = removeFromState(State, CC->getCXXThisVal().getAsRegion()); - auto CtorDec = CC->getDecl(); - // Check for copying a moved-from object and report the bug. - if (CtorDec && CtorDec->isCopyOrMoveConstructor()) { - const MemRegion *ArgRegion = CC->getArgSVal(0).getAsRegion(); - const RegionState *ArgState = State->get(ArgRegion); - if (ArgState && ArgState->isMoved()) { - if (!isInMoveSafeContext(LC)) { - if(CtorDec->isMoveConstructor()) - N = reportBug(ArgRegion, Call, C, MK_Move); - else - N = reportBug(ArgRegion, Call, C, MK_Copy); - State = State->set(ArgRegion, - RegionState::getReported()); - } - } - } - C.addTransition(State, N); - return; - } - - const auto IC = dyn_cast(&Call); - if (!IC) - return; - // In case of destructor call we do not track the object anymore. - const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); - if (!ThisRegion) - return; - - if (dyn_cast_or_null(Call.getDecl())) { - State = removeFromState(State, ThisRegion); - C.addTransition(State); - return; - } - - const auto MethodDecl = dyn_cast_or_null(IC->getDecl()); - if (!MethodDecl) - return; - // Checking assignment operators. - bool OperatorEq = MethodDecl->isOverloadedOperator() && - MethodDecl->getOverloadedOperator() == OO_Equal; - // Remove the tracked object for every assignment operator, but report bug - // only for move or copy assignment's argument. - if (OperatorEq) { - State = removeFromState(State, ThisRegion); - if (MethodDecl->isCopyAssignmentOperator() || - MethodDecl->isMoveAssignmentOperator()) { - const RegionState *ArgState = - State->get(IC->getArgSVal(0).getAsRegion()); - if (ArgState && ArgState->isMoved() && !isInMoveSafeContext(LC)) { - const MemRegion *ArgRegion = IC->getArgSVal(0).getAsRegion(); - if(MethodDecl->isMoveAssignmentOperator()) - N = reportBug(ArgRegion, Call, C, MK_Move); - else - N = reportBug(ArgRegion, Call, C, MK_Copy); - State = - State->set(ArgRegion, RegionState::getReported()); - } - } - C.addTransition(State, N); - return; - } - - // The remaining part is check only for method call on a moved-from object. - - // We want to investigate the whole object, not only sub-object of a parent - // class in which the encountered method defined. - while (const auto *BR = dyn_cast(ThisRegion)) - ThisRegion = BR->getSuperRegion(); - - if (isMoveSafeMethod(MethodDecl)) - return; - - if (isStateResetMethod(MethodDecl)) { - State = removeFromState(State, ThisRegion); - C.addTransition(State); - return; - } - - // If it is already reported then we don't report the bug again. - const RegionState *ThisState = State->get(ThisRegion); - if (!(ThisState && ThisState->isMoved())) - return; - - // Don't report it in case if any base region is already reported - if (isAnyBaseRegionReported(State, ThisRegion)) - return; - - if (isInMoveSafeContext(LC)) - return; - - N = reportBug(ThisRegion, Call, C, MK_FunCall); - State = State->set(ThisRegion, RegionState::getReported()); - C.addTransition(State, N); -} - -void MisusedMovedObjectChecker::checkDeadSymbols(SymbolReaper &SymReaper, - CheckerContext &C) const { - ProgramStateRef State = C.getState(); - TrackedRegionMapTy TrackedRegions = State->get(); - for (TrackedRegionMapTy::value_type E : TrackedRegions) { - const MemRegion *Region = E.first; - bool IsRegDead = !SymReaper.isLiveRegion(Region); - - // Remove the dead regions from the region map. - if (IsRegDead) { - State = State->remove(Region); - } - } - C.addTransition(State); -} - -ProgramStateRef MisusedMovedObjectChecker::checkRegionChanges( - ProgramStateRef State, const InvalidatedSymbols *Invalidated, - ArrayRef ExplicitRegions, - ArrayRef Regions, const LocationContext *LCtx, - const CallEvent *Call) const { - // In case of an InstanceCall don't remove the ThisRegion from the GDM since - // it is handled in checkPreCall and checkPostCall. - const MemRegion *ThisRegion = nullptr; - if (const auto *IC = dyn_cast_or_null(Call)) { - ThisRegion = IC->getCXXThisVal().getAsRegion(); - } - - for (const auto *Region : ExplicitRegions) { - if (ThisRegion != Region) - State = removeFromState(State, Region); - } - - return State; -} - -void MisusedMovedObjectChecker::printState(raw_ostream &Out, - ProgramStateRef State, - const char *NL, - const char *Sep) const { - - TrackedRegionMapTy RS = State->get(); - - if (!RS.isEmpty()) { - Out << Sep << "Moved-from objects :" << NL; - for (auto I: RS) { - I.first->dumpToStream(Out); - if (I.second.isMoved()) - Out << ": moved"; - else - Out << ": moved and reported"; - Out << NL; - } - } -} -void ento::registerMisusedMovedObjectChecker(CheckerManager &mgr) { - mgr.registerChecker(); -} Index: lib/StaticAnalyzer/Checkers/MoveChecker.cpp =================================================================== --- lib/StaticAnalyzer/Checkers/MoveChecker.cpp +++ lib/StaticAnalyzer/Checkers/MoveChecker.cpp @@ -0,0 +1,512 @@ +// MoveChecker.cpp - Check use of moved-from objects. - C++ ---------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This defines checker which checks for potential misuses of a moved-from +// object. That means method calls on the object or copying it in moved-from +// state. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/AST/ExprCXX.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +using namespace clang; +using namespace ento; + +namespace { + +struct RegionState { +private: + enum Kind { Moved, Reported } K; + RegionState(Kind InK) : K(InK) {} + +public: + bool isReported() const { return K == Reported; } + bool isMoved() const { return K == Moved; } + + static RegionState getReported() { return RegionState(Reported); } + static RegionState getMoved() { return RegionState(Moved); } + + bool operator==(const RegionState &X) const { return K == X.K; } + void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); } +}; + +class MoveChecker + : public Checker { +public: + void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; + void checkPreCall(const CallEvent &MC, CheckerContext &C) const; + void checkPostCall(const CallEvent &MC, CheckerContext &C) const; + void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; + ProgramStateRef + checkRegionChanges(ProgramStateRef State, + const InvalidatedSymbols *Invalidated, + ArrayRef ExplicitRegions, + ArrayRef Regions, + const LocationContext *LCtx, const CallEvent *Call) const; + void printState(raw_ostream &Out, ProgramStateRef State, + const char *NL, const char *Sep) const override; + +private: + enum MisuseKind {MK_FunCall, MK_Copy, MK_Move}; + class MovedBugVisitor : public BugReporterVisitor { + public: + MovedBugVisitor(const MemRegion *R) : Region(R), Found(false) {} + + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int X = 0; + ID.AddPointer(&X); + ID.AddPointer(Region); + } + + std::shared_ptr VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; + + private: + // The tracked region. + const MemRegion *Region; + bool Found; + }; + + mutable std::unique_ptr BT; + ExplodedNode *reportBug(const MemRegion *Region, const CallEvent &Call, + CheckerContext &C, MisuseKind MK) const; + bool isInMoveSafeContext(const LocationContext *LC) const; + bool isStateResetMethod(const CXXMethodDecl *MethodDec) const; + bool isMoveSafeMethod(const CXXMethodDecl *MethodDec) const; + const ExplodedNode *getMoveLocation(const ExplodedNode *N, + const MemRegion *Region, + CheckerContext &C) const; +}; +} // end anonymous namespace + +REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, RegionState) + +// If a region is removed all of the subregions needs to be removed too. +static ProgramStateRef removeFromState(ProgramStateRef State, + const MemRegion *Region) { + if (!Region) + return State; + for (auto &E : State->get()) { + if (E.first->isSubRegionOf(Region)) + State = State->remove(E.first); + } + return State; +} + +static bool isAnyBaseRegionReported(ProgramStateRef State, + const MemRegion *Region) { + for (auto &E : State->get()) { + if (Region->isSubRegionOf(E.first) && E.second.isReported()) + return true; + } + return false; +} + +std::shared_ptr +MoveChecker::MovedBugVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, BugReport &) { + // We need only the last move of the reported object's region. + // The visitor walks the ExplodedGraph backwards. + if (Found) + return nullptr; + ProgramStateRef State = N->getState(); + ProgramStateRef StatePrev = N->getFirstPred()->getState(); + const RegionState *TrackedObject = State->get(Region); + const RegionState *TrackedObjectPrev = + StatePrev->get(Region); + if (!TrackedObject) + return nullptr; + if (TrackedObjectPrev && TrackedObject) + return nullptr; + + // Retrieve the associated statement. + const Stmt *S = PathDiagnosticLocation::getStmt(N); + if (!S) + return nullptr; + Found = true; + + std::string ObjectName; + if (const auto DecReg = Region->getAs()) { + const auto *RegionDecl = dyn_cast(DecReg->getDecl()); + ObjectName = RegionDecl->getNameAsString(); + } + std::string InfoText; + if (ObjectName != "") + InfoText = "'" + ObjectName + "' became 'moved-from' here"; + else + InfoText = "Became 'moved-from' here"; + + // Generate the extra diagnostic. + PathDiagnosticLocation Pos(S, BRC.getSourceManager(), + N->getLocationContext()); + return std::make_shared(Pos, InfoText, true); +} + +const ExplodedNode *MoveChecker::getMoveLocation(const ExplodedNode *N, + const MemRegion *Region, + CheckerContext &C) const { + // Walk the ExplodedGraph backwards and find the first node that referred to + // the tracked region. + const ExplodedNode *MoveNode = N; + + while (N) { + ProgramStateRef State = N->getState(); + if (!State->get(Region)) + break; + MoveNode = N; + N = N->pred_empty() ? nullptr : *(N->pred_begin()); + } + return MoveNode; +} + +ExplodedNode *MoveChecker::reportBug(const MemRegion *Region, + const CallEvent &Call, CheckerContext &C, + MisuseKind MK) const { + if (ExplodedNode *N = C.generateNonFatalErrorNode()) { + if (!BT) + BT.reset(new BugType(this, "Usage of a 'moved-from' object", + "C++ move semantics")); + + // Uniqueing report to the same object. + PathDiagnosticLocation LocUsedForUniqueing; + const ExplodedNode *MoveNode = getMoveLocation(N, Region, C); + + if (const Stmt *MoveStmt = PathDiagnosticLocation::getStmt(MoveNode)) + LocUsedForUniqueing = PathDiagnosticLocation::createBegin( + MoveStmt, C.getSourceManager(), MoveNode->getLocationContext()); + + // Creating the error message. + std::string ErrorMessage; + switch(MK) { + case MK_FunCall: + ErrorMessage = "Method call on a 'moved-from' object"; + break; + case MK_Copy: + ErrorMessage = "Copying a 'moved-from' object"; + break; + case MK_Move: + ErrorMessage = "Moving a 'moved-from' object"; + break; + } + if (const auto DecReg = Region->getAs()) { + const auto *RegionDecl = dyn_cast(DecReg->getDecl()); + ErrorMessage += " '" + RegionDecl->getNameAsString() + "'"; + } + + auto R = + llvm::make_unique(*BT, ErrorMessage, N, LocUsedForUniqueing, + MoveNode->getLocationContext()->getDecl()); + R->addVisitor(llvm::make_unique(Region)); + C.emitReport(std::move(R)); + return N; + } + return nullptr; +} + +// Removing the function parameters' MemRegion from the state. This is needed +// for PODs where the trivial destructor does not even created nor executed. +void MoveChecker::checkEndFunction(const ReturnStmt *RS, + CheckerContext &C) const { + auto State = C.getState(); + TrackedRegionMapTy Objects = State->get(); + if (Objects.isEmpty()) + return; + + auto LC = C.getLocationContext(); + + const auto LD = dyn_cast_or_null(LC->getDecl()); + if (!LD) + return; + llvm::SmallSet InvalidRegions; + + for (auto Param : LD->parameters()) { + auto Type = Param->getType().getTypePtrOrNull(); + if (!Type) + continue; + if (!Type->isPointerType() && !Type->isReferenceType()) { + InvalidRegions.insert(State->getLValue(Param, LC).getAsRegion()); + } + } + + if (InvalidRegions.empty()) + return; + + for (const auto &E : State->get()) { + if (InvalidRegions.count(E.first->getBaseRegion())) + State = State->remove(E.first); + } + + C.addTransition(State); +} + +void MoveChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + const auto *AFC = dyn_cast(&Call); + if (!AFC) + return; + + ProgramStateRef State = C.getState(); + const auto MethodDecl = dyn_cast_or_null(AFC->getDecl()); + if (!MethodDecl) + return; + + const auto *ConstructorDecl = dyn_cast(MethodDecl); + + const auto *CC = dyn_cast_or_null(&Call); + // Check if an object became moved-from. + // Object can become moved from after a call to move assignment operator or + // move constructor . + if (ConstructorDecl && !ConstructorDecl->isMoveConstructor()) + return; + + if (!ConstructorDecl && !MethodDecl->isMoveAssignmentOperator()) + return; + + const auto ArgRegion = AFC->getArgSVal(0).getAsRegion(); + if (!ArgRegion) + return; + + // Skip moving the object to itself. + if (CC && CC->getCXXThisVal().getAsRegion() == ArgRegion) + return; + if (const auto *IC = dyn_cast(AFC)) + if (IC->getCXXThisVal().getAsRegion() == ArgRegion) + return; + + const MemRegion *BaseRegion = ArgRegion->getBaseRegion(); + // Skip temp objects because of their short lifetime. + if (BaseRegion->getAs() || + AFC->getArgExpr(0)->isRValue()) + return; + // If it has already been reported do not need to modify the state. + + if (State->get(ArgRegion)) + return; + // Mark object as moved-from. + State = State->set(ArgRegion, RegionState::getMoved()); + C.addTransition(State); +} + +bool MoveChecker::isMoveSafeMethod(const CXXMethodDecl *MethodDec) const { + // We abandon the cases where bool/void/void* conversion happens. + if (const auto *ConversionDec = + dyn_cast_or_null(MethodDec)) { + const Type *Tp = ConversionDec->getConversionType().getTypePtrOrNull(); + if (!Tp) + return false; + if (Tp->isBooleanType() || Tp->isVoidType() || Tp->isVoidPointerType()) + return true; + } + // Function call `empty` can be skipped. + return (MethodDec && MethodDec->getDeclName().isIdentifier() && + (MethodDec->getName().lower() == "empty" || + MethodDec->getName().lower() == "isempty")); +} + +bool MoveChecker::isStateResetMethod(const CXXMethodDecl *MethodDec) const { + if (!MethodDec) + return false; + if (MethodDec->hasAttr()) + return true; + if (MethodDec->getDeclName().isIdentifier()) { + std::string MethodName = MethodDec->getName().lower(); + if (MethodName == "reset" || MethodName == "clear" || + MethodName == "destroy") + return true; + } + return false; +} + +// Don't report an error inside a move related operation. +// We assume that the programmer knows what she does. +bool MoveChecker::isInMoveSafeContext(const LocationContext *LC) const { + do { + const auto *CtxDec = LC->getDecl(); + auto *CtorDec = dyn_cast_or_null(CtxDec); + auto *DtorDec = dyn_cast_or_null(CtxDec); + auto *MethodDec = dyn_cast_or_null(CtxDec); + if (DtorDec || (CtorDec && CtorDec->isCopyOrMoveConstructor()) || + (MethodDec && MethodDec->isOverloadedOperator() && + MethodDec->getOverloadedOperator() == OO_Equal) || + isStateResetMethod(MethodDec) || isMoveSafeMethod(MethodDec)) + return true; + } while ((LC = LC->getParent())); + return false; +} + +void MoveChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { + ProgramStateRef State = C.getState(); + const LocationContext *LC = C.getLocationContext(); + ExplodedNode *N = nullptr; + + // Remove the MemRegions from the map on which a ctor/dtor call or assignment + // happened. + + // Checking constructor calls. + if (const auto *CC = dyn_cast(&Call)) { + State = removeFromState(State, CC->getCXXThisVal().getAsRegion()); + auto CtorDec = CC->getDecl(); + // Check for copying a moved-from object and report the bug. + if (CtorDec && CtorDec->isCopyOrMoveConstructor()) { + const MemRegion *ArgRegion = CC->getArgSVal(0).getAsRegion(); + const RegionState *ArgState = State->get(ArgRegion); + if (ArgState && ArgState->isMoved()) { + if (!isInMoveSafeContext(LC)) { + if(CtorDec->isMoveConstructor()) + N = reportBug(ArgRegion, Call, C, MK_Move); + else + N = reportBug(ArgRegion, Call, C, MK_Copy); + State = State->set(ArgRegion, + RegionState::getReported()); + } + } + } + C.addTransition(State, N); + return; + } + + const auto IC = dyn_cast(&Call); + if (!IC) + return; + // In case of destructor call we do not track the object anymore. + const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); + if (!ThisRegion) + return; + + if (dyn_cast_or_null(Call.getDecl())) { + State = removeFromState(State, ThisRegion); + C.addTransition(State); + return; + } + + const auto MethodDecl = dyn_cast_or_null(IC->getDecl()); + if (!MethodDecl) + return; + // Checking assignment operators. + bool OperatorEq = MethodDecl->isOverloadedOperator() && + MethodDecl->getOverloadedOperator() == OO_Equal; + // Remove the tracked object for every assignment operator, but report bug + // only for move or copy assignment's argument. + if (OperatorEq) { + State = removeFromState(State, ThisRegion); + if (MethodDecl->isCopyAssignmentOperator() || + MethodDecl->isMoveAssignmentOperator()) { + const RegionState *ArgState = + State->get(IC->getArgSVal(0).getAsRegion()); + if (ArgState && ArgState->isMoved() && !isInMoveSafeContext(LC)) { + const MemRegion *ArgRegion = IC->getArgSVal(0).getAsRegion(); + if(MethodDecl->isMoveAssignmentOperator()) + N = reportBug(ArgRegion, Call, C, MK_Move); + else + N = reportBug(ArgRegion, Call, C, MK_Copy); + State = + State->set(ArgRegion, RegionState::getReported()); + } + } + C.addTransition(State, N); + return; + } + + // The remaining part is check only for method call on a moved-from object. + + // We want to investigate the whole object, not only sub-object of a parent + // class in which the encountered method defined. + while (const auto *BR = dyn_cast(ThisRegion)) + ThisRegion = BR->getSuperRegion(); + + if (isMoveSafeMethod(MethodDecl)) + return; + + if (isStateResetMethod(MethodDecl)) { + State = removeFromState(State, ThisRegion); + C.addTransition(State); + return; + } + + // If it is already reported then we don't report the bug again. + const RegionState *ThisState = State->get(ThisRegion); + if (!(ThisState && ThisState->isMoved())) + return; + + // Don't report it in case if any base region is already reported + if (isAnyBaseRegionReported(State, ThisRegion)) + return; + + if (isInMoveSafeContext(LC)) + return; + + N = reportBug(ThisRegion, Call, C, MK_FunCall); + State = State->set(ThisRegion, RegionState::getReported()); + C.addTransition(State, N); +} + +void MoveChecker::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + TrackedRegionMapTy TrackedRegions = State->get(); + for (TrackedRegionMapTy::value_type E : TrackedRegions) { + const MemRegion *Region = E.first; + bool IsRegDead = !SymReaper.isLiveRegion(Region); + + // Remove the dead regions from the region map. + if (IsRegDead) { + State = State->remove(Region); + } + } + C.addTransition(State); +} + +ProgramStateRef MoveChecker::checkRegionChanges( + ProgramStateRef State, const InvalidatedSymbols *Invalidated, + ArrayRef ExplicitRegions, + ArrayRef Regions, const LocationContext *LCtx, + const CallEvent *Call) const { + // In case of an InstanceCall don't remove the ThisRegion from the GDM since + // it is handled in checkPreCall and checkPostCall. + const MemRegion *ThisRegion = nullptr; + if (const auto *IC = dyn_cast_or_null(Call)) { + ThisRegion = IC->getCXXThisVal().getAsRegion(); + } + + for (const auto *Region : ExplicitRegions) { + if (ThisRegion != Region) + State = removeFromState(State, Region); + } + + return State; +} + +void MoveChecker::printState(raw_ostream &Out, ProgramStateRef State, + const char *NL, const char *Sep) const { + + TrackedRegionMapTy RS = State->get(); + + if (!RS.isEmpty()) { + Out << Sep << "Moved-from objects :" << NL; + for (auto I: RS) { + I.first->dumpToStream(Out); + if (I.second.isMoved()) + Out << ": moved"; + else + Out << ": moved and reported"; + Out << NL; + } + } +} +void ento::registerMoveChecker(CheckerManager &mgr) { + mgr.registerChecker(); +} Index: test/Analysis/MisusedMovedObject.cpp =================================================================== --- test/Analysis/MisusedMovedObject.cpp +++ test/Analysis/MisusedMovedObject.cpp @@ -1,712 +0,0 @@ -// RUN: %clang_cc1 -analyze -analyzer-checker=alpha.cplusplus.MisusedMovedObject -std=c++11 -verify -analyzer-output=text -analyzer-config exploration_strategy=unexplored_first_queue,eagerly-assume=false %s -// RUN: %clang_cc1 -analyze -analyzer-checker=alpha.cplusplus.MisusedMovedObject -std=c++11 -analyzer-config exploration_strategy=dfs,eagerly-assume=false -verify -analyzer-output=text -DDFS=1 %s - -namespace std { - -template -struct remove_reference; - -template -struct remove_reference { typedef _Tp type; }; - -template -struct remove_reference<_Tp &> { typedef _Tp type; }; - -template -struct remove_reference<_Tp &&> { typedef _Tp type; }; - -template -typename remove_reference<_Tp>::type &&move(_Tp &&__t) { - return static_cast::type &&>(__t); -} - -template -_Tp &&forward(typename remove_reference<_Tp>::type &__t) noexcept { - return static_cast<_Tp &&>(__t); -} - -template -void swap(T &a, T &b) { - T c(std::move(a)); - a = std::move(b); - b = std::move(c); -} - -} // namespace std - -class B { -public: - B() = default; - B(const B &) = default; - B(B &&) = default; - B& operator=(const B &q) = default; - void operator=(B &&b) { - return; - } - void foo() { return; } -}; - -class A { - int i; - double d; - -public: - B b; - A(int ii = 42, double dd = 1.0) : d(dd), i(ii), b(B()) {} - void moveconstruct(A &&other) { - std::swap(b, other.b); - std::swap(d, other.d); - std::swap(i, other.i); - return; - } - static A get() { - A v(12, 13); - return v; - } - A(A *a) { - moveconstruct(std::move(*a)); - } - A(const A &other) : i(other.i), d(other.d), b(other.b) {} - A(A &&other) : i(other.i), d(other.d), b(std::move(other.b)) { // expected-note {{'b' became 'moved-from' here}} - } - A(A &&other, char *k) { - moveconstruct(std::move(other)); - } - void operator=(const A &other) { - i = other.i; - d = other.d; - b = other.b; - return; - } - void operator=(A &&other) { - moveconstruct(std::move(other)); - return; - } - int getI() { return i; } - int foo() const; - void bar() const; - void reset(); - void destroy(); - void clear(); - bool empty() const; - bool isEmpty() const; - operator bool() const; -}; - -int bignum(); - -void moveInsideFunctionCall(A a) { - A b = std::move(a); -} -void leftRefCall(A &a) { - a.foo(); -} -void rightRefCall(A &&a) { - a.foo(); -} -void constCopyOrMoveCall(const A a) { - a.foo(); -} - -void copyOrMoveCall(A a) { - a.foo(); -} - -void simpleMoveCtorTest() { - { - A a; - A b = std::move(a); // expected-note {{'a' became 'moved-from' here}} - a.foo(); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} - } - { - A a; - A b = std::move(a); // expected-note {{'a' became 'moved-from' here}} - b = a; // expected-warning {{Copying a 'moved-from' object 'a'}} expected-note {{Copying a 'moved-from' object 'a'}} - } - { - A a; - A b = std::move(a); // expected-note {{'a' became 'moved-from' here}} - b = std::move(a); // expected-warning {{Moving a 'moved-from' object 'a'}} expected-note {{Moving a 'moved-from' object 'a'}} - } -} - -void simpleMoveAssignementTest() { - { - A a; - A b; - b = std::move(a); // expected-note {{'a' became 'moved-from' here}} - a.foo(); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} - } - { - A a; - A b; - b = std::move(a); // expected-note {{'a' became 'moved-from' here}} - A c(a); // expected-warning {{Copying a 'moved-from' object 'a'}} expected-note {{Copying a 'moved-from' object 'a'}} - } - { - A a; - A b; - b = std::move(a); // expected-note {{'a' became 'moved-from' here}} - A c(std::move(a)); // expected-warning {{Moving a 'moved-from' object 'a'}} expected-note {{Moving a 'moved-from' object 'a'}} - } -} - -void moveInInitListTest() { - struct S { - A a; - }; - A a; - S s{std::move(a)}; // expected-note {{'a' became 'moved-from' here}} - a.foo(); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} -} - -// Don't report a bug if the variable was assigned to in the meantime. -void reinitializationTest(int i) { - { - A a; - A b; - b = std::move(a); - a = A(); - a.foo(); - } - { - A a; - if (i == 1) { // expected-note {{Assuming 'i' is not equal to 1}} expected-note {{Taking false branch}} - // expected-note@-1 {{Assuming 'i' is not equal to 1}} expected-note@-1 {{Taking false branch}} - A b; - b = std::move(a); - a = A(); - } - if (i == 2) { // expected-note {{Assuming 'i' is not equal to 2}} expected-note {{Taking false branch}} - //expected-note@-1 {{Assuming 'i' is not equal to 2}} expected-note@-1 {{Taking false branch}} - a.foo(); // no-warning - } - } - { - A a; - if (i == 1) { // expected-note {{Taking false branch}} expected-note {{Taking false branch}} - std::move(a); - } - if (i == 2) { // expected-note {{Taking false branch}} expected-note {{Taking false branch}} - a = A(); - a.foo(); - } - } - // The built-in assignment operator should also be recognized as a - // reinitialization. (std::move() may be called on built-in types in template - // code.) - { - int a1 = 1, a2 = 2; - std::swap(a1, a2); - } - // A std::move() after the assignment makes the variable invalid again. - { - A a; - A b; - b = std::move(a); - a = A(); - b = std::move(a); // expected-note {{'a' became 'moved-from' here}} - a.foo(); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} - } - // If a path exist where we not reinitialize the variable we report a bug. - { - A a; - A b; - b = std::move(a); // expected-note {{'a' became 'moved-from' here}} - if (i < 10) { // expected-note {{Assuming 'i' is >= 10}} expected-note {{Taking false branch}} - a = A(); - } - if (i > 5) { // expected-note {{Taking true branch}} - a.foo(); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} - } - } -} - -// Using decltype on an expression is not a use. -void decltypeIsNotUseTest() { - A a; - // A b(std::move(a)); - decltype(a) other_a; // no-warning -} - -void loopTest() { - { - A a; - for (int i = 0; i < bignum(); i++) { // expected-note {{Loop condition is false. Execution jumps to the end of the function}} - rightRefCall(std::move(a)); // no-warning - } - } - { - A a; - for (int i = 0; i < 2; i++) { // expected-note {{Loop condition is true. Entering loop body}} - //expected-note@-1 {{Loop condition is true. Entering loop body}} - //expected-note@-2 {{Loop condition is false. Execution jumps to the end of the function}} - rightRefCall(std::move(a)); // no-warning - } - } - { - A a; - for (int i = 0; i < bignum(); i++) { // expected-note {{Loop condition is false. Execution jumps to the end of the function}} - leftRefCall(a); // no-warning - } - } - { - A a; - for (int i = 0; i < 2; i++) { // expected-note {{Loop condition is true. Entering loop body}} - //expected-note@-1 {{Loop condition is true. Entering loop body}} - //expected-note@-2 {{Loop condition is false. Execution jumps to the end of the function}} - leftRefCall(a); // no-warning - } - } - { - A a; - for (int i = 0; i < bignum(); i++) { // expected-note {{Loop condition is false. Execution jumps to the end of the function}} - constCopyOrMoveCall(a); // no-warning - } - } - { - A a; - for (int i = 0; i < 2; i++) { // expected-note {{Loop condition is true. Entering loop body}} - //expected-note@-1 {{Loop condition is true. Entering loop body}} - //expected-note@-2 {{Loop condition is false. Execution jumps to the end of the function}} - constCopyOrMoveCall(a); // no-warning - } - } - { - A a; - for (int i = 0; i < bignum(); i++) { // expected-note {{Loop condition is false. Execution jumps to the end of the function}} - moveInsideFunctionCall(a); // no-warning - } - } - { - A a; - for (int i = 0; i < 2; i++) { // expected-note {{Loop condition is true. Entering loop body}} - //expected-note@-1 {{Loop condition is true. Entering loop body}} - //expected-note@-2 {{Loop condition is false. Execution jumps to the end of the function}} - moveInsideFunctionCall(a); // no-warning - } - } - { - A a; - for (int i = 0; i < bignum(); i++) { // expected-note {{Loop condition is false. Execution jumps to the end of the function}} - copyOrMoveCall(a); // no-warning - } - } - { - A a; - for (int i = 0; i < 2; i++) { // expected-note {{Loop condition is true.}} - //expected-note@-1 {{Loop condition is true. Entering loop body}} - //expected-note@-2 {{Loop condition is false. Execution jumps to the end of the function}} - copyOrMoveCall(a); // no-warning - } - } - { - A a; - for (int i = 0; i < bignum(); i++) { // expected-note {{Loop condition is true. Entering loop body}} expected-note {{Loop condition is true. Entering loop body}} - constCopyOrMoveCall(std::move(a)); // expected-warning {{Moving a 'moved-from' object 'a'}} expected-note {{Moving a 'moved-from' object 'a'}} - // expected-note@-1 {{'a' became 'moved-from' here}} - } - } - - // Don't warn if we return after the move. - { - A a; - for (int i = 0; i < 3; ++i) { - a.bar(); - if (a.foo() > 0) { - A b; - b = std::move(a); // no-warning - return; - } - } - } -} - -//report a usage of a moved-from object only at the first use -void uniqueTest(bool cond) { - A a(42, 42.0); - A b; - b = std::move(a); // expected-note {{'a' became 'moved-from' here}} - - if (cond) { // expected-note {{Assuming 'cond' is not equal to 0}} expected-note {{Taking true branch}} - a.foo(); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} - } - if (cond) { - a.bar(); // no-warning - } - - a.bar(); // no-warning -} - -void uniqueTest2() { - A a; - A a1 = std::move(a); // expected-note {{'a' became 'moved-from' here}} - a.foo(); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} - - A a2 = std::move(a); // no-warning - a.foo(); // no-warning -} - -// There are exceptions where we assume in general that the method works fine -//even on moved-from objects. -void moveSafeFunctionsTest() { - A a; - A b = std::move(a); // expected-note {{'a' became 'moved-from' here}} - a.empty(); // no-warning - a.isEmpty(); // no-warning - (void)a; // no-warning - (bool)a; // expected-warning {{expression result unused}} - a.foo(); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} -} - -void moveStateResetFunctionsTest() { - { - A a; - A b = std::move(a); - a.reset(); // no-warning - a.foo(); // no-warning - // Test if resets the state of subregions as well. - a.b.foo(); // no-warning - } - { - A a; - A b = std::move(a); - a.destroy(); // no-warning - a.foo(); // no-warning - } - { - A a; - A b = std::move(a); - a.clear(); // no-warning - a.foo(); // no-warning - a.b.foo(); // no-warning - } -} - -// Moves or uses that occur as part of template arguments. -template -class ClassTemplate { -public: - void foo(A a); -}; - -template -void functionTemplate(A a); - -void templateArgIsNotUseTest() { - { - // A pattern like this occurs in the EXPECT_EQ and ASSERT_EQ macros in - // Google Test. - A a; - ClassTemplate().foo(std::move(a)); // no-warning - } - { - A a; - functionTemplate(std::move(a)); // no-warning - } -} - -// Moves of global variables are not reported. -A global_a; -void globalVariablesTest() { - std::move(global_a); - global_a.foo(); // no-warning -} - -// Moves of member variables. -class memberVariablesTest { - A a; - static A static_a; - - void f() { - A b; - b = std::move(a); // expected-note {{'a' became 'moved-from' here}} - a.foo(); // expected-warning {{Method call on a 'moved-from' object}} expected-note {{Method call on a 'moved-from' object 'a'}} - - b = std::move(static_a); // expected-note {{'static_a' became 'moved-from' here}} - static_a.foo(); // expected-warning {{Method call on a 'moved-from' object 'static_a'}} expected-note {{Method call on a 'moved-from' object 'static_a'}} - } -}; - -void PtrAndArrayTest() { - A *Ptr = new A(1, 1.5); - A Arr[10]; - Arr[2] = std::move(*Ptr); // expected-note {{Became 'moved-from' here}} - (*Ptr).foo(); // expected-warning {{Method call on a 'moved-from' object}} expected-note {{Method call on a 'moved-from' object}} - - Ptr = &Arr[1]; - Arr[3] = std::move(Arr[1]); // expected-note {{Became 'moved-from' here}} - Ptr->foo(); // expected-warning {{Method call on a 'moved-from' object}} expected-note {{Method call on a 'moved-from' object}} - - Arr[3] = std::move(Arr[2]); // expected-note {{Became 'moved-from' here}} - Arr[2].foo(); // expected-warning {{Method call on a 'moved-from' object}} expected-note {{Method call on a 'moved-from' object}} - - Arr[2] = std::move(Arr[3]); // reinitialization - Arr[2].foo(); // no-warning -} - -void exclusiveConditionsTest(bool cond) { - A a; - if (cond) { - A b; - b = std::move(a); - } - if (!cond) { - a.bar(); // no-warning - } -} - -void differentBranchesTest(int i) { - // Don't warn if the use is in a different branch from the move. - { - A a; - if (i > 0) { // expected-note {{Assuming 'i' is > 0}} expected-note {{Taking true branch}} - A b; - b = std::move(a); - } else { - a.foo(); // no-warning - } - } - // Same thing, but with a ternary operator. - { - A a, b; - i > 0 ? (void)(b = std::move(a)) : a.bar(); // no-warning // expected-note {{'?' condition is true}} - } - // A variation on the theme above. - { - A a; -#ifdef DFS - a.foo() > 0 ? a.foo() : A(std::move(a)).foo(); // expected-note {{Assuming the condition is false}} expected-note {{'?' condition is false}} -#else - a.foo() > 0 ? a.foo() : A(std::move(a)).foo(); // expected-note {{Assuming the condition is true}} expected-note {{'?' condition is true}} -#endif - } - // Same thing, but with a switch statement. - { - A a, b; - switch (i) { // expected-note {{Control jumps to 'case 1:'}} - case 1: - b = std::move(a); // no-warning - break; // expected-note {{Execution jumps to the end of the function}} - case 2: - a.foo(); // no-warning - break; - } - } - // However, if there's a fallthrough, we do warn. - { - A a, b; - switch (i) { // expected-note {{Control jumps to 'case 1:'}} - case 1: - b = std::move(a); // expected-note {{'a' became 'moved-from' here}} - case 2: - a.foo(); // expected-warning {{Method call on a 'moved-from' object}} expected-note {{Method call on a 'moved-from' object 'a'}} - break; - } - } -} - -void tempTest() { - A a = A::get(); - A::get().foo(); // no-warning - for (int i = 0; i < bignum(); i++) { - A::get().foo(); // no-warning - } -} - -void interFunTest1(A &a) { - a.bar(); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} -} - -void interFunTest2() { - A a; - A b; - b = std::move(a); // expected-note {{'a' became 'moved-from' here}} - interFunTest1(a); // expected-note {{Calling 'interFunTest1'}} -} - -void foobar(A a, int i); -void foobar(int i, A a); - -void paramEvaluateOrderTest() { - A a; - foobar(std::move(a), a.getI()); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} - // expected-note@-1 {{'a' became 'moved-from' here}} - - //FALSE NEGATIVE since parameters evaluate order is undefined - foobar(a.getI(), std::move(a)); //no-warning -} - -void not_known(A &a); -void not_known(A *a); - -void regionAndPointerEscapeTest() { - { - A a; - A b; - b = std::move(a); - not_known(a); - a.foo(); //no-warning - } - { - A a; - A b; - b = std::move(a); - not_known(&a); - a.foo(); // no-warning - } -} - -// A declaration statement containing multiple declarations sequences the -// initializer expressions. -void declarationSequenceTest() { - { - A a; - A a1 = a, a2 = std::move(a); // no-warning - } - { - A a; - A a1 = std::move(a), a2 = a; // expected-warning {{Copying a 'moved-from' object 'a'}} expected-note {{Copying a 'moved-from' object 'a'}} - // expected-note@-1 {{'a' became 'moved-from' here}} - } -} - -// The logical operators && and || sequence their operands. -void logicalOperatorsSequenceTest() { - { - A a; - if (a.foo() > 0 && A(std::move(a)).foo() > 0) { // expected-note {{Assuming the condition is false}} expected-note {{Assuming the condition is false}} - // expected-note@-1 {{Left side of '&&' is false}} expected-note@-1 {{Left side of '&&' is false}} - //expected-note@-2 {{Taking false branch}} expected-note@-2 {{Taking false branch}} - A().bar(); - } - } - // A variation: Negate the result of the && (which pushes the && further down - // into the AST). - { - A a; - if (!(a.foo() > 0 && A(std::move(a)).foo() > 0)) { // expected-note {{Assuming the condition is false}} expected-note {{Assuming the condition is false}} - // expected-note@-1 {{Left side of '&&' is false}} expected-note@-1 {{Left side of '&&' is false}} - // expected-note@-2 {{Taking true branch}} expected-note@-2 {{Taking true branch}} - A().bar(); - } - } - { - A a; - if (A(std::move(a)).foo() > 0 && a.foo() > 0) { // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} - // expected-note@-1 {{'a' became 'moved-from' here}} expected-note@-1 {{Assuming the condition is true}} expected-note@-1 {{Assuming the condition is false}} - // expected-note@-2 {{Left side of '&&' is false}} expected-note@-2 {{Left side of '&&' is true}} - // expected-note@-3 {{Taking false branch}} - A().bar(); - } - } - { - A a; - if (a.foo() > 0 || A(std::move(a)).foo() > 0) { // expected-note {{Assuming the condition is true}} - //expected-note@-1 {{Left side of '||' is true}} - //expected-note@-2 {{Taking true branch}} - A().bar(); - } - } - { - A a; - if (A(std::move(a)).foo() > 0 || a.foo() > 0) { // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} - // expected-note@-1 {{'a' became 'moved-from' here}} expected-note@-1 {{Assuming the condition is false}} expected-note@-1 {{Left side of '||' is false}} - A().bar(); - } - } -} - -// A range-based for sequences the loop variable declaration before the body. -void forRangeSequencesTest() { - A v[2] = {A(), A()}; - for (A &a : v) { - A b; - b = std::move(a); // no-warning - } -} - -// If a variable is declared in an if statement, the declaration of the variable -// (which is treated like a reinitialization by the check) is sequenced before -// the evaluation of the condition (which constitutes a use). -void ifStmtSequencesDeclAndConditionTest() { - for (int i = 0; i < 3; ++i) { - if (A a = A()) { - A b; - b = std::move(a); // no-warning - } - } -} - -struct C : public A { - [[clang::reinitializes]] void reinit(); -}; - -void subRegionMoveTest() { - { - A a; - B b = std::move(a.b); // expected-note {{'b' became 'moved-from' here}} - a.b.foo(); // expected-warning {{Method call on a 'moved-from' object 'b'}} expected-note {{Method call on a 'moved-from' object 'b'}} - } - { - A a; - A a1 = std::move(a); // expected-note {{Calling move constructor for 'A'}} expected-note {{Returning from move constructor for 'A'}} - a.b.foo(); // expected-warning {{Method call on a 'moved-from' object 'b'}} expected-note {{Method call on a 'moved-from' object 'b'}} - } - // Don't report a misuse if any SuperRegion is already reported. - { - A a; - A a1 = std::move(a); // expected-note {{'a' became 'moved-from' here}} - a.foo(); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} - a.b.foo(); // no-warning - } - { - C c; - C c1 = std::move(c); // expected-note {{'c' became 'moved-from' here}} - c.foo(); // expected-warning {{Method call on a 'moved-from' object 'c'}} expected-note {{Method call on a 'moved-from' object 'c'}} - c.b.foo(); // no-warning - } -} - -void resetSuperClass() { - C c; - C c1 = std::move(c); - c.clear(); - C c2 = c; // no-warning -} - -void resetSuperClass2() { - C c; - C c1 = std::move(c); - c.reinit(); - C c2 = c; // no-warning -} - -void reportSuperClass() { - C c; - C c1 = std::move(c); // expected-note {{'c' became 'moved-from' here}} - c.foo(); // expected-warning {{Method call on a 'moved-from' object 'c'}} expected-note {{Method call on a 'moved-from' object 'c'}} - C c2 = c; // no-warning -} - -struct Empty {}; - -Empty inlinedCall() { - // Used to warn because region 'e' failed to be cleaned up because no symbols - // have ever died during the analysis and the checkDeadSymbols callback - // was skipped entirely. - Empty e{}; - return e; // no-warning -} - -void checkInlinedCallZombies() { - while (true) - inlinedCall(); -} - -void checkLoopZombies() { - while (true) { - Empty e{}; - Empty f = std::move(e); // no-warning - } -} Index: test/Analysis/use-after-move.cpp =================================================================== --- test/Analysis/use-after-move.cpp +++ test/Analysis/use-after-move.cpp @@ -0,0 +1,716 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.cplusplus.Move -verify %s\ +// RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\ +// RUN: -analyzer-config exploration_strategy=unexplored_first_queue +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.cplusplus.Move -verify %s\ +// RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\ +// RUN: -analyzer-config exploration_strategy=dfs -DDFS=1 + +namespace std { + +template +struct remove_reference; + +template +struct remove_reference { typedef _Tp type; }; + +template +struct remove_reference<_Tp &> { typedef _Tp type; }; + +template +struct remove_reference<_Tp &&> { typedef _Tp type; }; + +template +typename remove_reference<_Tp>::type &&move(_Tp &&__t) { + return static_cast::type &&>(__t); +} + +template +_Tp &&forward(typename remove_reference<_Tp>::type &__t) noexcept { + return static_cast<_Tp &&>(__t); +} + +template +void swap(T &a, T &b) { + T c(std::move(a)); + a = std::move(b); + b = std::move(c); +} + +} // namespace std + +class B { +public: + B() = default; + B(const B &) = default; + B(B &&) = default; + B& operator=(const B &q) = default; + void operator=(B &&b) { + return; + } + void foo() { return; } +}; + +class A { + int i; + double d; + +public: + B b; + A(int ii = 42, double dd = 1.0) : d(dd), i(ii), b(B()) {} + void moveconstruct(A &&other) { + std::swap(b, other.b); + std::swap(d, other.d); + std::swap(i, other.i); + return; + } + static A get() { + A v(12, 13); + return v; + } + A(A *a) { + moveconstruct(std::move(*a)); + } + A(const A &other) : i(other.i), d(other.d), b(other.b) {} + A(A &&other) : i(other.i), d(other.d), b(std::move(other.b)) { // expected-note {{'b' became 'moved-from' here}} + } + A(A &&other, char *k) { + moveconstruct(std::move(other)); + } + void operator=(const A &other) { + i = other.i; + d = other.d; + b = other.b; + return; + } + void operator=(A &&other) { + moveconstruct(std::move(other)); + return; + } + int getI() { return i; } + int foo() const; + void bar() const; + void reset(); + void destroy(); + void clear(); + bool empty() const; + bool isEmpty() const; + operator bool() const; +}; + +int bignum(); + +void moveInsideFunctionCall(A a) { + A b = std::move(a); +} +void leftRefCall(A &a) { + a.foo(); +} +void rightRefCall(A &&a) { + a.foo(); +} +void constCopyOrMoveCall(const A a) { + a.foo(); +} + +void copyOrMoveCall(A a) { + a.foo(); +} + +void simpleMoveCtorTest() { + { + A a; + A b = std::move(a); // expected-note {{'a' became 'moved-from' here}} + a.foo(); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} + } + { + A a; + A b = std::move(a); // expected-note {{'a' became 'moved-from' here}} + b = a; // expected-warning {{Copying a 'moved-from' object 'a'}} expected-note {{Copying a 'moved-from' object 'a'}} + } + { + A a; + A b = std::move(a); // expected-note {{'a' became 'moved-from' here}} + b = std::move(a); // expected-warning {{Moving a 'moved-from' object 'a'}} expected-note {{Moving a 'moved-from' object 'a'}} + } +} + +void simpleMoveAssignementTest() { + { + A a; + A b; + b = std::move(a); // expected-note {{'a' became 'moved-from' here}} + a.foo(); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} + } + { + A a; + A b; + b = std::move(a); // expected-note {{'a' became 'moved-from' here}} + A c(a); // expected-warning {{Copying a 'moved-from' object 'a'}} expected-note {{Copying a 'moved-from' object 'a'}} + } + { + A a; + A b; + b = std::move(a); // expected-note {{'a' became 'moved-from' here}} + A c(std::move(a)); // expected-warning {{Moving a 'moved-from' object 'a'}} expected-note {{Moving a 'moved-from' object 'a'}} + } +} + +void moveInInitListTest() { + struct S { + A a; + }; + A a; + S s{std::move(a)}; // expected-note {{'a' became 'moved-from' here}} + a.foo(); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} +} + +// Don't report a bug if the variable was assigned to in the meantime. +void reinitializationTest(int i) { + { + A a; + A b; + b = std::move(a); + a = A(); + a.foo(); + } + { + A a; + if (i == 1) { // expected-note {{Assuming 'i' is not equal to 1}} expected-note {{Taking false branch}} + // expected-note@-1 {{Assuming 'i' is not equal to 1}} expected-note@-1 {{Taking false branch}} + A b; + b = std::move(a); + a = A(); + } + if (i == 2) { // expected-note {{Assuming 'i' is not equal to 2}} expected-note {{Taking false branch}} + //expected-note@-1 {{Assuming 'i' is not equal to 2}} expected-note@-1 {{Taking false branch}} + a.foo(); // no-warning + } + } + { + A a; + if (i == 1) { // expected-note {{Taking false branch}} expected-note {{Taking false branch}} + std::move(a); + } + if (i == 2) { // expected-note {{Taking false branch}} expected-note {{Taking false branch}} + a = A(); + a.foo(); + } + } + // The built-in assignment operator should also be recognized as a + // reinitialization. (std::move() may be called on built-in types in template + // code.) + { + int a1 = 1, a2 = 2; + std::swap(a1, a2); + } + // A std::move() after the assignment makes the variable invalid again. + { + A a; + A b; + b = std::move(a); + a = A(); + b = std::move(a); // expected-note {{'a' became 'moved-from' here}} + a.foo(); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} + } + // If a path exist where we not reinitialize the variable we report a bug. + { + A a; + A b; + b = std::move(a); // expected-note {{'a' became 'moved-from' here}} + if (i < 10) { // expected-note {{Assuming 'i' is >= 10}} expected-note {{Taking false branch}} + a = A(); + } + if (i > 5) { // expected-note {{Taking true branch}} + a.foo(); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} + } + } +} + +// Using decltype on an expression is not a use. +void decltypeIsNotUseTest() { + A a; + // A b(std::move(a)); + decltype(a) other_a; // no-warning +} + +void loopTest() { + { + A a; + for (int i = 0; i < bignum(); i++) { // expected-note {{Loop condition is false. Execution jumps to the end of the function}} + rightRefCall(std::move(a)); // no-warning + } + } + { + A a; + for (int i = 0; i < 2; i++) { // expected-note {{Loop condition is true. Entering loop body}} + //expected-note@-1 {{Loop condition is true. Entering loop body}} + //expected-note@-2 {{Loop condition is false. Execution jumps to the end of the function}} + rightRefCall(std::move(a)); // no-warning + } + } + { + A a; + for (int i = 0; i < bignum(); i++) { // expected-note {{Loop condition is false. Execution jumps to the end of the function}} + leftRefCall(a); // no-warning + } + } + { + A a; + for (int i = 0; i < 2; i++) { // expected-note {{Loop condition is true. Entering loop body}} + //expected-note@-1 {{Loop condition is true. Entering loop body}} + //expected-note@-2 {{Loop condition is false. Execution jumps to the end of the function}} + leftRefCall(a); // no-warning + } + } + { + A a; + for (int i = 0; i < bignum(); i++) { // expected-note {{Loop condition is false. Execution jumps to the end of the function}} + constCopyOrMoveCall(a); // no-warning + } + } + { + A a; + for (int i = 0; i < 2; i++) { // expected-note {{Loop condition is true. Entering loop body}} + //expected-note@-1 {{Loop condition is true. Entering loop body}} + //expected-note@-2 {{Loop condition is false. Execution jumps to the end of the function}} + constCopyOrMoveCall(a); // no-warning + } + } + { + A a; + for (int i = 0; i < bignum(); i++) { // expected-note {{Loop condition is false. Execution jumps to the end of the function}} + moveInsideFunctionCall(a); // no-warning + } + } + { + A a; + for (int i = 0; i < 2; i++) { // expected-note {{Loop condition is true. Entering loop body}} + //expected-note@-1 {{Loop condition is true. Entering loop body}} + //expected-note@-2 {{Loop condition is false. Execution jumps to the end of the function}} + moveInsideFunctionCall(a); // no-warning + } + } + { + A a; + for (int i = 0; i < bignum(); i++) { // expected-note {{Loop condition is false. Execution jumps to the end of the function}} + copyOrMoveCall(a); // no-warning + } + } + { + A a; + for (int i = 0; i < 2; i++) { // expected-note {{Loop condition is true.}} + //expected-note@-1 {{Loop condition is true. Entering loop body}} + //expected-note@-2 {{Loop condition is false. Execution jumps to the end of the function}} + copyOrMoveCall(a); // no-warning + } + } + { + A a; + for (int i = 0; i < bignum(); i++) { // expected-note {{Loop condition is true. Entering loop body}} expected-note {{Loop condition is true. Entering loop body}} + constCopyOrMoveCall(std::move(a)); // expected-warning {{Moving a 'moved-from' object 'a'}} expected-note {{Moving a 'moved-from' object 'a'}} + // expected-note@-1 {{'a' became 'moved-from' here}} + } + } + + // Don't warn if we return after the move. + { + A a; + for (int i = 0; i < 3; ++i) { + a.bar(); + if (a.foo() > 0) { + A b; + b = std::move(a); // no-warning + return; + } + } + } +} + +//report a usage of a moved-from object only at the first use +void uniqueTest(bool cond) { + A a(42, 42.0); + A b; + b = std::move(a); // expected-note {{'a' became 'moved-from' here}} + + if (cond) { // expected-note {{Assuming 'cond' is not equal to 0}} expected-note {{Taking true branch}} + a.foo(); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} + } + if (cond) { + a.bar(); // no-warning + } + + a.bar(); // no-warning +} + +void uniqueTest2() { + A a; + A a1 = std::move(a); // expected-note {{'a' became 'moved-from' here}} + a.foo(); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} + + A a2 = std::move(a); // no-warning + a.foo(); // no-warning +} + +// There are exceptions where we assume in general that the method works fine +//even on moved-from objects. +void moveSafeFunctionsTest() { + A a; + A b = std::move(a); // expected-note {{'a' became 'moved-from' here}} + a.empty(); // no-warning + a.isEmpty(); // no-warning + (void)a; // no-warning + (bool)a; // expected-warning {{expression result unused}} + a.foo(); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} +} + +void moveStateResetFunctionsTest() { + { + A a; + A b = std::move(a); + a.reset(); // no-warning + a.foo(); // no-warning + // Test if resets the state of subregions as well. + a.b.foo(); // no-warning + } + { + A a; + A b = std::move(a); + a.destroy(); // no-warning + a.foo(); // no-warning + } + { + A a; + A b = std::move(a); + a.clear(); // no-warning + a.foo(); // no-warning + a.b.foo(); // no-warning + } +} + +// Moves or uses that occur as part of template arguments. +template +class ClassTemplate { +public: + void foo(A a); +}; + +template +void functionTemplate(A a); + +void templateArgIsNotUseTest() { + { + // A pattern like this occurs in the EXPECT_EQ and ASSERT_EQ macros in + // Google Test. + A a; + ClassTemplate().foo(std::move(a)); // no-warning + } + { + A a; + functionTemplate(std::move(a)); // no-warning + } +} + +// Moves of global variables are not reported. +A global_a; +void globalVariablesTest() { + std::move(global_a); + global_a.foo(); // no-warning +} + +// Moves of member variables. +class memberVariablesTest { + A a; + static A static_a; + + void f() { + A b; + b = std::move(a); // expected-note {{'a' became 'moved-from' here}} + a.foo(); // expected-warning {{Method call on a 'moved-from' object}} expected-note {{Method call on a 'moved-from' object 'a'}} + + b = std::move(static_a); // expected-note {{'static_a' became 'moved-from' here}} + static_a.foo(); // expected-warning {{Method call on a 'moved-from' object 'static_a'}} expected-note {{Method call on a 'moved-from' object 'static_a'}} + } +}; + +void PtrAndArrayTest() { + A *Ptr = new A(1, 1.5); + A Arr[10]; + Arr[2] = std::move(*Ptr); // expected-note {{Became 'moved-from' here}} + (*Ptr).foo(); // expected-warning {{Method call on a 'moved-from' object}} expected-note {{Method call on a 'moved-from' object}} + + Ptr = &Arr[1]; + Arr[3] = std::move(Arr[1]); // expected-note {{Became 'moved-from' here}} + Ptr->foo(); // expected-warning {{Method call on a 'moved-from' object}} expected-note {{Method call on a 'moved-from' object}} + + Arr[3] = std::move(Arr[2]); // expected-note {{Became 'moved-from' here}} + Arr[2].foo(); // expected-warning {{Method call on a 'moved-from' object}} expected-note {{Method call on a 'moved-from' object}} + + Arr[2] = std::move(Arr[3]); // reinitialization + Arr[2].foo(); // no-warning +} + +void exclusiveConditionsTest(bool cond) { + A a; + if (cond) { + A b; + b = std::move(a); + } + if (!cond) { + a.bar(); // no-warning + } +} + +void differentBranchesTest(int i) { + // Don't warn if the use is in a different branch from the move. + { + A a; + if (i > 0) { // expected-note {{Assuming 'i' is > 0}} expected-note {{Taking true branch}} + A b; + b = std::move(a); + } else { + a.foo(); // no-warning + } + } + // Same thing, but with a ternary operator. + { + A a, b; + i > 0 ? (void)(b = std::move(a)) : a.bar(); // no-warning // expected-note {{'?' condition is true}} + } + // A variation on the theme above. + { + A a; +#ifdef DFS + a.foo() > 0 ? a.foo() : A(std::move(a)).foo(); // expected-note {{Assuming the condition is false}} expected-note {{'?' condition is false}} +#else + a.foo() > 0 ? a.foo() : A(std::move(a)).foo(); // expected-note {{Assuming the condition is true}} expected-note {{'?' condition is true}} +#endif + } + // Same thing, but with a switch statement. + { + A a, b; + switch (i) { // expected-note {{Control jumps to 'case 1:'}} + case 1: + b = std::move(a); // no-warning + break; // expected-note {{Execution jumps to the end of the function}} + case 2: + a.foo(); // no-warning + break; + } + } + // However, if there's a fallthrough, we do warn. + { + A a, b; + switch (i) { // expected-note {{Control jumps to 'case 1:'}} + case 1: + b = std::move(a); // expected-note {{'a' became 'moved-from' here}} + case 2: + a.foo(); // expected-warning {{Method call on a 'moved-from' object}} expected-note {{Method call on a 'moved-from' object 'a'}} + break; + } + } +} + +void tempTest() { + A a = A::get(); + A::get().foo(); // no-warning + for (int i = 0; i < bignum(); i++) { + A::get().foo(); // no-warning + } +} + +void interFunTest1(A &a) { + a.bar(); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} +} + +void interFunTest2() { + A a; + A b; + b = std::move(a); // expected-note {{'a' became 'moved-from' here}} + interFunTest1(a); // expected-note {{Calling 'interFunTest1'}} +} + +void foobar(A a, int i); +void foobar(int i, A a); + +void paramEvaluateOrderTest() { + A a; + foobar(std::move(a), a.getI()); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} + // expected-note@-1 {{'a' became 'moved-from' here}} + + //FALSE NEGATIVE since parameters evaluate order is undefined + foobar(a.getI(), std::move(a)); //no-warning +} + +void not_known(A &a); +void not_known(A *a); + +void regionAndPointerEscapeTest() { + { + A a; + A b; + b = std::move(a); + not_known(a); + a.foo(); //no-warning + } + { + A a; + A b; + b = std::move(a); + not_known(&a); + a.foo(); // no-warning + } +} + +// A declaration statement containing multiple declarations sequences the +// initializer expressions. +void declarationSequenceTest() { + { + A a; + A a1 = a, a2 = std::move(a); // no-warning + } + { + A a; + A a1 = std::move(a), a2 = a; // expected-warning {{Copying a 'moved-from' object 'a'}} expected-note {{Copying a 'moved-from' object 'a'}} + // expected-note@-1 {{'a' became 'moved-from' here}} + } +} + +// The logical operators && and || sequence their operands. +void logicalOperatorsSequenceTest() { + { + A a; + if (a.foo() > 0 && A(std::move(a)).foo() > 0) { // expected-note {{Assuming the condition is false}} expected-note {{Assuming the condition is false}} + // expected-note@-1 {{Left side of '&&' is false}} expected-note@-1 {{Left side of '&&' is false}} + //expected-note@-2 {{Taking false branch}} expected-note@-2 {{Taking false branch}} + A().bar(); + } + } + // A variation: Negate the result of the && (which pushes the && further down + // into the AST). + { + A a; + if (!(a.foo() > 0 && A(std::move(a)).foo() > 0)) { // expected-note {{Assuming the condition is false}} expected-note {{Assuming the condition is false}} + // expected-note@-1 {{Left side of '&&' is false}} expected-note@-1 {{Left side of '&&' is false}} + // expected-note@-2 {{Taking true branch}} expected-note@-2 {{Taking true branch}} + A().bar(); + } + } + { + A a; + if (A(std::move(a)).foo() > 0 && a.foo() > 0) { // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} + // expected-note@-1 {{'a' became 'moved-from' here}} expected-note@-1 {{Assuming the condition is true}} expected-note@-1 {{Assuming the condition is false}} + // expected-note@-2 {{Left side of '&&' is false}} expected-note@-2 {{Left side of '&&' is true}} + // expected-note@-3 {{Taking false branch}} + A().bar(); + } + } + { + A a; + if (a.foo() > 0 || A(std::move(a)).foo() > 0) { // expected-note {{Assuming the condition is true}} + //expected-note@-1 {{Left side of '||' is true}} + //expected-note@-2 {{Taking true branch}} + A().bar(); + } + } + { + A a; + if (A(std::move(a)).foo() > 0 || a.foo() > 0) { // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} + // expected-note@-1 {{'a' became 'moved-from' here}} expected-note@-1 {{Assuming the condition is false}} expected-note@-1 {{Left side of '||' is false}} + A().bar(); + } + } +} + +// A range-based for sequences the loop variable declaration before the body. +void forRangeSequencesTest() { + A v[2] = {A(), A()}; + for (A &a : v) { + A b; + b = std::move(a); // no-warning + } +} + +// If a variable is declared in an if statement, the declaration of the variable +// (which is treated like a reinitialization by the check) is sequenced before +// the evaluation of the condition (which constitutes a use). +void ifStmtSequencesDeclAndConditionTest() { + for (int i = 0; i < 3; ++i) { + if (A a = A()) { + A b; + b = std::move(a); // no-warning + } + } +} + +struct C : public A { + [[clang::reinitializes]] void reinit(); +}; + +void subRegionMoveTest() { + { + A a; + B b = std::move(a.b); // expected-note {{'b' became 'moved-from' here}} + a.b.foo(); // expected-warning {{Method call on a 'moved-from' object 'b'}} expected-note {{Method call on a 'moved-from' object 'b'}} + } + { + A a; + A a1 = std::move(a); // expected-note {{Calling move constructor for 'A'}} expected-note {{Returning from move constructor for 'A'}} + a.b.foo(); // expected-warning {{Method call on a 'moved-from' object 'b'}} expected-note {{Method call on a 'moved-from' object 'b'}} + } + // Don't report a misuse if any SuperRegion is already reported. + { + A a; + A a1 = std::move(a); // expected-note {{'a' became 'moved-from' here}} + a.foo(); // expected-warning {{Method call on a 'moved-from' object 'a'}} expected-note {{Method call on a 'moved-from' object 'a'}} + a.b.foo(); // no-warning + } + { + C c; + C c1 = std::move(c); // expected-note {{'c' became 'moved-from' here}} + c.foo(); // expected-warning {{Method call on a 'moved-from' object 'c'}} expected-note {{Method call on a 'moved-from' object 'c'}} + c.b.foo(); // no-warning + } +} + +void resetSuperClass() { + C c; + C c1 = std::move(c); + c.clear(); + C c2 = c; // no-warning +} + +void resetSuperClass2() { + C c; + C c1 = std::move(c); + c.reinit(); + C c2 = c; // no-warning +} + +void reportSuperClass() { + C c; + C c1 = std::move(c); // expected-note {{'c' became 'moved-from' here}} + c.foo(); // expected-warning {{Method call on a 'moved-from' object 'c'}} expected-note {{Method call on a 'moved-from' object 'c'}} + C c2 = c; // no-warning +} + +struct Empty {}; + +Empty inlinedCall() { + // Used to warn because region 'e' failed to be cleaned up because no symbols + // have ever died during the analysis and the checkDeadSymbols callback + // was skipped entirely. + Empty e{}; + return e; // no-warning +} + +void checkInlinedCallZombies() { + while (true) + inlinedCall(); +} + +void checkLoopZombies() { + while (true) { + Empty e{}; + Empty f = std::move(e); // no-warning + } +}