https://github.com/isuckatcs updated https://github.com/llvm/llvm-project/pull/82403
>From a11b863a62f3e27d90e5b337b47d7f20e260d98b Mon Sep 17 00:00:00 2001 From: isuckatcs <65320245+isucka...@users.noreply.github.com> Date: Fri, 16 Feb 2024 00:25:29 +0100 Subject: [PATCH 01/14] added new checker --- .../bugprone/BugproneTidyModule.cpp | 3 + .../clang-tidy/bugprone/CMakeLists.txt | 1 + .../clang-tidy/bugprone/UnsafeCrtpCheck.cpp | 92 +++++++++++++++++++ .../clang-tidy/bugprone/UnsafeCrtpCheck.h | 30 ++++++ clang-tools-extra/docs/ReleaseNotes.rst | 5 + .../checks/bugprone/unsafe-crtp.rst | 6 ++ .../docs/clang-tidy/checks/list.rst | 1 + .../checkers/bugprone/unsafe-crtp.cpp | 14 +++ 8 files changed, 152 insertions(+) create mode 100644 clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp create mode 100644 clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.h create mode 100644 clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-crtp.rst create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-crtp.cpp diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp index a8a23b045f80bb..b7b4d85a86cce2 100644 --- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp @@ -83,6 +83,7 @@ #include "UnhandledExceptionAtNewCheck.h" #include "UnhandledSelfAssignmentCheck.h" #include "UniquePtrArrayMismatchCheck.h" +#include "UnsafeCrtpCheck.h" #include "UnsafeFunctionsCheck.h" #include "UnusedLocalNonTrivialVariableCheck.h" #include "UnusedRaiiCheck.h" @@ -237,6 +238,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<UnusedLocalNonTrivialVariableCheck>( diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt index 1cd6fb207d7625..956b12ad1cdecd 100644 --- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt @@ -79,6 +79,7 @@ add_clang_library(clangTidyBugproneModule UnhandledExceptionAtNewCheck.cpp UnhandledSelfAssignmentCheck.cpp UniquePtrArrayMismatchCheck.cpp + UnsafeCrtpCheck.cpp UnsafeFunctionsCheck.cpp UnusedLocalNonTrivialVariableCheck.cpp UnusedRaiiCheck.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..eb0f6187e58b2e --- /dev/null +++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp @@ -0,0 +1,92 @@ +//===--- 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 "clang/AST/ASTContext.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; + }); +} +} // namespace + +void UnsafeCrtpCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + cxxRecordDecl( + decl().bind("record"), + hasAnyBase(hasType( + classTemplateSpecializationDecl( + hasAnyTemplateArgument(refersToType(recordType( + hasDeclaration(cxxRecordDecl(isBoundNode("record"))))))) + .bind("crtp")))), + this); +} + +void UnsafeCrtpCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedCRTP = Result.Nodes.getNodeAs<CXXRecordDecl>("crtp"); + + MatchedCRTP->dump(); + + for (auto &&ctor : MatchedCRTP->ctors()) { + if (ctor->getAccess() != AS_private) { + ctor->dump(); + }; + } + + return; + + // if (!MatchedDecl->hasDefinition()) + // return; + + // for (auto &&Base : MatchedDecl->bases()) { + // const auto *TemplateSpecDecl = + // llvm::dyn_cast_if_present<ClassTemplateSpecializationDecl>( + // Base.getType()->getAsTagDecl()); + + // if (!TemplateSpecDecl) + // continue; + + // TemplateSpecDecl->dump(); + + // for (auto &&TemplateArg : TemplateSpecDecl->getTemplateArgs().asArray()) + // { + // if (TemplateArg.getKind() != TemplateArgument::Type) + // continue; + + // const auto *Record = TemplateArg.getAsType()->getAsCXXRecordDecl(); + + // if (Record && Record == MatchedDecl) { + // diag(Record->getLocation(), "CRTP found"); + + // diag(TemplateSpecDecl->getLocation(), "used as CRTP", + // DiagnosticIDs::Note); + // TemplateSpecDecl->dump(); + // } + // } + // } + + // if (!MatchedDecl->getIdentifier() || + // MatchedDecl->getName().startswith("awesome_")) + // return; + // diag(MatchedDecl->getLocation(), "function %0 is insufficiently awesome") + // << MatchedDecl + // << FixItHint::CreateInsertion(MatchedDecl->getLocation(), "awesome_"); + // diag(MatchedDecl->getLocation(), "insert 'awesome'", 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..7c41bbb0678521 --- /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 { + +/// FIXME: Write a short description. +/// +/// 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 a0b9fcfe0d7774..785e90d0c75eb0 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -106,6 +106,11 @@ New checks Replaces certain conditional statements with equivalent calls to ``std::min`` or ``std::max``. +- New :doc:`bugprone-unsafe-crtp + <clang-tidy/checks/bugprone/unsafe-crtp>` check. + + FIXME: add release notes. + New check aliases ^^^^^^^^^^^^^^^^^ 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..3352af02a8744f --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-crtp.rst @@ -0,0 +1,6 @@ +.. title:: clang-tidy - bugprone-unsafe-crtp + +bugprone-unsafe-crtp +==================== + +FIXME: Describe what patterns does the check detect and why. Give examples. diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index 59ef69f390ee91..f33b8122f35945 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -149,6 +149,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-local-non-trivial-variable <bugprone/unused-local-non-trivial-variable>`, :doc:`bugprone-unused-raii <bugprone/unused-raii>`, "Yes" 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..90350ad7abc578 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-crtp.cpp @@ -0,0 +1,14 @@ +// RUN: %check_clang_tidy %s bugprone-unsafe-crtp %t + +// FIXME: Add something that triggers the check here. +void f(); +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'f' is insufficiently awesome [bugprone-unsafe-crtp] + +// FIXME: Verify the applied fix. +// * Make the CHECK patterns specific enough and try to make verified lines +// unique to avoid incorrect matches. +// * Use {{}} for regular expressions. +// CHECK-FIXES: {{^}}void awesome_f();{{$}} + +// FIXME: Add something that doesn't trigger the check here. +void awesome_f2(); >From fed617800b5965e59897dd72bc78f1105cf896f9 Mon Sep 17 00:00:00 2001 From: isuckatcs <65320245+isucka...@users.noreply.github.com> Date: Fri, 16 Feb 2024 01:18:56 +0100 Subject: [PATCH 02/14] use a different matcher --- .../clang-tidy/bugprone/UnsafeCrtpCheck.cpp | 53 ++++--------------- 1 file changed, 9 insertions(+), 44 deletions(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp index eb0f6187e58b2e..33ad37d7fbbfdb 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp @@ -26,15 +26,12 @@ AST_MATCHER_P(CXXRecordDecl, isBoundNode, std::string, ID) { } // namespace void UnsafeCrtpCheck::registerMatchers(MatchFinder *Finder) { - Finder->addMatcher( - cxxRecordDecl( - decl().bind("record"), - hasAnyBase(hasType( - classTemplateSpecializationDecl( - hasAnyTemplateArgument(refersToType(recordType( - hasDeclaration(cxxRecordDecl(isBoundNode("record"))))))) - .bind("crtp")))), - this); + Finder->addMatcher(classTemplateSpecializationDecl( + decl().bind("crtp"), + hasAnyTemplateArgument(refersToType(recordType( + hasDeclaration(cxxRecordDecl(isDerivedFrom( + cxxRecordDecl(isBoundNode("crtp"))))))))), + this); } void UnsafeCrtpCheck::check(const MatchFinder::MatchResult &Result) { @@ -42,44 +39,12 @@ void UnsafeCrtpCheck::check(const MatchFinder::MatchResult &Result) { MatchedCRTP->dump(); - for (auto &&ctor : MatchedCRTP->ctors()) { - if (ctor->getAccess() != AS_private) { - ctor->dump(); + for (auto &&Ctor : MatchedCRTP->ctors()) { + if (Ctor->getAccess() != AS_private) { + Ctor->dump(); }; } - return; - - // if (!MatchedDecl->hasDefinition()) - // return; - - // for (auto &&Base : MatchedDecl->bases()) { - // const auto *TemplateSpecDecl = - // llvm::dyn_cast_if_present<ClassTemplateSpecializationDecl>( - // Base.getType()->getAsTagDecl()); - - // if (!TemplateSpecDecl) - // continue; - - // TemplateSpecDecl->dump(); - - // for (auto &&TemplateArg : TemplateSpecDecl->getTemplateArgs().asArray()) - // { - // if (TemplateArg.getKind() != TemplateArgument::Type) - // continue; - - // const auto *Record = TemplateArg.getAsType()->getAsCXXRecordDecl(); - - // if (Record && Record == MatchedDecl) { - // diag(Record->getLocation(), "CRTP found"); - - // diag(TemplateSpecDecl->getLocation(), "used as CRTP", - // DiagnosticIDs::Note); - // TemplateSpecDecl->dump(); - // } - // } - // } - // if (!MatchedDecl->getIdentifier() || // MatchedDecl->getName().startswith("awesome_")) // return; >From 121de31cef02a331a52a4207340be665ce9702dc Mon Sep 17 00:00:00 2001 From: isuckatcs <65320245+isucka...@users.noreply.github.com> Date: Sat, 17 Feb 2024 17:22:59 +0100 Subject: [PATCH 03/14] implemented implicit constructor and friend declaration logic --- .../clang-tidy/bugprone/UnsafeCrtpCheck.cpp | 79 ++++++++++++++++--- 1 file changed, 67 insertions(+), 12 deletions(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp index 33ad37d7fbbfdb..9063b385c0d1b4 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp @@ -9,6 +9,7 @@ #include "UnsafeCrtpCheck.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" using namespace clang::ast_matchers; @@ -23,26 +24,80 @@ AST_MATCHER_P(CXXRecordDecl, isBoundNode, std::string, ID) { return BoundRecord != &Node; }); } + +bool hasPrivateConstructor(const CXXRecordDecl *RD) { + for (auto &&Ctor : RD->ctors()) { + if (Ctor->getAccess() == AS_private) + return true; + } + + return false; +} + +bool isDerivedBefriended(const CXXRecordDecl *CRTP, + const CXXRecordDecl *Derived) { + for (auto &&Friend : CRTP->friends()) { + if (Friend->getFriendType()->getType()->getAsCXXRecordDecl() == Derived) + return true; + } + + return false; +} } // namespace void UnsafeCrtpCheck::registerMatchers(MatchFinder *Finder) { - Finder->addMatcher(classTemplateSpecializationDecl( - decl().bind("crtp"), - hasAnyTemplateArgument(refersToType(recordType( - hasDeclaration(cxxRecordDecl(isDerivedFrom( - cxxRecordDecl(isBoundNode("crtp"))))))))), - this); + 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 *MatchedCRTP = Result.Nodes.getNodeAs<CXXRecordDecl>("crtp"); - MatchedCRTP->dump(); + const auto *MatchedCRTP = + Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("crtp"); + const auto *MatchedDerived = Result.Nodes.getNodeAs<CXXRecordDecl>("derived"); + + if (!MatchedCRTP->hasUserDeclaredConstructor()) { + diag(MatchedCRTP->getLocation(), + "the implicit default constructor of the CRTP is publicly accessible") + << MatchedCRTP + << FixItHint::CreateInsertion( + MatchedCRTP->getBraceRange().getBegin().getLocWithOffset(1), + "private: " + MatchedCRTP->getNameAsString() + "() = default;"); + + diag(MatchedCRTP->getLocation(), "consider making it private", + DiagnosticIDs::Note); + } + + // FIXME: Extract this. + size_t idx = 0; + for (auto &&TemplateArg : MatchedCRTP->getTemplateArgs().asArray()) { + if (TemplateArg.getKind() == TemplateArgument::Type && + TemplateArg.getAsType()->getAsCXXRecordDecl() == MatchedDerived) { + break; + } + ++idx; + } - for (auto &&Ctor : MatchedCRTP->ctors()) { - if (Ctor->getAccess() != AS_private) { - Ctor->dump(); - }; + if (hasPrivateConstructor(MatchedCRTP) && + !isDerivedBefriended(MatchedCRTP, MatchedDerived)) { + diag(MatchedCRTP->getLocation(), + "the CRTP cannot be constructed from the derived class") + << MatchedCRTP + << FixItHint::CreateInsertion( + MatchedCRTP->getBraceRange().getEnd().getLocWithOffset(-1), + "friend " + + MatchedCRTP->getSpecializedTemplate() + ->getTemplateParameters() + ->asArray()[idx] + ->getNameAsString() + + ';'); + diag(MatchedCRTP->getLocation(), + "consider declaring the derived class as friend", DiagnosticIDs::Note); } // if (!MatchedDecl->getIdentifier() || >From 67ce49a474deb62dcf8393daec8f927235fdc3fe Mon Sep 17 00:00:00 2001 From: isuckatcs <65320245+isucka...@users.noreply.github.com> Date: Sun, 18 Feb 2024 02:18:18 +0100 Subject: [PATCH 04/14] fix friend detection --- .../clang-tidy/bugprone/UnsafeCrtpCheck.cpp | 75 +++++++++++-------- 1 file changed, 45 insertions(+), 30 deletions(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp index 9063b385c0d1b4..05d9218e9c3a71 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp @@ -34,15 +34,39 @@ bool hasPrivateConstructor(const CXXRecordDecl *RD) { return false; } -bool isDerivedBefriended(const CXXRecordDecl *CRTP, - const CXXRecordDecl *Derived) { +bool isDerivedParameterBefriended(const CXXRecordDecl *CRTP, + const NamedDecl *Derived) { for (auto &&Friend : CRTP->friends()) { - if (Friend->getFriendType()->getType()->getAsCXXRecordDecl() == Derived) + const auto *TTPT = + dyn_cast<TemplateTypeParmType>(Friend->getFriendType()->getType()); + + if (TTPT && TTPT->getDecl() == 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); +} + } // namespace void UnsafeCrtpCheck::registerMatchers(MatchFinder *Finder) { @@ -61,42 +85,33 @@ void UnsafeCrtpCheck::check(const MatchFinder::MatchResult &Result) { Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("crtp"); const auto *MatchedDerived = Result.Nodes.getNodeAs<CXXRecordDecl>("derived"); - if (!MatchedCRTP->hasUserDeclaredConstructor()) { - diag(MatchedCRTP->getLocation(), + const auto *CRTPTemplate = + MatchedCRTP->getSpecializedTemplate()->getTemplatedDecl(); + + if (!CRTPTemplate->hasUserDeclaredConstructor()) { + diag(CRTPTemplate->getLocation(), "the implicit default constructor of the CRTP is publicly accessible") - << MatchedCRTP + << CRTPTemplate << FixItHint::CreateInsertion( - MatchedCRTP->getBraceRange().getBegin().getLocWithOffset(1), - "private: " + MatchedCRTP->getNameAsString() + "() = default;"); + CRTPTemplate->getBraceRange().getBegin().getLocWithOffset(1), + "private: " + CRTPTemplate->getNameAsString() + "() = default;"); - diag(MatchedCRTP->getLocation(), "consider making it private", + diag(CRTPTemplate->getLocation(), "consider making it private", DiagnosticIDs::Note); } - // FIXME: Extract this. - size_t idx = 0; - for (auto &&TemplateArg : MatchedCRTP->getTemplateArgs().asArray()) { - if (TemplateArg.getKind() == TemplateArgument::Type && - TemplateArg.getAsType()->getAsCXXRecordDecl() == MatchedDerived) { - break; - } - ++idx; - } + const auto *DerivedTemplateParameter = + *getDerivedParameter(MatchedCRTP, MatchedDerived); - if (hasPrivateConstructor(MatchedCRTP) && - !isDerivedBefriended(MatchedCRTP, MatchedDerived)) { - diag(MatchedCRTP->getLocation(), + if (hasPrivateConstructor(CRTPTemplate) && + !isDerivedParameterBefriended(CRTPTemplate, DerivedTemplateParameter)) { + diag(CRTPTemplate->getLocation(), "the CRTP cannot be constructed from the derived class") - << MatchedCRTP + << CRTPTemplate << FixItHint::CreateInsertion( - MatchedCRTP->getBraceRange().getEnd().getLocWithOffset(-1), - "friend " + - MatchedCRTP->getSpecializedTemplate() - ->getTemplateParameters() - ->asArray()[idx] - ->getNameAsString() + - ';'); - diag(MatchedCRTP->getLocation(), + CRTPTemplate->getBraceRange().getEnd().getLocWithOffset(-1), + "friend " + DerivedTemplateParameter->getNameAsString() + ';'); + diag(CRTPTemplate->getLocation(), "consider declaring the derived class as friend", DiagnosticIDs::Note); } >From 77fc451bb00d240b3b19857d7ae418358a9cf3d4 Mon Sep 17 00:00:00 2001 From: isuckatcs <65320245+isucka...@users.noreply.github.com> Date: Sun, 18 Feb 2024 02:28:22 +0100 Subject: [PATCH 05/14] cleanup --- .../clang-tidy/bugprone/UnsafeCrtpCheck.cpp | 48 ++++++++----------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp index 05d9218e9c3a71..09d2db4d993984 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp @@ -7,9 +7,7 @@ //===----------------------------------------------------------------------===// #include "UnsafeCrtpCheck.h" -#include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" -#include "clang/Lex/Lexer.h" using namespace clang::ast_matchers; @@ -80,48 +78,40 @@ void UnsafeCrtpCheck::registerMatchers(MatchFinder *Finder) { } void UnsafeCrtpCheck::check(const MatchFinder::MatchResult &Result) { - - const auto *MatchedCRTP = + const auto *CRTPInstantiation = Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("crtp"); - const auto *MatchedDerived = Result.Nodes.getNodeAs<CXXRecordDecl>("derived"); + const auto *Derived = Result.Nodes.getNodeAs<CXXRecordDecl>("derived"); - const auto *CRTPTemplate = - MatchedCRTP->getSpecializedTemplate()->getTemplatedDecl(); + const CXXRecordDecl *CRTPDeclaration = + CRTPInstantiation->getSpecializedTemplate()->getTemplatedDecl(); - if (!CRTPTemplate->hasUserDeclaredConstructor()) { - diag(CRTPTemplate->getLocation(), + if (!CRTPDeclaration->hasUserDeclaredConstructor()) { + diag(CRTPDeclaration->getLocation(), "the implicit default constructor of the CRTP is publicly accessible") - << CRTPTemplate + << CRTPDeclaration << FixItHint::CreateInsertion( - CRTPTemplate->getBraceRange().getBegin().getLocWithOffset(1), - "private: " + CRTPTemplate->getNameAsString() + "() = default;"); - - diag(CRTPTemplate->getLocation(), "consider making it private", + CRTPDeclaration->getBraceRange().getBegin().getLocWithOffset(1), + "private: " + CRTPDeclaration->getNameAsString() + + "() = default;"); + diag(CRTPDeclaration->getLocation(), "consider making it private", DiagnosticIDs::Note); } const auto *DerivedTemplateParameter = - *getDerivedParameter(MatchedCRTP, MatchedDerived); + *getDerivedParameter(CRTPInstantiation, Derived); - if (hasPrivateConstructor(CRTPTemplate) && - !isDerivedParameterBefriended(CRTPTemplate, DerivedTemplateParameter)) { - diag(CRTPTemplate->getLocation(), + if (hasPrivateConstructor(CRTPDeclaration) && + !isDerivedParameterBefriended(CRTPDeclaration, + DerivedTemplateParameter)) { + diag(CRTPDeclaration->getLocation(), "the CRTP cannot be constructed from the derived class") - << CRTPTemplate + << CRTPDeclaration << FixItHint::CreateInsertion( - CRTPTemplate->getBraceRange().getEnd().getLocWithOffset(-1), + CRTPDeclaration->getBraceRange().getEnd().getLocWithOffset(-1), "friend " + DerivedTemplateParameter->getNameAsString() + ';'); - diag(CRTPTemplate->getLocation(), + diag(CRTPDeclaration->getLocation(), "consider declaring the derived class as friend", DiagnosticIDs::Note); } - - // if (!MatchedDecl->getIdentifier() || - // MatchedDecl->getName().startswith("awesome_")) - // return; - // diag(MatchedDecl->getLocation(), "function %0 is insufficiently awesome") - // << MatchedDecl - // << FixItHint::CreateInsertion(MatchedDecl->getLocation(), "awesome_"); - // diag(MatchedDecl->getLocation(), "insert 'awesome'", DiagnosticIDs::Note); } } // namespace clang::tidy::bugprone >From f486bb6ad5c3308c805fd3121278cc344edccc63 Mon Sep 17 00:00:00 2001 From: isuckatcs <65320245+isucka...@users.noreply.github.com> Date: Sun, 18 Feb 2024 04:17:25 +0100 Subject: [PATCH 06/14] implement ctor checks --- .../clang-tidy/bugprone/UnsafeCrtpCheck.cpp | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp index 09d2db4d993984..85d8b8a8ab8cd8 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "UnsafeCrtpCheck.h" +#include "../utils/LexerUtils.h" #include "clang/ASTMatchers/ASTMatchFinder.h" using namespace clang::ast_matchers; @@ -65,6 +66,24 @@ getDerivedParameter(const ClassTemplateSpecializationDecl *CRTP, return CRTP->getSpecializedTemplate()->getTemplateParameters()->getParam(Idx); } +std::vector<FixItHint> hintMakeCtorPrivate(const CXXConstructorDecl *Ctor, + std::string OriginalAccess, + const SourceManager &SM, + const LangOptions &LangOpts) { + std::vector<FixItHint> Hints; + + Hints.emplace_back(FixItHint::CreateInsertion( + Ctor->getBeginLoc().getLocWithOffset(-1), "private:\n")); + + Hints.emplace_back(FixItHint::CreateInsertion( + Ctor->isExplicitlyDefaulted() + ? utils::lexer::findNextTerminator(Ctor->getEndLoc(), SM, LangOpts) + .getLocWithOffset(1) + : Ctor->getEndLoc().getLocWithOffset(1), + '\n' + OriginalAccess + ':' + '\n')); + + return Hints; +} } // namespace void UnsafeCrtpCheck::registerMatchers(MatchFinder *Finder) { @@ -112,6 +131,23 @@ void UnsafeCrtpCheck::check(const MatchFinder::MatchResult &Result) { 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 >From dc250cdebd9c990ffb16ffde0e2b2794597e1647 Mon Sep 17 00:00:00 2001 From: isuckatcs <65320245+isucka...@users.noreply.github.com> Date: Sun, 18 Feb 2024 04:23:18 +0100 Subject: [PATCH 07/14] fix struct default ctor generation --- clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp index 85d8b8a8ab8cd8..9295ba0009ac24 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp @@ -111,7 +111,8 @@ void UnsafeCrtpCheck::check(const MatchFinder::MatchResult &Result) { << FixItHint::CreateInsertion( CRTPDeclaration->getBraceRange().getBegin().getLocWithOffset(1), "private: " + CRTPDeclaration->getNameAsString() + - "() = default;"); + "() = default;" + + (CRTPDeclaration->isStruct() ? "\npublic:\n" : "")); diag(CRTPDeclaration->getLocation(), "consider making it private", DiagnosticIDs::Note); } >From 10355cb112d1ec8b07c1a016f7cf6e327c30cc94 Mon Sep 17 00:00:00 2001 From: isuckatcs <65320245+isucka...@users.noreply.github.com> Date: Sun, 18 Feb 2024 18:09:47 +0100 Subject: [PATCH 08/14] testing --- .../clang-tidy/bugprone/UnsafeCrtpCheck.cpp | 11 +- .../checkers/bugprone/unsafe-crtp.cpp | 172 ++++++++++++++++-- 2 files changed, 166 insertions(+), 17 deletions(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp index 9295ba0009ac24..b97e0142420be4 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp @@ -110,9 +110,9 @@ void UnsafeCrtpCheck::check(const MatchFinder::MatchResult &Result) { << CRTPDeclaration << FixItHint::CreateInsertion( CRTPDeclaration->getBraceRange().getBegin().getLocWithOffset(1), - "private: " + CRTPDeclaration->getNameAsString() + - "() = default;" + - (CRTPDeclaration->isStruct() ? "\npublic:\n" : "")); + (CRTPDeclaration->isStruct() ? "\nprivate:\n" : "\n") + + CRTPDeclaration->getNameAsString() + "() = default;" + + (CRTPDeclaration->isStruct() ? "\npublic:\n" : "\n")); diag(CRTPDeclaration->getLocation(), "consider making it private", DiagnosticIDs::Note); } @@ -127,8 +127,9 @@ void UnsafeCrtpCheck::check(const MatchFinder::MatchResult &Result) { "the CRTP cannot be constructed from the derived class") << CRTPDeclaration << FixItHint::CreateInsertion( - CRTPDeclaration->getBraceRange().getEnd().getLocWithOffset(-1), - "friend " + DerivedTemplateParameter->getNameAsString() + ';'); + CRTPDeclaration->getBraceRange().getEnd(), + "friend " + DerivedTemplateParameter->getNameAsString() + ';' + + '\n'); diag(CRTPDeclaration->getLocation(), "consider declaring the derived class as friend", DiagnosticIDs::Note); } 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 index 90350ad7abc578..b8e38aa18e5e41 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-crtp.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-crtp.cpp @@ -1,14 +1,162 @@ // RUN: %check_clang_tidy %s bugprone-unsafe-crtp %t -// FIXME: Add something that triggers the check here. -void f(); -// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'f' is insufficiently awesome [bugprone-unsafe-crtp] - -// FIXME: Verify the applied fix. -// * Make the CHECK patterns specific enough and try to make verified lines -// unique to avoid incorrect matches. -// * Use {{}} for regular expressions. -// CHECK-FIXES: {{^}}void awesome_f();{{$}} - -// FIXME: Add something that doesn't trigger the check here. -void awesome_f2(); +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 >From d0e20bb3854fa7dc3331adf7a9da7dcf26d759a6 Mon Sep 17 00:00:00 2001 From: isuckatcs <65320245+isucka...@users.noreply.github.com> Date: Tue, 20 Feb 2024 15:53:16 +0100 Subject: [PATCH 09/14] docs --- .../clang-tidy/bugprone/UnsafeCrtpCheck.h | 2 +- clang-tools-extra/docs/ReleaseNotes.rst | 2 +- .../checks/bugprone/unsafe-crtp.rst | 58 ++++++++++++++++++- 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.h b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.h index 7c41bbb0678521..ac1a8f09208f95 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.h @@ -13,7 +13,7 @@ namespace clang::tidy::bugprone { -/// FIXME: Write a short description. +/// 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 diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 785e90d0c75eb0..4883e1bcaad35c 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -109,7 +109,7 @@ New checks - New :doc:`bugprone-unsafe-crtp <clang-tidy/checks/bugprone/unsafe-crtp>` check. - FIXME: add release notes. + Detects error-prone CRTP. New check aliases ^^^^^^^^^^^^^^^^^ 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 index 3352af02a8744f..8e6955999512a6 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-crtp.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-crtp.rst @@ -3,4 +3,60 @@ bugprone-unsafe-crtp ==================== -FIXME: Describe what patterns does the check detect and why. Give examples. +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; >From aff328b35303a1a366625d7764f08431b8329f8b Mon Sep 17 00:00:00 2001 From: isuckatcs <65320245+isucka...@users.noreply.github.com> Date: Tue, 20 Feb 2024 18:36:19 +0100 Subject: [PATCH 10/14] cleanup --- .../clang-tidy/bugprone/UnsafeCrtpCheck.cpp | 19 ++++++++++--------- clang-tools-extra/docs/ReleaseNotes.rst | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp index b97e0142420be4..a1caf934ef2cd6 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp @@ -67,7 +67,7 @@ getDerivedParameter(const ClassTemplateSpecializationDecl *CRTP, } std::vector<FixItHint> hintMakeCtorPrivate(const CXXConstructorDecl *Ctor, - std::string OriginalAccess, + const std::string &OriginalAccess, const SourceManager &SM, const LangOptions &LangOpts) { std::vector<FixItHint> Hints; @@ -75,12 +75,12 @@ std::vector<FixItHint> hintMakeCtorPrivate(const CXXConstructorDecl *Ctor, Hints.emplace_back(FixItHint::CreateInsertion( Ctor->getBeginLoc().getLocWithOffset(-1), "private:\n")); - Hints.emplace_back(FixItHint::CreateInsertion( + SourceLocation CtorEndLoc = Ctor->isExplicitlyDefaulted() ? utils::lexer::findNextTerminator(Ctor->getEndLoc(), SM, LangOpts) - .getLocWithOffset(1) - : Ctor->getEndLoc().getLocWithOffset(1), - '\n' + OriginalAccess + ':' + '\n')); + : Ctor->getEndLoc(); + Hints.emplace_back(FixItHint::CreateInsertion( + CtorEndLoc.getLocWithOffset(1), '\n' + OriginalAccess + ':' + '\n')); return Hints; } @@ -100,19 +100,20 @@ void UnsafeCrtpCheck::check(const MatchFinder::MatchResult &Result) { const auto *CRTPInstantiation = Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("crtp"); const auto *Derived = 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), - (CRTPDeclaration->isStruct() ? "\nprivate:\n" : "\n") + - CRTPDeclaration->getNameAsString() + "() = default;" + - (CRTPDeclaration->isStruct() ? "\npublic:\n" : "\n")); + (IsStruct ? "\nprivate:\n" : "\n") + + CRTPDeclaration->getNameAsString() + "() = default;\n" + + (IsStruct ? "public:\n" : "")); diag(CRTPDeclaration->getLocation(), "consider making it private", DiagnosticIDs::Note); } diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 4883e1bcaad35c..c80746dce2de25 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -109,7 +109,7 @@ New checks - New :doc:`bugprone-unsafe-crtp <clang-tidy/checks/bugprone/unsafe-crtp>` check. - Detects error-prone CRTP. + Detects error-prone CRTP usage. New check aliases ^^^^^^^^^^^^^^^^^ >From d5c77a42b4174ea44b33c76411b3f308508325ef Mon Sep 17 00:00:00 2001 From: isuckatcs <65320245+isucka...@users.noreply.github.com> Date: Tue, 20 Feb 2024 18:50:41 +0100 Subject: [PATCH 11/14] more testing --- .../checkers/bugprone/unsafe-crtp.cpp | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) 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 index b8e38aa18e5e41..13bd19610ed942 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-crtp.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-crtp.cpp @@ -160,3 +160,45 @@ class CRTP { 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>> {}; + +template<> +class A<int> : CRTP<A<int>> {}; +} // namespace template_derived_explicit_specialization + +namespace no_warning { +template <typename T> +class CRTP +{ + CRTP() = default; + friend T; +}; + +class A : CRTP<A> {}; +} // namespace no_warning >From 9901bc3af91afe26f0f54382fdffc83f97164541 Mon Sep 17 00:00:00 2001 From: isuckatcs <65320245+isucka...@users.noreply.github.com> Date: Tue, 20 Feb 2024 19:46:13 +0100 Subject: [PATCH 12/14] extend --- .../clang-tidy/bugprone/UnsafeCrtpCheck.cpp | 21 ++++++++++---- .../checkers/bugprone/unsafe-crtp.cpp | 28 +++++++++++++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp index a1caf934ef2cd6..7bb50aaf950cbe 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp @@ -34,12 +34,22 @@ bool hasPrivateConstructor(const CXXRecordDecl *RD) { } bool isDerivedParameterBefriended(const CXXRecordDecl *CRTP, - const NamedDecl *Derived) { + const NamedDecl *Param) { for (auto &&Friend : CRTP->friends()) { const auto *TTPT = dyn_cast<TemplateTypeParmType>(Friend->getFriendType()->getType()); - if (TTPT && TTPT->getDecl() == Derived) + 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; } @@ -99,7 +109,7 @@ void UnsafeCrtpCheck::registerMatchers(MatchFinder *Finder) { void UnsafeCrtpCheck::check(const MatchFinder::MatchResult &Result) { const auto *CRTPInstantiation = Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("crtp"); - const auto *Derived = Result.Nodes.getNodeAs<CXXRecordDecl>("derived"); + const auto *DerivedRecord = Result.Nodes.getNodeAs<CXXRecordDecl>("derived"); const CXXRecordDecl *CRTPDeclaration = CRTPInstantiation->getSpecializedTemplate()->getTemplatedDecl(); @@ -119,11 +129,12 @@ void UnsafeCrtpCheck::check(const MatchFinder::MatchResult &Result) { } const auto *DerivedTemplateParameter = - *getDerivedParameter(CRTPInstantiation, Derived); + *getDerivedParameter(CRTPInstantiation, DerivedRecord); if (hasPrivateConstructor(CRTPDeclaration) && !isDerivedParameterBefriended(CRTPDeclaration, - DerivedTemplateParameter)) { + DerivedTemplateParameter) && + !isDerivedClassBefriended(CRTPDeclaration, DerivedRecord)) { diag(CRTPDeclaration->getLocation(), "the CRTP cannot be constructed from the derived class") << CRTPDeclaration 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 index 13bd19610ed942..edcfd67677fd7b 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-crtp.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-crtp.cpp @@ -192,6 +192,34 @@ template<> class A<int> : CRTP<A<int>> {}; } // namespace template_derived_explicit_specialization +namespace explicit_derived_friend { +class A; + +template <typename T> +class CRTP { + CRTP() = default; + friend A; +}; + +class A : CRTP<A> {}; +} // namespace explicit_derived_friend + +namespace explicit_derived_friend_multiple { +class A; + +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; + friend A; +}; + +class A : CRTP<A> {}; +class B : CRTP<B> {}; +} // namespace explicit_derived_friend_multiple + namespace no_warning { template <typename T> class CRTP >From 74f69781f6a8881da59cb6e52ca3a2230d2cffc0 Mon Sep 17 00:00:00 2001 From: isuckatcs <65320245+isucka...@users.noreply.github.com> Date: Tue, 20 Feb 2024 20:09:12 +0100 Subject: [PATCH 13/14] format --- clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp index b7b4d85a86cce2..78e128fb0e5e6d 100644 --- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp @@ -238,8 +238,7 @@ 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<UnsafeCrtpCheck>("bugprone-unsafe-crtp"); CheckFactories.registerCheck<UnsafeFunctionsCheck>( "bugprone-unsafe-functions"); CheckFactories.registerCheck<UnusedLocalNonTrivialVariableCheck>( >From fe8dbf3f9cc0cd294ed2b3e1148bdbff77009847 Mon Sep 17 00:00:00 2001 From: isuckatcs <65320245+isucka...@users.noreply.github.com> Date: Tue, 20 Feb 2024 20:29:39 +0100 Subject: [PATCH 14/14] addressed comments --- .../clang-tidy/bugprone/UnsafeCrtpCheck.cpp | 12 ++++++++---- .../clang-tidy/bugprone/UnsafeCrtpCheck.h | 3 ++- clang-tools-extra/docs/ReleaseNotes.rst | 10 +++++----- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp index 7bb50aaf950cbe..227bd9803a1267 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.cpp @@ -85,7 +85,7 @@ std::vector<FixItHint> hintMakeCtorPrivate(const CXXConstructorDecl *Ctor, Hints.emplace_back(FixItHint::CreateInsertion( Ctor->getBeginLoc().getLocWithOffset(-1), "private:\n")); - SourceLocation CtorEndLoc = + const SourceLocation CtorEndLoc = Ctor->isExplicitlyDefaulted() ? utils::lexer::findNextTerminator(Ctor->getEndLoc(), SM, LangOpts) : Ctor->getEndLoc(); @@ -114,7 +114,7 @@ void UnsafeCrtpCheck::check(const MatchFinder::MatchResult &Result) { CRTPInstantiation->getSpecializedTemplate()->getTemplatedDecl(); if (!CRTPDeclaration->hasUserDeclaredConstructor()) { - bool IsStruct = CRTPDeclaration->isStruct(); + const bool IsStruct = CRTPDeclaration->isStruct(); diag(CRTPDeclaration->getLocation(), "the implicit default constructor of the CRTP is publicly accessible") @@ -150,8 +150,8 @@ void UnsafeCrtpCheck::check(const MatchFinder::MatchResult &Result) { if (Ctor->getAccess() == AS_private) continue; - bool IsPublic = Ctor->getAccess() == AS_public; - std::string Access = IsPublic ? "public" : "protected"; + const bool IsPublic = Ctor->getAccess() == AS_public; + const std::string Access = IsPublic ? "public" : "protected"; diag(Ctor->getLocation(), "%0 contructor allows the CRTP to be %select{inherited " @@ -164,4 +164,8 @@ void UnsafeCrtpCheck::check(const MatchFinder::MatchResult &Result) { } } +bool UnsafeCrtpCheck::isLanguageVersionSupported( + const LangOptions &LangOpts) const { + return LangOpts.CPlusPlus; +} } // namespace clang::tidy::bugprone diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.h b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.h index ac1a8f09208f95..fb7245edeb0fa9 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeCrtpCheck.h @@ -13,7 +13,7 @@ namespace clang::tidy::bugprone { -/// Finds CRTP used in an error-prone way. +/// Detects error-prone CRTP usage. /// /// For the user-facing documentation see: /// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/unsafe-crtp.html @@ -23,6 +23,7 @@ class UnsafeCrtpCheck : public ClangTidyCheck { : ClangTidyCheck(Name, Context) {} void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override; }; } // namespace clang::tidy::bugprone diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index c80746dce2de25..bfe4e5761bbe8e 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -100,17 +100,17 @@ Improvements to clang-tidy New checks ^^^^^^^^^^ +- New :doc:`bugprone-unsafe-crtp + <clang-tidy/checks/bugprone/unsafe-crtp>` check. + + Detects error-prone CRTP usage. + - New :doc:`readability-use-std-min-max <clang-tidy/checks/readability/use-std-min-max>` check. Replaces certain conditional statements with equivalent calls to ``std::min`` or ``std::max``. -- New :doc:`bugprone-unsafe-crtp - <clang-tidy/checks/bugprone/unsafe-crtp>` check. - - Detects error-prone CRTP usage. - New check aliases ^^^^^^^^^^^^^^^^^ _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits