r298698 - [analyzer] Add MisusedMovedObjectChecker for detecting use-after-move errors.

Artem Dergachev via cfe-commits cfe-commits at lists.llvm.org
Fri Mar 24 02:52:31 PDT 2017


Author: dergachev
Date: Fri Mar 24 04:52:30 2017
New Revision: 298698

URL: http://llvm.org/viewvc/llvm-project?rev=298698&view=rev
Log:
[analyzer] Add MisusedMovedObjectChecker for detecting use-after-move errors.

The checker currently warns on copying, moving, or calling methods on an object
that was recently std::move'd from. It understands a set of "state reset"
methods that bring a moved-from object back to a well-specified state.

Patch by Peter Szecsi!

Differential Revision: https://reviews.llvm.org/D24246

Added:
    cfe/trunk/lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp
    cfe/trunk/test/Analysis/MisusedMovedObject.cpp
Modified:
    cfe/trunk/include/clang/StaticAnalyzer/Checkers/Checkers.td
    cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt

Modified: cfe/trunk/include/clang/StaticAnalyzer/Checkers/Checkers.td
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/StaticAnalyzer/Checkers/Checkers.td?rev=298698&r1=298697&r2=298698&view=diff
==============================================================================
--- cfe/trunk/include/clang/StaticAnalyzer/Checkers/Checkers.td (original)
+++ cfe/trunk/include/clang/StaticAnalyzer/Checkers/Checkers.td Fri Mar 24 04:52:30 2017
@@ -279,6 +279,11 @@ def VirtualCallChecker : Checker<"Virtua
 
 let ParentPackage = CplusplusAlpha in {
 
+def MisusedMovedObjectChecker: Checker<"MisusedMovedObject">,
+     HelpText<"Method calls on a moved-from object and copying a moved-from "
+              "object will be reported">,
+     DescFile<"MisusedMovedObjectChecker.cpp">;
+
 def IteratorPastEndChecker : Checker<"IteratorPastEnd">,
   HelpText<"Check iterators used past end">,
   DescFile<"IteratorPastEndChecker.cpp">;

Modified: cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt?rev=298698&r1=298697&r2=298698&view=diff
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt (original)
+++ cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt Fri Mar 24 04:52:30 2017
@@ -48,6 +48,7 @@ add_clang_library(clangStaticAnalyzerChe
   MallocChecker.cpp
   MallocOverflowSecurityChecker.cpp
   MallocSizeofChecker.cpp
+  MisusedMovedObjectChecker.cpp
   MPI-Checker/MPIBugReporter.cpp
   MPI-Checker/MPIChecker.cpp
   MPI-Checker/MPIFunctionClassifier.cpp

Added: cfe/trunk/lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp?rev=298698&view=auto
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp (added)
+++ cfe/trunk/lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp Fri Mar 24 04:52:30 2017
@@ -0,0 +1,488 @@
+// 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(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;
+  bool wantsRegionChangeUpdate(ProgramStateRef State) const;
+  ProgramStateRef
+  checkRegionChanges(ProgramStateRef State,
+                     const InvalidatedSymbols *Invalidated,
+                     ArrayRef<const MemRegion *> ExplicitRegions,
+                     ArrayRef<const MemRegion *> Regions,
+                     const LocationContext *LCtx, const CallEvent *Call) const;
+
+private:
+  class MovedBugVisitor : public BugReporterVisitorImpl<MovedBugVisitor> {
+  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, bool isCopy) 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;
+  // Note: The isSubRegionOf function is not reflexive.
+  State = State->remove<TrackedRegionMap>(Region);
+  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,
+                                                   bool isCopy = false) 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;
+    if (isCopy)
+      ErrorMessage = "Copying a 'moved-from' object";
+    else
+      ErrorMessage = "Method call on a 'moved-from' object";
+    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(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 assignement
+  // 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)) {
+          N = reportBug(ArgRegion, Call, C, /*isCopy=*/true);
+          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 (dyn_cast_or_null<CXXDestructorDecl>(Call.getDecl())) {
+    State = removeFromState(State, IC->getCXXThisVal().getAsRegion());
+    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();
+        N = reportBug(ArgRegion, Call, C, /*isCopy=*/true);
+        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.
+  if (isMoveSafeMethod(MethodDecl))
+    return;
+
+  if (isStateResetMethod(MethodDecl)) {
+    State = State->remove<TrackedRegionMap>(ThisRegion);
+    C.addTransition(State);
+    return;
+  }
+
+  // If it is already reported then we dont report the bug again.
+  const RegionState *ThisState = State->get<TrackedRegionMap>(ThisRegion);
+  if (!(ThisState && ThisState->isMoved()))
+    return;
+
+  // Dont 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);
+  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);
+}
+
+bool MisusedMovedObjectChecker::wantsRegionChangeUpdate(
+    ProgramStateRef State) const {
+  TrackedRegionMapTy Regions = State->get<TrackedRegionMap>();
+  return !Regions.isEmpty();
+}
+
+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 ento::registerMisusedMovedObjectChecker(CheckerManager &mgr) {
+  mgr.registerChecker<MisusedMovedObjectChecker>();
+}

Added: cfe/trunk/test/Analysis/MisusedMovedObject.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/MisusedMovedObject.cpp?rev=298698&view=auto
==============================================================================
--- cfe/trunk/test/Analysis/MisusedMovedObject.cpp (added)
+++ cfe/trunk/test/Analysis/MisusedMovedObject.cpp Fri Mar 24 04:52:30 2017
@@ -0,0 +1,619 @@
+// RUN: %clang_cc1 -analyze -analyzer-checker=alpha.cplusplus.MisusedMovedObject -std=c++11 -verify -analyzer-output=text %s
+
+namespace std {
+
+template <typename>
+struct remove_reference;
+
+template <typename _Tp>
+struct remove_reference { typedef _Tp type; };
+
+template <typename _Tp>
+struct remove_reference<_Tp &> { typedef _Tp type; };
+
+template <typename _Tp>
+struct remove_reference<_Tp &&> { typedef _Tp type; };
+
+template <typename _Tp>
+typename remove_reference<_Tp>::type &&move(_Tp &&__t) {
+  return static_cast<typename remove_reference<_Tp>::type &&>(__t);
+}
+
+template <typename _Tp>
+_Tp &&forward(typename remove_reference<_Tp>::type &__t) noexcept {
+  return static_cast<_Tp &&>(__t);
+}
+
+template <class T>
+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;
+  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=(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;
+  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'}}
+}
+
+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'}}
+}
+
+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 at -1 {{Assuming 'i' is not equal to 1}} expected-note at -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 at -1 {{Assuming 'i' is not equal to 2}} expected-note at -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 at -1 {{Loop condition is true.  Entering loop body}}
+			//expected-note at -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 at -1 {{Loop condition is true.  Entering loop body}}
+			//expected-note at -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 at -1 {{Loop condition is true.  Entering loop body}}
+			//expected-note at -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 at -1 {{Loop condition is true.  Entering loop body}}
+			//expected-note at -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 at -1 {{Loop condition is true.  Entering loop body}}
+			//expected-note at -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 {{Copying a 'moved-from' object 'a'}} expected-note {{Copying a 'moved-from' object 'a'}}
+      // expected-note at -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
+  }
+  {
+    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
+  }
+}
+
+// Moves or uses that occur as part of template arguments.
+template <int>
+class ClassTemplate {
+public:
+  void foo(A a);
+};
+
+template <int>
+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<sizeof(A(std::move(a)))>().foo(std::move(a)); // no-warning
+  }
+  {
+    A a;
+    functionTemplate<sizeof(A(std::move(a)))>(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;
+    a.foo() > 0 ? a.foo() : A(std::move(a)).foo(); // expected-note {{Assuming the condition is false}} expected-note {{'?' condition is false}}
+  }
+  // Same thing, but with a switch statement.
+  {
+    A a, b;
+    switch (i) { // expected-note {{Control jumps to 'case 1:'  at line 448}}
+    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:'  at line 460}}
+    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 at -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 at -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 at -1 {{Left side of '&&' is false}} expected-note at -1 {{Left side of '&&' is false}}
+			//expected-note at -2 {{Taking false branch}} expected-note at -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 at -1 {{Left side of '&&' is false}} expected-note at -1 {{Left side of '&&' is false}}
+      // expected-note at -2 {{Taking true branch}} expected-note at -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 at -1 {{'a' became 'moved-from' here}} expected-note at -1 {{Assuming the condition is true}} expected-note at -1 {{Assuming the condition is false}}
+      // expected-note at -2 {{Left side of '&&' is false}} expected-note at -2 {{Left side of '&&' is true}}
+      // expected-note at -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 at -1 {{Left side of '||' is true}}
+			//expected-note at -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 at -1 {{'a' became 'moved-from' here}} expected-note at -1 {{Assuming the condition is false}} expected-note at -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
+    }
+  }
+}
+
+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
+  }
+}




More information about the cfe-commits mailing list