Author: Venkatesh Srinivasan
Date: 2026-10-15T17:58:32Z
New Revision: 9b3b3d45e35b9c7f18ea293e6ea6ca6106ec915a

URL: 
https://github.com/llvm/llvm-project/commit/9b3b3d45e35b9c7f18ea293e6ea6ca6106ec915a
DIFF: 
https://github.com/llvm/llvm-project/commit/9b3b3d45e35b9c7f18ea293e6ea6ca6106ec915a.diff

LOG: [Clang][Sema] Add fortify warnings for vsprintf (#230672)

Extend the `sprintf` `-Wfortify-source` format overflow check in 
`Sema::checkFortifiedBuiltinMemoryFunction` to also cover `vsprintf`, 
`__builtin_vsprintf`, `__builtin___vsprintf_chk`, and `__builtin_sprintf`.

Also translate the format string argument index via `Checker.TranslateIndex`, 
fixing a latent bug when `sprintf` or `__builtin___sprintf_chk` is wrapped with 
`__attribute__((diagnose_as_builtin(...)))`.

Part of #142230

Assisted-by: Gemini

Added: 
    

Modified: 
    clang/docs/ReleaseNotes.md
    clang/lib/Sema/SemaChecking.cpp
    clang/test/Sema/attr-diagnose-as-builtin.c
    clang/test/Sema/warn-fortify-source.c

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index ed30ac6566d55..9e5d92cae727c 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -415,6 +415,10 @@ features cannot lower the translation-unit ABI level;
 - `-Wfortify-source` now diagnoses when `poll`, `ppoll`, or `ppoll64` is called
   with a descriptor count whose total size exceeds the `fds` array size.
 
+- `-Wfortify-source` now diagnoses when `vsprintf`, `__builtin_vsprintf`,
+  `__builtin___vsprintf_chk`, or `__builtin_sprintf` is called with a format
+  string whose output exceeds the destination buffer size.
+
 - The `cannot overload a member function` diagnostic now describes the previous
   declaration first, matching the order in which the declarations appear in the
   source. (#GH219803)

diff  --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 86f7654f42ff2..51506c62d4e35 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1406,9 +1406,18 @@ void 
Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
   }
 
   case Builtin::BIsprintf:
-  case Builtin::BI__builtin___sprintf_chk: {
-    size_t FormatIndex = BuiltinID == Builtin::BIsprintf ? 1 : 3;
-    auto *FormatExpr = TheCall->getArg(FormatIndex)->IgnoreParenImpCasts();
+  case Builtin::BI__builtin_sprintf:
+  case Builtin::BI__builtin___sprintf_chk:
+  case Builtin::BIvsprintf:
+  case Builtin::BI__builtin_vsprintf:
+  case Builtin::BI__builtin___vsprintf_chk: {
+    bool IsChk = BuiltinID == Builtin::BI__builtin___sprintf_chk ||
+                 BuiltinID == Builtin::BI__builtin___vsprintf_chk;
+    std::optional<unsigned> FormatIndex = Checker.TranslateIndex(IsChk ? 3 : 
1);
+    if (!FormatIndex)
+      return;
+    const auto *FormatExpr =
+        TheCall->getArg(*FormatIndex)->IgnoreParenImpCasts();
 
     StringRef FormatStrRef;
     size_t StrLen;
@@ -1423,7 +1432,7 @@ void 
Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
                      : diag::warn_format_overflow_non_kprintf;
         AccessSize = llvm::APSInt::getUnsigned(H.getSizeLowerBound())
                          .extOrTrunc(SizeTypeWidth);
-        if (BuiltinID == Builtin::BI__builtin___sprintf_chk) {
+        if (IsChk) {
           BufferSize = Checker.ComputeExplicitObjectSizeArgument(2);
         } else {
           BufferSize = Checker.ComputeSizeArgument(0);

diff  --git a/clang/test/Sema/attr-diagnose-as-builtin.c 
b/clang/test/Sema/attr-diagnose-as-builtin.c
index 10962de1a3f0d..3634b1b251893 100644
--- a/clang/test/Sema/attr-diagnose-as-builtin.c
+++ b/clang/test/Sema/attr-diagnose-as-builtin.c
@@ -83,6 +83,15 @@ void warn_mysccanf(void) {
   mysscanf("", "%10s", buf); // expected-warning{{'sscanf' may overflow; 
destination buffer in argument 3 has size 10, but the corresponding specifier 
may require size 11}}
 }
 
+int my_vsprintf(const char *format, char *str, __builtin_va_list ap) 
__attribute__((diagnose_as_builtin(__builtin_vsprintf, 2, 1, 3)));
+
+void warn_my_vsprintf(void) {
+  char buf[6];
+  __builtin_va_list list;
+  my_vsprintf("hello", buf, list);
+  my_vsprintf("hello!", buf, list); // expected-warning{{'vsprintf' will 
always overflow; destination buffer has size 6, but format string expands to at 
least 7}}
+}
+
 #ifdef __cplusplus
 
 template <class T>

diff  --git a/clang/test/Sema/warn-fortify-source.c 
b/clang/test/Sema/warn-fortify-source.c
index 73a10070008f4..3e61e73268206 100644
--- a/clang/test/Sema/warn-fortify-source.c
+++ b/clang/test/Sema/warn-fortify-source.c
@@ -27,9 +27,12 @@ extern "C" {
 #endif
 
 extern int sprintf(char *str, const char *format, ...);
+extern int vsprintf(char *str, const char *format, __builtin_va_list ap);
 
 #if defined(USE_BUILTINS)
 #define memcpy(x,y,z) __builtin_memcpy(x,y,z)
+#define sprintf(x, ...) __builtin_sprintf(x, __VA_ARGS__)
+#define vsprintf(x, y, z) __builtin_vsprintf(x, y, z)
 // Also test the Windows winsock2.h signature where len is a signed int.
 int recv(int, char *, int, int);
 int recvfrom(int, char *, int, int, struct sockaddr *, int *);
@@ -299,6 +302,33 @@ void call_sprintf(void) {
   sprintf(buf, "5%.1e", 9.f); // expected-warning {{'sprintf' will always 
overflow; destination buffer has size 6, but format string expands to at least 
8}}
 }
 
