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