[clang-tools-extra] 530456c - [clang-tidy] Add new check 'bugprone-unhandled-exception-at-new'.

Balázs Kéri via cfe-commits cfe-commits at lists.llvm.org
Wed Apr 14 00:24:38 PDT 2021


Author: Balázs Kéri
Date: 2021-04-14T09:33:11+02:00
New Revision: 530456caf9088b8eb237c0ab75086722ce0f2950

URL: https://github.com/llvm/llvm-project/commit/530456caf9088b8eb237c0ab75086722ce0f2950
DIFF: https://github.com/llvm/llvm-project/commit/530456caf9088b8eb237c0ab75086722ce0f2950.diff

LOG: [clang-tidy] Add new check 'bugprone-unhandled-exception-at-new'.

Reviewed By: aaron.ballman

Differential Revision: https://reviews.llvm.org/D97196

Added: 
    clang-tools-extra/clang-tidy/bugprone/UnhandledExceptionAtNewCheck.cpp
    clang-tools-extra/clang-tidy/bugprone/UnhandledExceptionAtNewCheck.h
    clang-tools-extra/docs/clang-tidy/checks/bugprone-unhandled-exception-at-new.rst
    clang-tools-extra/test/clang-tidy/checkers/bugprone-unhandled-exception-at-new.cpp

Modified: 
    clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
    clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
    clang-tools-extra/docs/ReleaseNotes.rst
    clang-tools-extra/docs/clang-tidy/checks/list.rst

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
index 9cdadf7bf92ba..595a30e8d8ce3 100644
--- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -61,6 +61,7 @@
 #include "TooSmallLoopVariableCheck.h"
 #include "UndefinedMemoryManipulationCheck.h"
 #include "UndelegatedConstructorCheck.h"
+#include "UnhandledExceptionAtNewCheck.h"
 #include "UnhandledSelfAssignmentCheck.h"
 #include "UnusedRaiiCheck.h"
 #include "UnusedReturnValueCheck.h"
@@ -178,6 +179,8 @@ class BugproneModule : public ClangTidyModule {
         "bugprone-undelegated-constructor");
     CheckFactories.registerCheck<UnhandledSelfAssignmentCheck>(
         "bugprone-unhandled-self-assignment");
