brintos

brintos / llvm-project-archived public Read only

0
0
Text · 41.8 KiB · 87dfec4 Raw
1648 lines · cpp
1// RUN: %check_clang_tidy -std=c++11 -check-suffixes=,CXX11 %s bugprone-use-after-move %t -- -- -fno-delayed-template-parsing2// RUN: %check_clang_tidy -std=c++17-or-later %s bugprone-use-after-move %t -- -- -fno-delayed-template-parsing3 4typedef decltype(nullptr) nullptr_t;5 6namespace std {7typedef unsigned size_t;8 9template <typename T>10struct unique_ptr {11  unique_ptr();12  T *get() const;13  explicit operator bool() const;14  void reset(T *ptr);15  T &operator*() const;16  T *operator->() const;17  T& operator[](size_t i) const;18};19 20template <typename T>21struct shared_ptr {22  shared_ptr();23  T *get() const;24  explicit operator bool() const;25  void reset(T *ptr);26  T &operator*() const;27  T *operator->() const;28};29 30template <typename T>31struct weak_ptr {32  weak_ptr();33  bool expired() const;34};35 36template <typename T>37struct optional {38  optional();39  T& operator*();40  const T& operator*() const;41  void reset();42};43 44struct any {45  any();46  void reset();47};48 49template <typename T1, typename T2>50struct pair {};51 52template <typename Key, typename T>53struct map {54  struct iterator {};55 56  map();57  void clear();58  bool empty();59  template <class... Args>60  pair<iterator, bool> try_emplace(const Key &key, Args &&...args);61};62 63template <typename Key, typename T>64struct unordered_map {65  struct iterator {};66 67  unordered_map();68  void clear();69  bool empty();70  template <class... Args>71  pair<iterator, bool> try_emplace(const Key &key, Args &&...args);72};73 74#define DECLARE_STANDARD_CONTAINER(name) \75  template <typename T>                  \76  struct name {                          \77    name();                              \78    void clear();                        \79    bool empty();                        \80  }81 82#define DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(name) \83  template <typename T>                              \84  struct name {                                      \85    name();                                          \86    void clear();                                    \87    bool empty();                                    \88    void assign(size_t, const T &);                  \89  }90 91DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(basic_string);92DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(vector);93DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(deque);94DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(forward_list);95DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(list);96DECLARE_STANDARD_CONTAINER(set);97DECLARE_STANDARD_CONTAINER(multiset);98DECLARE_STANDARD_CONTAINER(multimap);99DECLARE_STANDARD_CONTAINER(unordered_set);100DECLARE_STANDARD_CONTAINER(unordered_multiset);101DECLARE_STANDARD_CONTAINER(unordered_multimap);102 103typedef basic_string<char> string;104 105template <typename>106struct remove_reference;107 108template <typename _Tp>109struct remove_reference {110  typedef _Tp type;111};112 113template <typename _Tp>114struct remove_reference<_Tp &> {115  typedef _Tp type;116};117 118template <typename _Tp>119struct remove_reference<_Tp &&> {120  typedef _Tp type;121};122 123template <typename _Tp>124constexpr typename std::remove_reference<_Tp>::type &&move(_Tp &&__t) noexcept {125  return static_cast<typename remove_reference<_Tp>::type &&>(__t);126}127 128template <class _Tp>129constexpr _Tp&&130forward(typename std::remove_reference<_Tp>::type& __t) noexcept {131  return static_cast<_Tp&&>(__t);132}133 134template <class _Tp>135constexpr _Tp&&136forward(typename std::remove_reference<_Tp>::type&& __t) noexcept {137  return static_cast<_Tp&&>(__t);138}139 140} // namespace std141 142class A {143public:144  A();145  A(const A &);146  A(A &&);147 148  A &operator=(const A &);149  A &operator=(A &&);150 151  void foo() const;152  void bar(int i) const;153  int getInt() const;154 155  operator bool() const;156 157  int i;158};159 160template <class T>161class AnnotatedContainer {162public:163  AnnotatedContainer();164 165  void foo() const;166  [[clang::reinitializes]] void clear();167};168 169////////////////////////////////////////////////////////////////////////////////170// General tests.171 172// Simple case.173void simple() {174  A a;175  a.foo();176  A other_a = std::move(a);177  a.foo();178  // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved179  // CHECK-NOTES: [[@LINE-3]]:15: note: move occurred here180}181 182// Don't flag a move-to-self.183void selfMove() {184  A a;185  a = std::move(a);186  a.foo();187}188 189// A warning should only be emitted for one use-after-move.190void onlyFlagOneUseAfterMove() {191  A a;192  a.foo();193  A other_a = std::move(a);194  a.foo();195  // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved196  // CHECK-NOTES: [[@LINE-3]]:15: note: move occurred here197  a.foo();198}199 200void moveAfterMove() {201  // Move-after-move also counts as a use.202  {203    A a;204    std::move(a);205    std::move(a);206    // CHECK-NOTES: [[@LINE-1]]:15: warning: 'a' used after it was moved207    // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here208  }209  // This is also true if the move itself turns into the use on the second loop210  // iteration.211  {212    A a;213    for (int i = 0; i < 10; ++i) {214      std::move(a);215      // CHECK-NOTES: [[@LINE-1]]:17: warning: 'a' used after it was moved216      // CHECK-NOTES: [[@LINE-2]]:7: note: move occurred here217      // CHECK-NOTES: [[@LINE-3]]:17: note: the use happens in a later loop218    }219  }220}221 222// Checks also works on function parameters that have a use-after move.223void parameters(A a) {224  std::move(a);225  a.foo();226  // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved227  // CHECK-NOTES: [[@LINE-3]]:3: note: move occurred here228}229 230void standardSmartPtr() {231  // std::unique_ptr<>, std::shared_ptr<> and std::weak_ptr<> are guaranteed to232  // be null after a std::move. So the check only flags accesses that would233  // dereference the pointer.234  {235    std::unique_ptr<A> ptr;236    std::move(ptr);237    ptr.get();238    static_cast<bool>(ptr);239    *ptr;240    // CHECK-NOTES: [[@LINE-1]]:6: warning: 'ptr' used after it was moved241    // CHECK-NOTES: [[@LINE-5]]:5: note: move occurred here242  }243  {244    std::unique_ptr<A> ptr;245    std::move(ptr);246    ptr->foo();247    // CHECK-NOTES: [[@LINE-1]]:5: warning: 'ptr' used after it was moved248    // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here249  }250  {251    std::unique_ptr<A> ptr;252    std::move(ptr);253    ptr[0];254    // CHECK-NOTES: [[@LINE-1]]:5: warning: 'ptr' used after it was moved255    // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here256  }257  {258    std::shared_ptr<A> ptr;259    std::move(ptr);260    ptr.get();261    static_cast<bool>(ptr);262    *ptr;263    // CHECK-NOTES: [[@LINE-1]]:6: warning: 'ptr' used after it was moved264    // CHECK-NOTES: [[@LINE-5]]:5: note: move occurred here265  }266  {267    std::shared_ptr<A> ptr;268    std::move(ptr);269    ptr->foo();270    // CHECK-NOTES: [[@LINE-1]]:5: warning: 'ptr' used after it was moved271    // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here272  }273  {274    std::optional<A> opt;275    std::move(opt);276    A val = *opt;277    (void)val;278    // CHECK-NOTES: [[@LINE-2]]:14: warning: 'opt' used after it was moved279    // CHECK-NOTES: [[@LINE-4]]:5: note: move occurred here280  }281  {282    // std::weak_ptr<> cannot be dereferenced directly, so we only check that283    // member functions may be called on it after a move.284    std::weak_ptr<A> ptr;285    std::move(ptr);286    ptr.expired();287  }288  // Make sure we recognize std::unique_ptr<> or std::shared_ptr<> if they're289  // wrapped in a typedef.290  {291    typedef std::unique_ptr<A> PtrToA;292    PtrToA ptr;293    std::move(ptr);294    ptr.get();295  }296  {297    typedef std::shared_ptr<A> PtrToA;298    PtrToA ptr;299    std::move(ptr);300    ptr.get();301  }302  // And we don't get confused if the template argument is a little more303  // involved.304  {305    struct B {306      typedef A AnotherNameForA;307    };308    std::unique_ptr<B::AnotherNameForA> ptr;309    std::move(ptr);310    ptr.get();311  }312  // Make sure we treat references to smart pointers correctly.313  {314    std::unique_ptr<A> ptr;315    std::unique_ptr<A>& ref_to_ptr = ptr;316    std::move(ref_to_ptr);317    ref_to_ptr.get();318  }319  {320    std::unique_ptr<A> ptr;321    std::unique_ptr<A>&& rvalue_ref_to_ptr = std::move(ptr);322    std::move(rvalue_ref_to_ptr);323    rvalue_ref_to_ptr.get();324  }325  // We don't give any special treatment to types that are called "unique_ptr"326  // or "shared_ptr" but are not in the "::std" namespace.327  {328    struct unique_ptr {329      void get();330    } ptr;331    std::move(ptr);332    ptr.get();333    // CHECK-NOTES: [[@LINE-1]]:5: warning: 'ptr' used after it was moved334    // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here335  }336}337 338// The check also works in member functions.339class Container {340  void useAfterMoveInMemberFunction() {341    A a;342    std::move(a);343    a.foo();344    // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved345    // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here346  }347};348 349// We see the std::move() if it's inside a declaration.350void moveInDeclaration() {351  A a;352  A another_a(std::move(a));353  a.foo();354  // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved355  // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here356}357 358// We see the std::move if it's inside an initializer list. Initializer lists359// are a special case because they cause ASTContext::getParents() to return360// multiple parents for certain nodes in their subtree. This is because361// RecursiveASTVisitor visits both the syntactic and semantic forms of362// InitListExpr, and the parent-child relationships are different between the363// two forms.364void moveInInitList() {365  struct S {366    A a;367  };368  A a;369  S s{std::move(a)};370  a.foo();371  // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved372  // CHECK-NOTES: [[@LINE-3]]:7: note: move occurred here373}374 375void lambdas() {376  // Use-after-moves inside a lambda should be detected.377  {378    A a;379    auto lambda = [a] {380      std::move(a);381      a.foo();382      // CHECK-NOTES: [[@LINE-1]]:7: warning: 'a' used after it was moved383      // CHECK-NOTES: [[@LINE-3]]:7: note: move occurred here384    };385  }386  // This is just as true if the variable was declared inside the lambda.387  {388    auto lambda = [] {389      A a;390      std::move(a);391      a.foo();392      // CHECK-NOTES: [[@LINE-1]]:7: warning: 'a' used after it was moved393      // CHECK-NOTES: [[@LINE-3]]:7: note: move occurred here394    };395  }396  // But don't warn if the move happened inside the lambda but the use happened397  // outside -- because398  // - the 'a' inside the lambda is a copy, and399  // - we don't know when the lambda will get called anyway400  {401    A a;402    auto lambda = [a] {403      std::move(a);404    };405    a.foo();406  }407  // Don't warn if 'a' is a copy inside a synchronous lambda408  {409    A a;410    A copied{[a] mutable { return std::move(a); }()};411    a.foo();412  }413  // False negative (should warn if 'a' is a ref inside a synchronous lambda)414  {415    A a;416    A moved{[&a] mutable { return std::move(a); }()};417    a.foo();418  }419  // Warn if the use consists of a capture that happens after a move.420  {421    A a;422    std::move(a);423    auto lambda = [a]() { a.foo(); };424    // CHECK-NOTES: [[@LINE-1]]:20: warning: 'a' used after it was moved425    // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here426  }427  // ...even if the capture was implicit.428  {429    A a;430    std::move(a);431    auto lambda = [=]() { a.foo(); };432    // CHECK-NOTES: [[@LINE-1]]:20: warning: 'a' used after it was moved433    // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here434  }435  // Same tests but for capture by reference.436  {437    A a;438    std::move(a);439    auto lambda = [&a]() { a.foo(); };440    // CHECK-NOTES: [[@LINE-1]]:21: warning: 'a' used after it was moved441    // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here442  }443  {444    A a;445    std::move(a);446    auto lambda = [&]() { a.foo(); };447    // CHECK-NOTES: [[@LINE-1]]:20: warning: 'a' used after it was moved448    // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here449  }450  // But don't warn if the move happened after the capture.451  {452    A a;453    auto lambda = [a]() { a.foo(); };454    std::move(a);455  }456  // ...and again, same thing with an implicit move.457  {458    A a;459    auto lambda = [=]() { a.foo(); };460    std::move(a);461  }462  // Same tests but for capture by reference.463  {464    A a;465    auto lambda = [&a]() { a.foo(); };466    std::move(a);467  }468  {469    A a;470    auto lambda = [&]() { a.foo(); };471    std::move(a);472  }473  {474    A a;475    auto lambda = [a = std::move(a)] { a.foo(); };476    a.foo();477    // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved478    // CHECK-NOTES: [[@LINE-3]]:24: note: move occurred here479  }480}481 482// Use-after-moves are detected in uninstantiated templates if the moved type483// is not a dependent type.484template <class T>485void movedTypeIsNotDependentType() {486  T t;487  A a;488  std::move(a);489  a.foo();490  // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved491  // CHECK-NOTES: [[@LINE-3]]:3: note: move occurred here492}493 494// And if the moved type is a dependent type, the use-after-move is detected if495// the template is instantiated.496template <class T>497void movedTypeIsDependentType() {498  T t;499  std::move(t);500  t.foo();501  // CHECK-NOTES: [[@LINE-1]]:3: warning: 't' used after it was moved502  // CHECK-NOTES: [[@LINE-3]]:3: note: move occurred here503}504template void movedTypeIsDependentType<A>();505 506// We handle the case correctly where the move consists of an implicit call507// to a conversion operator.508void implicitConversionOperator() {509  struct Convertible {510    operator A() && { return A(); }511  };512  void takeA(A a);513 514  Convertible convertible;515  takeA(std::move(convertible));516  convertible;517  // CHECK-NOTES: [[@LINE-1]]:3: warning: 'convertible' used after it was moved518  // CHECK-NOTES: [[@LINE-3]]:9: note: move occurred here519}520 521// Using decltype on an expression is not a use.522void decltypeIsNotUse() {523  A a;524  std::move(a);525  decltype(a) other_a;526}527 528// Ignore moves or uses that occur as part of template arguments.529template <int>530class ClassTemplate {531public:532  void foo(A a);533};534template <int>535void functionTemplate(A a);536void templateArgIsNotUse() {537  {538    // A pattern like this occurs in the EXPECT_EQ and ASSERT_EQ macros in539    // Google Test.540    A a;541    ClassTemplate<sizeof(A(std::move(a)))>().foo(std::move(a));542  }543  {544    A a;545    functionTemplate<sizeof(A(std::move(a)))>(std::move(a));546  }547}548 549// Ignore moves of global variables.550A global_a;551void ignoreGlobalVariables() {552  std::move(global_a);553  global_a.foo();554}555 556// Ignore moves of member variables.557class IgnoreMemberVariables {558  A a;559  static A static_a;560 561  void f() {562    std::move(a);563    a.foo();564 565    std::move(static_a);566    static_a.foo();567  }568};569 570// Ignore moves that happen in a try_emplace.571void ignoreMoveInTryEmplace() {572  {573    std::map<int, A> amap;574    A a;575    amap.try_emplace(1, std::move(a));576    a.foo();577  }578  {579    std::unordered_map<int, A> amap;580    A a;581    amap.try_emplace(1, std::move(a));582    a.foo();583  }584}585 586////////////////////////////////////////////////////////////////////////////////587// Tests involving control flow.588 589void useAndMoveInLoop() {590  // Warn about use-after-moves if they happen in a later loop iteration than591  // the std::move().592  {593    A a;594    for (int i = 0; i < 10; ++i) {595      a.foo();596      // CHECK-NOTES: [[@LINE-1]]:7: warning: 'a' used after it was moved597      // CHECK-NOTES: [[@LINE+2]]:7: note: move occurred here598      // CHECK-NOTES: [[@LINE-3]]:7: note: the use happens in a later loop599      std::move(a);600    }601  }602  // Same as above, but the use and the move are in different CFG blocks.603  {604    A a;605    for (int i = 0; i < 10; ++i) {606      if (i < 10)607        a.foo();608      // CHECK-NOTES: [[@LINE-1]]:9: warning: 'a' used after it was moved609      // CHECK-NOTES: [[@LINE+3]]:9: note: move occurred here610      // CHECK-NOTES: [[@LINE-3]]:9: note: the use happens in a later loop611      if (i < 10)612        std::move(a);613    }614  }615  // However, this case shouldn't be flagged -- the scope of the declaration of616  // 'a' is important.617  {618    for (int i = 0; i < 10; ++i) {619      A a;620      a.foo();621      std::move(a);622    }623  }624  // Same as above, except that we have an unrelated variable being declared in625  // the same declaration as 'a'. This case is interesting because it tests that626  // the synthetic DeclStmts generated by the CFG are sequenced correctly627  // relative to the other statements.628  {629    for (int i = 0; i < 10; ++i) {630      A a, other;631      a.foo();632      std::move(a);633    }634  }635  // Don't warn if we return after the move.636  {637    A a;638    for (int i = 0; i < 10; ++i) {639      a.foo();640      if (a.getInt() > 0) {641        std::move(a);642        return;643      }644    }645  }646}647 648void differentBranches(int i) {649  // Don't warn if the use is in a different branch from the move.650  {651    A a;652    if (i > 0) {653      std::move(a);654    } else {655      a.foo();656    }657  }658  // Same thing, but with a ternary operator.659  {660    A a;661    i > 0 ? (void)std::move(a) : a.foo();662  }663  // A variation on the theme above.664  {665    A a;666    a.getInt() > 0 ? a.getInt() : A(std::move(a)).getInt();667  }668  // Same thing, but with a switch statement.669  {670    A a;671    switch (i) {672    case 1:673      std::move(a);674      break;675    case 2:676      a.foo();677      break;678    }679  }680  // However, if there's a fallthrough, we do warn.681  {682    A a;683    switch (i) {684    case 1:685      std::move(a);686    case 2:687      a.foo();688      // CHECK-NOTES: [[@LINE-1]]:7: warning: 'a' used after it was moved689      // CHECK-NOTES: [[@LINE-4]]:7: note: move occurred here690      break;691    }692  }693}694 695// False positive: A use-after-move is flagged even though the "if (b)" and696// "if (!b)" are mutually exclusive.697void mutuallyExclusiveBranchesFalsePositive(bool b) {698  A a;699  if (b) {700    std::move(a);701  }702  if (!b) {703    a.foo();704    // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved705    // CHECK-NOTES: [[@LINE-5]]:5: note: move occurred here706  }707}708 709// Destructors marked [[noreturn]] are handled correctly in the control flow710// analysis. (These are used in some styles of assertion macros.)711class FailureLogger {712public:713  FailureLogger();714  [[noreturn]] ~FailureLogger();715  void log(const char *);716};717#define ASSERT(x) \718  while (x)       \719  FailureLogger().log(#x)720bool operationOnA(A);721void noreturnDestructor() {722  A a;723  // The while loop in the ASSERT() would ordinarily have the potential to cause724  // a use-after-move because the second iteration of the loop would be using a725  // variable that had been moved from in the first iteration. Check that the726  // CFG knows that the second iteration of the loop is never reached because727  // the FailureLogger destructor is marked [[noreturn]].728  ASSERT(operationOnA(std::move(a)));729}730#undef ASSERT731 732////////////////////////////////////////////////////////////////////////////////733// Tests for reinitializations734 735template <class T>736void swap(T &a, T &b) {737  T tmp = std::move(a);738  a = std::move(b);739  b = std::move(tmp);740}741void assignments(int i) {742  // Don't report a use-after-move if the variable was assigned to in the743  // meantime.744  {745    A a;746    std::move(a);747    a = A();748    a.foo();749  }750  // The assignment should also be recognized if move, assignment and use don't751  // all happen in the same block (but the assignment is still guaranteed to752  // prevent a use-after-move).753  {754    A a;755    if (i == 1) {756      std::move(a);757      a = A();758    }759    if (i == 2) {760      a.foo();761    }762  }763  {764    A a;765    if (i == 1) {766      std::move(a);767    }768    if (i == 2) {769      a = A();770      a.foo();771    }772  }773  // The built-in assignment operator should also be recognized as a774  // reinitialization. (std::move() may be called on built-in types in template775  // code.)776  {777    int a1 = 1, a2 = 2;778    swap(a1, a2);779  }780  // A std::move() after the assignment makes the variable invalid again.781  {782    A a;783    std::move(a);784    a = A();785    std::move(a);786    a.foo();787    // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved788    // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here789  }790  // Report a use-after-move if we can't be sure that the variable was assigned791  // to.792  {793    A a;794    std::move(a);795    if (i < 10) {796      a = A();797    }798    if (i > 5) {799      a.foo();800      // CHECK-NOTES: [[@LINE-1]]:7: warning: 'a' used after it was moved801      // CHECK-NOTES: [[@LINE-7]]:5: note: move occurred here802    }803  }804}805 806// Passing the object to a function through a non-const pointer or reference807// counts as a re-initialization.808void passByNonConstPointer(A *);809void passByNonConstReference(A &);810void passByNonConstPointerIsReinit() {811  {812    A a;813    std::move(a);814    passByNonConstPointer(&a);815    a.foo();816  }817  {818    A a;819    std::move(a);820    passByNonConstReference(a);821    a.foo();822  }823}824 825// Passing the object through a const pointer or reference counts as a use --826// since the called function cannot reinitialize the object.827void passByConstPointer(const A *);828void passByConstReference(const A &);829void passByConstPointerIsUse() {830  {831    // Declaring 'a' as const so that no ImplicitCastExpr is inserted into the832    // AST -- we wouldn't want the check to rely solely on that to detect a833    // const pointer argument.834    const A a;835    std::move(a);836    passByConstPointer(&a);837    // CHECK-NOTES: [[@LINE-1]]:25: warning: 'a' used after it was moved838    // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here839  }840  const A a;841  std::move(a);842  passByConstReference(a);843  // CHECK-NOTES: [[@LINE-1]]:24: warning: 'a' used after it was moved844  // CHECK-NOTES: [[@LINE-3]]:3: note: move occurred here845}846 847// Clearing a standard container using clear() is treated as a848// re-initialization.849void standardContainerClearIsReinit() {850  {851    std::string container;852    std::move(container);853    container.clear();854    container.empty();855  }856  {857    std::vector<int> container;858    std::move(container);859    container.clear();860    container.empty();861 862    auto container2 = container;863    std::move(container2);864    container2.clear();865    container2.empty();866  }867  {868    std::deque<int> container;869    std::move(container);870    container.clear();871    container.empty();872  }873  {874    std::forward_list<int> container;875    std::move(container);876    container.clear();877    container.empty();878  }879  {880    std::list<int> container;881    std::move(container);882    container.clear();883    container.empty();884  }885  {886    std::set<int> container;887    std::move(container);888    container.clear();889    container.empty();890  }891  {892    std::map<int, int> container;893    std::move(container);894    container.clear();895    container.empty();896  }897  {898    std::multiset<int> container;899    std::move(container);900    container.clear();901    container.empty();902  }903  {904    std::multimap<int> container;905    std::move(container);906    container.clear();907    container.empty();908  }909  {910    std::unordered_set<int> container;911    std::move(container);912    container.clear();913    container.empty();914  }915  {916    std::unordered_map<int, int> container;917    std::move(container);918    container.clear();919    container.empty();920  }921  {922    std::unordered_multiset<int> container;923    std::move(container);924    container.clear();925    container.empty();926  }927  {928    std::unordered_multimap<int> container;929    std::move(container);930    container.clear();931    container.empty();932  }933  // This should also work for typedefs of standard containers.934  {935    typedef std::vector<int> IntVector;936    IntVector container;937    std::move(container);938    container.clear();939    container.empty();940  }941  // But it shouldn't work for non-standard containers.942  {943    // This might be called "vector", but it's not in namespace "std".944    struct vector {945      void clear() {}946    } container;947    std::move(container);948    container.clear();949    // CHECK-NOTES: [[@LINE-1]]:5: warning: 'container' used after it was950    // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here951  }952  // An intervening clear() on a different container does not reinitialize.953  {954    std::vector<int> container1, container2;955    std::move(container1);956    container2.clear();957    container1.empty();958    // CHECK-NOTES: [[@LINE-1]]:5: warning: 'container1' used after it was959    // CHECK-NOTES: [[@LINE-4]]:5: note: move occurred here960  }961}962 963// Clearing a standard container using assign() is treated as a964// re-initialization.965void standardContainerAssignIsReinit() {966  {967    std::string container;968    std::move(container);969    container.assign(0, ' ');970    container.empty();971  }972  {973    std::vector<int> container;974    std::move(container);975    container.assign(0, 0);976    container.empty();977  }978  {979    std::deque<int> container;980    std::move(container);981    container.assign(0, 0);982    container.empty();983  }984  {985    std::forward_list<int> container;986    std::move(container);987    container.assign(0, 0);988    container.empty();989  }990  {991    std::list<int> container;992    std::move(container);993    container.clear();994    container.empty();995  }996  // But it doesn't work for non-standard containers.997  {998    // This might be called "vector", but it's not in namespace "std".999    struct vector {1000      void assign(std::size_t, int) {}1001    } container;1002    std::move(container);1003    container.assign(0, 0);1004    // CHECK-NOTES: [[@LINE-1]]:5: warning: 'container' used after it was1005    // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here1006  }1007  // An intervening assign() on a different container does not reinitialize.1008  {1009    std::vector<int> container1, container2;1010    std::move(container1);1011    container2.assign(0, 0);1012    container1.empty();1013    // CHECK-NOTES: [[@LINE-1]]:5: warning: 'container1' used after it was1014    // CHECK-NOTES: [[@LINE-4]]:5: note: move occurred here1015  }1016}1017 1018// Resetting the standard smart owning types using reset() is treated as a1019// re-initialization. (We don't test std::weak_ptr<> because it can't be1020// dereferenced directly.)1021void resetIsReinit() {1022  {1023    std::unique_ptr<A> ptr;1024    std::move(ptr);1025    ptr.reset(new A);1026    *ptr;1027  }1028  {1029    std::shared_ptr<A> ptr;1030    std::move(ptr);1031    ptr.reset(new A);1032    *ptr;1033  }1034  {1035    std::optional<A> opt;1036    std::move(opt);1037    opt.reset();1038    std::optional<A> opt2 = opt;1039    (void)opt2;1040  }1041  {1042    std::any a;1043    std::move(a);1044    a.reset();1045    std::any a2 = a;1046    (void)a2;1047  }1048}1049 1050void reinitAnnotation() {1051  {1052    AnnotatedContainer<int> obj;1053    std::move(obj);1054    obj.foo();1055    // CHECK-NOTES: [[@LINE-1]]:5: warning: 'obj' used after it was1056    // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here1057  }1058  {1059    AnnotatedContainer<int> obj;1060    std::move(obj);1061    obj.clear();1062    obj.foo();1063  }1064  {1065    // Calling clear() on a different object to the one that was moved is not1066    // considered a reinitialization.1067    AnnotatedContainer<int> obj1, obj2;1068    std::move(obj1);1069    obj2.clear();1070    obj1.foo();1071    // CHECK-NOTES: [[@LINE-1]]:5: warning: 'obj1' used after it was1072    // CHECK-NOTES: [[@LINE-4]]:5: note: move occurred here1073  }1074}1075 1076////////////////////////////////////////////////////////////////////////////////1077// Tests related to order of evaluation within expressions1078 1079// Relative sequencing of move and use.1080void passByRvalueReference(int i, A &&a);1081void passByValue(int i, A a);1082void passByValue(A a, int i);1083A g(A, A &&);1084int intFromA(A &&);1085int intFromInt(int);1086void sequencingOfMoveAndUse() {1087  // This case is fine because the move only happens inside1088  // passByRvalueReference(). At this point, a.getInt() is guaranteed to have1089  // been evaluated.1090  {1091    A a;1092    passByRvalueReference(a.getInt(), std::move(a));1093  }1094  // However, if we pass by value, the move happens when the move constructor is1095  // called to create a temporary, and this happens before the call to1096  // passByValue(). Because the order in which arguments are evaluated isn't1097  // defined, the move may happen before the call to a.getInt().1098  //1099  // Check that we warn about a potential use-after move for both orderings of1100  // a.getInt() and std::move(a), independent of the order in which the1101  // arguments happen to get evaluated by the compiler.1102  {1103    A a;1104    passByValue(a.getInt(), std::move(a));1105    // CHECK-NOTES: [[@LINE-1]]:17: warning: 'a' used after it was moved1106    // CHECK-NOTES: [[@LINE-2]]:29: note: move occurred here1107    // CHECK-NOTES: [[@LINE-3]]:17: note: the use and move are unsequenced1108  }1109  {1110    A a;1111    passByValue(std::move(a), a.getInt());1112    // CHECK-NOTES: [[@LINE-1]]:31: warning: 'a' used after it was moved1113    // CHECK-NOTES: [[@LINE-2]]:17: note: move occurred here1114    // CHECK-NOTES: [[@LINE-3]]:31: note: the use and move are unsequenced1115  }1116  // An even more convoluted example.1117  {1118    A a;1119    g(g(a, std::move(a)), g(a, std::move(a)));1120    // CHECK-NOTES: [[@LINE-1]]:9: warning: 'a' used after it was moved1121    // CHECK-NOTES: [[@LINE-2]]:27: note: move occurred here1122    // CHECK-NOTES: [[@LINE-3]]:9: note: the use and move are unsequenced1123    // CHECK-NOTES: [[@LINE-4]]:29: warning: 'a' used after it was moved1124    // CHECK-NOTES: [[@LINE-5]]:7: note: move occurred here1125    // CHECK-NOTES: [[@LINE-6]]:29: note: the use and move are unsequenced1126  }1127  // This case is fine because the actual move only happens inside the call to1128  // operator=(). a.getInt(), by necessity, is evaluated before that call.1129  {1130    A a;1131    A vec[1];1132    vec[a.getInt()] = std::move(a);1133  }1134  // However, in the following case, the move happens before the assignment, and1135  // so the order of evaluation is not guaranteed.1136  {1137    A a;1138    int v[3];1139    v[a.getInt()] = intFromA(std::move(a));1140    // CHECK-NOTES: [[@LINE-1]]:7: warning: 'a' used after it was moved1141    // CHECK-NOTES: [[@LINE-2]]:21: note: move occurred here1142    // CHECK-NOTES: [[@LINE-3]]:7: note: the use and move are unsequenced1143  }1144  {1145    A a;1146    int v[3];1147    v[intFromA(std::move(a))] = intFromInt(a.i);1148    // CHECK-NOTES: [[@LINE-1]]:44: warning: 'a' used after it was moved1149    // CHECK-NOTES: [[@LINE-2]]:7: note: move occurred here1150    // CHECK-NOTES: [[@LINE-3]]:44: note: the use and move are unsequenced1151  }1152}1153 1154// Relative sequencing of move and reinitialization. If the two are unsequenced,1155// we conservatively assume that the move happens after the reinitialization,1156// i.e. the that object does not get reinitialized after the move.1157A MutateA(A a);1158void passByValue(A a1, A a2);1159void sequencingOfMoveAndReinit() {1160  // Move and reinitialization as function arguments (which are indeterminately1161  // sequenced). Again, check that we warn for both orderings.1162  {1163    A a;1164    passByValue(std::move(a), (a = A()));1165    a.foo();1166    // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved1167    // CHECK-NOTES: [[@LINE-3]]:17: note: move occurred here1168  }1169  {1170    A a;1171    passByValue((a = A()), std::move(a));1172    a.foo();1173    // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved1174    // CHECK-NOTES: [[@LINE-3]]:28: note: move occurred here1175  }1176  // Common usage pattern: Move the object to a function that mutates it in some1177  // way, then reassign the result to the object. This pattern is fine.1178  {1179    A a;1180    a = MutateA(std::move(a));1181    a.foo();1182  }1183}1184 1185// Relative sequencing of reinitialization and use. If the two are unsequenced,1186// we conservatively assume that the reinitialization happens after the use,1187// i.e. that the object is not reinitialized at the point in time when it is1188// used.1189void sequencingOfReinitAndUse() {1190  // Reinitialization and use in function arguments. Again, check both possible1191  // orderings.1192  {1193    A a;1194    std::move(a);1195    passByValue(a.getInt(), (a = A()));1196    // CHECK-NOTES: [[@LINE-1]]:17: warning: 'a' used after it was moved1197    // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here1198  }1199  {1200    A a;1201    std::move(a);1202    passByValue((a = A()), a.getInt());1203    // CHECK-NOTES: [[@LINE-1]]:28: warning: 'a' used after it was moved1204    // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here1205  }1206}1207 1208// The comma operator sequences its operands.1209void commaOperatorSequences() {1210  {1211    A a;1212    A(std::move(a))1213    , (a = A());1214    a.foo();1215  }1216  {1217    A a;1218    (a = A()), A(std::move(a));1219    a.foo();1220    // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved1221    // CHECK-NOTES: [[@LINE-3]]:16: note: move occurred here1222  }1223}1224 1225namespace InitializerListSequences {1226 1227struct S1 {1228  int i;1229  A a;1230};1231 1232struct S2 {1233  A a;1234  int i;1235};1236 1237struct S3 {1238  S3() {}1239  S3(int, A) {}1240  S3(A, int) {}1241};1242 1243// An initializer list sequences its initialization clauses.1244void initializerListSequences() {1245  {1246    A a;1247    S1 s1{a.getInt(), std::move(a)};1248  }1249  {1250    A a;1251    S1 s1{.i = a.getInt(), .a = std::move(a)};1252  }1253  {1254    A a;1255    S2 s2{std::move(a), a.getInt()};1256    // CHECK-NOTES: [[@LINE-1]]:25: warning: 'a' used after it was moved1257    // CHECK-NOTES: [[@LINE-2]]:11: note: move occurred here1258  }1259  {1260    A a;1261    S2 s2{.a = std::move(a), .i = a.getInt()};1262    // CHECK-NOTES: [[@LINE-1]]:35: warning: 'a' used after it was moved1263    // CHECK-NOTES: [[@LINE-2]]:11: note: move occurred here1264  }1265  {1266    // Check the case where the constructed type has a constructor and the1267    // initializer list therefore manifests as a `CXXConstructExpr` instead of1268    // an `InitListExpr`.1269    A a;1270    S3 s3{a.getInt(), std::move(a)};1271  }1272  {1273    A a;1274    S3 s3{std::move(a), a.getInt()};1275    // CHECK-NOTES: [[@LINE-1]]:25: warning: 'a' used after it was moved1276    // CHECK-NOTES: [[@LINE-2]]:11: note: move occurred here1277  }1278}1279 1280} // namespace InitializerListSequences1281 1282// A declaration statement containing multiple declarations sequences the1283// initializer expressions.1284void declarationSequences() {1285  {1286    A a;1287    A a1 = a, a2 = std::move(a);1288  }1289  {1290    A a;1291    A a1 = std::move(a), a2 = a;1292    // CHECK-NOTES: [[@LINE-1]]:31: warning: 'a' used after it was moved1293    // CHECK-NOTES: [[@LINE-2]]:12: note: move occurred here1294  }1295}1296 1297// The logical operators && and || sequence their operands.1298void logicalOperatorsSequence() {1299  {1300    A a;1301    if (a.getInt() > 0 && A(std::move(a)).getInt() > 0) {1302      A().foo();1303    }1304  }1305  // A variation: Negate the result of the && (which pushes the && further down1306  // into the AST).1307  {1308    A a;1309    if (!(a.getInt() > 0 && A(std::move(a)).getInt() > 0)) {1310      A().foo();1311    }1312  }1313  {1314    A a;1315    if (A(std::move(a)).getInt() > 0 && a.getInt() > 0) {1316      // CHECK-NOTES: [[@LINE-1]]:41: warning: 'a' used after it was moved1317      // CHECK-NOTES: [[@LINE-2]]:9: note: move occurred here1318      A().foo();1319    }1320  }1321  {1322    A a;1323    if (a.getInt() > 0 || A(std::move(a)).getInt() > 0) {1324      A().foo();1325    }1326  }1327  {1328    A a;1329    if (A(std::move(a)).getInt() > 0 || a.getInt() > 0) {1330      // CHECK-NOTES: [[@LINE-1]]:41: warning: 'a' used after it was moved1331      // CHECK-NOTES: [[@LINE-2]]:9: note: move occurred here1332      A().foo();1333    }1334  }1335}1336 1337// A range-based for sequences the loop variable declaration before the body.1338void forRangeSequences() {1339  A v[2] = {A(), A()};1340  for (A &a : v) {1341    std::move(a);1342  }1343}1344 1345// If a variable is declared in an if, while or switch statement, the init1346// statement (for if and switch) is sequenced before the variable declaration,1347// which in turn is sequenced before the evaluation of the condition. We place1348// all tests inside a for loop to ensure that the checker understands the1349// sequencing. If it didn't, then the loop would trigger the "moved twice"1350// logic.1351void ifWhileAndSwitchSequenceInitDeclAndCondition() {1352  for (int i = 0; i < 10; ++i) {1353    A a1;1354    if (A a2 = std::move(a1)) {1355      std::move(a2);1356    }1357  }1358  for (int i = 0; i < 10; ++i) {1359    A a1;1360    if (A a2 = std::move(a1); a2) {1361      std::move(a2);1362    }1363  }1364  for (int i = 0; i < 10; ++i) {1365    A a1;1366    if (A a2 = std::move(a1); A a3 = std::move(a2)) {1367      std::move(a3);1368    }1369  }1370  for (int i = 0; i < 10; ++i) {1371    // init followed by condition with move, but without variable declaration.1372    if (A a1; A(std::move(a1)).getInt() > 0) {}1373  }1374  for (int i = 0; i < 10; ++i) {1375    if (A a1; A(std::move(a1)).getInt() > a1.getInt()) {}1376    // CHECK-NOTES: [[@LINE-1]]:43: warning: 'a1' used after it was moved1377    // CHECK-NOTES: [[@LINE-2]]:15: note: move occurred here1378    // CHECK-NOTES: [[@LINE-3]]:43: note: the use and move are unsequenced1379  }1380  for (int i = 0; i < 10; ++i) {1381    A a1;1382    if (A a2 = std::move(a1); A(a1) > 0) {}1383    // CHECK-NOTES: [[@LINE-1]]:33: warning: 'a1' used after it was moved1384    // CHECK-NOTES: [[@LINE-2]]:16: note: move occurred here1385  }1386  while (A a = A()) {1387    std::move(a);1388  }1389  for (int i = 0; i < 10; ++i) {1390    A a1;1391    switch (A a2 = std::move(a1); a2) {1392      case true:1393        std::move(a2);1394    }1395  }1396  for (int i = 0; i < 10; ++i) {1397    A a1;1398    switch (A a2 = a1; A a3 = std::move(a2)) {1399      case true:1400        std::move(a3);1401    }1402  }1403}1404 1405// In a function call, the expression that determines the callee is sequenced1406// before the arguments -- but only in C++17 and later.1407namespace CalleeSequencedBeforeArguments {1408int consumeA(std::unique_ptr<A> a);1409int consumeA(A &&a);1410 1411void calleeSequencedBeforeArguments() {1412  {1413    std::unique_ptr<A> a;1414    a->bar(consumeA(std::move(a)));1415    // CHECK-NOTES-CXX11: [[@LINE-1]]:5: warning: 'a' used after it was moved1416    // CHECK-NOTES-CXX11: [[@LINE-2]]:21: note: move occurred here1417    // CHECK-NOTES-CXX11: [[@LINE-3]]:5: note: the use and move are unsequenced1418  }1419  {1420    std::unique_ptr<A> a;1421    std::unique_ptr<A> getArg(std::unique_ptr<A> a);1422    getArg(std::move(a))->bar(a->getInt());1423    // CHECK-NOTES: [[@LINE-1]]:31: warning: 'a' used after it was moved1424    // CHECK-NOTES: [[@LINE-2]]:12: note: move occurred here1425    // CHECK-NOTES-CXX11: [[@LINE-3]]:31: note: the use and move are unsequenced1426  }1427  {1428    A a;1429    // Nominally, the callee `a.bar` is evaluated before the argument1430    // `consumeA(std::move(a))`, but in effect `a` is only accessed after the1431    // call to `A::bar()` happens, i.e. after the argument has been evaluted.1432    a.bar(consumeA(std::move(a)));1433    // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved1434    // CHECK-NOTES: [[@LINE-2]]:11: note: move occurred here1435  }1436}1437} // namespace CalleeSequencedBeforeArguments1438 1439// Some statements in templates (e.g. null, break and continue statements) may1440// be shared between the uninstantiated and instantiated versions of the1441// template and therefore have multiple parents. Make sure the sequencing code1442// handles this correctly.1443template <class> void nullStatementSequencesInTemplate() {1444  int c = 0;1445  (void)c;1446  ;1447  std::move(c);1448}1449template void nullStatementSequencesInTemplate<int>();1450 1451namespace PR33020 {1452class D {1453  ~D();1454};1455struct A {1456  D d;1457};1458class B {1459  A a;1460};1461template <typename T>1462class C : T, B {1463  void m_fn1() {1464    int a;1465    std::move(a);1466    C c;1467  }1468};1469} // namespace PR330201470 1471namespace UnevalContext {1472struct Foo {};1473void noExcept() {1474  Foo Bar;1475  (void) noexcept(Foo{std::move(Bar)});1476  Foo Other{std::move(Bar)};1477}1478void sizeOf() {1479  Foo Bar;1480  (void)sizeof(Foo{std::move(Bar)});1481  Foo Other{std::move(Bar)};1482}1483void alignOf() {1484  Foo Bar;1485#pragma clang diagnostic push1486#pragma clang diagnostic ignored "-Wgnu-alignof-expression"1487  (void)alignof(Foo{std::move(Bar)});1488#pragma clang diagnostic pop1489  Foo Other{std::move(Bar)};1490}1491void typeId() {1492  Foo Bar;1493  // error: you need to include <typeinfo> before using the 'typeid' operator1494  // (void) typeid(Foo{std::move(Bar)}).name();1495  Foo Other{std::move(Bar)};1496}1497} // namespace UnevalContext1498 1499class CtorInit {1500public:1501  CtorInit(std::string val)1502      : a{val.empty()},    // fine1503        s{std::move(val)},1504        b{val.empty()}1505  // CHECK-NOTES: [[@LINE-1]]:11: warning: 'val' used after it was moved1506  // CHECK-NOTES: [[@LINE-3]]:9: note: move occurred here1507  {}1508 1509private:1510  bool a;1511  std::string s;1512  bool b;1513};1514 1515class CtorInitLambda {1516public:1517  CtorInitLambda(std::string val)1518      : a{val.empty()},    // fine1519        s{std::move(val)},1520        b{[&] { return val.empty(); }()},1521        // CHECK-NOTES: [[@LINE-1]]:12: warning: 'val' used after it was moved1522        // CHECK-NOTES: [[@LINE-3]]:9: note: move occurred here1523        c{[] {1524          std::string str{};1525          std::move(str);1526          return str.empty();1527          // CHECK-NOTES: [[@LINE-1]]:18: warning: 'str' used after it was moved1528          // CHECK-NOTES: [[@LINE-3]]:11: note: move occurred here1529        }()} {1530    std::move(val);1531    // CHECK-NOTES: [[@LINE-1]]:15: warning: 'val' used after it was moved1532    // CHECK-NOTES: [[@LINE-13]]:9: note: move occurred here1533    std::string val2{};1534    std::move(val2);1535    val2.empty();1536    // CHECK-NOTES: [[@LINE-1]]:5: warning: 'val2' used after it was moved1537    // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here1538  }1539 1540private:1541  bool a;1542  std::string s;1543  bool b;1544  bool c;1545  bool d{};1546};1547 1548class CtorInitOrder {1549public:1550  CtorInitOrder(std::string val)1551      : a{val.empty()}, // fine1552        b{val.empty()},1553        // CHECK-NOTES: [[@LINE-1]]:11: warning: 'val' used after it was moved1554        s{std::move(val)} {} // wrong order1555  // CHECK-NOTES: [[@LINE-1]]:9: note: move occurred here1556 1557private:1558  bool a;1559  std::string s;1560  bool b;1561};1562 1563struct Obj {};1564struct CtorD {1565  CtorD(Obj b);1566};1567 1568struct CtorC {1569  CtorC(Obj b);1570};1571 1572struct CtorB {1573  CtorB(Obj &b);1574};1575 1576struct CtorA : CtorB, CtorC, CtorD {1577  CtorA(Obj b) : CtorB{b}, CtorC{std::move(b)}, CtorD{b} {}1578  // CHECK-NOTES: [[@LINE-1]]:55: warning: 'b' used after it was moved1579  // CHECK-NOTES: [[@LINE-2]]:34: note: move occurred here1580};1581 1582struct Base {1583  Base(Obj b) : bb{std::move(b)} {}1584  template <typename Call> Base(Call &&c) : bb{c()} {};1585 1586  Obj bb;1587};1588 1589struct Derived : Base, CtorC {1590  Derived(Obj b)1591      : Base{[&] mutable { return std::move(b); }()},1592        // False negative: The lambda/std::move was executed, so it should warn1593        // below1594        CtorC{b} {}1595};1596 1597struct Derived2 : Base, CtorC {1598  Derived2(Obj b)1599      : Base{[&] mutable { return std::move(b); }},1600        // This was a move, but it doesn't warn below, because it can't know if1601        // the lambda/std::move was actually called1602        CtorC{b} {}1603};1604 1605struct Derived3 : Base, CtorC {1606  Derived3(Obj b)1607      : Base{[c = std::move(b)] mutable { return std::move(c); }}, CtorC{b} {}1608  // CHECK-NOTES: [[@LINE-1]]:74: warning: 'b' used after it was moved1609  // CHECK-NOTES: [[@LINE-2]]:19: note: move occurred here1610};1611 1612class PR38187 {1613public:1614  PR38187(std::string val) : val_(std::move(val)) {1615    val.empty();1616    // CHECK-NOTES: [[@LINE-1]]:5: warning: 'val' used after it was moved1617    // CHECK-NOTES: [[@LINE-3]]:30: note: move occurred here1618  }1619 1620private:1621  std::string val_;1622};1623 1624namespace issue820231625{1626 1627struct S {1628  S();1629  S(S&&);1630};1631 1632void consume(S s);1633 1634template <typename T>1635void forward(T&& t) {1636  consume(std::forward<T>(t));1637  consume(std::forward<T>(t));1638  // CHECK-NOTES: [[@LINE-1]]:27: warning: 't' used after it was forwarded1639  // CHECK-NOTES: [[@LINE-3]]:11: note: forward occurred here1640}1641 1642void create() {1643  S s;1644  forward(std::move(s));1645}1646 1647} // namespace issue820231648