[PATCH] D146922: [clang-tidy] Fix false positve for defaulted move constructor in performance-noexcept-move-constructor

André Schackier via Phabricator via cfe-commits cfe-commits at lists.llvm.org
Thu Mar 30 09:04:13 PDT 2023


AMS21 added inline comments.


================
Comment at: clang-tools-extra/clang-tidy/performance/NoexceptMoveConstructorCheck.cpp:45-49
+    const auto *ProtoType = Decl->getType()->castAs<FunctionProtoType>();
+    const Expr *NoexceptExpr = ProtoType->getNoexceptExpr();
+    if (NoexceptExpr) {
+      NoexceptExpr = NoexceptExpr->IgnoreImplicit();
+      if (!isa<CXXBoolLiteralExpr>(NoexceptExpr)) {
----------------
PiotrZSL wrote:
> woudn't getExceptionSpecInfo() != EST_NoexceptFalse do a trick here ?
I've tested it and it seem that a struct like this:

```
struct B {
  static constexpr bool kFalse = false;
  B(B &&) noexcept(kFalse);
};
```

Also has the the `ExceptionSpecType` set to `EST_NoexceptFalse`. So changing this would change the previous behavior.


================
Comment at: clang-tools-extra/clang-tidy/utils/ExceptionSpecAnalyzer.cpp:95
+                                     DefaultableMemberKind Kind,
+                                     SkipMethods SkipMethods) {
+  if (!RecordDecl)
----------------
PiotrZSL wrote:
> Can we hit some endless recursion here ?
> Maybe so protection against checking Record that we currently checking.
Yes we can hit an infinite recursion here.
Take this class:

```
struct A {
    A(A &&) = default;
};
```

Since the move constructor is defaulted, we need to call `analyzeUnresolvedOrDefaulted`. There we determine the kind of defaulted member function which in this case is a move constructor.
Then we call `analyzeRecord`. Without setting `SkipMethods::Yes` we would try to check the move constructor of the class which in this case is `A`. We would call `analyze` on the move constructor of `A` which is exactly where we started and we'd have an infinite loop.

That is why I've added the `SkipMethods` parameter. While analyzing the defaulted move constructor of `A` we only want to look at its base classes and it's fields and determine if they are declared as throwing or not. While for their the base classes and fields of `A` we also want to check their move constructor (if they have any).

I hope my explanation was at least a bit helpful.

There might be a better solution to this, Before this I had essentially the same code twice for checking the bases and field and wanted to combine them.


================
Comment at: clang-tools-extra/clang-tidy/utils/ExceptionSpecAnalyzer.cpp:95
+                                     DefaultableMemberKind Kind,
+                                     SkipMethods SkipMethods) {
+  if (!RecordDecl)
----------------
AMS21 wrote:
> PiotrZSL wrote:
> > Can we hit some endless recursion here ?
> > Maybe so protection against checking Record that we currently checking.
> Yes we can hit an infinite recursion here.
> Take this class:
> 
> ```
> struct A {
>     A(A &&) = default;
> };
> ```
> 
> Since the move constructor is defaulted, we need to call `analyzeUnresolvedOrDefaulted`. There we determine the kind of defaulted member function which in this case is a move constructor.
> Then we call `analyzeRecord`. Without setting `SkipMethods::Yes` we would try to check the move constructor of the class which in this case is `A`. We would call `analyze` on the move constructor of `A` which is exactly where we started and we'd have an infinite loop.
> 
> That is why I've added the `SkipMethods` parameter. While analyzing the defaulted move constructor of `A` we only want to look at its base classes and it's fields and determine if they are declared as throwing or not. While for their the base classes and fields of `A` we also want to check their move constructor (if they have any).
> 
> I hope my explanation was at least a bit helpful.
> 
> There might be a better solution to this, Before this I had essentially the same code twice for checking the bases and field and wanted to combine them.
Another way to defiantly have an infinite recursion is if to resolve a `struct A;` we needed to resolve `struct B;` and to resolve that we needed to resolve `struct A`.  You would need something like `A` inheriting from `B` and `B` inheriting from `A` or having the other type as a member variable. Same goes for a struct which contains itself.

But I'm currently unaware on how to create such a scenario with legal C++.


================
Comment at: clang-tools-extra/clang-tidy/utils/ExceptionSpecAnalyzer.cpp:108-114
+  BasesToVisit Bases = isConstructor(Kind)
+                           ? BasesToVisit::VisitPotentiallyConstructedBases
+                           : BasesToVisit::VisitAllBases;
+
+  if (Bases == BasesToVisit::VisitPotentiallyConstructedBases)
+    Bases = RecordDecl->isAbstract() ? BasesToVisit::VisitNonVirtualBases
+                                     : BasesToVisit::VisitAllBases;
----------------
PiotrZSL wrote:
> I'm not sure if we need to be so picky...
> In short we could check all bases.
> Virtual, Abstract or not...
Honestly I not sure about it. I just tried to copy what `Sema` does when resolving noexcept.
Removing it definitely makes the code easier to read.


================
Comment at: clang-tools-extra/clang-tidy/utils/ExceptionSpecAnalyzer.h:23
+public:
+  enum class State : std::int8_t {
+    Throwing,    ///< This function has been declared as possibly throwing.
----------------
PiotrZSL wrote:
> No need for std::int8_t, and if you really want then use std::uint8_t
I prefer to not use it, but saw `ExceptionAnalyzer` use it. So I just copied it from there.


Repository:
  rG LLVM Github Monorepo

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

https://reviews.llvm.org/D146922



More information about the cfe-commits mailing list