diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp --- 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 @@ "bugprone-undelegated-constructor"); CheckFactories.registerCheck( "bugprone-unhandled-self-assignment"); + CheckFactories.registerCheck( + "bugprone-unhandled-exception-at-new"); CheckFactories.registerCheck( "bugprone-unused-raii"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt @@ -56,6 +56,7 @@ TooSmallLoopVariableCheck.cpp UndefinedMemoryManipulationCheck.cpp UndelegatedConstructorCheck.cpp + UnhandledExceptionAtNewCheck.cpp UnhandledSelfAssignmentCheck.cpp UnusedRaiiCheck.cpp UnusedReturnValueCheck.cpp diff --git a/clang-tools-extra/clang-tidy/bugprone/UnhandledExceptionAtNewCheck.h b/clang-tools-extra/clang-tidy/bugprone/UnhandledExceptionAtNewCheck.h new file mode 100644 --- /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/clang-tidy/bugprone/UnhandledExceptionAtNewCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnhandledExceptionAtNewCheck.cpp new file mode 100644 --- /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, 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()->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("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/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -107,6 +107,11 @@ Finds member initializations in the constructor body which can be placed into the initialization list instead. +- New :doc:`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 --- /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 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -101,6 +101,7 @@ `bugprone-too-small-loop-variable `_, `bugprone-undefined-memory-manipulation `_, `bugprone-undelegated-constructor `_, + `bugprone-unhandled-exception-at-new `_, `bugprone-unhandled-self-assignment `_, `bugprone-unused-raii `_, "Yes" `bugprone-unused-return-value `_, 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 --- /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(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(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 +void f_est_noexcept_dependent_unused() noexcept(P) { + int *I = new int; +} + +template +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 +void f_dependent_new() { + T *X = new T; +} + +void f_1() { + f_est_noexcept_dependent_used(); +}