xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp (revision 2c5945ca20ae3edf098e146e9901074f4dd97168)
1 // MoveChecker.cpp - Check use of moved-from objects. - C++ ---------------===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 //
10 // This defines checker which checks for potential misuses of a moved-from
11 // object. That means method calls on the object or copying it in moved-from
12 // state.
13 //
14 //===----------------------------------------------------------------------===//
15 
16 #include "ClangSACheckers.h"
17 #include "clang/AST/ExprCXX.h"
18 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
19 #include "clang/StaticAnalyzer/Core/Checker.h"
20 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
21 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
22 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
23 
24 using namespace clang;
25 using namespace ento;
26 
27 namespace {
28 
29 struct RegionState {
30 private:
31   enum Kind { Moved, Reported } K;
32   RegionState(Kind InK) : K(InK) {}
33 
34 public:
35   bool isReported() const { return K == Reported; }
36   bool isMoved() const { return K == Moved; }
37 
38   static RegionState getReported() { return RegionState(Reported); }
39   static RegionState getMoved() { return RegionState(Moved); }
40 
41   bool operator==(const RegionState &X) const { return K == X.K; }
42   void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); }
43 };
44 
45 class MoveChecker
46     : public Checker<check::PreCall, check::PostCall, check::EndFunction,
47                      check::DeadSymbols, check::RegionChanges> {
48 public:
49   void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
50   void checkPreCall(const CallEvent &MC, CheckerContext &C) const;
51   void checkPostCall(const CallEvent &MC, CheckerContext &C) const;
52   void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
53   ProgramStateRef
54   checkRegionChanges(ProgramStateRef State,
55                      const InvalidatedSymbols *Invalidated,
56                      ArrayRef<const MemRegion *> ExplicitRegions,
57                      ArrayRef<const MemRegion *> Regions,
58                      const LocationContext *LCtx, const CallEvent *Call) const;
59   void printState(raw_ostream &Out, ProgramStateRef State,
60                   const char *NL, const char *Sep) const override;
61 
62 private:
63   enum MisuseKind {MK_FunCall, MK_Copy, MK_Move};
64   class MovedBugVisitor : public BugReporterVisitor {
65   public:
66     MovedBugVisitor(const MemRegion *R) : Region(R), Found(false) {}
67 
68     void Profile(llvm::FoldingSetNodeID &ID) const override {
69       static int X = 0;
70       ID.AddPointer(&X);
71       ID.AddPointer(Region);
72     }
73 
74     std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
75                                                    BugReporterContext &BRC,
76                                                    BugReport &BR) override;
77 
78   private:
79     // The tracked region.
80     const MemRegion *Region;
81     bool Found;
82   };
83 
84   mutable std::unique_ptr<BugType> BT;
85   ExplodedNode *reportBug(const MemRegion *Region, const CallEvent &Call,
86                           CheckerContext &C, MisuseKind MK) const;
87   bool isInMoveSafeContext(const LocationContext *LC) const;
88   bool isStateResetMethod(const CXXMethodDecl *MethodDec) const;
89   bool isMoveSafeMethod(const CXXMethodDecl *MethodDec) const;
90   const ExplodedNode *getMoveLocation(const ExplodedNode *N,
91                                       const MemRegion *Region,
92                                       CheckerContext &C) const;
93 };
94 } // end anonymous namespace
95 
96 REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, RegionState)
97 
98 // If a region is removed all of the subregions needs to be removed too.
99 static ProgramStateRef removeFromState(ProgramStateRef State,
100                                        const MemRegion *Region) {
101   if (!Region)
102     return State;
103   for (auto &E : State->get<TrackedRegionMap>()) {
104     if (E.first->isSubRegionOf(Region))
105       State = State->remove<TrackedRegionMap>(E.first);
106   }
107   return State;
108 }
109 
110 static bool isAnyBaseRegionReported(ProgramStateRef State,
111                                     const MemRegion *Region) {
112   for (auto &E : State->get<TrackedRegionMap>()) {
113     if (Region->isSubRegionOf(E.first) && E.second.isReported())
114       return true;
115   }
116   return false;
117 }
118 
119 std::shared_ptr<PathDiagnosticPiece>
120 MoveChecker::MovedBugVisitor::VisitNode(const ExplodedNode *N,
121                                         BugReporterContext &BRC, BugReport &) {
122   // We need only the last move of the reported object's region.
123   // The visitor walks the ExplodedGraph backwards.
124   if (Found)
125     return nullptr;
126   ProgramStateRef State = N->getState();
127   ProgramStateRef StatePrev = N->getFirstPred()->getState();
128   const RegionState *TrackedObject = State->get<TrackedRegionMap>(Region);
129   const RegionState *TrackedObjectPrev =
130       StatePrev->get<TrackedRegionMap>(Region);
131   if (!TrackedObject)
132     return nullptr;
133   if (TrackedObjectPrev && TrackedObject)
134     return nullptr;
135 
136   // Retrieve the associated statement.
137   const Stmt *S = PathDiagnosticLocation::getStmt(N);
138   if (!S)
139     return nullptr;
140   Found = true;
141 
142   std::string ObjectName;
143   if (const auto DecReg = Region->getAs<DeclRegion>()) {
144     const auto *RegionDecl = dyn_cast<NamedDecl>(DecReg->getDecl());
145     ObjectName = RegionDecl->getNameAsString();
146   }
147   std::string InfoText;
148   if (ObjectName != "")
149     InfoText = "'" + ObjectName + "' became 'moved-from' here";
150   else
151     InfoText = "Became 'moved-from' here";
152 
153   // Generate the extra diagnostic.
154   PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
155                              N->getLocationContext());
156   return std::make_shared<PathDiagnosticEventPiece>(Pos, InfoText, true);
157 }
158 
159 const ExplodedNode *MoveChecker::getMoveLocation(const ExplodedNode *N,
160                                                  const MemRegion *Region,
161                                                  CheckerContext &C) const {
162   // Walk the ExplodedGraph backwards and find the first node that referred to
163   // the tracked region.
164   const ExplodedNode *MoveNode = N;
165 
166   while (N) {
167     ProgramStateRef State = N->getState();
168     if (!State->get<TrackedRegionMap>(Region))
169       break;
170     MoveNode = N;
171     N = N->pred_empty() ? nullptr : *(N->pred_begin());
172   }
173   return MoveNode;
174 }
175 
176 ExplodedNode *MoveChecker::reportBug(const MemRegion *Region,
177                                      const CallEvent &Call, CheckerContext &C,
178                                      MisuseKind MK) const {
179   if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
180     if (!BT)
181       BT.reset(new BugType(this, "Usage of a 'moved-from' object",
182                            "C++ move semantics"));
183 
184     // Uniqueing report to the same object.
185     PathDiagnosticLocation LocUsedForUniqueing;
186     const ExplodedNode *MoveNode = getMoveLocation(N, Region, C);
187 
188     if (const Stmt *MoveStmt = PathDiagnosticLocation::getStmt(MoveNode))
189       LocUsedForUniqueing = PathDiagnosticLocation::createBegin(
190           MoveStmt, C.getSourceManager(), MoveNode->getLocationContext());
191 
192     // Creating the error message.
193     std::string ErrorMessage;
194     switch(MK) {
195       case MK_FunCall:
196         ErrorMessage = "Method call on a 'moved-from' object";
197         break;
198       case MK_Copy:
199         ErrorMessage = "Copying a 'moved-from' object";
200         break;
201       case MK_Move:
202         ErrorMessage = "Moving a 'moved-from' object";
203         break;
204     }
205     if (const auto DecReg = Region->getAs<DeclRegion>()) {
206       const auto *RegionDecl = dyn_cast<NamedDecl>(DecReg->getDecl());
207       ErrorMessage += " '" + RegionDecl->getNameAsString() + "'";
208     }
209 
210     auto R =
211         llvm::make_unique<BugReport>(*BT, ErrorMessage, N, LocUsedForUniqueing,
212                                      MoveNode->getLocationContext()->getDecl());
213     R->addVisitor(llvm::make_unique<MovedBugVisitor>(Region));
214     C.emitReport(std::move(R));
215     return N;
216   }
217   return nullptr;
218 }
219 
220 // Removing the function parameters' MemRegion from the state. This is needed
221 // for PODs where the trivial destructor does not even created nor executed.
222 void MoveChecker::checkEndFunction(const ReturnStmt *RS,
223                                    CheckerContext &C) const {
224   auto State = C.getState();
225   TrackedRegionMapTy Objects = State->get<TrackedRegionMap>();
226   if (Objects.isEmpty())
227     return;
228 
229   auto LC = C.getLocationContext();
230 
231   const auto LD = dyn_cast_or_null<FunctionDecl>(LC->getDecl());
232   if (!LD)
233     return;
234   llvm::SmallSet<const MemRegion *, 8> InvalidRegions;
235 
236   for (auto Param : LD->parameters()) {
237     auto Type = Param->getType().getTypePtrOrNull();
238     if (!Type)
239       continue;
240     if (!Type->isPointerType() && !Type->isReferenceType()) {
241       InvalidRegions.insert(State->getLValue(Param, LC).getAsRegion());
242     }
243   }
244 
245   if (InvalidRegions.empty())
246     return;
247 
248   for (const auto &E : State->get<TrackedRegionMap>()) {
249     if (InvalidRegions.count(E.first->getBaseRegion()))
250       State = State->remove<TrackedRegionMap>(E.first);
251   }
252 
253   C.addTransition(State);
254 }
255 
256 void MoveChecker::checkPostCall(const CallEvent &Call,
257                                 CheckerContext &C) const {
258   const auto *AFC = dyn_cast<AnyFunctionCall>(&Call);
259   if (!AFC)
260     return;
261 
262   ProgramStateRef State = C.getState();
263   const auto MethodDecl = dyn_cast_or_null<CXXMethodDecl>(AFC->getDecl());
264   if (!MethodDecl)
265     return;
266 
267   const auto *ConstructorDecl = dyn_cast<CXXConstructorDecl>(MethodDecl);
268 
269   const auto *CC = dyn_cast_or_null<CXXConstructorCall>(&Call);
270   // Check if an object became moved-from.
271   // Object can become moved from after a call to move assignment operator or
272   // move constructor .
273   if (ConstructorDecl && !ConstructorDecl->isMoveConstructor())
274     return;
275 
276   if (!ConstructorDecl && !MethodDecl->isMoveAssignmentOperator())
277     return;
278 
279   const auto ArgRegion = AFC->getArgSVal(0).getAsRegion();
280   if (!ArgRegion)
281     return;
282 
283   // Skip moving the object to itself.
284   if (CC && CC->getCXXThisVal().getAsRegion() == ArgRegion)
285     return;
286   if (const auto *IC = dyn_cast<CXXInstanceCall>(AFC))
287     if (IC->getCXXThisVal().getAsRegion() == ArgRegion)
288       return;
289 
290   const MemRegion *BaseRegion = ArgRegion->getBaseRegion();
291   // Skip temp objects because of their short lifetime.
292   if (BaseRegion->getAs<CXXTempObjectRegion>() ||
293       AFC->getArgExpr(0)->isRValue())
294     return;
295   // If it has already been reported do not need to modify the state.
296 
297   if (State->get<TrackedRegionMap>(ArgRegion))
298     return;
299   // Mark object as moved-from.
300   State = State->set<TrackedRegionMap>(ArgRegion, RegionState::getMoved());
301   C.addTransition(State);
302 }
303 
304 bool MoveChecker::isMoveSafeMethod(const CXXMethodDecl *MethodDec) const {
305   // We abandon the cases where bool/void/void* conversion happens.
306   if (const auto *ConversionDec =
307           dyn_cast_or_null<CXXConversionDecl>(MethodDec)) {
308     const Type *Tp = ConversionDec->getConversionType().getTypePtrOrNull();
309     if (!Tp)
310       return false;
311     if (Tp->isBooleanType() || Tp->isVoidType() || Tp->isVoidPointerType())
312       return true;
313   }
314   // Function call `empty` can be skipped.
315   return (MethodDec && MethodDec->getDeclName().isIdentifier() &&
316       (MethodDec->getName().lower() == "empty" ||
317        MethodDec->getName().lower() == "isempty"));
318 }
319 
320 bool MoveChecker::isStateResetMethod(const CXXMethodDecl *MethodDec) const {
321   if (!MethodDec)
322       return false;
323   if (MethodDec->hasAttr<ReinitializesAttr>())
324       return true;
325   if (MethodDec->getDeclName().isIdentifier()) {
326     std::string MethodName = MethodDec->getName().lower();
327     if (MethodName == "reset" || MethodName == "clear" ||
328         MethodName == "destroy")
329       return true;
330   }
331   return false;
332 }
333 
334 // Don't report an error inside a move related operation.
335 // We assume that the programmer knows what she does.
336 bool MoveChecker::isInMoveSafeContext(const LocationContext *LC) const {
337   do {
338     const auto *CtxDec = LC->getDecl();
339     auto *CtorDec = dyn_cast_or_null<CXXConstructorDecl>(CtxDec);
340     auto *DtorDec = dyn_cast_or_null<CXXDestructorDecl>(CtxDec);
341     auto *MethodDec = dyn_cast_or_null<CXXMethodDecl>(CtxDec);
342     if (DtorDec || (CtorDec && CtorDec->isCopyOrMoveConstructor()) ||
343         (MethodDec && MethodDec->isOverloadedOperator() &&
344          MethodDec->getOverloadedOperator() == OO_Equal) ||
345         isStateResetMethod(MethodDec) || isMoveSafeMethod(MethodDec))
346       return true;
347   } while ((LC = LC->getParent()));
348   return false;
349 }
350 
351 void MoveChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const {
352   ProgramStateRef State = C.getState();
353   const LocationContext *LC = C.getLocationContext();
354   ExplodedNode *N = nullptr;
355 
356   // Remove the MemRegions from the map on which a ctor/dtor call or assignment
357   // happened.
358 
359   // Checking constructor calls.
360   if (const auto *CC = dyn_cast<CXXConstructorCall>(&Call)) {
361     State = removeFromState(State, CC->getCXXThisVal().getAsRegion());
362     auto CtorDec = CC->getDecl();
363     // Check for copying a moved-from object and report the bug.
364     if (CtorDec && CtorDec->isCopyOrMoveConstructor()) {
365       const MemRegion *ArgRegion = CC->getArgSVal(0).getAsRegion();
366       const RegionState *ArgState = State->get<TrackedRegionMap>(ArgRegion);
367       if (ArgState && ArgState->isMoved()) {
368         if (!isInMoveSafeContext(LC)) {
369           if(CtorDec->isMoveConstructor())
370             N = reportBug(ArgRegion, Call, C, MK_Move);
371           else
372             N = reportBug(ArgRegion, Call, C, MK_Copy);
373           State = State->set<TrackedRegionMap>(ArgRegion,
374                                                RegionState::getReported());
375         }
376       }
377     }
378     C.addTransition(State, N);
379     return;
380   }
381 
382   const auto IC = dyn_cast<CXXInstanceCall>(&Call);
383   if (!IC)
384     return;
385   // In case of destructor call we do not track the object anymore.
386   const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
387   if (!ThisRegion)
388     return;
389 
390   if (dyn_cast_or_null<CXXDestructorDecl>(Call.getDecl())) {
391     State = removeFromState(State, ThisRegion);
392     C.addTransition(State);
393     return;
394   }
395 
396   const auto MethodDecl = dyn_cast_or_null<CXXMethodDecl>(IC->getDecl());
397   if (!MethodDecl)
398     return;
399   // Checking assignment operators.
400   bool OperatorEq = MethodDecl->isOverloadedOperator() &&
401                     MethodDecl->getOverloadedOperator() == OO_Equal;
402   // Remove the tracked object for every assignment operator, but report bug
403   // only for move or copy assignment's argument.
404   if (OperatorEq) {
405     State = removeFromState(State, ThisRegion);
406     if (MethodDecl->isCopyAssignmentOperator() ||
407         MethodDecl->isMoveAssignmentOperator()) {
408       const RegionState *ArgState =
409           State->get<TrackedRegionMap>(IC->getArgSVal(0).getAsRegion());
410       if (ArgState && ArgState->isMoved() && !isInMoveSafeContext(LC)) {
411         const MemRegion *ArgRegion = IC->getArgSVal(0).getAsRegion();
412         if(MethodDecl->isMoveAssignmentOperator())
413           N = reportBug(ArgRegion, Call, C, MK_Move);
414         else
415           N = reportBug(ArgRegion, Call, C, MK_Copy);
416         State =
417             State->set<TrackedRegionMap>(ArgRegion, RegionState::getReported());
418       }
419     }
420     C.addTransition(State, N);
421     return;
422   }
423 
424   // The remaining part is check only for method call on a moved-from object.
425 
426   // We want to investigate the whole object, not only sub-object of a parent
427   // class in which the encountered method defined.
428   while (const auto *BR = dyn_cast<CXXBaseObjectRegion>(ThisRegion))
429     ThisRegion = BR->getSuperRegion();
430 
431   if (isMoveSafeMethod(MethodDecl))
432     return;
433 
434   if (isStateResetMethod(MethodDecl)) {
435     State = removeFromState(State, ThisRegion);
436     C.addTransition(State);
437     return;
438   }
439 
440   // If it is already reported then we don't report the bug again.
441   const RegionState *ThisState = State->get<TrackedRegionMap>(ThisRegion);
442   if (!(ThisState && ThisState->isMoved()))
443     return;
444 
445   // Don't report it in case if any base region is already reported
446   if (isAnyBaseRegionReported(State, ThisRegion))
447     return;
448 
449   if (isInMoveSafeContext(LC))
450     return;
451 
452   N = reportBug(ThisRegion, Call, C, MK_FunCall);
453   State = State->set<TrackedRegionMap>(ThisRegion, RegionState::getReported());
454   C.addTransition(State, N);
455 }
456 
457 void MoveChecker::checkDeadSymbols(SymbolReaper &SymReaper,
458                                    CheckerContext &C) const {
459   ProgramStateRef State = C.getState();
460   TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>();
461   for (TrackedRegionMapTy::value_type E : TrackedRegions) {
462     const MemRegion *Region = E.first;
463     bool IsRegDead = !SymReaper.isLiveRegion(Region);
464 
465     // Remove the dead regions from the region map.
466     if (IsRegDead) {
467       State = State->remove<TrackedRegionMap>(Region);
468     }
469   }
470   C.addTransition(State);
471 }
472 
473 ProgramStateRef MoveChecker::checkRegionChanges(
474     ProgramStateRef State, const InvalidatedSymbols *Invalidated,
475     ArrayRef<const MemRegion *> ExplicitRegions,
476     ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx,
477     const CallEvent *Call) const {
478   // In case of an InstanceCall don't remove the ThisRegion from the GDM since
479   // it is handled in checkPreCall and checkPostCall.
480   const MemRegion *ThisRegion = nullptr;
481   if (const auto *IC = dyn_cast_or_null<CXXInstanceCall>(Call)) {
482     ThisRegion = IC->getCXXThisVal().getAsRegion();
483   }
484 
485   for (const auto *Region : ExplicitRegions) {
486     if (ThisRegion != Region)
487       State = removeFromState(State, Region);
488   }
489 
490   return State;
491 }
492 
493 void MoveChecker::printState(raw_ostream &Out, ProgramStateRef State,
494                              const char *NL, const char *Sep) const {
495 
496   TrackedRegionMapTy RS = State->get<TrackedRegionMap>();
497 
498   if (!RS.isEmpty()) {
499     Out << Sep << "Moved-from objects :" << NL;
500     for (auto I: RS) {
501       I.first->dumpToStream(Out);
502       if (I.second.isMoved())
503         Out << ": moved";
504       else
505         Out << ": moved and reported";
506       Out << NL;
507     }
508   }
509 }
510 void ento::registerMoveChecker(CheckerManager &mgr) {
511   mgr.registerChecker<MoveChecker>();
512 }
513