Skip to content

Commit 047273f

Browse files
committed
[clang-tidy] Add bugprone-empty-catch check
Detects and suggests addressing issues with empty catch statements. Reviewed By: xgupta Differential Revision: https://reviews.llvm.org/D144748
1 parent 862b93a commit 047273f

File tree

9 files changed

+373
-0
lines changed

9 files changed

+373
-0
lines changed

clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "DanglingHandleCheck.h"
2121
#include "DynamicStaticInitializersCheck.h"
2222
#include "EasilySwappableParametersCheck.h"
23+
#include "EmptyCatchCheck.h"
2324
#include "ExceptionEscapeCheck.h"
2425
#include "FoldInitTypeCheck.h"
2526
#include "ForwardDeclarationNamespaceCheck.h"
@@ -106,6 +107,7 @@ class BugproneModule : public ClangTidyModule {
106107
"bugprone-dynamic-static-initializers");
107108
CheckFactories.registerCheck<EasilySwappableParametersCheck>(
108109
"bugprone-easily-swappable-parameters");
110+
CheckFactories.registerCheck<EmptyCatchCheck>("bugprone-empty-catch");
109111
CheckFactories.registerCheck<ExceptionEscapeCheck>(
110112
"bugprone-exception-escape");
111113
CheckFactories.registerCheck<FoldInitTypeCheck>("bugprone-fold-init-type");

clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ add_clang_library(clangTidyBugproneModule
1515
DanglingHandleCheck.cpp
1616
DynamicStaticInitializersCheck.cpp
1717
EasilySwappableParametersCheck.cpp
18+
EmptyCatchCheck.cpp
1819
ExceptionEscapeCheck.cpp
1920
FoldInitTypeCheck.cpp
2021
ForwardDeclarationNamespaceCheck.cpp
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//===--- EmptyCatchCheck.cpp - clang-tidy ---------------------------------===//
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+
9+
#include "EmptyCatchCheck.h"
10+
#include "../utils/Matchers.h"
11+
#include "../utils/OptionsUtils.h"
12+
#include "clang/AST/ASTContext.h"
13+
#include "clang/ASTMatchers/ASTMatchFinder.h"
14+
#include "clang/Lex/Lexer.h"
15+
#include <algorithm>
16+
17+
using namespace clang::ast_matchers;
18+
using ::clang::ast_matchers::internal::Matcher;
19+
20+
namespace clang::tidy::bugprone {
21+
22+
namespace {
23+
AST_MATCHER(CXXCatchStmt, isInMacro) {
24+
return Node.getBeginLoc().isMacroID() || Node.getEndLoc().isMacroID() ||
25+
Node.getCatchLoc().isMacroID();
26+
}
27+
28+
AST_MATCHER_P(CXXCatchStmt, hasHandler, Matcher<Stmt>, InnerMatcher) {
29+
Stmt *Handler = Node.getHandlerBlock();
30+
if (!Handler)
31+
return false;
32+
return InnerMatcher.matches(*Handler, Finder, Builder);
33+
}
34+
35+
AST_MATCHER_P(CXXCatchStmt, hasCaughtType, Matcher<QualType>, InnerMatcher) {
36+
return InnerMatcher.matches(Node.getCaughtType(), Finder, Builder);
37+
}
38+
39+
AST_MATCHER_P(CompoundStmt, hasAnyTextFromList, std::vector<llvm::StringRef>,
40+
List) {
41+
if (List.empty())
42+
return false;
43+
44+
ASTContext &Context = Finder->getASTContext();
45+
SourceManager &SM = Context.getSourceManager();
46+
StringRef Text = Lexer::getSourceText(
47+
CharSourceRange::getTokenRange(Node.getSourceRange()), SM,
48+
Context.getLangOpts());
49+
return std::any_of(List.begin(), List.end(), [&](const StringRef &Str) {
50+
return Text.contains_insensitive(Str);
51+
});
52+
}
53+
54+
} // namespace
55+
56+
EmptyCatchCheck::EmptyCatchCheck(StringRef Name, ClangTidyContext *Context)
57+
: ClangTidyCheck(Name, Context),
58+
IgnoreCatchWithKeywords(utils::options::parseStringList(
59+
Options.get("IgnoreCatchWithKeywords", "@TODO;@FIXME"))),
60+
AllowEmptyCatchForExceptions(utils::options::parseStringList(
61+
Options.get("AllowEmptyCatchForExceptions", ""))) {}
62+
63+
void EmptyCatchCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
64+
Options.store(Opts, "IgnoreCatchWithKeywords",
65+
utils::options::serializeStringList(IgnoreCatchWithKeywords));
66+
Options.store(
67+
Opts, "AllowEmptyCatchForExceptions",
68+
utils::options::serializeStringList(AllowEmptyCatchForExceptions));
69+
}
70+
71+
bool EmptyCatchCheck::isLanguageVersionSupported(
72+
const LangOptions &LangOpts) const {
73+
return LangOpts.CPlusPlus;
74+
}
75+
76+
std::optional<TraversalKind> EmptyCatchCheck::getCheckTraversalKind() const {
77+
return TK_IgnoreUnlessSpelledInSource;
78+
}
79+
80+
void EmptyCatchCheck::registerMatchers(MatchFinder *Finder) {
81+
auto AllowedNamedExceptionDecl =
82+
namedDecl(matchers::matchesAnyListedName(AllowEmptyCatchForExceptions));
83+
auto AllowedNamedExceptionTypes =
84+
qualType(anyOf(hasDeclaration(AllowedNamedExceptionDecl),
85+
references(AllowedNamedExceptionDecl),
86+
pointsTo(AllowedNamedExceptionDecl)));
87+
auto IgnoredExceptionType =
88+
qualType(anyOf(AllowedNamedExceptionTypes,
89+
hasCanonicalType(AllowedNamedExceptionTypes)));
90+
91+
Finder->addMatcher(
92+
cxxCatchStmt(unless(isExpansionInSystemHeader()), unless(isInMacro()),
93+
unless(hasCaughtType(IgnoredExceptionType)),
94+
hasHandler(compoundStmt(
95+
statementCountIs(0),
96+
unless(hasAnyTextFromList(IgnoreCatchWithKeywords)))))
97+
.bind("catch"),
98+
this);
99+
}
100+
101+
void EmptyCatchCheck::check(const MatchFinder::MatchResult &Result) {
102+
const auto *MatchedCatchStmt = Result.Nodes.getNodeAs<CXXCatchStmt>("catch");
103+
104+
diag(
105+
MatchedCatchStmt->getCatchLoc(),
106+
"empty catch statements hide issues; to handle exceptions appropriately, "
107+
"consider re-throwing, handling, or avoiding catch altogether");
108+
}
109+
110+
} // namespace clang::tidy::bugprone
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//===--- EmptyCatchCheck.h - clang-tidy -------------------------*- C++ -*-===//
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+
9+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_EMPTYCATCHCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_EMPTYCATCHCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
#include <vector>
14+
15+
namespace clang::tidy::bugprone {
16+
17+
/// Detects and suggests addressing issues with empty catch statements.
18+
///
19+
/// For the user-facing documentation see:
20+
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/empty-catch.html
21+
class EmptyCatchCheck : public ClangTidyCheck {
22+
public:
23+
EmptyCatchCheck(StringRef Name, ClangTidyContext *Context);
24+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
25+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
26+
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
27+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override;
28+
std::optional<TraversalKind> getCheckTraversalKind() const override;
29+
30+
private:
31+
std::vector<llvm::StringRef> IgnoreCatchWithKeywords;
32+
std::vector<llvm::StringRef> AllowEmptyCatchForExceptions;
33+
};
34+
35+
} // namespace clang::tidy::bugprone
36+
37+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_EMPTYCATCHCHECK_H

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ Improvements to clang-tidy
114114
New checks
115115
^^^^^^^^^^
116116

