Author: Hans Wennborg
Date: 2026-01-19T10:11:34Z
New Revision: 3d90b7a2d7333ead420b18baece183249404329a

URL: 
https://github.com/llvm/llvm-project/commit/3d90b7a2d7333ead420b18baece183249404329a
DIFF: 
https://github.com/llvm/llvm-project/commit/3d90b7a2d7333ead420b18baece183249404329a.diff

LOG: [-Wunsafe-buffer-usage] Separate flag for format-attributed functions 
(#175749)

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-in-format-attr-call flag (implicitly enabled by
-Wunsafe-buffer-usage), allowing projects to decide whether they want to
opt in to this or not.

Added: 
    

Modified: 
    clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h
    clang/include/clang/Basic/DiagnosticGroups.td
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/lib/Analysis/UnsafeBufferUsage.cpp
    clang/lib/Sema/AnalysisBasedWarnings.cpp
    clang/test/SemaCXX/warn-unsafe-buffer-usage-libc-functions.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h 
b/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h
index 462dabcc41da7..876682ad779d4 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 f4717505e1bdd..de1d1e13ea712 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1775,7 +1775,8 @@ def UnsafeBufferUsageInContainer : 
DiagGroup<"unsafe-buffer-usage-in-container">
 def UnsafeBufferUsageInLibcCall : 
DiagGroup<"unsafe-buffer-usage-in-libc-call">;
 def UnsafeBufferUsageInUniquePtrArrayAccess : 
DiagGroup<"unsafe-buffer-usage-in-unique-ptr-array-access">;
 def UnsafeBufferUsageInStaticSizedArray : 
DiagGroup<"unsafe-buffer-usage-in-static-sized-array">;
-def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", 
[UnsafeBufferUsageInContainer, UnsafeBufferUsageInLibcCall, 
UnsafeBufferUsageInUniquePtrArrayAccess, UnsafeBufferUsageInStaticSizedArray]>;
+def UnsafeBufferUsageInFormatAttrCall : 
DiagGroup<"unsafe-buffer-usage-in-format-attr-call">;
+def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", 
[UnsafeBufferUsageInContainer, UnsafeBufferUsageInLibcCall, 
UnsafeBufferUsageInUniquePtrArrayAccess, UnsafeBufferUsageInStaticSizedArray, 
UnsafeBufferUsageInFormatAttrCall]>;
 
 // 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 1ab3f537d36a3..44541a4c68197 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -13454,6 +13454,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<UnsafeBufferUsageInFormatAttrCall>, 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 2abc3d7a3963f..3f5704458d59e 100644
--- a/clang/lib/Analysis/UnsafeBufferUsage.cpp
+++ b/clang/lib/Analysis/UnsafeBufferUsage.cpp
@@ -2109,6 +2109,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)
@@ -2293,11 +2294,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 d297d9e80cf2f..ed743bf192c10 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2527,7 +2527,15 @@ 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) {
+      // 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;
+    }
+    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..8a8d3bd77711b 100644
--- a/clang/test/SemaCXX/warn-unsafe-buffer-usage-libc-functions.cpp
+++ b/clang/test/SemaCXX/warn-unsafe-buffer-usage-libc-functions.cpp
@@ -2,9 +2,11 @@
 // 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: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage-in-libc-call \
+// 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 
-Wno-gcc-compat\
+// RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage-in-libc-call \
+// RUN:            -Wunsafe-buffer-usage-in-format-attr-call -Wno-gcc-compat \
 // RUN:            -verify %s -DTEST_STD_NS
 
 typedef struct {} FILE;
@@ -295,7 +297,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 +306,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 +327,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 +359,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 +369,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());
 }


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to