+    CheckFactories.registerCheck<UnhandledExceptionAtNewCheck>(
+        "bugprone-unhandled-exception-at-new");
     CheckFactories.registerCheck<UnusedRaiiCheck>(
         "bugprone-unused-raii");
     CheckFactories.registerCheck<UnusedReturnValueCheck>(

diff  --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
index b16dbf576c374..022e5c5842ee2 100644
--- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
@@ -56,6 +56,7 @@ add_clang_library(clangTidyBugproneModule
   TooSmallLoopVariableCheck.cpp
   UndefinedMemoryManipulationCheck.cpp
   UndelegatedConstructorCheck.cpp
+  UnhandledExceptionAtNewCheck.cpp
   UnhandledSelfAssignmentCheck.cpp
   UnusedRaiiCheck.cpp
   UnusedReturnValueCheck.cpp

diff  --git a/clang-tools-extra/clang-tidy/bugprone/UnhandledExceptionAtNewCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnhandledExceptionAtNewCheck.cpp
new file mode 100644
index 0000000000000..c558b3148c9d5
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/UnhandledExceptionAtNewCheck.cpp
@@ -0,0 +1,78 @@
+//===--- UnhandledExceptionAtNewCheck.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 "UnhandledExceptionAtNewCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace tidy {
+namespace bugprone {
+
+AST_MATCHER_P(CXXTryStmt, hasHandlerFor,
+              ast_matchers::internal::Matcher<QualType>, InnerMatcher) {
+  for (unsigned NH = Node.getNumHandlers(), I = 0; I < NH; ++I) {
+    const CXXCatchStmt *CatchS = Node.getHandler(I);
+    // Check for generic catch handler (match anything).
+    if (CatchS->getCaughtType().isNull())
+      return true;
+    ast_matchers::internal::BoundNodesTreeBuilder Result(*Builder);
+    if (InnerMatcher.matches(CatchS->getCaughtType(), Finder, &Result)) {
+      *Builder = std::move(Result);
+      return true;
+    }
+  }
+  return false;
+}
+
+AST_MATCHER(CXXNewExpr, mayThrow) {
+  FunctionDecl *OperatorNew = Node.getOperatorNew();
+  if (!OperatorNew)
+    return false;
+  return !OperatorNew->getType()->castAs<FunctionProtoType>()->isNothrow();
+}
+
+UnhandledExceptionAtNewCheck::UnhandledExceptionAtNewCheck(
+    StringRef Name, ClangTidyContext *Context)
+    : ClangTidyCheck(Name, Context) {}
+
+void UnhandledExceptionAtNewCheck::registerMatchers(MatchFinder *Finder) {
+  auto BadAllocType =
+      recordType(hasDeclaration(cxxRecordDecl(hasName("::std::bad_alloc"))));
+  auto ExceptionType =
+      recordType(hasDeclaration(cxxRecordDecl(hasName("::std::exception"))));
+  auto BadAllocReferenceType = referenceType(pointee(BadAllocType));
+  auto ExceptionReferenceType = referenceType(pointee(ExceptionType));
+
+  auto CatchBadAllocType =
+      qualType(hasCanonicalType(anyOf(BadAllocType, BadAllocReferenceType,
+                                      ExceptionType, ExceptionReferenceType)));
+  auto BadAllocCatchingTryBlock = cxxTryStmt(hasHandlerFor(CatchBadAllocType));
+
+  auto FunctionMayNotThrow = functionDecl(isNoThrow());
+
+  Finder->addMatcher(cxxNewExpr(mayThrow(),
+                                unless(hasAncestor(BadAllocCatchingTryBlock)),
+                                hasAncestor(FunctionMayNotThrow))
+                         .bind("new-expr"),
+                     this);
+}
+
+void UnhandledExceptionAtNewCheck::check(
+    const MatchFinder::MatchResult &Result) {
+  const auto *MatchedExpr = Result.Nodes.getNodeAs<CXXNewExpr>("new-expr");
+  if (MatchedExpr)
+    diag(MatchedExpr->getBeginLoc(),
+         "missing exception handler for allocation failure at 'new'");
+}
+
+} // namespace bugprone
+} // namespace tidy
+} // namespace clang

diff  --git a/clang-tools-extra/clang-tidy/bugprone/UnhandledExceptionAtNewCheck.h b/clang-tools-extra/clang-tidy/bugprone/UnhandledExceptionAtNewCheck.h
new file mode 100644
index 0000000000000..d8839919b8386
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/UnhandledExceptionAtNewCheck.h
@@ -0,0 +1,38 @@
+//===--- UnhandledExceptionAtNewCheck.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_UNHANDLEDEXCEPTIONATNEWCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNHANDLEDEXCEPTIONATNEWCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang {
+namespace tidy {
+namespace bugprone {
+
+/// Finds calls to 'new' that may throw unhandled exception at allocation
+/// failure.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-unhandled-exception-at-new.html
+class UnhandledExceptionAtNewCheck : public ClangTidyCheck {
+public:
+  UnhandledExceptionAtNewCheck(StringRef Name, ClangTidyContext *Context);
+
+  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+    return LangOpts.CPlusPlus && LangOpts.CXXExceptions;
+  }
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+};
+
+} // namespace bugprone
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNHANDLEDEXCEPTIONATNEWCHECK_H

diff  --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index e744d4d957ddd..a9c943795e945 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -107,6 +107,11 @@ New checks
   Finds member initializations in the constructor body which can be placed into
   the initialization list instead.
 
+- New :doc:`bugprone-unhandled-exception-at-new
+  <clang-tidy/checks/bugprone-unhandled-exception-at-new>` check.
+
+  Finds calls to ``new`` with missing exception handler for ``std::bad_alloc``.
+
 New check aliases
 ^^^^^^^^^^^^^^^^^
 

