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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> --- .../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 <[email protected]> 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 <[email protected]> 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()`` _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
