Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[clang-tidy] CRTP Constructor Accessibility Check #82403

Merged
merged 31 commits into from
Mar 5, 2024

Conversation

isuckatcs
Copy link
Member

Finds CRTP used in an error-prone way.

If the constructor of a class intended to be used in a CRTP is public, then
it allows users to construct that class on its own.

  template <typename T> class CRTP {
  public:
    CRTP() = default;
  };

  class Good : CRTP<Good> {};
  Good GoodInstance;

  CRTP<int> BadInstance;

If the constructor is protected, the possibility of an accidental instantiation
is prevented, however it can fade an error, when a different class is used as
the template parameter instead of the derived one.

  template <typename T> class CRTP {
  protected:
    CRTP() = default;
  };

  class Good : CRTP<Good> {};
  Good GoodInstance;

  class Bad : CRTP<Good> {};
  Bad BadInstance;

To ensure that no accidental instantiation happens, the best practice is to make
the constructor private and declare the derived class as friend.

  template <typename T> class CRTP {
    CRTP() = default;
    friend T;
  };

  class Good : CRTP<Good> {};
  Good GoodInstance;

  class Bad : CRTP<Good> {};
  Bad CompileTimeError;

  CRTP<int> AlsoCompileTimeError;

@llvmbot
Copy link
Collaborator

llvmbot commented Feb 20, 2024

@llvm/pr-subscribers-clang-tidy

Author: None (isuckatcs)

Changes

Finds CRTP used in an error-prone way.

If the constructor of a class intended to be used in a CRTP is public, then
it allows users to construct that class on its own.

  template &lt;typename T&gt; class CRTP {
  public:
    CRTP() = default;
  };

  class Good : CRTP&lt;Good&gt; {};
  Good GoodInstance;

  CRTP&lt;int&gt; BadInstance;

If the constructor is protected, the possibility of an accidental instantiation
is prevented, however it can fade an error, when a different class is used as
the template parameter instead of the derived one.

  template &lt;typename T&gt; class CRTP {
  protected:
    CRTP() = default;
  };

  class Good : CRTP&lt;Good&gt; {};
  Good GoodInstance;

  class Bad : CRTP&lt;Good&gt; {};
  Bad BadInstance;

To ensure that no accidental instantiation happens, the best practice is to make
the constructor private and declare the derived class as friend.

  template &lt;typename T&gt; class CRTP {
    CRTP() = default;
    friend T;
  };

  class Good : CRTP&lt;Good&gt; {};
  Good GoodInstance;

  class Bad : CRTP&lt;Good&gt; {};
  Bad CompileTimeError;

  CRTP&lt;int&gt; AlsoCompileTimeError;

Patch is 20.90 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/82403.diff

8 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp (+3)
  • (modified) clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt (+1)
  • (added) clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp (+167)
  • (added) clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.h (+30)
  • (modified) clang-tools-extra/docs/ReleaseNotes.rst (+5)
  • (added) clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-crtp.rst (+62)
  • (modified) clang-tools-extra/docs/clang-tidy/checks/list.rst (+1)
  • (added) clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-crtp.cpp (+232)
diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
index 7a910037368c83..50b791ae10faf0 100644
--- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -82,6 +82,7 @@
 #include "UnhandledExceptionAtNewCheck.h"
 #include "UnhandledSelfAssignmentCheck.h"
 #include "UniquePtrArrayMismatchCheck.h"
+#include "UnsafeCrtpCheck.h"
 #include "UnsafeFunctionsCheck.h"
 #include "UnusedRaiiCheck.h"
 #include "UnusedReturnValueCheck.h"
@@ -233,6 +234,8 @@ class BugproneModule : public ClangTidyModule {
         "bugprone-unhandled-exception-at-new");
     CheckFactories.registerCheck<UniquePtrArrayMismatchCheck>(
         "bugprone-unique-ptr-array-mismatch");
+    CheckFactories.registerCheck<UnsafeCrtpCheck>(
+        "bugprone-unsafe-crtp");
     CheckFactories.registerCheck<UnsafeFunctionsCheck>(
         "bugprone-unsafe-functions");
     CheckFactories.registerCheck<UnusedRaiiCheck>("bugprone-unused-raii");
diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
index d443fd8d1452f1..572ff622b47473 100644
--- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
@@ -78,6 +78,7 @@ add_clang_library(clangTidyBugproneModule
   UnhandledExceptionAtNewCheck.cpp
   UnhandledSelfAssignmentCheck.cpp
   UniquePtrArrayMismatchCheck.cpp
+  UnsafeCrtpCheck.cpp
   UnsafeFunctionsCheck.cpp
   UnusedRaiiCheck.cpp
   UnusedReturnValueCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp
new file mode 100644
index 00000000000000..7bb50aaf950cbe
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp
@@ -0,0 +1,167 @@
+//===--- UnsafeCrtpCheck.cpp - clang-tidy ---------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "UnsafeCrtpCheck.h"
+#include "../utils/LexerUtils.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::bugprone {
+
+namespace {
+// Finds a node if it's already a bound node.
+AST_MATCHER_P(CXXRecordDecl, isBoundNode, std::string, ID) {
+  return Builder->removeBindings(
+      [&](const ast_matchers::internal::BoundNodesMap &Nodes) {
+        const auto *BoundRecord = Nodes.getNodeAs<CXXRecordDecl>(ID);
+        return BoundRecord != &Node;
+      });
+}
+
+bool hasPrivateConstructor(const CXXRecordDecl *RD) {
+  for (auto &&Ctor : RD->ctors()) {
+    if (Ctor->getAccess() == AS_private)
+      return true;
+  }
+
+  return false;
+}
+
+bool isDerivedParameterBefriended(const CXXRecordDecl *CRTP,
+                                  const NamedDecl *Param) {
+  for (auto &&Friend : CRTP->friends()) {
+    const auto *TTPT =
+        dyn_cast<TemplateTypeParmType>(Friend->getFriendType()->getType());
+
+    if (TTPT && TTPT->getDecl() == Param)
+      return true;
+  }
+
+  return false;
+}
+
+bool isDerivedClassBefriended(const CXXRecordDecl *CRTP,
+                              const CXXRecordDecl *Derived) {
+  for (auto &&Friend : CRTP->friends()) {
+    if (Friend->getFriendType()->getType()->getAsCXXRecordDecl() == Derived)
+      return true;
+  }
+
+  return false;
+}
+
+std::optional<const NamedDecl *>
+getDerivedParameter(const ClassTemplateSpecializationDecl *CRTP,
+                    const CXXRecordDecl *Derived) {
+  size_t Idx = 0;
+  bool Found = false;
+  for (auto &&TemplateArg : CRTP->getTemplateArgs().asArray()) {
+    if (TemplateArg.getKind() == TemplateArgument::Type &&
+        TemplateArg.getAsType()->getAsCXXRecordDecl() == Derived) {
+      Found = true;
+      break;
+    }
+    ++Idx;
+  }
+
+  if (!Found)
+    return std::nullopt;
+
+  return CRTP->getSpecializedTemplate()->getTemplateParameters()->getParam(Idx);
+}
+
+std::vector<FixItHint> hintMakeCtorPrivate(const CXXConstructorDecl *Ctor,
+                                           const std::string &OriginalAccess,
+                                           const SourceManager &SM,
+                                           const LangOptions &LangOpts) {
+  std::vector<FixItHint> Hints;
+
+  Hints.emplace_back(FixItHint::CreateInsertion(
+      Ctor->getBeginLoc().getLocWithOffset(-1), "private:\n"));
+
+  SourceLocation CtorEndLoc =
+      Ctor->isExplicitlyDefaulted()
+          ? utils::lexer::findNextTerminator(Ctor->getEndLoc(), SM, LangOpts)
+          : Ctor->getEndLoc();
+  Hints.emplace_back(FixItHint::CreateInsertion(
+      CtorEndLoc.getLocWithOffset(1), '\n' + OriginalAccess + ':' + '\n'));
+
+  return Hints;
+}
+} // namespace
+
+void UnsafeCrtpCheck::registerMatchers(MatchFinder *Finder) {
+  Finder->addMatcher(
+      classTemplateSpecializationDecl(
+          decl().bind("crtp"),
+          hasAnyTemplateArgument(refersToType(recordType(hasDeclaration(
+              cxxRecordDecl(isDerivedFrom(cxxRecordDecl(isBoundNode("crtp"))))
+                  .bind("derived")))))),
+      this);
+}
+
+void UnsafeCrtpCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *CRTPInstantiation =
+      Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("crtp");
+  const auto *DerivedRecord = Result.Nodes.getNodeAs<CXXRecordDecl>("derived");
+  const CXXRecordDecl *CRTPDeclaration =
+      CRTPInstantiation->getSpecializedTemplate()->getTemplatedDecl();
+
+  if (!CRTPDeclaration->hasUserDeclaredConstructor()) {
+    bool IsStruct = CRTPDeclaration->isStruct();
+
+    diag(CRTPDeclaration->getLocation(),
+         "the implicit default constructor of the CRTP is publicly accessible")
+        << CRTPDeclaration
+        << FixItHint::CreateInsertion(
+               CRTPDeclaration->getBraceRange().getBegin().getLocWithOffset(1),
+               (IsStruct ? "\nprivate:\n" : "\n") +
+                   CRTPDeclaration->getNameAsString() + "() = default;\n" +
+                   (IsStruct ? "public:\n" : ""));
+    diag(CRTPDeclaration->getLocation(), "consider making it private",
+         DiagnosticIDs::Note);
+  }
+
+  const auto *DerivedTemplateParameter =
+      *getDerivedParameter(CRTPInstantiation, DerivedRecord);
+
+  if (hasPrivateConstructor(CRTPDeclaration) &&
+      !isDerivedParameterBefriended(CRTPDeclaration,
+                                    DerivedTemplateParameter) &&
+      !isDerivedClassBefriended(CRTPDeclaration, DerivedRecord)) {
+    diag(CRTPDeclaration->getLocation(),
+         "the CRTP cannot be constructed from the derived class")
+        << CRTPDeclaration
+        << FixItHint::CreateInsertion(
+               CRTPDeclaration->getBraceRange().getEnd(),
+               "friend " + DerivedTemplateParameter->getNameAsString() + ';' +
+                   '\n');
+    diag(CRTPDeclaration->getLocation(),
+         "consider declaring the derived class as friend", DiagnosticIDs::Note);
+  }
+
+  for (auto &&Ctor : CRTPDeclaration->ctors()) {
+    if (Ctor->getAccess() == AS_private)
+      continue;
+
+    bool IsPublic = Ctor->getAccess() == AS_public;
+    std::string Access = IsPublic ? "public" : "protected";
+
+    diag(Ctor->getLocation(),
+         "%0 contructor allows the CRTP to be %select{inherited "
+         "from|constructed}1 as a regular template class")
+        << Access << IsPublic << Ctor
+        << hintMakeCtorPrivate(Ctor, Access, *Result.SourceManager,
+                               getLangOpts());
+    diag(Ctor->getLocation(), "consider making it private",
+         DiagnosticIDs::Note);
+  }
+}
+
+} // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.h b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.h
new file mode 100644
index 00000000000000..ac1a8f09208f95
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.h
@@ -0,0 +1,30 @@
+//===--- UnsafeCrtpCheck.h - clang-tidy -------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNSAFECRTPCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNSAFECRTPCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::bugprone {
+
+/// Finds CRTP used in an error-prone way.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/unsafe-crtp.html
+class UnsafeCrtpCheck : public ClangTidyCheck {
+public:
+  UnsafeCrtpCheck(StringRef Name, ClangTidyContext *Context)
+      : ClangTidyCheck(Name, Context) {}
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+};
+
+} // namespace clang::tidy::bugprone
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNSAFECRTPCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index b5348384e965ab..1506e4631806a4 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -167,6 +167,11 @@ New checks
   extracted from an optional-like type and then used to create a new instance
   of the same optional-like type.
 