diff  --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone-unhandled-exception-at-new.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone-unhandled-exception-at-new.rst
new file mode 100644
index 0000000000000..01f59af49f816
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone-unhandled-exception-at-new.rst
@@ -0,0 +1,25 @@
+.. title:: clang-tidy - bugprone-unhandled-exception-at-new
+
+bugprone-unhandled-exception-at-new
+===================================
+
+Finds calls to ``new`` with missing exception handler for ``std::bad_alloc``.
+
+.. code-block:: c++
+
+  int *f() noexcept {
+    int *p = new int[1000];
+    // ...
+    return p;
+  }
+
+Calls to ``new`` can throw exceptions of type ``std::bad_alloc`` that should
+be handled by the code. Alternatively, the nonthrowing form of ``new`` can be
+used. The check verifies that the exception is handled in the function
+that calls ``new``, unless a nonthrowing version is used or the exception
+is allowed to propagate out of the function (exception handler is checked for
+types ``std::bad_alloc``, ``std::exception``, and catch-all handler).
+The check assumes that any user-defined ``operator new`` is either
+``noexcept`` or may throw an exception of type ``std::bad_alloc`` (or derived
+from it). Other exception types or exceptions occuring in the objects's
+constructor are not taken into account.

diff  --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index a17fc80790631..8889be098b6df 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -101,6 +101,7 @@ Clang-Tidy Checks
    `bugprone-too-small-loop-variable <bugprone-too-small-loop-variable.html>`_,
    `bugprone-undefined-memory-manipulation <bugprone-undefined-memory-manipulation.html>`_,
    `bugprone-undelegated-constructor <bugprone-undelegated-constructor.html>`_,
