[clang-tools-extra] [clang-tidy] Add bugprone-unsafe-api-functions-calls (PR #187637)

Mats Kindahl via cfe-commits cfe-commits at lists.llvm.org
Wed Mar 25 23:50:51 PDT 2026


https://github.com/mkindahl updated https://github.com/llvm/llvm-project/pull/187637

>From 2dc4b9ea4041b3fd74dcb9eceb77a3bea3d0c566 Mon Sep 17 00:00:00 2001
From: Mats Kindahl <mats at kindahl.net>
Date: Sat, 7 Mar 2026 15:51:13 +0100
Subject: [PATCH 1/5] [clang-tidy] Add bugprone-setvbuf-stack-buffer check

Warn when setvbuf() is called with a stack-allocated array buffer.
The C standard requires the buffer to outlive the stream; using a
local automatic array leads to undefined behavior when the function
returns but the stream remains open. The check allows NULL, static,
global, and pointer (potentially heap-allocated) buffers.
---
 .../bugprone/BugproneTidyModule.cpp           |  3 +
 .../clang-tidy/bugprone/CMakeLists.txt        |  1 +
 .../bugprone/SetvbufStackBufferCheck.cpp      | 69 +++++++++++++++++++
 .../bugprone/SetvbufStackBufferCheck.h        | 32 +++++++++
 .../checks/bugprone/setvbuf-stack-buffer.rst  | 27 ++++++++
 .../docs/clang-tidy/checks/list.rst           |  1 +
 .../checkers/bugprone/setvbuf-stack-buffer.c  | 57 +++++++++++++++
 7 files changed, 190 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/bugprone/SetvbufStackBufferCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/bugprone/SetvbufStackBufferCheck.h
 create mode 100644 clang-tools-extra/docs/clang-tidy/checks/bugprone/setvbuf-stack-buffer.rst
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/setvbuf-stack-buffer.c

diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
index b39aea62a9546..1184b85e19605 100644
--- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -69,6 +69,7 @@
 #include "RedundantBranchConditionCheck.h"
 #include "ReservedIdentifierCheck.h"
 #include "ReturnConstRefFromParameterCheck.h"
+#include "SetvbufStackBufferCheck.h"
 #include "SharedPtrArrayMismatchCheck.h"
 #include "SignalHandlerCheck.h"
 #include "SignedCharMisuseCheck.h"
@@ -241,6 +242,8 @@ class BugproneModule : public ClangTidyModule {
         "bugprone-raw-memory-call-on-non-trivial-type");
     CheckFactories.registerCheck<ReservedIdentifierCheck>(
         "bugprone-reserved-identifier");
+    CheckFactories.registerCheck<SetvbufStackBufferCheck>(
+        "bugprone-setvbuf-stack-buffer");
     CheckFactories.registerCheck<SharedPtrArrayMismatchCheck>(
         "bugprone-shared-ptr-array-mismatch");
     CheckFactories.registerCheck<SignalHandlerCheck>("bugprone-signal-handler");
diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
index f7f185d53b269..9b0e9fc0c9da2 100644
--- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
@@ -72,6 +72,7 @@ add_clang_library(clangTidyBugproneModule STATIC
   RedundantBranchConditionCheck.cpp
   ReservedIdentifierCheck.cpp
   ReturnConstRefFromParameterCheck.cpp
+  SetvbufStackBufferCheck.cpp
   SharedPtrArrayMismatchCheck.cpp
   SignalHandlerCheck.cpp
   SignedCharMisuseCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/SetvbufStackBufferCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/SetvbufStackBufferCheck.cpp
new file mode 100644
index 0000000000000..8952a5a2a0877
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/SetvbufStackBufferCheck.cpp
@@ -0,0 +1,69 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "SetvbufStackBufferCheck.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::bugprone {
+
+void SetvbufStackBufferCheck::registerMatchers(MatchFinder *Finder) {
+  Finder->addMatcher(
+      callExpr(callee(functionDecl(hasAnyName("setvbuf", "::std::setvbuf"))))
+          .bind("call"),
+      this);
+}
+
+void SetvbufStackBufferCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
+  if (!Call || Call->getNumArgs() < 2)
+    return;
+
+  const Expr *BufArg = Call->getArg(1)->IgnoreParenImpCasts();
+
+  // NULL is fine (used for _IONBF).
+  if (BufArg->isNullPointerConstant(*Result.Context,
+                                    Expr::NPC_ValueDependentIsNotNull))
+    return;
+
+  // Resolve to the underlying VarDecl if it's a DeclRefExpr.
+  const VarDecl *VD = nullptr;
+  if (const auto *DRE = dyn_cast<DeclRefExpr>(BufArg))
+    VD = dyn_cast<VarDecl>(DRE->getDecl());
+  else if (const auto *UO = dyn_cast<UnaryOperator>(BufArg)) {
+    // Handle &buf[0]
+    if (UO->getOpcode() == UO_AddrOf) {
+      if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(
+              UO->getSubExpr()->IgnoreParenImpCasts())) {
+        if (const auto *DRE =
+                dyn_cast<DeclRefExpr>(ASE->getBase()->IgnoreParenImpCasts()))
+          VD = dyn_cast<VarDecl>(DRE->getDecl());
+      }
+    }
+  }
+
+  if (!VD)
+    return;
+
+  // Only warn for local automatic (stack) variables.
+  if (!VD->isLocalVarDecl() || VD->isStaticLocal())
+    return;
+
+  // Check if the variable is an array type (direct stack buffer).
+  // For pointer variables, they could point to malloc'd memory — don't warn.
+  if (!VD->getType()->isArrayType())
+    return;
+
+  diag(Call->getBeginLoc(),
+       "passing stack-allocated buffer to 'setvbuf'; buffer must outlive the "
+       "stream; use a static, global, or dynamically allocated buffer instead")
+      << Call->getArg(1)->getSourceRange();
+}
+
+} // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/clang-tidy/bugprone/SetvbufStackBufferCheck.h b/clang-tools-extra/clang-tidy/bugprone/SetvbufStackBufferCheck.h
new file mode 100644
index 0000000000000..4fb97bc06a6d6
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/SetvbufStackBufferCheck.h
@@ -0,0 +1,32 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_SETVBUFSTACKBUFFERCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SETVBUFSTACKBUFFERCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::bugprone {
+
+/// Warns when setvbuf() is called with a stack-allocated buffer, which leads to
+/// undefined behavior if the buffer's lifetime ends before the stream is closed
+/// or reassigned.
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/bugprone/setvbuf-stack-buffer.html
+class SetvbufStackBufferCheck : public ClangTidyCheck {
+public:
+  SetvbufStackBufferCheck(StringRef Name, ClangTidyContext *Context)
+      : ClangTidyCheck(Name, Context) {}
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+};
+
+} // namespace clang::tidy::bugprone
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SETVBUFSTACKBUFFERCHECK_H
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/setvbuf-stack-buffer.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/setvbuf-stack-buffer.rst
new file mode 100644
index 0000000000000..3b54ce405496d
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/setvbuf-stack-buffer.rst
@@ -0,0 +1,27 @@
+.. title:: clang-tidy - bugprone-setvbuf-stack-buffer
+
+bugprone-setvbuf-stack-buffer
+=============================
+
+Warns when ``setvbuf()`` is called with a stack-allocated buffer.
+
+The C standard (C11 §7.21.5.6) requires that the buffer passed to ``setvbuf()``
+must have a lifetime at least as long as the open stream. Passing a local
+(automatic storage duration) buffer leads to undefined behavior when the function
+returns but the stream remains open — the stream will continue to use the
+now-dangling buffer.
+
+.. code-block:: c
+
+   void bad(void) {
+     char buf[BUFSIZ];
+     setvbuf(stdout, buf, _IOFBF, BUFSIZ);  // warning
+     // buf goes out of scope, but stdout keeps using it!
+   }
+
+Safe alternatives:
+
+- ``static`` local buffer: ``static char buf[BUFSIZ];``
+- Global buffer: ``char buf[BUFSIZ];`` at file scope
+- Dynamically allocated: ``char *buf = malloc(BUFSIZ);``
+- Unbuffered: ``setvbuf(stream, NULL, _IONBF, 0);``
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index ceab1e9414951..f20c580a25390 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -139,6 +139,7 @@ Clang-Tidy Checks
    :doc:`bugprone-redundant-branch-condition <bugprone/redundant-branch-condition>`, "Yes"
    :doc:`bugprone-reserved-identifier <bugprone/reserved-identifier>`, "Yes"
    :doc:`bugprone-return-const-ref-from-parameter <bugprone/return-const-ref-from-parameter>`,
