Skip to content

Commit 8ebc35f

Browse files
[clang-tidy] Create bugprone-incorrect-enable-shared-from-this check (llvm#102299)
This checks that classes/structs inheriting from ``std::enable_shared_from_this`` does so with public inheritance, so it prevents crashes due to ``std::make_shared`` and ``shared_from_this()`` getting called when the internal weak pointer was not initialized (e.g. due to private inheritance).
1 parent fdfe7e7 commit 8ebc35f

File tree

8 files changed

+330
-0
lines changed

8 files changed

+330
-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
@@ -33,6 +33,7 @@
3333
#include "InaccurateEraseCheck.h"
3434
#include "IncDecInConditionsCheck.h"
3535
#include "IncorrectEnableIfCheck.h"
36+
#include "IncorrectEnableSharedFromThisCheck.h"
3637
#include "IncorrectRoundingsCheck.h"
3738
#include "InfiniteLoopCheck.h"
3839
#include "IntegerDivisionCheck.h"
@@ -144,6 +145,8 @@ class BugproneModule : public ClangTidyModule {
144145
"bugprone-inaccurate-erase");
145146
CheckFactories.registerCheck<IncorrectEnableIfCheck>(
146147
"bugprone-incorrect-enable-if");
148+
CheckFactories.registerCheck<IncorrectEnableSharedFromThisCheck>(
149+
"bugprone-incorrect-enable-shared-from-this");
147150
CheckFactories.registerCheck<ReturnConstRefFromParameterCheck>(
148151
"bugprone-return-const-ref-from-parameter");
149152
CheckFactories.registerCheck<SwitchMissingDefaultCaseCheck>(

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ add_clang_library(clangTidyBugproneModule STATIC
2727
ForwardingReferenceOverloadCheck.cpp
2828
ImplicitWideningOfMultiplicationResultCheck.cpp
2929
InaccurateEraseCheck.cpp
30+
IncorrectEnableIfCheck.cpp
31+
IncorrectEnableSharedFromThisCheck.cpp
32+
ReturnConstRefFromParameterCheck.cpp
33+
SuspiciousStringviewDataUsageCheck.cpp
34+
SwitchMissingDefaultCaseCheck.cpp
3035
IncDecInConditionsCheck.cpp
3136
IncorrectEnableIfCheck.cpp
3237
IncorrectRoundingsCheck.cpp
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//===--- IncorrectEnableSharedFromThisCheck.cpp - clang-tidy --------------===//
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 "IncorrectEnableSharedFromThisCheck.h"
10+
#include "clang/AST/ASTContext.h"
11+
#include "clang/AST/DeclCXX.h"
12+
#include "clang/ASTMatchers/ASTMatchFinder.h"
13+
14+
using namespace clang::ast_matchers;
15+
16+
namespace clang::tidy::bugprone {
17+
18+
void IncorrectEnableSharedFromThisCheck::registerMatchers(MatchFinder *Finder) {
19+
const auto EnableSharedFromThis =
20+
cxxRecordDecl(hasName("enable_shared_from_this"), isInStdNamespace());
21+
const auto QType = hasCanonicalType(hasDeclaration(
22+
cxxRecordDecl(
23+
anyOf(EnableSharedFromThis.bind("enable_rec"),
24+
cxxRecordDecl(hasAnyBase(cxxBaseSpecifier(
25+
isPublic(), hasType(hasCanonicalType(
26+
hasDeclaration(EnableSharedFromThis))))))))
27+
.bind("base_rec")));
28+
Finder->addMatcher(
29+
cxxRecordDecl(
30+
unless(isExpansionInSystemHeader()),
31+
hasDirectBase(cxxBaseSpecifier(unless(isPublic()), hasType(QType))
32+
.bind("base")))
33+
.bind("derived"),
34+
this);
35+
}
36+
37+
void IncorrectEnableSharedFromThisCheck::check(
38+
const MatchFinder::MatchResult &Result) {
39+
const auto *BaseSpec = Result.Nodes.getNodeAs<CXXBaseSpecifier>("base");
40+
const auto *Base = Result.Nodes.getNodeAs<CXXRecordDecl>("base_rec");
41+
const auto *Derived = Result.Nodes.getNodeAs<CXXRecordDecl>("derived");
42+
const bool IsEnableSharedFromThisDirectBase =
43+
Result.Nodes.getNodeAs<CXXRecordDecl>("enable_rec") == Base;
44+
const bool HasWrittenAccessSpecifier =
45+
BaseSpec->getAccessSpecifierAsWritten() != AS_none;
46+
const auto ReplacementRange = CharSourceRange(
47+
SourceRange(BaseSpec->getBeginLoc()), HasWrittenAccessSpecifier);
48+
const llvm::StringRef Replacement =
49+
HasWrittenAccessSpecifier ? "public" : "public ";
50+
const FixItHint Hint =
51+
IsEnableSharedFromThisDirectBase
52+
? FixItHint::CreateReplacement(ReplacementRange, Replacement)
53+
: FixItHint();
54+
diag(Derived->getLocation(),
55+
"%2 is not publicly inheriting from "
56+
"%select{%1 which inherits from |}0'std::enable_shared_"
57+
"from_this', "
58+
"which will cause unintended behaviour "
59+
"when using 'shared_from_this'; make the inheritance "
60+
"public",
61+
DiagnosticIDs::Warning)
62+
<< IsEnableSharedFromThisDirectBase << Base << Derived << Hint;
63+
}
64+
65+
} // namespace clang::tidy::bugprone
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//===--- IncorrectEnableSharedFromThisCheck.h - clang-tidy ------*- C++ -*-===//
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_INCORRECTENABLESHAREDFROMTHISCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_INCORRECTENABLESHAREDFROMTHISCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
14+
namespace clang::tidy::bugprone {
15+
16+
/// Detect classes or structs that do not publicly inherit from
17+
/// ``std::enable_shared_from_this``, because unintended behavior will
18+
/// otherwise occur when calling ``shared_from_this``.
19+
///
20+
/// For the user-facing documentation see:
21+
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/incorrect-enable-shared-from-this.html
22+
class IncorrectEnableSharedFromThisCheck : public ClangTidyCheck {
23+
public:
24+
IncorrectEnableSharedFromThisCheck(StringRef Name, ClangTidyContext *Context)
25+
: ClangTidyCheck(Name, Context) {}
26+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
27+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
28+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
29+
return LangOpts.CPlusPlus11;
30+
}
31+
};
32+
33+
} // namespace clang::tidy::bugprone
34+
35+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_INCORRECTENABLESHAREDFROMTHISCHECK_H

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,13 @@ New checks
145145
Warns about code that tries to cast between pointers by means of
146146
``std::bit_cast`` or ``memcpy``.
147147

148+
- New :doc:`bugprone-incorrect-enable-shared-from-this
149+
<clang-tidy/checks/bugprone/incorrect-enable-shared-from-this>` check.
150+
151+
Detect classes or structs that do not publicly inherit from
152+
``std::enable_shared_from_this``, because unintended behavior will
153+
otherwise occur when calling ``shared_from_this``.
154+
148155
- New :doc:`bugprone-nondeterministic-pointer-iteration-order
149156
<clang-tidy/checks/bugprone/nondeterministic-pointer-iteration-order>`
150157
check.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
.. title:: clang-tidy - bugprone-incorrect-enable-shared-from-this
2+
3+
bugprone-incorrect-enable-shared-from-this
4+
==========================================
5+
6+
Detect classes or structs that do not publicly inherit from
7+
``std::enable_shared_from_this``, because unintended behavior will
8+
otherwise occur when calling ``shared_from_this``.
9+
10+
Consider the following code:
11+
12+
.. code-block:: c++
13+
14+
#include <memory>
15+
16+
// private inheritance
17+
class BadExample : std::enable_shared_from_this<BadExample> {
18+
19+
// ``shared_from_this``` unintended behaviour
20+
// `libstdc++` implementation returns uninitialized ``weak_ptr``
21+
public:
22+
BadExample* foo() { return shared_from_this().get(); }
23+
void bar() { return; }
24+
};
25+
26+
void using_not_public() {
27+
auto bad_example = std::make_shared<BadExample>();
28+
auto* b_ex = bad_example->foo();
29+
b_ex->bar();
30+
}
31+
32+
Using `libstdc++` implementation, ``shared_from_this`` will throw
33+
``std::bad_weak_ptr``. When ``using_not_public()`` is called, this code will
34+
crash without exception handling.

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ Clang-Tidy Checks
101101
:doc:`bugprone-inaccurate-erase <bugprone/inaccurate-erase>`, "Yes"
102102
:doc:`bugprone-inc-dec-in-conditions <bugprone/inc-dec-in-conditions>`,
103103
:doc:`bugprone-incorrect-enable-if <bugprone/incorrect-enable-if>`, "Yes"
104+
:doc:`bugprone-incorrect-enable-shared-from-this <bugprone/incorrect-enable-shared-from-this>`, "Yes"
104105
:doc:`bugprone-incorrect-roundings <bugprone/incorrect-roundings>`,
105106
:doc:`bugprone-infinite-loop <bugprone/infinite-loop>`,
106107
:doc:`bugprone-integer-division <bugprone/integer-division>`,

0 commit comments

Comments
 (0)