+   `bugprone-unhandled-exception-at-new <bugprone-unhandled-exception-at-new.html>`_,
    `bugprone-unhandled-self-assignment <bugprone-unhandled-self-assignment.html>`_,
    `bugprone-unused-raii <bugprone-unused-raii.html>`_, "Yes"
    `bugprone-unused-return-value <bugprone-unused-return-value.html>`_,

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone-unhandled-exception-at-new.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone-unhandled-exception-at-new.cpp
new file mode 100644
index 0000000000000..433a266cc1858
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone-unhandled-exception-at-new.cpp
@@ -0,0 +1,208 @@
+// RUN: %check_clang_tidy -std=c++14 %s bugprone-unhandled-exception-at-new %t
+
+namespace std {
+typedef __typeof__(sizeof(0)) size_t;
+enum class align_val_t : std::size_t {};
+class exception {};
+class bad_alloc : public exception {};
+class bad_array_new_length : public bad_alloc {};
+struct nothrow_t {};
+extern const nothrow_t nothrow;
+} // namespace std
+
+void *operator new(std::size_t, const std::nothrow_t &) noexcept;
+void *operator new(std::size_t, std::align_val_t, const std::nothrow_t &) noexcept;
+void *operator new(std::size_t, void *) noexcept;
+
+class A {};
+typedef std::bad_alloc badalloc1;
+using badalloc2 = std::bad_alloc;
+using badalloc3 = std::bad_alloc &;
+
+void *operator new(std::size_t, int, int);
+void *operator new(std::size_t, int, int, int) noexcept;
+
+struct ClassSpecificNew {
+  void *operator new(std::size_t);
+  void *operator new(std::size_t, std::align_val_t);
+  void *operator new(std::size_t, int, int) noexcept;
+  void *operator new(std::size_t, int, int, int);
+};
+
+void f1() noexcept {
+  int *I1 = new int;
+  // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: missing exception handler for allocation failure at 'new'
+  try {
+    int *I2 = new int;
+    try {
+      int *I3 = new int;
+    } catch (A) {
+    }
+  } catch (std::bad_alloc) {
+  }
+
+  try {
+    int *I = new int;
+  } catch (std::bad_alloc &) {
+  }
+
+  try {
+    int *I = new int;
+  } catch (const std::bad_alloc &) {
+  }
+
+  try {
+    int *I = new int;
+  } catch (badalloc1) {
+  }
+
+  try {
+    int *I = new int;
+  } catch (badalloc1 &) {
+  }
+
+  try {
+    int *I = new int;
+  } catch (const badalloc1 &) {
+  }
+
+  try {
+    int *I = new int;
+  } catch (badalloc2) {
+  }
+
+  try {
+    int *I = new int;
+  } catch (badalloc3) {
+  }
+
+  try {
+    int *I = new int;
+    // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: missing exception handler for allocation failure at 'new'
+  } catch (std::bad_alloc *) {
+  }
+
+  try {
+    int *I = new int;
+    // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: missing exception handler for allocation failure at 'new'
+  } catch (A) {
+  }
+}
+
+void f2() noexcept {
+  try {
+    int *I = new int;
+  } catch (A) {
+  } catch (std::bad_alloc) {
+  }
+
+  try {
+    int *I = new int;
+  } catch (...) {
+  }
+
+  try {
+    int *I = new int;
+  } catch (const std::exception &) {
+  }
+
+  try {
+    int *I = new int;
+    // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: missing exception handler for allocation failure at 'new'
+  } catch (const std::bad_array_new_length &) {
+  }
+}
+
+void f_new_nothrow() noexcept {
+  int *I1 = new (std::nothrow) int;
+  int *I2 = new (static_cast<std::align_val_t>(1), std::nothrow) int;
+}
+
+void f_new_placement() noexcept {
+  char buf[100];
+  int *I = new (buf) int;
+}
+
+void f_new_user_defined() noexcept {
+  int *I1 = new (1, 2) int;
+  // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: missing exception handler for allocation failure at 'new'
+  int *I2 = new (1, 2, 3) int;
+}
+
+void f_class_specific() noexcept {
+  ClassSpecificNew *N1 = new ClassSpecificNew;
+  // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: missing exception handler for allocation failure at 'new'
+  ClassSpecificNew *N2 = new (static_cast<std::align_val_t>(1)) ClassSpecificNew;
+  // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: missing exception handler for allocation failure at 'new'
+  ClassSpecificNew *N3 = new (1, 2) ClassSpecificNew;
+  ClassSpecificNew *N4 = new (1, 2, 3) ClassSpecificNew;
+  // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: missing exception handler for allocation failure at 'new'
+}
+
+void f_est_none() {
+  int *I = new int;
+}
+
+void f_est_noexcept_false() noexcept(false) {
+  int *I = new int;
+}
+
+void f_est_noexcept_true() noexcept(true) {
+  int *I = new int;
+  // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: missing exception handler for allocation failure at 'new'
+}
+
+void f_est_dynamic_none() throw() {
+  int *I = new int;
+  // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: missing exception handler for allocation failure at 'new'
+}
+
+void f_est_dynamic_1() throw(std::bad_alloc) {
+  int *I = new int;
+}
+
+void f_est_dynamic_2() throw(A) {
+  // the exception specification list is not checked
+  int *I = new int;
+}
+
+void f_try() noexcept try {
+  int *I = new int;
+} catch (const std::bad_alloc &) {
+}
+
+void f_try_bad() noexcept try {
+  int *I = new int;
+  // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: missing exception handler for allocation failure at 'new'
+} catch (const A &) {
+}
+
+void f_embedded_try() noexcept {
+  try {
+    try {
+      int *I = new int;
+    } catch (const A &) {
+    }
+  } catch (const std::bad_alloc &) {
+  }
+}
+
+template <bool P>
+void f_est_noexcept_dependent_unused() noexcept(P) {
+  int *I = new int;
+}
+
+template <bool P>
+void f_est_noexcept_dependent_used() noexcept(P) {
+  int *I = new int;
+  // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: missing exception handler for allocation failure at 'new'
+}
+
+template <class T>
+void f_dependent_new() {
+  T *X = new T;
+}
+
+void f_1() {
+  f_est_noexcept_dependent_used<true>();
+}


        


More information about the cfe-commits mailing list