+   :doc:`bugprone-setvbuf-stack-buffer <bugprone/setvbuf-stack-buffer>`,
    :doc:`bugprone-shared-ptr-array-mismatch <bugprone/shared-ptr-array-mismatch>`, "Yes"
    :doc:`bugprone-signal-handler <bugprone/signal-handler>`,
    :doc:`bugprone-signed-char-misuse <bugprone/signed-char-misuse>`,
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/setvbuf-stack-buffer.c b/clang-tools-extra/test/clang-tidy/checkers/bugprone/setvbuf-stack-buffer.c
new file mode 100644
index 0000000000000..b96773f7cc2d6
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/setvbuf-stack-buffer.c
@@ -0,0 +1,57 @@
+// RUN: %check_clang_tidy %s bugprone-setvbuf-stack-buffer %t
+
+typedef unsigned long size_t;
+typedef struct FILE FILE;
+extern FILE *stdin;
+
+int setvbuf(FILE *stream, char *buf, int mode, size_t size);
+void *malloc(size_t size);
+void *calloc(size_t count, size_t size);
+
+#define _IOFBF 0
+#define _IOLBF 1
+#define _IONBF 2
+#define BUFSIZ 1024
+
+// Test 1: Stack buffer — should warn.
+void stack_buffer(void) {
+  char buf[BUFSIZ];
+  setvbuf(stdin, buf, _IOFBF, BUFSIZ);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: passing stack-allocated buffer to 'setvbuf'
+}
+
+// Test 2: NULL buffer (unbuffered) — no warning.
+void null_buffer(void) {
+  setvbuf(stdin, (void *)0, _IONBF, 0);
+}
+
+// Test 3: malloc'd buffer — no warning.
+void malloc_buffer(void) {
+  char *buf = (char *)malloc(BUFSIZ);
+  setvbuf(stdin, buf, _IOFBF, BUFSIZ);
+}
+
+// Test 4: calloc'd buffer — no warning.
+void calloc_buffer(void) {
+  char *buf = (char *)calloc(1, BUFSIZ);
+  setvbuf(stdin, buf, _IOFBF, BUFSIZ);
+}
+
+// Test 5: Static buffer — no warning.
+void static_buffer(void) {
+  static char buf[BUFSIZ];
+  setvbuf(stdin, buf, _IOFBF, BUFSIZ);
+}
+
+// Test 6: Global buffer — no warning.
+char global_buf[BUFSIZ];
+void global_buffer(void) {
+  setvbuf(stdin, global_buf, _IOFBF, BUFSIZ);
+}
+
+// Test 7: Stack buffer via &arr[0] — should warn.
+void stack_buffer_addr(void) {
+  char buf[BUFSIZ];
+  setvbuf(stdin, &buf[0], _IOFBF, BUFSIZ);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: passing stack-allocated buffer to 'setvbuf'
+}

