[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