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/STLExtras.h"
28#include "llvm/ADT/StringSet.h"
29
30using namespace clang;
31using namespace ento;
32using namespace iterator;
33
34namespace {
35struct RegionState {
36private:
37 enum Kind { Moved, Reported } K;
38 RegionState(Kind InK) : K(InK) {}
39
40public:
41 bool isReported() const { return K == Reported; }
42 bool isMoved() const { return K == Moved; }
43
44 static RegionState getReported() { return RegionState(Reported); }
45 static RegionState getMoved() { return RegionState(Moved); }
46
47 bool operator==(const RegionState &X) const { return K == X.K; }
48 void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(I: K); }
49};
50} // end of anonymous namespace
51
52namespace {
53class MoveChecker
54 : public Checker<check::PreCall, check::PostCall, check::DeadSymbols,
55 check::RegionChanges, eval::Call> {
56public:
57 void checkPreCall(const CallEvent &MC, CheckerContext &C) const;
58 void checkPostCall(const CallEvent &MC, CheckerContext &C) const;
59 void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
60 bool evalCall(const CallEvent &Call, CheckerContext &C) const;
61 ProgramStateRef
62 checkRegionChanges(ProgramStateRef State,
63 const InvalidatedSymbols *Invalidated,
64 ArrayRef<const MemRegion *> RequestedRegions,
65 ArrayRef<const MemRegion *> InvalidatedRegions,
66 const StackFrame *SF, const CallEvent *Call) const;
67 void printState(raw_ostream &Out, ProgramStateRef State,
68 const char *NL, const char *Sep) const override;
69
70private:
71 enum MisuseKind { MK_FunCall, MK_Copy, MK_Move, MK_Dereference };
72 enum StdObjectKind { SK_NonStd, SK_Unsafe, SK_Safe, SK_SmartPtr };
73
74 enum AggressivenessKind { // In any case, don't warn after a reset.
75 AK_Invalid = -1,
76 AK_KnownsOnly = 0, // Warn only about known move-unsafe classes.
77 AK_KnownsAndLocals = 1, // Also warn about all local objects.
78 AK_All = 2, // Warn on any use-after-move.
79 AK_NumKinds = AK_All
80 };
81
82 static bool misuseCausesCrash(MisuseKind MK) {
83 return MK == MK_Dereference;
84 }
85
86 struct ObjectKind {
87 // Is this a local variable or a local rvalue reference?
88 bool IsLocal;
89 // Is this an STL object? If so, of what kind?
90 StdObjectKind StdKind;
91 };
92
93 // STL smart pointers are automatically re-initialized to null when moved
94 // from. So we can't warn on many methods, but we can warn when it is
95 // dereferenced, which is UB even if the resulting lvalue never gets read.
96 const llvm::StringSet<> StdSmartPtrClasses = {
97 "shared_ptr",
98 "unique_ptr",
99 "weak_ptr",
100 };
101
102 // Not all of these are entirely move-safe, but they do provide *some*
103 // guarantees, and it means that somebody is using them after move
104 // in a valid manner.
105 // TODO: We can still try to identify *unsafe* use after move,
106 // like we did with smart pointers.
107 const llvm::StringSet<> StdSafeClasses = {
108 "basic_filebuf",
109 "basic_ios",
110 "future",
111 "optional",
112 "packaged_task",
113 "promise",
114 "shared_future",
115 "shared_lock",
116 "thread",
117 "unique_lock",
118 };
119
120 // Should we bother tracking the state of the object?
121 bool shouldBeTracked(ObjectKind OK) const {
122 // In non-aggressive mode, only warn on use-after-move of local variables
123 // (or local rvalue references) and of STL objects. The former is possible
124 // because local variables (or local rvalue references) are not tempting
125 // their user to re-use the storage. The latter is possible because STL
126 // objects are known to end up in a valid but unspecified state after the
127 // move and their state-reset methods are also known, which allows us to
128 // predict precisely when use-after-move is invalid.
129 // Some STL objects are known to conform to additional contracts after move,
130 // so they are not tracked. However, smart pointers specifically are tracked
131 // because we can perform extra checking over them.
132 // In aggressive mode, warn on any use-after-move because the user has
133 // intentionally asked us to completely eliminate use-after-move
134 // in his code.
135 return (Aggressiveness == AK_All) ||
136 (Aggressiveness >= AK_KnownsAndLocals && OK.IsLocal) ||
137 OK.StdKind == SK_Unsafe || OK.StdKind == SK_SmartPtr;
138 }
139
140 // Some objects only suffer from some kinds of misuses, but we need to track
141 // them anyway because we cannot know in advance what misuse will we find.
142 bool shouldWarnAbout(ObjectKind OK, MisuseKind MK) const {
143 // Additionally, only warn on smart pointers when they are dereferenced (or
144 // local or we are aggressive).
145 return shouldBeTracked(OK) &&
146 ((Aggressiveness == AK_All) ||
147 (Aggressiveness >= AK_KnownsAndLocals && OK.IsLocal) ||
148 OK.StdKind != SK_SmartPtr || MK == MK_Dereference);
149 }
150
151 // Obtains ObjectKind of an object. Because class declaration cannot always
152 // be easily obtained from the memory region, it is supplied separately.
153 ObjectKind classifyObject(ProgramStateRef State, const MemRegion *MR,
154 const CXXRecordDecl *RD) const;
155
156 // Classifies the object and dumps a user-friendly description string to
157 // the stream.
158 void explainObject(ProgramStateRef State, llvm::raw_ostream &OS,
159 const MemRegion *MR, const CXXRecordDecl *RD,
160 MisuseKind MK) const;
161
162 bool belongsTo(const CXXRecordDecl *RD, const llvm::StringSet<> &Set) const;
163
164 class MovedBugVisitor : public BugReporterVisitor {
165 public:
166 MovedBugVisitor(const MoveChecker &Chk, const MemRegion *R,
167 const CXXRecordDecl *RD, MisuseKind MK)
168 : Chk(Chk), Region(R), RD(RD), MK(MK), Found(false) {}
169
170 void Profile(llvm::FoldingSetNodeID &ID) const override {
171 static int X = 0;
172 ID.AddPointer(Ptr: &X);
173 ID.AddPointer(Ptr: Region);
174 // Don't add RD because it's, in theory, uniquely determined by
175 // the region. In practice though, it's not always possible to obtain
176 // the declaration directly from the region, that's why we store it
177 // in the first place.
178 }
179
180 PathDiagnosticPieceRef VisitNode(const ExplodedNode *N,
181 BugReporterContext &BRC,
182 PathSensitiveBugReport &BR) override;
183
184 private:
185 const MoveChecker &Chk;
186 // The tracked region.
187 const MemRegion *Region;
188 // The class of the tracked object.
189 const CXXRecordDecl *RD;
190 // How exactly the object was misused.
191 const MisuseKind MK;
192 bool Found;
193 };
194
195 AggressivenessKind Aggressiveness = AK_KnownsAndLocals;
196
197public:
198 void setAggressiveness(StringRef Str, CheckerManager &Mgr) {
199 Aggressiveness =
200 llvm::StringSwitch<AggressivenessKind>(Str)
201 .Case(S: "KnownsOnly", Value: AK_KnownsOnly)
202 .Case(S: "KnownsAndLocals", Value: AK_KnownsAndLocals)
203 .Case(S: "All", Value: AK_All)
204 .Default(Value: AK_Invalid);
205
206 if (Aggressiveness == AK_Invalid)
207 Mgr.reportInvalidCheckerOptionValue(Checker: this, OptionName: "WarnOn",
208 ExpectedValueDesc: "either \"KnownsOnly\", \"KnownsAndLocals\" or \"All\" string value");
209 };
210
211private:
212 BugType BT{this, "Use-after-move", categories::CXXMoveSemantics};
213
214 // Modelling the 3 argument std::move calls
215 const CallDescription StdMoveCall{CDM::SimpleFunc, {"std", "move"}, 3};
216
217 // Check if the given form of potential misuse of a given object
218 // should be reported. If so, get it reported. The callback from which
219 // this function was called should immediately return after the call
220 // because this function adds one or two transitions.
221 void modelUse(ProgramStateRef State, const MemRegion *Region,
222 const CXXRecordDecl *RD, MisuseKind MK,
223 CheckerContext &C) const;
224
225 // Returns the exploded node against which the report was emitted.
226 // The caller *must* add any further transitions against this node.
227 // Returns nullptr and does not report if such node already exists.
228 ExplodedNode *tryToReportBug(const MemRegion *Region, const CXXRecordDecl *RD,
229 CheckerContext &C, MisuseKind MK) const;
230
231 bool isInMoveSafeStackFrame(const CheckerContext &C) const;
232 bool isStateResetMethod(const CXXMethodDecl *MethodDec) const;
233 bool isMoveSafeMethod(const CXXMethodDecl *MethodDec) const;
234 const ExplodedNode *getMoveLocation(const ExplodedNode *N,
235 const MemRegion *Region,
236 CheckerContext &C) const;
237};
238} // end anonymous namespace
239
240REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, RegionState)
241
242// Custom map designed to track containers whose contents were moved by 3-arg
243// std::move
244REGISTER_MAP_WITH_PROGRAMSTATE(TrackedContentsMap, const MemRegion *,
245 RegionState)
246
247// Define the inter-checker API.
248namespace clang {
249namespace ento {
250namespace move {
251bool isMovedFrom(ProgramStateRef State, const MemRegion *Region) {
252 const RegionState *RS = State->get<TrackedRegionMap>(key: Region);
253 return RS && (RS->isMoved() || RS->isReported());
254}
255} // namespace move
256} // namespace ento
257} // namespace clang
258
259// If a region is removed all of the subregions needs to be removed too.
260static ProgramStateRef removeFromState(ProgramStateRef State,
261 const MemRegion *Region,
262 bool Strict = false) {
263 if (!Region)
264 return State;
265 for (auto &E : State->get<TrackedRegionMap>()) {
266 if ((!Strict || E.first != Region) && E.first->isSubRegionOf(R: Region))
267 State = State->remove<TrackedRegionMap>(K: E.first);
268 }
269 return State;
270}
271
272static bool isAnyBaseRegionReported(ProgramStateRef State,
273 const MemRegion *Region) {
274 for (auto &E : State->get<TrackedRegionMap>()) {
275 if (Region->isSubRegionOf(R: E.first) && E.second.isReported())
276 return true;
277 }
278 return false;
279}
280
281static const MemRegion *unwrapRValueReferenceIndirection(const MemRegion *MR) {
282 if (const auto *SR = dyn_cast_or_null<SymbolicRegion>(Val: MR)) {
283 SymbolRef Sym = SR->getSymbol();
284 if (Sym->getType()->isRValueReferenceType())
285 if (const MemRegion *OriginMR = Sym->getOriginRegion())
286 return OriginMR;
287 }
288 return MR;
289}
290
291PathDiagnosticPieceRef
292MoveChecker::MovedBugVisitor::VisitNode(const ExplodedNode *N,
293 BugReporterContext &BRC,
294 PathSensitiveBugReport &BR) {
295 // We need only the last move of the reported object's region.
296 // The visitor walks the ExplodedGraph backwards.
297 if (Found)
298 return nullptr;
299 ProgramStateRef State = N->getState();
300 ProgramStateRef StatePrev = N->getFirstPred()->getState();
301 const RegionState *TrackedObject = State->get<TrackedRegionMap>(key: Region);
302 const RegionState *TrackedObjectPrev =
303 StatePrev->get<TrackedRegionMap>(key: Region);
304 if (!TrackedObject)
305 return nullptr;
306 if (TrackedObjectPrev && TrackedObject)
307 return nullptr;
308
309 // Retrieve the associated statement.
310 const Stmt *S = N->getStmtForDiagnostics();
311 if (!S)
312 return nullptr;
313 Found = true;
314
315 SmallString<128> Str;
316 llvm::raw_svector_ostream OS(Str);
317
318 ObjectKind OK = Chk.classifyObject(State, MR: Region, RD);
319 switch (OK.StdKind) {
320 case SK_SmartPtr:
321 if (MK == MK_Dereference) {
322 OS << "Smart pointer";
323 Chk.explainObject(State, OS, MR: Region, RD, MK);
324 OS << " is reset to null when moved from";
325 break;
326 }
327
328 // If it's not a dereference, we don't care if it was reset to null
329 // or that it is even a smart pointer.
330 [[fallthrough]];
331 case SK_NonStd:
332 case SK_Safe:
333 OS << "Object";
334 Chk.explainObject(State, OS, MR: Region, RD, MK);
335 OS << " is moved";
336 break;
337 case SK_Unsafe:
338 OS << "Object";
339 Chk.explainObject(State, OS, MR: Region, RD, MK);
340 OS << " is left in a valid but unspecified state after move";
341 break;
342 }
343
344 // Generate the extra diagnostic.
345 PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getStackFrame());
346 return std::make_shared<PathDiagnosticEventPiece>(args&: Pos, args: OS.str(), args: true);
347}
348
349const ExplodedNode *MoveChecker::getMoveLocation(const ExplodedNode *N,
350 const MemRegion *Region,
351 CheckerContext &C) const {
352 // Walk the ExplodedGraph backwards and find the first node that referred to
353 // the tracked region.
354 const ExplodedNode *MoveNode = N;
355
356 while (N) {
357 ProgramStateRef State = N->getState();
358 if (!State->get<TrackedRegionMap>(key: Region))
359 break;
360 MoveNode = N;
361 N = N->pred_empty() ? nullptr : *(N->pred_begin());
362 }
363 return MoveNode;
364}
365
366void MoveChecker::modelUse(ProgramStateRef State, const MemRegion *Region,
367 const CXXRecordDecl *RD, MisuseKind MK,
368 CheckerContext &C) const {
369 assert(!C.isDifferent() && "No transitions should have been made by now");
370 const RegionState *RS = State->get<TrackedRegionMap>(key: Region);
371 ObjectKind OK = classifyObject(State, MR: Region, RD);
372
373 // Just in case: if it's not a smart pointer but it does have operator *,
374 // we shouldn't call the bug a dereference.
375 if (MK == MK_Dereference && OK.StdKind != SK_SmartPtr)
376 MK = MK_FunCall;
377
378 if (!RS || !shouldWarnAbout(OK, MK) || isInMoveSafeStackFrame(C)) {
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 CheckerContext &C) const {
619 return llvm::any_of(Range: C.stackframes(), P: [this](const StackFrame &Frame) {
620 const auto *SFDec = Frame.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 return DtorDec || (CtorDec && CtorDec->isCopyOrMoveConstructor()) ||
625 (MethodDec && MethodDec->isOverloadedOperator() &&
626 MethodDec->getOverloadedOperator() == OO_Equal) ||
627 isStateResetMethod(MethodDec) || isMoveSafeMethod(MethodDec);
628 });
629}
630
631bool MoveChecker::belongsTo(const CXXRecordDecl *RD,
632 const llvm::StringSet<> &Set) const {
633 const IdentifierInfo *II = RD->getIdentifier();
634 return II && Set.count(Key: II->getName());
635}
636
637MoveChecker::ObjectKind
638MoveChecker::classifyObject(ProgramStateRef State, const MemRegion *MR,
639 const CXXRecordDecl *RD) const {
640 // Local variables and local rvalue references are classified as "Local".
641 // For the purposes of this checker, we classify move-safe STL types
642 // as not-"STL" types, because that's how the checker treats them.
643 MR = unwrapRValueReferenceIndirection(MR);
644 bool IsLocal =
645 isa_and_nonnull<VarRegion, CXXLifetimeExtendedObjectRegion>(Val: MR) &&
646 MR->hasMemorySpace<StackSpaceRegion>(State);
647
648 if (!RD || !RD->getDeclContext()->isStdNamespace())
649 return { .IsLocal: IsLocal, .StdKind: SK_NonStd };
650
651 if (belongsTo(RD, Set: StdSmartPtrClasses))
652 return { .IsLocal: IsLocal, .StdKind: SK_SmartPtr };
653
654 if (belongsTo(RD, Set: StdSafeClasses))
655 return { .IsLocal: IsLocal, .StdKind: SK_Safe };
656
657 return { .IsLocal: IsLocal, .StdKind: SK_Unsafe };
658}
659
660void MoveChecker::explainObject(ProgramStateRef State, llvm::raw_ostream &OS,
661 const MemRegion *MR, const CXXRecordDecl *RD,
662 MisuseKind MK) const {
663 // We may need a leading space every time we actually explain anything,
664 // and we never know if we are to explain anything until we try.
665 if (const auto DR =
666 dyn_cast_or_null<DeclRegion>(Val: unwrapRValueReferenceIndirection(MR))) {
667 const auto *RegionDecl = cast<NamedDecl>(Val: DR->getDecl());
668 OS << " '" << RegionDecl->getDeclName() << "'";
669 }
670
671 ObjectKind OK = classifyObject(State, MR, RD);
672 switch (OK.StdKind) {
673 case SK_NonStd:
674 case SK_Safe:
675 break;
676 case SK_SmartPtr:
677 if (MK != MK_Dereference)
678 break;
679
680 // We only care about the type if it's a dereference.
681 [[fallthrough]];
682 case SK_Unsafe:
683 OS << " of type '" << RD->getQualifiedNameAsString() << "'";
684 break;
685 };
686}
687
688void MoveChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const {
689 ProgramStateRef State = C.getState();
690
691 // Remove the MemRegions from the map on which a ctor/dtor call or assignment
692 // happened.
693
694 // Checking constructor calls.
695 if (const auto *CC = dyn_cast<CXXConstructorCall>(Val: &Call)) {
696 State = removeFromState(State, Region: CC->getCXXThisVal().getAsRegion());
697 auto CtorDec = CC->getDecl();
698 // Check for copying a moved-from object and report the bug.
699 if (CtorDec && CtorDec->isCopyOrMoveConstructor()) {
700 const MemRegion *ArgRegion = CC->getArgSVal(Index: 0).getAsRegion();
701 const CXXRecordDecl *RD = CtorDec->getParent();
702 MisuseKind MK = CtorDec->isMoveConstructor() ? MK_Move : MK_Copy;
703 modelUse(State, Region: ArgRegion, RD, MK, C);
704 return;
705 }
706 }
707
708 const auto IC = dyn_cast<CXXInstanceCall>(Val: &Call);
709 if (!IC)
710 return;
711
712 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
713 if (!ThisRegion)
714 return;
715
716 // The remaining part is check only for method call on a moved-from object.
717 const auto MethodDecl = dyn_cast_or_null<CXXMethodDecl>(Val: IC->getDecl());
718 if (!MethodDecl)
719 return;
720
721 // Calling a destructor on a moved object is fine.
722 if (isa<CXXDestructorDecl>(Val: MethodDecl))
723 return;
724
725 // We want to investigate the whole object, not only sub-object of a parent
726 // class in which the encountered method defined.
727 ThisRegion = ThisRegion->getMostDerivedObjectRegion();
728
729 // Store class declaration as well, for bug reporting purposes.
730 const CXXRecordDecl *RD = MethodDecl->getParent();
731
732 if (MethodDecl->getOverloadedOperator() == OO_Star ||
733 MethodDecl->getOverloadedOperator() == OO_Arrow) {
734 SVal Val = IC->getCXXThisVal();
735
736 if (const auto *POS = getIteratorPosition(State, Val)) {
737 const MemRegion *ContainerRegion = POS->getContainer();
738 if (!ContainerRegion)
739 return;
740
741 const auto *TypedRegion = dyn_cast<TypedValueRegion>(Val: ContainerRegion);
742 if (!TypedRegion)
743 return;
744
745 QualType ObjTy = TypedRegion->getValueType();
746 const auto *R = ObjTy->getAsCXXRecordDecl();
747 if (!R)
748 return;
749
750 if (State->get<TrackedContentsMap>(key: ContainerRegion)) {
751 ExplodedNode *N = tryToReportBug(Region: ContainerRegion, RD: R, C, MK: MK_FunCall);
752 if (!N || N->isSink())
753 return;
754
755 State = State->set<TrackedContentsMap>(K: ContainerRegion,
756 E: RegionState::getReported());
757 C.addTransition(State, Pred: N);
758 return;
759 }
760 }
761 }
762
763 if (isStateResetMethod(MethodDec: MethodDecl)) {
764 State = removeFromState(State, Region: ThisRegion);
765 C.addTransition(State);
766 return;
767 }
768
769 if (isMoveSafeMethod(MethodDec: MethodDecl))
770 return;
771
772 if (MethodDecl->isOverloadedOperator()) {
773 OverloadedOperatorKind OOK = MethodDecl->getOverloadedOperator();
774
775 if (OOK == OO_Equal) {
776 // Remove the tracked object for every assignment operator, but report bug
777 // only for move or copy assignment's argument.
778 State = removeFromState(State, Region: ThisRegion);
779
780 if (MethodDecl->isCopyAssignmentOperator() ||
781 MethodDecl->isMoveAssignmentOperator()) {
782 const MemRegion *ArgRegion = IC->getArgSVal(Index: 0).getAsRegion();
783 MisuseKind MK =
784 MethodDecl->isMoveAssignmentOperator() ? MK_Move : MK_Copy;
785 modelUse(State, Region: ArgRegion, RD, MK, C);
786 return;
787 }
788 C.addTransition(State);
789 return;
790 }
791
792 if (OOK == OO_Star || OOK == OO_Arrow) {
793 modelUse(State, Region: ThisRegion, RD, MK: MK_Dereference, C);
794 return;
795 }
796 }
797
798 modelUse(State, Region: ThisRegion, RD, MK: MK_FunCall, C);
799}
800
801void MoveChecker::checkDeadSymbols(SymbolReaper &SymReaper,
802 CheckerContext &C) const {
803 ProgramStateRef State = C.getState();
804 TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>();
805 for (auto E : TrackedRegions) {
806 const MemRegion *Region = E.first;
807 bool IsRegDead = !SymReaper.isLiveRegion(region: Region);
808
809 // Remove the dead regions from the region map.
810 if (IsRegDead) {
811 State = State->remove<TrackedRegionMap>(K: Region);
812 }
813 }
814 C.addTransition(State);
815}
816
817ProgramStateRef MoveChecker::checkRegionChanges(
818 ProgramStateRef State, const InvalidatedSymbols *Invalidated,
819 ArrayRef<const MemRegion *> RequestedRegions,
820 ArrayRef<const MemRegion *> InvalidatedRegions, const StackFrame *SF,
821 const CallEvent *Call) const {
822 if (Call) {
823 // Relax invalidation upon function calls: only invalidate parameters
824 // that are passed directly via non-const pointers or non-const references
825 // or rvalue references.
826 // In case of an InstanceCall don't invalidate the this-region since
827 // it is fully handled in checkPreCall and checkPostCall, but do invalidate
828 // its strict subregions, as they are not handled.
829
830 // Requested ("explicit") regions are the regions passed into the call
831 // directly, but not all of them end up being invalidated.
832 // But when they do, they appear in the InvalidatedRegions array as well.
833 for (const auto *Region : RequestedRegions) {
834 if (llvm::is_contained(Range&: InvalidatedRegions, Element: Region))
835 State = removeFromState(State, Region,
836 /*Strict=*/isa<CXXInstanceCall>(Val: Call));
837 }
838 } else {
839 // For invalidations that aren't caused by calls, assume nothing. In
840 // particular, direct write into an object's field invalidates the status.
841 for (const auto *Region : InvalidatedRegions)
842 State = removeFromState(State, Region: Region->getBaseRegion());
843 }
844
845 return State;
846}
847
848void MoveChecker::printState(raw_ostream &Out, ProgramStateRef State,
849 const char *NL, const char *Sep) const {
850
851 TrackedRegionMapTy RS = State->get<TrackedRegionMap>();
852
853 if (!RS.isEmpty()) {
854 Out << Sep << "Moved-from objects :" << NL;
855 for (auto I: RS) {
856 I.first->dumpToStream(os&: Out);
857 if (I.second.isMoved())
858 Out << ": moved";
859 else
860 Out << ": moved and reported";
861 Out << NL;
862 }
863 }
864}
865void ento::registerMoveChecker(CheckerManager &mgr) {
866 MoveChecker *chk = mgr.registerChecker<MoveChecker>();
867 chk->setAggressiveness(
868 Str: mgr.getAnalyzerOptions().getCheckerStringOption(C: chk, OptionName: "WarnOn"), Mgr&: mgr);
869}
870
871bool ento::shouldRegisterMoveChecker(const CheckerManager &mgr) {
872 return true;
873}
874