@@ -61,19 +61,35 @@ class MoveChecker
61
61
const char *NL, const char *Sep) const override ;
62
62
63
63
private:
64
- enum MisuseKind { MK_FunCall, MK_Copy, MK_Move };
64
+ enum MisuseKind { MK_FunCall, MK_Copy, MK_Move, MK_Dereference };
65
+ enum StdObjectKind { SK_NonStd, SK_Unsafe, SK_Safe, SK_SmartPtr };
66
+
67
+ static bool misuseCausesCrash (MisuseKind MK) {
68
+ return MK == MK_Dereference;
69
+ }
65
70
66
71
struct ObjectKind {
67
- bool Local : 1 ; // Is this a local variable or a local rvalue reference?
68
- bool STL : 1 ; // Is this an object of a standard type?
72
+ // Is this a local variable or a local rvalue reference?
73
+ bool IsLocal : 1 ;
74
+ // Is this an STL object? If so, of what kind?
75
+ StdObjectKind StdKind : 2 ;
76
+ };
77
+
78
+ // STL smart pointers are automatically re-initialized to null when moved
79
+ // from. So we can't warn on many methods, but we can warn when it is
80
+ // dereferenced, which is UB even if the resulting lvalue never gets read.
81
+ const llvm::StringSet<> StdSmartPtrClasses = {
82
+ " shared_ptr" ,
83
+ " unique_ptr" ,
84
+ " weak_ptr" ,
69
85
};
70
86
71
87
// Not all of these are entirely move-safe, but they do provide *some*
72
88
// guarantees, and it means that somebody is using them after move
73
89
// in a valid manner.
74
- // TODO: We can still try to identify *unsafe* use after move, such as
75
- // dereference of a moved-from smart pointer (which is guaranteed to be null) .
76
- const llvm::StringSet<> StandardMoveSafeClasses = {
90
+ // TODO: We can still try to identify *unsafe* use after move,
91
+ // like we did with smart pointers .
92
+ const llvm::StringSet<> StdSafeClasses = {
77
93
" basic_filebuf" ,
78
94
" basic_ios" ,
79
95
" future" ,
@@ -82,11 +98,8 @@ class MoveChecker
82
98
" promise" ,
83
99
" shared_future" ,
84
100
" shared_lock" ,
85
- " shared_ptr" ,
86
101
" thread" ,
87
- " unique_ptr" ,
88
102
" unique_lock" ,
89
- " weak_ptr" ,
90
103
};
91
104
92
105
// Should we bother tracking the state of the object?
@@ -97,28 +110,43 @@ class MoveChecker
97
110
// their user to re-use the storage. The latter is possible because STL
98
111
// objects are known to end up in a valid but unspecified state after the
99
112
// move and their state-reset methods are also known, which allows us to
100
- // predict precisely when use-after-move is invalid. In aggressive mode,
101
- // warn on any use-after-move because the user has intentionally asked us
102
- // to completely eliminate use-after-move in his code.
103
- return IsAggressive || OK.Local || OK.STL ;
113
+ // predict precisely when use-after-move is invalid.
114
+ // Some STL objects are known to conform to additional contracts after move,
115
+ // so they are not tracked. However, smart pointers specifically are tracked
116
+ // because we can perform extra checking over them.
117
+ // In aggressive mode, warn on any use-after-move because the user has
118
+ // intentionally asked us to completely eliminate use-after-move
119
+ // in his code.
120
+ return IsAggressive || OK.IsLocal
121
+ || OK.StdKind == SK_Unsafe || OK.StdKind == SK_SmartPtr;
122
+ }
123
+
124
+ // Some objects only suffer from some kinds of misuses, but we need to track
125
+ // them anyway because we cannot know in advance what misuse will we find.
126
+ bool shouldWarnAbout (ObjectKind OK, MisuseKind MK) const {
127
+ // Additionally, only warn on smart pointers when they are dereferenced (or
128
+ // local or we are aggressive).
129
+ return shouldBeTracked (OK) &&
130
+ (IsAggressive || OK.IsLocal
131
+ || OK.StdKind != SK_SmartPtr || MK == MK_Dereference);
104
132
}
105
133
106
134
// Obtains ObjectKind of an object. Because class declaration cannot always
107
135
// be easily obtained from the memory region, it is supplied separately.
108
136
ObjectKind classifyObject (const MemRegion *MR, const CXXRecordDecl *RD) const ;
109
137
110
138
// Classifies the object and dumps a user-friendly description string to
111
- // the stream. Return value is equivalent to classifyObject.
112
- ObjectKind explainObject (llvm::raw_ostream &OS,
113
- const MemRegion *MR, const CXXRecordDecl *RD ) const ;
139
+ // the stream.
140
+ void explainObject (llvm::raw_ostream &OS, const MemRegion *MR ,
141
+ const CXXRecordDecl *RD, MisuseKind MK ) const ;
114
142
115
- bool isStandardMoveSafeClass (const CXXRecordDecl *RD) const ;
143
+ bool belongsTo (const CXXRecordDecl *RD, const llvm::StringSet<> &Set ) const ;
116
144
117
145
class MovedBugVisitor : public BugReporterVisitor {
118
146
public:
119
- MovedBugVisitor (const MoveChecker &Chk,
120
- const MemRegion *R, const CXXRecordDecl *RD )
121
- : Chk(Chk), Region(R), RD(RD), Found(false ) {}
147
+ MovedBugVisitor (const MoveChecker &Chk, const MemRegion *R,
148
+ const CXXRecordDecl *RD, MisuseKind MK )
149
+ : Chk(Chk), Region(R), RD(RD), MK(MK), Found(false ) {}
122
150
123
151
void Profile (llvm::FoldingSetNodeID &ID) const override {
124
152
static int X = 0 ;
@@ -140,6 +168,8 @@ class MoveChecker
140
168
const MemRegion *Region;
141
169
// The class of the tracked object.
142
170
const CXXRecordDecl *RD;
171
+ // How exactly the object was misused.
172
+ const MisuseKind MK;
143
173
bool Found;
144
174
};
145
175
@@ -232,18 +262,37 @@ MoveChecker::MovedBugVisitor::VisitNode(const ExplodedNode *N,
232
262
SmallString<128 > Str;
233
263
llvm::raw_svector_ostream OS (Str);
234
264
235
- OS << " Object" ;
236
- ObjectKind OK = Chk.explainObject (OS, Region, RD);
237
- if (OK.STL )
238
- OS << " is left in a valid but unspecified state after move" ;
239
- else
240
- OS << " is moved" ;
265
+ ObjectKind OK = Chk.classifyObject (Region, RD);
266
+ switch (OK.StdKind ) {
267
+ case SK_SmartPtr:
268
+ if (MK == MK_Dereference) {
269
+ OS << " Smart pointer" ;
270
+ Chk.explainObject (OS, Region, RD, MK);
271
+ OS << " is reset to null when moved from" ;
272
+ break ;
273
+ }
274
+
275
+ // If it's not a dereference, we don't care if it was reset to null
276
+ // or that it is even a smart pointer.
277
+ LLVM_FALLTHROUGH;
278
+ case SK_NonStd:
279
+ case SK_Safe:
280
+ OS << " Object" ;
281
+ Chk.explainObject (OS, Region, RD, MK);
282
+ OS << " is moved" ;
283
+ break ;
284
+ case SK_Unsafe:
285
+ OS << " Object" ;
286
+ Chk.explainObject (OS, Region, RD, MK);
287
+ OS << " is left in a valid but unspecified state after move" ;
288
+ break ;
289
+ }
241
290
242
291
// Generate the extra diagnostic.
243
292
PathDiagnosticLocation Pos (S, BRC.getSourceManager (),
244
293
N->getLocationContext ());
245
294
return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str (), true );
246
- }
295
+ }
247
296
248
297
const ExplodedNode *MoveChecker::getMoveLocation (const ExplodedNode *N,
249
298
const MemRegion *Region,
@@ -267,25 +316,48 @@ void MoveChecker::modelUse(ProgramStateRef State, const MemRegion *Region,
267
316
CheckerContext &C) const {
268
317
assert (!C.isDifferent () && " No transitions should have been made by now" );
269
318
const RegionState *RS = State->get <TrackedRegionMap>(Region);
319
+ ObjectKind OK = classifyObject (Region, RD);
320
+
321
+ // Just in case: if it's not a smart pointer but it does have operator *,
322
+ // we shouldn't call the bug a dereference.
323
+ if (MK == MK_Dereference && OK.StdKind != SK_SmartPtr)
324
+ MK = MK_FunCall;
270
325
271
- if (!RS || isAnyBaseRegionReported (State, Region )
326
+ if (!RS || ! shouldWarnAbout (OK, MK )
272
327
|| isInMoveSafeContext (C.getLocationContext ())) {
273
328
// Finalize changes made by the caller.
274
329
C.addTransition (State);
275
330
return ;
276
331
}
277
332
333
+ // Don't report it in case if any base region is already reported.
334
+ // But still generate a sink in case of UB.
335
+ // And still finalize changes made by the caller.
336
+ if (isAnyBaseRegionReported (State, Region)) {
337
+ if (misuseCausesCrash (MK)) {
338
+ C.generateSink (State, C.getPredecessor ());
339
+ } else {
340
+ C.addTransition (State);
341
+ }
342
+ return ;
343
+ }
344
+
278
345
ExplodedNode *N = reportBug (Region, RD, C, MK);
279
346
347
+ // If the program has already crashed on this path, don't bother.
348
+ if (N->isSink ())
349
+ return ;
350
+
280
351
State = State->set <TrackedRegionMap>(Region, RegionState::getReported ());
281
352
C.addTransition (State, N);
282
353
}
283
354
284
355
ExplodedNode *MoveChecker::reportBug (const MemRegion *Region,
285
- const CXXRecordDecl *RD,
286
- CheckerContext &C,
356
+ const CXXRecordDecl *RD, CheckerContext &C,
287
357
MisuseKind MK) const {
288
- if (ExplodedNode *N = C.generateNonFatalErrorNode ()) {
358
+ if (ExplodedNode *N = misuseCausesCrash (MK) ? C.generateErrorNode ()
359
+ : C.generateNonFatalErrorNode ()) {
360
+
289
361
if (!BT)
290
362
BT.reset (new BugType (this , " Use-after-move" ,
291
363
" C++ move semantics" ));
@@ -304,24 +376,28 @@ ExplodedNode *MoveChecker::reportBug(const MemRegion *Region,
304
376
switch (MK) {
305
377
case MK_FunCall:
306
378
OS << " Method called on moved-from object" ;
307
- explainObject (OS, Region, RD);
379
+ explainObject (OS, Region, RD, MK );
308
380
break ;
309
381
case MK_Copy:
310
382
OS << " Moved-from object" ;
311
- explainObject (OS, Region, RD);
383
+ explainObject (OS, Region, RD, MK );
312
384
OS << " is copied" ;
313
385
break ;
314
386
case MK_Move:
315
387
OS << " Moved-from object" ;
316
- explainObject (OS, Region, RD);
388
+ explainObject (OS, Region, RD, MK );
317
389
OS << " is moved" ;
318
390
break ;
391
+ case MK_Dereference:
392
+ OS << " Dereference of null smart pointer" ;
393
+ explainObject (OS, Region, RD, MK);
394
+ break ;
319
395
}
320
396
321
397
auto R =
322
398
llvm::make_unique<BugReport>(*BT, OS.str (), N, LocUsedForUniqueing,
323
399
MoveNode->getLocationContext ()->getDecl ());
324
- R->addVisitor (llvm::make_unique<MovedBugVisitor>(*this , Region, RD));
400
+ R->addVisitor (llvm::make_unique<MovedBugVisitor>(*this , Region, RD, MK ));
325
401
C.emitReport (std::move (R));
326
402
return N;
327
403
}
@@ -433,9 +509,10 @@ bool MoveChecker::isInMoveSafeContext(const LocationContext *LC) const {
433
509
return false ;
434
510
}
435
511
436
- bool MoveChecker::isStandardMoveSafeClass (const CXXRecordDecl *RD) const {
512
+ bool MoveChecker::belongsTo (const CXXRecordDecl *RD,
513
+ const llvm::StringSet<> &Set) const {
437
514
const IdentifierInfo *II = RD->getIdentifier ();
438
- return II && StandardMoveSafeClasses .count (II->getName ());
515
+ return II && Set .count (II->getName ());
439
516
}
440
517
441
518
MoveChecker::ObjectKind
@@ -445,30 +522,46 @@ MoveChecker::classifyObject(const MemRegion *MR,
445
522
// For the purposes of this checker, we classify move-safe STL types
446
523
// as not-"STL" types, because that's how the checker treats them.
447
524
MR = unwrapRValueReferenceIndirection (MR);
448
- return {
449
- /* Local=*/
450
- MR && isa<VarRegion>(MR) && isa<StackSpaceRegion>(MR->getMemorySpace ()),
451
- /* STL=*/
452
- RD && RD->getDeclContext ()->isStdNamespace () &&
453
- !isStandardMoveSafeClass (RD)
454
- };
525
+ bool IsLocal =
526
+ MR && isa<VarRegion>(MR) && isa<StackSpaceRegion>(MR->getMemorySpace ());
527
+
528
+ if (!RD || !RD->getDeclContext ()->isStdNamespace ())
529
+ return { IsLocal, SK_NonStd };
530
+
531
+ if (belongsTo (RD, StdSmartPtrClasses))
532
+ return { IsLocal, SK_SmartPtr };
533
+
534
+ if (belongsTo (RD, StdSafeClasses))
535
+ return { IsLocal, SK_Safe };
536
+
537
+ return { IsLocal, SK_Unsafe };
455
538
}
456
539
457
- MoveChecker::ObjectKind
458
- MoveChecker::explainObject (llvm::raw_ostream &OS, const MemRegion *MR,
459
- const CXXRecordDecl *RD) const {
540
+ void MoveChecker::explainObject (llvm::raw_ostream &OS, const MemRegion *MR,
541
+ const CXXRecordDecl *RD, MisuseKind MK) const {
460
542
// We may need a leading space every time we actually explain anything,
461
543
// and we never know if we are to explain anything until we try.
462
544
if (const auto DR =
463
545
dyn_cast_or_null<DeclRegion>(unwrapRValueReferenceIndirection (MR))) {
464
546
const auto *RegionDecl = cast<NamedDecl>(DR->getDecl ());
465
547
OS << " '" << RegionDecl->getNameAsString () << " '" ;
466
548
}
549
+
467
550
ObjectKind OK = classifyObject (MR, RD);
468
- if (OK.STL ) {
469
- OS << " of type '" << RD->getQualifiedNameAsString () << " '" ;
470
- }
471
- return OK;
551
+ switch (OK.StdKind ) {
552
+ case SK_NonStd:
553
+ case SK_Safe:
554
+ break ;
555
+ case SK_SmartPtr:
556
+ if (MK != MK_Dereference)
557
+ break ;
558
+
559
+ // We only care about the type if it's a dereference.
560
+ LLVM_FALLTHROUGH;
561
+ case SK_Unsafe:
562
+ OS << " of type '" << RD->getQualifiedNameAsString () << " '" ;
563
+ break ;
564
+ };
472
565
}
473
566
474
567
void MoveChecker::checkPreCall (const CallEvent &Call, CheckerContext &C) const {
@@ -543,6 +636,11 @@ void MoveChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const {
543
636
C.addTransition (State);
544
637
return ;
545
638
}
639
+
640
+ if (OOK == OO_Star || OOK == OO_Arrow) {
641
+ modelUse (State, ThisRegion, RD, MK_Dereference, C);
642
+ return ;
643
+ }
546
644
}
547
645
548
646
modelUse (State, ThisRegion, RD, MK_FunCall, C);
0 commit comments