//===----------------------------------------------------------------------===// // // 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 "ExceptionEscapeCheck.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "llvm/ADT/StringSet.h" using namespace clang::ast_matchers; namespace clang::tidy::bugprone { namespace { AST_MATCHER_P(FunctionDecl, isEnabled, llvm::StringSet<>, FunctionsThatShouldNotThrow) { return FunctionsThatShouldNotThrow.contains(Node.getNameAsString()); } AST_MATCHER(FunctionDecl, isExplicitThrow) { return isExplicitThrowExceptionSpec(Node.getExceptionSpecType()) && Node.getExceptionSpecSourceRange().isValid(); } AST_MATCHER(FunctionDecl, hasAtLeastOneParameter) { return Node.getNumParams() > 0; } } // namespace ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), RawFunctionsThatShouldNotThrow(Options.get( "FunctionsThatShouldNotThrow", "")), RawIgnoredExceptions(Options.get("IgnoredExceptions", "")), RawCheckedSwapFunctions( Options.get("CheckedSwapFunctions", "swap,iter_swap,iter_move")), CheckDestructors(Options.get("CheckDestructors", true)), CheckMoveMemberFunctions(Options.get("CheckMoveMemberFunctions", true)), CheckMain(Options.get("CheckMain", true)), CheckNothrowFunctions(Options.get("CheckNothrowFunctions", true)) { llvm::SmallVector FunctionsThatShouldNotThrowVec, IgnoredExceptionsVec, CheckedSwapFunctionsVec; RawFunctionsThatShouldNotThrow.split(FunctionsThatShouldNotThrowVec, ",", -1, false); FunctionsThatShouldNotThrow.insert_range(FunctionsThatShouldNotThrowVec); RawCheckedSwapFunctions.split(CheckedSwapFunctionsVec, ",", -1, false); CheckedSwapFunctions.insert_range(CheckedSwapFunctionsVec); llvm::StringSet<> IgnoredExceptions; RawIgnoredExceptions.split(IgnoredExceptionsVec, ",", -1, false); IgnoredExceptions.insert_range(IgnoredExceptionsVec); Tracer.ignoreExceptions(std::move(IgnoredExceptions)); Tracer.ignoreBadAlloc(true); } void ExceptionEscapeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "FunctionsThatShouldNotThrow", RawFunctionsThatShouldNotThrow); Options.store(Opts, "IgnoredExceptions", RawIgnoredExceptions); Options.store(Opts, "CheckedSwapFunctions", RawCheckedSwapFunctions); Options.store(Opts, "CheckDestructors", CheckDestructors); Options.store(Opts, "CheckMoveMemberFunctions", CheckMoveMemberFunctions); Options.store(Opts, "CheckMain", CheckMain); Options.store(Opts, "CheckNothrowFunctions", CheckNothrowFunctions); } void ExceptionEscapeCheck::registerMatchers(MatchFinder *Finder) { auto MatchIf = [](bool Enabled, const auto &Matcher) { ast_matchers::internal::Matcher Nothing = unless(anything()); return Enabled ? Matcher : Nothing; }; Finder->addMatcher( functionDecl( isDefinition(), anyOf( MatchIf(CheckNothrowFunctions, isNoThrow()), allOf(anyOf(MatchIf(CheckDestructors, cxxDestructorDecl()), MatchIf( CheckMoveMemberFunctions, anyOf(cxxConstructorDecl(isMoveConstructor()), cxxMethodDecl(isMoveAssignmentOperator()))), MatchIf(CheckMain, isMain()), allOf(isEnabled(CheckedSwapFunctions), hasAtLeastOneParameter())), unless(isExplicitThrow())), isEnabled(FunctionsThatShouldNotThrow))) .bind("thrower"), this); } void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) { const auto *MatchedDecl = Result.Nodes.getNodeAs("thrower"); if (!MatchedDecl) return; const utils::ExceptionAnalyzer::ExceptionInfo Info = Tracer.analyze(MatchedDecl); if (Info.getBehaviour() != utils::ExceptionAnalyzer::State::Throwing) return; diag(MatchedDecl->getLocation(), "an exception may be thrown in function " "%0 which should not throw exceptions") << MatchedDecl; const auto &[ThrowType, ThrowInfo] = *Info.getExceptions().begin(); if (ThrowInfo.Loc.isInvalid()) return; const utils::ExceptionAnalyzer::CallStack &Stack = ThrowInfo.Stack; diag(ThrowInfo.Loc, "frame #0: unhandled exception of type %0 may be thrown in function %1 " "here", DiagnosticIDs::Note) << QualType(ThrowType, 0U) << Stack.back().first; size_t FrameNo = 1; for (auto CurrIt = ++Stack.rbegin(), PrevIt = Stack.rbegin(); CurrIt != Stack.rend(); ++CurrIt, ++PrevIt) { const FunctionDecl *CurrFunction = CurrIt->first; const FunctionDecl *PrevFunction = PrevIt->first; const SourceLocation PrevLocation = PrevIt->second; if (PrevLocation.isValid()) { diag(PrevLocation, "frame #%0: function %1 calls function %2 here", DiagnosticIDs::Note) << FrameNo << CurrFunction << PrevFunction; } else { diag(CurrFunction->getLocation(), "frame #%0: function %1 calls function %2", DiagnosticIDs::Note) << FrameNo << CurrFunction << PrevFunction; } ++FrameNo; } } } // namespace clang::tidy::bugprone