Index: clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp =================================================================== --- clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp +++ clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp @@ -67,6 +67,7 @@ #include "UndefinedMemoryManipulationCheck.h" #include "UndelegatedConstructorCheck.h" #include "UnhandledExceptionAtNewCheck.h" +#include "UnhandledExceptionAtStoCheck.h" #include "UnhandledSelfAssignmentCheck.h" #include "UnusedRaiiCheck.h" #include "UnusedReturnValueCheck.h" @@ -192,6 +193,8 @@ "bugprone-undefined-memory-manipulation"); CheckFactories.registerCheck( "bugprone-undelegated-constructor"); + CheckFactories.registerCheck( + "bugprone-unhandled-exception-at-sto"); CheckFactories.registerCheck( "bugprone-unhandled-self-assignment"); CheckFactories.registerCheck( Index: clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt =================================================================== --- clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt +++ clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt @@ -63,6 +63,7 @@ UndefinedMemoryManipulationCheck.cpp UndelegatedConstructorCheck.cpp UnhandledExceptionAtNewCheck.cpp + UnhandledExceptionAtStoCheck.cpp UnhandledSelfAssignmentCheck.cpp UnusedRaiiCheck.cpp UnusedReturnValueCheck.cpp Index: clang-tools-extra/clang-tidy/bugprone/UnhandledExceptionAtStoCheck.h =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/bugprone/UnhandledExceptionAtStoCheck.h @@ -0,0 +1,38 @@ +//===--- UnhandledExceptionAtStoCheck.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_UNHANDLEDEXCEPTIONATSTOCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNHANDLEDEXCEPTIONATSTOCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Finds calls to 'stoi' and 'stof' families of functions that may throw +/// unhandled exception at invalid argument and out of range. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/unhandled-exception-at-sto.html +class UnhandledExceptionAtStoCheck : public ClangTidyCheck { +public: + UnhandledExceptionAtStoCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, 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_UNHANDLEDEXCEPTIONATSTOCHECK_H Index: clang-tools-extra/clang-tidy/bugprone/UnhandledExceptionAtStoCheck.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/bugprone/UnhandledExceptionAtStoCheck.cpp @@ -0,0 +1,131 @@ +//===--- UnhandledExceptionAtStoCheck.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 "UnhandledExceptionAtStoCheck.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; +} + +void UnhandledExceptionAtStoCheck::registerMatchers(MatchFinder *Finder) { + auto ExceptionType = + recordType(hasDeclaration(cxxRecordDecl(hasName("::std::exception")))); + auto LogicErrorType = + recordType(hasDeclaration(cxxRecordDecl(hasName("::std::logic_error")))); + auto InvalidArgumentType = recordType( + hasDeclaration(cxxRecordDecl(hasName("::std::invalid_argument")))); + auto OutOfRangeType = + recordType(hasDeclaration(cxxRecordDecl(hasName("::std::out_of_range")))); + + auto ExceptionReferenceType = referenceType(pointee(ExceptionType)); + auto LogicErrorReferenceType = referenceType(pointee(LogicErrorType)); + auto InvalidArgumentReferenceType = + referenceType(pointee(InvalidArgumentType)); + auto OutOfRangeReferenceType = referenceType(pointee(OutOfRangeType)); + + auto CatchStoExceptionType = qualType( + hasCanonicalType(anyOf(ExceptionType, ExceptionReferenceType, + LogicErrorType, LogicErrorReferenceType))); + auto CatchInvalidArgumentType = qualType(hasCanonicalType( + anyOf(InvalidArgumentType, InvalidArgumentReferenceType))); + auto CatchOutOfRangeType = qualType( + hasCanonicalType(anyOf(OutOfRangeType, OutOfRangeReferenceType))); + + auto StoExceptionCatchingTryBlock = + cxxTryStmt(hasHandlerFor(CatchStoExceptionType)).bind("all"); + auto StoInvalidArgumentCatchingTryBlock = + cxxTryStmt(hasHandlerFor(CatchInvalidArgumentType)).bind("i"); + auto StoOutOfRangeCatchingTryBlock = + cxxTryStmt(hasHandlerFor(CatchOutOfRangeType)).bind("o"); + + const auto StoCallee = callee(functionDecl(anyOf( + hasName("std::stoi"), hasName("std::stol"), hasName("std::stoll"), + hasName("std::stof"), hasName("std::stod"), hasName("std::stold")))); + + Finder->addMatcher( + callExpr(StoCallee, + allOf(unless(hasAncestor(StoExceptionCatchingTryBlock)), + unless(hasAncestor(StoInvalidArgumentCatchingTryBlock)), + unless(hasAncestor(StoOutOfRangeCatchingTryBlock)))) + .bind("catched-no-exception-call"), + this); + Finder->addMatcher( + callExpr(StoCallee, + allOf(unless(hasAncestor(StoExceptionCatchingTryBlock)), + hasAncestor(StoInvalidArgumentCatchingTryBlock), + unless(hasAncestor(StoOutOfRangeCatchingTryBlock)))) + .bind("catched-invalid-argument-call"), + this); + Finder->addMatcher( + callExpr(StoCallee, + allOf(unless(hasAncestor(StoExceptionCatchingTryBlock)), + hasAncestor(StoOutOfRangeCatchingTryBlock), + unless(hasAncestor(StoInvalidArgumentCatchingTryBlock)))) + .bind("catched-out-of-range-call"), + this); +} + +void UnhandledExceptionAtStoCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *CatchedNoExceptionCall = + Result.Nodes.getNodeAs("catched-no-exception-call"); + const auto *CatchedInvalidArgumentCall = + Result.Nodes.getNodeAs("catched-invalid-argument-call"); + const auto *CatchedOutOfRangeCall = + Result.Nodes.getNodeAs("catched-out-of-range-call"); + + const CallExpr *CatedStoCall = nullptr; + const FunctionDecl *StoCallDecl = nullptr; + std::string Message = "missing exception handler for "; + + if (CatchedNoExceptionCall) { + CatedStoCall = CatchedNoExceptionCall; + StoCallDecl = CatchedNoExceptionCall->getDirectCallee(); + if (!StoCallDecl) + return; + Message += "invalid argument and out of range"; + } else if (CatchedInvalidArgumentCall) { + CatedStoCall = CatchedInvalidArgumentCall; + StoCallDecl = CatchedInvalidArgumentCall->getDirectCallee(); + if (!StoCallDecl) + return; + Message += "out of range"; + } else { + CatedStoCall = CatchedOutOfRangeCall; + StoCallDecl = CatchedOutOfRangeCall->getDirectCallee(); + if (!StoCallDecl) + return; + Message += "invalid argument"; + } + diag(CatedStoCall->getBeginLoc(), Message + " at 'std::%0'") + << StoCallDecl->getName(); +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang Index: clang-tools-extra/docs/ReleaseNotes.rst =================================================================== --- clang-tools-extra/docs/ReleaseNotes.rst +++ clang-tools-extra/docs/ReleaseNotes.rst @@ -132,6 +132,11 @@ Detects confusable Unicode identifiers. +- New :doc:`bugprone-unhandled-exception-at-sto + ` check. + + Finds calls to ``stoi`` and ``stod`` families of functions with missing exception handler for ``std::invalid_argument`` and ``std::out_of_range``. + - New :doc:`modernize-macro-to-enum ` check. Index: clang-tools-extra/docs/clang-tidy/checks/bugprone/unhandled-exception-at-sto.rst =================================================================== --- /dev/null +++ clang-tools-extra/docs/clang-tidy/checks/bugprone/unhandled-exception-at-sto.rst @@ -0,0 +1,15 @@ +.. title:: clang-tidy - bugprone-unhandled-exception-at-sto + +bugprone-unhandled-exception-at-sto +=================================== + +Finds calls to ``stoi`` and ``stof`` families of functions with missing exception handler for ``std::invalid_argument`` and ``std::out_of_range``. + +.. code-block:: c++ + + int test(const std::string &s) { + int v = std::stoi(s); + return v; + } + +Calls to ``stoi`` and ``stof`` families of functions can throw exceptions of type ``std::invalid_argument`` and ``std::out_of_range`` that should be handled by the code. Index: clang-tools-extra/docs/clang-tidy/checks/list.rst =================================================================== --- clang-tools-extra/docs/clang-tidy/checks/list.rst +++ clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -133,6 +133,7 @@ `bugprone-undefined-memory-manipulation `_, `bugprone-undelegated-constructor `_, `bugprone-unhandled-exception-at-new `_, + `bugprone-unhandled-exception-at-sto `_, "Yes" `bugprone-unhandled-self-assignment `_, `bugprone-unused-raii `_, "Yes" `bugprone-unused-return-value `_, Index: clang-tools-extra/test/clang-tidy/checkers/bugprone/unhandled-exception-at-sto.cpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/bugprone/unhandled-exception-at-sto.cpp @@ -0,0 +1,128 @@ +// RUN: %check_clang_tidy %s bugprone-unhandled-exception-at-sto %t + +namespace std { + +template +struct basic_string { + basic_string(const Char *); + ~basic_string(); +}; +typedef basic_string string; + +typedef unsigned int size_t; + +int stoi(const std::string &str, size_t *pos = nullptr, int base = 10) { return 0; } +long stol(const std::string &str, size_t *pos = nullptr, int base = 10) { return 0; } +long long stoll(const std::string &str, size_t *pos = nullptr, int base = 10) { return 0; } + +float stof(const std::string &str, size_t *pos = nullptr) { return 0; } +double stod(const std::string &str, size_t *pos = nullptr) { return 0; } +long double stold(const std::string &str, size_t *pos = nullptr) { return 0; } + +struct exception {}; +struct logic_error : public exception {}; +struct invalid_argument : public logic_error {}; +struct out_of_range : public logic_error {}; + +} // namespace std + +void test() { + std::string s("123"); + + int a = std::stoi(s); + // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: missing exception handler for invalid argument and out of range at 'std::stoi' [bugprone-unhandled-exception-at-sto] + long b = std::stol(s); + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: missing exception handler for invalid argument and out of range at 'std::stol' [bugprone-unhandled-exception-at-sto] + long long c = std::stoll(s); + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: missing exception handler for invalid argument and out of range at 'std::stoll' [bugprone-unhandled-exception-at-sto] + float d = std::stof(s); + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: missing exception handler for invalid argument and out of range at 'std::stof' [bugprone-unhandled-exception-at-sto] + double e = std::stod(s); + // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: missing exception handler for invalid argument and out of range at 'std::stod' [bugprone-unhandled-exception-at-sto] + long double f = std::stold(s); + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: missing exception handler for invalid argument and out of range at 'std::stold' [bugprone-unhandled-exception-at-sto] + + try { + int v = std::stoi(s); + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: missing exception handler for invalid argument at 'std::stoi' [bugprone-unhandled-exception-at-sto] + } catch (std::out_of_range) { + } + try { + int v = std::stoi(s); + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: missing exception handler for invalid argument at 'std::stoi' [bugprone-unhandled-exception-at-sto] + } catch (std::out_of_range &) { + } + try { + int v = std::stoi(s); + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: missing exception handler for invalid argument at 'std::stoi' [bugprone-unhandled-exception-at-sto] + } catch (const std::out_of_range &) { + } + try { + int v = std::stoi(s); + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: missing exception handler for invalid argument and out of range at 'std::stoi' [bugprone-unhandled-exception-at-sto] + } catch (std::out_of_range *) { + } + + try { + int v = std::stoi(s); + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: missing exception handler for out of range at 'std::stoi' [bugprone-unhandled-exception-at-sto] + } catch (std::invalid_argument) { + } + try { + int v = std::stoi(s); + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: missing exception handler for out of range at 'std::stoi' [bugprone-unhandled-exception-at-sto] + } catch (std::invalid_argument &) { + } + try { + int v = std::stoi(s); + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: missing exception handler for out of range at 'std::stoi' [bugprone-unhandled-exception-at-sto] + } catch (const std::invalid_argument &) { + } + try { + int v = std::stoi(s); + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: missing exception handler for invalid argument and out of range at 'std::stoi' [bugprone-unhandled-exception-at-sto] + } catch (std::invalid_argument *) { + } + + try { + int v = std::stoi(s); + } catch (std::exception) { + } + try { + int v = std::stoi(s); + } catch (std::exception &) { + } + try { + int v = std::stoi(s); + } catch (const std::exception &) { + } + try { + int v = std::stoi(s); + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: missing exception handler for invalid argument and out of range at 'std::stoi' [bugprone-unhandled-exception-at-sto] + } catch (std::exception *) { + } + + try { + int v = std::stoi(s); + } catch (std::logic_error) { + } + try { + int v = std::stoi(s); + } catch (std::logic_error &) { + } + try { + int v = std::stoi(s); + } catch (const std::logic_error &) { + } + try { + int v = std::stoi(s); + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: missing exception handler for invalid argument and out of range at 'std::stoi' [bugprone-unhandled-exception-at-sto] + } catch (std::logic_error *) { + } + + try { + int v = std::stoi(s); + } catch (const std::invalid_argument &) { + } catch (const std::out_of_range &) { + } +}