>From ebdf043dcbf7c9439d4d2dd0e177ae55354790a7 Mon Sep 17 00:00:00 2001
From: Mats Kindahl <mats at kindahl.net>
Date: Sun, 22 Mar 2026 12:57:15 +0100
Subject: [PATCH 2/5] Handle review comments
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Rename the check to a more generic name and extend it to also detect
stack-allocated buffers passed to setbuf(), which has the same undefined
behavior as setvbuf() since setbuf(stream, buf) is equivalent to
(void)setvbuf(stream, buf, _IOFBF, BUFSIZ) per C11 §7.21.5.5.
---
 .../bugprone/BugproneTidyModule.cpp           |  6 +-
 .../clang-tidy/bugprone/CMakeLists.txt        |  2 +-
 ...k.cpp => UnsafeApiFunctionsCallsCheck.cpp} | 21 +++--
 ...Check.h => UnsafeApiFunctionsCallsCheck.h} | 18 ++--
 .../checks/bugprone/setvbuf-stack-buffer.rst  | 27 ------
 .../bugprone/unsafe-api-functions-calls.rst   | 41 +++++++++
 .../docs/clang-tidy/checks/list.rst           |  2 +-
 .../checkers/bugprone/setvbuf-stack-buffer.c  | 57 ------------
 .../bugprone/unsafe-api-functions-calls.c     | 91 +++++++++++++++++++
 9 files changed, 160 insertions(+), 105 deletions(-)
 rename clang-tools-extra/clang-tidy/bugprone/{SetvbufStackBufferCheck.cpp => UnsafeApiFunctionsCallsCheck.cpp} (74%)
 rename clang-tools-extra/clang-tidy/bugprone/{SetvbufStackBufferCheck.h => UnsafeApiFunctionsCallsCheck.h} (51%)
 delete mode 100644 clang-tools-extra/docs/clang-tidy/checks/bugprone/setvbuf-stack-buffer.rst
 create mode 100644 clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-api-functions-calls.rst
 delete mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/setvbuf-stack-buffer.c
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-api-functions-calls.c

diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
index 1184b85e19605..54e786b011de7 100644
--- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -69,7 +69,6 @@
 #include "RedundantBranchConditionCheck.h"
 #include "ReservedIdentifierCheck.h"
 #include "ReturnConstRefFromParameterCheck.h"
-#include "SetvbufStackBufferCheck.h"
 #include "SharedPtrArrayMismatchCheck.h"
 #include "SignalHandlerCheck.h"
 #include "SignedCharMisuseCheck.h"
