Skip to content

[clang-tidy] Added check 'bugprone-function-visibility-change' #140086

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/misc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ add_clang_library(clangTidyMiscModule STATIC
UnusedUsingDeclsCheck.cpp
UseAnonymousNamespaceCheck.cpp
UseInternalLinkageCheck.cpp
VisibilityChangeToVirtualFunctionCheck.cpp

LINK_LIBS
clangTidy
Expand Down
3 changes: 3 additions & 0 deletions clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "UnusedUsingDeclsCheck.h"
#include "UseAnonymousNamespaceCheck.h"
#include "UseInternalLinkageCheck.h"
#include "VisibilityChangeToVirtualFunctionCheck.h"

namespace clang::tidy {
namespace misc {
Expand Down Expand Up @@ -81,6 +82,8 @@ class MiscModule : public ClangTidyModule {
"misc-use-anonymous-namespace");
CheckFactories.registerCheck<UseInternalLinkageCheck>(
"misc-use-internal-linkage");
CheckFactories.registerCheck<VisibilityChangeToVirtualFunctionCheck>(
"misc-visibility-change-to-virtual-function");
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
//===--- VisibilityChangeToVirtualFunctionCheck.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 "VisibilityChangeToVirtualFunctionCheck.h"
#include "../utils/Matchers.h"
#include "../utils/OptionsUtils.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"

using namespace clang::ast_matchers;
using namespace clang;

namespace {
AST_MATCHER(NamedDecl, isOperatorDecl) {
DeclarationName::NameKind NK = Node.getDeclName().getNameKind();
return NK != DeclarationName::Identifier &&
NK != DeclarationName::CXXConstructorName &&
NK != DeclarationName::CXXDestructorName;
}
} // namespace

namespace clang::tidy {

template <>
struct OptionEnumMapping<
misc::VisibilityChangeToVirtualFunctionCheck::ChangeKind> {
static llvm::ArrayRef<std::pair<
misc::VisibilityChangeToVirtualFunctionCheck::ChangeKind, StringRef>>
getEnumMapping() {
static constexpr std::pair<
misc::VisibilityChangeToVirtualFunctionCheck::ChangeKind, StringRef>
Mapping[] = {
{misc::VisibilityChangeToVirtualFunctionCheck::ChangeKind::Any,
"any"},
{misc::VisibilityChangeToVirtualFunctionCheck::ChangeKind::Widening,
"widening"},
{misc::VisibilityChangeToVirtualFunctionCheck::ChangeKind::
Narrowing,
"narrowing"},
};
return {Mapping};
}
};

namespace misc {

VisibilityChangeToVirtualFunctionCheck::VisibilityChangeToVirtualFunctionCheck(
StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
DetectVisibilityChange(
Options.get("DisallowedVisibilityChange", ChangeKind::Any)),
CheckDestructors(Options.get("CheckDestructors", false)),
CheckOperators(Options.get("CheckOperators", false)),
IgnoredFunctions(utils::options::parseStringList(
Options.get("IgnoredFunctions", ""))) {}

void VisibilityChangeToVirtualFunctionCheck::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 VisibilityChangeToVirtualFunctionCheck::registerMatchers(
MatchFinder *Finder) {
auto IgnoredDecl =
namedDecl(matchers::matchesAnyListedName(IgnoredFunctions));
auto FilterDestructors =
CheckDestructors ? decl() : decl(unless(cxxDestructorDecl()));
auto FilterOperators =
CheckOperators ? namedDecl() : namedDecl(unless(isOperatorDecl()));
Finder->addMatcher(
cxxMethodDecl(
isVirtual(), FilterDestructors, FilterOperators,
ofClass(
cxxRecordDecl(unless(isExpansionInSystemHeader())).bind("class")),
forEachOverridden(cxxMethodDecl(ofClass(cxxRecordDecl().bind("base")),
unless(IgnoredDecl))
.bind("base_func")))
.bind("func"),
this);
}

void VisibilityChangeToVirtualFunctionCheck::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");

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 (const CXXBasePathElement &Elem : Path) {
if (Elem.Base->getAccessSpecifier() > OverriddenAccess) {
OverriddenAccess = Elem.Base->getAccessSpecifier();
InheritanceWithStrictVisibility = Elem.Base;
}
}
}

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 "
"inheritance of class %2) to %3")
<< MatchedFunction << OverriddenAccess
<< InheritanceWithStrictVisibility->getType() << ActualAccess;
diag(InheritanceWithStrictVisibility->getBeginLoc(),
"%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")
<< MatchedFunction << OverriddenAccess << BaseClass << ActualAccess;
}
diag(OverriddenFunction->getLocation(), "function declared here as %0",
DiagnosticIDs::Note)
<< OverriddenFunction->getAccess();
}
}

} // namespace misc

} // namespace clang::tidy
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//===--- VisibilityChangeToVirtualFunctionCheck.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_MISC_VISIBILITYCHANGETOVIRTUALFUNCTIONCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_VISIBILITYCHANGETOVIRTUALFUNCTIONCHECK_H

#include "../ClangTidyCheck.h"

namespace clang::tidy::misc {

/// Finds virtual function overrides with different visibility than the function
/// in the base class.
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/misc/visibility-change-to-virtual-function.html
class VisibilityChangeToVirtualFunctionCheck : public ClangTidyCheck {
public:
enum class ChangeKind { Any, Widening, Narrowing };

VisibilityChangeToVirtualFunctionCheck(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;
bool CheckDestructors;
bool CheckOperators;
std::vector<llvm::StringRef> IgnoredFunctions;
};

} // namespace clang::tidy::misc

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_VISIBILITYCHANGETOVIRTUALFUNCTIONCHECK_H
6 changes: 6 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ New checks
Finds unscoped (non-class) ``enum`` declarations and suggests using
``enum class`` instead.

- New :doc:`misc-visibility-change-to-virtual-function
<clang-tidy/checks/misc/visibility-change-to-virtual-function>` check.

Finds virtual function overrides with different visibility than the function
in the base class.

- New :doc:`modernize-use-scoped-lock
<clang-tidy/checks/modernize/use-scoped-lock>` check.

Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ Clang-Tidy Checks
:doc:`misc-unused-using-decls <misc/unused-using-decls>`, "Yes"
:doc:`misc-use-anonymous-namespace <misc/use-anonymous-namespace>`,
:doc:`misc-use-internal-linkage <misc/use-internal-linkage>`, "Yes"
:doc:`misc-visibility-change-to-virtual-function <misc/visibility-change-to-virtual-function>`,
:doc:`modernize-avoid-bind <modernize/avoid-bind>`, "Yes"
:doc:`modernize-avoid-c-arrays <modernize/avoid-c-arrays>`,
:doc:`modernize-concat-nested-namespaces <modernize/concat-nested-namespaces>`, "Yes"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
.. title:: clang-tidy - misc-visibility-change-to-virtual-function

misc-visibility-change-to-virtual-function
==========================================

Finds virtual function overrides with different visibility than the function
in the base class. 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. The check applies to
any normal virtual function and optionally to destructors or operators. Use of
the ``using`` keyword is not considered as visibility change by this check.


.. 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 is empty string.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#pragma clang system_header

namespace sys {

struct Base {
virtual void publicF();
};

struct Derived: public Base {
private:
void publicF() override;
};

}
Loading
Loading