clang-tools 22.0.0git
bugprone/ExceptionEscapeCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
10
11#include "clang/ASTMatchers/ASTMatchFinder.h"
12#include "llvm/ADT/StringSet.h"
13
14using namespace clang::ast_matchers;
15
16namespace clang::tidy::bugprone {
17namespace {
18
19AST_MATCHER_P(FunctionDecl, isEnabled, llvm::StringSet<>,
20 FunctionsThatShouldNotThrow) {
21 return FunctionsThatShouldNotThrow.contains(Node.getNameAsString());
22}
23
24AST_MATCHER(FunctionDecl, isExplicitThrow) {
25 return isExplicitThrowExceptionSpec(Node.getExceptionSpecType()) &&
26 Node.getExceptionSpecSourceRange().isValid();
27}
28
29AST_MATCHER(FunctionDecl, hasAtLeastOneParameter) {
30 return Node.getNumParams() > 0;
31}
32
33} // namespace
34
36 ClangTidyContext *Context)
37 : ClangTidyCheck(Name, Context), RawFunctionsThatShouldNotThrow(Options.get(
38 "FunctionsThatShouldNotThrow", "")),
39 RawIgnoredExceptions(Options.get("IgnoredExceptions", "")),
40 RawCheckedSwapFunctions(
41 Options.get("CheckedSwapFunctions", "swap,iter_swap,iter_move")),
42 CheckDestructors(Options.get("CheckDestructors", true)),
43 CheckMoveMemberFunctions(Options.get("CheckMoveMemberFunctions", true)),
44 CheckMain(Options.get("CheckMain", true)),
45 CheckNothrowFunctions(Options.get("CheckNothrowFunctions", true)) {
46 llvm::SmallVector<StringRef, 8> FunctionsThatShouldNotThrowVec,
47 IgnoredExceptionsVec, CheckedSwapFunctionsVec;
48 RawFunctionsThatShouldNotThrow.split(FunctionsThatShouldNotThrowVec, ",", -1,
49 false);
50 FunctionsThatShouldNotThrow.insert_range(FunctionsThatShouldNotThrowVec);
51
52 RawCheckedSwapFunctions.split(CheckedSwapFunctionsVec, ",", -1, false);
53 CheckedSwapFunctions.insert_range(CheckedSwapFunctionsVec);
54
55 llvm::StringSet<> IgnoredExceptions;
56 RawIgnoredExceptions.split(IgnoredExceptionsVec, ",", -1, false);
57 IgnoredExceptions.insert_range(IgnoredExceptionsVec);
58 Tracer.ignoreExceptions(std::move(IgnoredExceptions));
59 Tracer.ignoreBadAlloc(true);
60}
61
63 Options.store(Opts, "FunctionsThatShouldNotThrow",
64 RawFunctionsThatShouldNotThrow);
65 Options.store(Opts, "IgnoredExceptions", RawIgnoredExceptions);
66 Options.store(Opts, "CheckedSwapFunctions", RawCheckedSwapFunctions);
67 Options.store(Opts, "CheckDestructors", CheckDestructors);
68 Options.store(Opts, "CheckMoveMemberFunctions", CheckMoveMemberFunctions);
69 Options.store(Opts, "CheckMain", CheckMain);
70 Options.store(Opts, "CheckNothrowFunctions", CheckNothrowFunctions);
71}
72
73void ExceptionEscapeCheck::registerMatchers(MatchFinder *Finder) {
74 auto MatchIf = [](bool Enabled, const auto &Matcher) {
75 const ast_matchers::internal::Matcher<FunctionDecl> Nothing =
76 unless(anything());
77 return Enabled ? Matcher : Nothing;
78 };
79 Finder->addMatcher(
80 functionDecl(
81 isDefinition(),
82 anyOf(
83 MatchIf(CheckNothrowFunctions, isNoThrow()),
84 allOf(anyOf(MatchIf(CheckDestructors, cxxDestructorDecl()),
85 MatchIf(
86 CheckMoveMemberFunctions,
87 anyOf(cxxConstructorDecl(isMoveConstructor()),
88 cxxMethodDecl(isMoveAssignmentOperator()))),
89 MatchIf(CheckMain, isMain()),
90 allOf(isEnabled(CheckedSwapFunctions),
91 hasAtLeastOneParameter())),
92 unless(isExplicitThrow())),
93 isEnabled(FunctionsThatShouldNotThrow)))
94 .bind("thrower"),
95 this);
96}
97
98void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) {
99 const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("thrower");
100
101 if (!MatchedDecl)
102 return;
103
104 const utils::ExceptionAnalyzer::ExceptionInfo Info =
105 Tracer.analyze(MatchedDecl);
106
107 if (Info.getBehaviour() != utils::ExceptionAnalyzer::State::Throwing)
108 return;
109
110 diag(MatchedDecl->getLocation(), "an exception may be thrown in function "
111 "%0 which should not throw exceptions")
112 << MatchedDecl;
113
114 const auto &[ThrowType, ThrowInfo] = *Info.getExceptions().begin();
115
116 if (ThrowInfo.Loc.isInvalid())
117 return;
118
119 const utils::ExceptionAnalyzer::CallStack &Stack = ThrowInfo.Stack;
120 diag(ThrowInfo.Loc,
121 "frame #0: unhandled exception of type %0 may be thrown in function %1 "
122 "here",
123 DiagnosticIDs::Note)
124 << QualType(ThrowType, 0U) << Stack.back().first;
125
126 size_t FrameNo = 1;
127 for (auto CurrIt = ++Stack.rbegin(), PrevIt = Stack.rbegin();
128 CurrIt != Stack.rend(); ++CurrIt, ++PrevIt) {
129 const FunctionDecl *CurrFunction = CurrIt->first;
130 const FunctionDecl *PrevFunction = PrevIt->first;
131 const SourceLocation PrevLocation = PrevIt->second;
132 if (PrevLocation.isValid()) {
133 diag(PrevLocation, "frame #%0: function %1 calls function %2 here",
134 DiagnosticIDs::Note)
135 << FrameNo << CurrFunction << PrevFunction;
136 } else {
137 diag(CurrFunction->getLocation(),
138 "frame #%0: function %1 calls function %2", DiagnosticIDs::Note)
139 << FrameNo << CurrFunction << PrevFunction;
140 }
141 ++FrameNo;
142 }
143}
144
145} // namespace clang::tidy::bugprone
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
ExceptionEscapeCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
@ Throwing
The function can definitely throw given an AST.
llvm::MapVector< const FunctionDecl *, SourceLocation > CallStack
We use a MapVector to preserve the order of the functions in the call stack as well as have fast look...
ExceptionInfo analyze(const FunctionDecl *Func)
AST_MATCHER_P(Stmt, isStatementIdenticalToBoundNode, std::string, ID)
AST_MATCHER(BinaryOperator, isRelationalOperator)
llvm::StringMap< ClangTidyValue > OptionMap