@@ -107,6 +106,7 @@
 #include "UnhandledSelfAssignmentCheck.h"
 #include "UnintendedCharOstreamOutputCheck.h"
 #include "UniquePtrArrayMismatchCheck.h"
+#include "UnsafeApiFunctionsCallsCheck.h"
 #include "UnsafeFunctionsCheck.h"
 #include "UnsafeToAllowExceptionsCheck.h"
 #include "UnusedLocalNonTrivialVariableCheck.h"
@@ -242,8 +242,6 @@ class BugproneModule : public ClangTidyModule {
         "bugprone-raw-memory-call-on-non-trivial-type");
     CheckFactories.registerCheck<ReservedIdentifierCheck>(
         "bugprone-reserved-identifier");
-    CheckFactories.registerCheck<SetvbufStackBufferCheck>(
-        "bugprone-setvbuf-stack-buffer");
     CheckFactories.registerCheck<SharedPtrArrayMismatchCheck>(
         "bugprone-shared-ptr-array-mismatch");
     CheckFactories.registerCheck<SignalHandlerCheck>("bugprone-signal-handler");
@@ -311,6 +309,8 @@ class BugproneModule : public ClangTidyModule {
         "bugprone-unhandled-exception-at-new");
     CheckFactories.registerCheck<UniquePtrArrayMismatchCheck>(
         "bugprone-unique-ptr-array-mismatch");
