Skip to content

Commit f329e3e

Browse files
authored
[clang-tidy] Add bugprone-pointer-arithmetic-on-polymorphic-object check (#91951)
Finds pointer arithmetic on classes that declare a virtual function. This check corresponds to the SEI Cert rule [CTR56-CPP: Do not use pointer arithmetic on polymorphic objects](https://wiki.sei.cmu.edu/confluence/display/cplusplus/CTR56-CPP.+Do+not+use+pointer+arithmetic+on+polymorphic+objects). ```cpp struct Base { virtual void ~Base(); }; struct Derived : public Base {}; void foo(Base *b) { b += 1; // passing `Derived` to `foo()` results in UB } ``` [Results on open-source projects](https://codechecker-demo.eastus.cloudapp.azure.com/Default/runs?run=Discookie-ctr56-with-classnames). Most of the Qtbase reports are from having a `virtual override` declaration, and the LLVM reports are true positives, as far as I can tell.
1 parent d43ec97 commit f329e3e

11 files changed

+501
-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
@@ -51,6 +51,7 @@
5151
#include "NotNullTerminatedResultCheck.h"
5252
#include "OptionalValueConversionCheck.h"
5353
#include "ParentVirtualCallCheck.h"
54+
#include "PointerArithmeticOnPolymorphicObjectCheck.h"
5455
#include "PosixReturnCheck.h"
5556
#include "RedundantBranchConditionCheck.h"
5657
#include "ReservedIdentifierCheck.h"
@@ -171,6 +172,8 @@ class BugproneModule : public ClangTidyModule {
171172
"bugprone-multiple-statement-macro");
172173
CheckFactories.registerCheck<OptionalValueConversionCheck>(
173174
"bugprone-optional-value-conversion");
175+
CheckFactories.registerCheck<PointerArithmeticOnPolymorphicObjectCheck>(
176+
"bugprone-pointer-arithmetic-on-polymorphic-object");
174177
CheckFactories.registerCheck<RedundantBranchConditionCheck>(
175178
"bugprone-redundant-branch-condition");
176179
CheckFactories.registerCheck<cppcoreguidelines::NarrowingConversionsCheck>(

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ add_clang_library(clangTidyBugproneModule
4848
NotNullTerminatedResultCheck.cpp
4949
OptionalValueConversionCheck.cpp
5050
ParentVirtualCallCheck.cpp
51+
PointerArithmeticOnPolymorphicObjectCheck.cpp
5152
PosixReturnCheck.cpp
5253
RedundantBranchConditionCheck.cpp
5354
ReservedIdentifierCheck.cpp
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//===--- PointerArithmeticOnPolymorphicObjectCheck.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 "PointerArithmeticOnPolymorphicObjectCheck.h"
10+
#include "clang/AST/ASTContext.h"
11+
#include "clang/ASTMatchers/ASTMatchFinder.h"
12+
13+
using namespace clang::ast_matchers;
14+
15+
namespace clang::tidy::bugprone {
16+
17+
namespace {
18+
AST_MATCHER(CXXRecordDecl, isAbstract) { return Node.isAbstract(); }
19+
AST_MATCHER(CXXRecordDecl, isPolymorphic) { return Node.isPolymorphic(); }
20+
} // namespace
21+
22+
PointerArithmeticOnPolymorphicObjectCheck::
23+
PointerArithmeticOnPolymorphicObjectCheck(StringRef Name,
24+
ClangTidyContext *Context)
25+
: ClangTidyCheck(Name, Context),
26+
IgnoreInheritedVirtualFunctions(
27+
Options.get("IgnoreInheritedVirtualFunctions", false)) {}
28+
29+
void PointerArithmeticOnPolymorphicObjectCheck::storeOptions(
30+
ClangTidyOptions::OptionMap &Opts) {
31+
Options.store(Opts, "IgnoreInheritedVirtualFunctions",
32+
IgnoreInheritedVirtualFunctions);
33+
}
34+
35+
void PointerArithmeticOnPolymorphicObjectCheck::registerMatchers(
36+
MatchFinder *Finder) {
37+
const auto PolymorphicPointerExpr =
38+
expr(hasType(hasCanonicalType(pointerType(pointee(hasCanonicalType(
39+
hasDeclaration(cxxRecordDecl(unless(isFinal()), isPolymorphic())
40+
.bind("pointee"))))))))
41+
.bind("pointer");
42+
43+
const auto PointerExprWithVirtualMethod =
44+
expr(hasType(hasCanonicalType(
45+
pointerType(pointee(hasCanonicalType(hasDeclaration(
46+
cxxRecordDecl(
47+
unless(isFinal()),
48+
anyOf(hasMethod(isVirtualAsWritten()), isAbstract()))
49+
.bind("pointee"))))))))
50+
.bind("pointer");
51+
52+
const auto SelectedPointerExpr = IgnoreInheritedVirtualFunctions
53+
? PointerExprWithVirtualMethod
54+
: PolymorphicPointerExpr;
55+
56+
const auto ArraySubscript = arraySubscriptExpr(hasBase(SelectedPointerExpr));
57+
58+
const auto BinaryOperators =
59+
binaryOperator(hasAnyOperatorName("+", "-", "+=", "-="),
60+
hasEitherOperand(SelectedPointerExpr));
61+
62+
const auto UnaryOperators = unaryOperator(
63+
hasAnyOperatorName("++", "--"), hasUnaryOperand(SelectedPointerExpr));
64+
65+
Finder->addMatcher(ArraySubscript, this);
66+
Finder->addMatcher(BinaryOperators, this);
67+
Finder->addMatcher(UnaryOperators, this);
68+
}
69+
70+
void PointerArithmeticOnPolymorphicObjectCheck::check(
71+
const MatchFinder::MatchResult &Result) {
72+
const auto *PointerExpr = Result.Nodes.getNodeAs<Expr>("pointer");
73+
const auto *PointeeDecl = Result.Nodes.getNodeAs<CXXRecordDecl>("pointee");
74+
75+
diag(PointerExpr->getBeginLoc(),
76+
"pointer arithmetic on polymorphic object of type %0 can result in "
77+
"undefined behavior if the dynamic type differs from the pointer type")
78+
<< PointeeDecl << PointerExpr->getSourceRange();
79+
}
80+
81+
} // namespace clang::tidy::bugprone
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//===--- PointerArithmeticOnPolymorphicObjectCheck.h ------------*- 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_POINTERARITHMETICONPOLYMORPHICOBJECTCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_POINTERARITHMETICONPOLYMORPHICOBJECTCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
14+
namespace clang::tidy::bugprone {
15+
16+
/// Finds pointer arithmetic performed on classes that contain a
17+
/// virtual function.
18+
///
19+
/// For the user-facing documentation see:
20+
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/pointer-arithmetic-on-polymorphic-object.html
21+
class PointerArithmeticOnPolymorphicObjectCheck : public ClangTidyCheck {
22+
public:
23+
PointerArithmeticOnPolymorphicObjectCheck(StringRef Name,
24+
ClangTidyContext *Context);
25+
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
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.CPlusPlus;
30+
}
31+
std::optional<TraversalKind> getCheckTraversalKind() const override {
32+
return TK_IgnoreUnlessSpelledInSource;
33+
}
34+
35+
private:
36+
const bool IgnoreInheritedVirtualFunctions;
37+
};
38+
39+
} // namespace clang::tidy::bugprone
40+
41+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_POINTERARITHMETICONPOLYMORPHICOBJECTCHECK_H

clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "../ClangTidyModule.h"
1111
#include "../ClangTidyModuleRegistry.h"
1212
#include "../bugprone/BadSignalToKillThreadCheck.h"
13+
#include "../bugprone/PointerArithmeticOnPolymorphicObjectCheck.h"
1314
#include "../bugprone/ReservedIdentifierCheck.h"
1415
#include "../bugprone/SignalHandlerCheck.h"
1516
#include "../bugprone/SignedCharMisuseCheck.h"
@@ -238,6 +239,10 @@ class CERTModule : public ClangTidyModule {
238239
// CON
239240
CheckFactories.registerCheck<bugprone::SpuriouslyWakeUpFunctionsCheck>(
240241
"cert-con54-cpp");
242+
// CTR
243+
CheckFactories
244+
.registerCheck<bugprone::PointerArithmeticOnPolymorphicObjectCheck>(
245+
"cert-ctr56-cpp");
241246
// DCL
242247
CheckFactories.registerCheck<VariadicFunctionDefCheck>("cert-dcl50-cpp");
243248
CheckFactories.registerCheck<bugprone::ReservedIdentifierCheck>(

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@ New checks
137137
Detects error-prone Curiously Recurring Template Pattern usage, when the CRTP
138138
can be constructed outside itself and the derived class.
139139

140+
- New :doc:`bugprone-pointer-arithmetic-on-polymorphic-object
141+
<clang-tidy/checks/bugprone/pointer-arithmetic-on-polymorphic-object>` check.
142+
143+
Finds pointer arithmetic performed on classes that contain a virtual function.
144+
140145
- New :doc:`bugprone-return-const-ref-from-parameter
141146
<clang-tidy/checks/bugprone/return-const-ref-from-parameter>` check.
142147

@@ -199,6 +204,11 @@ New checks
199204
New check aliases
200205
^^^^^^^^^^^^^^^^^
201206

207+
- New alias :doc:`cert-ctr56-cpp <clang-tidy/checks/cert/ctr56-cpp>` to
208+
:doc:`bugprone-pointer-arithmetic-on-polymorphic-object
209+
<clang-tidy/checks/bugprone/pointer-arithmetic-on-polymorphic-object>`
210+
was added.
211+
202212
- New alias :doc:`cert-int09-c <clang-tidy/checks/cert/int09-c>` to
203213
:doc:`readability-enum-initial-value <clang-tidy/checks/readability/enum-initial-value>`
204214
was added.
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
.. title:: clang-tidy - bugprone-pointer-arithmetic-on-polymorphic-object
2+
3+
bugprone-pointer-arithmetic-on-polymorphic-object
4+
=================================================
5+
6+
Finds pointer arithmetic performed on classes that contain a virtual function.
7+
8+
Pointer arithmetic on polymorphic objects where the pointer's static type is
9+
different from its dynamic type is undefined behavior, as the two types could
10+
have different sizes, and thus the vtable pointer could point to an
11+
invalid address.
12+
13+
Finding pointers where the static type contains a virtual member function is a
14+
good heuristic, as the pointer is likely to point to a different,
15+
derived object.
16+
17+
Example:
18+
19+
.. code-block:: c++
20+
21+
struct Base {
22+
virtual void ~Base();
23+
};
24+
25+
struct Derived : public Base {};
26+
27+
void foo() {
28+
Base *b = new Derived[10];
29+
30+
b += 1;
31+
// warning: pointer arithmetic on class that declares a virtual function can
32+
// result in undefined behavior if the dynamic type differs from the
33+
// pointer type
34+
35+
delete[] static_cast<Derived*>(b);
36+
}
37+
38+
Options
39+
-------
40+
41+
.. option:: IgnoreInheritedVirtualFunctions
42+
43+
When `true`, objects that only inherit a virtual function are not checked.
44+
Classes that do not declare a new virtual function are excluded
45+
by default, as they make up the majority of false positives.
46+
Default: `false`.
47+
48+
.. code-block:: c++
49+
50+
void bar() {
51+
Base *b = new Base[10];
52+
b += 1; // warning, as Base declares a virtual destructor
53+
54+
delete[] b;
55+
56+
Derived *d = new Derived[10]; // Derived overrides the destructor, and
57+
// declares no other virtual functions
58+
d += 1; // warning only if IgnoreVirtualDeclarationsOnly is set to false
59+
60+
delete[] d;
61+
}
62+
63+
References
64+
----------
65+
66+
This check corresponds to the SEI Cert rule
67+
`CTR56-CPP. Do not use pointer arithmetic on polymorphic objects
68+
<https://wiki.sei.cmu.edu/confluence/display/cplusplus/CTR56-CPP.+Do+not+use+pointer+arithmetic+on+polymorphic+objects>`_.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.. title:: clang-tidy - cert-ctr56-cpp
2+
.. meta::
3+
:http-equiv=refresh: 5;URL=../bugprone/pointer-arithmetic-on-polymorphic-object.html
4+
5+
cert-ctr56-cpp
6+
==============
7+
8+
The `cert-ctr56-cpp` check is an alias, please see
9+
:doc:`bugprone-pointer-arithmetic-on-polymorphic-object
10+
<../bugprone/pointer-arithmetic-on-polymorphic-object>` for more information.

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ Clang-Tidy Checks
157157
:doc:`bugprone-unused-raii <bugprone/unused-raii>`, "Yes"
158158
:doc:`bugprone-unused-return-value <bugprone/unused-return-value>`,
159159
:doc:`bugprone-use-after-move <bugprone/use-after-move>`,
160+
:doc:`bugprone-pointer-arithmetic-on-polymorphic-object <bugprone/pointer-arithmetic-on-polymorphic-object>`,
160161
:doc:`bugprone-virtual-near-miss <bugprone/virtual-near-miss>`, "Yes"
161162
:doc:`cert-dcl50-cpp <cert/dcl50-cpp>`,
162163
:doc:`cert-dcl58-cpp <cert/dcl58-cpp>`,

0 commit comments

Comments
 (0)