[clang] [-Wunsafe-buffer-usage] Separate flag for format-attributed functions (PR #175749)
Hans Wennborg via cfe-commits
cfe-commits at lists.llvm.org
Mon Jan 19 01:27:08 PST 2026
https://github.com/zmodem updated https://github.com/llvm/llvm-project/pull/175749
>From f19cd75675df48320fab4a9131ac535eb5f9de93 Mon Sep 17 00:00:00 2001
From: Hans Wennborg <hans at chromium.org>
Date: Tue, 13 Jan 2026 11:21:44 +0100
Subject: [PATCH 1/3] [-Wunsafe-buffer-usage] Separate flag for
format-attributed functions
PR #173096 extended -Wunsafe-buffer-usage-in-libc-call to apply to all
functions with the 'format' attribute.
This change moves those warnings behind a separate
-Wunsafe-buffer-usage-format-attr flag (implicitly enabled by
-Wunsafe-buffer-usage), allowing projects to decide whether they
want to opt in to this or not.
---
.../Analysis/Analyses/UnsafeBufferUsage.h | 3 +-
clang/include/clang/Basic/DiagnosticGroups.td | 3 +-
.../clang/Basic/DiagnosticSemaKinds.td | 4 +++
clang/lib/Analysis/UnsafeBufferUsage.cpp | 12 +++++--
clang/lib/Sema/AnalysisBasedWarnings.cpp | 7 +++-
...arn-unsafe-buffer-usage-libc-functions.cpp | 34 +++++++++----------
6 files changed, 40 insertions(+), 23 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h b/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h
index ea41eb3becfcf..65dbd3b2ab9d1 100644
--- a/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h
+++ b/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h
@@ -117,8 +117,9 @@ class UnsafeBufferUsageHandler {
/// safe pattern;
/// is 3 if string arguments do not guarantee null-termination
/// is 4 if the callee takes va_list
+ /// has bit 3 (0x8) set if the callee is a function with the format attribute
/// \param UnsafeArg one of the actual arguments that is unsafe, non-null
- /// only when `2 <= PrintfInfo <= 3`
+ /// only when `2 <= PrintfInfo <= 3 (ignoring the "format attribute" bit)`
virtual void handleUnsafeLibcCall(const CallExpr *Call, unsigned PrintfInfo,
ASTContext &Ctx,
const Expr *UnsafeArg = nullptr) = 0;
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 3764475fbd3df..08ad2fc4b954d 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1773,8 +1773,9 @@ def ReadOnlyPlacementChecks : DiagGroup<"read-only-types">;
// Warnings and fixes to support the "safe buffers" programming model.
def UnsafeBufferUsageInContainer : DiagGroup<"unsafe-buffer-usage-in-container">;
def UnsafeBufferUsageInLibcCall : DiagGroup<"unsafe-buffer-usage-in-libc-call">;
+def UnsafeBufferUsageFormatAttr : DiagGroup<"unsafe-buffer-usage-format-attr">;
def UnsafeBufferUsageInUniquePtrArrayAccess : DiagGroup<"unsafe-buffer-usage-in-unique-ptr-array-access">;
-def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInContainer, UnsafeBufferUsageInLibcCall, UnsafeBufferUsageInUniquePtrArrayAccess]>;
+def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInContainer, UnsafeBufferUsageInLibcCall, UnsafeBufferUsageFormatAttr, UnsafeBufferUsageInUniquePtrArrayAccess]>;
// Warnings and notes InstallAPI verification.
def InstallAPIViolation : DiagGroup<"installapi-violation">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 5cbbc7d130c99..967d54bc44979 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -13450,6 +13450,10 @@ def warn_unsafe_buffer_operation : Warning<
def warn_unsafe_buffer_libc_call : Warning<
"function %0 is unsafe">,
InGroup<UnsafeBufferUsageInLibcCall>, DefaultIgnore;
+def warn_unsafe_buffer_format_attr_call : Warning<
+ "formatting function %0 is unsafe">,
+ InGroup<UnsafeBufferUsageFormatAttr>, DefaultIgnore;
+
def note_unsafe_buffer_printf_call : Note<
"%select{|change to 'snprintf' for explicit bounds checking | buffer pointer and size may not match"
"|string argument is not guaranteed to be null-terminated"
diff --git a/clang/lib/Analysis/UnsafeBufferUsage.cpp b/clang/lib/Analysis/UnsafeBufferUsage.cpp
index c7a5988445aa1..12af536053581 100644
--- a/clang/lib/Analysis/UnsafeBufferUsage.cpp
+++ b/clang/lib/Analysis/UnsafeBufferUsage.cpp
@@ -2098,6 +2098,7 @@ class UnsafeLibcFunctionCallGadget : public WarningGadget {
// guarantee null-termination
VA_LIST = 4, // one of the `-printf`s function that take va_list, which is
// considered unsafe as it is not compile-time check
+ FORMAT_ATTR = 8, // flag: the callee has the format attribute
} WarnedFunKind = OTHERS;
UnsafeLibcFunctionCallGadget(const MatchResult &Result)
@@ -2282,11 +2283,16 @@ class UnsafeFormatAttributedFunctionCallGadget : public WarningGadget {
ASTContext &Ctx) const override {
if (UnsafeArg)
Handler.handleUnsafeLibcCall(
- Call, UnsafeLibcFunctionCallGadget::UnsafeKind::STRING, Ctx,
- UnsafeArg);
+ Call,
+ UnsafeLibcFunctionCallGadget::UnsafeKind::STRING |
+ UnsafeLibcFunctionCallGadget::UnsafeKind::FORMAT_ATTR,
+ Ctx, UnsafeArg);
else
Handler.handleUnsafeLibcCall(
- Call, UnsafeLibcFunctionCallGadget::UnsafeKind::OTHERS, Ctx);
+ Call,
+ UnsafeLibcFunctionCallGadget::UnsafeKind::OTHERS |
+ UnsafeLibcFunctionCallGadget::UnsafeKind::FORMAT_ATTR,
+ Ctx);
}
DeclUseList getClaimedVarUseSites() const override { return {}; }
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 7b08648080710..df7488c3059e7 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2523,7 +2523,12 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
void handleUnsafeLibcCall(const CallExpr *Call, unsigned PrintfInfo,
ASTContext &Ctx,
const Expr *UnsafeArg = nullptr) override {
- S.Diag(Call->getBeginLoc(), diag::warn_unsafe_buffer_libc_call)
+ unsigned DiagID = diag::warn_unsafe_buffer_libc_call;
+ if (PrintfInfo & 0x8) {
+ DiagID = diag::warn_unsafe_buffer_format_attr_call;
+ PrintfInfo ^= 0x8;
+ }
+ S.Diag(Call->getBeginLoc(), DiagID)
<< Call->getDirectCallee() // We've checked there is a direct callee
<< Call->getSourceRange();
if (PrintfInfo > 0) {
diff --git a/clang/test/SemaCXX/warn-unsafe-buffer-usage-libc-functions.cpp b/clang/test/SemaCXX/warn-unsafe-buffer-usage-libc-functions.cpp
index fe9bc7c809c96..7d6b118154bec 100644
--- a/clang/test/SemaCXX/warn-unsafe-buffer-usage-libc-functions.cpp
+++ b/clang/test/SemaCXX/warn-unsafe-buffer-usage-libc-functions.cpp
@@ -2,10 +2,10 @@
// RUN: -verify %s
// RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage -Wno-gcc-compat\
// RUN: -verify %s -x objective-c++
-// RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage-in-libc-call -Wno-gcc-compat\
-// RUN: -verify %s
-// RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage-in-libc-call -Wno-gcc-compat\
-// RUN: -verify %s -DTEST_STD_NS
+// RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage-in-libc-call \
+// RUN: -Wunsafe-buffer-usage-format-attr -Wno-gcc-compat -verify %s
+// RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage-in-libc-call \
+// RUN: -Wunsafe-buffer-usage-format-attr -Wno-gcc-compat -verify %s -DTEST_STD_NS
typedef struct {} FILE;
typedef unsigned int size_t;
@@ -295,7 +295,7 @@ struct FormatAttrTestMember2 {
void test_format_attr(char * Str, std::string StdStr) {
myprintf("hello", Str);
myprintf("hello %s", StdStr.c_str());
- myprintf("hello %s", Str); // expected-warning{{function 'myprintf' is unsafe}} \
+ myprintf("hello %s", Str); // expected-warning{{formatting function 'myprintf' is unsafe}} \
expected-note{{string argument is not guaranteed to be null-terminated}}
extern int errno;
@@ -304,12 +304,12 @@ void test_format_attr(char * Str, std::string StdStr) {
myprintf_2("hello", 0, Str);
myprintf_2("hello %s", 0, StdStr.c_str());
- myprintf_2("hello %s", 0, Str); // expected-warning{{function 'myprintf_2' is unsafe}} \
+ myprintf_2("hello %s", 0, Str); // expected-warning{{formatting function 'myprintf_2' is unsafe}} \
expected-note{{string argument is not guaranteed to be null-terminated}}
myprintf_3("irrelevant", "hello", 0, Str);
myprintf_3("irrelevant", "hello %s", 0, StdStr.c_str());
- myprintf_3("irrelevant", "hello %s", 0, Str); // expected-warning{{function 'myprintf_3' is unsafe}} \
+ myprintf_3("irrelevant", "hello %s", 0, Str); // expected-warning{{formatting function 'myprintf_3' is unsafe}} \
expected-note{{string argument is not guaranteed to be null-terminated}}
myscanf("hello %s");
myscanf("hello %s", Str); // expected-warning{{function 'myscanf' is unsafe}}
@@ -325,30 +325,30 @@ void test_format_attr(char * Str, std::string StdStr) {
Obj.myprintf("hello", Str);
Obj.myprintf("hello %s", StdStr.c_str());
- Obj.myprintf("hello %s", Str); // expected-warning{{function 'myprintf' is unsafe}} \
+ Obj.myprintf("hello %s", Str); // expected-warning{{formatting function 'myprintf' is unsafe}} \
expected-note{{string argument is not guaranteed to be null-terminated}}
Obj.myprintf_2("hello", 0, Str);
Obj.myprintf_2("hello %s", 0, StdStr.c_str());
- Obj.myprintf_2("hello %s", 0, Str); // expected-warning{{function 'myprintf_2' is unsafe}} \
+ Obj.myprintf_2("hello %s", 0, Str); // expected-warning{{formatting function 'myprintf_2' is unsafe}} \
expected-note{{string argument is not guaranteed to be null-terminated}}
Obj.myprintf_3("irrelevant", "hello", 0, Str);
Obj.myprintf_3("irrelevant", "hello %s", 0, StdStr.c_str());
- Obj.myprintf_3("irrelevant", "hello %s", 0, Str); // expected-warning{{function 'myprintf_3' is unsafe}} \
+ Obj.myprintf_3("irrelevant", "hello %s", 0, Str); // expected-warning{{formatting function 'myprintf_3' is unsafe}} \
expected-note{{string argument is not guaranteed to be null-terminated}}
Obj.myscanf("hello %s");
- Obj.myscanf("hello %s", Str); // expected-warning{{function 'myscanf' is unsafe}}
+ Obj.myscanf("hello %s", Str); // expected-warning{{formatting function 'myscanf' is unsafe}}
- Obj.myscanf("hello %d", &X); // expected-warning{{function 'myscanf' is unsafe}}
+ Obj.myscanf("hello %d", &X); // expected-warning{{formatting function 'myscanf' is unsafe}}
- Obj.myprintf_default("irrelevant"); // expected-warning{{function 'myprintf_default' is unsafe}}
+ Obj.myprintf_default("irrelevant"); // expected-warning{{formatting function 'myprintf_default' is unsafe}}
// expected-note@*{{string argument is not guaranteed to be null-terminated}}
Obj("hello", Str);
Obj("hello %s", StdStr.c_str());
- Obj("hello %s", Str); // expected-warning{{function 'operator()' is unsafe}} \
+ Obj("hello %s", Str); // expected-warning{{formatting function 'operator()' is unsafe}} \
expected-note{{string argument is not guaranteed to be null-terminated}}
Obj["hello"];
Obj["hello %s"];
@@ -357,7 +357,7 @@ void test_format_attr(char * Str, std::string StdStr) {
Obj2("hello", Str);
Obj2("hello %s", StdStr.c_str());
- Obj2("hello %s", Str); // expected-warning{{function 'operator()' is unsafe}} \
+ Obj2("hello %s", Str); // expected-warning{{formatting function 'operator()' is unsafe}} \
expected-note{{string argument is not guaranteed to be null-terminated}}
}
@@ -367,9 +367,9 @@ void myprintf_arg_idx_oob(const char *) __attribute__((__format__ (__printf__, 1
void test_format_attr_invalid_arg_idx(char * Str, std::string StdStr) {
myprintf_arg_idx_oob("hello");
- myprintf_arg_idx_oob(Str); // expected-warning{{function 'myprintf_arg_idx_oob' is unsafe}} expected-note{{string argument is not guaranteed to be null-terminated}}
+ myprintf_arg_idx_oob(Str); // expected-warning{{formatting function 'myprintf_arg_idx_oob' is unsafe}} expected-note{{string argument is not guaranteed to be null-terminated}}
myprintf_arg_idx_oob(StdStr.c_str());
myprintf("hello");
- myprintf(Str); // expected-warning{{function 'myprintf' is unsafe}} expected-note{{string argument is not guaranteed to be null-terminated}}
+ myprintf(Str); // expected-warning{{formatting function 'myprintf' is unsafe}} expected-note{{string argument is not guaranteed to be null-terminated}}
myprintf(StdStr.c_str());
}
>From 5cec0940987ff2df1a51acddc56534f9569e4492 Mon Sep 17 00:00:00 2001
From: Hans Wennborg <hans at chromium.org>
Date: Mon, 19 Jan 2026 10:16:58 +0100
Subject: [PATCH 2/3] unsafe-buffer-usage-in-format-attr-call
---
clang/include/clang/Basic/DiagnosticGroups.td | 4 ++--
clang/include/clang/Basic/DiagnosticSemaKinds.td | 2 +-
.../SemaCXX/warn-unsafe-buffer-usage-libc-functions.cpp | 6 ++++--
3 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 08ad2fc4b954d..2890a0f27e90d 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1773,9 +1773,9 @@ def ReadOnlyPlacementChecks : DiagGroup<"read-only-types">;
// Warnings and fixes to support the "safe buffers" programming model.
def UnsafeBufferUsageInContainer : DiagGroup<"unsafe-buffer-usage-in-container">;
def UnsafeBufferUsageInLibcCall : DiagGroup<"unsafe-buffer-usage-in-libc-call">;
-def UnsafeBufferUsageFormatAttr : DiagGroup<"unsafe-buffer-usage-format-attr">;
+def UnsafeBufferUsageInFormatAttrCall : DiagGroup<"unsafe-buffer-usage-in-format-attr-call">;
def UnsafeBufferUsageInUniquePtrArrayAccess : DiagGroup<"unsafe-buffer-usage-in-unique-ptr-array-access">;
-def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInContainer, UnsafeBufferUsageInLibcCall, UnsafeBufferUsageFormatAttr, UnsafeBufferUsageInUniquePtrArrayAccess]>;
+def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInContainer, UnsafeBufferUsageInLibcCall, UnsafeBufferUsageInFormatAttrCall, UnsafeBufferUsageInUniquePtrArrayAccess]>;
// Warnings and notes InstallAPI verification.
def InstallAPIViolation : DiagGroup<"installapi-violation">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 967d54bc44979..43315a6f7b7d9 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -13452,7 +13452,7 @@ def warn_unsafe_buffer_libc_call : Warning<
InGroup<UnsafeBufferUsageInLibcCall>, DefaultIgnore;
def warn_unsafe_buffer_format_attr_call : Warning<
"formatting function %0 is unsafe">,
- InGroup<UnsafeBufferUsageFormatAttr>, DefaultIgnore;
+ InGroup<UnsafeBufferUsageInFormatAttrCall>, DefaultIgnore;
def note_unsafe_buffer_printf_call : Note<
"%select{|change to 'snprintf' for explicit bounds checking | buffer pointer and size may not match"
diff --git a/clang/test/SemaCXX/warn-unsafe-buffer-usage-libc-functions.cpp b/clang/test/SemaCXX/warn-unsafe-buffer-usage-libc-functions.cpp
index 7d6b118154bec..8a8d3bd77711b 100644
--- a/clang/test/SemaCXX/warn-unsafe-buffer-usage-libc-functions.cpp
+++ b/clang/test/SemaCXX/warn-unsafe-buffer-usage-libc-functions.cpp
@@ -3,9 +3,11 @@
// RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage -Wno-gcc-compat\
// RUN: -verify %s -x objective-c++
// RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage-in-libc-call \
-// RUN: -Wunsafe-buffer-usage-format-attr -Wno-gcc-compat -verify %s
+// RUN: -Wunsafe-buffer-usage-in-format-attr-call -Wno-gcc-compat \
+// RUN: -verify %s
// RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage-in-libc-call \
-// RUN: -Wunsafe-buffer-usage-format-attr -Wno-gcc-compat -verify %s -DTEST_STD_NS
+// RUN: -Wunsafe-buffer-usage-in-format-attr-call -Wno-gcc-compat \
+// RUN: -verify %s -DTEST_STD_NS
typedef struct {} FILE;
typedef unsigned int size_t;
>From 2d86922f86caabc6ae7594bbae98740d36f31924 Mon Sep 17 00:00:00 2001
From: Hans Wennborg <hans at chromium.org>
Date: Mon, 19 Jan 2026 10:26:24 +0100
Subject: [PATCH 3/3] comment about the 0x8
---
clang/lib/Sema/AnalysisBasedWarnings.cpp | 3 +++
1 file changed, 3 insertions(+)
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index df7488c3059e7..4d163ed281c12 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2525,6 +2525,9 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
const Expr *UnsafeArg = nullptr) override {
unsigned DiagID = diag::warn_unsafe_buffer_libc_call;
if (PrintfInfo & 0x8) {
+ // The callee is a function with the format attribute. See the
+ // documentation of PrintfInfo in UnsafeBufferUsageHandler, and
+ // UnsafeLibcFunctionCallGadget::UnsafeKind.
DiagID = diag::warn_unsafe_buffer_format_attr_call;
PrintfInfo ^= 0x8;
}
More information about the cfe-commits
mailing list