+void call_vsprintf_chk(char *buf) {
+  __builtin_va_list list;
+  __builtin___vsprintf_chk(buf, 1, 6, "hell\n", list);
+  __builtin___vsprintf_chk(buf, 1, 5, "hell\n", list); // expected-warning 
{{'vsprintf' will always overflow; destination buffer has size 5, but format 
string expands to at least 6}}
+  __builtin___vsprintf_chk(buf, 1, 6, "hell\0 boy", list); // expected-warning 
{{format string contains '\0' within the string body}}
+  __builtin___vsprintf_chk(buf, 1, 2, "hell\0 boy", list); // expected-warning 
{{format string contains '\0' within the string body}} \
+                                                           // expected-warning 
{{'vsprintf' will always overflow; destination buffer has size 2, but format 
string expands to at least 5}}
+  __builtin___vsprintf_chk(buf, 1, 2, "%d", list);
+  __builtin___vsprintf_chk(buf, 1, 1, "%d", list); // expected-warning 
{{'vsprintf' will always overflow; destination buffer has size 1, but format 
string expands to at least 2}}
+  __builtin___vsprintf_chk(buf, 1, 9, "%f", list);
+  __builtin___vsprintf_chk(buf, 1, 8, "%f", list); // expected-warning 
{{'vsprintf' will always overflow; destination buffer has size 8, but format 
string expands to at least 9}}
+}
+
+void call_vsprintf(void) {
+  char buf[6];
+  __builtin_va_list list;
+  vsprintf(buf, "hell\0 boy", list); // expected-warning {{format string 
contains '\0' within the string body}}
+  vsprintf(buf, "hello b\0y", list); // expected-warning {{format string 
contains '\0' within the string body}} \
+                                     // expected-warning {{'vsprintf' will 
always overflow; destination buffer has size 6, but format string expands to at 
least 8}}
+  vsprintf(buf, "hello", list);
+  vsprintf(buf, "hello!", list); // expected-warning {{'vsprintf' will always 
overflow; destination buffer has size 6, but format string expands to at least 
7}}
+  vsprintf(buf, "1234%d", list);
+  vsprintf(buf, "12345%d", list); // expected-warning {{'vsprintf' will always 
overflow; destination buffer has size 6, but format string expands to at least 
7}}
+  vsprintf(buf, "%.3f", list);
+  vsprintf(buf, "5%.3f", list); // expected-warning {{'vsprintf' will always 
overflow; destination buffer has size 6, but format string expands to at least 
7}}
+}
+
 void call_umask(mode_t runtime_mode) {
   umask(0);
   umask(022);


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

Reply via email to