1// RUN: %check_clang_tidy -std=c++17-or-later %s bugprone-use-after-move %t -- -- -fno-delayed-template-parsing
2
3typedef decltype(nullptr) nullptr_t;
4
5namespace std {
6typedef unsigned size_t;
7
8template <typename T>
9struct unique_ptr {
10 unique_ptr();
11 T *get() const;
12 explicit operator bool() const;
13 void reset(T *ptr);
14 T &operator*() const;
15 T *operator->() const;
16 T& operator[](size_t i) const;
17};
18
19template <typename T>
20struct shared_ptr {
21 shared_ptr();
22 T *get() const;
23 explicit operator bool() const;
24 void reset(T *ptr);
25 T &operator*() const;
26 T *operator->() const;
27};
28
29template <typename T>
30struct weak_ptr {
31 weak_ptr();
32 bool expired() const;
33};
34
35template <typename T1, typename T2>
36struct pair {};
37
38template <typename Key, typename T>
39struct map {
40 struct iterator {};
41
42 map();
43 void clear();
44 bool empty();
45 template <class... Args>
46 pair<iterator, bool> try_emplace(const Key &key, Args &&...args);
47};
48
49template <typename Key, typename T>
50struct unordered_map {
51 struct iterator {};
52
53 unordered_map();
54 void clear();
55 bool empty();
56 template <class... Args>
57 pair<iterator, bool> try_emplace(const Key &key, Args &&...args);
58};
59
60#define DECLARE_STANDARD_CONTAINER(name) \
61 template <typename T> \
62 struct name { \
63 name(); \
64 void clear(); \
65 bool empty(); \
66 }
67
68#define DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(name) \
69 template <typename T> \
70 struct name { \
71 name(); \
72 void clear(); \
73 bool empty(); \
74 void assign(size_t, const T &); \
75 }
76
77DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(basic_string);
78DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(vector);
79DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(deque);
80DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(forward_list);
81DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(list);
82DECLARE_STANDARD_CONTAINER(set);
83DECLARE_STANDARD_CONTAINER(multiset);
84DECLARE_STANDARD_CONTAINER(multimap);
85DECLARE_STANDARD_CONTAINER(unordered_set);
86DECLARE_STANDARD_CONTAINER(unordered_multiset);
87DECLARE_STANDARD_CONTAINER(unordered_multimap);
88
89typedef basic_string<char> string;
90
91template <typename>
92struct remove_reference;
93
94template <typename _Tp>
95struct remove_reference {
96 typedef _Tp type;
97};
98
99template <typename _Tp>
100struct remove_reference<_Tp &> {
101 typedef _Tp type;
102};
103
104template <typename _Tp>
105struct remove_reference<_Tp &&> {
106 typedef _Tp type;
107};
108
109template <typename _Tp>
110constexpr typename std::remove_reference<_Tp>::type &&move(_Tp &&__t) noexcept {
111 return static_cast<typename remove_reference<_Tp>::type &&>(__t);
112}
113
114template <class _Tp>
115constexpr _Tp&&
116forward(typename std::remove_reference<_Tp>::type& __t) noexcept {
117 return static_cast<_Tp&&>(__t);
118}
119
120template <class _Tp>
121constexpr _Tp&&
122forward(typename std::remove_reference<_Tp>::type&& __t) noexcept {
123 return static_cast<_Tp&&>(__t);
124}
125
126} // namespace std
127
128class A {
129public:
130 A();
131 A(const A &);
132 A(A &&);
133
134 A &operator=(const A &);
135 A &operator=(A &&);
136
137 void foo() const;
138 int getInt() const;
139
140 operator bool() const;
141
142 int i;
143};
144
145template <class T>
146class AnnotatedContainer {
147public:
148 AnnotatedContainer();
149
150 void foo() const;
151 [[clang::reinitializes]] void clear();
152};
153
154////////////////////////////////////////////////////////////////////////////////
155// General tests.
156
157// Simple case.
158void simple() {
159 A a;
160 a.foo();
161 A other_a = std::move(a);
162 a.foo();
163 // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved
164 // CHECK-NOTES: [[@LINE-3]]:15: note: move occurred here
165}
166
167// Don't flag a move-to-self.
168void selfMove() {
169 A a;
170 a = std::move(a);
171 a.foo();
172}
173
174// A warning should only be emitted for one use-after-move.
175void onlyFlagOneUseAfterMove() {
176 A a;
177 a.foo();
178 A other_a = std::move(a);
179 a.foo();
180 // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved
181 // CHECK-NOTES: [[@LINE-3]]:15: note: move occurred here
182 a.foo();
183}
184
185void moveAfterMove() {
186 // Move-after-move also counts as a use.
187 {
188 A a;
189 std::move(a);
190 std::move(a);
191 // CHECK-NOTES: [[@LINE-1]]:15: warning: 'a' used after it was moved
192 // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here
193 }
194 // This is also true if the move itself turns into the use on the second loop
195 // iteration.
196 {
197 A a;
198 for (int i = 0; i < 10; ++i) {
199 std::move(a);
200 // CHECK-NOTES: [[@LINE-1]]:17: warning: 'a' used after it was moved
201 // CHECK-NOTES: [[@LINE-2]]:7: note: move occurred here
202 // CHECK-NOTES: [[@LINE-3]]:17: note: the use happens in a later loop
203 }
204 }
205}
206
207// Checks also works on function parameters that have a use-after move.
208void parameters(A a) {
209 std::move(a);
210 a.foo();
211 // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved
212 // CHECK-NOTES: [[@LINE-3]]:3: note: move occurred here
213}
214
215void standardSmartPtr() {
216 // std::unique_ptr<>, std::shared_ptr<> and std::weak_ptr<> are guaranteed to
217 // be null after a std::move. So the check only flags accesses that would
218 // dereference the pointer.
219 {
220 std::unique_ptr<A> ptr;
221 std::move(ptr);
222 ptr.get();
223 static_cast<bool>(ptr);
224 *ptr;
225 // CHECK-NOTES: [[@LINE-1]]:6: warning: 'ptr' used after it was moved
226 // CHECK-NOTES: [[@LINE-5]]:5: note: move occurred here
227 }
228 {
229 std::unique_ptr<A> ptr;
230 std::move(ptr);
231 ptr->foo();
232 // CHECK-NOTES: [[@LINE-1]]:5: warning: 'ptr' used after it was moved
233 // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here
234 }
235 {
236 std::unique_ptr<A> ptr;
237 std::move(ptr);
238 ptr[0];
239 // CHECK-NOTES: [[@LINE-1]]:5: warning: 'ptr' used after it was moved
240 // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here
241 }
242 {
243 std::shared_ptr<A> ptr;
244 std::move(ptr);
245 ptr.get();
246 static_cast<bool>(ptr);
247 *ptr;
248 // CHECK-NOTES: [[@LINE-1]]:6: warning: 'ptr' used after it was moved
249 // CHECK-NOTES: [[@LINE-5]]:5: note: move occurred here
250 }
251 {
252 std::shared_ptr<A> ptr;
253 std::move(ptr);
254 ptr->foo();
255 // CHECK-NOTES: [[@LINE-1]]:5: warning: 'ptr' used after it was moved
256 // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here
257 }
258 {
259 // std::weak_ptr<> cannot be dereferenced directly, so we only check that
260 // member functions may be called on it after a move.
261 std::weak_ptr<A> ptr;
262 std::move(ptr);
263 ptr.expired();
264 }
265 // Make sure we recognize std::unique_ptr<> or std::shared_ptr<> if they're
266 // wrapped in a typedef.
267 {
268 typedef std::unique_ptr<A> PtrToA;
269 PtrToA ptr;
270 std::move(ptr);
271 ptr.get();
272 }
273 {
274 typedef std::shared_ptr<A> PtrToA;
275 PtrToA ptr;
276 std::move(ptr);
277 ptr.get();
278 }
279 // And we don't get confused if the template argument is a little more
280 // involved.
281 {
282 struct B {
283 typedef A AnotherNameForA;
284 };
285 std::unique_ptr<B::AnotherNameForA> ptr;
286 std::move(ptr);
287 ptr.get();
288 }
289 // Make sure we treat references to smart pointers correctly.
290 {
291 std::unique_ptr<A> ptr;
292 std::unique_ptr<A>& ref_to_ptr = ptr;
293 std::move(ref_to_ptr);
294 ref_to_ptr.get();
295 }
296 {
297 std::unique_ptr<A> ptr;
298 std::unique_ptr<A>&& rvalue_ref_to_ptr = std::move(ptr);
299 std::move(rvalue_ref_to_ptr);
300 rvalue_ref_to_ptr.get();
301 }
302 // We don't give any special treatment to types that are called "unique_ptr"
303 // or "shared_ptr" but are not in the "::std" namespace.
304 {
305 struct unique_ptr {
306 void get();
307 } ptr;
308 std::move(ptr);
309 ptr.get();
310 // CHECK-NOTES: [[@LINE-1]]:5: warning: 'ptr' used after it was moved
311 // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here
312 }
313}
314
315// The check also works in member functions.
316class Container {
317 void useAfterMoveInMemberFunction() {
318 A a;
319 std::move(a);
320 a.foo();
321 // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved
322 // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here
323 }
324};
325
326// We see the std::move() if it's inside a declaration.
327void moveInDeclaration() {
328 A a;
329 A another_a(std::move(a));
330 a.foo();
331 // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved
332 // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here
333}
334
335// We see the std::move if it's inside an initializer list. Initializer lists
336// are a special case because they cause ASTContext::getParents() to return
337// multiple parents for certain nodes in their subtree. This is because
338// RecursiveASTVisitor visits both the syntactic and semantic forms of
339// InitListExpr, and the parent-child relationships are different between the
340// two forms.
341void moveInInitList() {
342 struct S {
343 A a;
344 };
345 A a;
346 S s{.a: std::move(a)};
347 a.foo();
348 // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved
349 // CHECK-NOTES: [[@LINE-3]]:7: note: move occurred here
350}
351
352void lambdas() {
353 // Use-after-moves inside a lambda should be detected.
354 {
355 A a;
356 auto lambda = [a] {
357 std::move(a);
358 a.foo();
359 // CHECK-NOTES: [[@LINE-1]]:7: warning: 'a' used after it was moved
360 // CHECK-NOTES: [[@LINE-3]]:7: note: move occurred here
361 };
362 }
363 // This is just as true if the variable was declared inside the lambda.
364 {
365 auto lambda = [] {
366 A a;
367 std::move(a);
368 a.foo();
369 // CHECK-NOTES: [[@LINE-1]]:7: warning: 'a' used after it was moved
370 // CHECK-NOTES: [[@LINE-3]]:7: note: move occurred here
371 };
372 }
373 // But don't warn if the move happened inside the lambda but the use happened
374 // outside -- because
375 // - the 'a' inside the lambda is a copy, and
376 // - we don't know when the lambda will get called anyway
377 {
378 A a;
379 auto lambda = [a] {
380 std::move(a);
381 };
382 a.foo();
383 }
384 // Don't warn if 'a' is a copy inside a synchronous lambda
385 {
386 A a;
387 A copied{[a] mutable { return std::move(a); }()};
388 a.foo();
389 }
390 // False negative (should warn if 'a' is a ref inside a synchronous lambda)
391 {
392 A a;
393 A moved{[&a] mutable { return std::move(a); }()};
394 a.foo();
395 }
396 // Warn if the use consists of a capture that happens after a move.
397 {
398 A a;
399 std::move(a);
400 auto lambda = [a]() { a.foo(); };
401 // CHECK-NOTES: [[@LINE-1]]:20: warning: 'a' used after it was moved
402 // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here
403 }
404 // ...even if the capture was implicit.
405 {
406 A a;
407 std::move(a);
408 auto lambda = [=]() { a.foo(); };
409 // CHECK-NOTES: [[@LINE-1]]:20: warning: 'a' used after it was moved
410 // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here
411 }
412 // Same tests but for capture by reference.
413 {
414 A a;
415 std::move(a);
416 auto lambda = [&a]() { a.foo(); };
417 // CHECK-NOTES: [[@LINE-1]]:21: warning: 'a' used after it was moved
418 // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here
419 }
420 {
421 A a;
422 std::move(a);
423 auto lambda = [&]() { a.foo(); };
424 // CHECK-NOTES: [[@LINE-1]]:20: warning: 'a' used after it was moved
425 // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here
426 }
427 // But don't warn if the move happened after the capture.
428 {
429 A a;
430 auto lambda = [a]() { a.foo(); };
431 std::move(a);
432 }
433 // ...and again, same thing with an implicit move.
434 {
435 A a;
436 auto lambda = [=]() { a.foo(); };
437 std::move(a);
438 }
439 // Same tests but for capture by reference.
440 {
441 A a;
442 auto lambda = [&a]() { a.foo(); };
443 std::move(a);
444 }
445 {
446 A a;
447 auto lambda = [&]() { a.foo(); };
448 std::move(a);
449 }
450 {
451 A a;
452 auto lambda = [a = std::move(a)] { a.foo(); };
453 a.foo();
454 // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved
455 // CHECK-NOTES: [[@LINE-3]]:24: note: move occurred here
456 }
457}
458
459// Use-after-moves are detected in uninstantiated templates if the moved type
460// is not a dependent type.
461template <class T>
462void movedTypeIsNotDependentType() {
463 T t;
464 A a;
465 std::move(a);
466 a.foo();
467 // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved
468 // CHECK-NOTES: [[@LINE-3]]:3: note: move occurred here
469}
470
471// And if the moved type is a dependent type, the use-after-move is detected if
472// the template is instantiated.
473template <class T>
474void movedTypeIsDependentType() {
475 T t;
476 std::move(t);
477 t.foo();
478 // CHECK-NOTES: [[@LINE-1]]:3: warning: 't' used after it was moved
479 // CHECK-NOTES: [[@LINE-3]]:3: note: move occurred here
480}
481template void movedTypeIsDependentType<A>();
482
483// We handle the case correctly where the move consists of an implicit call
484// to a conversion operator.
485void implicitConversionOperator() {
486 struct Convertible {
487 operator A() && { return A(); }
488 };
489 void takeA(A a);
490
491 Convertible convertible;
492 takeA(a: std::move(convertible));
493 convertible;
494 // CHECK-NOTES: [[@LINE-1]]:3: warning: 'convertible' used after it was moved
495 // CHECK-NOTES: [[@LINE-3]]:9: note: move occurred here
496}
497
498// Using decltype on an expression is not a use.
499void decltypeIsNotUse() {
500 A a;
501 std::move(a);
502 decltype(a) other_a;
503}
504
505// Ignore moves or uses that occur as part of template arguments.
506template <int>
507class ClassTemplate {
508public:
509 void foo(A a);
510};
511template <int>
512void functionTemplate(A a);
513void templateArgIsNotUse() {
514 {
515 // A pattern like this occurs in the EXPECT_EQ and ASSERT_EQ macros in
516 // Google Test.
517 A a;
518 ClassTemplate<sizeof(A(std::move(a)))>().foo(a: std::move(a));
519 }
520 {
521 A a;
522 functionTemplate<sizeof(A(std::move(a)))>(a: std::move(a));
523 }
524}
525
526// Ignore moves of global variables.
527A global_a;
528void ignoreGlobalVariables() {
529 std::move(global_a);
530 global_a.foo();
531}
532
533// Ignore moves of member variables.
534class IgnoreMemberVariables {
535 A a;
536 static A static_a;
537
538 void f() {
539 std::move(a);
540 a.foo();
541
542 std::move(static_a);
543 static_a.foo();
544 }
545};
546
547// Ignore moves that happen in a try_emplace.
548void ignoreMoveInTryEmplace() {
549 {
550 std::map<int, A> amap;
551 A a;
552 amap.try_emplace(key: 1, args: std::move(a));
553 a.foo();
554 }
555 {
556 std::unordered_map<int, A> amap;
557 A a;
558 amap.try_emplace(key: 1, args: std::move(a));
559 a.foo();
560 }
561}
562
563////////////////////////////////////////////////////////////////////////////////
564// Tests involving control flow.
565
566void useAndMoveInLoop() {
567 // Warn about use-after-moves if they happen in a later loop iteration than
568 // the std::move().
569 {
570 A a;
571 for (int i = 0; i < 10; ++i) {
572 a.foo();
573 // CHECK-NOTES: [[@LINE-1]]:7: warning: 'a' used after it was moved
574 // CHECK-NOTES: [[@LINE+2]]:7: note: move occurred here
575 // CHECK-NOTES: [[@LINE-3]]:7: note: the use happens in a later loop
576 std::move(a);
577 }
578 }
579 // However, this case shouldn't be flagged -- the scope of the declaration of
580 // 'a' is important.
581 {
582 for (int i = 0; i < 10; ++i) {
583 A a;
584 a.foo();
585 std::move(a);
586 }
587 }
588 // Same as above, except that we have an unrelated variable being declared in
589 // the same declaration as 'a'. This case is interesting because it tests that
590 // the synthetic DeclStmts generated by the CFG are sequenced correctly
591 // relative to the other statements.
592 {
593 for (int i = 0; i < 10; ++i) {
594 A a, other;
595 a.foo();
596 std::move(a);
597 }
598 }
599 // Don't warn if we return after the move.
600 {
601 A a;
602 for (int i = 0; i < 10; ++i) {
603 a.foo();
604 if (a.getInt() > 0) {
605 std::move(a);
606 return;
607 }
608 }
609 }
610}
611
612void differentBranches(int i) {
613 // Don't warn if the use is in a different branch from the move.
614 {
615 A a;
616 if (i > 0) {
617 std::move(a);
618 } else {
619 a.foo();
620 }
621 }
622 // Same thing, but with a ternary operator.
623 {
624 A a;
625 i > 0 ? (void)std::move(a) : a.foo();
626 }
627 // A variation on the theme above.
628 {
629 A a;
630 a.getInt() > 0 ? a.getInt() : A(std::move(a)).getInt();
631 }
632 // Same thing, but with a switch statement.
633 {
634 A a;
635 switch (i) {
636 case 1:
637 std::move(a);
638 break;
639 case 2:
640 a.foo();
641 break;
642 }
643 }
644 // However, if there's a fallthrough, we do warn.
645 {
646 A a;
647 switch (i) {
648 case 1:
649 std::move(a);
650 case 2:
651 a.foo();
652 // CHECK-NOTES: [[@LINE-1]]:7: warning: 'a' used after it was moved
653 // CHECK-NOTES: [[@LINE-4]]:7: note: move occurred here
654 break;
655 }
656 }
657}
658
659// False positive: A use-after-move is flagged even though the "if (b)" and
660// "if (!b)" are mutually exclusive.
661void mutuallyExclusiveBranchesFalsePositive(bool b) {
662 A a;
663 if (b) {
664 std::move(a);
665 }
666 if (!b) {
667 a.foo();
668 // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved
669 // CHECK-NOTES: [[@LINE-5]]:5: note: move occurred here
670 }
671}
672
673// Destructors marked [[noreturn]] are handled correctly in the control flow
674// analysis. (These are used in some styles of assertion macros.)
675class FailureLogger {
676public:
677 FailureLogger();
678 [[noreturn]] ~FailureLogger();
679 void log(const char *);
680};
681#define ASSERT(x) \
682 while (x) \
683 FailureLogger().log(#x)
684bool operationOnA(A);
685void noreturnDestructor() {
686 A a;
687 // The while loop in the ASSERT() would ordinarily have the potential to cause
688 // a use-after-move because the second iteration of the loop would be using a
689 // variable that had been moved from in the first iteration. Check that the
690 // CFG knows that the second iteration of the loop is never reached because
691 // the FailureLogger destructor is marked [[noreturn]].
692 ASSERT(operationOnA(std::move(a)));
693}
694#undef ASSERT
695
696////////////////////////////////////////////////////////////////////////////////
697// Tests for reinitializations
698
699template <class T>
700void swap(T &a, T &b) {
701 T tmp = std::move(a);
702 a = std::move(b);
703 b = std::move(tmp);
704}
705void assignments(int i) {
706 // Don't report a use-after-move if the variable was assigned to in the
707 // meantime.
708 {
709 A a;
710 std::move(a);
711 a = A();
712 a.foo();
713 }
714 // The assignment should also be recognized if move, assignment and use don't
715 // all happen in the same block (but the assignment is still guaranteed to
716 // prevent a use-after-move).
717 {
718 A a;
719 if (i == 1) {
720 std::move(a);
721 a = A();
722 }
723 if (i == 2) {
724 a.foo();
725 }
726 }
727 {
728 A a;
729 if (i == 1) {
730 std::move(a);
731 }
732 if (i == 2) {
733 a = A();
734 a.foo();
735 }
736 }
737 // The built-in assignment operator should also be recognized as a
738 // reinitialization. (std::move() may be called on built-in types in template
739 // code.)
740 {
741 int a1 = 1, a2 = 2;
742 swap(a&: a1, b&: a2);
743 }
744 // A std::move() after the assignment makes the variable invalid again.
745 {
746 A a;
747 std::move(a);
748 a = A();
749 std::move(a);
750 a.foo();
751 // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved
752 // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here
753 }
754 // Report a use-after-move if we can't be sure that the variable was assigned
755 // to.
756 {
757 A a;
758 std::move(a);
759 if (i < 10) {
760 a = A();
761 }
762 if (i > 5) {
763 a.foo();
764 // CHECK-NOTES: [[@LINE-1]]:7: warning: 'a' used after it was moved
765 // CHECK-NOTES: [[@LINE-7]]:5: note: move occurred here
766 }
767 }
768}
769
770// Passing the object to a function through a non-const pointer or reference
771// counts as a re-initialization.
772void passByNonConstPointer(A *);
773void passByNonConstReference(A &);
774void passByNonConstPointerIsReinit() {
775 {
776 A a;
777 std::move(a);
778 passByNonConstPointer(&a);
779 a.foo();
780 }
781 {
782 A a;
783 std::move(a);
784 passByNonConstReference(a);
785 a.foo();
786 }
787}
788
789// Passing the object through a const pointer or reference counts as a use --
790// since the called function cannot reinitialize the object.
791void passByConstPointer(const A *);
792void passByConstReference(const A &);
793void passByConstPointerIsUse() {
794 {
795 // Declaring 'a' as const so that no ImplicitCastExpr is inserted into the
796 // AST -- we wouldn't want the check to rely solely on that to detect a
797 // const pointer argument.
798 const A a;
799 std::move(a);
800 passByConstPointer(&a);
801 // CHECK-NOTES: [[@LINE-1]]:25: warning: 'a' used after it was moved
802 // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here
803 }
804 const A a;
805 std::move(a);
806 passByConstReference(a);
807 // CHECK-NOTES: [[@LINE-1]]:24: warning: 'a' used after it was moved
808 // CHECK-NOTES: [[@LINE-3]]:3: note: move occurred here
809}
810
811// Clearing a standard container using clear() is treated as a
812// re-initialization.
813void standardContainerClearIsReinit() {
814 {
815 std::string container;
816 std::move(container);
817 container.clear();
818 container.empty();
819 }
820 {
821 std::vector<int> container;
822 std::move(container);
823 container.clear();
824 container.empty();
825
826 auto container2 = container;
827 std::move(container2);
828 container2.clear();
829 container2.empty();
830 }
831 {
832 std::deque<int> container;
833 std::move(container);
834 container.clear();
835 container.empty();
836 }
837 {
838 std::forward_list<int> container;
839 std::move(container);
840 container.clear();
841 container.empty();
842 }
843 {
844 std::list<int> container;
845 std::move(container);
846 container.clear();
847 container.empty();
848 }
849 {
850 std::set<int> container;
851 std::move(container);
852 container.clear();
853 container.empty();
854 }
855 {
856 std::map<int, int> container;
857 std::move(container);
858 container.clear();
859 container.empty();
860 }
861 {
862 std::multiset<int> container;
863 std::move(container);
864 container.clear();
865 container.empty();
866 }
867 {
868 std::multimap<int> container;
869 std::move(container);
870 container.clear();
871 container.empty();
872 }
873 {
874 std::unordered_set<int> container;
875 std::move(container);
876 container.clear();
877 container.empty();
878 }
879 {
880 std::unordered_map<int, int> container;
881 std::move(container);
882 container.clear();
883 container.empty();
884 }
885 {
886 std::unordered_multiset<int> container;
887 std::move(container);
888 container.clear();
889 container.empty();
890 }
891 {
892 std::unordered_multimap<int> container;
893 std::move(container);
894 container.clear();
895 container.empty();
896 }
897 // This should also work for typedefs of standard containers.
898 {
899 typedef std::vector<int> IntVector;
900 IntVector container;
901 std::move(container);
902 container.clear();
903 container.empty();
904 }
905 // But it shouldn't work for non-standard containers.
906 {
907 // This might be called "vector", but it's not in namespace "std".
908 struct vector {
909 void clear() {}
910 } container;
911 std::move(container);
912 container.clear();
913 // CHECK-NOTES: [[@LINE-1]]:5: warning: 'container' used after it was
914 // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here
915 }
916 // An intervening clear() on a different container does not reinitialize.
917 {
918 std::vector<int> container1, container2;
919 std::move(container1);
920 container2.clear();
921 container1.empty();
922 // CHECK-NOTES: [[@LINE-1]]:5: warning: 'container1' used after it was
923 // CHECK-NOTES: [[@LINE-4]]:5: note: move occurred here
924 }
925}
926
927// Clearing a standard container using assign() is treated as a
928// re-initialization.
929void standardContainerAssignIsReinit() {
930 {
931 std::string container;
932 std::move(container);
933 container.assign(0, ' ');
934 container.empty();
935 }
936 {
937 std::vector<int> container;
938 std::move(container);
939 container.assign(0, 0);
940 container.empty();
941 }
942 {
943 std::deque<int> container;
944 std::move(container);
945 container.assign(0, 0);
946 container.empty();
947 }
948 {
949 std::forward_list<int> container;
950 std::move(container);
951 container.assign(0, 0);
952 container.empty();
953 }
954 {
955 std::list<int> container;
956 std::move(container);
957 container.clear();
958 container.empty();
959 }
960 // But it doesn't work for non-standard containers.
961 {
962 // This might be called "vector", but it's not in namespace "std".
963 struct vector {
964 void assign(std::size_t, int) {}
965 } container;
966 std::move(container);
967 container.assign(0, 0);
968 // CHECK-NOTES: [[@LINE-1]]:5: warning: 'container' used after it was
969 // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here
970 }
971 // An intervening assign() on a different container does not reinitialize.
972 {
973 std::vector<int> container1, container2;
974 std::move(container1);
975 container2.assign(0, 0);
976 container1.empty();
977 // CHECK-NOTES: [[@LINE-1]]:5: warning: 'container1' used after it was
978 // CHECK-NOTES: [[@LINE-4]]:5: note: move occurred here
979 }
980}
981
982// Resetting the standard smart pointer types using reset() is treated as a
983// re-initialization. (We don't test std::weak_ptr<> because it can't be
984// dereferenced directly.)
985void standardSmartPointerResetIsReinit() {
986 {
987 std::unique_ptr<A> ptr;
988 std::move(ptr);
989 ptr.reset(ptr: new A);
990 *ptr;
991 }
992 {
993 std::shared_ptr<A> ptr;
994 std::move(ptr);
995 ptr.reset(ptr: new A);
996 *ptr;
997 }
998}
999
1000void reinitAnnotation() {
1001 {
1002 AnnotatedContainer<int> obj;
1003 std::move(obj);
1004 obj.foo();
1005 // CHECK-NOTES: [[@LINE-1]]:5: warning: 'obj' used after it was
1006 // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here
1007 }
1008 {
1009 AnnotatedContainer<int> obj;
1010 std::move(obj);
1011 obj.clear();
1012 obj.foo();
1013 }
1014 {
1015 // Calling clear() on a different object to the one that was moved is not
1016 // considered a reinitialization.
1017 AnnotatedContainer<int> obj1, obj2;
1018 std::move(obj1);
1019 obj2.clear();
1020 obj1.foo();
1021 // CHECK-NOTES: [[@LINE-1]]:5: warning: 'obj1' used after it was
1022 // CHECK-NOTES: [[@LINE-4]]:5: note: move occurred here
1023 }
1024}
1025
1026////////////////////////////////////////////////////////////////////////////////
1027// Tests related to order of evaluation within expressions
1028
1029// Relative sequencing of move and use.
1030void passByRvalueReference(int i, A &&a);
1031void passByValue(int i, A a);
1032void passByValue(A a, int i);
1033A g(A, A &&);
1034int intFromA(A &&);
1035int intFromInt(int);
1036void sequencingOfMoveAndUse() {
1037 // This case is fine because the move only happens inside
1038 // passByRvalueReference(). At this point, a.getInt() is guaranteed to have
1039 // been evaluated.
1040 {
1041 A a;
1042 passByRvalueReference(i: a.getInt(), a: std::move(a));
1043 }
1044 // However, if we pass by value, the move happens when the move constructor is
1045 // called to create a temporary, and this happens before the call to
1046 // passByValue(). Because the order in which arguments are evaluated isn't
1047 // defined, the move may happen before the call to a.getInt().
1048 //
1049 // Check that we warn about a potential use-after move for both orderings of
1050 // a.getInt() and std::move(a), independent of the order in which the
1051 // arguments happen to get evaluated by the compiler.
1052 {
1053 A a;
1054 passByValue(i: a.getInt(), a: std::move(a));
1055 // CHECK-NOTES: [[@LINE-1]]:17: warning: 'a' used after it was moved
1056 // CHECK-NOTES: [[@LINE-2]]:29: note: move occurred here
1057 // CHECK-NOTES: [[@LINE-3]]:17: note: the use and move are unsequenced
1058 }
1059 {
1060 A a;
1061 passByValue(a: std::move(a), i: a.getInt());
1062 // CHECK-NOTES: [[@LINE-1]]:31: warning: 'a' used after it was moved
1063 // CHECK-NOTES: [[@LINE-2]]:17: note: move occurred here
1064 // CHECK-NOTES: [[@LINE-3]]:31: note: the use and move are unsequenced
1065 }
1066 // An even more convoluted example.
1067 {
1068 A a;
1069 g(g(a, std::move(a)), g(a, std::move(a)));
1070 // CHECK-NOTES: [[@LINE-1]]:9: warning: 'a' used after it was moved
1071 // CHECK-NOTES: [[@LINE-2]]:27: note: move occurred here
1072 // CHECK-NOTES: [[@LINE-3]]:9: note: the use and move are unsequenced
1073 // CHECK-NOTES: [[@LINE-4]]:29: warning: 'a' used after it was moved
1074 // CHECK-NOTES: [[@LINE-5]]:7: note: move occurred here
1075 // CHECK-NOTES: [[@LINE-6]]:29: note: the use and move are unsequenced
1076 }
1077 // This case is fine because the actual move only happens inside the call to
1078 // operator=(). a.getInt(), by necessity, is evaluated before that call.
1079 {
1080 A a;
1081 A vec[1];
1082 vec[a.getInt()] = std::move(a);
1083 }
1084 // However, in the following case, the move happens before the assignment, and
1085 // so the order of evaluation is not guaranteed.
1086 {
1087 A a;
1088 int v[3];
1089 v[a.getInt()] = intFromA(std::move(a));
1090 // CHECK-NOTES: [[@LINE-1]]:7: warning: 'a' used after it was moved
1091 // CHECK-NOTES: [[@LINE-2]]:21: note: move occurred here
1092 // CHECK-NOTES: [[@LINE-3]]:7: note: the use and move are unsequenced
1093 }
1094 {
1095 A a;
1096 int v[3];
1097 v[intFromA(std::move(a))] = intFromInt(a.i);
1098 // CHECK-NOTES: [[@LINE-1]]:44: warning: 'a' used after it was moved
1099 // CHECK-NOTES: [[@LINE-2]]:7: note: move occurred here
1100 // CHECK-NOTES: [[@LINE-3]]:44: note: the use and move are unsequenced
1101 }
1102}
1103
1104// Relative sequencing of move and reinitialization. If the two are unsequenced,
1105// we conservatively assume that the move happens after the reinitialization,
1106// i.e. the that object does not get reinitialized after the move.
1107A MutateA(A a);
1108void passByValue(A a1, A a2);
1109void sequencingOfMoveAndReinit() {
1110 // Move and reinitialization as function arguments (which are indeterminately
1111 // sequenced). Again, check that we warn for both orderings.
1112 {
1113 A a;
1114 passByValue(a1: std::move(a), a2: (a = A()));
1115 a.foo();
1116 // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved
1117 // CHECK-NOTES: [[@LINE-3]]:17: note: move occurred here
1118 }
1119 {
1120 A a;
1121 passByValue(a1: (a = A()), a2: std::move(a));
1122 a.foo();
1123 // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved
1124 // CHECK-NOTES: [[@LINE-3]]:28: note: move occurred here
1125 }
1126 // Common usage pattern: Move the object to a function that mutates it in some
1127 // way, then reassign the result to the object. This pattern is fine.
1128 {
1129 A a;
1130 a = MutateA(a: std::move(a));
1131 a.foo();
1132 }
1133}
1134
1135// Relative sequencing of reinitialization and use. If the two are unsequenced,
1136// we conservatively assume that the reinitialization happens after the use,
1137// i.e. that the object is not reinitialized at the point in time when it is
1138// used.
1139void sequencingOfReinitAndUse() {
1140 // Reinitialization and use in function arguments. Again, check both possible
1141 // orderings.
1142 {
1143 A a;
1144 std::move(a);
1145 passByValue(i: a.getInt(), a: (a = A()));
1146 // CHECK-NOTES: [[@LINE-1]]:17: warning: 'a' used after it was moved
1147 // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here
1148 }
1149 {
1150 A a;
1151 std::move(a);
1152 passByValue(a: (a = A()), i: a.getInt());
1153 // CHECK-NOTES: [[@LINE-1]]:28: warning: 'a' used after it was moved
1154 // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here
1155 }
1156}
1157
1158// The comma operator sequences its operands.
1159void commaOperatorSequences() {
1160 {
1161 A a;
1162 A(std::move(a))
1163 , (a = A());
1164 a.foo();
1165 }
1166 {
1167 A a;
1168 (a = A()), A(std::move(a));
1169 a.foo();
1170 // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved
1171 // CHECK-NOTES: [[@LINE-3]]:16: note: move occurred here
1172 }
1173}
1174
1175namespace InitializerListSequences {
1176
1177struct S1 {
1178 int i;
1179 A a;
1180};
1181
1182struct S2 {
1183 A a;
1184 int i;
1185};
1186
1187struct S3 {
1188 S3() {}
1189 S3(int, A) {}
1190 S3(A, int) {}
1191};
1192
1193// An initializer list sequences its initialization clauses.
1194void initializerListSequences() {
1195 {
1196 A a;
1197 S1 s1{.i: a.getInt(), .a: std::move(a)};
1198 }
1199 {
1200 A a;
1201 S1 s1{.i = a.getInt(), .a = std::move(a)};
1202 }
1203 {
1204 A a;
1205 S2 s2{.a: std::move(a), .i: a.getInt()};
1206 // CHECK-NOTES: [[@LINE-1]]:25: warning: 'a' used after it was moved
1207 // CHECK-NOTES: [[@LINE-2]]:11: note: move occurred here
1208 }
1209 {
1210 A a;
1211 S2 s2{.a = std::move(a), .i = a.getInt()};
1212 // CHECK-NOTES: [[@LINE-1]]:35: warning: 'a' used after it was moved
1213 // CHECK-NOTES: [[@LINE-2]]:11: note: move occurred here
1214 }
1215 {
1216 // Check the case where the constructed type has a constructor and the
1217 // initializer list therefore manifests as a `CXXConstructExpr` instead of
1218 // an `InitListExpr`.
1219 A a;
1220 S3 s3{a.getInt(), std::move(a)};
1221 }
1222 {
1223 A a;
1224 S3 s3{std::move(a), a.getInt()};
1225 // CHECK-NOTES: [[@LINE-1]]:25: warning: 'a' used after it was moved
1226 // CHECK-NOTES: [[@LINE-2]]:11: note: move occurred here
1227 }
1228}
1229
1230} // namespace InitializerListSequences
1231
1232// A declaration statement containing multiple declarations sequences the
1233// initializer expressions.
1234void declarationSequences() {
1235 {
1236 A a;
1237 A a1 = a, a2 = std::move(a);
1238 }
1239 {
1240 A a;
1241 A a1 = std::move(a), a2 = a;
1242 // CHECK-NOTES: [[@LINE-1]]:31: warning: 'a' used after it was moved
1243 // CHECK-NOTES: [[@LINE-2]]:12: note: move occurred here
1244 }
1245}
1246
1247// The logical operators && and || sequence their operands.
1248void logicalOperatorsSequence() {
1249 {
1250 A a;
1251 if (a.getInt() > 0 && A(std::move(a)).getInt() > 0) {
1252 A().foo();
1253 }
1254 }
1255 // A variation: Negate the result of the && (which pushes the && further down
1256 // into the AST).
1257 {
1258 A a;
1259 if (!(a.getInt() > 0 && A(std::move(a)).getInt() > 0)) {
1260 A().foo();
1261 }
1262 }
1263 {
1264 A a;
1265 if (A(std::move(a)).getInt() > 0 && a.getInt() > 0) {
1266 // CHECK-NOTES: [[@LINE-1]]:41: warning: 'a' used after it was moved
1267 // CHECK-NOTES: [[@LINE-2]]:9: note: move occurred here
1268 A().foo();
1269 }
1270 }
1271 {
1272 A a;
1273 if (a.getInt() > 0 || A(std::move(a)).getInt() > 0) {
1274 A().foo();
1275 }
1276 }
1277 {
1278 A a;
1279 if (A(std::move(a)).getInt() > 0 || a.getInt() > 0) {
1280 // CHECK-NOTES: [[@LINE-1]]:41: warning: 'a' used after it was moved
1281 // CHECK-NOTES: [[@LINE-2]]:9: note: move occurred here
1282 A().foo();
1283 }
1284 }
1285}
1286
1287// A range-based for sequences the loop variable declaration before the body.
1288void forRangeSequences() {
1289 A v[2] = {A(), A()};
1290 for (A &a : v) {
1291 std::move(a);
1292 }
1293}
1294
1295// If a variable is declared in an if, while or switch statement, the init
1296// statement (for if and switch) is sequenced before the variable declaration,
1297// which in turn is sequenced before the evaluation of the condition. We place
1298// all tests inside a for loop to ensure that the checker understands the
1299// sequencing. If it didn't, then the loop would trigger the "moved twice"
1300// logic.
1301void ifWhileAndSwitchSequenceInitDeclAndCondition() {
1302 for (int i = 0; i < 10; ++i) {
1303 A a1;
1304 if (A a2 = std::move(a1)) {
1305 std::move(a2);
1306 }
1307 }
1308 for (int i = 0; i < 10; ++i) {
1309 A a1;
1310 if (A a2 = std::move(a1); a2) {
1311 std::move(a2);
1312 }
1313 }
1314 for (int i = 0; i < 10; ++i) {
1315 A a1;
1316 if (A a2 = std::move(a1); A a3 = std::move(a2)) {
1317 std::move(a3);
1318 }
1319 }
1320 for (int i = 0; i < 10; ++i) {
1321 // init followed by condition with move, but without variable declaration.
1322 if (A a1; A(std::move(a1)).getInt() > 0) {}
1323 }
1324 for (int i = 0; i < 10; ++i) {
1325 if (A a1; A(std::move(a1)).getInt() > a1.getInt()) {}
1326 // CHECK-NOTES: [[@LINE-1]]:43: warning: 'a1' used after it was moved
1327 // CHECK-NOTES: [[@LINE-2]]:15: note: move occurred here
1328 // CHECK-NOTES: [[@LINE-3]]:43: note: the use and move are unsequenced
1329 }
1330 for (int i = 0; i < 10; ++i) {
1331 A a1;
1332 if (A a2 = std::move(a1); A(a1) > 0) {}
1333 // CHECK-NOTES: [[@LINE-1]]:33: warning: 'a1' used after it was moved
1334 // CHECK-NOTES: [[@LINE-2]]:16: note: move occurred here
1335 }
1336 while (A a = A()) {
1337 std::move(a);
1338 }
1339 for (int i = 0; i < 10; ++i) {
1340 A a1;
1341 switch (A a2 = std::move(a1); a2) {
1342 case true:
1343 std::move(a2);
1344 }
1345 }
1346 for (int i = 0; i < 10; ++i) {
1347 A a1;
1348 switch (A a2 = a1; A a3 = std::move(a2)) {
1349 case true:
1350 std::move(a3);
1351 }
1352 }
1353}
1354
1355// Some statements in templates (e.g. null, break and continue statements) may
1356// be shared between the uninstantiated and instantiated versions of the
1357// template and therefore have multiple parents. Make sure the sequencing code
1358// handles this correctly.
1359template <class> void nullStatementSequencesInTemplate() {
1360 int c = 0;
1361 (void)c;
1362 ;
1363 std::move(c);
1364}
1365template void nullStatementSequencesInTemplate<int>();
1366
1367namespace PR33020 {
1368class D {
1369 ~D();
1370};
1371struct A {
1372 D d;
1373};
1374class B {
1375 A a;
1376};
1377template <typename T>
1378class C : T, B {
1379 void m_fn1() {
1380 int a;
1381 std::move(a);
1382 C c;
1383 }
1384};
1385} // namespace PR33020
1386
1387namespace UnevalContext {
1388struct Foo {};
1389void noExcept() {
1390 Foo Bar;
1391 (void) noexcept(Foo{std::move(Bar)});
1392 Foo Other{std::move(Bar)};
1393}
1394void sizeOf() {
1395 Foo Bar;
1396 (void)sizeof(Foo{std::move(Bar)});
1397 Foo Other{std::move(Bar)};
1398}
1399void alignOf() {
1400 Foo Bar;
1401#pragma clang diagnostic push
1402#pragma clang diagnostic ignored "-Wgnu-alignof-expression"
1403 (void)alignof(Foo{std::move(Bar)});
1404#pragma clang diagnostic pop
1405 Foo Other{std::move(Bar)};
1406}
1407void typeId() {
1408 Foo Bar;
1409 // error: you need to include <typeinfo> before using the 'typeid' operator
1410 // (void) typeid(Foo{std::move(Bar)}).name();
1411 Foo Other{std::move(Bar)};
1412}
1413} // namespace UnevalContext
1414
1415class CtorInit {
1416public:
1417 CtorInit(std::string val)
1418 : a{val.empty()}, // fine
1419 s{std::move(val)},
1420 b{val.empty()}
1421 // CHECK-NOTES: [[@LINE-1]]:11: warning: 'val' used after it was moved
1422 // CHECK-NOTES: [[@LINE-3]]:9: note: move occurred here
1423 {}
1424
1425private:
1426 bool a;
1427 std::string s;
1428 bool b;
1429};
1430
1431class CtorInitLambda {
1432public:
1433 CtorInitLambda(std::string val)
1434 : a{val.empty()}, // fine
1435 s{std::move(val)},
1436 b{[&] { return val.empty(); }()},
1437 // CHECK-NOTES: [[@LINE-1]]:12: warning: 'val' used after it was moved
1438 // CHECK-NOTES: [[@LINE-3]]:9: note: move occurred here
1439 c{[] {
1440 std::string str{};
1441 std::move(str);
1442 return str.empty();
1443 // CHECK-NOTES: [[@LINE-1]]:18: warning: 'str' used after it was moved
1444 // CHECK-NOTES: [[@LINE-3]]:11: note: move occurred here
1445 }()} {
1446 std::move(val);
1447 // CHECK-NOTES: [[@LINE-1]]:15: warning: 'val' used after it was moved
1448 // CHECK-NOTES: [[@LINE-13]]:9: note: move occurred here
1449 std::string val2{};
1450 std::move(val2);
1451 val2.empty();
1452 // CHECK-NOTES: [[@LINE-1]]:5: warning: 'val2' used after it was moved
1453 // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here
1454 }
1455
1456private:
1457 bool a;
1458 std::string s;
1459 bool b;
1460 bool c;
1461 bool d{};
1462};
1463
1464class CtorInitOrder {
1465public:
1466 CtorInitOrder(std::string val)
1467 : a{val.empty()}, // fine
1468 b{val.empty()},
1469 // CHECK-NOTES: [[@LINE-1]]:11: warning: 'val' used after it was moved
1470 s{std::move(val)} {} // wrong order
1471 // CHECK-NOTES: [[@LINE-1]]:9: note: move occurred here
1472 // CHECK-NOTES: [[@LINE-4]]:11: note: the use happens in a later loop iteration than the move
1473
1474private:
1475 bool a;
1476 std::string s;
1477 bool b;
1478};
1479
1480struct Obj {};
1481struct CtorD {
1482 CtorD(Obj b);
1483};
1484
1485struct CtorC {
1486 CtorC(Obj b);
1487};
1488
1489struct CtorB {
1490 CtorB(Obj &b);
1491};
1492
1493struct CtorA : CtorB, CtorC, CtorD {
1494 CtorA(Obj b) : CtorB{b}, CtorC{std::move(b)}, CtorD{b} {}
1495 // CHECK-NOTES: [[@LINE-1]]:55: warning: 'b' used after it was moved
1496 // CHECK-NOTES: [[@LINE-2]]:34: note: move occurred here
1497};
1498
1499struct Base {
1500 Base(Obj b) : bb{std::move(b)} {}
1501 template <typename Call> Base(Call &&c) : bb{c()} {};
1502
1503 Obj bb;
1504};
1505
1506struct Derived : Base, CtorC {
1507 Derived(Obj b)
1508 : Base{[&] mutable { return std::move(b); }()},
1509 // False negative: The lambda/std::move was executed, so it should warn
1510 // below
1511 CtorC{b} {}
1512};
1513
1514struct Derived2 : Base, CtorC {
1515 Derived2(Obj b)
1516 : Base{[&] mutable { return std::move(b); }},
1517 // This was a move, but it doesn't warn below, because it can't know if
1518 // the lambda/std::move was actually called
1519 CtorC{b} {}
1520};
1521
1522struct Derived3 : Base, CtorC {
1523 Derived3(Obj b)
1524 : Base{[c = std::move(b)] mutable { return std::move(c); }}, CtorC{b} {}
1525 // CHECK-NOTES: [[@LINE-1]]:74: warning: 'b' used after it was moved
1526 // CHECK-NOTES: [[@LINE-2]]:19: note: move occurred here
1527};
1528
1529class PR38187 {
1530public:
1531 PR38187(std::string val) : val_(std::move(val)) {
1532 val.empty();
1533 // CHECK-NOTES: [[@LINE-1]]:5: warning: 'val' used after it was moved
1534 // CHECK-NOTES: [[@LINE-3]]:30: note: move occurred here
1535 }
1536
1537private:
1538 std::string val_;
1539};
1540
1541namespace issue82023
1542{
1543
1544struct S {
1545 S();
1546 S(S&&);
1547};
1548
1549void consume(S s);
1550
1551template <typename T>
1552void forward(T&& t) {
1553 consume(std::forward<T>(t));
1554 consume(std::forward<T>(t));
1555 // CHECK-NOTES: [[@LINE-1]]:27: warning: 't' used after it was forwarded
1556 // CHECK-NOTES: [[@LINE-3]]:11: note: forward occurred here
1557}
1558
1559void create() {
1560 S s;
1561 forward(t: std::move(s));
1562}
1563
1564} // namespace issue82023
1565

source code of clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move.cpp