+- New :doc:`bugprone-unsafe-crtp
+  <clang-tidy/checks/bugprone/unsafe-crtp>` check.
+
+  Detects error-prone CRTP usage.
+
 - New :doc:`cppcoreguidelines-no-suspend-with-lock
   <clang-tidy/checks/cppcoreguidelines/no-suspend-with-lock>` check.
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-crtp.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-crtp.rst
new file mode 100644
index 00000000000000..8e6955999512a6
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-crtp.rst
@@ -0,0 +1,62 @@
+.. title:: clang-tidy - bugprone-unsafe-crtp
+
+bugprone-unsafe-crtp
+====================
+
+Finds CRTP used in an error-prone way.
+
+If the constructor of a class intended to be used in a CRTP is public, then
+it allows users to construct that class on its own.
+
+Example:
+
+.. code-block:: c++
+
+  template <typename T> class CRTP {
+  public:
+    CRTP() = default;
+  };
+
+  class Good : CRTP<Good> {};
+  Good GoodInstance;
+
+  CRTP<int> BadInstance;
+
+If the constructor is protected, the possibility of an accidental instantiation
+is prevented, however it can fade an error, when a different class is used as
+the template parameter instead of the derived one.
+
+Example:
+
+.. code-block:: c++
+
+  template <typename T> class CRTP {
+  protected:
+    CRTP() = default;
+  };
+
+  class Good : CRTP<Good> {};
+  Good GoodInstance;
+
+  class Bad : CRTP<Good> {};
+  Bad BadInstance;
+
+To ensure that no accidental instantiation happens, the best practice is to make
+the constructor private and declare the derived class as friend.
+
+Example:
+
+.. code-block:: c++
+
+  template <typename T> class CRTP {
+    CRTP() = default;
+    friend T;
+  };
+
+  class Good : CRTP<Good> {};
+  Good GoodInstance;
+
+  class Bad : CRTP<Good> {};
+  Bad CompileTimeError;
+
+  CRTP<int> AlsoCompileTimeError;
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 6f987ba1672e3f..9083b6d28f1400 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -148,6 +148,7 @@ Clang-Tidy Checks
    :doc:`bugprone-unhandled-exception-at-new <bugprone/unhandled-exception-at-new>`,
    :doc:`bugprone-unhandled-self-assignment <bugprone/unhandled-self-assignment>`,
    :doc:`bugprone-unique-ptr-array-mismatch <bugprone/unique-ptr-array-mismatch>`, "Yes"
