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 @@ -60,6 +60,7 @@ #include "TooSmallLoopVariableCheck.h" #include "UndefinedMemoryManipulationCheck.h" #include "UndelegatedConstructorCheck.h" +#include "UnhandledExceptionAtNewCheck.h" #include "UnhandledSelfAssignmentCheck.h" #include "UnusedRaiiCheck.h" #include "UnusedReturnValueCheck.h" @@ -175,6 +176,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 @@ -55,6 +55,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 @@ +//===--- UnhandledSelfAssignmentCheck.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; + } + 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,74 @@ +//===--- 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); + if (InnerMatcher.matches(CatchS->getCaughtType(), Finder, Builder)) + return true; + // Generic catch handler should match anything. + if (CatchS->getCaughtType().isNull()) + return true; + } + return false; +} + +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 NothrowTType = + recordType(hasDeclaration(cxxRecordDecl(hasName("::std::nothrow_t")))); + auto BadAllocReferenceType = referenceType(pointee(BadAllocType)); + auto ExceptionReferenceType = referenceType(pointee(ExceptionType)); + auto NothrowTConstReferenceType = referenceType( + pointee(hasUnqualifiedDesugaredType(NothrowTType), isConstQualified())); + + auto CatchBadAllocType = + qualType(hasCanonicalType(anyOf(BadAllocType, BadAllocReferenceType, + ExceptionType, ExceptionReferenceType))); + auto BadAllocCatchingTryBlock = cxxTryStmt(hasHandlerFor(CatchBadAllocType)); + + auto FunctionMayNotThrow = functionDecl(isNoThrow()); + auto NothrowOperatorNew = functionDecl( + hasAnyParameter(parmVarDecl(hasType(NothrowTConstReferenceType)))); + + Finder->addMatcher(cxxNewExpr(unless(hasDeclaration(NothrowOperatorNew)), + 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 @@ -67,7 +67,16 @@ Improvements to clang-tidy -------------------------- -The improvements are... +Improvements to clang-tidy +-------------------------- + +New checks +^^^^^^^^^^ + +- New :doc:`bugprone-unhandled-exception-at-new + ` check. + +Finds calls to ``new`` that may throw exception that is not handled. Improvements to include-fixer ----------------------------- 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,20 @@ +.. title:: clang-tidy - bugprone-unhandled-exception-at-new + +bugprone-unhandled-exception-at-new +=================================== + +Finds calls to ``new`` that may throw exception that is not handled. + +.. code-block:: c++ + + int *f() noexcept { + int *p = new int[1000]; + // ... + return p; + } + +Calls to ``new`` can throw exception of type ``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. 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 @@ -99,6 +99,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,133 @@ +// 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 {}; +struct nothrow_t {}; +extern const nothrow_t nothrow; +} // namespace std + +void *operator new(std::size_t, const std::nothrow_t &); +void *operator new(std::size_t, std::align_val_t, const std::nothrow_t &); + +class A {}; +typedef std::bad_alloc badalloc1; +using badalloc2 = std::bad_alloc; +using badalloc3 = std::bad_alloc &; + +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 &) { + } +} + +void f3() noexcept { + int *I1 = new (std::nothrow) int; + int *I2 = new (static_cast(1), std::nothrow) int; +} + +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; +} + +template +void f_est_noexcept_dependent() noexcept(P) { + int *I = new int; +}