-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[clang-tidy] Add bugprone-pointer-arithmetic-on-polymorphic-object
check
#91951
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
Conversation
Finds pointer arithmetic on classes that declare a virtual function.
Useful for reports within template instantiations.
@llvm/pr-subscribers-clang-tidy Author: Discookie (Discookie) ChangesFinds 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. 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. Most of the Qtbase reports are from having a Full diff: https://github.com/llvm/llvm-project/pull/91951.diff 7 Files Affected:
diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
index 1b92d2e60cc17..813f6720074ae 100644
--- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -91,6 +91,7 @@
#include "UnusedRaiiCheck.h"
#include "UnusedReturnValueCheck.h"
#include "UseAfterMoveCheck.h"
+#include "VirtualArithmeticCheck.h"
#include "VirtualNearMissCheck.h"
namespace clang::tidy {
@@ -254,6 +255,8 @@ class BugproneModule : public ClangTidyModule {
CheckFactories.registerCheck<UnusedReturnValueCheck>(
"bugprone-unused-return-value");
CheckFactories.registerCheck<UseAfterMoveCheck>("bugprone-use-after-move");
+ CheckFactories.registerCheck<VirtualArithmeticCheck>(
+ "bugprone-virtual-arithmetic");
CheckFactories.registerCheck<VirtualNearMissCheck>(
"bugprone-virtual-near-miss");
}
diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
index 2d303191f8865..ec1f3231e7990 100644
--- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
@@ -87,6 +87,7 @@ add_clang_library(clangTidyBugproneModule
UnusedRaiiCheck.cpp
UnusedReturnValueCheck.cpp
UseAfterMoveCheck.cpp
+ VirtualArithmeticCheck.cpp
VirtualNearMissCheck.cpp
LINK_LIBS
diff --git a/clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.cpp
new file mode 100644
index 0000000000000..dec43ae9bd8ca
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.cpp
@@ -0,0 +1,49 @@
+//===--- VirtualArithmeticCheck.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 "VirtualArithmeticCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::bugprone {
+
+void VirtualArithmeticCheck::registerMatchers(MatchFinder *Finder) {
+ const auto PointerExprWithVirtualMethod =
+ expr(hasType(pointerType(pointee(hasDeclaration(
+ cxxRecordDecl(hasMethod(isVirtualAsWritten())))))))
+ .bind("pointer");
+
+ const auto ArraySubscript =
+ arraySubscriptExpr(hasBase(PointerExprWithVirtualMethod));
+
+ const auto BinaryOperators =
+ binaryOperator(hasAnyOperatorName("+", "-", "+=", "-="),
+ hasEitherOperand(PointerExprWithVirtualMethod));
+
+ const auto UnaryOperators =
+ unaryOperator(hasAnyOperatorName("++", "--"),
+ hasUnaryOperand(PointerExprWithVirtualMethod));
+
+ Finder->addMatcher(
+ expr(anyOf(ArraySubscript, BinaryOperators, UnaryOperators)), this);
+}
+
+void VirtualArithmeticCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *PointerExpr = Result.Nodes.getNodeAs<Expr>("pointer");
+ const CXXRecordDecl *PointeeType =
+ PointerExpr->getType()->getPointeeType()->getAsCXXRecordDecl();
+
+ diag(PointerExpr->getBeginLoc(),
+ "pointer arithmetic on class '%0' that declares a virtual function, "
+ "undefined behavior if the pointee is a different class")
+ << PointeeType->getName();
+}
+
+} // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.h b/clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.h
new file mode 100644
index 0000000000000..6a5f86a391747
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.h
@@ -0,0 +1,30 @@
+//===--- VirtualArithmeticCheck.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_VIRTUAL_ARITHMETIC_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_VIRTUAL_ARITHMETIC_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::bugprone {
+
+/// Finds pointer arithmetic on classes that declare a virtual function.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/virtual-arithmetic.html
+class VirtualArithmeticCheck : public ClangTidyCheck {
+public:
+ VirtualArithmeticCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context) {}
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+};
+
+} // namespace clang::tidy::bugprone
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_VIRTUAL_ARITHMETIC_H
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/virtual-arithmetic.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/virtual-arithmetic.rst
new file mode 100644
index 0000000000000..69d43e13392be
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/virtual-arithmetic.rst
@@ -0,0 +1,50 @@
+.. title:: clang-tidy - bugprone-virtual-arithmetic
+
+bugprone-virtual-arithmetic
+===========================
+
+Warn if pointer arithmetic is performed on a class that declares a
+virtual function.
+
+Pointer arithmetic on polymorphic objects where the pointer's static type is
+different from its dynamic type is undefined behavior.
+Finding pointers where the static type contains a virtual member function is a
+good heuristic, as the pointer is likely to point to a different, derived class.
+
+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>`_.
+
+Example:
+
+.. code-block:: c++
+
+ struct Base {
+ virtual void ~Base();
+ };
+
+ struct Derived : public Base {};
+
+ void foo() {
+ Base *b = new Derived[10];
+
+ b += 1;
+ // warning: pointer arithmetic on class that declares a virtual function, undefined behavior if the pointee is a different class
+
+ delete[] static_cast<Derived*>(b);
+ }
+
+Classes that do not declare a new virtual function are excluded,
+as they make up the majority of false positives.
+
+.. code-block:: c++
+
+ void bar() {
+ Base *b = new Base[10];
+ b += 1; // warning, as Base has a virtual destructor
+
+ delete[] b;
+
+ Derived *d = new Derived[10];
+ d += 1; // no warning, as Derived overrides the destructor
+
+ delete[] d;
+ }
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 5cdaf9e35b6ac..ba1e5cafa6e47 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -157,6 +157,7 @@ Clang-Tidy Checks
:doc:`bugprone-unused-raii <bugprone/unused-raii>`, "Yes"
:doc:`bugprone-unused-return-value <bugprone/unused-return-value>`,
:doc:`bugprone-use-after-move <bugprone/use-after-move>`,
+ :doc:`bugprone-virtual-arithmetic <bugprone/virtual-arithmetic>`,
:doc:`bugprone-virtual-near-miss <bugprone/virtual-near-miss>`, "Yes"
:doc:`cert-dcl50-cpp <cert/dcl50-cpp>`,
:doc:`cert-dcl58-cpp <cert/dcl58-cpp>`,
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/virtual-arithmetic.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/virtual-arithmetic.cpp
new file mode 100644
index 0000000000000..d89e75186c60f
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/virtual-arithmetic.cpp
@@ -0,0 +1,59 @@
+// RUN: %check_clang_tidy %s bugprone-virtual-arithmetic %t
+
+class Base {
+public:
+ virtual ~Base() {}
+};
+
+class Derived : public Base {};
+
+void operators() {
+ Base *b = new Derived[10];
+
+ b += 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on class 'Base' that declares a virtual function, undefined behavior if the pointee is a different class [bugprone-virtual-arithmetic]
+
+ b = b + 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: pointer arithmetic on class 'Base' that declares a virtual function, undefined behavior if the pointee is a different class [bugprone-virtual-arithmetic]
+
+ b++;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on class 'Base' that declares a virtual function, undefined behavior if the pointee is a different class [bugprone-virtual-arithmetic]
+
+ b[1];
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on class 'Base' that declares a virtual function, undefined behavior if the pointee is a different class [bugprone-virtual-arithmetic]
+
+ delete[] static_cast<Derived*>(b);
+}
+
+void subclassWarnings() {
+ Base *b = new Base[10];
+
+ // False positive that's impossible to distinguish without
+ // path-sensitive analysis, but the code is bug-prone regardless.
+ b += 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on class 'Base' that declares a virtual function
+
+ delete[] b;
+
+ // Common false positive is a class that overrides all parent functions.
+ Derived *d = new Derived[10];
+
+ d += 1;
+ // no-warning
+
+ delete[] d;
+}
+
+template <typename T>
+void templateWarning(T *t) {
+ // FIXME: Show the location of the template instantiation in diagnostic.
+ t += 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on class 'Base' that declares a virtual function
+}
+
+void functionArgument(Base *b) {
+ b += 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on class 'Base' that declares a virtual function
+
+ templateWarning(b);
+}
\ No newline at end of file
|
@llvm/pr-subscribers-clang-tools-extra Author: Discookie (Discookie) ChangesFinds 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. 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. Most of the Qtbase reports are from having a Full diff: https://github.com/llvm/llvm-project/pull/91951.diff 7 Files Affected:
diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
index 1b92d2e60cc17..813f6720074ae 100644
--- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -91,6 +91,7 @@
#include "UnusedRaiiCheck.h"
#include "UnusedReturnValueCheck.h"
#include "UseAfterMoveCheck.h"
+#include "VirtualArithmeticCheck.h"
#include "VirtualNearMissCheck.h"
namespace clang::tidy {
@@ -254,6 +255,8 @@ class BugproneModule : public ClangTidyModule {
CheckFactories.registerCheck<UnusedReturnValueCheck>(
"bugprone-unused-return-value");
CheckFactories.registerCheck<UseAfterMoveCheck>("bugprone-use-after-move");
+ CheckFactories.registerCheck<VirtualArithmeticCheck>(
+ "bugprone-virtual-arithmetic");
CheckFactories.registerCheck<VirtualNearMissCheck>(
"bugprone-virtual-near-miss");
}
diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
index 2d303191f8865..ec1f3231e7990 100644
--- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
@@ -87,6 +87,7 @@ add_clang_library(clangTidyBugproneModule
UnusedRaiiCheck.cpp
UnusedReturnValueCheck.cpp
UseAfterMoveCheck.cpp
+ VirtualArithmeticCheck.cpp
VirtualNearMissCheck.cpp
LINK_LIBS
diff --git a/clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.cpp
new file mode 100644
index 0000000000000..dec43ae9bd8ca
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.cpp
@@ -0,0 +1,49 @@
+//===--- VirtualArithmeticCheck.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 "VirtualArithmeticCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::bugprone {
+
+void VirtualArithmeticCheck::registerMatchers(MatchFinder *Finder) {
+ const auto PointerExprWithVirtualMethod =
+ expr(hasType(pointerType(pointee(hasDeclaration(
+ cxxRecordDecl(hasMethod(isVirtualAsWritten())))))))
+ .bind("pointer");
+
+ const auto ArraySubscript =
+ arraySubscriptExpr(hasBase(PointerExprWithVirtualMethod));
+
+ const auto BinaryOperators =
+ binaryOperator(hasAnyOperatorName("+", "-", "+=", "-="),
+ hasEitherOperand(PointerExprWithVirtualMethod));
+
+ const auto UnaryOperators =
+ unaryOperator(hasAnyOperatorName("++", "--"),
+ hasUnaryOperand(PointerExprWithVirtualMethod));
+
+ Finder->addMatcher(
+ expr(anyOf(ArraySubscript, BinaryOperators, UnaryOperators)), this);
+}
+
+void VirtualArithmeticCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *PointerExpr = Result.Nodes.getNodeAs<Expr>("pointer");
+ const CXXRecordDecl *PointeeType =
+ PointerExpr->getType()->getPointeeType()->getAsCXXRecordDecl();
+
+ diag(PointerExpr->getBeginLoc(),
+ "pointer arithmetic on class '%0' that declares a virtual function, "
+ "undefined behavior if the pointee is a different class")
+ << PointeeType->getName();
+}
+
+} // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.h b/clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.h
new file mode 100644
index 0000000000000..6a5f86a391747
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.h
@@ -0,0 +1,30 @@
+//===--- VirtualArithmeticCheck.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_VIRTUAL_ARITHMETIC_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_VIRTUAL_ARITHMETIC_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::bugprone {
+
+/// Finds pointer arithmetic on classes that declare a virtual function.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/virtual-arithmetic.html
+class VirtualArithmeticCheck : public ClangTidyCheck {
+public:
+ VirtualArithmeticCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context) {}
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+};
+
+} // namespace clang::tidy::bugprone
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_VIRTUAL_ARITHMETIC_H
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/virtual-arithmetic.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/virtual-arithmetic.rst
new file mode 100644
index 0000000000000..69d43e13392be
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/virtual-arithmetic.rst
@@ -0,0 +1,50 @@
+.. title:: clang-tidy - bugprone-virtual-arithmetic
+
+bugprone-virtual-arithmetic
+===========================
+
+Warn if pointer arithmetic is performed on a class that declares a
+virtual function.
+
+Pointer arithmetic on polymorphic objects where the pointer's static type is
+different from its dynamic type is undefined behavior.
+Finding pointers where the static type contains a virtual member function is a
+good heuristic, as the pointer is likely to point to a different, derived class.
+
+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>`_.
+
+Example:
+
+.. code-block:: c++
+
+ struct Base {
+ virtual void ~Base();
+ };
+
+ struct Derived : public Base {};
+
+ void foo() {
+ Base *b = new Derived[10];
+
+ b += 1;
+ // warning: pointer arithmetic on class that declares a virtual function, undefined behavior if the pointee is a different class
+
+ delete[] static_cast<Derived*>(b);
+ }
+
+Classes that do not declare a new virtual function are excluded,
+as they make up the majority of false positives.
+
+.. code-block:: c++
+
+ void bar() {
+ Base *b = new Base[10];
+ b += 1; // warning, as Base has a virtual destructor
+
+ delete[] b;
+
+ Derived *d = new Derived[10];
+ d += 1; // no warning, as Derived overrides the destructor
+
+ delete[] d;
+ }
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 5cdaf9e35b6ac..ba1e5cafa6e47 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -157,6 +157,7 @@ Clang-Tidy Checks
:doc:`bugprone-unused-raii <bugprone/unused-raii>`, "Yes"
:doc:`bugprone-unused-return-value <bugprone/unused-return-value>`,
:doc:`bugprone-use-after-move <bugprone/use-after-move>`,
+ :doc:`bugprone-virtual-arithmetic <bugprone/virtual-arithmetic>`,
:doc:`bugprone-virtual-near-miss <bugprone/virtual-near-miss>`, "Yes"
:doc:`cert-dcl50-cpp <cert/dcl50-cpp>`,
:doc:`cert-dcl58-cpp <cert/dcl58-cpp>`,
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/virtual-arithmetic.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/virtual-arithmetic.cpp
new file mode 100644
index 0000000000000..d89e75186c60f
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/virtual-arithmetic.cpp
@@ -0,0 +1,59 @@
+// RUN: %check_clang_tidy %s bugprone-virtual-arithmetic %t
+
+class Base {
+public:
+ virtual ~Base() {}
+};
+
+class Derived : public Base {};
+
+void operators() {
+ Base *b = new Derived[10];
+
+ b += 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on class 'Base' that declares a virtual function, undefined behavior if the pointee is a different class [bugprone-virtual-arithmetic]
+
+ b = b + 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: pointer arithmetic on class 'Base' that declares a virtual function, undefined behavior if the pointee is a different class [bugprone-virtual-arithmetic]
+
+ b++;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on class 'Base' that declares a virtual function, undefined behavior if the pointee is a different class [bugprone-virtual-arithmetic]
+
+ b[1];
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on class 'Base' that declares a virtual function, undefined behavior if the pointee is a different class [bugprone-virtual-arithmetic]
+
+ delete[] static_cast<Derived*>(b);
+}
+
+void subclassWarnings() {
+ Base *b = new Base[10];
+
+ // False positive that's impossible to distinguish without
+ // path-sensitive analysis, but the code is bug-prone regardless.
+ b += 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on class 'Base' that declares a virtual function
+
+ delete[] b;
+
+ // Common false positive is a class that overrides all parent functions.
+ Derived *d = new Derived[10];
+
+ d += 1;
+ // no-warning
+
+ delete[] d;
+}
+
+template <typename T>
+void templateWarning(T *t) {
+ // FIXME: Show the location of the template instantiation in diagnostic.
+ t += 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on class 'Base' that declares a virtual function
+}
+
+void functionArgument(Base *b) {
+ b += 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on class 'Base' that declares a virtual function
+
+ templateWarning(b);
+}
\ No newline at end of file
|
(Finding classes with virtual member fns declared is a good heuristic for finding polymorphic objects in general, as previously discussed in #82977 (comment).) |
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.
Please mention new check in Release Notes.
clang-tools-extra/docs/clang-tidy/checks/bugprone/virtual-arithmetic.rst
Outdated
Show resolved
Hide resolved
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.
Needs some small changes before landing. It's a good check to have, thanks.
diag(PointerExpr->getBeginLoc(), | ||
"pointer arithmetic on class '%0' that declares a virtual function, " | ||
"undefined behavior if the pointee is a different class") | ||
<< PointeeType->getName(); |
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.
- Please stream the
SourceRange
ofPointerExpr
into the diagnostic, so that it is fully underlined in the diagnostic/editor - I think the comma should be replaced with
(can) result in
, wdyt? - You don't have to escape the
%0
with'
when you passPointeeType
without thegetName
instead.
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.
In Tidy we can only use a single SourceLocation and not a range, right? I don't know of a way to feed ranges into it.
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.
The diag
member function only accepts a SourceLocation
as the first parameter, that is correct.
When you stream a SourceRange
into the diagnostic, that range will be underlined by the diagnostic engine (and in clangd). The same goes for attached fix-it hints, which highlight the ranges they change.
E.g.
125 | base += 1;
| ^
vs
125 | base += 1;
| ^~~~
when using
diag(PointerExpr->getBeginLoc(),
"pointer arithmetic on polymorphic class '%0', which can result in "
"undefined behavior if the pointee is a different class")
<< PointeeType->getName()
<< PointerExpr->getSourceRange(); // adding this
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.
Tidy supports source ranges like that!? I never knew, thank you! Added.
I don't think I've seen support for it in CodeChecker, that's why I was surprised. Will have to follow up on that.
clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/docs/clang-tidy/checks/bugprone/virtual-arithmetic.rst
Outdated
Show resolved
Hide resolved
clang-tools-extra/test/clang-tidy/checkers/bugprone/virtual-arithmetic.cpp
Outdated
Show resolved
Hide resolved
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.
Few nits.
My main concern is an check name, had to open PR to find out what it does.
maybe should be more a "bugprone-arithmetic-on-polymorphic-pointer" or something like that.
clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/test/clang-tidy/checkers/bugprone/virtual-arithmetic.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/docs/clang-tidy/checks/bugprone/virtual-arithmetic.rst
Outdated
Show resolved
Hide resolved
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on class 'Base' that declares a virtual function | ||
|
||
templateWarning(b); | ||
} |
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.
Add test for bugfree scenario:
void test()
{
Object table[10];
Object* ptr = table[5];
}
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.
Is this snippet meant to be Object &ref
? Array subscript can't return a pointer AFAIK.
Other than that the case is not too distinct from Derived d[10]; d += 1;
for which I have a test.
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.
Object* ptr = &table[5];
I'm not very happy about the name, there's no such thing as "virtual arithmetic". What about "bugprone-pointer-arithmetic-polymorphic-object"? I rather have a long descriptive name than a short ambiguous name. |
Otherwise since this is a problem also on non-polymorphic objects, maybe "bugprone-pointer-arithmetic-base-class"? |
bugprone-pointer-arithmetic-polymorphic-object or bugprone-pointer-arithmetic-on-polymorphic-object could be fine. Also check-alias should be added to cert. |
I realize it's probably very hard (if not impossible) to detect whether a class is a base class unless it has virtual functions. That's why the corresponding AUTOSAR rule bans only non-final classes. But that is a bit overkill and leads to some pain. |
clang-tools-extra/clang-tidy/bugprone/PointerArithmeticOnPolymorphicObjectCheck.h
Show resolved
Hide resolved
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.
Renamed the check as requested, and fixed what I reasonably could regarding the rest.
Added the check option MatchInheritedVirtualFunctions
to differentiate between inherited and newly-declared virtual functions. Inherited has many more false positives on the projects I tested.
diag(PointerExpr->getBeginLoc(), | ||
"pointer arithmetic on class '%0' that declares a virtual function, " | ||
"undefined behavior if the pointee is a different class") | ||
<< PointeeType->getName(); |
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.
In Tidy we can only use a single SourceLocation and not a range, right? I don't know of a way to feed ranges into it.
clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/test/clang-tidy/checkers/bugprone/virtual-arithmetic.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/docs/clang-tidy/checks/bugprone/virtual-arithmetic.rst
Outdated
Show resolved
Hide resolved
clang-tools-extra/clang-tidy/bugprone/PointerArithmeticOnPolymorphicObjectCheck.h
Show resolved
Hide resolved
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on class 'Base' that declares a virtual function | ||
|
||
templateWarning(b); | ||
} |
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.
Is this snippet meant to be Object &ref
? Array subscript can't return a pointer AFAIK.
Other than that the case is not too distinct from Derived d[10]; d += 1;
for which I have a test.
clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.cpp
Outdated
Show resolved
Hide resolved
bugprone-virtual-arithmetic
checkbugprone-pointer-arithmetic-on-polymorphic-object
check
clang-tools-extra/docs/clang-tidy/checks/bugprone/pointer-arithmetic-on-polymorphic-object.rst
Outdated
Show resolved
Hide resolved
clang-tools-extra/docs/clang-tidy/checks/bugprone/pointer-arithmetic-on-polymorphic-object.rst
Outdated
Show resolved
Hide resolved
clang-tools-extra/docs/clang-tidy/checks/bugprone/pointer-arithmetic-on-polymorphic-object.rst
Show resolved
Hide resolved
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.
LGTM minus the stored option value. (please wait for others to finish their review)
clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/clang-tidy/bugprone/PointerArithmeticOnPolymorphicObjectCheck.cpp
Outdated
Show resolved
Hide resolved
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.
Few nits:
- storeOptions needs fix
- Consider excluding implicit code with TK_IgnoreUnlessSpelledInSource (not this may exclude also template instances, so I leave it up to you)
- Documentation require small work
- Make check more strict by default, and add options to relax it (as thats what CTR56-CPP require). I undestand that on your project this may cause some false-positives, but still thats your project problem. Consider adding options to "Allow classes where only virtual function is destructor", "Classes where all virtual functions are marked final", "Classes that do not add new virtual functions or members". (Just check what suit your project most).
Note that next release branch out is in a ~month. Would be nice if this check could fit-in.
clang-tools-extra/docs/clang-tidy/checks/bugprone/pointer-arithmetic-on-polymorphic-object.rst
Outdated
Show resolved
Hide resolved
clang-tools-extra/docs/clang-tidy/checks/bugprone/pointer-arithmetic-on-polymorphic-object.rst
Outdated
Show resolved
Hide resolved
clang-tools-extra/clang-tidy/bugprone/PointerArithmeticOnPolymorphicObjectCheck.h
Show resolved
Hide resolved
clang-tools-extra/clang-tidy/bugprone/PointerArithmeticOnPolymorphicObjectCheck.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/docs/clang-tidy/checks/bugprone/pointer-arithmetic-on-polymorphic-object.rst
Outdated
Show resolved
Hide resolved
clang-tools-extra/docs/clang-tidy/checks/bugprone/pointer-arithmetic-on-polymorphic-object.rst
Outdated
Show resolved
Hide resolved
clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/docs/clang-tidy/checks/bugprone/virtual-arithmetic.rst
Outdated
Show resolved
Hide resolved
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on class 'Base' that declares a virtual function | ||
|
||
templateWarning(b); | ||
} |
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.
Object* ptr = &table[5];
* Renamed IgnoreInheritedVirtualFunctions * Documented cert-ctr56-cpp * IgnoreInheritedVirtualFunctions is now false when using cert-ctr56-cpp
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.
I found a curious report when browsing through the results for llvm-project
: MemoryBuffer.cpp
I have no idea where these spurious fixits could be coming from. My check doesn't emit any. Could this be because of the source range I'm piping into my diagnostic, or is it just some side-effect of other checks, or maybe the Tidy module? I'll investigate and fix this before merging.
Other than this case, fixed the reviews. I'm debating adding a flag for virtual destructor only as well, but I haven't seen a case in the wild where it was the only inherited function.
Made IgnoreInheritedVirtualFunctions false
by default - doesn't have too many meaningful differences, but it does have more reports. Results on projects
const auto PointerExprWithVirtualMethod = | ||
expr(hasType(hasCanonicalType(pointerType( | ||
pointee(hasCanonicalType(hasDeclaration(cxxRecordDecl( | ||
unless(isFinal()), | ||
anyOf(hasMethod(isVirtualAsWritten()), isAbstract()))))))))) | ||
.bind("pointer"); |
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.
Indeed, vtable-wise it works out, but the size could still be different. In fact, as noted in the previous CSA checker's PR, the size very rarely stays the same for subclasses, so this is still a good heuristic for bug-prone cases.
clang-tools-extra/clang-tidy/bugprone/PointerArithmeticOnPolymorphicObjectCheck.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/clang-tidy/bugprone/PointerArithmeticOnPolymorphicObjectCheck.cpp
Show resolved
Hide resolved
clang-tools-extra/docs/clang-tidy/checks/bugprone/pointer-arithmetic-on-polymorphic-object.rst
Outdated
Show resolved
Hide resolved
clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/docs/clang-tidy/checks/bugprone/virtual-arithmetic.rst
Outdated
Show resolved
Hide resolved
clang-tools-extra/docs/clang-tidy/checks/bugprone/pointer-arithmetic-on-polymorphic-object.rst
Outdated
Show resolved
Hide resolved
Seems to be a CodeChecker-specific issue, doesn't block upstreaming this review. |
clang-tools-extra/clang-tidy/bugprone/PointerArithmeticOnPolymorphicObjectCheck.cpp
Outdated
Show resolved
Hide resolved
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.
Some small issues with diagnostic left.
Except that, looks good enough.
clang-tools-extra/clang-tidy/bugprone/PointerArithmeticOnPolymorphicObjectCheck.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/clang-tidy/bugprone/PointerArithmeticOnPolymorphicObjectCheck.cpp
Outdated
Show resolved
Hide resolved
…check (llvm#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.
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.
Results on open-source projects. 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.