+   :doc:`bugprone-unsafe-crtp <bugprone/unsafe-crtp>`, "Yes"
    :doc:`bugprone-unsafe-functions <bugprone/unsafe-functions>`,
    :doc:`bugprone-unused-raii <bugprone/unused-raii>`, "Yes"
    :doc:`bugprone-unused-return-value <bugprone/unused-return-value>`,
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-crtp.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-crtp.cpp
new file mode 100644
index 00000000000000..edcfd67677fd7b
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-crtp.cpp
@@ -0,0 +1,232 @@
+// RUN: %check_clang_tidy %s bugprone-unsafe-crtp %t
+
+namespace class_implicit_ctor {
+template <typename T>
+class CRTP {};
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the implicit default constructor of the CRTP is publicly accessible [bugprone-unsafe-crtp]
+// CHECK-MESSAGES: :[[@LINE-2]]:7: note: consider making it private
+// CHECK-FIXES: CRTP() = default;
+
+class A : CRTP<A> {};
+} // namespace class_implicit_ctor
+
+namespace class_uncostructible {
+template <typename T>
+class CRTP {
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the CRTP cannot be constructed from the derived class [bugprone-unsafe-crtp]
+// CHECK-MESSAGES: :[[@LINE-2]]:7: note: consider declaring the derived class as friend
+// CHECK-FIXES: friend T;
+    CRTP() = default;
+};
+
+class A : CRTP<A> {};
+} // namespace class_uncostructible 
+
+namespace class_public_default_ctor {
+template <typename T>
+class CRTP {
+public:
+    CRTP() = default;
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: public contructor allows the CRTP to be constructed as a regular template class [bugprone-unsafe-crtp]
+    // CHECK-MESSAGES: :[[@LINE-2]]:5: note: consider making it private
+    // CHECK-FIXES: private:{{[[:space:]]*}}CRTP() = default;{{[[:space:]]*}}public:
+};
+
+class A : CRTP<A> {};
+} // namespace class_public_default_ctor
+
+namespace class_public_user_provided_ctor {
+template <typename T>
+class CRTP {
+public:
+    CRTP(int) {}
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: public contructor allows the CRTP to be constructed as a regular template class [bugprone-unsafe-crtp]
+    // CHECK-MESSAGES: :[[@LINE-2]]:5: note: consider making it private
+    // CHECK-FIXES: private:{{[[:space:]]*}}CRTP(int) {}{{[[:space:]]*}}public:
+};
+
+class A : CRTP<A> {};
+} // namespace class_public_user_provided_ctor
+
+namespace class_public_multiple_user_provided_ctors {
+template <typename T>
+class CRTP {
+public:
+    CRTP(int) {}
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: public contructor allows the CRTP to be constructed as a regular template class [bugprone-unsafe-crtp]
+    // CHECK-MESSAGES: :[[@LINE-2]]:5: note: consider making it private
+    // CHECK-FIXES: private:{{[[:space:]]*}}CRTP(int) {}{{[[:space:]]*}}public:
+    CRTP(float) {}
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: public contructor allows the CRTP to be constructed as a regular template class [bugprone-unsafe-crtp]
+    // CHECK-MESSAGES: :[[@LINE-2]]:5: note: consider making it private
+    // CHECK-FIXES: private:{{[[:space:]]*}}CRTP(float) {}{{[[:space:]]*}}public:
+};
+
+class A : CRTP<A> {};
+} // namespace class_public_multiple_user_provided_ctors
+
+namespace class_protected_ctors {
+template <typename T>
+class CRTP {
+protected:
+    CRTP(int) {}
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: protected contructor allows the CRTP to be inherited from as a regular template class [bugprone-unsafe-crtp]
+    // CHECK-MESSAGES: :[[@LINE-2]]:5: note: consider making it private
+    // CHECK-FIXES: private:{{[[:space:]]*}}CRTP(int) {}{{[[:space:]]*}}protected:
+    CRTP() = default;
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: protected contructor allows the CRTP to be inherited from as a regular template class [bugprone-unsafe-crtp]
+    // CHECK-MESSAGES: :[[@LINE-2]]:5: note: consider making it private
+    // CHECK-FIXES: private:{{[[:space:]]*}}CRTP() = default;{{[[:space:]]*}}protected:
+    CRTP(float) {}
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: protected contructor allows the CRTP to be inherited from as a regular template class [bugprone-unsafe-crtp]
+    // CHECK-MESSAGES: :[[@LINE-2]]:5: note: consider making it private
+    // CHECK-FIXES: private:{{[[:space:]]*}}CRTP(float) {}{{[[:space:]]*}}protected:
+};
+
+class A : CRTP<A> {};
+} // namespace class_protected_ctors
+
+namespace struct_implicit_ctor {
+template <typename T>
+struct CRTP {};
+// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: the implicit default constructor of the CRTP is publicly accessible [bugprone-unsafe-crtp]
+// CHECK-MESSAGES: :[[@LINE-2]]:8: note: consider making it private
+// CHECK-FIXES: private:{{[[:space:]]*}}CRTP() = default;{{[[:space:]]*}}public:
+
+class A : CRTP<A> {};
+} // namespace struct_implicit_ctor
+
+namespace struct_default_ctor {
+template <typename T>
+struct CRTP {
+    CRTP() = default;
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: public contructor allows the CRTP to be constructed as a regular template class [bugprone-unsafe-crtp]
+    // CHECK-MESSAGES: :[[@LINE-2]]:5: note: consider making it private
+    // CHECK-FIXES: private:{{[[:space:]]*}}CRTP() = default;{{[[:space:]]*}}public:
+};
+
+class A : CRTP<A> {};
+} // namespace struct_default_ctor
+
+namespace same_class_multiple_crtps {
+template <typename T>
+struct CRTP {};
+// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: the implicit default constructor of the CRTP is publicly accessible [bugprone-unsafe-crtp]
+// CHECK-MESSAGES: :[[@LINE-2]]:8: note: consider making it private
+// CHECK-FIXES: private:{{[[:space:]]*}}CRTP() = default;{{[[:space:]]*}}public:
+
+template <typename T>
+struct CRTP2 {};
+// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: the implicit default constructor of the CRTP is publicly accessible [bugprone-unsafe-crtp]
+// CHECK-MESSAGES: :[[@LINE-2]]:8: note: consider making it private
+// CHECK-FIXES: private:{{[[:space:]]*}}CRTP2() = default;{{[[:space:]]*}}public:
+
+class A : CRTP<A>, CRTP2<A> {};
+} // namespace same_class_multiple_crtps
+
+namespace same_crtp_multiple_classes {
+template <typename T>
+class CRTP {
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the CRTP cannot be constructed from the derived class [bugprone-unsafe-crtp]
+// CHECK-MESSAGES: :[[@LINE-2]]:7: note: consider declaring the derived class as friend
+// CHECK-FIXES: friend T;
+    CRTP() = default;
+};
+
+class A : CRTP<A> {};
+class B : CRTP<B> {};
+} // namespace same_crtp_multiple_classes
+
+namespace crtp_template {
+template <typename T, typename U>
+class CRTP {
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the CRTP cannot be constructed from the derived class [bugprone-unsafe-crtp]
+// CHECK-MESSAGES: :[[@LINE-2]]:7: note: consider declaring the derived class as friend
+// CHECK-FIXES: friend U;
+    CRTP() = default;
+};
+
+class A : CRTP<int, A> {};
+} // namespace crtp_template
+
+namespace crtp_template2 {
+template <typename T, typename U>
+class CRTP {
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the CRTP cannot be constructed from the derived class [bugprone-unsafe-crtp]
+// CHECK-MESSAGES: :[[@LINE-2]]:7: note: consider declaring the derived class as friend
+// CHECK-FIXES: friend T;
+    CRTP() = default;
+};
+
+class A : CRTP<A, A> {};
+} // namespace crtp_template2
+
+namespace template_derived {
+template <typename T>
+class CRTP {};
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the implicit default constructor of the CRTP is publicly accessible [bugprone-unsafe-crtp]
+// CHECK-MESSAGES: :[[@LINE-2]]:7: note: consider making it private
+// CHECK-FIXES: CRTP() = default;
+
+template<typename T>
+class A : CRTP<A<T>> {};
+
+// FIXME: Ideally the warning should be triggered without instantiation.
+void foo() {
+  A<int> A;
+  (void) A;
+}
+} // namespace template_derived
+
+namespace template_derived_explicit_specialization {
+template <typename T>
+class CRTP {};
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the implicit default constructor of the CRTP is publicly accessible [bugprone-unsafe-crtp]
+// CHECK-MESSAGES: :[[@LINE-2]]:7: note: consider making it private
+// CHECK-FIXES: CRTP() = default;
+
+template<typename T>
+class A : CRTP<A<T>> {};
+
+te...
[truncated]

@llvmbot
Copy link
Collaborator

llvmbot commented Feb 20, 2024

@llvm/pr-subscribers-clang-tools-extra

Author: None (isuckatcs)

Changes

Finds CRTP used in an error-prone way.

If the constructor of a class intended to be used in a CRTP is public, then
it allows users to construct that class on its own.

  template &lt;typename T&gt; class CRTP {
  public:
    CRTP() = default;
  };

  class Good : CRTP&lt;Good&gt; {};
  Good GoodInstance;

  CRTP&lt;int&gt; BadInstance;

If the constructor is protected, the possibility of an accidental instantiation
is prevented, however it can fade an error, when a different class is used as
the template parameter instead of the derived one.

  template &lt;typename T&gt; class CRTP {
  protected:
    CRTP() = default;
  };

  class Good : CRTP&lt;Good&gt; {};
  Good GoodInstance;

  class Bad : CRTP&lt;Good&gt; {};
  Bad BadInstance;

To ensure that no accidental instantiation happens, the best practice is to make
the constructor private and declare the derived class as friend.

  template &lt;typename T&gt; class CRTP {
    CRTP() = default;
    friend T;
  };

  class Good : CRTP&lt;Good&gt; {};
  Good GoodInstance;

  class Bad : CRTP&lt;Good&gt; {};
  Bad CompileTimeError;

  CRTP&lt;int&gt; AlsoCompileTimeError;

Patch is 20.90 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/82403.diff

8 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp (+3)
  • (modified) clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt (+1)
  • (added) clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp (+167)
  • (added) clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.h (+30)
  • (modified) clang-tools-extra/docs/ReleaseNotes.rst (+5)
  • (added) clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-crtp.rst (+62)
  • (modified) clang-tools-extra/docs/clang-tidy/checks/list.rst (+1)
  • (added) clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-crtp.cpp (+232)
diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
index 7a910037368c83..50b791ae10faf0 100644
--- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -82,6 +82,7 @@
 #include "UnhandledExceptionAtNewCheck.h"
 #include "UnhandledSelfAssignmentCheck.h"
 #include "UniquePtrArrayMismatchCheck.h"
+#include "UnsafeCrtpCheck.h"
 #include "UnsafeFunctionsCheck.h"
 #include "UnusedRaiiCheck.h"
 #include "UnusedReturnValueCheck.h"
@@ -233,6 +234,8 @@ class BugproneModule : public ClangTidyModule {
         "bugprone-unhandled-exception-at-new");
     CheckFactories.registerCheck<UniquePtrArrayMismatchCheck>(
         "bugprone-unique-ptr-array-mismatch");
+    CheckFactories.registerCheck<UnsafeCrtpCheck>(
+        "bugprone-unsafe-crtp");
     CheckFactories.registerCheck<UnsafeFunctionsCheck>(
         "bugprone-unsafe-functions");
     CheckFactories.registerCheck<UnusedRaiiCheck>("bugprone-unused-raii");
diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
index d443fd8d1452f1..572ff622b47473 100644
--- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
@@ -78,6 +78,7 @@ add_clang_library(clangTidyBugproneModule
   UnhandledExceptionAtNewCheck.cpp
   UnhandledSelfAssignmentCheck.cpp
   UniquePtrArrayMismatchCheck.cpp
+  UnsafeCrtpCheck.cpp
   UnsafeFunctionsCheck.cpp
   UnusedRaiiCheck.cpp
   UnusedReturnValueCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp
new file mode 100644
index 00000000000000..7bb50aaf950cbe
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp
@@ -0,0 +1,167 @@
+//===--- UnsafeCrtpCheck.cpp - clang-tidy ---------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "UnsafeCrtpCheck.h"
+#include "../utils/LexerUtils.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::bugprone {
+
+namespace {
+// Finds a node if it's already a bound node.
+AST_MATCHER_P(CXXRecordDecl, isBoundNode, std::string, ID) {
+  return Builder->removeBindings(
+      [&](const ast_matchers::internal::BoundNodesMap &Nodes) {
+        const auto *BoundRecord = Nodes.getNodeAs<CXXRecordDecl>(ID);
+        return BoundRecord != &Node;
+      });
+}
+
+bool hasPrivateConstructor(const CXXRecordDecl *RD) {
+  for (auto &&Ctor : RD->ctors()) {
+    if (Ctor->getAccess() == AS_private)
+      return true;
+  }
+
+  return false;
+}
+
+bool isDerivedParameterBefriended(const CXXRecordDecl *CRTP,
+                                  const NamedDecl *Param) {
+  for (auto &&Friend : CRTP->friends()) {
+    const auto *TTPT =
+        dyn_cast<TemplateTypeParmType>(Friend->getFriendType()->getType());
+
+    if (TTPT && TTPT->getDecl() == Param)
+      return true;
+  }
+
+  return false;
+}
+
+bool isDerivedClassBefriended(const CXXRecordDecl *CRTP,
+                              const CXXRecordDecl *Derived) {
+  for (auto &&Friend : CRTP->friends()) {
+    if (Friend->getFriendType()->getType()->getAsCXXRecordDecl() == Derived)
+      return true;
+  }
+
+  return false;
+}
+
+std::optional<const NamedDecl *>
+getDerivedParameter(const ClassTemplateSpecializationDecl *CRTP,
+                    const CXXRecordDecl *Derived) {
+  size_t Idx = 0;
+  bool Found = false;
+  for (auto &&TemplateArg : CRTP->getTemplateArgs().asArray()) {
+    if (TemplateArg.getKind() == TemplateArgument::Type &&
+        TemplateArg.getAsType()->getAsCXXRecordDecl() == Derived) {
+      Found = true;
+      break;
+    }
+    ++Idx;
+  }
+
+  if (!Found)
+    return std::nullopt;
+
+  return CRTP->getSpecializedTemplate()->getTemplateParameters()->getParam(Idx);
+}
+
+std::vector<FixItHint> hintMakeCtorPrivate(const CXXConstructorDecl *Ctor,
+                                           const std::string &OriginalAccess,
+                                           const SourceManager &SM,
+                                           const LangOptions &LangOpts) {
+  std::vector<FixItHint> Hints;
+
+  Hints.emplace_back(FixItHint::CreateInsertion(
+      Ctor->getBeginLoc().getLocWithOffset(-1), "private:\n"));
+
+  SourceLocation CtorEndLoc =
+      Ctor->isExplicitlyDefaulted()
+          ? utils::lexer::findNextTerminator(Ctor->getEndLoc(), SM, LangOpts)
+          : Ctor->getEndLoc();
+  Hints.emplace_back(FixItHint::CreateInsertion(
+      CtorEndLoc.getLocWithOffset(1), '\n' + OriginalAccess + ':' + '\n'));
+
+  return Hints;
+}
+} // namespace
+
+void UnsafeCrtpCheck::registerMatchers(MatchFinder *Finder) {
+  Finder->addMatcher(
+      classTemplateSpecializationDecl(
+          decl().bind("crtp"),
+          hasAnyTemplateArgument(refersToType(recordType(hasDeclaration(
+              cxxRecordDecl(isDerivedFrom(cxxRecordDecl(isBoundNode("crtp"))))
+                  .bind("derived")))))),
+      this);
+}
+
+void UnsafeCrtpCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *CRTPInstantiation =
+      Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("crtp");
+  const auto *DerivedRecord = Result.Nodes.getNodeAs<CXXRecordDecl>("derived");
+  const CXXRecordDecl *CRTPDeclaration =
+      CRTPInstantiation->getSpecializedTemplate()->getTemplatedDecl();
+
+  if (!CRTPDeclaration->hasUserDeclaredConstructor()) {
+    bool IsStruct = CRTPDeclaration->isStruct();
+
+    diag(CRTPDeclaration->getLocation(),
+         "the implicit default constructor of the CRTP is publicly accessible")
+        << CRTPDeclaration
+        << FixItHint::CreateInsertion(
+               CRTPDeclaration->getBraceRange().getBegin().getLocWithOffset(1),
+               (IsStruct ? "\nprivate:\n" : "\n") +
+                   CRTPDeclaration->getNameAsString() + "() = default;\n" +
+                   (IsStruct ? "public:\n" : ""));
+    diag(CRTPDeclaration->getLocation(), "consider making it private",
+         DiagnosticIDs::Note);
+  }
+
+  const auto *DerivedTemplateParameter =
+      *getDerivedParameter(CRTPInstantiation, DerivedRecord);
+
+  if (hasPrivateConstructor(CRTPDeclaration) &&
+      !isDerivedParameterBefriended(CRTPDeclaration,
+                                    DerivedTemplateParameter) &&
+      !isDerivedClassBefriended(CRTPDeclaration, DerivedRecord)) {
+    diag(CRTPDeclaration->getLocation(),
+         "the CRTP cannot be constructed from the derived class")
+        << CRTPDeclaration
+        << FixItHint::CreateInsertion(
+               CRTPDeclaration->getBraceRange().getEnd(),
+               "friend " + DerivedTemplateParameter->getNameAsString() + ';' +
+                   '\n');
+    diag(CRTPDeclaration->getLocation(),
+         "consider declaring the derived class as friend", DiagnosticIDs::Note);
+  }
+
+  for (auto &&Ctor : CRTPDeclaration->ctors()) {
+    if (Ctor->getAccess() == AS_private)
+      continue;
+
+    bool IsPublic = Ctor->getAccess() == AS_public;
+    std::string Access = IsPublic ? "public" : "protected";
+
+    diag(Ctor->getLocation(),
+         "%0 contructor allows the CRTP to be %select{inherited "
+         "from|constructed}1 as a regular template class")
+        << Access << IsPublic << Ctor
+        << hintMakeCtorPrivate(Ctor, Access, *Result.SourceManager,
+                               getLangOpts());
+    diag(Ctor->getLocation(), "consider making it private",
+         DiagnosticIDs::Note);
+  }
+}
+
+} // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.h b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.h
new file mode 100644
index 00000000000000..ac1a8f09208f95
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.h
@@ -0,0 +1,30 @@
+//===--- UnsafeCrtpCheck.h - clang-tidy -------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNSAFECRTPCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNSAFECRTPCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::bugprone {
+
+/// Finds CRTP used in an error-prone way.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/unsafe-crtp.html
+class UnsafeCrtpCheck : public ClangTidyCheck {
+public:
+  UnsafeCrtpCheck(StringRef Name, ClangTidyContext *Context)
+      : ClangTidyCheck(Name, Context) {}
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+};
+
+} // namespace clang::tidy::bugprone
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNSAFECRTPCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index b5348384e965ab..1506e4631806a4 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -167,6 +167,11 @@ New checks
   extracted from an optional-like type and then used to create a new instance
   of the same optional-like type.
 
