Skip to content

Commit 8f378ea

Browse files
authored
[clang-tidy] Add check 'bugprone-unsafe-to-allow-exceptions' (llvm#176430)
`ExceptionEscapeCheck` does not warn if a function is `noexcept(false)` (may throw) but is one of the functions where throwing exceptions is unsafe. This check can be used to find such cases.
1 parent d0ce5d6 commit 8f378ea

10 files changed

Lines changed: 257 additions & 1 deletion

File tree

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
#include "UnintendedCharOstreamOutputCheck.h"
107107
#include "UniquePtrArrayMismatchCheck.h"
108108
#include "UnsafeFunctionsCheck.h"
109+
#include "UnsafeToAllowExceptionsCheck.h"
109110
#include "UnusedLocalNonTrivialVariableCheck.h"
110111
#include "UnusedRaiiCheck.h"
111112
#include "UnusedReturnValueCheck.h"
@@ -308,6 +309,8 @@ class BugproneModule : public ClangTidyModule {
308309
"bugprone-crtp-constructor-accessibility");
309310
CheckFactories.registerCheck<UnsafeFunctionsCheck>(
310311
"bugprone-unsafe-functions");
312+
CheckFactories.registerCheck<UnsafeToAllowExceptionsCheck>(
313+
"bugprone-unsafe-to-allow-exceptions");
311314
CheckFactories.registerCheck<UnusedLocalNonTrivialVariableCheck>(
312315
"bugprone-unused-local-non-trivial-variable");
313316
CheckFactories.registerCheck<UnusedRaiiCheck>("bugprone-unused-raii");

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ add_clang_library(clangTidyBugproneModule STATIC
108108
UnhandledSelfAssignmentCheck.cpp
109109
UniquePtrArrayMismatchCheck.cpp
110110
UnsafeFunctionsCheck.cpp
111+
UnsafeToAllowExceptionsCheck.cpp
111112
UnusedLocalNonTrivialVariableCheck.cpp
112113
UnusedRaiiCheck.cpp
113114
UnusedReturnValueCheck.cpp
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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+
9+
#include "UnsafeToAllowExceptionsCheck.h"
10+
#include "../utils/OptionsUtils.h"
11+
#include "clang/ASTMatchers/ASTMatchFinder.h"
12+
13+
using namespace clang::ast_matchers;
14+
15+
namespace clang::tidy::bugprone {
16+
namespace {
17+
18+
AST_MATCHER(FunctionDecl, isExplicitThrow) {
19+
return isExplicitThrowExceptionSpec(Node.getExceptionSpecType()) &&
20+
Node.getExceptionSpecSourceRange().isValid();
21+
}
22+
23+
} // namespace
24+
25+
UnsafeToAllowExceptionsCheck::UnsafeToAllowExceptionsCheck(
26+
StringRef Name, ClangTidyContext *Context)
27+
: ClangTidyCheck(Name, Context),
28+
CheckedSwapFunctions(utils::options::parseStringList(
29+
Options.get("CheckedSwapFunctions", "swap;iter_swap;iter_move"))) {}
30+
31+
void UnsafeToAllowExceptionsCheck::storeOptions(
32+
ClangTidyOptions::OptionMap &Opts) {
33+
Options.store(Opts, "CheckedSwapFunctions",
34+
utils::options::serializeStringList(CheckedSwapFunctions));
35+
}
36+
37+
void UnsafeToAllowExceptionsCheck::registerMatchers(MatchFinder *Finder) {
38+
Finder->addMatcher(
39+
functionDecl(isDefinition(), isExplicitThrow(),
40+
anyOf(cxxDestructorDecl(),
41+
cxxConstructorDecl(isMoveConstructor()),
42+
cxxMethodDecl(isMoveAssignmentOperator()),
43+
allOf(hasAnyName(CheckedSwapFunctions),
44+
unless(parameterCountIs(0))),
45+
isMain()))
46+
.bind("f"),
47+
this);
48+
}
49+
50+
void UnsafeToAllowExceptionsCheck::check(
51+
const MatchFinder::MatchResult &Result) {
52+
const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("f");
53+
assert(MatchedDecl);
54+
55+
diag(MatchedDecl->getLocation(),
56+
"function %0 should not throw exceptions but "
57+
"it is still marked as potentially throwing")
58+
<< MatchedDecl;
59+
}
60+
61+
} // namespace clang::tidy::bugprone
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
9+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNSAFETOALLOWEXCEPTIONSCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNSAFETOALLOWEXCEPTIONSCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
14+
namespace clang::tidy::bugprone {
15+
16+
/// Finds functions where throwing exceptions is unsafe but the function is
17+
/// still marked as potentially throwing.
18+
///
19+
/// For the user-facing documentation see:
20+
/// https://clang.llvm.org/extra/clang-tidy/checks/bugprone/unsafe-to-allow-exceptions.html
21+
class UnsafeToAllowExceptionsCheck : public ClangTidyCheck {
22+
public:
23+
UnsafeToAllowExceptionsCheck(StringRef Name, ClangTidyContext *Context);
24+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
25+
return LangOpts.CPlusPlus && LangOpts.CXXExceptions;
26+
}
27+
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
28+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
29+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
30+
31+
private:
32+
std::vector<llvm::StringRef> CheckedSwapFunctions;
33+
};
34+
35+
} // namespace clang::tidy::bugprone
36+
37+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNSAFETOALLOWEXCEPTIONSCHECK_H

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ Improvements to clang-tidy
102102
New checks
103103
^^^^^^^^^^
104104

105+
- New :doc:`bugprone-unsafe-to-allow-exceptions
106+
<clang-tidy/checks/bugprone/unsafe-to-allow-exceptions>` check.
107+
108+
Finds functions where throwing exceptions is unsafe but the function is still
109+
marked as potentially throwing.
110+
105111
- New :doc:`llvm-type-switch-case-types
106112
<clang-tidy/checks/llvm/type-switch-case-types>` check.
107113

clang-tools-extra/docs/clang-tidy/checks/bugprone/exception-escape.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ Functions declared explicitly with ``noexcept(false)`` or ``throw(exception)``
2828
will be excluded from the analysis, as even though it is not recommended for
2929
functions like ``swap()``, ``main()``, move constructors, move assignment
3030
operators and destructors, it is a clear indication of the developer's
31-
intention and should be respected.
31+
intention and should be respected. To check if these special functions are
32+
marked as potentially throwing, the check
33+
:doc:`bugprone-unsafe-to-allow-exceptions <unsafe-to-allow-exceptions>` can be
34+
used.
3235

3336
WARNING! This check may be expensive on large source files.
3437

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
.. title:: clang-tidy - bugprone-unsafe-to-allow-exceptions
2+
3+
bugprone-unsafe-to-allow-exceptions
4+
===================================
5+
6+
Finds functions where throwing exceptions is unsafe but the function is still
7+
marked as potentially throwing. Throwing exceptions from the following
8+
functions can be problematic:
9+
10+
* Destructors
11+
* Move constructors
12+
* Move assignment operators
13+
* The ``main()`` functions
14+
* ``swap()`` functions
15+
* ``iter_swap()`` functions
16+
* ``iter_move()`` functions
17+
18+
A destructor throwing an exception may result in undefined behavior, resource
19+
leaks or unexpected termination of the program. Throwing move constructor or
20+
move assignment also may result in undefined behavior or resource leak. The
21+
``swap()`` operations expected to be non throwing most of the cases and they
22+
are always possible to implement in a non throwing way. Non throwing ``swap()``
23+
operations are also used to create move operations. A throwing ``main()``
24+
function also results in unexpected termination.
25+
26+
The check finds any of these functions if it is marked with ``noexcept(false)``
27+
or ``throw(exception)``. This would indicate that the function is expected to
28+
throw exceptions. Only the presence of these keywords is checked, not if the
29+
function actually throws any exception. To check if the function actually
30+
throws exception, the check :doc:`bugprone-exception-escape <exception-escape>`
31+
can be used (but it does not warn if a function is explicitly marked as
32+
throwing).
33+
34+
Options
35+
-------
36+
37+
.. option:: CheckedSwapFunctions
38+
39+
Semicolon-separated list of checked swap function names (where throwing
40+
exceptions is unsafe). These functions are checked if the parameter count is
41+
at least 1. Default value is `swap;iter_swap;iter_move`.

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ Clang-Tidy Checks
176176
:doc:`bugprone-unintended-char-ostream-output <bugprone/unintended-char-ostream-output>`, "Yes"
177177
:doc:`bugprone-unique-ptr-array-mismatch <bugprone/unique-ptr-array-mismatch>`, "Yes"
178178
:doc:`bugprone-unsafe-functions <bugprone/unsafe-functions>`,
179+
:doc:`bugprone-unsafe-to-allow-exceptions <bugprone/unsafe-to-allow-exceptions>`,
179180
:doc:`bugprone-unused-local-non-trivial-variable <bugprone/unused-local-non-trivial-variable>`,
180181
:doc:`bugprone-unused-raii <bugprone/unused-raii>`, "Yes"
181182
:doc:`bugprone-unused-return-value <bugprone/unused-return-value>`,
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// RUN: %check_clang_tidy -std=c++11,c++14 %s bugprone-unsafe-to-allow-exceptions %t -- \
2+
// RUN: -config="{CheckOptions: { \
3+
// RUN: bugprone-unsafe-to-allow-exceptions.CheckedSwapFunctions: 'swap', \
4+
// RUN: }}" \
5+
// RUN: -- -fexceptions
6+
7+
class Exception {};
8+
9+
struct may_throw {
10+
may_throw(may_throw&&) throw(int) {
11+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'may_throw' should not throw exceptions but it is still marked as potentially throwing [bugprone-unsafe-to-allow-exceptions]
12+
}
13+
may_throw& operator=(may_throw&&) throw(Exception) {
14+
// CHECK-MESSAGES: :[[@LINE-1]]:14: warning: function 'operator=' should not throw exceptions but it is still marked as potentially throwing
15+
}
16+
~may_throw() throw(char, Exception) {
17+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function '~may_throw' should not throw exceptions but it is still marked as potentially throwing
18+
}
19+
20+
void f() noexcept(false) {
21+
}
22+
};
23+
24+
struct no_throw {
25+
no_throw(no_throw&&) throw() {
26+
}
27+
no_throw& operator=(no_throw&&) noexcept(true) {
28+
}
29+
~no_throw() noexcept(true) {
30+
}
31+
};
32+
33+
int main() throw(char) {
34+
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: function 'main' should not throw exceptions but it is still marked as potentially throwing
35+
return 0;
36+
}
37+
38+
void swap(no_throw&, no_throw&) throw(bool) {
39+
// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'swap' should not throw exceptions but it is still marked as potentially throwing
40+
}
41+
42+
void iter_swap(int&, int&) throw(bool) {
43+
}
44+
45+
void iter_move(int&) throw(bool) {
46+
}
47+
48+
void swap(double&, double&) {
49+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// RUN: %check_clang_tidy -std=c++17-or-later %s bugprone-unsafe-to-allow-exceptions %t -- -- -fexceptions
2+
// RUN: %check_clang_tidy -std=c++17-or-later -check-suffix=,CUSTOMSWAP %s bugprone-unsafe-to-allow-exceptions %t -- \
3+
// RUN: -config="{CheckOptions: { \
4+
// RUN: bugprone-unsafe-to-allow-exceptions.CheckedSwapFunctions: 'swap;iter_swap;iter_move;swap1', \
5+
// RUN: }}" \
6+
// RUN: -- -fexceptions
7+
8+
struct may_throw {
9+
may_throw(may_throw&&) noexcept(false) {
10+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'may_throw' should not throw exceptions but it is still marked as potentially throwing [bugprone-unsafe-to-allow-exceptions]
11+
}
12+
may_throw& operator=(may_throw&&) noexcept(false) {
13+
// CHECK-MESSAGES: :[[@LINE-1]]:14: warning: function 'operator=' should not throw exceptions but it is still marked as potentially throwing
14+
}
15+
~may_throw() noexcept(false) {
16+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function '~may_throw' should not throw exceptions but it is still marked as potentially throwing
17+
}
18+
19+
void f() noexcept(false) {
20+
}
21+
};
22+
23+
struct no_throw {
24+
no_throw(no_throw&&) throw() {
25+
}
26+
no_throw& operator=(no_throw&&) noexcept(true) {
27+
}
28+
~no_throw() noexcept(true) {
29+
}
30+
};
31+
32+
int main() noexcept(false) {
33+
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: function 'main' should not throw exceptions but it is still marked as potentially throwing
34+
return 0;
35+
}
36+
37+
void swap(int&, int&) noexcept(false) {
38+
// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'swap' should not throw exceptions but it is still marked as potentially throwing
39+
}
40+
41+
void iter_swap(int&, int&) noexcept(false) {
42+
// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'iter_swap' should not throw exceptions but it is still marked as potentially throwing
43+
}
44+
45+
void iter_move(int&) noexcept(false) {
46+
// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'iter_move' should not throw exceptions but it is still marked as potentially throwing
47+
}
48+
49+
void swap(double&, double&) {
50+
}
51+
52+
void swap1(long&) noexcept(false) {
53+
// CHECK-MESSAGES-CUSTOMSWAP: :[[@LINE-1]]:6: warning: function 'swap1' should not throw exceptions but it is still marked as potentially throwing
54+
}

0 commit comments

Comments
 (0)