clang-tools 23.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 {
17
18template <>
20 bugprone::ExceptionEscapeCheck::TreatFunctionsWithoutSpecification> {
23
24 static llvm::ArrayRef<
25 std::pair<TreatFunctionsWithoutSpecification, StringRef>>
27 static constexpr std::pair<TreatFunctionsWithoutSpecification, StringRef>
28 Mapping[] = {
29 {TreatFunctionsWithoutSpecification::None, "None"},
30 {TreatFunctionsWithoutSpecification::OnlyUndefined,
31 "OnlyUndefined"},
32 {TreatFunctionsWithoutSpecification::All, "All"},
33 };
34 return {Mapping};
35 }
36};
37
38namespace bugprone {
39namespace {
40
41AST_MATCHER_P(FunctionDecl, isEnabled, llvm::StringSet<>,
42 FunctionsThatShouldNotThrow) {
43 return FunctionsThatShouldNotThrow.contains(Node.getNameAsString());
44}
45
46AST_MATCHER(FunctionDecl, isExplicitThrow) {
47 return isExplicitThrowExceptionSpec(Node.getExceptionSpecType()) &&
48 Node.getExceptionSpecSourceRange().isValid();
49}
50
51AST_MATCHER(FunctionDecl, hasAtLeastOneParameter) {
52 return Node.getNumParams() > 0;
53}
54
55} // namespace
56
58 ClangTidyContext *Context)
59 : ClangTidyCheck(Name, Context), RawFunctionsThatShouldNotThrow(Options.get(
60 "FunctionsThatShouldNotThrow", "")),
61 RawIgnoredExceptions(Options.get("IgnoredExceptions", "")),
62 RawCheckedSwapFunctions(
63 Options.get("CheckedSwapFunctions", "swap,iter_swap,iter_move")),
64 CheckDestructors(Options.get("CheckDestructors", true)),
65 CheckMoveMemberFunctions(Options.get("CheckMoveMemberFunctions", true)),
66 CheckMain(Options.get("CheckMain", true)),
67 CheckNothrowFunctions(Options.get("CheckNothrowFunctions", true)),
68 TreatFunctionsWithoutSpecificationAsThrowing(
69 Options.get("TreatFunctionsWithoutSpecificationAsThrowing",
71 llvm::SmallVector<StringRef, 8> FunctionsThatShouldNotThrowVec,
72 IgnoredExceptionsVec, CheckedSwapFunctionsVec;
73 RawFunctionsThatShouldNotThrow.split(FunctionsThatShouldNotThrowVec, ",", -1,
74 false);
75 FunctionsThatShouldNotThrow.insert_range(FunctionsThatShouldNotThrowVec);
76
77 RawCheckedSwapFunctions.split(CheckedSwapFunctionsVec, ",", -1, false);
78 CheckedSwapFunctions.insert_range(CheckedSwapFunctionsVec);
79
80 llvm::StringSet<> IgnoredExceptions;
81 RawIgnoredExceptions.split(IgnoredExceptionsVec, ",", -1, false);
82 IgnoredExceptions.insert_range(IgnoredExceptionsVec);
83 Tracer.ignoreExceptions(std::move(IgnoredExceptions));
84 Tracer.ignoreBadAlloc(true);
85
86 Tracer.assumeMissingDefinitionsFunctionsAsThrowing(
87 TreatFunctionsWithoutSpecificationAsThrowing !=
89
90 Tracer.assumeUnannotatedFunctionsAsThrowing(
91 TreatFunctionsWithoutSpecificationAsThrowing ==
93}
94
96 Options.store(Opts, "FunctionsThatShouldNotThrow",
97 RawFunctionsThatShouldNotThrow);
98 Options.store(Opts, "IgnoredExceptions", RawIgnoredExceptions);
99 Options.store(Opts, "CheckedSwapFunctions", RawCheckedSwapFunctions);
100 Options.store(Opts, "CheckDestructors", CheckDestructors);
101 Options.store(Opts, "CheckMoveMemberFunctions", CheckMoveMemberFunctions);
102 Options.store(Opts, "CheckMain", CheckMain);
103 Options.store(Opts, "CheckNothrowFunctions", CheckNothrowFunctions);
104 Options.store(Opts, "TreatFunctionsWithoutSpecificationAsThrowing",
105 TreatFunctionsWithoutSpecificationAsThrowing);
106}
107
108void ExceptionEscapeCheck::registerMatchers(MatchFinder *Finder) {
109 auto MatchIf = [](bool Enabled, const auto &Matcher) {
110 const ast_matchers::internal::Matcher<FunctionDecl> Nothing =
111 unless(anything());
112 return Enabled ? Matcher : Nothing;
113 };
114 Finder->addMatcher(
115 functionDecl(
116 isDefinition(),
117 anyOf(
118 MatchIf(CheckNothrowFunctions, isNoThrow()),
119 allOf(anyOf(MatchIf(CheckDestructors, cxxDestructorDecl()),
120 MatchIf(
121 CheckMoveMemberFunctions,
122 anyOf(cxxConstructorDecl(isMoveConstructor()),
123 cxxMethodDecl(isMoveAssignmentOperator()))),
124 MatchIf(CheckMain, isMain()),
125 allOf(isEnabled(CheckedSwapFunctions),
126 hasAtLeastOneParameter())),
127 unless(isExplicitThrow())),
128 isEnabled(FunctionsThatShouldNotThrow)))
129 .bind("thrower"),
130 this);
131}
132
133void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) {
134 const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("thrower");
135
136 if (!MatchedDecl)
137 return;
138
139 const utils::ExceptionAnalyzer::ExceptionInfo Info =
140 Tracer.analyze(MatchedDecl);
141
142 if (Info.getBehaviour() != utils::ExceptionAnalyzer::State::Throwing)
143 return;
144
145 diag(MatchedDecl->getLocation(), "an exception may be thrown in function "
146 "%0 which should not throw exceptions")
147 << MatchedDecl;
148
149 if (Info.getExceptions().empty())
150 return;
151
152 const auto &[ThrowType, ThrowInfo] = *Info.getExceptions().begin();
153
154 if (ThrowInfo.Loc.isInvalid())
155 return;
156
157 const utils::ExceptionAnalyzer::CallStack &Stack = ThrowInfo.Stack;
158 diag(ThrowInfo.Loc,
159 "frame #0: unhandled exception of type %0 may be thrown in function %1 "
160 "here",
161 DiagnosticIDs::Note)
162 << QualType(ThrowType, 0U) << Stack.back().first;
163
164 size_t FrameNo = 1;
165 for (auto CurrIt = ++Stack.rbegin(), PrevIt = Stack.rbegin();
166 CurrIt != Stack.rend(); ++CurrIt, ++PrevIt) {
167 const FunctionDecl *CurrFunction = CurrIt->first;
168 const FunctionDecl *PrevFunction = PrevIt->first;
169 const SourceLocation PrevLocation = PrevIt->second;
170 if (PrevLocation.isValid()) {
171 diag(PrevLocation, "frame #%0: function %1 calls function %2 here",
172 DiagnosticIDs::Note)
173 << FrameNo << CurrFunction << PrevFunction;
174 } else {
175 diag(CurrFunction->getLocation(),
176 "frame #%0: function %1 calls function %2", DiagnosticIDs::Note)
177 << FrameNo << CurrFunction << PrevFunction;
178 }
179 ++FrameNo;
180 }
181}
182
183} // namespace bugprone
184} // namespace clang::tidy
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(BinaryOperator, isRelationalOperator)
llvm::StringMap< ClangTidyValue > OptionMap
static llvm::ArrayRef< std::pair< TreatFunctionsWithoutSpecification, StringRef > > getEnumMapping()
This class should be specialized by any enum type that needs to be converted to and from an llvm::Str...