1// MoveChecker.cpp - Check use of moved-from objects. - C++ ---------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This defines checker which checks for potential misuses of a moved-from
10// object. That means method calls on the object or copying it in moved-from
11// state.
12//
13//===----------------------------------------------------------------------===//
14
15#include "Iterator.h"
16#include "Move.h"
17#include "clang/AST/Attr.h"
18#include "clang/AST/ExprCXX.h"
19#include "clang/Basic/OperatorKinds.h"
20#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
21#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
22#include "clang/StaticAnalyzer/Core/Checker.h"
23#include "clang/StaticAnalyzer/Core/CheckerManager.h"
24#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
25#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
26#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
27#include "llvm/ADT/StringSet.h"
28
29using namespace clang;
30using namespace ento;
31using namespace iterator;
32
33namespace {
34struct RegionState {
35private:
36 enum Kind { Moved, Reported } K;
37 RegionState(Kind InK) : K(InK) {}
38
39public:
40 bool isReported() const { return K == Reported; }
41 bool isMoved() const { return K == Moved; }
42
43 static RegionState getReported() { return RegionState(Reported); }
44 static RegionState getMoved() { return RegionState(Moved); }
45
46 bool operator==(const RegionState &X) const { return K == X.K; }
47 void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(I: K); }
48};
49} // end of anonymous namespace
50
51namespace {
52class MoveChecker
53 : public Checker<check::PreCall, check::PostCall, check::DeadSymbols,
54 check::RegionChanges, eval::Call> {
55public:
56 void checkPreCall(const CallEvent &MC, CheckerContext &C) const;
57 void checkPostCall(const CallEvent &MC, CheckerContext &C) const;
58 void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
59 bool evalCall(const CallEvent &Call, CheckerContext &C) const;
60 ProgramStateRef
61 checkRegionChanges(ProgramStateRef State,
62 const InvalidatedSymbols *Invalidated,
63 ArrayRef<const MemRegion *> RequestedRegions,
64 ArrayRef<const MemRegion *> InvalidatedRegions,
65 const StackFrame *SF, const CallEvent *Call) const;
66 void printState(raw_ostream &Out, ProgramStateRef State,
67 const char *NL, const char *Sep) const override;
68
69private:
70 enum MisuseKind { MK_FunCall, MK_Copy, MK_Move, MK_Dereference };
71 enum StdObjectKind { SK_NonStd, SK_Unsafe, SK_Safe, SK_SmartPtr };
72
73 enum AggressivenessKind { // In any case, don't warn after a reset.
74 AK_Invalid = -1,
75 AK_KnownsOnly = 0, // Warn only about known move-unsafe classes.
76 AK_KnownsAndLocals = 1, // Also warn about all local objects.
77 AK_All = 2, // Warn on any use-after-move.
78 AK_NumKinds = AK_All
79 };
80
81 static bool misuseCausesCrash(MisuseKind MK) {
82 return MK == MK_Dereference;
83 }
84
85 struct ObjectKind {
86 // Is this a local variable or a local rvalue reference?
87 bool IsLocal;
88 // Is this an STL object? If so, of what kind?
89 StdObjectKind StdKind;
90 };
91
92 // STL smart pointers are automatically re-initialized to null when moved
93 // from. So we can't warn on many methods, but we can warn when it is
94 // dereferenced, which is UB even if the resulting lvalue never gets read.
95 const llvm::StringSet<> StdSmartPtrClasses = {
96 "shared_ptr",
97 "unique_ptr",
98 "weak_ptr",
99 };
100
101 // Not all of these are entirely move-safe, but they do provide *some*
102 // guarantees, and it means that somebody is using them after move
103 // in a valid manner.
104 // TODO: We can still try to identify *unsafe* use after move,
105 // like we did with smart pointers.
106 const llvm::StringSet<> StdSafeClasses = {
107 "basic_filebuf",
108 "basic_ios",
109 "future",
110 "optional",
111 "packaged_task",
112 "promise",
113 "shared_future",
114 "shared_lock",
115 "thread",
116 "unique_lock",
117 };
118
119 // Should we bother tracking the state of the object?
120 bool shouldBeTracked(ObjectKind OK) const {
121 // In non-aggressive mode, only warn on use-after-move of local variables
122 // (or local rvalue references) and of STL objects. The former is possible
123 // because local variables (or local rvalue references) are not tempting
124 // their user to re-use the storage. The latter is possible because STL
125 // objects are known to end up in a valid but unspecified state after the
126 // move and their state-reset methods are also known, which allows us to
127 // predict precisely when use-after-move is invalid.
128 // Some STL objects are known to conform to additional contracts after move,
129 // so they are not tracked. However, smart pointers specifically are tracked
130 // because we can perform extra checking over them.
131 // In aggressive mode, warn on any use-after-move because the user has
132 // intentionally asked us to completely eliminate use-after-move
133 // in his code.
134 return (Aggressiveness == AK_All) ||
135 (Aggressiveness >= AK_KnownsAndLocals && OK.IsLocal) ||
136 OK.StdKind == SK_Unsafe || OK.StdKind == SK_SmartPtr;
137 }
138
139 // Some objects only suffer from some kinds of misuses, but we need to track
140 // them anyway because we cannot know in advance what misuse will we find.
141 bool shouldWarnAbout(ObjectKind OK, MisuseKind MK) const {
142 // Additionally, only warn on smart pointers when they are dereferenced (or
143 // local or we are aggressive).
144 return shouldBeTracked(OK) &&
145 ((Aggressiveness == AK_All) ||
146 (Aggressiveness >= AK_KnownsAndLocals && OK.IsLocal) ||
147 OK.StdKind != SK_SmartPtr || MK == MK_Dereference);
148 }
149
150 // Obtains ObjectKind of an object. Because class declaration cannot always
151 // be easily obtained from the memory region, it is supplied separately.
152 ObjectKind classifyObject(ProgramStateRef State, const MemRegion *MR,
153 const CXXRecordDecl *RD) const;
154
155 // Classifies the object and dumps a user-friendly description string to
156 // the stream.
157 void explainObject(ProgramStateRef State, llvm::raw_ostream &OS,
158 const MemRegion *MR, const CXXRecordDecl *RD,
159 MisuseKind MK) const;
160
161 bool belongsTo(const CXXRecordDecl *RD, const llvm::StringSet<> &Set) const;
162
163 class MovedBugVisitor : public BugReporterVisitor {
164 public:
165 MovedBugVisitor(const MoveChecker &Chk, const MemRegion *R,
166 const CXXRecordDecl *RD, MisuseKind MK)
167 : Chk(Chk), Region(R), RD(RD), MK(MK), Found(false) {}
168
169 void Profile(llvm::FoldingSetNodeID &ID) const override {
170 static int X = 0;
171 ID.AddPointer(Ptr: &X);
172 ID.AddPointer(Ptr: Region);
173 // Don't add RD because it's, in theory, uniquely determined by
174 // the region. In practice though, it's not always possible to obtain
175 // the declaration directly from the region, that's why we store it
176 // in the first place.
177 }
178
179 PathDiagnosticPieceRef VisitNode(const ExplodedNode *N,
180 BugReporterContext &BRC,
181 PathSensitiveBugReport &BR) override;
182
183 private:
184 const MoveChecker &Chk;
185 // The tracked region.
186 const MemRegion *Region;
187 // The class of the tracked object.
188 const CXXRecordDecl *RD;
189 // How exactly the object was misused.
190 const MisuseKind MK;
191 bool Found;
192 };
193
194 AggressivenessKind Aggressiveness = AK_KnownsAndLocals;
195
196public:
197 void setAggressiveness(StringRef Str, CheckerManager &Mgr) {
198 Aggressiveness =
199 llvm::StringSwitch<AggressivenessKind>(Str)
200 .Case(S: "KnownsOnly", Value: AK_KnownsOnly)
201 .Case(S: "KnownsAndLocals", Value: AK_KnownsAndLocals)
202 .Case(S: "All", Value: AK_All)
203 .Default(Value: AK_Invalid);
204
205 if (Aggressiveness == AK_Invalid)
206 Mgr.reportInvalidCheckerOptionValue(Checker: this, OptionName: "WarnOn",
207 ExpectedValueDesc: "either \"KnownsOnly\", \"KnownsAndLocals\" or \"All\" string value");
208 };
209
210private:
211 BugType BT{this, "Use-after-move", categories::CXXMoveSemantics};
212
213 // Modelling the 3 argument std::move calls
214 const CallDescription StdMoveCall{CDM::SimpleFunc, {"std", "move"}, 3};
215
216 // Check if the given form of potential misuse of a given object
217 // should be reported. If so, get it reported. The callback from which
218 // this function was called should immediately return after the call
219 // because this function adds one or two transitions.
220 void modelUse(ProgramStateRef State, const MemRegion *Region,
221 const CXXRecordDecl *RD, MisuseKind MK,
222 CheckerContext &C) const;
223
224 // Returns the exploded node against which the report was emitted.
225 // The caller *must* add any further transitions against this node.
226 // Returns nullptr and does not report if such node already exists.
227 ExplodedNode *tryToReportBug(const MemRegion *Region, const CXXRecordDecl *RD,
228 CheckerContext &C, MisuseKind MK) const;
229
230 bool isInMoveSafeStackFrame(const StackFrame *SF) const;
231 bool isStateResetMethod(const CXXMethodDecl *MethodDec) const;
232 bool isMoveSafeMethod(const CXXMethodDecl *MethodDec) const;
233 const ExplodedNode *getMoveLocation(const ExplodedNode *N,
234 const MemRegion *Region,
235 CheckerContext &C) const;
236};
237} // end anonymous namespace
238
239REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, RegionState)
240
241// Custom map designed to track containers whose contents were moved by 3-arg
242// std::move
243REGISTER_MAP_WITH_PROGRAMSTATE(TrackedContentsMap, const MemRegion *,
244 RegionState)
245
246// Define the inter-checker API.
247namespace clang {
248namespace ento {
249namespace move {
250bool isMovedFrom(ProgramStateRef State, const MemRegion *Region) {
251 const RegionState *RS = State->get<TrackedRegionMap>(key: Region);
252 return RS && (RS->isMoved() || RS->isReported());
253}
254} // namespace move
255} // namespace ento
256} // namespace clang
257
258// If a region is removed all of the subregions needs to be removed too.
259static ProgramStateRef removeFromState(ProgramStateRef State,
260 const MemRegion *Region,
261 bool Strict = false) {
262 if (!Region)
263 return State;
264 for (auto &E : State->get<TrackedRegionMap>()) {
265 if ((!Strict || E.first != Region) && E.first->isSubRegionOf(R: Region))
266 State = State->remove<TrackedRegionMap>(K: E.first);
267 }
268 return State;
269}
270
271static bool isAnyBaseRegionReported(ProgramStateRef State,
272 const MemRegion *Region) {
273 for (auto &E : State->get<TrackedRegionMap>()) {
274 if (Region->isSubRegionOf(R: E.first) && E.second.isReported())
275 return true;
276 }
277 return false;
278}
279
280static const MemRegion *unwrapRValueReferenceIndirection(const MemRegion *MR) {
281 if (const auto *SR = dyn_cast_or_null<SymbolicRegion>(Val: MR)) {
282 SymbolRef Sym = SR->getSymbol();
283 if (Sym->getType()->isRValueReferenceType())
284 if (const MemRegion *OriginMR = Sym->getOriginRegion())
285 return OriginMR;
286 }
287 return MR;
288}
289
290PathDiagnosticPieceRef
291MoveChecker::MovedBugVisitor::VisitNode(const ExplodedNode *N,
292 BugReporterContext &BRC,
293 PathSensitiveBugReport &BR) {
294 // We need only the last move of the reported object's region.
295 // The visitor walks the ExplodedGraph backwards.
296 if (Found)
297 return nullptr;
298 ProgramStateRef State = N->getState();
299 ProgramStateRef StatePrev = N->getFirstPred()->getState();
300 const RegionState *TrackedObject = State->get<TrackedRegionMap>(key: Region);
301 const RegionState *TrackedObjectPrev =
302 StatePrev->get<TrackedRegionMap>(key: Region);
303 if (!TrackedObject)
304 return nullptr;
305 if (TrackedObjectPrev && TrackedObject)
306 return nullptr;
307
308 // Retrieve the associated statement.
309 const Stmt *S = N->getStmtForDiagnostics();
310 if (!S)
311 return nullptr;
312 Found = true;
313
314 SmallString<128> Str;
315 llvm::raw_svector_ostream OS(Str);
316
317 ObjectKind OK = Chk.classifyObject(State, MR: Region, RD);
318 switch (OK.StdKind) {
319 case SK_SmartPtr:
320 if (MK == MK_Dereference) {
321 OS << "Smart pointer";
322 Chk.explainObject(State, OS, MR: Region, RD, MK);
323 OS << " is reset to null when moved from";
324 break;
325 }
326
327 // If it's not a dereference, we don't care if it was reset to null
328 // or that it is even a smart pointer.
329 [[fallthrough]];
330 case SK_NonStd:
331 case SK_Safe:
332 OS << "Object";
333 Chk.explainObject(State, OS, MR: Region, RD, MK);
334 OS << " is moved";
335 break;
336 case SK_Unsafe:
337 OS << "Object";
338 Chk.explainObject(State, OS, MR: Region, RD, MK);
339 OS << " is left in a valid but unspecified state after move";
340 break;
341 }
342
343 // Generate the extra diagnostic.
344 PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getStackFrame());
345 return std::make_shared<PathDiagnosticEventPiece>(args&: Pos, args: OS.str(), args: true);
346}
347
348const ExplodedNode *MoveChecker::getMoveLocation(const ExplodedNode *N,
349 const MemRegion *Region,
350 CheckerContext &C) const {
351 // Walk the ExplodedGraph backwards and find the first node that referred to
352 // the tracked region.
353 const ExplodedNode *MoveNode = N;
354
355 while (N) {
356 ProgramStateRef State = N->getState();
357 if (!State->get<TrackedRegionMap>(key: Region))
358 break;
359 MoveNode = N;
360 N = N->pred_empty() ? nullptr : *(N->pred_begin());
361 }
362 return MoveNode;
363}
364
365void MoveChecker::modelUse(ProgramStateRef State, const MemRegion *Region,
366 const CXXRecordDecl *RD, MisuseKind MK,
367 CheckerContext &C) const {
368 assert(!C.isDifferent() && "No transitions should have been made by now");
369 const RegionState *RS = State->get<TrackedRegionMap>(key: Region);
370 ObjectKind OK = classifyObject(State, MR: Region, RD);
371
372 // Just in case: if it's not a smart pointer but it does have operator *,
373 // we shouldn't call the bug a dereference.
374 if (MK == MK_Dereference && OK.StdKind != SK_SmartPtr)
375 MK = MK_FunCall;
376
377 if (!RS || !shouldWarnAbout(OK, MK) ||
378 isInMoveSafeStackFrame(SF: C.getStackFrame())) {
379 // Finalize changes made by the caller.
380 C.addTransition(State);
381 return;
382 }
383
384 // Don't report it in case if any base region is already reported.
385 // But still generate a sink in case of UB.
386 // And still finalize changes made by the caller.
387 if (isAnyBaseRegionReported(State, Region)) {
388 if (misuseCausesCrash(MK)) {
389 C.generateSink(State, Pred: C.getPredecessor());
390 } else {
391 C.addTransition(State);
392 }
393 return;
394 }
395
396 ExplodedNode *N = tryToReportBug(Region, RD, C, MK);
397
398 // If the program has already crashed on this path, don't bother.
399 if (!N || N->isSink())
400 return;
401
402 State = State->set<TrackedRegionMap>(K: Region, E: RegionState::getReported());
403 C.addTransition(State, Pred: N);
404}
405
406ExplodedNode *MoveChecker::tryToReportBug(const MemRegion *Region,
407 const CXXRecordDecl *RD,
408 CheckerContext &C,
409 MisuseKind MK) const {
410 if (ExplodedNode *N = misuseCausesCrash(MK) ? C.generateErrorNode()
411 : C.generateNonFatalErrorNode()) {
412 // Uniqueing report to the same object.
413 PathDiagnosticLocation LocUsedForUniqueing;
414 const ExplodedNode *MoveNode = getMoveLocation(N, Region, C);
415
416 if (const Stmt *MoveStmt = MoveNode->getStmtForDiagnostics())
417 LocUsedForUniqueing = PathDiagnosticLocation::createBegin(
418 S: MoveStmt, SM: C.getSourceManager(), SFAC: MoveNode->getStackFrame());
419
420 // Creating the error message.
421 llvm::SmallString<128> Str;
422 llvm::raw_svector_ostream OS(Str);
423 ProgramStateRef State = N->getState();
424 switch(MK) {
425 case MK_FunCall:
426 OS << "Method called on moved-from object";
427 explainObject(State, OS, MR: Region, RD, MK);
428 break;
429 case MK_Copy:
430 OS << "Moved-from object";
431 explainObject(State, OS, MR: Region, RD, MK);
432 OS << " is copied";
433 break;
434 case MK_Move:
435 OS << "Moved-from object";
436 explainObject(State, OS, MR: Region, RD, MK);
437 OS << " is moved";
438 break;
439 case MK_Dereference:
440 OS << "Dereference of null smart pointer";
441 explainObject(State, OS, MR: Region, RD, MK);
442 break;
443 }
444
445 auto R = std::make_unique<PathSensitiveBugReport>(
446 args: BT, args: OS.str(), args&: N, args&: LocUsedForUniqueing,
447 args: MoveNode->getStackFrame()->getDecl());
448 R->addVisitor(visitor: std::make_unique<MovedBugVisitor>(args: *this, args&: Region, args&: RD, args&: MK));
449 C.emitReport(R: std::move(R));
450 return N;
451 }
452 return nullptr;
453}
454
455void MoveChecker::checkPostCall(const CallEvent &Call,
456 CheckerContext &C) const {
457 const auto *AFC = dyn_cast<AnyFunctionCall>(Val: &Call);
458 if (!AFC)
459 return;
460
461 ProgramStateRef State = C.getState();
462 const auto MethodDecl = dyn_cast_or_null<CXXMethodDecl>(Val: AFC->getDecl());
463 if (!MethodDecl)
464 return;
465
466 // Check if an object became moved-from.
467 // Object can become moved from after a call to move assignment operator or
468 // move constructor .
469 const auto *ConstructorDecl = dyn_cast<CXXConstructorDecl>(Val: MethodDecl);
470 if (ConstructorDecl && !ConstructorDecl->isMoveConstructor())
471 return;
472
473 if (!ConstructorDecl && !MethodDecl->isMoveAssignmentOperator())
474 return;
475
476 const auto ArgRegion = AFC->getArgSVal(Index: 0).getAsRegion();
477 if (!ArgRegion)
478 return;
479
480 // Skip moving the object to itself.
481 const auto *CC = dyn_cast_or_null<CXXConstructorCall>(Val: &Call);
482 if (CC && CC->getCXXThisVal().getAsRegion() == ArgRegion)
483 return;
484
485 if (const auto *IC = dyn_cast<CXXInstanceCall>(Val: AFC))
486 if (IC->getCXXThisVal().getAsRegion() == ArgRegion)
487 return;
488
489 const MemRegion *BaseRegion = ArgRegion->getBaseRegion();
490 // Skip temp objects because of their short lifetime.
491 if (BaseRegion->getAs<CXXTempObjectRegion>() ||
492 AFC->getArgExpr(Index: 0)->isPRValue())
493 return;
494 // If it has already been reported do not need to modify the state.
495
496 if (State->get<TrackedRegionMap>(key: ArgRegion))
497 return;
498
499 const CXXRecordDecl *RD = MethodDecl->getParent();
500 ObjectKind OK = classifyObject(State, MR: ArgRegion, RD);
501 if (shouldBeTracked(OK)) {
502 // Mark object as moved-from.
503 State = State->set<TrackedRegionMap>(K: ArgRegion, E: RegionState::getMoved());
504 C.addTransition(State);
505 return;
506 }
507 assert(!C.isDifferent() && "Should not have made transitions on this path!");
508}
509
510bool MoveChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
511
512 const auto *CE = dyn_cast_if_present<CallExpr>(Val: Call.getOriginExpr());
513 if (!CE)
514 return false;
515
516 ProgramStateRef State = C.getState();
517
518 if (!StdMoveCall.matches(Call))
519 return false;
520
521 const auto *POS = getIteratorPosition(State, Val: Call.getArgSVal(Index: 0));
522 if (!POS)
523 return false;
524
525 const MemRegion *ContainerRegion = POS->getContainer();
526 if (!ContainerRegion)
527 return false;
528
529 const auto *TypedRegion = dyn_cast<TypedValueRegion>(Val: ContainerRegion);
530
531 if (!TypedRegion)
532 return false;
533
534 QualType ObjTy = TypedRegion->getValueType();
535
536 const auto *RD = ObjTy->getAsCXXRecordDecl();
537 if (!RD)
538 return false;
539
540 ObjectKind OK = classifyObject(State, MR: ContainerRegion, RD);
541
542 // FIXME: IteratorModeling does not handle output iterators like
543 // std::back_inserter. For this reason, we fall back to AST pattern matching
544 // for destination recovery. Once IteratorModeling handles output iterators
545 // like std::back_inserter, this can be replaced with getIteratorPosition().
546 const auto *BackInsCall = dyn_cast<CallExpr>(Val: CE->getArg(Arg: 2)->IgnoreImpCasts());
547 if (!BackInsCall)
548 return false;
549
550 const Expr *DestExpr = BackInsCall->getArg(Arg: 0)->IgnoreImpCasts();
551 if (!DestExpr)
552 return false;
553
554 const auto *DestDRE = dyn_cast<DeclRefExpr>(Val: DestExpr);
555 if (!DestDRE)
556 return false;
557
558 const auto *DestVD = dyn_cast<VarDecl>(Val: DestDRE->getDecl());
559 if (!DestVD)
560 return false;
561
562 const MemRegion *DestRegion =
563 State->getLValue(VD: DestVD, SF: C.getStackFrame()).getAsRegion();
564 if (!DestRegion)
565 return false;
566
567 SValBuilder &SVB = State->getStateManager().getSValBuilder();
568 SVal ReturnVal = SVB.conjureSymbolVal(call: Call, visitCount: C.blockCount());
569 State = State->BindExpr(E: CE, SF: C.getStackFrame(), V: ReturnVal);
570
571 State = State->invalidateRegions(Regions: {DestRegion}, Elem: Call.getCFGElementRef(),
572 BlockCount: C.blockCount(), SF: C.getStackFrame(),
573 /*CausesPointerEscape=*/false);
574
575 if (shouldBeTracked(OK))
576 State = State->set<TrackedContentsMap>(K: ContainerRegion,
577 E: RegionState::getMoved());
578
579 C.addTransition(State);
580 return true;
581}
582
583bool MoveChecker::isMoveSafeMethod(const CXXMethodDecl *MethodDec) const {
584 // We abandon the cases where bool/void/void* conversion happens.
585 if (const auto *ConversionDec =
586 dyn_cast_or_null<CXXConversionDecl>(Val: MethodDec)) {
587 const Type *Tp = ConversionDec->getConversionType().getTypePtrOrNull();
588 if (!Tp)
589 return false;
590 if (Tp->isBooleanType() || Tp->isVoidType() || Tp->isVoidPointerType())
591 return true;
592 }
593 // Function call `empty` can be skipped.
594 return (MethodDec && MethodDec->getDeclName().isIdentifier() &&
595 (MethodDec->getName().lower() == "empty" ||
596 MethodDec->getName().lower() == "isempty"));
597}
598
599bool MoveChecker::isStateResetMethod(const CXXMethodDecl *MethodDec) const {
600 if (!MethodDec)
601 return false;
602 if (MethodDec->hasAttr<ReinitializesAttr>())
603 return true;
604 if (MethodDec->getDeclName().isIdentifier()) {
605 std::string MethodName = MethodDec->getName().lower();
606 // TODO: Some of these methods (eg., resize) are not always resetting
607 // the state, so we should consider looking at the arguments.
608 if (MethodName == "assign" || MethodName == "clear" ||
609 MethodName == "destroy" || MethodName == "reset" ||
610 MethodName == "resize" || MethodName == "shrink")
611 return true;
612 }
613 return false;
614}
615
616// Don't report an error inside a move related operation.
617// We assume that the programmer knows what she does.
618bool MoveChecker::isInMoveSafeStackFrame(const StackFrame *SF) const {
619 do {
620 const auto *SFDec = SF->getDecl();
621 auto *CtorDec = dyn_cast_or_null<CXXConstructorDecl>(Val: SFDec);
622 auto *DtorDec = dyn_cast_or_null<CXXDestructorDecl>(Val: SFDec);
623 auto *MethodDec = dyn_cast_or_null<CXXMethodDecl>(Val: SFDec);
624 if (DtorDec || (CtorDec && CtorDec->isCopyOrMoveConstructor()) ||
625 (MethodDec && MethodDec->isOverloadedOperator() &&
626 MethodDec->getOverloadedOperator() == OO_Equal) ||
627 isStateResetMethod(MethodDec) || isMoveSafeMethod(MethodDec))
628 return true;
629 } while ((SF = SF->getParent()));
630 return false;
631}
632
633bool MoveChecker::belongsTo(const CXXRecordDecl *RD,
634 const llvm::StringSet<> &Set) const {
635 const IdentifierInfo *II = RD->getIdentifier();
636 return II && Set.count(Key: II->getName());
637}
638
639MoveChecker::ObjectKind
640MoveChecker::classifyObject(ProgramStateRef State, const MemRegion *MR,
641 const CXXRecordDecl *RD) const {
642 // Local variables and local rvalue references are classified as "Local".
643 // For the purposes of this checker, we classify move-safe STL types
644 // as not-"STL" types, because that's how the checker treats them.
645 MR = unwrapRValueReferenceIndirection(MR);
646 bool IsLocal =
647 isa_and_nonnull<VarRegion, CXXLifetimeExtendedObjectRegion>(Val: MR) &&
648 MR->hasMemorySpace<StackSpaceRegion>(State);
649
650 if (!RD || !RD->getDeclContext()->isStdNamespace())
651 return { .IsLocal: IsLocal, .StdKind: SK_NonStd };
652
653 if (belongsTo(RD, Set: StdSmartPtrClasses))
654 return { .IsLocal: IsLocal, .StdKind: SK_SmartPtr };
655
656 if (belongsTo(RD, Set: StdSafeClasses))
657 return { .IsLocal: IsLocal, .StdKind: SK_Safe };
658
659 return { .IsLocal: IsLocal, .StdKind: SK_Unsafe };
660}
661
662void MoveChecker::explainObject(ProgramStateRef State, llvm::raw_ostream &OS,
663 const MemRegion *MR, const CXXRecordDecl *RD,
664 MisuseKind MK) const {
665 // We may need a leading space every time we actually explain anything,
666 // and we never know if we are to explain anything until we try.
667 if (const auto DR =
668 dyn_cast_or_null<DeclRegion>(Val: unwrapRValueReferenceIndirection(MR))) {
669 const auto *RegionDecl = cast<NamedDecl>(Val: DR->getDecl());
670 OS << " '" << RegionDecl->getDeclName() << "'";
671 }
672
673 ObjectKind OK = classifyObject(State, MR, RD);
674 switch (OK.StdKind) {
675 case SK_NonStd:
676 case SK_Safe:
677 break;
678 case SK_SmartPtr:
679 if (MK != MK_Dereference)
680 break;
681
682 // We only care about the type if it's a dereference.
683 [[fallthrough]];
684 case SK_Unsafe:
685 OS << " of type '" << RD->getQualifiedNameAsString() << "'";
686 break;
687 };
688}
689
690void MoveChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const {
691 ProgramStateRef State = C.getState();
692
693 // Remove the MemRegions from the map on which a ctor/dtor call or assignment
694 // happened.
695
696 // Checking constructor calls.
697 if (const auto *CC = dyn_cast<CXXConstructorCall>(Val: &Call)) {
698 State = removeFromState(State, Region: CC->getCXXThisVal().getAsRegion());
699 auto CtorDec = CC->getDecl();
700 // Check for copying a moved-from object and report the bug.
701 if (CtorDec && CtorDec->isCopyOrMoveConstructor()) {
702 const MemRegion *ArgRegion = CC->getArgSVal(Index: 0).getAsRegion();
703 const CXXRecordDecl *RD = CtorDec->getParent();
704 MisuseKind MK = CtorDec->isMoveConstructor() ? MK_Move : MK_Copy;
705 modelUse(State, Region: ArgRegion, RD, MK, C);
706 return;
707 }
708 }
709
710 const auto IC = dyn_cast<CXXInstanceCall>(Val: &Call);
711 if (!IC)
712 return;
713
714 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
715 if (!ThisRegion)
716 return;
717
718 // The remaining part is check only for method call on a moved-from object.
719 const auto MethodDecl = dyn_cast_or_null<CXXMethodDecl>(Val: IC->getDecl());
720 if (!MethodDecl)
721 return;
722
723 // Calling a destructor on a moved object is fine.
724 if (isa<CXXDestructorDecl>(Val: MethodDecl))
725 return;
726
727 // We want to investigate the whole object, not only sub-object of a parent
728 // class in which the encountered method defined.
729 ThisRegion = ThisRegion->getMostDerivedObjectRegion();
730
731 // Store class declaration as well, for bug reporting purposes.
732 const CXXRecordDecl *RD = MethodDecl->getParent();
733
734 if (MethodDecl->getOverloadedOperator() == OO_Star ||
735 MethodDecl->getOverloadedOperator() == OO_Arrow) {
736 SVal Val = IC->getCXXThisVal();
737
738 if (const auto *POS = getIteratorPosition(State, Val)) {
739 const MemRegion *ContainerRegion = POS->getContainer();
740 if (!ContainerRegion)
741 return;
742
743 const auto *TypedRegion = dyn_cast<TypedValueRegion>(Val: ContainerRegion);
744 if (!TypedRegion)
745 return;
746
747 QualType ObjTy = TypedRegion->getValueType();
748 const auto *R = ObjTy->getAsCXXRecordDecl();
749 if (!R)
750 return;
751
752 if (State->get<TrackedContentsMap>(key: ContainerRegion)) {
753 ExplodedNode *N = tryToReportBug(Region: ContainerRegion, RD: R, C, MK: MK_FunCall);
754 if (!N || N->isSink())
755 return;
756
757 State = State->set<TrackedContentsMap>(K: ContainerRegion,
758 E: RegionState::getReported());
759 C.addTransition(State, Pred: N);
760 return;
761 }
762 }
763 }
764
765 if (isStateResetMethod(MethodDec: MethodDecl)) {
766 State = removeFromState(State, Region: ThisRegion);
767 C.addTransition(State);
768 return;
769 }
770
771 if (isMoveSafeMethod(MethodDec: MethodDecl))
772 return;
773
774 if (MethodDecl->isOverloadedOperator()) {
775 OverloadedOperatorKind OOK = MethodDecl->getOverloadedOperator();
776
777 if (OOK == OO_Equal) {
778 // Remove the tracked object for every assignment operator, but report bug
779 // only for move or copy assignment's argument.
780 State = removeFromState(State, Region: ThisRegion);
781
782 if (MethodDecl->isCopyAssignmentOperator() ||
783 MethodDecl->isMoveAssignmentOperator()) {
784 const MemRegion *ArgRegion = IC->getArgSVal(Index: 0).getAsRegion();
785 MisuseKind MK =
786 MethodDecl->isMoveAssignmentOperator() ? MK_Move : MK_Copy;
787 modelUse(State, Region: ArgRegion, RD, MK, C);
788 return;
789 }
790 C.addTransition(State);
791 return;
792 }
793
794 if (OOK == OO_Star || OOK == OO_Arrow) {
795 modelUse(State, Region: ThisRegion, RD, MK: MK_Dereference, C);
796 return;
797 }
798 }
799
800 modelUse(State, Region: ThisRegion, RD, MK: MK_FunCall, C);
801}
802
803void MoveChecker::checkDeadSymbols(SymbolReaper &SymReaper,
804 CheckerContext &C) const {
805 ProgramStateRef State = C.getState();
806 TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>();
807 for (auto E : TrackedRegions) {
808 const MemRegion *Region = E.first;
809 bool IsRegDead = !SymReaper.isLiveRegion(region: Region);
810
811 // Remove the dead regions from the region map.
812 if (IsRegDead) {
813 State = State->remove<TrackedRegionMap>(K: Region);
814 }
815 }
816 C.addTransition(State);
817}
818
819ProgramStateRef MoveChecker::checkRegionChanges(
820 ProgramStateRef State, const InvalidatedSymbols *Invalidated,
821 ArrayRef<const MemRegion *> RequestedRegions,
822 ArrayRef<const MemRegion *> InvalidatedRegions, const StackFrame *SF,
823 const CallEvent *Call) const {
824 if (Call) {
825 // Relax invalidation upon function calls: only invalidate parameters
826 // that are passed directly via non-const pointers or non-const references
827 // or rvalue references.
828 // In case of an InstanceCall don't invalidate the this-region since
829 // it is fully handled in checkPreCall and checkPostCall, but do invalidate
830 // its strict subregions, as they are not handled.
831
832 // Requested ("explicit") regions are the regions passed into the call
833 // directly, but not all of them end up being invalidated.
834 // But when they do, they appear in the InvalidatedRegions array as well.
835 for (const auto *Region : RequestedRegions) {
836 if (llvm::is_contained(Range&: InvalidatedRegions, Element: Region))
837 State = removeFromState(State, Region,
838 /*Strict=*/isa<CXXInstanceCall>(Val: Call));
839 }
840 } else {
841 // For invalidations that aren't caused by calls, assume nothing. In
842 // particular, direct write into an object's field invalidates the status.
843 for (const auto *Region : InvalidatedRegions)
844 State = removeFromState(State, Region: Region->getBaseRegion());
845 }
846
847 return State;
848}
849
850void MoveChecker::printState(raw_ostream &Out, ProgramStateRef State,
851 const char *NL, const char *Sep) const {
852
853 TrackedRegionMapTy RS = State->get<TrackedRegionMap>();
854
855 if (!RS.isEmpty()) {
856 Out << Sep << "Moved-from objects :" << NL;
857 for (auto I: RS) {
858 I.first->dumpToStream(os&: Out);
859 if (I.second.isMoved())
860 Out << ": moved";
861 else
862 Out << ": moved and reported";
863 Out << NL;
864 }
865 }
866}
867void ento::registerMoveChecker(CheckerManager &mgr) {
868 MoveChecker *chk = mgr.registerChecker<MoveChecker>();
869 chk->setAggressiveness(
870 Str: mgr.getAnalyzerOptions().getCheckerStringOption(C: chk, OptionName: "WarnOn"), Mgr&: mgr);
871}
872
873bool ento::shouldRegisterMoveChecker(const CheckerManager &mgr) {
874 return true;
875}
876