Skip to content

[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

Merged
merged 23 commits into from
Jul 4, 2024

Conversation

Discookie
Copy link
Contributor

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.

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 virtual override declaration, and the LLVM reports are true positives, as far as I can tell.

Discookie added 2 commits May 6, 2024 06:11
Finds pointer arithmetic on classes that declare a virtual function.
Useful for reports within template instantiations.
@llvmbot
Copy link
Member

llvmbot commented May 13, 2024

@llvm/pr-subscribers-clang-tidy

Author: Discookie (Discookie)

Changes

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.

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 virtual override declaration, and the LLVM reports are true positives, as far as I can tell.


Full diff: https://github.com/llvm/llvm-project/pull/91951.diff

7 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp (+3)
  • (modified) clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt (+1)
  • (added) clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.cpp (+49)
  • (added) clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.h (+30)
  • (added) clang-tools-extra/docs/clang-tidy/checks/bugprone/virtual-arithmetic.rst (+50)
  • (modified) clang-tools-extra/docs/clang-tidy/checks/list.rst (+1)
  • (added) clang-tools-extra/test/clang-tidy/checkers/bugprone/virtual-arithmetic.cpp (+59)
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

@llvmbot
Copy link
Member

llvmbot commented May 13, 2024

@llvm/pr-subscribers-clang-tools-extra

Author: Discookie (Discookie)

Changes

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.

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 virtual override declaration, and the LLVM reports are true positives, as far as I can tell.


Full diff: https://github.com/llvm/llvm-project/pull/91951.diff

7 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp (+3)
  • (modified) clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt (+1)
  • (added) clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.cpp (+49)
  • (added) clang-tools-extra/clang-tidy/bugprone/VirtualArithmeticCheck.h (+30)
  • (added) clang-tools-extra/docs/clang-tidy/checks/bugprone/virtual-arithmetic.rst (+50)
  • (modified) clang-tools-extra/docs/clang-tidy/checks/list.rst (+1)
  • (added) clang-tools-extra/test/clang-tidy/checkers/bugprone/virtual-arithmetic.cpp (+59)
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

@Discookie Discookie requested a review from gamesh411 May 13, 2024 12:30
@Discookie
Copy link
Contributor Author

Discookie commented May 13, 2024

(Finding classes with virtual member fns declared is a good heuristic for finding polymorphic objects in general, as previously discussed in #82977 (comment).)

Copy link
Contributor

@EugeneZelenko EugeneZelenko left a 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.

Copy link
Contributor

@5chmidti 5chmidti left a 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.

Comment on lines 43 to 46
diag(PointerExpr->getBeginLoc(),
"pointer arithmetic on class '%0' that declares a virtual function, "
"undefined behavior if the pointee is a different class")
<< PointeeType->getName();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Please stream the SourceRange of PointerExpr 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 pass PointeeType without the getName instead.

Copy link
Contributor Author

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.

Copy link
Contributor

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

Copy link
Contributor Author

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.

Copy link
Member

@PiotrZSL PiotrZSL left a 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.

// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on class 'Base' that declares a virtual function

templateWarning(b);
}
Copy link
Member

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];
}

Copy link
Contributor Author

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Object* ptr = &table[5];

@carlosgalvezp
Copy link
Contributor

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.

@carlosgalvezp
Copy link
Contributor

Otherwise since this is a problem also on non-polymorphic objects, maybe "bugprone-pointer-arithmetic-base-class"?

@PiotrZSL
Copy link
Member

bugprone-pointer-arithmetic-polymorphic-object or bugprone-pointer-arithmetic-on-polymorphic-object could be fine.
limiting check to polymorphic objects it's fine, someone can always enable an cppguidelines check to cover all cases.

Also check-alias should be added to cert.

@carlosgalvezp
Copy link
Contributor

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.

Copy link
Contributor Author

@Discookie Discookie left a 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.

Comment on lines 43 to 46
diag(PointerExpr->getBeginLoc(),
"pointer arithmetic on class '%0' that declares a virtual function, "
"undefined behavior if the pointee is a different class")
<< PointeeType->getName();
Copy link
Contributor Author

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.

// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on class 'Base' that declares a virtual function

templateWarning(b);
}
Copy link
Contributor Author

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.

@Discookie Discookie changed the title [clang-tidy] Add bugprone-virtual-arithmetic check [clang-tidy] Add bugprone-pointer-arithmetic-on-polymorphic-object check May 29, 2024
Copy link
Contributor

@5chmidti 5chmidti left a 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)

Copy link
Member

@PiotrZSL PiotrZSL left a 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.

// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on class 'Base' that declares a virtual function

templateWarning(b);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Object* ptr = &table[5];

Copy link
Contributor Author

@Discookie Discookie left a 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

Comment on lines 42 to 47
const auto PointerExprWithVirtualMethod =
expr(hasType(hasCanonicalType(pointerType(
pointee(hasCanonicalType(hasDeclaration(cxxRecordDecl(
unless(isFinal()),
anyOf(hasMethod(isVirtualAsWritten()), isAbstract())))))))))
.bind("pointer");
Copy link
Contributor Author

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.

@Discookie
Copy link
Contributor Author

Seems to be a CodeChecker-specific issue, doesn't block upstreaming this review.

Copy link
Member

@PiotrZSL PiotrZSL left a 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.

@Discookie Discookie merged commit f329e3e into llvm:main Jul 4, 2024
8 checks passed
kbluck pushed a commit to kbluck/llvm-project that referenced this pull request Jul 6, 2024
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants