diff options
Diffstat (limited to 'lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp')
-rw-r--r-- | lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp | 525 |
1 files changed, 0 insertions, 525 deletions
diff --git a/lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp b/lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp deleted file mode 100644 index 19c1d077afa15..0000000000000 --- a/lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp +++ /dev/null @@ -1,525 +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<check::PreCall, check::PostCall, check::EndFunction, - check::DeadSymbols, check::RegionChanges> { -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<const MemRegion *> ExplicitRegions, - ArrayRef<const MemRegion *> 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<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, - BugReporterContext &BRC, - BugReport &BR) override; - - private: - // The tracked region. - const MemRegion *Region; - bool Found; - }; - - mutable std::unique_ptr<BugType> 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<TrackedRegionMap>()) { - if (E.first->isSubRegionOf(Region)) - State = State->remove<TrackedRegionMap>(E.first); - } - return State; -} - -static bool isAnyBaseRegionReported(ProgramStateRef State, - const MemRegion *Region) { - for (auto &E : State->get<TrackedRegionMap>()) { - if (Region->isSubRegionOf(E.first) && E.second.isReported()) - return true; - } - return false; -} - -std::shared_ptr<PathDiagnosticPiece> -MisusedMovedObjectChecker::MovedBugVisitor::VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, - BugReporterContext &BRC, - BugReport &BR) { - // 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 = PrevN->getState(); - const RegionState *TrackedObject = State->get<TrackedRegionMap>(Region); - const RegionState *TrackedObjectPrev = - StatePrev->get<TrackedRegionMap>(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<DeclRegion>()) { - const auto *RegionDecl = dyn_cast<NamedDecl>(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<PathDiagnosticEventPiece>(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<TrackedRegionMap>(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<DeclRegion>()) { - const auto *RegionDecl = dyn_cast<NamedDecl>(DecReg->getDecl()); - ErrorMessage += " '" + RegionDecl->getNameAsString() + "'"; - } - - auto R = - llvm::make_unique<BugReport>(*BT, ErrorMessage, N, LocUsedForUniqueing, - MoveNode->getLocationContext()->getDecl()); - R->addVisitor(llvm::make_unique<MovedBugVisitor>(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<TrackedRegionMap>(); - if (Objects.isEmpty()) - return; - - auto LC = C.getLocationContext(); - - const auto LD = dyn_cast_or_null<FunctionDecl>(LC->getDecl()); - if (!LD) - return; - llvm::SmallSet<const MemRegion *, 8> 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<TrackedRegionMap>()) { - if (InvalidRegions.count(E.first->getBaseRegion())) - State = State->remove<TrackedRegionMap>(E.first); - } - - C.addTransition(State); -} - -void MisusedMovedObjectChecker::checkPostCall(const CallEvent &Call, - CheckerContext &C) const { - const auto *AFC = dyn_cast<AnyFunctionCall>(&Call); - if (!AFC) - return; - - ProgramStateRef State = C.getState(); - const auto MethodDecl = dyn_cast_or_null<CXXMethodDecl>(AFC->getDecl()); - if (!MethodDecl) - return; - - const auto *ConstructorDecl = dyn_cast<CXXConstructorDecl>(MethodDecl); - - const auto *CC = dyn_cast_or_null<CXXConstructorCall>(&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<CXXInstanceCall>(AFC)) - if (IC->getCXXThisVal().getAsRegion() == ArgRegion) - return; - - const MemRegion *BaseRegion = ArgRegion->getBaseRegion(); - // Skip temp objects because of their short lifetime. - if (BaseRegion->getAs<CXXTempObjectRegion>() || - AFC->getArgExpr(0)->isRValue()) - return; - // If it has already been reported do not need to modify the state. - - if (State->get<TrackedRegionMap>(ArgRegion)) - return; - // Mark object as moved-from. - State = State->set<TrackedRegionMap>(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<CXXConversionDecl>(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. - if (MethodDec && MethodDec->getDeclName().isIdentifier() && - (MethodDec->getName().lower() == "empty" || - MethodDec->getName().lower() == "isempty")) - return true; - - return false; -} - -bool MisusedMovedObjectChecker::isStateResetMethod( - const CXXMethodDecl *MethodDec) const { - if (MethodDec && 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<CXXConstructorDecl>(CtxDec); - auto *DtorDec = dyn_cast_or_null<CXXDestructorDecl>(CtxDec); - auto *MethodDec = dyn_cast_or_null<CXXMethodDecl>(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<CXXConstructorCall>(&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<TrackedRegionMap>(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<TrackedRegionMap>(ArgRegion, - RegionState::getReported()); - } - } - } - C.addTransition(State, N); - return; - } - - const auto IC = dyn_cast<CXXInstanceCall>(&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<CXXDestructorDecl>(Call.getDecl())) { - State = removeFromState(State, ThisRegion); - C.addTransition(State); - return; - } - - const auto MethodDecl = dyn_cast_or_null<CXXMethodDecl>(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<TrackedRegionMap>(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<TrackedRegionMap>(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 CXXBaseObjectRegion *BR = - dyn_cast<CXXBaseObjectRegion>(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<TrackedRegionMap>(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<TrackedRegionMap>(ThisRegion, RegionState::getReported()); - C.addTransition(State, N); -} - -void MisusedMovedObjectChecker::checkDeadSymbols(SymbolReaper &SymReaper, - CheckerContext &C) const { - ProgramStateRef State = C.getState(); - TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>(); - 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<TrackedRegionMap>(Region); - } - } - C.addTransition(State); -} - -ProgramStateRef MisusedMovedObjectChecker::checkRegionChanges( - ProgramStateRef State, const InvalidatedSymbols *Invalidated, - ArrayRef<const MemRegion *> ExplicitRegions, - ArrayRef<const MemRegion *> 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<CXXInstanceCall>(Call)) { - ThisRegion = IC->getCXXThisVal().getAsRegion(); - } - - for (ArrayRef<const MemRegion *>::iterator I = ExplicitRegions.begin(), - E = ExplicitRegions.end(); - I != E; ++I) { - const auto *Region = *I; - 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<TrackedRegionMap>(); - - 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<MisusedMovedObjectChecker>(); -} |