[clang-tools-extra] Feature/labmda uaf (PR #145307)
Vasiliy Kulikov via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 23 03:46:28 PDT 2025
https://github.com/segoon created https://github.com/llvm/llvm-project/pull/145307
None
>From 546567ee93c1c0c0de5e34a1ec4d7c2ccfd081c8 Mon Sep 17 00:00:00 2001
From: Vasily Kulikov <segoon at yandex-team.ru>
Date: Mon, 12 May 2025 20:36:43 +0300
Subject: [PATCH 1/2] clang-tidy: add bugprone-taxi-async-use-after-free
---
.../bugprone/BugproneTidyModule.cpp | 3 ++
.../clang-tidy/bugprone/CMakeLists.txt | 1 +
.../bugprone/TaxiAsyncUseAfterFreeCheck.cpp | 53 +++++++++++++++++++
.../bugprone/TaxiAsyncUseAfterFreeCheck.h | 33 ++++++++++++
clang-tools-extra/docs/ReleaseNotes.rst | 5 ++
.../bugprone/taxi-async-use-after-free.rst | 6 +++
.../docs/clang-tidy/checks/list.rst | 1 +
.../bugprone/taxi-async-use-after-free.cpp | 35 ++++++++++++
8 files changed, 137 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/bugprone/TaxiAsyncUseAfterFreeCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/bugprone/TaxiAsyncUseAfterFreeCheck.h
create mode 100644 clang-tools-extra/docs/clang-tidy/checks/bugprone/taxi-async-use-after-free.rst
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/taxi-async-use-after-free.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
index b780a85bdf3fe..e22741e66580f 100644
--- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -83,6 +83,7 @@
#include "SwappedArgumentsCheck.h"
#include "SwitchMissingDefaultCaseCheck.h"
#include "TaggedUnionMemberCountCheck.h"
+#include "TaxiAsyncUseAfterFreeCheck.h"
#include "TerminatingContinueCheck.h"
#include "ThrowKeywordMissingCheck.h"
#include "TooSmallLoopVariableCheck.h"
@@ -151,6 +152,8 @@ class BugproneModule : public ClangTidyModule {
"bugprone-incorrect-enable-if");
CheckFactories.registerCheck<IncorrectEnableSharedFromThisCheck>(
"bugprone-incorrect-enable-shared-from-this");
+ CheckFactories.registerCheck<TaxiAsyncUseAfterFreeCheck>(
+ "bugprone-taxi-async-use-after-free");
CheckFactories.registerCheck<UnintendedCharOstreamOutputCheck>(
"bugprone-unintended-char-ostream-output");
CheckFactories.registerCheck<ReturnConstRefFromParameterCheck>(
diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
index e310ea9c94543..b39177320b6ce 100644
--- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
@@ -30,6 +30,7 @@ add_clang_library(clangTidyBugproneModule STATIC
InaccurateEraseCheck.cpp
IncorrectEnableIfCheck.cpp
IncorrectEnableSharedFromThisCheck.cpp
+ TaxiAsyncUseAfterFreeCheck.cpp
UnintendedCharOstreamOutputCheck.cpp
ReturnConstRefFromParameterCheck.cpp
SuspiciousStringviewDataUsageCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/TaxiAsyncUseAfterFreeCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/TaxiAsyncUseAfterFreeCheck.cpp
new file mode 100644
index 0000000000000..6fd51b167ac7a
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/TaxiAsyncUseAfterFreeCheck.cpp
@@ -0,0 +1,53 @@
+//===--- TaxiAsyncUseAfterFreeCheck.cpp - clang-tidy ----------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "TaxiAsyncUseAfterFreeCheck.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::bugprone {
+
+void TaxiAsyncUseAfterFreeCheck::registerMatchers(MatchFinder* Finder) {
+ auto hasAsyncName = hasAnyName(
+ "Async", "AsyncNoSpan", "SharedAsyncNoSpan", "CriticalAsyncNoSpan",
+ "SharedCriticalAsyncNoSpan", "CriticalAsync", "SharedCriticalAsync");
+
+ Finder->addMatcher(
+ lambdaExpr(
+ hasParent(materializeTemporaryExpr(hasParent(callExpr(
+ hasParent(cxxMemberCallExpr(
+
+ callee(cxxMethodDecl(hasName("push_back"))),
+
+ on(declRefExpr(hasDeclaration(varDecl().bind("tasks")))))),
+
+ callee(functionDecl(hasAsyncName)))))))
+ .bind("lambda"),
+ this);
+}
+
+void TaxiAsyncUseAfterFreeCheck::check(const MatchFinder::MatchResult& Result) {
+ const auto* MatchedLambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda");
+ const auto* MatchedTasks = Result.Nodes.getNodeAs<VarDecl>("tasks");
+ const SourceLocation TasksLocation = MatchedTasks->getLocation();
+
+ for (const auto& Capture : MatchedLambda->captures()) {
+ if (!Capture.capturesVariable() || Capture.getCaptureKind() != LCK_ByRef)
+ continue;
+
+ const ValueDecl* CapturedVarDecl = Capture.getCapturedVar();
+ if (CapturedVarDecl->getLocation() >= TasksLocation) {
+ diag(Capture.getLocation(), "captured here");
+ diag(CapturedVarDecl->getLocation(), "variable can be used after free");
+ diag(TasksLocation, "std::vector<Task> can die after variable");
+ }
+ }
+}
+
+} // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/clang-tidy/bugprone/TaxiAsyncUseAfterFreeCheck.h b/clang-tools-extra/clang-tidy/bugprone/TaxiAsyncUseAfterFreeCheck.h
new file mode 100644
index 0000000000000..7e56e6a2b3c23
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/TaxiAsyncUseAfterFreeCheck.h
@@ -0,0 +1,33 @@
+//===--- TaxiAsyncUseAfterFreeCheck.h - clang-tidy --------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TAXIASYNCUSEAFTERFREECHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TAXIASYNCUSEAFTERFREECHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::bugprone {
+
+/// Use-after-free for engine::Async.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/taxi-async-use-after-free.html
+class TaxiAsyncUseAfterFreeCheck : public ClangTidyCheck {
+public:
+ TaxiAsyncUseAfterFreeCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context) {}
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+ return LangOpts.CPlusPlus;
+ }
+};
+
+} // namespace clang::tidy::bugprone
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TAXIASYNCUSEAFTERFREECHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 579fca54924d5..ea1081e603b72 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -124,6 +124,11 @@ New checks
pointer and store it as class members without handle the copy and move
constructors and the assignments.
+- New :doc:`bugprone-taxi-async-use-after-free
+ <clang-tidy/checks/bugprone/taxi-async-use-after-free>` check.
+
+ Use-after-free for engine::Async.
+
- New :doc:`bugprone-unintended-char-ostream-output
<clang-tidy/checks/bugprone/unintended-char-ostream-output>` check.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/taxi-async-use-after-free.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/taxi-async-use-after-free.rst
new file mode 100644
index 0000000000000..bff4f8dd974b3
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/taxi-async-use-after-free.rst
@@ -0,0 +1,6 @@
+.. title:: clang-tidy - bugprone-taxi-async-use-after-free
+
+bugprone-taxi-async-use-after-free
+==================================
+
+FIXME: Describe what patterns does the check detect and why. Give examples.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 18f1467285fab..c098ee3d5efa6 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -151,6 +151,7 @@ Clang-Tidy Checks
:doc:`bugprone-swapped-arguments <bugprone/swapped-arguments>`, "Yes"
:doc:`bugprone-switch-missing-default-case <bugprone/switch-missing-default-case>`,
:doc:`bugprone-tagged-union-member-count <bugprone/tagged-union-member-count>`,
+ :doc:`bugprone-taxi-async-use-after-free <bugprone/taxi-async-use-after-free>`, "Yes"
:doc:`bugprone-terminating-continue <bugprone/terminating-continue>`, "Yes"
:doc:`bugprone-throw-keyword-missing <bugprone/throw-keyword-missing>`,
:doc:`bugprone-too-small-loop-variable <bugprone/too-small-loop-variable>`,
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/taxi-async-use-after-free.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/taxi-async-use-after-free.cpp
new file mode 100644
index 0000000000000..7c96f2b575a0f
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/taxi-async-use-after-free.cpp
@@ -0,0 +1,35 @@
+// RUN: %check_clang_tidy %s bugprone-taxi-async-use-after-free %t
+
+namespace std {
+
+template<typename T>
+class vector {
+public:
+ void push_back(T) {}
+};
+
+}
+
+namespace engine {
+
+template <typename Function, typename... Args>
+int Async(Function&& f, Args&&... args) {
+ return 1;
+}
+
+}
+
+void f_ok() {
+ int x = 1;
+ std::vector<int> v;
+
+ v.push_back(engine::Async([&x]{ x = 2;}));
+}
+
+void f_use_after_free() {
+ std::vector<int> v;
+ int x = 1;
+
+ v.push_back(engine::Async([&x]{ x = 2;}));
+}
+
>From edc0413f2bf8ac77514ecdbcfc21a0e0a2168b9e Mon Sep 17 00:00:00 2001
From: Vasily Kulikov <segoon at yandex-team.ru>
Date: Mon, 23 Jun 2025 13:29:41 +0300
Subject: [PATCH 2/2] handle refs
---
.../bugprone/TaxiAsyncUseAfterFreeCheck.cpp | 5 +++++
.../bugprone/taxi-async-use-after-free.cpp | 18 ++++++++++++++++++
2 files changed, 23 insertions(+)
diff --git a/clang-tools-extra/clang-tidy/bugprone/TaxiAsyncUseAfterFreeCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/TaxiAsyncUseAfterFreeCheck.cpp
index 6fd51b167ac7a..c044cf7b56f72 100644
--- a/clang-tools-extra/clang-tidy/bugprone/TaxiAsyncUseAfterFreeCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/TaxiAsyncUseAfterFreeCheck.cpp
@@ -42,6 +42,11 @@ void TaxiAsyncUseAfterFreeCheck::check(const MatchFinder::MatchResult& Result) {
continue;
const ValueDecl* CapturedVarDecl = Capture.getCapturedVar();
+ if (CapturedVarDecl->getType().getCanonicalType()->isLValueReferenceType() ||
+ CapturedVarDecl->getType().getCanonicalType()->isRValueReferenceType()) {
+ continue;
+ }
+
if (CapturedVarDecl->getLocation() >= TasksLocation) {
diag(Capture.getLocation(), "captured here");
diag(CapturedVarDecl->getLocation(), "variable can be used after free");
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/taxi-async-use-after-free.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/taxi-async-use-after-free.cpp
index 7c96f2b575a0f..f8d4eee0411cb 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/taxi-async-use-after-free.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/taxi-async-use-after-free.cpp
@@ -28,8 +28,26 @@ void f_ok() {
void f_use_after_free() {
std::vector<int> v;
+ // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: std::vector<Task> can die after variable [bugprone-taxi-async-use-after-free]
int x = 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable can be used after free [bugprone-taxi-async-use-after-free]
v.push_back(engine::Async([&x]{ x = 2;}));
+ // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: captured here [bugprone-taxi-async-use-after-free]
}
+void f_ref() {
+ int xx = 1;
+ std::vector<int> v;
+ int &x = x;
+
+ v.push_back(engine::Async([&x]{ x = 2;}));
+}
+
+void f_ref_ref() {
+ int xx = 1;
+ std::vector<int> v;
+ int &&x = static_cast<int&&>(xx);
+
+ v.push_back(engine::Async([&x]{ x = 2;}));
+}
More information about the cfe-commits
mailing list