[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