+- New :doc:`bugprone-unsafe-crtp
+  <clang-tidy/checks/bugprone/unsafe-crtp>` check.
+
+  Detects error-prone CRTP usage.
+
 - New :doc:`cppcoreguidelines-no-suspend-with-lock
   <clang-tidy/checks/cppcoreguidelines/no-suspend-with-lock>` check.
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-crtp.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-crtp.rst
new file mode 100644
index 00000000000000..8e6955999512a6
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-crtp.rst
@@ -0,0 +1,62 @@
+.. title:: clang-tidy - bugprone-unsafe-crtp
+
+bugprone-unsafe-crtp
+====================
+
+Finds CRTP used in an error-prone way.
+
+If the constructor of a class intended to be used in a CRTP is public, then
+it allows users to construct that class on its own.
+
+Example:
+
+.. code-block:: c++
+
+  template <typename T> class CRTP {
+  public:
+    CRTP() = default;
+  };
+
+  class Good : CRTP<Good> {};
+  Good GoodInstance;
+
+  CRTP<int> BadInstance;
+
+If the constructor is protected, the possibility of an accidental instantiation
+is prevented, however it can fade an error, when a different class is used as
+the template parameter instead of the derived one.
+
+Example:
+
+.. code-block:: c++
+
+  template <typename T> class CRTP {
+  protected:
+    CRTP() = default;
+  };
+
+  class Good : CRTP<Good> {};
+  Good GoodInstance;
+
+  class Bad : CRTP<Good> {};
+  Bad BadInstance;
+
+To ensure that no accidental instantiation happens, the best practice is to make
+the constructor private and declare the derived class as friend.
+
+Example:
+
+.. code-block:: c++
+
+  template <typename T> class CRTP {
+    CRTP() = default;
+    friend T;
+  };
+
+  class Good : CRTP<Good> {};
+  Good GoodInstance;
+
+  class Bad : CRTP<Good> {};
+  Bad CompileTimeError;
+
+  CRTP<int> AlsoCompileTimeError;
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 6f987ba1672e3f..9083b6d28f1400 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -148,6 +148,7 @@ Clang-Tidy Checks
    :doc:`bugprone-unhandled-exception-at-new <bugprone/unhandled-exception-at-new>`,
    :doc:`bugprone-unhandled-self-assignment <bugprone/unhandled-self-assignment>`,
    :doc:`bugprone-unique-ptr-array-mismatch <bugprone/unique-ptr-array-mismatch>`, "Yes"
