Skip to content

Commit 85d2a46

Browse files
t-a-jamesTom JamesEugeneZelenkovbvictor
authored
[clang-tidy] New bugprone-derived-method-shadowing-base-method (#154746)
This PR introduces a new bugprone check to find methods in derived classes that hide methods with the same signature in base classes. The best description of what this new check does is the unit test code in `clang-tools-extra/test/clang-tidy/checkers/bugprone/derived-method-shadowing-base-method.cpp`. --------- Co-authored-by: Tom James <tom.james@siemens.com> Co-authored-by: EugeneZelenko <eugene.zelenko@gmail.com> Co-authored-by: Baranov Victor <bar.victor.2002@gmail.com>
1 parent bc44ff2 commit 85d2a46

File tree

8 files changed

+344
-0
lines changed

8 files changed

+344
-0
lines changed

clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "CopyConstructorInitCheck.h"
2424
#include "CrtpConstructorAccessibilityCheck.h"
2525
#include "DanglingHandleCheck.h"
26+
#include "DerivedMethodShadowingBaseMethodCheck.h"
2627
#include "DynamicStaticInitializersCheck.h"
2728
#include "EasilySwappableParametersCheck.h"
2829
#include "EmptyCatchCheck.h"
@@ -134,6 +135,8 @@ class BugproneModule : public ClangTidyModule {
134135
"bugprone-copy-constructor-init");
135136
CheckFactories.registerCheck<DanglingHandleCheck>(
136137
"bugprone-dangling-handle");
138+
CheckFactories.registerCheck<DerivedMethodShadowingBaseMethodCheck>(
139+
"bugprone-derived-method-shadowing-base-method");
137140
CheckFactories.registerCheck<DynamicStaticInitializersCheck>(
138141
"bugprone-dynamic-static-initializers");
139142
CheckFactories.registerCheck<EasilySwappableParametersCheck>(

clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ add_clang_library(clangTidyBugproneModule STATIC
1919
CopyConstructorInitCheck.cpp
2020
CrtpConstructorAccessibilityCheck.cpp
2121
DanglingHandleCheck.cpp
22+
DerivedMethodShadowingBaseMethodCheck.cpp
2223
DynamicStaticInitializersCheck.cpp
2324
EasilySwappableParametersCheck.cpp
2425
EmptyCatchCheck.cpp
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "DerivedMethodShadowingBaseMethodCheck.h"
10+
#include "clang/ASTMatchers/ASTMatchFinder.h"
11+
#include "clang/ASTMatchers/ASTMatchers.h"
12+
13+
using namespace clang::ast_matchers;
14+
15+
namespace clang::tidy::bugprone {
16+
17+
static bool sameBasicType(const ParmVarDecl *Lhs, const ParmVarDecl *Rhs) {
18+
return Lhs && Rhs &&
19+
Lhs->getType()
20+
.getCanonicalType()
21+
.getNonReferenceType()
22+
.getUnqualifiedType() == Rhs->getType()
23+
.getCanonicalType()
24+
.getNonReferenceType()
25+
.getUnqualifiedType();
26+
}
27+
28+
static bool namesCollide(const CXXMethodDecl &Lhs, const CXXMethodDecl &Rhs) {
29+
if (Lhs.getNameAsString() != Rhs.getNameAsString())
30+
return false;
31+
if (Lhs.isConst() != Rhs.isConst())
32+
return false;
33+
if (Lhs.getNumParams() != Rhs.getNumParams())
34+
return false;
35+
for (unsigned int It = 0; It < Lhs.getNumParams(); ++It)
36+
if (!sameBasicType(Lhs.getParamDecl(It), Rhs.getParamDecl(It)))
37+
return false;
38+
return true;
39+
}
40+
41+
namespace {
42+
43+
AST_MATCHER(CXXMethodDecl, nameCollidesWithMethodInBase) {
44+
const CXXRecordDecl *DerivedClass = Node.getParent();
45+
for (const auto &Base : DerivedClass->bases()) {
46+
llvm::SmallVector<const CXXBaseSpecifier *, 8> Stack;
47+
Stack.push_back(&Base);
48+
while (!Stack.empty()) {
49+
const CXXBaseSpecifier *CurrentBaseSpec = Stack.back();
50+
Stack.pop_back();
51+
52+
if (CurrentBaseSpec->getAccessSpecifier() ==
53+
clang::AccessSpecifier::AS_private)
54+
continue;
55+
56+
const CXXRecordDecl *CurrentRecord =
57+
CurrentBaseSpec->getType()->getAsCXXRecordDecl();
58+
if (!CurrentRecord)
59+
continue;
60+
61+
// For multiple inheritance, we ignore only the bases that come from the
62+
// std:: namespace
63+
if (CurrentRecord->isInStdNamespace())
64+
continue;
65+
66+
for (const auto &BaseMethod : CurrentRecord->methods()) {
67+
if (namesCollide(*BaseMethod, Node)) {
68+
ast_matchers::internal::BoundNodesTreeBuilder Result(*Builder);
69+
Builder->setBinding("base_method",
70+
clang::DynTypedNode::create(*BaseMethod));
71+
return true;
72+
}
73+
}
74+
75+
for (const auto &SubBase : CurrentRecord->bases())
76+
Stack.push_back(&SubBase);
77+
}
78+
}
79+
return false;
80+
}
81+
82+
// Same as clang-tools-extra/clang-tidy/modernize/UseEqualsDefaultCheck.cpp,
83+
// similar matchers are used elsewhere in LLVM
84+
AST_MATCHER(CXXMethodDecl, isOutOfLine) { return Node.isOutOfLine(); }
85+
86+
} // namespace
87+
88+
DerivedMethodShadowingBaseMethodCheck::DerivedMethodShadowingBaseMethodCheck(
89+
StringRef Name, ClangTidyContext *Context)
90+
: ClangTidyCheck(Name, Context) {}
91+
92+
void DerivedMethodShadowingBaseMethodCheck::registerMatchers(
93+
MatchFinder *Finder) {
94+
Finder->addMatcher(
95+
cxxMethodDecl(
96+
unless(anyOf(isOutOfLine(), isStaticStorageClass(), isImplicit(),
97+
cxxConstructorDecl(), isOverride(), isPrivate(),
98+
// isFinal(), //included with isOverride,
99+
// Templates are not handled yet
100+
ast_matchers::isTemplateInstantiation(),
101+
ast_matchers::isExplicitTemplateSpecialization())),
102+
ofClass(cxxRecordDecl(isDerivedFrom(cxxRecordDecl()))
103+
.bind("derived_class")),
104+
nameCollidesWithMethodInBase())
105+
.bind("shadowing_method"),
106+
this);
107+
}
108+
109+
void DerivedMethodShadowingBaseMethodCheck::check(
110+
const MatchFinder::MatchResult &Result) {
111+
const auto *ShadowingMethod =
112+
Result.Nodes.getNodeAs<CXXMethodDecl>("shadowing_method");
113+
const auto *DerivedClass =
114+
Result.Nodes.getNodeAs<CXXRecordDecl>("derived_class");
115+
const auto *BaseMethod = Result.Nodes.getNodeAs<CXXMethodDecl>("base_method");
116+
117+
if (!ShadowingMethod || !DerivedClass || !BaseMethod)
118+
llvm_unreachable("Required binding not found");
119+
120+
diag(ShadowingMethod->getBeginLoc(),
121+
"'%0' shadows method with the same name in class %1")
122+
<< ShadowingMethod->getQualifiedNameAsString() << BaseMethod->getParent();
123+
diag(BaseMethod->getBeginLoc(), "previous definition of %0 is here",
124+
DiagnosticIDs::Note)
125+
<< ShadowingMethod;
126+
}
127+
128+
} // namespace clang::tidy::bugprone
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DERIVEDMETHODSHADOWINGBASEMETHODCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DERIVEDMETHODSHADOWINGBASEMETHODCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
14+
namespace clang::tidy::bugprone {
15+
16+
/// Checks that a derived class does not define the same (non virtual) method as
17+
/// a base class
18+
///
19+
/// For the user-facing documentation see:
20+
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/derived-method-shadowing-base-method.html
21+
class DerivedMethodShadowingBaseMethodCheck : public ClangTidyCheck {
22+
public:
23+
DerivedMethodShadowingBaseMethodCheck(StringRef Name,
24+
ClangTidyContext *Context);
25+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
26+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
27+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
28+
return LangOpts.CPlusPlus;
29+
}
30+
std::optional<TraversalKind> getCheckTraversalKind() const override {
31+
return TK_IgnoreUnlessSpelledInSource;
32+
}
33+
};
34+
35+
} // namespace clang::tidy::bugprone
36+
37+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DERIVEDMETHODSHADOWINGBASEMETHODCHECK_H

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,11 @@ New checks
163163
Detects default initialization (to 0) of variables with ``enum`` type where
164164
the enum has no enumerator with value of 0.
165165

166+
- New :doc:`bugprone-derived-method-shadowing-base-method
167+
<clang-tidy/checks/bugprone/derived-method-shadowing-base-method>` check.
168+
169+
Finds derived class methods that shadow a (non-virtual) base class method.
170+
166171
- New :doc:`cppcoreguidelines-pro-bounds-avoid-unchecked-container-access
167172
<clang-tidy/checks/cppcoreguidelines/pro-bounds-avoid-unchecked-container-access>`
168173
check.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
.. title:: clang-tidy - bugprone-derived-method-shadowing-base-method
2+
3+
bugprone-derived-method-shadowing-base-method
4+
=============================================
5+
6+
Finds derived class methods that shadow a (non-virtual) base class method.
7+
8+
In order to be considered "shadowing", methods must have the same signature
9+
(i.e. the same name, same number of parameters, same parameter types, etc).
10+
Only checks public, non-templated methods.
11+
12+
The below example is bugprone because consumers of the ``Derived`` class will
13+
expect the ``reset`` method to do the work of ``Base::reset()`` in addition to extra
14+
work required to reset the ``Derived`` class. Common fixes include:
15+
16+
- Making the ``reset`` method polymorphic
17+
- Re-naming ``Derived::reset`` if it's not meant to intersect with ``Base::reset``
18+
- Using ``using Base::reset`` to change the access specifier
19+
20+
This is also a violation of the Liskov Substitution Principle.
21+
22+
.. code-block:: c++
23+
24+
struct Base {
25+
void reset() {/* reset the base class */};
26+
};
27+
28+
struct Derived : public Base {
29+
void reset() {/* reset the derived class, but not the base class */};
30+
};

clang-tools-extra/docs/clang-tidy/checks/list.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ Clang-Tidy Checks
9191
:doc:`bugprone-copy-constructor-init <bugprone/copy-constructor-init>`, "Yes"
9292
:doc:`bugprone-crtp-constructor-accessibility <bugprone/crtp-constructor-accessibility>`, "Yes"
9393
:doc:`bugprone-dangling-handle <bugprone/dangling-handle>`,
94+
:doc:`bugprone-derived-method-shadowing-base-method <bugprone/derived-method-shadowing-base-method>`,
9495
:doc:`bugprone-dynamic-static-initializers <bugprone/dynamic-static-initializers>`,
9596
:doc:`bugprone-easily-swappable-parameters <bugprone/easily-swappable-parameters>`,
9697
:doc:`bugprone-empty-catch <bugprone/empty-catch>`,
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// RUN: %check_clang_tidy %s bugprone-derived-method-shadowing-base-method %t
2+
3+
class Base
4+
{
5+
void method();
6+
void methodWithArg(int I);
7+
8+
virtual Base* getThis() = 0;
9+
};
10+
11+
class A : public Base
12+
{
13+
public:
14+
void method();
15+
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'A::method' shadows method with the same name in class 'Base' [bugprone-derived-method-shadowing-base-method]
16+
// CHECK-MESSAGES: :5:5: note: previous definition of 'method' is here
17+
};
18+
19+
// only declaration should be checked
20+
void A::method()
21+
{
22+
}
23+
24+
class B
25+
{
26+
public:
27+
void method();
28+
};
29+
30+
class D: public Base
31+
{
32+
33+
};
34+
35+
// test indirect inheritance
36+
class E : public D
37+
{
38+
public:
39+
void method();
40+
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'E::method' shadows method with the same name in class 'Base' [bugprone-derived-method-shadowing-base-method]
41+
};
42+
43+
class H : public Base
44+
{
45+
public:
46+
Base* getThis() override;
47+
Base const* getThis() const;
48+
};
49+
50+
class I : public Base
51+
{
52+
public:
53+
// test with inline implementation
54+
void method()
55+
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'I::method' shadows method with the same name in class 'Base' [bugprone-derived-method-shadowing-base-method]
56+
{
57+
58+
}
59+
};
60+
61+
class J : public Base
62+
{
63+
public:
64+
Base* getThis() final;
65+
};
66+
67+
template<typename T>
68+
class TemplateBase
69+
{
70+
public:
71+
virtual void size() const = 0;
72+
};
73+
74+
template<typename T>
75+
class K : public TemplateBase<T>
76+
{
77+
public:
78+
void size() const final;
79+
};
80+
81+
class L : public Base
82+
{
83+
public:
84+
// not same signature (take const ref) but still ambiguous
85+
void methodWithArg(int const& I);
86+
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'L::methodWithArg' shadows method with the same name in class 'Base' [bugprone-derived-method-shadowing-base-method]
87+
88+
void methodWithArg(int const I);
89+
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'L::methodWithArg' shadows method with the same name in class 'Base' [bugprone-derived-method-shadowing-base-method]
90+
91+
void methodWithArg(int *I);
92+
void methodWithArg(int const* I);
93+
};
94+
95+
class M : public Base
96+
{
97+
public:
98+
static void method();
99+
};
100+
101+
class N : public Base
102+
{
103+
public:
104+
template<typename T>
105+
void methodWithArg(T I);
106+
// TODO: Templates are not handled yet
107+
template<> void methodWithArg<int>(int I);
108+
};
109+
110+
namespace std{
111+
struct thread{
112+
void join();
113+
};
114+
}
115+
116+
struct O: public std::thread{
117+
void join();
118+
};
119+
120+
struct P: public std::thread, Base{
121+
void join();
122+
void method();
123+
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'P::method' shadows method with the same name in class 'Base' [bugprone-derived-method-shadowing-base-method]
124+
};
125+
126+
class Q : public Base
127+
{
128+
public:
129+
typedef int MyInt;
130+
// not same signature (take const ref) but still ambiguous
131+
void methodWithArg(MyInt const& I);
132+
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'Q::methodWithArg' shadows method with the same name in class 'Base' [bugprone-derived-method-shadowing-base-method]
133+
134+
void methodWithArg(MyInt const I);
135+
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'Q::methodWithArg' shadows method with the same name in class 'Base' [bugprone-derived-method-shadowing-base-method]
136+
137+
void methodWithArg(MyInt *I);
138+
void methodWithArg(MyInt const* I);
139+
};

0 commit comments

Comments
 (0)