-
Notifications
You must be signed in to change notification settings - Fork 13.5k
[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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
//===--- 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 "../utils/Matchers.h" | ||
#include "../utils/OptionsUtils.h" | ||
#include "clang/ASTMatchers/ASTMatchFinder.h" | ||
|
||
using namespace clang::ast_matchers; | ||
|
||
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)), | ||
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")), | ||
unless(IgnoredDecl)) | ||
.bind("base_func"))) | ||
.bind("func"), | ||
this); | ||
} | ||
|
||
void FunctionVisibilityChangeCheck::check( | ||
const MatchFinder::MatchResult &Result) { | ||
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 = | ||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider allowing changing access from wider to narrower, maybe some config option for that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Option is added now to set the type of change. |
||
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") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider adding "to %3 in class %4" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will this not make the message too long? (The message is already located in the class %4.) |
||
<< 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 bugprone | ||
|
||
} // namespace clang::tidy |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
//===--- 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 { | ||
|
||
/// Checks function visibility changes in subclasses. | ||
/// | ||
/// For the user-facing documentation see: | ||
/// 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 }; | ||
|
||
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; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding some option to exclude some methods by name, for example if I would have some generic method like "process_event" that I change visibility on prupose, then maybe would be good to have option for that, or option to allow of changing visibility for some base classes. |
||
|
||
private: | ||
ChangeKind DetectVisibilityChange; | ||
bool CheckDestructors; | ||
bool CheckOperators; | ||
std::vector<llvm::StringRef> IgnoredFunctions; | ||
}; | ||
|
||
} // namespace clang::tidy::bugprone | ||
|
||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_FUNCTIONVISIBILITYCHANGECHECK_H |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
.. 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. |
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; | ||
}; | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider excluding system code and add "isVirtual()"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed (inheritance from system code in non-system code is detected now)