+   :doc:`bugprone-unsafe-crtp <bugprone/unsafe-crtp>`, "Yes"
    :doc:`bugprone-unsafe-functions <bugprone/unsafe-functions>`,
    :doc:`bugprone-unused-raii <bugprone/unused-raii>`, "Yes"
    :doc:`bugprone-unused-return-value <bugprone/unused-return-value>`,
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-crtp.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-crtp.cpp
new file mode 100644
index 00000000000000..edcfd67677fd7b
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-crtp.cpp
@@ -0,0 +1,232 @@
+// RUN: %check_clang_tidy %s bugprone-unsafe-crtp %t
+
+namespace class_implicit_ctor {
+template <typename T>
+class CRTP {};
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the implicit default constructor of the CRTP is publicly accessible [bugprone-unsafe-crtp]
+// CHECK-MESSAGES: :[[@LINE-2]]:7: note: consider making it private
+// CHECK-FIXES: CRTP() = default;
+
+class A : CRTP<A> {};
+} // namespace class_implicit_ctor
+
+namespace class_uncostructible {
+template <typename T>
+class CRTP {
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the CRTP cannot be constructed from the derived class [bugprone-unsafe-crtp]
+// CHECK-MESSAGES: :[[@LINE-2]]:7: note: consider declaring the derived class as friend
+// CHECK-FIXES: friend T;
+    CRTP() = default;
+};
+
+class A : CRTP<A> {};
+} // namespace class_uncostructible 
+
+namespace class_public_default_ctor {
+template <typename T>
+class CRTP {
+public:
+    CRTP() = default;
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: public contructor allows the CRTP to be constructed as a regular template class [bugprone-unsafe-crtp]
+    // CHECK-MESSAGES: :[[@LINE-2]]:5: note: consider making it private
+    // CHECK-FIXES: private:{{[[:space:]]*}}CRTP() = default;{{[[:space:]]*}}public:
+};
+
+class A : CRTP<A> {};
+} // namespace class_public_default_ctor
+
+namespace class_public_user_provided_ctor {
+template <typename T>
+class CRTP {
+public:
+    CRTP(int) {}
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: public contructor allows the CRTP to be constructed as a regular template class [bugprone-unsafe-crtp]
+    // CHECK-MESSAGES: :[[@LINE-2]]:5: note: consider making it private
+    // CHECK-FIXES: private:{{[[:space:]]*}}CRTP(int) {}{{[[:space:]]*}}public:
+};
+
+class A : CRTP<A> {};
+} // namespace class_public_user_provided_ctor
+
+namespace class_public_multiple_user_provided_ctors {
+template <typename T>
+class CRTP {
+public:
+    CRTP(int) {}
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: public contructor allows the CRTP to be constructed as a regular template class [bugprone-unsafe-crtp]
+    // CHECK-MESSAGES: :[[@LINE-2]]:5: note: consider making it private
+    // CHECK-FIXES: private:{{[[:space:]]*}}CRTP(int) {}{{[[:space:]]*}}public:
+    CRTP(float) {}
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: public contructor allows the CRTP to be constructed as a regular template class [bugprone-unsafe-crtp]
+    // CHECK-MESSAGES: :[[@LINE-2]]:5: note: consider making it private
+    // CHECK-FIXES: private:{{[[:space:]]*}}CRTP(float) {}{{[[:space:]]*}}public:
+};
+
+class A : CRTP<A> {};
+} // namespace class_public_multiple_user_provided_ctors
+
+namespace class_protected_ctors {
+template <typename T>
+class CRTP {
+protected:
+    CRTP(int) {}
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: protected contructor allows the CRTP to be inherited from as a regular template class [bugprone-unsafe-crtp]
+    // CHECK-MESSAGES: :[[@LINE-2]]:5: note: consider making it private
+    // CHECK-FIXES: private:{{[[:space:]]*}}CRTP(int) {}{{[[:space:]]*}}protected:
+    CRTP() = default;
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: protected contructor allows the CRTP to be inherited from as a regular template class [bugprone-unsafe-crtp]
+    // CHECK-MESSAGES: :[[@LINE-2]]:5: note: consider making it private
+    // CHECK-FIXES: private:{{[[:space:]]*}}CRTP() = default;{{[[:space:]]*}}protected:
+    CRTP(float) {}
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: protected contructor allows the CRTP to be inherited from as a regular template class [bugprone-unsafe-crtp]
+    // CHECK-MESSAGES: :[[@LINE-2]]:5: note: consider making it private
+    // CHECK-FIXES: private:{{[[:space:]]*}}CRTP(float) {}{{[[:space:]]*}}protected:
+};
+
+class A : CRTP<A> {};
+} // namespace class_protected_ctors
+
+namespace struct_implicit_ctor {
+template <typename T>
+struct CRTP {};
+// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: the implicit default constructor of the CRTP is publicly accessible [bugprone-unsafe-crtp]
+// CHECK-MESSAGES: :[[@LINE-2]]:8: note: consider making it private
+// CHECK-FIXES: private:{{[[:space:]]*}}CRTP() = default;{{[[:space:]]*}}public:
+
+class A : CRTP<A> {};
+} // namespace struct_implicit_ctor
+
+namespace struct_default_ctor {
+template <typename T>
+struct CRTP {
+    CRTP() = default;
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: public contructor allows the CRTP to be constructed as a regular template class [bugprone-unsafe-crtp]
+    // CHECK-MESSAGES: :[[@LINE-2]]:5: note: consider making it private
+    // CHECK-FIXES: private:{{[[:space:]]*}}CRTP() = default;{{[[:space:]]*}}public:
+};
+
+class A : CRTP<A> {};
+} // namespace struct_default_ctor
+
+namespace same_class_multiple_crtps {
+template <typename T>
+struct CRTP {};
+// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: the implicit default constructor of the CRTP is publicly accessible [bugprone-unsafe-crtp]
+// CHECK-MESSAGES: :[[@LINE-2]]:8: note: consider making it private
+// CHECK-FIXES: private:{{[[:space:]]*}}CRTP() = default;{{[[:space:]]*}}public:
+
+template <typename T>
+struct CRTP2 {};
+// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: the implicit default constructor of the CRTP is publicly accessible [bugprone-unsafe-crtp]
+// CHECK-MESSAGES: :[[@LINE-2]]:8: note: consider making it private
+// CHECK-FIXES: private:{{[[:space:]]*}}CRTP2() = default;{{[[:space:]]*}}public:
+
+class A : CRTP<A>, CRTP2<A> {};
+} // namespace same_class_multiple_crtps
+
+namespace same_crtp_multiple_classes {
+template <typename T>
+class CRTP {
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the CRTP cannot be constructed from the derived class [bugprone-unsafe-crtp]
+// CHECK-MESSAGES: :[[@LINE-2]]:7: note: consider declaring the derived class as friend
+// CHECK-FIXES: friend T;
+    CRTP() = default;
+};
+
+class A : CRTP<A> {};
+class B : CRTP<B> {};
+} // namespace same_crtp_multiple_classes
+
+namespace crtp_template {
+template <typename T, typename U>
+class CRTP {
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the CRTP cannot be constructed from the derived class [bugprone-unsafe-crtp]
+// CHECK-MESSAGES: :[[@LINE-2]]:7: note: consider declaring the derived class as friend
+// CHECK-FIXES: friend U;
+    CRTP() = default;
+};
+
+class A : CRTP<int, A> {};
+} // namespace crtp_template
+
+namespace crtp_template2 {
+template <typename T, typename U>
+class CRTP {
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the CRTP cannot be constructed from the derived class [bugprone-unsafe-crtp]
+// CHECK-MESSAGES: :[[@LINE-2]]:7: note: consider declaring the derived class as friend
+// CHECK-FIXES: friend T;
+    CRTP() = default;
+};
+
+class A : CRTP<A, A> {};
+} // namespace crtp_template2
+
+namespace template_derived {
+template <typename T>
+class CRTP {};
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the implicit default constructor of the CRTP is publicly accessible [bugprone-unsafe-crtp]
+// CHECK-MESSAGES: :[[@LINE-2]]:7: note: consider making it private
+// CHECK-FIXES: CRTP() = default;
+
+template<typename T>
+class A : CRTP<A<T>> {};
+
+// FIXME: Ideally the warning should be triggered without instantiation.
+void foo() {
+  A<int> A;
+  (void) A;
+}
+} // namespace template_derived
+
+namespace template_derived_explicit_specialization {
+template <typename T>
+class CRTP {};
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the implicit default constructor of the CRTP is publicly accessible [bugprone-unsafe-crtp]
+// CHECK-MESSAGES: :[[@LINE-2]]:7: note: consider making it private
+// CHECK-FIXES: CRTP() = default;
+
+template<typename T>
+class A : CRTP<A<T>> {};
+
+te...
[truncated]

Copy link

⚠️ We detected that you are using a GitHub private e-mail address to contribute to the repo.
Please turn off Keep my email addresses private setting in your account.
See LLVM Discourse for more information.

Copy link

github-actions bot commented Feb 20, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Member

@PiotrZSL PiotrZSL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not fan of using "friends", currently they consider more a code-smell, and i'm not fan of exposing other private implementation to derived class, this could cause some issues, like give access to class that isn't a derived class (Bad example), even if object of such CRTP would never be created simply because static methods would be used.

I assume that this check base on this approach: https://www.fluentcpp.com/2017/05/12/curiously-recurring-template-pattern/

Consider changing check name to more detailed, updating diagnostic, fixing known issues, adding more tests and proper support for all C++ standards, and then well, it could be merged.

clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp Outdated Show resolved Hide resolved
clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp Outdated Show resolved Hide resolved
class CRTP {
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the CRTP cannot be constructed from the derived class [bugprone-unsafe-crtp]
// CHECK-MESSAGES: :[[@LINE-2]]:7: note: consider declaring the derived class as friend
// CHECK-FIXES: friend T;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isn't a bug, maybe someone want this class be to not constructable.
boost::noncopiable could be such example.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to use boost::noncopyable as a CRPT, then it is an interesting question.

The idea that the checker is built around is that we want to help the user ensuring, that their CRTP class won't be instantiated accidentally. This could be achieved by making the constructor accessible only to the derived class.

boost::noncopyable uses a protected constructor and makes the copy-constructor private, which if used as a CRTP would automatically trigger the other issue, when a protected constructor allows any template parameter to be passed. E.g.: class Derived : Noncopyable<Foo> {};

As a result I think a functionality such as boost::noncopyable is not suitable for a CRTP, but if the user insists on creating it // NOLINT(bugprone-unsafe-crtp) could be applied.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

boost::noncopyable may not be best example, but thing is that private constructor is not an issue, simply check shouldn't say, "hey you forget friend" because such class may have private constructor on purpose, for example to use an factory function or just to provide static methods.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how a factory function in a CRTP context would make sense. If the only constructor is for a static factory function, which is unaccessible by the derived class, then the derived class cannot be constructed either, right? Also since the goal of the CRTP is to provide some functionality to it's template parameter, having a factory method would just result in instantiating it on it's own, which also doesn't make sense, right?

Otherwise since the goal is to prevent any kind of accidental instantiation, the check assumes that every other constructor is private, and then we need the friend, since the derived class has no way to instantiate the CRTP. If anything else other than the derived class and the CRTP itself has access to the constructors, then accidental instantiation can happen, which we want to prevent.

If the class only provides static methods, then why would any other class inherit from it, or use it as a CRTP?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the only constructor is for a static factory function, which is unaccessible by the derived class, then the derived class cannot be constructed either, right?

@isuckatcs I think you are right, at least I tried to give this some thought (actually by trying to construct a counter-example).

template <class T>
class Base
{
    Base(int I) {}

protected:
    static Base factory(int I) { return Base{I}; }
};

class Derived : public Base<Derived>
{
    // Derived() : Base(0) {} // Does not work.
    Derived() : Base(Base::factory(1)) {}
};

void test()
{
    Derived D;
}
<source>:18:13: error: calling a private constructor of class 'Derived'
   18 |     Derived D;
      |             ^
<source>:13:5: note: implicitly declared private here
   13 |     Derived() : Base(Base::factory(1)) {}
      |     ^

Copy link
Member Author

@isuckatcs isuckatcs Feb 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@whisperity I think you meant struct Derived, and then your example proves me wrong.

The check should also warn about the accessibility of any function, which can construct the CRTP from anywhere other than the derived class and itself.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since checking if a public or protected function can construct the class would require path sensitive analysis I'd add this example as a limitation to the docs.

E.g.:

template <class T>
class CRTP
{
    CRTP(int I) {}

public:
    static void hardToDetect(CRTP* Escape, int X) { 
        if(X > 2) {
            Escape = new CRTP{1};
        }
    }
};

Another solution would be to walk the callgraph, but that would just pollute the user with non-meaningful warnings.

E.g.:

template <class T>
class CRTP
{
    CRTP(int I) {}

public:
    void hardToDetect(CRTP* Escape, int X) { 
        if(CRTP && X > 2) {
            Escape = new CRTP{1};
        }
    }
    
    void warning1() { hardToDetect(nullptr, 3); }
    void warning2() { warning1(); }
    void warning3() { warning2(); }
};

In the above CRTP we could report a warning for every function, right?

class Bad : CRTP<Good> {};
Bad CompileTimeError;

CRTP<int> AlsoCompileTimeError;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wold be nice if check had an option that would allow to select one of two:

  • prefer protected constructor
  • prefer private constructor + friend

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think protected is still as bugprone as public is. It will fade an accidentaly wrong inheritance, e.g.: class Derived : CRTP<Foo> {};, I'm not sure if we want to suggest users a fix like this, because it still results in the problem, which we want to fix.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing is that some projects may consider friend to invasive, or simply forbid using friends.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, protected constructor might be needed in cases where the class we are currently checking a base class in a hierarchy (while also using a CRTP) and there is no reason to allow its direct instantiation by clients, because clients should instantiate the current class's deriveds to get to useful objects. However, those deriveds might not be using CRTP on their own level.

Contrived example:

template <class NodeFamily>
struct ASTNode {
  protected:
    // Users should not construct this base class directly.
    ASTNode(...) { ... }
};

struct Stmt : ASTNode<Stmt> {
  protected:
    // Users should not construct this base class directly, either.
    Stmt(...) : ASTNode(...) { ... }
};

struct IfStmt : Stmt {
  // This should be constructible directly by clients.
  IfStmt(...) : Stmt(...) { ... }
};

Copy link
Member Author

@isuckatcs isuckatcs Feb 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the constructor is protected as in your example, the following will also compile, which is not desired.

struct Decl {};

struct Stmt : ASTNode<Decl> {
  protected:
    Stmt() : ASTNode() {  }
};

This is exactly the kind of usage, this check wants to catch.

Also note that with the suggested fix, your example still compiles.

template <class NodeFamily>
struct ASTNode {
  private:
    ASTNode() {  }
    friend NodeFamily;
};

struct Stmt : ASTNode<Stmt> {
  protected:
    Stmt() : ASTNode() {  }
};

struct IfStmt : Stmt {
  IfStmt() : Stmt() {  }
};

IfStmt calls the constructor of Stmt, and not the constructor of ASTNode<>. The latter constructor is called by Stmt, which has access to it either way. Derived classes supposed to invoke the constructor of the base class, not the the constructor of the base class of their base class.

For the record, AFAIK the CRTP is supposed to describe has a relationships and not is a relationships, so making ASTNode<> a CRTP might not be an ideal design choice.

clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp Outdated Show resolved Hide resolved
clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp Outdated Show resolved Hide resolved
clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp Outdated Show resolved Hide resolved
clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp Outdated Show resolved Hide resolved
Comment on lines 27 to 28
bool hasPrivateConstructor(const CXXRecordDecl *RD) {
for (auto &&Ctor : RD->ctors()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You take by pointer but do not check for nullness before dereferencing. If it is guaranteed and expected that a valid value will be here, consider using a reference instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After taking a look at other similar helpers throughout the clang-tidy codebase (grep static .*\(.*\*.*), none of them actually check for nullptr, or assert the opposite. With llvm::any_of(), all of these are 1 liners, so I wouldn't pollute them with an additional check either.

clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp Outdated Show resolved Hide resolved
Comment on lines 127 to 128
diag(CRTPDeclaration->getLocation(), "consider making it private",
DiagnosticIDs::Note);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a separate note? And if this is a separate note, why is the action suggested by the note (making the entity private) handled by a fixit that's attached to the warning instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the answer in a previous discussion.

clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.h Outdated Show resolved Hide resolved
const CXXRecordDecl *CRTPDeclaration =
CRTPInstantiation->getSpecializedTemplate()->getTemplatedDecl();

if (!CRTPDeclaration->hasUserDeclaredConstructor()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Following from 💬...)

How possible it is that these cases appear in conjunction with one another? Based on style, it would look like the returns are missing from the end of the statements.

Perhaps you could create helper functions that create the diagnostics for each individual case with a telling name, and then the check() function can be simplified to if (condition) diagnoseCaseX(); if (condition2) diagnoseCaseY(); etc.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Private constructors are always checked, but I think that logic is fine in the current function, in case of the implicit default ctor and friend declaration the extracted function would only contain 1 diag() call, so I don't see the value of those either.

class CRTP {
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the CRTP cannot be constructed from the derived class [bugprone-unsafe-crtp]
// CHECK-MESSAGES: :[[@LINE-2]]:7: note: consider declaring the derived class as friend
// CHECK-FIXES: friend T;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the only constructor is for a static factory function, which is unaccessible by the derived class, then the derived class cannot be constructed either, right?

@isuckatcs I think you are right, at least I tried to give this some thought (actually by trying to construct a counter-example).

template <class T>
class Base
{
    Base(int I) {}

protected:
    static Base factory(int I) { return Base{I}; }
};

class Derived : public Base<Derived>
{
    // Derived() : Base(0) {} // Does not work.
    Derived() : Base(Base::factory(1)) {}
};

void test()
{
    Derived D;
}
<source>:18:13: error: calling a private constructor of class 'Derived'
   18 |     Derived D;
      |             ^
<source>:13:5: note: implicitly declared private here
   13 |     Derived() : Base(Base::factory(1)) {}
      |     ^

namespace class_uncostructible {
template <typename T>
class CRTP {
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the CRTP cannot be constructed from the derived class [bugprone-unsafe-crtp]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Note: These warnings are matched as regexes and substrings, so you can simplify the test file by not repeating the check's name on every warning line.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some checker test files leave the name of the checker there, some don't, some mixes the two. I think it doesn't hurt to know that the message should be expected from this checker, though it might be obvious based on the name of the test.

Copy link
Member

@PiotrZSL PiotrZSL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall doesn't look to bad, probably could be merged in +- this state, it need to be run on some exist code-base like llvm. I will look again in next 1-2 days. Now just had quick glance.

@isuckatcs
Copy link
Member Author

isuckatcs commented Feb 29, 2024

I ran the checker on LLVM, Clang and Clang-Tidy (run-clang-tidy.py in my build directory). There are a total of 33 warnings by the checker, excluding the ones from the headers (including them, there are 19999+), which can be found below. I omitted the use site, where the template parameter says Derived, because I think that makes it obvious that it's a CRTP.

template <typename Derived, typename KeyType, typename UnversionedDataType>
class VersionedTableInfo { ... };
.../llvm-project/clang/lib/APINotes/APINotesReader.cpp:49:7: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
   49 | class VersionedTableInfo {
      |       ^  
template <typename ImplT, typename IteratorT, typename CollectionT>
class CalcLiveRangeUtilBase {
protected:
  LiveRange *LR;

protected:
  CalcLiveRangeUtilBase(LiveRange *LR) : LR(LR) {}
  ...
}

class CalcLiveRangeUtilVector;
using CalcLiveRangeUtilVectorBase =
    CalcLiveRangeUtilBase<CalcLiveRangeUtilVector, LiveRange::iterator,
                          LiveRange::Segments>;

class CalcLiveRangeUtilVector : public CalcLiveRangeUtilVectorBase { ... }
.../llvm-project/llvm/lib/CodeGen/LiveInterval.cpp:70:3: warning: protected contructor allows the CRTP to be inherited from as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
   70 |   CalcLiveRangeUtilBase(LiveRange *LR) : LR(LR) {}
      |   ^  
template <typename T>
class TestVisitor : public clang::RecursiveASTVisitor<T> { ... }

class ClassDeclXVisitor : public TestVisitor<ClassDeclXVisitor> { ... }
.../llvm-project/clang/unittests/Tooling/RefactoringTest.cpp:652:7: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
  652 | class TestVisitor : public clang::RecursiveASTVisitor<T> {
      |       ^   
template <class Derived> struct DiagTextVisitor { ... }
.../llvm-project/clang/utils/TableGen/ClangDiagnosticsEmitter.cpp:725:3: warning: public contructor allows the CRTP to be constructed as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
  725 |   DiagTextVisitor(DiagnosticTextBuilder &Builder) : Builder(Builder) {}
      |   ^  
template <typename Derived> class OpSplitter { ... }
.../llvm-project/llvm/lib/Transforms/Scalar/SROA.cpp:3765:5: warning: protected contructor allows the CRTP to be inherited from as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
 3765 |     OpSplitter(Instruction *InsertionPoint, Value *Ptr, Type *BaseTy,
      |     ^
template <typename Derived>
class Class5Tmpl : private llvm::TrailingObjects<Derived, float, int> { ... }
.../llvm-project/llvm/unittests/Support/TrailingObjectsTest.cpp:243:7: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
  243 | class Class5Tmpl : private llvm::TrailingObjects<Derived, float, int> {
      |       ^                                                                
template <typename Derived>
struct SimpleTransformVisitor : public TypeVisitor<Derived, QualType> { ... }
.../llvm-project/clang/lib/AST/Type.cpp:888:12: warning: public contructor allows the CRTP to be constructed as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
  888 |   explicit SimpleTransformVisitor(ASTContext &ctx) : Ctx(ctx) {}
      |            ^      
template <typename DERIVED>
class ClusterAnalysis  { ... }
.../llvm-project/clang/lib/StaticAnalyzer/Core/RegionStore.cpp:721:3: warning: public contructor allows the CRTP to be constructed as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
  721 |   ClusterAnalysis(RegionStoreManager &rm, ProgramStateManager &StateMgr,
      |   ^
template <typename DerivedCCG, typename FuncTy, typename CallTy>
class CallsiteContextGraph { ... }
.../llvm-project/llvm/lib/Transforms/IPO/MemProfContextDisambiguation.cpp:150:3: warning: public contructor allows the CRTP to be constructed as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
  150 |   CallsiteContextGraph() = default;
      |   ^                                
.../llvm-project/llvm/lib/Transforms/IPO/MemProfContextDisambiguation.cpp:151:3: warning: public contructor allows the CRTP to be constructed as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
  151 |   CallsiteContextGraph(const CallsiteContextGraph &) = default;
      |   ^                                                            
.../llvm-project/llvm/lib/Transforms/IPO/MemProfContextDisambiguation.cpp:152:3: warning: public contructor allows the CRTP to be constructed as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
  152 |   CallsiteContextGraph(CallsiteContextGraph &&) = default;
      |   ^ 
template <typename DerivedT, typename IRUnitT,
          typename AnalysisManagerT = AnalysisManager<IRUnitT>,
          typename... ExtraArgTs>
class MockAnalysisHandleBase { ... }
.../llvm-project/llvm/unittests/Transforms/Scalar/LoopPassManagerTest.cpp:43:7: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
   43 | class MockAnalysisHandleBase {
      |       ^  
template <typename DerivedT, typename IRUnitT,
          typename AnalysisManagerT = AnalysisManager<IRUnitT>,
          typename... ExtraArgTs>
class MockPassHandleBase { ... }
.../llvm-project/llvm/unittests/Transforms/Scalar/LoopPassManagerTest.cpp:154:7: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
  154 | class MockPassHandleBase {
      |       ^   
template <class Derived> struct StructVisitor { ... }
.../llvm-project/clang/lib/CodeGen/CGNonTrivialStruct.cpp:37:3: warning: public contructor allows the CRTP to be constructed as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
   37 |   StructVisitor(ASTContext &Ctx) : Ctx(Ctx) {}
      |   ^  

In this next one there are no warnings for CopiedTypeVisitor, but there should be.

template <class Derived, bool IsMove>
struct CopyStructVisitor : StructVisitor<Derived>,
                           CopiedTypeVisitor<Derived, IsMove> { ... }
.../llvm-project/clang/lib/CodeGen/CGNonTrivialStruct.cpp:83:3: warning: public contructor allows the CRTP to be constructed as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
   83 |   CopyStructVisitor(ASTContext &Ctx) : StructVisitor<Derived>(Ctx) {}
      |   ^  
.../llvm-project/clang/lib/CodeGen/CGNonTrivialStruct.cpp:150:33: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
  150 | template <class Derived> struct GenFuncNameBase {
      |                                 ^                
template <class Derived>
struct GenUnaryFuncName : StructVisitor<Derived>, GenFuncNameBase<Derived> { ... }
.../llvm-project/clang/lib/CodeGen/CGNonTrivialStruct.cpp:220:3: warning: public contructor allows the CRTP to be constructed as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
  220 |   GenUnaryFuncName(StringRef Prefix, CharUnits DstAlignment, ASTContext &Ctx)
      |   ^
.../llvm-project/clang/lib/CodeGen/CGNonTrivialStruct.cpp:335:33: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
  335 | template <class Derived> struct GenFuncBase {
      |                                 ^   
struct GenBinaryFunc : CopyStructVisitor<Derived, IsMove>,
                       GenFuncBase<Derived> { ... }
.../llvm-project/clang/lib/CodeGen/CGNonTrivialStruct.cpp:509:3: warning: public contructor allows the CRTP to be constructed as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
  509 |   GenBinaryFunc(ASTContext &Ctx) : CopyStructVisitor<Derived, IsMove>(Ctx) {}
      |   ^ 
.../llvm-project/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp:1983:32: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
 1983 | template <class Derived> class ConstraintAssignorBase {
      |                                ^  
template <typename DerivedT, typename IRUnitT,
          typename AnalysisManagerT = AnalysisManager<IRUnitT>,
          typename... ExtraArgTs>
class MockAnalysisHandleBase { ... }
.../llvm-project/llvm/unittests/IR/PassBuilderCallbacksTest.cpp:44:7: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
   44 | class MockAnalysisHandleBase {
      |       ^               
template <typename DerivedT, typename IRUnitT,
          typename AnalysisManagerT = AnalysisManager<IRUnitT>,
          typename... ExtraArgTs>
class MockPassHandleBase { ... }
.../llvm-project/llvm/unittests/IR/PassBuilderCallbacksTest.cpp:121:7: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
  121 | class MockPassHandleBase {
      |       ^   

In the above base class there is one use of a CRTP, PassInfoMixin<>, for which the check fails to report warnings, but it should be.

class MockPassHandleBase {
public:
  class Pass : public PassInfoMixin<Pass> { ... }
  ...
}
template <class Derived>
  struct DestroyNRVOVariable : EHScopeStack::Cleanup { ... }
.../llvm-project/clang/lib/CodeGen/CGDecl.cpp:518:5: warning: public contructor allows the CRTP to be constructed as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
  518 |     DestroyNRVOVariable(Address addr, QualType type, llvm::Value *NRVOFlag)
      |     ^

Here there are no warnings for ConstStmtVisitor<>, but there should be.

template <class Derived>
class ExprEvaluatorBase
  : public ConstStmtVisitor<Derived, bool> { ... }
.../llvm-project/clang/lib/AST/ExprConstant.cpp:7673:3: warning: public contructor allows the CRTP to be constructed as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
 7673 |   ExprEvaluatorBase(EvalInfo &Info) : Info(Info) {}
      |   ^  
template<class Derived>
class LValueExprEvaluatorBase
  : public ExprEvaluatorBase<Derived> { ... }
/home/danny/llvm-project/clang/lib/AST/ExprConstant.cpp:8311:3: warning: public contructor allows the CRTP to be constructed as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
 8311 |   LValueExprEvaluatorBase(EvalInfo &Info, LValue &Result, bool InvalidBaseOK)
      |   ^
template <typename Derived, typename KeyType, typename UnversionedDataType>
class VersionedTableInfo { ... }
.../llvm-project/clang/lib/APINotes/APINotesWriter.cpp:458:7: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
  458 | class VersionedTableInfo {
      |       ^ 
template <typename Derived, typename UnversionedDataType>
class CommonTypeTableInfo
    : public VersionedTableInfo<Derived, ContextTableKey, UnversionedDataType> { ... }
.../llvm-project/clang/lib/APINotes/APINotesWriter.cpp:1093:7: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
 1093 | class CommonTypeTableInfo
      |       ^
template <typename Derived, typename UnversionedDataType>
class CommonTypeTableInfo
    : public VersionedTableInfo<Derived, ContextTableKey, UnversionedDataType> { ... }
.../llvm-project/clang/lib/APINotes/APINotesWriter.cpp:1093:7: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
 1093 | class CommonTypeTableInfo
      |       ^
/// A CRTP base class for emitting expressions of retainable object
/// pointer type in ARC.
template <typename Impl, typename Result> class ARCExprEmitter { ... }
.../llvm-project/clang/lib/CodeGen/CGObjC.cpp:3104:3: warning: protected contructor allows the CRTP to be inherited from as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
 3104 |   ARCExprEmitter(CodeGenFunction &CGF) : CGF(CGF) {}
      |   ^ 
template<typename Derived, typename ResultList, typename Result,
         typename Subobject>
class DefaultedComparisonVisitor { ... }
.../llvm-project/clang/lib/Sema/SemaDeclCXX.cpp:7949:3: warning: public contructor allows the CRTP to be constructed as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
 7949 |   DefaultedComparisonVisitor(Sema &S, CXXRecordDecl *RD, FunctionDecl *FD,
      |   ^
/// CRTP base class for visiting operations performed by a special member
/// function (or inherited constructor).
template<typename Derived>
struct SpecialMemberVisitor { ... }
.../llvm-project/clang/lib/Sema/SemaDeclCXX.cpp:9269:3: warning: public contructor allows the CRTP to be constructed as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
 9269 |   SpecialMemberVisitor(Sema &S, CXXMethodDecl *MD, Sema::CXXSpecialMember CSM,
      |   ^
.../llvm-project/llvm/unittests/MIR/PassBuilderCallbacksTest.cpp:175:36: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
  175 | template <typename DerivedT> class MockAnalysisHandleBase {
      |                                    ^ 
.../llvm-project/llvm/unittests/MIR/PassBuilderCallbacksTest.cpp:238:36: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
  238 | template <typename DerivedT> class MockPassHandleBase {
      |                                    ^ 
template <typename Derived, typename KeyType, typename UnversionedDataType>
class VersionedTableInfo { ... }
.../llvm-project/clang/lib/APINotes/APINotesReader.cpp:49:7: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
   49 | class VersionedTableInfo {
      |       ^   

TLDR, we have some false negatives, but the possitives look correct. I forgot to add --header-filter=.* and I was surprised warnings are missing from the headers.

@isuckatcs isuckatcs changed the title [clang-tidy] Unsafe CRTP check [clang-tidy] CRTP Constructor Accessibility Check Feb 29, 2024
@isuckatcs
Copy link
Member Author

ping @PiotrZSL @whisperity

Copy link
Member

@PiotrZSL PiotrZSL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks fine, at least fine to be merged.
I will try to take a deeper look into particular cases, but even if some changes could be needed, probably better would be to merge this, and push fixes in separate PR.

So lest merge this, and let other dev to test this check.

@isuckatcs isuckatcs merged commit 8e56fb8 into llvm:main Mar 5, 2024
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants