[clang-tools-extra] [clang-tidy] Improve `bugprone-exception-escape`'s handling of lambdas (PR #160592)
Victor Chernyakin via cfe-commits
cfe-commits at lists.llvm.org
Thu Sep 25 08:36:32 PDT 2025
https://github.com/localspook updated https://github.com/llvm/llvm-project/pull/160592
>From e0ca1f5e689d09f91f5de7bc37a9a588e0ad5a56 Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Wed, 24 Sep 2025 12:43:55 -0700
Subject: [PATCH 1/3] [clang-tidy] Improve `bugprone-exception-escape`'s
handling of lambdas
---
.../clang-tidy/utils/ExceptionAnalyzer.cpp | 5 +++
clang-tools-extra/docs/ReleaseNotes.rst | 5 +++
.../checkers/bugprone/exception-escape.cpp | 34 +++++++++++++++++++
3 files changed, 44 insertions(+)
diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
index bdde7249d2796..fd4320eb8144b 100644
--- a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
+++ b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
@@ -595,6 +595,11 @@ ExceptionAnalyzer::throwsException(const Stmt *St,
Results.merge(DestructorExcs);
}
}
+ } else if (const auto *Lambda = dyn_cast<LambdaExpr>(St)) {
+ for (const Stmt *Init : Lambda->capture_inits()) {
+ ExceptionInfo Excs = throwsException(Init, Caught, CallStack);
+ Results.merge(Excs);
+ }
} else {
for (const Stmt *Child : St->children()) {
ExceptionInfo Excs = throwsException(Child, Caught, CallStack);
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 7cdff86beeec6..63ea45cb39270 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -210,6 +210,11 @@ Changes in existing checks
correcting a spelling mistake on its option
``NamePrefixSuffixSilenceDissimilarityTreshold``.
+- Improved :doc:`bugprone-exception-escape
+ <clang-tidy/checks/bugprone/exception-escape>` check's handling of lambdas:
+ exceptions from captures are now diagnosed, exceptions in the bodies of
+ lambdas that aren't actually invoked are not.
+
- Improved :doc:`bugprone-infinite-loop
<clang-tidy/checks/bugprone/infinite-loop>` check by adding detection for
variables introduced by structured bindings.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp
index b10bd1d482867..90022b96a3130 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp
@@ -894,3 +894,37 @@ void pointer_exception_can_not_escape_with_void_handler() noexcept {
} catch (void *) {
}
}
+
+void throw_in_uninvoked_lambda() noexcept {
+ [] { throw 42; };
+}
+
+void throw_in_lambda() noexcept {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_in_lambda' which should not throw exceptions
+ [] { throw 42; }();
+ // CHECK-MESSAGES: :[[@LINE-1]]:8: note: frame #0: unhandled exception of type 'int' may be thrown in function 'operator()' here
+ // CHECK-MESSAGES: :[[@LINE-2]]:19: note: frame #1: function 'throw_in_lambda' calls function 'operator()' here
+}
+
+struct copy_constructor_throws {
+ copy_constructor_throws(const copy_constructor_throws&) { throw 42; }
+};
+
+void throw_in_lambda_default_by_value_capture(const copy_constructor_throws& a) noexcept {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_in_lambda_default_by_value_capture' which should not throw exceptions
+ [=] { a; };
+ // CHECK-MESSAGES: :[[@LINE-6]]:61: note: frame #0: unhandled exception of type 'int' may be thrown in function 'copy_constructor_throws' here
+ // CHECK-MESSAGES: :[[@LINE-2]]:4: note: frame #1: function 'throw_in_lambda_default_by_value_capture' calls function 'copy_constructor_throws' here
+}
+
+void throw_in_lambda_explicit_by_value_capture(const copy_constructor_throws& a) noexcept {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_in_lambda_explicit_by_value_capture' which should not throw exceptions
+ [a] {};
+ // CHECK-MESSAGES: :[[@LINE-13]]:61: note: frame #0: unhandled exception of type 'int' may be thrown in function 'copy_constructor_throws' here
+ // CHECK-MESSAGES: :[[@LINE-2]]:4: note: frame #1: function 'throw_in_lambda_explicit_by_value_capture' calls function 'copy_constructor_throws' here
+}
+
+void no_throw_in_lambda_by_reference_capture(const copy_constructor_throws& a) noexcept {
+ [&] { a; };
+ [&a] {};
+}
>From 5cf9561ced5c2c8de360a85dd73aad42bf43f873 Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Wed, 24 Sep 2025 13:34:39 -0700
Subject: [PATCH 2/3] test throw in lambda
---
.../test/clang-tidy/checkers/bugprone/exception-escape.cpp | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp
index 90022b96a3130..e27148f81fcdd 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp
@@ -928,3 +928,7 @@ void no_throw_in_lambda_by_reference_capture(const copy_constructor_throws& a) n
[&] { a; };
[&a] {};
}
+
+const auto throw_in_noexcept_lambda = [] () noexcept { throw 42; };
+// CHECK-MESSAGES: :[[@LINE-1]]:39: warning: an exception may be thrown in function 'operator()' which should not throw exceptions
+// CHECK-MESSAGES: :[[@LINE-2]]:56: note: frame #0: unhandled exception of type 'int' may be thrown in function 'operator()' here
>From 76518363feaaa526b7bc33b75af041498d5b0fd1 Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Thu, 25 Sep 2025 08:35:47 -0700
Subject: [PATCH 3/3] more tests
---
.../checkers/bugprone/exception-escape.cpp | 24 +++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp
index e27148f81fcdd..a52bbe2246d1e 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp
@@ -929,6 +929,30 @@ void no_throw_in_lambda_by_reference_capture(const copy_constructor_throws& a) n
[&a] {};
}
+void throw_in_lambda_init_capture() noexcept {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_in_lambda_init_capture' which should not throw exceptions
+ [a = [] { throw 42; return 0; }()] {};
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: note: frame #0: unhandled exception of type 'int' may be thrown in function 'operator()' here
+ // CHECK-MESSAGES: :[[@LINE-2]]:34: note: frame #1: function 'throw_in_lambda_init_capture' calls function 'operator()' here
+}
+
+void throw_from_nested_lambda() noexcept {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_from_nested_lambda' which should not throw exceptions
+ [] { [] { throw 42; }(); }();
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: note: frame #0: unhandled exception of type 'int' may be thrown in function 'operator()' here
+ // CHECK-MESSAGES: :[[@LINE-2]]:24: note: frame #1: function 'operator()' calls function 'operator()' here
+ // CHECK-MESSAGES: :[[@LINE-3]]:29: note: frame #2: function 'throw_from_nested_lambda' calls function 'operator()' here
+}
+
const auto throw_in_noexcept_lambda = [] () noexcept { throw 42; };
// CHECK-MESSAGES: :[[@LINE-1]]:39: warning: an exception may be thrown in function 'operator()' which should not throw exceptions
// CHECK-MESSAGES: :[[@LINE-2]]:56: note: frame #0: unhandled exception of type 'int' may be thrown in function 'operator()' here
+
+void thrower() {
+ throw 42;
+}
+
+const auto indirect_throw_in_noexcept_lambda = [] () noexcept { thrower(); };
+// CHECK-MESSAGES: :[[@LINE-1]]:48: warning: an exception may be thrown in function 'operator()' which should not throw exceptions
+// CHECK-MESSAGES: :[[@LINE-5]]:3: note: frame #0: unhandled exception of type 'int' may be thrown in function 'thrower' here
+// CHECK-MESSAGES: :[[@LINE-3]]:65: note: frame #1: function 'operator()' calls function 'thrower' here
More information about the cfe-commits
mailing list