[PATCH] D146288: clang-tidy: Detect use-after-move in CXXCtorInitializer

Piotr Zegar via Phabricator via cfe-commits cfe-commits at lists.llvm.org
Sat Mar 18 04:26:41 PDT 2023


PiotrZSL requested changes to this revision.
PiotrZSL added a comment.
This revision now requires changes to proceed.

Requesting changes due to lack of support for base class initializes & got some concerns related lambdas used in initializers.



================
Comment at: clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp:400
 void UseAfterMoveCheck::registerMatchers(MatchFinder *Finder) {
   auto CallMoveMatcher =
+      callExpr(
----------------
This is correct but consider this:

```
auto TryEmplaceMatcher = cxxMemberCallExpr(callee(cxxMethodDecl(hasName("try_emplace"))));

auto CallMoveMatcher =
callExpr(argumentCountIs(1), // because matching this will be faster than checking name.
             callee(functionDecl(hasName("::std::move"))),
             unless(inDecltypeOrTemplateArg()),
             expr().bind("call-move"),
             unless(hasParent(TryEmplaceMatcher)),
             anyOf(hasAncestor(compoundStmt(hasParent(lambdaExpr().bind("containing-lambda")))),
                        hasAncestor(functionDecl(anyOf(cxxConstructorDecl(hasAnyConstructorInitializer(
                                                                                                                   withInitializer(expr(
                                                                                                                          anyOf(equalsBounNode("call-move"), 
                                                                                                                                     hasDescendant(equalsBounNode("call-move")))
                                                                                                                     ).bind("containing-ctor-init-stmt")
                                                                                                              ))
                                                                                                             ).bind("containing-ctor"),
                                                                             functionDecl().bind("containing-func")))))
            );
```
              


================
Comment at: clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp:474
+      for (CXXCtorInitializer *Init : ContainingCtor->inits()) {
+        if (Init->getInit() == ContainingCtorInitStmt) {
+          ContainingCtorField = Init->getMember();
----------------
consider using here `->IgnoreImplicit() == ContainingCtorInitStmt->IgnoreImplicit()`


================
Comment at: clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp:479-488
+      if (ContainingCtorField) {
+        // Collect the constructor initializer expressions.
+        for (CXXCtorInitializer *Init : ContainingCtor->inits()) {
+          if (Init->getMember() && Init->getMember()->getFieldIndex() >=
+                                       ContainingCtorField->getFieldIndex()) {
+            if (Init->getInit())
+              CodeBlocks.push_back(Init->getInit());
----------------
This won't work for std::move used to initialize base class. Assuming that Inits are in specific order, then you just should include all init's after one found, or use things like `SourceManager::isBeforeInTranslationUnit`, but still probably using something like:

```
bool PastMove = false;
for (CXXCtorInitializer *Init : ContainingCtor->inits()) {
if (!PastMove  && Init->getInit()->IgnoreImplicit() == ContainingCtorInitStmt->IgnoreImplicit()) {
    PastMove  = true;
}

if (PastMove && Init->getInit())
{
    CodeBlocks.push_back(Init->getInit());
}
```

would be better, as it should work for both field inits and base constructor calls



================
Comment at: clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move.cpp:1431
   std::string val_;
 };
----------------
Missing tests:
- Test with A derive from B, C, D, and argument is moved into C, but D initialization also uses it.
- Test with lambda call inside init, and in lambda std::move of captured by reference constructor parameter, and lambda could be mutable, and lambda could be saved into member (or passed to base class), or called instantly (better both tests).
- Test with lambda that takes argument into capture by move

In short:
```
struct Obj {};
struct D
{
    D(Obj b);
};

struct C
{
     C(Obj b);
};

struct B
{
     B(Obj& b);
}

struct A : B, C, D
{
    A(Obj b)
      : B(b), C(std::move(b)), D(b)
    {
    }
};

and second:

struct Base
{
    Base(Obj b) : bb(std::move(b)) {}
     template<typename T>
     Base(T&& b) : bb(b()) {};
   
     Obj bb;
};

struct Derived : Base, C
{
     Derived(Obj b) 
           : Base([&] mutable { return std::move(b); }()), C(b)
   {
   }
};

struct Derived2 : Base, C
{
     Derived2(Obj b) 
           : Base([&] mutable { return std::move(b); }), C(b)
   {
   }
};

struct Derived3 : Base, C
{
     Derived3(Obj b) 
           : Base([c = std::move(b)] mutable { return std::move(c); }), C(b)
   {
   }
};
```


CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D146288/new/

https://reviews.llvm.org/D146288



More information about the cfe-commits mailing list