diff options
author | Artem Dergachev <artem.dergachev@gmail.com> | 2018-12-03 22:32:32 +0000 |
---|---|---|
committer | Artem Dergachev <artem.dergachev@gmail.com> | 2018-12-03 22:32:32 +0000 |
commit | 2c5945ca20ae3edf098e146e9901074f4dd97168 (patch) | |
tree | 1e6fb3d5a4b0537e44d2f173b1d6b93b576825d0 /clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp | |
parent | ca3ace55dc8b2269f73ed7b27ff42fe826d6c3e7 (diff) | |
download | bcm5719-llvm-2c5945ca20ae3edf098e146e9901074f4dd97168.tar.gz bcm5719-llvm-2c5945ca20ae3edf098e146e9901074f4dd97168.zip |
[analyzer] Rename MisusedMovedObjectChecker to MoveChecker
This follows the Static Analyzer's tradition to name checkers after
things in which they find bugs, not after bugs they find.
Differential Revision: https://reviews.llvm.org/D54556
llvm-svn: 348201
Diffstat (limited to 'clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp')
-rw-r--r-- | clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp | 512 |
1 files changed, 512 insertions, 0 deletions
diff --git a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp new file mode 100644 index 00000000000..33a89f9ea5b --- /dev/null +++ b/clang/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<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, + 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> +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<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 *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<TrackedRegionMap>(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<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 MoveChecker::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 MoveChecker::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 MoveChecker::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. + 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<ReinitializesAttr>()) + 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<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 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<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 auto *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 MoveChecker::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 MoveChecker::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 (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<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::registerMoveChecker(CheckerManager &mgr) { + mgr.registerChecker<MoveChecker>(); +} |