117+
- New :doc:`bugprone-empty-catch
118+
<clang-tidy/checks/bugprone/empty-catch>` check.
119+
120+
Detects and suggests addressing issues with empty catch statements.
121+
117122
- New :doc:`bugprone-multiple-new-in-one-expression
118123
<clang-tidy/checks/bugprone/multiple-new-in-one-expression>` check.
119124

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
.. title:: clang-tidy - bugprone-empty-catch
2+
3+
bugprone-empty-catch
4+
====================
5+
6+
Detects and suggests addressing issues with empty catch statements.
7+
8+
.. code-block:: c++
9+
10+
try {
11+
// Some code that can throw an exception
12+
} catch(const std::exception&) {
13+
}
14+
15+
Having empty catch statements in a codebase can be a serious problem that
16+
developers should be aware of. Catch statements are used to handle exceptions
17+
that are thrown during program execution. When an exception is thrown, the
18+
program jumps to the nearest catch statement that matches the type of the
19+
exception.
20+
21+
Empty catch statements, also known as "swallowing" exceptions, catch the
22+
exception but do nothing with it. This means that the exception is not handled
23+
properly, and the program continues to run as if nothing happened. This can
24+
lead to several issues, such as:
25+
26+
* *Hidden Bugs*: If an exception is caught and ignored, it can lead to hidden
27+
bugs that are difficult to diagnose and fix. The root cause of the problem
28+
may not be apparent, and the program may continue to behave in unexpected
29+
ways.
30+
31+
* *Security Issues*: Ignoring exceptions can lead to security issues, such as
32+
buffer overflows or null pointer dereferences. Hackers can exploit these
33+
vulnerabilities to gain access to sensitive data or execute malicious code.
34+
35+
* *Poor Code Quality*: Empty catch statements can indicate poor code quality
36+
and a lack of attention to detail. This can make the codebase difficult to
37+
maintain and update, leading to longer development cycles and increased
38+
costs.
39+
40+
* *Unreliable Code*: Code that ignores exceptions is often unreliable and can
41+
lead to unpredictable behavior. This can cause frustration for users and
42+
erode trust in the software.
43+
44+
To avoid these issues, developers should always handle exceptions properly.
45+
This means either fixing the underlying issue that caused the exception or
46+
propagating the exception up the call stack to a higher-level handler.
47+
If an exception is not important, it should still be logged or reported in
48+
some way so that it can be tracked and addressed later.
49+
50+
If the exception is something that can be handled locally, then it should be
51+
handled within the catch block. This could involve logging the exception or
52+
taking other appropriate action to ensure that the exception is not ignored.
53+
54+
Here is an example:
55+
56+
.. code-block:: c++
57+
58+
try {
59+
// Some code that can throw an exception
60+
} catch (const std::exception& ex) {
61+
// Properly handle the exception, e.g.:
62+
std::cerr << "Exception caught: " << ex.what() << std::endl;
63+
}
64+
65+
If the exception cannot be handled locally and needs to be propagated up the
66+
call stack, it should be re-thrown or new exception should be thrown.
67+
68+
Here is an example:
69+
70+
.. code-block:: c++
71+
72+
try {
73+
// Some code that can throw an exception
74+
} catch (const std::exception& ex) {
75+
// Re-throw the exception
76+
throw;
77+
}
78+
79+
In some cases, catching the exception at this level may not be necessary, and
80+
it may be appropriate to let the exception propagate up the call stack.
81+
This can be done simply by not using ``try/catch`` block.
82+
83+
Here is an example:
84+
85+
.. code-block:: c++
86+
87+
void function() {
88+
// Some code that can throw an exception
89+
}
90+
91+
void callerFunction() {
92+
try {
93+
function();
94+
} catch (const std::exception& ex) {
95+
// Handling exception on higher level
96+
std::cerr << "Exception caught: " << ex.what() << std::endl;
97+
}
98+
}
99+
100+
Other potential solution to avoid empty catch statements is to modify the code
101+
to avoid throwing the exception in the first place. This can be achieved by
102+
using a different API, checking for error conditions beforehand, or handling
103+
errors in a different way that does not involve exceptions. By eliminating the
104+
need for try-catch blocks, the code becomes simpler and less error-prone.
105+
106+
Here is an example:
107+
108+
.. code-block:: c++
109+
110+
// Old code:
111+
try {
112+
mapContainer["Key"].callFunction();
113+
} catch(const std::out_of_range&) {
114+
}
115+
116+
// New code
117+
if (auto it = mapContainer.find("Key"); it != mapContainer.end()) {
118+
it->second.callFunction();
119+
}
120+
121+
In conclusion, empty catch statements are a bad practice that can lead to hidden
122+
bugs, security issues, poor code quality, and unreliable code. By handling
123+
exceptions properly, developers can ensure that their code is robust, secure,
124+
and maintainable.
125+
126+
Options
127+
-------
128+
129+
.. option:: IgnoreCatchWithKeywords
130+
131+
This option can be used to ignore specific catch statements containing
132+
certain keywords. If a ``catch`` statement body contains (case-insensitive)
133+
any of the keywords listed in this semicolon-separated option, then the
134+
catch will be ignored, and no warning will be raised.
135+
Default value: `@TODO;@FIXME`.
136+
137+
.. option:: AllowEmptyCatchForExceptions
138+
139+
This option can be used to ignore empty catch statements for specific
140+
exception types. By default, the check will raise a warning if an empty
141+
catch statement is detected, regardless of the type of exception being
142+
caught. However, in certain situations, such as when a developer wants to
143+
intentionally ignore certain exceptions or handle them in a different way,
144+
it may be desirable to allow empty catch statements for specific exception
145+
types.
146+
To configure this option, a semicolon-separated list of exception type names
147+
should be provided. If an exception type name in the list is caught in an
148+
empty catch statement, no warning will be raised.
149+
Default value: empty string.

clang-tools-extra/docs/clang-tidy/checks/list.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ Clang-Tidy Checks
8686
`bugprone-dangling-handle <bugprone/dangling-handle.html>`_,
8787
`bugprone-dynamic-static-initializers <bugprone/dynamic-static-initializers.html>`_,
8888
`bugprone-easily-swappable-parameters <bugprone/easily-swappable-parameters.html>`_,
89+
`bugprone-empty-catch <bugprone/empty-catch.html>`_,
8990
`bugprone-exception-escape <bugprone/exception-escape.html>`_,
9091
`bugprone-fold-init-type <bugprone/fold-init-type.html>`_,
9192
`bugprone-forward-declaration-namespace <bugprone/forward-declaration-namespace.html>`_,

0 commit comments

Comments
 (0)