+    CheckFactories.registerCheck<UnsafeApiFunctionsCallsCheck>(
+        "bugprone-unsafe-api-functions-calls");
     CheckFactories.registerCheck<CrtpConstructorAccessibilityCheck>(
         "bugprone-crtp-constructor-accessibility");
     CheckFactories.registerCheck<UnsafeFunctionsCheck>(
diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
index 9b0e9fc0c9da2..1cc4c4830e0c5 100644
--- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
@@ -72,7 +72,6 @@ add_clang_library(clangTidyBugproneModule STATIC
   RedundantBranchConditionCheck.cpp
   ReservedIdentifierCheck.cpp
   ReturnConstRefFromParameterCheck.cpp
-  SetvbufStackBufferCheck.cpp
   SharedPtrArrayMismatchCheck.cpp
   SignalHandlerCheck.cpp
   SignedCharMisuseCheck.cpp
@@ -109,6 +108,7 @@ add_clang_library(clangTidyBugproneModule STATIC
   UnhandledExceptionAtNewCheck.cpp
   UnhandledSelfAssignmentCheck.cpp
   UniquePtrArrayMismatchCheck.cpp
+  UnsafeApiFunctionsCallsCheck.cpp
   UnsafeFunctionsCheck.cpp
   UnsafeToAllowExceptionsCheck.cpp
   UnusedLocalNonTrivialVariableCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/SetvbufStackBufferCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnsafeApiFunctionsCallsCheck.cpp
similarity index 74%
rename from clang-tools-extra/clang-tidy/bugprone/SetvbufStackBufferCheck.cpp
rename to clang-tools-extra/clang-tidy/bugprone/UnsafeApiFunctionsCallsCheck.cpp
index 8952a5a2a0877..5a2d0866bdbe4 100644
--- a/clang-tools-extra/clang-tidy/bugprone/SetvbufStackBufferCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeApiFunctionsCallsCheck.cpp
@@ -6,28 +6,31 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include "SetvbufStackBufferCheck.h"
+#include "UnsafeApiFunctionsCallsCheck.h"
 #include "clang/ASTMatchers/ASTMatchFinder.h"
 
 using namespace clang::ast_matchers;
 
 namespace clang::tidy::bugprone {
 
-void SetvbufStackBufferCheck::registerMatchers(MatchFinder *Finder) {
+void UnsafeApiFunctionsCallsCheck::registerMatchers(MatchFinder *Finder) {
   Finder->addMatcher(
-      callExpr(callee(functionDecl(hasAnyName("setvbuf", "::std::setvbuf"))))
+      callExpr(callee(functionDecl(hasAnyName("setvbuf", "::std::setvbuf",
+                                              "setbuf", "::std::setbuf"))))
           .bind("call"),
       this);
 }
 
-void SetvbufStackBufferCheck::check(const MatchFinder::MatchResult &Result) {
+void UnsafeApiFunctionsCallsCheck::check(
+    const MatchFinder::MatchResult &Result) {
   const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
   if (!Call || Call->getNumArgs() < 2)
     return;
 
   const Expr *BufArg = Call->getArg(1)->IgnoreParenImpCasts();
 
-  // NULL is fine (used for _IONBF).
+  // NULL is fine (used for _IONBF with setvbuf, or to disable buffering with
+  // setbuf).
   if (BufArg->isNullPointerConstant(*Result.Context,
                                     Expr::NPC_ValueDependentIsNotNull))
     return;
@@ -60,10 +63,14 @@ void SetvbufStackBufferCheck::check(const MatchFinder::MatchResult &Result) {
   if (!VD->getType()->isArrayType())
     return;
 
+  // Get the function name for the diagnostic message.
+  const auto *Callee = Call->getDirectCallee();
+  StringRef FuncName = Callee ? Callee->getName() : "setvbuf";
+
   diag(Call->getBeginLoc(),
-       "passing stack-allocated buffer to 'setvbuf'; buffer must outlive the "
+       "passing stack-allocated buffer to '%0'; buffer must outlive the "
        "stream; use a static, global, or dynamically allocated buffer instead")
-      << Call->getArg(1)->getSourceRange();
+      << FuncName << Call->getArg(1)->getSourceRange();
 }
 
 } // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/clang-tidy/bugprone/SetvbufStackBufferCheck.h b/clang-tools-extra/clang-tidy/bugprone/UnsafeApiFunctionsCallsCheck.h
similarity index 51%
rename from clang-tools-extra/clang-tidy/bugprone/SetvbufStackBufferCheck.h
rename to clang-tools-extra/clang-tidy/bugprone/UnsafeApiFunctionsCallsCheck.h
index 4fb97bc06a6d6..b95f084bd63a1 100644
--- a/clang-tools-extra/clang-tidy/bugprone/SetvbufStackBufferCheck.h
+++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeApiFunctionsCallsCheck.h
@@ -6,22 +6,22 @@
 //
 //===----------------------------------------------------------------------===//
 
-#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SETVBUFSTACKBUFFERCHECK_H
-#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SETVBUFSTACKBUFFERCHECK_H
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNSAFEAPIFUNCTIONSCALLSCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNSAFEAPIFUNCTIONSCALLSCHECK_H
 
 #include "../ClangTidyCheck.h"
 
 namespace clang::tidy::bugprone {
 
-/// Warns when setvbuf() is called with a stack-allocated buffer, which leads to
-/// undefined behavior if the buffer's lifetime ends before the stream is closed
-/// or reassigned.
+/// Warns when setvbuf() or setbuf() is called with a stack-allocated buffer,
+/// which leads to undefined behavior if the buffer's lifetime ends before the
+/// stream is closed or reassigned.
 ///
 /// For the user-facing documentation see:
-/// https://clang.llvm.org/extra/clang-tidy/checks/bugprone/setvbuf-stack-buffer.html
-class SetvbufStackBufferCheck : public ClangTidyCheck {
+/// https://clang.llvm.org/extra/clang-tidy/checks/bugprone/unsafe-api-functions-calls.html
+class UnsafeApiFunctionsCallsCheck : public ClangTidyCheck {
 public:
-  SetvbufStackBufferCheck(StringRef Name, ClangTidyContext *Context)
+  UnsafeApiFunctionsCallsCheck(StringRef Name, ClangTidyContext *Context)
       : ClangTidyCheck(Name, Context) {}
   void registerMatchers(ast_matchers::MatchFinder *Finder) override;
   void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
@@ -29,4 +29,4 @@ class SetvbufStackBufferCheck : public ClangTidyCheck {
 
 } // namespace clang::tidy::bugprone
 
-#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SETVBUFSTACKBUFFERCHECK_H
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNSAFEAPIFUNCTIONSCALLSCHECK_H
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/setvbuf-stack-buffer.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/setvbuf-stack-buffer.rst
deleted file mode 100644
index 3b54ce405496d..0000000000000
--- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/setvbuf-stack-buffer.rst
+++ /dev/null
@@ -1,27 +0,0 @@
-.. title:: clang-tidy - bugprone-setvbuf-stack-buffer
-
-bugprone-setvbuf-stack-buffer
-=============================
-
-Warns when ``setvbuf()`` is called with a stack-allocated buffer.
-
-The C standard (C11 §7.21.5.6) requires that the buffer passed to ``setvbuf()``
-must have a lifetime at least as long as the open stream. Passing a local
-(automatic storage duration) buffer leads to undefined behavior when the function
-returns but the stream remains open — the stream will continue to use the
-now-dangling buffer.
-
-.. code-block:: c
-
-   void bad(void) {
-     char buf[BUFSIZ];
-     setvbuf(stdout, buf, _IOFBF, BUFSIZ);  // warning
-     // buf goes out of scope, but stdout keeps using it!
-   }
-
-Safe alternatives:
-
-- ``static`` local buffer: ``static char buf[BUFSIZ];``
-- Global buffer: ``char buf[BUFSIZ];`` at file scope
-- Dynamically allocated: ``char *buf = malloc(BUFSIZ);``
-- Unbuffered: ``setvbuf(stream, NULL, _IONBF, 0);``
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-api-functions-calls.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-api-functions-calls.rst
new file mode 100644
index 0000000000000..1f77979a24606
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-api-functions-calls.rst
@@ -0,0 +1,41 @@
+.. title:: clang-tidy - bugprone-unsafe-api-functions-calls
+
+bugprone-unsafe-api-functions-calls
+====================================
+
+Warns when ``setvbuf()`` or ``setbuf()`` is called with a
+stack-allocated buffer.
+
+The C standard (`C11 §7.21.5.6`_) requires that the buffer passed to
+``setvbuf()`` must have a lifetime at least as long as the open
+stream. Since ``setbuf(stream, buf)`` is defined to be equivalent to
+``(void)setvbuf(stream, buf, _IOFBF, BUFSIZ)`` (`C11 §7.21.5.5`_), the
+same requirement applies.
+
+.. _C11 §7.21.5.6: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1548.pdf
+.. _C11 §7.21.5.5: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1548.pdf
+
+Passing a local (automatic storage duration) buffer leads to undefined behavior
+when the function returns but the stream remains open - the stream will continue
+to use the now-dangling buffer.
+
+.. code-block:: c
+
+   void bad_setvbuf(void) {
+     char buf[BUFSIZ];
+     setvbuf(stdout, buf, _IOFBF, BUFSIZ);  // warning
+     // buf goes out of scope, but stdout keeps using it!
+   }
+
+   void bad_setbuf(void) {
+     char buf[BUFSIZ];
+     setbuf(stdout, buf);  // warning
+     // buf goes out of scope, but stdout keeps using it!
+   }
+
+Safe alternatives:
+
+- ``static`` local buffer: ``static char buf[BUFSIZ];``
+- Global buffer: ``char buf[BUFSIZ];`` at file scope
+- Dynamically allocated: ``char *buf = malloc(BUFSIZ);``
+- Unbuffered: ``setvbuf(stream, NULL, _IONBF, 0);`` or ``setbuf(stream, NULL);``
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index f20c580a25390..ac7134495066d 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -139,7 +139,6 @@ Clang-Tidy Checks
    :doc:`bugprone-redundant-branch-condition <bugprone/redundant-branch-condition>`, "Yes"
    :doc:`bugprone-reserved-identifier <bugprone/reserved-identifier>`, "Yes"
    :doc:`bugprone-return-const-ref-from-parameter <bugprone/return-const-ref-from-parameter>`,
-   :doc:`bugprone-setvbuf-stack-buffer <bugprone/setvbuf-stack-buffer>`,
    :doc:`bugprone-shared-ptr-array-mismatch <bugprone/shared-ptr-array-mismatch>`, "Yes"
    :doc:`bugprone-signal-handler <bugprone/signal-handler>`,
    :doc:`bugprone-signed-char-misuse <bugprone/signed-char-misuse>`,
@@ -177,6 +176,7 @@ Clang-Tidy Checks
    :doc:`bugprone-unhandled-self-assignment <bugprone/unhandled-self-assignment>`,
    :doc:`bugprone-unintended-char-ostream-output <bugprone/unintended-char-ostream-output>`, "Yes"
    :doc:`bugprone-unique-ptr-array-mismatch <bugprone/unique-ptr-array-mismatch>`, "Yes"
+   :doc:`bugprone-unsafe-api-functions-calls <bugprone/unsafe-api-functions-calls>`,
    :doc:`bugprone-unsafe-functions <bugprone/unsafe-functions>`,
    :doc:`bugprone-unsafe-to-allow-exceptions <bugprone/unsafe-to-allow-exceptions>`,
    :doc:`bugprone-unused-local-non-trivial-variable <bugprone/unused-local-non-trivial-variable>`,
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/setvbuf-stack-buffer.c b/clang-tools-extra/test/clang-tidy/checkers/bugprone/setvbuf-stack-buffer.c
deleted file mode 100644
index b96773f7cc2d6..0000000000000
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/setvbuf-stack-buffer.c
+++ /dev/null
@@ -1,57 +0,0 @@
-// RUN: %check_clang_tidy %s bugprone-setvbuf-stack-buffer %t
-
-typedef unsigned long size_t;
-typedef struct FILE FILE;
-extern FILE *stdin;
-
-int setvbuf(FILE *stream, char *buf, int mode, size_t size);
-void *malloc(size_t size);
-void *calloc(size_t count, size_t size);
-
-#define _IOFBF 0
-#define _IOLBF 1
-#define _IONBF 2
-#define BUFSIZ 1024
-
-// Test 1: Stack buffer — should warn.
-void stack_buffer(void) {
-  char buf[BUFSIZ];
-  setvbuf(stdin, buf, _IOFBF, BUFSIZ);
-  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: passing stack-allocated buffer to 'setvbuf'
-}
-
-// Test 2: NULL buffer (unbuffered) — no warning.
-void null_buffer(void) {
-  setvbuf(stdin, (void *)0, _IONBF, 0);
-}
-
-// Test 3: malloc'd buffer — no warning.
-void malloc_buffer(void) {
-  char *buf = (char *)malloc(BUFSIZ);
-  setvbuf(stdin, buf, _IOFBF, BUFSIZ);
-}
-
-// Test 4: calloc'd buffer — no warning.
-void calloc_buffer(void) {
-  char *buf = (char *)calloc(1, BUFSIZ);
-  setvbuf(stdin, buf, _IOFBF, BUFSIZ);
-}
-
-// Test 5: Static buffer — no warning.
-void static_buffer(void) {
-  static char buf[BUFSIZ];
-  setvbuf(stdin, buf, _IOFBF, BUFSIZ);
-}
-
-// Test 6: Global buffer — no warning.
-char global_buf[BUFSIZ];
-void global_buffer(void) {
-  setvbuf(stdin, global_buf, _IOFBF, BUFSIZ);
-}
-
-// Test 7: Stack buffer via &arr[0] — should warn.
-void stack_buffer_addr(void) {
-  char buf[BUFSIZ];
-  setvbuf(stdin, &buf[0], _IOFBF, BUFSIZ);
-  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: passing stack-allocated buffer to 'setvbuf'
-}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-api-functions-calls.c b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-api-functions-calls.c
new file mode 100644
index 0000000000000..49bc6425783c7
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-api-functions-calls.c
@@ -0,0 +1,91 @@
+// RUN: %check_clang_tidy %s bugprone-unsafe-api-functions-calls %t
+
+#include <stdlib.h>
+#include <stdio.h>
+
+// === setvbuf tests ===
+
+// Test 1: Stack buffer - should warn.
+void setvbuf_stack_buffer(void) {
+  char buf[BUFSIZ];
+  setvbuf(stdin, buf, _IOFBF, BUFSIZ);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: passing stack-allocated buffer to 'setvbuf'
+}
+
+// Test 2: NULL buffer (unbuffered) - no warning.
+void setvbuf_null_buffer(void) {
+  setvbuf(stdin, (void *)0, _IONBF, 0);
+  setvbuf(stdin, NULL, _IONBF, 0);
+  setvbuf(stdin, 0, _IONBF, 0);
+}
+
+// Test 3: malloc'd buffer - no warning.
+void setvbuf_malloc_buffer(void) {
+  char *buf = (char *)malloc(BUFSIZ);
+  setvbuf(stdin, buf, _IOFBF, BUFSIZ);
+}
+
+// Test 4: calloc'd buffer - no warning.
+void setvbuf_calloc_buffer(void) {
+  char *buf = (char *)calloc(1, BUFSIZ);
+  setvbuf(stdin, buf, _IOFBF, BUFSIZ);
+}
+
+// Test 5: Static buffer - no warning.
+void setvbuf_static_buffer(void) {
+  static char buf[BUFSIZ];
+  setvbuf(stdin, buf, _IOFBF, BUFSIZ);
+}
+
+// Test 6: Global buffer - no warning.
+char global_buf[BUFSIZ];
+void setvbuf_global_buffer(void) {
+  setvbuf(stdin, global_buf, _IOFBF, BUFSIZ);
+}
+
+// Test 7: Stack buffer via &arr[0] - should warn.
+void setvbuf_stack_buffer_addr(void) {
+  char buf[BUFSIZ];
+  setvbuf(stdin, &buf[0], _IOFBF, BUFSIZ);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: passing stack-allocated buffer to 'setvbuf'
+}
+
+// === setbuf tests ===
+
+// Test 8: Stack buffer with setbuf - should warn.
+void setbuf_stack_buffer(void) {
+  char buf[BUFSIZ];
+  setbuf(stdin, buf);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: passing stack-allocated buffer to 'setbuf'
+}
+
+// Test 9: NULL buffer with setbuf (disable buffering) - no warning.
+void setbuf_null_buffer(void) {
+  setbuf(stdin, (void *)0);
+  setbuf(stdin, 0);
+  setbuf(stdin, NULL);
+}
+
+// Test 10: malloc'd buffer with setbuf - no warning.
+void setbuf_malloc_buffer(void) {
+  char *buf = (char*)malloc(BUFSIZ);
+  setbuf(stdin, buf);
+}
+
+// Test 11: Static buffer with setbuf - no warning.
+void setbuf_static_buffer(void) {
+  static char buf[BUFSIZ];
+  setbuf(stdin, buf);
+}
+
+// Test 12: Global buffer with setbuf - no warning.
+void setbuf_global_buffer(void) {
+  setbuf(stdin, global_buf);
+}
+
+// Test 13: Stack buffer via &arr[0] with setbuf - should warn.
+void setbuf_stack_buffer_addr(void) {
+  char buf[BUFSIZ];
+  setbuf(stdin, &buf[0]);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: passing stack-allocated buffer to 'setbuf'
+}

>From 0d4e904d73e49adb68881d8cfad79c0acadf29a7 Mon Sep 17 00:00:00 2001
From: Mats Kindahl <mats.kindahl at gmail.com>
Date: Mon, 23 Mar 2026 08:23:04 +0100
Subject: [PATCH 3/5] Update
 clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-api-functions-calls.rst

Co-authored-by: EugeneZelenko <eugene.zelenko at gmail.com>
---
 .../clang-tidy/checks/bugprone/unsafe-api-functions-calls.rst   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-api-functions-calls.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-api-functions-calls.rst
index 1f77979a24606..822a3d8a47c8c 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-api-functions-calls.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-api-functions-calls.rst
@@ -1,7 +1,7 @@
 .. title:: clang-tidy - bugprone-unsafe-api-functions-calls
 
 bugprone-unsafe-api-functions-calls
-====================================
+===================================
 
 Warns when ``setvbuf()`` or ``setbuf()`` is called with a
 stack-allocated buffer.

>From 63cf7eab512269b25de8ce00eac472eae4aa7cf9 Mon Sep 17 00:00:00 2001
From: Mats Kindahl <mats at kindahl.net>
Date: Mon, 23 Mar 2026 08:34:20 +0100
Subject: [PATCH 4/5] Handling review comments

---
 .../bugprone/unsafe-api-functions-calls.rst      | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-api-functions-calls.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-api-functions-calls.rst
index 822a3d8a47c8c..810008ed77221 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-api-functions-calls.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-api-functions-calls.rst
@@ -3,8 +3,14 @@
 bugprone-unsafe-api-functions-calls
 ===================================
 
-Warns when ``setvbuf()`` or ``setbuf()`` is called with a
-stack-allocated buffer.
+Finds C standard function calls that are used in an undefined or
+unsafe manner.
+
+Unsafe usage of ``setvbuf()`` or ``setbuf()``
+---------------------------------------------
+
+Enabling this check will warn when ``setvbuf()`` or ``setbuf()`` is
+called with a stack-allocated buffer.
 
 The C standard (`C11 §7.21.5.6`_) requires that the buffer passed to
 ``setvbuf()`` must have a lifetime at least as long as the open
@@ -15,9 +21,9 @@ same requirement applies.
 .. _C11 §7.21.5.6: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1548.pdf
 .. _C11 §7.21.5.5: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1548.pdf
 
-Passing a local (automatic storage duration) buffer leads to undefined behavior
-when the function returns but the stream remains open - the stream will continue
-to use the now-dangling buffer.
+Passing a local (automatic storage duration) buffer leads to undefined
+behavior when the function returns but the stream remains open. After
+the return the stream will continue to use the now-dangling buffer.
 
 .. code-block:: c
 

>From a25866037221bf31712374c2b4191d15258d04ff Mon Sep 17 00:00:00 2001
From: Mats Kindahl <mats at kindahl.net>
Date: Mon, 23 Mar 2026 08:41:57 +0100
Subject: [PATCH 5/5] adding release notes

---
 clang-tools-extra/docs/ReleaseNotes.rst                    | 7 +++++++
 .../checks/bugprone/unsafe-api-functions-calls.rst         | 2 +-
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index f8550e72dcc85..ef93c30c4f567 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -169,6 +169,13 @@ New checks
   Checks for presence or absence of trailing commas in enum definitions and
   initializer lists.
 
+- New :doc:`bugprone-unsafe-api-functions-calls
+  <clang-tidy/checks/bugprone/bugprone-unsafe-api-functions-calls>`
+  check.
+
+  Checks for C standard function calls that are used in an undefined
+  or unsafe manner.
+
 New check aliases
 ^^^^^^^^^^^^^^^^^
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-api-functions-calls.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-api-functions-calls.rst
index 810008ed77221..aff6953fdc7e8 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-api-functions-calls.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-api-functions-calls.rst
@@ -3,7 +3,7 @@
 bugprone-unsafe-api-functions-calls
 ===================================
 
-Finds C standard function calls that are used in an undefined or
+Checks for C standard function calls that are used in an undefined or
 unsafe manner.
 
 Unsafe usage of ``setvbuf()`` or ``setbuf()``



More information about the cfe-commits mailing list