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