https://github.com/balazske updated https://github.com/llvm/llvm-project/pull/140086
From 65d44a4eb9621e49a96f1ac43e5a1bbd6691dc13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= <balazs.k...@ericsson.com> Date: Thu, 15 May 2025 17:41:16 +0200 Subject: [PATCH 1/3] [clang-tidy] Added check 'bugprone-function-visibility-change' --- .../bugprone/BugproneTidyModule.cpp | 3 + .../clang-tidy/bugprone/CMakeLists.txt | 1 + .../FunctionVisibilityChangeCheck.cpp | 74 ++++++ .../bugprone/FunctionVisibilityChangeCheck.h | 33 +++ clang-tools-extra/docs/ReleaseNotes.rst | 5 + .../bugprone/function-visibility-change.rst | 43 ++++ .../docs/clang-tidy/checks/list.rst | 1 + .../bugprone/function-visibility-change.cpp | 234 ++++++++++++++++++ 8 files changed, 394 insertions(+) create mode 100644 clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.cpp create mode 100644 clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.h create mode 100644 clang-tools-extra/docs/clang-tidy/checks/bugprone/function-visibility-change.rst create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/function-visibility-change.cpp diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp index b780a85bdf3fe..7cf58c5218969 100644 --- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp @@ -30,6 +30,7 @@ #include "FoldInitTypeCheck.h" #include "ForwardDeclarationNamespaceCheck.h" #include "ForwardingReferenceOverloadCheck.h" +#include "FunctionVisibilityChangeCheck.h" #include "ImplicitWideningOfMultiplicationResultCheck.h" #include "InaccurateEraseCheck.h" #include "IncDecInConditionsCheck.h" @@ -143,6 +144,8 @@ class BugproneModule : public ClangTidyModule { "bugprone-forward-declaration-namespace"); CheckFactories.registerCheck<ForwardingReferenceOverloadCheck>( "bugprone-forwarding-reference-overload"); + CheckFactories.registerCheck<FunctionVisibilityChangeCheck>( + "bugprone-function-visibility-change"); CheckFactories.registerCheck<ImplicitWideningOfMultiplicationResultCheck>( "bugprone-implicit-widening-of-multiplication-result"); CheckFactories.registerCheck<InaccurateEraseCheck>( diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt index e310ea9c94543..b4f7ba76f4cee 100644 --- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt @@ -26,6 +26,7 @@ add_clang_library(clangTidyBugproneModule STATIC FoldInitTypeCheck.cpp ForwardDeclarationNamespaceCheck.cpp ForwardingReferenceOverloadCheck.cpp + FunctionVisibilityChangeCheck.cpp ImplicitWideningOfMultiplicationResultCheck.cpp InaccurateEraseCheck.cpp IncorrectEnableIfCheck.cpp diff --git a/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.cpp new file mode 100644 index 0000000000000..7ea4ed20705ed --- /dev/null +++ b/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.cpp @@ -0,0 +1,74 @@ +//===--- FunctionVisibilityChangeCheck.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 "FunctionVisibilityChangeCheck.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::bugprone { + +void FunctionVisibilityChangeCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + cxxMethodDecl( + ofClass(cxxRecordDecl().bind("class")), + forEachOverridden(cxxMethodDecl(ofClass(cxxRecordDecl().bind("base"))) + .bind("base_func"))) + .bind("func"), + this); +} + +void FunctionVisibilityChangeCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *MatchedFunction = Result.Nodes.getNodeAs<FunctionDecl>("func"); + const auto *ParentClass = Result.Nodes.getNodeAs<CXXRecordDecl>("class"); + const auto *OverriddenFunction = + Result.Nodes.getNodeAs<FunctionDecl>("base_func"); + const auto *BaseClass = Result.Nodes.getNodeAs<CXXRecordDecl>("base"); + + if (!MatchedFunction->isCanonicalDecl()) + return; + + AccessSpecifier ActualAccess = MatchedFunction->getAccess(); + AccessSpecifier OverriddenAccess = OverriddenFunction->getAccess(); + + CXXBasePaths Paths; + if (!ParentClass->isDerivedFrom(BaseClass, Paths)) + return; + const CXXBaseSpecifier *InheritanceWithStrictVisibility = nullptr; + for (const CXXBasePath &Path : Paths) { + for (auto Elem : Path) { + if (Elem.Base->getAccessSpecifier() > OverriddenAccess) { + OverriddenAccess = Elem.Base->getAccessSpecifier(); + InheritanceWithStrictVisibility = Elem.Base; + } + } + } + + if (ActualAccess != OverriddenAccess) { + if (InheritanceWithStrictVisibility) { + diag(MatchedFunction->getLocation(), + "visibility of function %0 is changed from %1 (through %1 " + "inheritance of class %2) to %3") + << MatchedFunction << OverriddenAccess + << InheritanceWithStrictVisibility->getType() << ActualAccess; + diag(InheritanceWithStrictVisibility->getBeginLoc(), + "this inheritance would make %0 %1", DiagnosticIDs::Note) + << MatchedFunction << OverriddenAccess; + } else { + diag(MatchedFunction->getLocation(), + "visibility of function %0 is changed from %1 in class %2 to %3") + << MatchedFunction << OverriddenAccess << BaseClass << ActualAccess; + } + diag(OverriddenFunction->getLocation(), "function declared here as %0", + DiagnosticIDs::Note) + << OverriddenFunction->getAccess(); + } +} + +} // namespace clang::tidy::bugprone diff --git a/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.h b/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.h new file mode 100644 index 0000000000000..ec7152fb63acb --- /dev/null +++ b/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.h @@ -0,0 +1,33 @@ +//===--- FunctionVisibilityChangeCheck.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_FUNCTIONVISIBILITYCHANGECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_FUNCTIONVISIBILITYCHANGECHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::bugprone { + +/// Check function visibility changes in subclasses. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/function-visibility-change.html +class FunctionVisibilityChangeCheck : public ClangTidyCheck { +public: + FunctionVisibilityChangeCheck(StringRef Name, ClangTidyContext *Context) + : 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 { + return LangOpts.CPlusPlus; + } +}; + +} // namespace clang::tidy::bugprone + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_FUNCTIONVISIBILITYCHANGECHECK_H diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 579fca54924d5..52f61a78fa3b7 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -124,6 +124,11 @@ New checks pointer and store it as class members without handle the copy and move constructors and the assignments. +- New :doc:`bugprone-function-visibility-change + <clang-tidy/checks/bugprone/function-visibility-change>` check. + + Check function visibility changes in subclasses. + - New :doc:`bugprone-unintended-char-ostream-output <clang-tidy/checks/bugprone/unintended-char-ostream-output>` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/function-visibility-change.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/function-visibility-change.rst new file mode 100644 index 0000000000000..3b7940e5e584e --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/function-visibility-change.rst @@ -0,0 +1,43 @@ +.. title:: clang-tidy - bugprone-function-visibility-change + +bugprone-function-visibility-change +=================================== + +Check changes in visibility of C++ member functions in subclasses. The check +detects if a virtual function is overridden with a different visibility than in +the base class declaration. Only normal functions are detected, no constructors, +operators, conversions or other special functions. + +.. code-block:: c++ + + class A { + public: + virtual void f_pub(); + private: + virtual void f_priv(); + }; + + class B: public A { + public: + void f_priv(); // warning: changed visibility from private to public + private: + void f_pub(); // warning: changed visibility from public to private + }; + + class C: private A { + // no warning: f_pub becomes private in this case but this is from the + // private inheritance + }; + + class D: private A { + public: + void f_pub(); // warning: changed visibility from private to public + // 'f_pub' would have private access but is forced to be + // public + }; + +The changed visibility can be an indicator of bad design or a result of +coding error or code changes. If it is intentional, it can be avoided by +adding an additional virtual function with the new access. + + diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index 18f1467285fab..59653a2059e64 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -98,6 +98,7 @@ Clang-Tidy Checks :doc:`bugprone-fold-init-type <bugprone/fold-init-type>`, :doc:`bugprone-forward-declaration-namespace <bugprone/forward-declaration-namespace>`, :doc:`bugprone-forwarding-reference-overload <bugprone/forwarding-reference-overload>`, + :doc:`bugprone-function-visibility-change <bugprone/function-visibility-change>`, "Yes" :doc:`bugprone-implicit-widening-of-multiplication-result <bugprone/implicit-widening-of-multiplication-result>`, "Yes" :doc:`bugprone-inaccurate-erase <bugprone/inaccurate-erase>`, "Yes" :doc:`bugprone-inc-dec-in-conditions <bugprone/inc-dec-in-conditions>`, diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/function-visibility-change.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/function-visibility-change.cpp new file mode 100644 index 0000000000000..eb4532374267b --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/function-visibility-change.cpp @@ -0,0 +1,234 @@ +// RUN: %check_clang_tidy %s bugprone-function-visibility-change %t + +class A { +public: + virtual void pub_foo1() {} + virtual void pub_foo2() {} + virtual void pub_foo3() {} +protected: + virtual void prot_foo1(); + virtual void prot_foo2(); + virtual void prot_foo3(); +private: + virtual void priv_foo1() {} + virtual void priv_foo2() {} + virtual void priv_foo3() {} +}; + +void A::prot_foo1() {} +void A::prot_foo2() {} +void A::prot_foo3() {} + +namespace test1 { + +class B: public A { +public: + void pub_foo1() override {} + void prot_foo1() override {} + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo1' is changed from protected in class 'A' to public [bugprone-function-visibility-change] + // CHECK-MESSAGES: :9:16: note: function declared here as protected + void priv_foo1() override {} + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'priv_foo1' is changed from private in class 'A' to public [bugprone-function-visibility-change] + // CHECK-MESSAGES: :13:16: note: function declared here as private +protected: + void pub_foo2() override {} + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'pub_foo2' is changed from public in class 'A' to protected [bugprone-function-visibility-change] + // CHECK-MESSAGES: :6:16: note: function declared here as public + void prot_foo2() override {} + void priv_foo2() override {} + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'priv_foo2' is changed from private in class 'A' to protected [bugprone-function-visibility-change] + // CHECK-MESSAGES: :14:16: note: function declared here as private +private: + void pub_foo3() override {} + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'pub_foo3' is changed from public in class 'A' to private [bugprone-function-visibility-change] + // CHECK-MESSAGES: :7:16: note: function declared here as public + void prot_foo3() override {} + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo3' is changed from protected in class 'A' to private [bugprone-function-visibility-change] + // CHECK-MESSAGES: :11:16: note: function declared here as protected + void priv_foo3() override {} +}; + +class C: public B { +public: + void pub_foo1() override; +protected: + void prot_foo1() override; + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo1' is changed from public in class 'B' to protected [bugprone-function-visibility-change] + // CHECK-MESSAGES: :27:8: note: function declared here as public +private: + void priv_foo1() override; + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'priv_foo1' is changed from public in class 'B' to private [bugprone-function-visibility-change] + // CHECK-MESSAGES: :30:8: note: function declared here as public +}; + +void C::prot_foo1() {} +void C::priv_foo1() {} + +} + +namespace test2 { + +class B: public A { +public: + void pub_foo1() override; +protected: + void prot_foo1() override; +private: + void priv_foo1() override; +}; + +class C: public B { +public: + void pub_foo1() override; + void prot_foo1() override; + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo1' is changed from protected in class 'B' to public + // CHECK-MESSAGES: :75:8: note: function declared here as protected + void priv_foo1() override; + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'priv_foo1' is changed from private in class 'B' to public + // CHECK-MESSAGES: :77:8: note: function declared here as private + + void pub_foo2() override; + void prot_foo2() override; + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo2' is changed from protected in class 'A' to public + // CHECK-MESSAGES: :10:16: note: function declared here as protected + void priv_foo2() override; + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'priv_foo2' is changed from private in class 'A' to public + // CHECK-MESSAGES: :14:16: note: function declared here as private +}; + +} + +namespace test3 { + +class B: private A { +public: + void pub_foo1() override; + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'pub_foo1' is changed from private (through private inheritance of class 'A') to public + // CHECK-MESSAGES: :103:10: note: this inheritance would make 'pub_foo1' private + // CHECK-MESSAGES: :5:16: note: function declared here as public +protected: + void prot_foo1() override; + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo1' is changed from private (through private inheritance of class 'A') to protected + // CHECK-MESSAGES: :103:10: note: this inheritance would make 'prot_foo1' private + // CHECK-MESSAGES: :9:16: note: function declared here as protected +private: + void priv_foo1() override; + +public: + void prot_foo2() override; + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo2' is changed from private (through private inheritance of class 'A') to public + // CHECK-MESSAGES: :103:10: note: this inheritance would make 'prot_foo2' private + // CHECK-MESSAGES: :10:16: note: function declared here as protected + void priv_foo2() override; + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'priv_foo2' is changed from private in class 'A' to public + // CHECK-MESSAGES: :14:16: note: function declared here as private + +private: + void pub_foo3() override; + void prot_foo3() override; +}; + +class C: private A { +}; + +class D: public C { +public: + void pub_foo1() override; + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'pub_foo1' is changed from private (through private inheritance of class 'A') to public + // CHECK-MESSAGES: :131:10: note: this inheritance would make 'pub_foo1' private + // CHECK-MESSAGES: :5:16: note: function declared here as public +}; + + +} + +namespace test4 { + +struct Base1 { +public: + virtual void foo1(); +private: + virtual void foo2(); +}; + +struct Base2 { +public: + virtual void foo2(); +private: + virtual void foo1(); +}; + +struct A : public Base1, public Base2 { +protected: + void foo1() override; + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'foo1' is changed from private in class 'Base2' to protected + // CHECK-MESSAGES: :158:16: note: function declared here as private + // CHECK-MESSAGES: :[[@LINE-3]]:8: warning: visibility of function 'foo1' is changed from public in class 'Base1' to protected + // CHECK-MESSAGES: :149:16: note: function declared here as public +private: + void foo2() override; + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'foo2' is changed from public in class 'Base2' to private + // CHECK-MESSAGES: :156:16: note: function declared here as public +}; + +} + +namespace test5 { + +struct B1: virtual public A {}; +struct B2: virtual private A {}; +struct B: public B1, public B2 { +public: + void pub_foo1() override; + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'pub_foo1' is changed from private (through private inheritance of class 'A') to public + // CHECK-MESSAGES: :179:12: note: this inheritance would make 'pub_foo1' private + // CHECK-MESSAGES: :5:16: note: function declared here as public +}; + +} + +namespace test6 { +class A { +private: + A(int); +}; +class B: public A { +public: + using A::A; +}; +} + +namespace test7 { + +template <typename T> +class A { +protected: + virtual T foo(); +}; + +template <typename T> +class B: public A<T> { +private: + T foo() override; + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: visibility of function 'foo' is changed from protected in class 'A<int>' to private + // CHECK-MESSAGES: :206:13: note: function declared here as protected +}; + +template <typename T> +class C: private A<T> { +public: + T foo() override; + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: visibility of function 'foo' is changed from private (through private inheritance of class 'A<int>') to public + // CHECK-MESSAGES: :218:10: note: this inheritance would make 'foo' private + // CHECK-MESSAGES: :206:13: note: function declared here as protected +}; + +B<int> fB() { + return B<int>{}; +} + +C<int> fC() { + return C<int>{}; +} + +} From f19ab2e4cd0eee1b261767486cf7826f8c7b7ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= <balazs.k...@ericsson.com> Date: Mon, 19 May 2025 18:11:40 +0200 Subject: [PATCH 2/3] code and doc fixes, added option --- .../FunctionVisibilityChangeCheck.cpp | 60 +++++++++++--- .../bugprone/FunctionVisibilityChangeCheck.h | 11 ++- clang-tools-extra/docs/ReleaseNotes.rst | 2 +- .../bugprone/function-visibility-change.rst | 20 ++--- .../docs/clang-tidy/checks/list.rst | 2 +- .../test-system-header.h | 14 ++++ .../bugprone/function-visibility-change.cpp | 79 ++++++++++++++++--- 7 files changed, 153 insertions(+), 35 deletions(-) create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/function-visibility-change/test-system-header.h diff --git a/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.cpp index 7ea4ed20705ed..f5cfd0c37d4c4 100644 --- a/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.cpp @@ -11,12 +11,45 @@ using namespace clang::ast_matchers; -namespace clang::tidy::bugprone { +namespace clang::tidy { + +template <> +struct OptionEnumMapping<bugprone::FunctionVisibilityChangeCheck::ChangeKind> { + static llvm::ArrayRef< + std::pair<bugprone::FunctionVisibilityChangeCheck::ChangeKind, StringRef>> + getEnumMapping() { + static constexpr std::pair< + bugprone::FunctionVisibilityChangeCheck::ChangeKind, StringRef> + Mapping[] = { + {bugprone::FunctionVisibilityChangeCheck::ChangeKind::Any, "any"}, + {bugprone::FunctionVisibilityChangeCheck::ChangeKind::Widening, + "widening"}, + {bugprone::FunctionVisibilityChangeCheck::ChangeKind::Narrowing, + "narrowing"}, + }; + return {Mapping}; + } +}; + +namespace bugprone { + +FunctionVisibilityChangeCheck::FunctionVisibilityChangeCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + DetectVisibilityChange( + Options.get("DisallowedVisibilityChange", ChangeKind::Any)) {} + +void FunctionVisibilityChangeCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "DisallowedVisibilityChange", DetectVisibilityChange); +} void FunctionVisibilityChangeCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( cxxMethodDecl( - ofClass(cxxRecordDecl().bind("class")), + isVirtual(), + ofClass( + cxxRecordDecl(unless(isExpansionInSystemHeader())).bind("class")), forEachOverridden(cxxMethodDecl(ofClass(cxxRecordDecl().bind("base"))) .bind("base_func"))) .bind("func"), @@ -26,14 +59,14 @@ void FunctionVisibilityChangeCheck::registerMatchers(MatchFinder *Finder) { void FunctionVisibilityChangeCheck::check( const MatchFinder::MatchResult &Result) { const auto *MatchedFunction = Result.Nodes.getNodeAs<FunctionDecl>("func"); + if (!MatchedFunction->isCanonicalDecl()) + return; + const auto *ParentClass = Result.Nodes.getNodeAs<CXXRecordDecl>("class"); const auto *OverriddenFunction = Result.Nodes.getNodeAs<FunctionDecl>("base_func"); const auto *BaseClass = Result.Nodes.getNodeAs<CXXRecordDecl>("base"); - if (!MatchedFunction->isCanonicalDecl()) - return; - AccessSpecifier ActualAccess = MatchedFunction->getAccess(); AccessSpecifier OverriddenAccess = OverriddenFunction->getAccess(); @@ -42,7 +75,7 @@ void FunctionVisibilityChangeCheck::check( return; const CXXBaseSpecifier *InheritanceWithStrictVisibility = nullptr; for (const CXXBasePath &Path : Paths) { - for (auto Elem : Path) { + for (const CXXBasePathElement &Elem : Path) { if (Elem.Base->getAccessSpecifier() > OverriddenAccess) { OverriddenAccess = Elem.Base->getAccessSpecifier(); InheritanceWithStrictVisibility = Elem.Base; @@ -51,6 +84,13 @@ void FunctionVisibilityChangeCheck::check( } if (ActualAccess != OverriddenAccess) { + if (DetectVisibilityChange == ChangeKind::Widening && + ActualAccess > OverriddenAccess) + return; + if (DetectVisibilityChange == ChangeKind::Narrowing && + ActualAccess < OverriddenAccess) + return; + if (InheritanceWithStrictVisibility) { diag(MatchedFunction->getLocation(), "visibility of function %0 is changed from %1 (through %1 " @@ -58,8 +98,8 @@ void FunctionVisibilityChangeCheck::check( << MatchedFunction << OverriddenAccess << InheritanceWithStrictVisibility->getType() << ActualAccess; diag(InheritanceWithStrictVisibility->getBeginLoc(), - "this inheritance would make %0 %1", DiagnosticIDs::Note) - << MatchedFunction << OverriddenAccess; + "%0 is inherited as %1 here", DiagnosticIDs::Note) + << InheritanceWithStrictVisibility->getType() << OverriddenAccess; } else { diag(MatchedFunction->getLocation(), "visibility of function %0 is changed from %1 in class %2 to %3") @@ -71,4 +111,6 @@ void FunctionVisibilityChangeCheck::check( } } -} // namespace clang::tidy::bugprone +} // namespace bugprone + +} // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.h b/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.h index ec7152fb63acb..9d4b701d51314 100644 --- a/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.h @@ -13,19 +13,24 @@ namespace clang::tidy::bugprone { -/// Check function visibility changes in subclasses. +/// Checks function visibility changes in subclasses. /// /// For the user-facing documentation see: /// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/function-visibility-change.html class FunctionVisibilityChangeCheck : public ClangTidyCheck { public: - FunctionVisibilityChangeCheck(StringRef Name, ClangTidyContext *Context) - : ClangTidyCheck(Name, Context) {} + enum class ChangeKind { Any, Widening, Narrowing }; + + FunctionVisibilityChangeCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { return LangOpts.CPlusPlus; } + +private: + ChangeKind DetectVisibilityChange; }; } // namespace clang::tidy::bugprone diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 52f61a78fa3b7..6b752359c712d 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -127,7 +127,7 @@ New checks - New :doc:`bugprone-function-visibility-change <clang-tidy/checks/bugprone/function-visibility-change>` check. - Check function visibility changes in subclasses. + Checks function visibility changes in subclasses. - New :doc:`bugprone-unintended-char-ostream-output <clang-tidy/checks/bugprone/unintended-char-ostream-output>` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/function-visibility-change.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/function-visibility-change.rst index 3b7940e5e584e..4e0744eede128 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/function-visibility-change.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/function-visibility-change.rst @@ -3,10 +3,13 @@ bugprone-function-visibility-change =================================== -Check changes in visibility of C++ member functions in subclasses. The check -detects if a virtual function is overridden with a different visibility than in -the base class declaration. Only normal functions are detected, no constructors, -operators, conversions or other special functions. +Checks changes in visibility of C++ member functions in subclasses. This +includes for example if a virtual function declared as `private` is overridden +and declared as `public` in a subclass. The detected change is the modification +of visibility resulting from keywords `public`, `protected`, `private` at +overridden virtual functions. Use of the `using` keyword is not considered by +this check. The check applies to any normal virtual function and optionally to +destructors or operators. .. code-block:: c++ @@ -36,8 +39,7 @@ operators, conversions or other special functions. // public }; -The changed visibility can be an indicator of bad design or a result of -coding error or code changes. If it is intentional, it can be avoided by -adding an additional virtual function with the new access. - - +If the visibility is changed in this way, it can indicate bad design or +programming error. If the change is necessary, it can be achieved by the +``using`` keyword in a more safe way (this has no effect on the visibility +in further subclasses). diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index 59653a2059e64..c5b8a73f3af65 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -98,7 +98,7 @@ Clang-Tidy Checks :doc:`bugprone-fold-init-type <bugprone/fold-init-type>`, :doc:`bugprone-forward-declaration-namespace <bugprone/forward-declaration-namespace>`, :doc:`bugprone-forwarding-reference-overload <bugprone/forwarding-reference-overload>`, - :doc:`bugprone-function-visibility-change <bugprone/function-visibility-change>`, "Yes" + :doc:`bugprone-function-visibility-change <bugprone/function-visibility-change>`, :doc:`bugprone-implicit-widening-of-multiplication-result <bugprone/implicit-widening-of-multiplication-result>`, "Yes" :doc:`bugprone-inaccurate-erase <bugprone/inaccurate-erase>`, "Yes" :doc:`bugprone-inc-dec-in-conditions <bugprone/inc-dec-in-conditions>`, diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/function-visibility-change/test-system-header.h b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/function-visibility-change/test-system-header.h new file mode 100644 index 0000000000000..e64e1924a1708 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/function-visibility-change/test-system-header.h @@ -0,0 +1,14 @@ +#pragma clang system_header + +namespace sys { + +struct Base { + virtual void publicF(); +}; + +struct Derived: public Base { +private: + void publicF() override; +}; + +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/function-visibility-change.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/function-visibility-change.cpp index eb4532374267b..b2279d45fe47e 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/function-visibility-change.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/function-visibility-change.cpp @@ -1,5 +1,5 @@ -// RUN: %check_clang_tidy %s bugprone-function-visibility-change %t - +// RUN: %check_clang_tidy %s bugprone-function-visibility-change %t -- -- -I %S/Inputs/function-visibility-change +#include <test-system-header.h> class A { public: virtual void pub_foo1() {} @@ -104,12 +104,12 @@ class B: private A { public: void pub_foo1() override; // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'pub_foo1' is changed from private (through private inheritance of class 'A') to public - // CHECK-MESSAGES: :103:10: note: this inheritance would make 'pub_foo1' private + // CHECK-MESSAGES: :103:10: note: 'A' is inherited as private here // CHECK-MESSAGES: :5:16: note: function declared here as public protected: void prot_foo1() override; // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo1' is changed from private (through private inheritance of class 'A') to protected - // CHECK-MESSAGES: :103:10: note: this inheritance would make 'prot_foo1' private + // CHECK-MESSAGES: :103:10: note: 'A' is inherited as private here // CHECK-MESSAGES: :9:16: note: function declared here as protected private: void priv_foo1() override; @@ -117,7 +117,7 @@ class B: private A { public: void prot_foo2() override; // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo2' is changed from private (through private inheritance of class 'A') to public - // CHECK-MESSAGES: :103:10: note: this inheritance would make 'prot_foo2' private + // CHECK-MESSAGES: :103:10: note: 'A' is inherited as private here // CHECK-MESSAGES: :10:16: note: function declared here as protected void priv_foo2() override; // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'priv_foo2' is changed from private in class 'A' to public @@ -135,7 +135,7 @@ class D: public C { public: void pub_foo1() override; // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'pub_foo1' is changed from private (through private inheritance of class 'A') to public - // CHECK-MESSAGES: :131:10: note: this inheritance would make 'pub_foo1' private + // CHECK-MESSAGES: :131:10: note: 'A' is inherited as private here // CHECK-MESSAGES: :5:16: note: function declared here as public }; @@ -181,24 +181,30 @@ struct B: public B1, public B2 { public: void pub_foo1() override; // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'pub_foo1' is changed from private (through private inheritance of class 'A') to public - // CHECK-MESSAGES: :179:12: note: this inheritance would make 'pub_foo1' private + // CHECK-MESSAGES: :179:12: note: 'A' is inherited as private here // CHECK-MESSAGES: :5:16: note: function declared here as public }; } -namespace test6 { +namespace test_using { + class A { private: A(int); +protected: + virtual void f(); }; + class B: public A { public: using A::A; + using A::f; }; + } -namespace test7 { +namespace test_template { template <typename T> class A { @@ -211,7 +217,7 @@ class B: public A<T> { private: T foo() override; // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: visibility of function 'foo' is changed from protected in class 'A<int>' to private - // CHECK-MESSAGES: :206:13: note: function declared here as protected + // CHECK-MESSAGES: :[[@LINE-8]]:13: note: function declared here as protected }; template <typename T> @@ -219,8 +225,8 @@ class C: private A<T> { public: T foo() override; // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: visibility of function 'foo' is changed from private (through private inheritance of class 'A<int>') to public - // CHECK-MESSAGES: :218:10: note: this inheritance would make 'foo' private - // CHECK-MESSAGES: :206:13: note: function declared here as protected + // CHECK-MESSAGES: :[[@LINE-4]]:10: note: 'A<int>' is inherited as private here + // CHECK-MESSAGES: :[[@LINE-17]]:13: note: function declared here as protected }; B<int> fB() { @@ -232,3 +238,52 @@ C<int> fC() { } } + +namespace test_system_header { + +struct SysDerived: public sys::Base { +private: + void publicF(); + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'publicF' is changed from public in class 'Base' to private +}; + +} + +namespace test_destructor { + +class A { +public: + virtual ~A(); +}; + +class B: public A { +protected: + ~B(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: visibility of function '~B' + // CHECK-MESSAGES: :[[@LINE-7]]:11: note: function declared here +}; + +} + +namespace test_operator { + +class A { + virtual int operator()(int); + virtual A& operator++(); + virtual operator double() const; +}; + +class B: public A { +protected: + int operator()(int); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: visibility of function 'operator()' + // CHECK-MESSAGES: :[[@LINE-9]]:15: note: function declared here + A& operator++(); + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: visibility of function 'operator++' + // CHECK-MESSAGES: :[[@LINE-11]]:14: note: function declared here + operator double() const; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: visibility of function 'operator double' + // CHECK-MESSAGES: :[[@LINE-13]]:11: note: function declared here +}; + +} \ No newline at end of file From 8f1b20eb1531d21d65e74e42965b37acea1a15c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= <balazs.k...@ericsson.com> Date: Wed, 21 May 2025 12:55:47 +0200 Subject: [PATCH 3/3] improvements, added options, check rename --- .../bugprone/BugproneTidyModule.cpp | 4 +- .../FunctionVisibilityChangeCheck.cpp | 24 +++++- .../bugprone/FunctionVisibilityChangeCheck.h | 5 +- .../bugprone/function-visibility-change.rst | 45 ---------- .../visibility-change-to-virtual-function.rst | 86 +++++++++++++++++++ .../docs/clang-tidy/checks/list.rst | 2 +- .../test-system-header.h | 0 ...lity-change-to-virtual-function-ignore.cpp | 60 +++++++++++++ ...ity-change-to-virtual-function-options.cpp | 75 ++++++++++++++++ ...visibility-change-to-virtual-function.cpp} | 20 ++--- 10 files changed, 260 insertions(+), 61 deletions(-) delete mode 100644 clang-tools-extra/docs/clang-tidy/checks/bugprone/function-visibility-change.rst create mode 100644 clang-tools-extra/docs/clang-tidy/checks/bugprone/visibility-change-to-virtual-function.rst rename clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/{function-visibility-change => visibility-change-to-virtual-function}/test-system-header.h (100%) create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/visibility-change-to-virtual-function-ignore.cpp create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/visibility-change-to-virtual-function-options.cpp rename clang-tools-extra/test/clang-tidy/checkers/bugprone/{function-visibility-change.cpp => visibility-change-to-virtual-function.cpp} (92%) diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp index 7cf58c5218969..bef6b876f6960 100644 --- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp @@ -144,8 +144,6 @@ class BugproneModule : public ClangTidyModule { "bugprone-forward-declaration-namespace"); CheckFactories.registerCheck<ForwardingReferenceOverloadCheck>( "bugprone-forwarding-reference-overload"); - CheckFactories.registerCheck<FunctionVisibilityChangeCheck>( - "bugprone-function-visibility-change"); CheckFactories.registerCheck<ImplicitWideningOfMultiplicationResultCheck>( "bugprone-implicit-widening-of-multiplication-result"); CheckFactories.registerCheck<InaccurateEraseCheck>( @@ -280,6 +278,8 @@ class BugproneModule : public ClangTidyModule { CheckFactories.registerCheck<UseAfterMoveCheck>("bugprone-use-after-move"); CheckFactories.registerCheck<VirtualNearMissCheck>( "bugprone-virtual-near-miss"); + CheckFactories.registerCheck<FunctionVisibilityChangeCheck>( + "bugprone-visibility-change-to-virtual-function"); } }; diff --git a/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.cpp index f5cfd0c37d4c4..5ba2f51eb3dd9 100644 --- a/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.cpp @@ -7,6 +7,8 @@ //===----------------------------------------------------------------------===// #include "FunctionVisibilityChangeCheck.h" +#include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" #include "clang/ASTMatchers/ASTMatchFinder.h" using namespace clang::ast_matchers; @@ -37,20 +39,31 @@ FunctionVisibilityChangeCheck::FunctionVisibilityChangeCheck( StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), DetectVisibilityChange( - Options.get("DisallowedVisibilityChange", ChangeKind::Any)) {} + Options.get("DisallowedVisibilityChange", ChangeKind::Any)), + CheckDestructors(Options.get("CheckDestructors", false)), + CheckOperators(Options.get("CheckOperators", false)), + IgnoredFunctions(utils::options::parseStringList( + Options.get("IgnoredFunctions", ""))) {} void FunctionVisibilityChangeCheck::storeOptions( ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "DisallowedVisibilityChange", DetectVisibilityChange); + Options.store(Opts, "CheckDestructors", CheckDestructors); + Options.store(Opts, "CheckOperators", CheckOperators); + Options.store(Opts, "IgnoredFunctions", + utils::options::serializeStringList(IgnoredFunctions)); } void FunctionVisibilityChangeCheck::registerMatchers(MatchFinder *Finder) { + auto IgnoredDecl = + namedDecl(matchers::matchesAnyListedName(IgnoredFunctions)); Finder->addMatcher( cxxMethodDecl( isVirtual(), ofClass( cxxRecordDecl(unless(isExpansionInSystemHeader())).bind("class")), - forEachOverridden(cxxMethodDecl(ofClass(cxxRecordDecl().bind("base"))) + forEachOverridden(cxxMethodDecl(ofClass(cxxRecordDecl().bind("base")), + unless(IgnoredDecl)) .bind("base_func"))) .bind("func"), this); @@ -61,6 +74,13 @@ void FunctionVisibilityChangeCheck::check( const auto *MatchedFunction = Result.Nodes.getNodeAs<FunctionDecl>("func"); if (!MatchedFunction->isCanonicalDecl()) return; + DeclarationName::NameKind NK = MatchedFunction->getDeclName().getNameKind(); + if (!CheckDestructors && NK == DeclarationName::CXXDestructorName) + return; + if (!CheckOperators && NK != DeclarationName::Identifier && + NK != DeclarationName::CXXConstructorName && + NK != DeclarationName::CXXDestructorName) + return; const auto *ParentClass = Result.Nodes.getNodeAs<CXXRecordDecl>("class"); const auto *OverriddenFunction = diff --git a/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.h b/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.h index 9d4b701d51314..2c397217faddd 100644 --- a/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/FunctionVisibilityChangeCheck.h @@ -16,7 +16,7 @@ namespace clang::tidy::bugprone { /// Checks function visibility changes in subclasses. /// /// For the user-facing documentation see: -/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/function-visibility-change.html +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/visibility-change-to-virtual-function.html class FunctionVisibilityChangeCheck : public ClangTidyCheck { public: enum class ChangeKind { Any, Widening, Narrowing }; @@ -31,6 +31,9 @@ class FunctionVisibilityChangeCheck : public ClangTidyCheck { private: ChangeKind DetectVisibilityChange; + bool CheckDestructors; + bool CheckOperators; + std::vector<llvm::StringRef> IgnoredFunctions; }; } // namespace clang::tidy::bugprone diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/function-visibility-change.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/function-visibility-change.rst deleted file mode 100644 index 4e0744eede128..0000000000000 --- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/function-visibility-change.rst +++ /dev/null @@ -1,45 +0,0 @@ -.. title:: clang-tidy - bugprone-function-visibility-change - -bugprone-function-visibility-change -=================================== - -Checks changes in visibility of C++ member functions in subclasses. This -includes for example if a virtual function declared as `private` is overridden -and declared as `public` in a subclass. The detected change is the modification -of visibility resulting from keywords `public`, `protected`, `private` at -overridden virtual functions. Use of the `using` keyword is not considered by -this check. The check applies to any normal virtual function and optionally to -destructors or operators. - -.. code-block:: c++ - - class A { - public: - virtual void f_pub(); - private: - virtual void f_priv(); - }; - - class B: public A { - public: - void f_priv(); // warning: changed visibility from private to public - private: - void f_pub(); // warning: changed visibility from public to private - }; - - class C: private A { - // no warning: f_pub becomes private in this case but this is from the - // private inheritance - }; - - class D: private A { - public: - void f_pub(); // warning: changed visibility from private to public - // 'f_pub' would have private access but is forced to be - // public - }; - -If the visibility is changed in this way, it can indicate bad design or -programming error. If the change is necessary, it can be achieved by the -``using`` keyword in a more safe way (this has no effect on the visibility -in further subclasses). diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/visibility-change-to-virtual-function.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/visibility-change-to-virtual-function.rst new file mode 100644 index 0000000000000..afd215d199f4a --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/visibility-change-to-virtual-function.rst @@ -0,0 +1,86 @@ +.. title:: clang-tidy - bugprone-visibility-change-to-virtual-function + +bugprone-visibility-change-to-virtual-function +============================================== + +Checks changes in visibility of C++ member functions in subclasses. This +includes for example if a virtual function declared as ``private`` is overridden +and declared as ``public`` in a subclass. The detected change is the +modification of visibility resulting from keywords ``public``, ``protected``, +``private`` at overridden virtual functions. Use of the ``using`` keyword is not +considered by this check. The check applies to any normal virtual function and +optionally to destructors or operators. + +.. code-block:: c++ + + class A { + public: + virtual void f_pub(); + private: + virtual void f_priv(); + }; + + class B: public A { + public: + void f_priv(); // warning: changed visibility from private to public + private: + void f_pub(); // warning: changed visibility from public to private + }; + + class C: private A { + // no warning: f_pub becomes private in this case but this is from the + // private inheritance + }; + + class D: private A { + public: + void f_pub(); // warning: changed visibility from private to public + // 'f_pub' would have private access but is forced to be + // public + }; + +If the visibility is changed in this way, it can indicate bad design or +programming error. + +If a virtual function is private in a subclass but public in the base class, it +can still be accessed from a pointer to the subclass if the pointer is converted +to the base type. Probably private inheritance can be used instead. + +A protected virtual function that is made public in a subclass may have valid +use cases but similar (not exactly same) effect can be achieved with the +``using`` keyword. + +Options +------- + +.. option:: DisallowedVisibilityChange + + Controls what kind of change to the visibility will be detected by the check. + Possible values are `any`, `widening`, `narrowing`. For example the + `widening` option will produce warning only if the visibility is changed + from more restrictive (``private``) to less restrictive (``public``). + Default value is `any`. + +.. option:: CheckDestructors + + If turned on, the check does apply to destructors too. Otherwise destructors + are ignored by the check. + Default value is false. + +.. option:: CheckOperators + + If turned on, the check does apply to overloaded C++ operators (as virtual + member functions) too. This includes other special member functions (like + conversions) too. This option is probably useful only in rare cases because + operators and conversions are not often virtual functions. + Default value is false. + +.. option:: IgnoredFunctions + + This option can be used to ignore the check at specific functions. + To configure this option, a semicolon-separated list of function names + should be provided. The list can contain regular expressions, in this way it + is possible to select all functions of a specific class (like `MyClass::.*`) + or a specific function of any class (like `my_function` or + `::.*::my_function`). The function names are matched at the base class. + Default value: empty string. diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index c5b8a73f3af65..2b39b0b7f5934 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -98,7 +98,6 @@ Clang-Tidy Checks :doc:`bugprone-fold-init-type <bugprone/fold-init-type>`, :doc:`bugprone-forward-declaration-namespace <bugprone/forward-declaration-namespace>`, :doc:`bugprone-forwarding-reference-overload <bugprone/forwarding-reference-overload>`, - :doc:`bugprone-function-visibility-change <bugprone/function-visibility-change>`, :doc:`bugprone-implicit-widening-of-multiplication-result <bugprone/implicit-widening-of-multiplication-result>`, "Yes" :doc:`bugprone-inaccurate-erase <bugprone/inaccurate-erase>`, "Yes" :doc:`bugprone-inc-dec-in-conditions <bugprone/inc-dec-in-conditions>`, @@ -168,6 +167,7 @@ Clang-Tidy Checks :doc:`bugprone-unused-return-value <bugprone/unused-return-value>`, :doc:`bugprone-use-after-move <bugprone/use-after-move>`, :doc:`bugprone-virtual-near-miss <bugprone/virtual-near-miss>`, "Yes" + :doc:`bugprone-visibility-change-to-virtual-function <bugprone/visibility-change-to-virtual-function>`, :doc:`cert-dcl50-cpp <cert/dcl50-cpp>`, :doc:`cert-dcl58-cpp <cert/dcl58-cpp>`, :doc:`cert-env33-c <cert/env33-c>`, diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/function-visibility-change/test-system-header.h b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/visibility-change-to-virtual-function/test-system-header.h similarity index 100% rename from clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/function-visibility-change/test-system-header.h rename to clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/visibility-change-to-virtual-function/test-system-header.h diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/visibility-change-to-virtual-function-ignore.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/visibility-change-to-virtual-function-ignore.cpp new file mode 100644 index 0000000000000..324fae5382219 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/visibility-change-to-virtual-function-ignore.cpp @@ -0,0 +1,60 @@ +// RUN: %check_clang_tidy %s bugprone-visibility-change-to-virtual-function %t -- \ +// RUN: -config="{CheckOptions: {bugprone-visibility-change-to-virtual-function.IgnoredFunctions: 'IgnoreAlways::.*;::a::IgnoreSelected::.*;IgnoreFunctions::f1;ignored_f'}}" + +class IgnoreAlways { + virtual void f(); +}; + +class IgnoreSelected { + virtual void f(); +}; + +namespace a { +class IgnoreAlways { + virtual void f(); +}; +class IgnoreSelected { + virtual void f(); +}; +} + +namespace ignore_always { +class Test1: public IgnoreAlways { +public: + void f(); + void ignored_f(int); +}; +class Test2: public a::IgnoreAlways { +public: + void f(); +}; +} + +namespace ignore_selected { +class Test1: public IgnoreSelected { +public: + void f(); + // CHECK-NOTES: :[[@LINE-1]]:8: warning: visibility of function 'f' + // CHECK-NOTES: :9:16: note: function declared here + void ignored_f(int); +}; +class Test2: public a::IgnoreSelected { +public: + void f(); +}; +} + +class IgnoreFunctions { + virtual void f1(); + virtual void f2(); + virtual void ignored_f(); +}; + +class IgnoreFunctionsTest: public IgnoreFunctions { +public: + void f1(); + void f2(); + // CHECK-NOTES: :[[@LINE-1]]:8: warning: visibility of function 'f2' + // CHECK-NOTES: :[[@LINE-9]]:16: note: function declared here + void ignored_f(); +}; diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/visibility-change-to-virtual-function-options.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/visibility-change-to-virtual-function-options.cpp new file mode 100644 index 0000000000000..a74476849d1fa --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/visibility-change-to-virtual-function-options.cpp @@ -0,0 +1,75 @@ +// RUN: %check_clang_tidy -check-suffixes=DTORS,WIDENING,NARROWING %s bugprone-visibility-change-to-virtual-function %t -- \ +// RUN: -config="{CheckOptions: {bugprone-visibility-change-to-virtual-function.CheckDestructors: true}}" + +// RUN: %check_clang_tidy -check-suffixes=OPS,WIDENING,NARROWING %s bugprone-visibility-change-to-virtual-function %t -- \ +// RUN: -config="{CheckOptions: {bugprone-visibility-change-to-virtual-function.CheckOperators: true}}" + +// RUN: %check_clang_tidy -check-suffixes=WIDENING %s bugprone-visibility-change-to-virtual-function %t -- \ +// RUN: -config="{CheckOptions: {bugprone-visibility-change-to-virtual-function.DisallowedVisibilityChange: 'widening'}}" + +// RUN: %check_clang_tidy -check-suffixes=NARROWING %s bugprone-visibility-change-to-virtual-function %t -- \ +// RUN: -config="{CheckOptions: {bugprone-visibility-change-to-virtual-function.DisallowedVisibilityChange: 'narrowing'}}" + +namespace test_change { + +class A { +protected: + virtual void f1(); + virtual void f2(); +}; + +class B: public A { +public: + void f1(); + // CHECK-NOTES-WIDENING: :[[@LINE-1]]:8: warning: visibility of function 'f1' + // CHECK-NOTES-WIDENING: :[[@LINE-8]]:16: note: function declared here +private: + void f2(); + // CHECK-NOTES-NARROWING: :[[@LINE-1]]:8: warning: visibility of function 'f2' + // CHECK-NOTES-NARROWING: :[[@LINE-11]]:16: note: function declared here +}; + +} + +namespace test_destructor { + +class A { +public: + virtual ~A(); +}; + +class B: public A { +protected: + ~B(); + // CHECK-NOTES-DTORS: :[[@LINE-1]]:3: warning: visibility of function '~B' + // CHECK-NOTES-DTORS: :[[@LINE-7]]:11: note: function declared here +}; + +} + +namespace test_operator { + +class A { + virtual A& operator=(const A&); + virtual A& operator++(); + virtual int operator()(int); + virtual operator double() const; +}; + +class B: public A { +protected: + A& operator=(const A&); + // CHECK-NOTES-OPS: :[[@LINE-1]]:6: warning: visibility of function 'operator=' + // CHECK-NOTES-OPS: :[[@LINE-10]]:14: note: function declared here + A& operator++(); + // CHECK-NOTES-OPS: :[[@LINE-1]]:6: warning: visibility of function 'operator++' + // CHECK-NOTES-OPS: :[[@LINE-12]]:14: note: function declared here + int operator()(int); + // CHECK-NOTES-OPS: :[[@LINE-1]]:7: warning: visibility of function 'operator()' + // CHECK-NOTES-OPS: :[[@LINE-14]]:15: note: function declared here + operator double() const; + // CHECK-NOTES-OPS: :[[@LINE-1]]:3: warning: visibility of function 'operator double' + // CHECK-NOTES-OPS: :[[@LINE-16]]:11: note: function declared here +}; + +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/function-visibility-change.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/visibility-change-to-virtual-function.cpp similarity index 92% rename from clang-tools-extra/test/clang-tidy/checkers/bugprone/function-visibility-change.cpp rename to clang-tools-extra/test/clang-tidy/checkers/bugprone/visibility-change-to-virtual-function.cpp index b2279d45fe47e..ce31ea7c56239 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/function-visibility-change.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/visibility-change-to-virtual-function.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s bugprone-function-visibility-change %t -- -- -I %S/Inputs/function-visibility-change +// RUN: %check_clang_tidy %s bugprone-visibility-change-to-virtual-function %t -- -config="{CheckOptions: {bugprone-visibility-change-to-virtual-function.CheckDestructors: true,bugprone-visibility-change-to-virtual-function.CheckOperators: true}}" -- -I %S/Inputs/visibility-change-to-virtual-function #include <test-system-header.h> class A { public: @@ -25,25 +25,25 @@ class B: public A { public: void pub_foo1() override {} void prot_foo1() override {} - // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo1' is changed from protected in class 'A' to public [bugprone-function-visibility-change] + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo1' is changed from protected in class 'A' to public [bugprone-visibility-change-to-virtual-function] // CHECK-MESSAGES: :9:16: note: function declared here as protected void priv_foo1() override {} - // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'priv_foo1' is changed from private in class 'A' to public [bugprone-function-visibility-change] + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'priv_foo1' is changed from private in class 'A' to public [bugprone-visibility-change-to-virtual-function] // CHECK-MESSAGES: :13:16: note: function declared here as private protected: void pub_foo2() override {} - // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'pub_foo2' is changed from public in class 'A' to protected [bugprone-function-visibility-change] + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'pub_foo2' is changed from public in class 'A' to protected [bugprone-visibility-change-to-virtual-function] // CHECK-MESSAGES: :6:16: note: function declared here as public void prot_foo2() override {} void priv_foo2() override {} - // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'priv_foo2' is changed from private in class 'A' to protected [bugprone-function-visibility-change] + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'priv_foo2' is changed from private in class 'A' to protected [bugprone-visibility-change-to-virtual-function] // CHECK-MESSAGES: :14:16: note: function declared here as private private: void pub_foo3() override {} - // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'pub_foo3' is changed from public in class 'A' to private [bugprone-function-visibility-change] + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'pub_foo3' is changed from public in class 'A' to private [bugprone-visibility-change-to-virtual-function] // CHECK-MESSAGES: :7:16: note: function declared here as public void prot_foo3() override {} - // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo3' is changed from protected in class 'A' to private [bugprone-function-visibility-change] + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo3' is changed from protected in class 'A' to private [bugprone-visibility-change-to-virtual-function] // CHECK-MESSAGES: :11:16: note: function declared here as protected void priv_foo3() override {} }; @@ -53,11 +53,11 @@ class C: public B { void pub_foo1() override; protected: void prot_foo1() override; - // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo1' is changed from public in class 'B' to protected [bugprone-function-visibility-change] + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo1' is changed from public in class 'B' to protected [bugprone-visibility-change-to-virtual-function] // CHECK-MESSAGES: :27:8: note: function declared here as public private: void priv_foo1() override; - // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'priv_foo1' is changed from public in class 'B' to private [bugprone-function-visibility-change] + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'priv_foo1' is changed from public in class 'B' to private [bugprone-visibility-change-to-virtual-function] // CHECK-MESSAGES: :30:8: note: function declared here as public }; @@ -286,4 +286,4 @@ class B: public A { // CHECK-MESSAGES: :[[@LINE-13]]:11: note: function declared here }; -} \ No newline at end of file +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits