[clang] [clang][-Wunsafe-buffer-usage] Add per-family subgroups of -Wunsafe-buffer-usage-in-libc-call (PR #233604)
Ilyas Mallah via cfe-commits
cfe-commits at lists.llvm.org
Mon Oct 19 15:02:24 PDT 2026
https://github.com/ilyas-mallah created https://github.com/llvm/llvm-project/pull/233604
Depends on #232898. The first commit is from that PR, so only the second one is new here.
#127849 asks to turn off `strlen` while keeping `memcpy`, which isn't possible while all unsafe libc calls share one diagnostic. Clang can't create warning groups at run time, and one group per function would be about 70 diagnostics. So each family that `PredefinedNames` already lists in its comments gets a subgroup of `-Wunsafe-buffer-usage-in-libc-call`: numeric-conversion, string-manipulation, string-examination, memory, io, printf and scanf.
The parent flag, `-Werror=` included, still covers all of them, and the warning text doesn't change. `strerror_s`, `strerror_r`, `bsearch` and `qsort` have no family and stay under the parent only. `bcopy` and `bzero` moved from "others" to memory. A call whose family is off is dropped in the reporter, so a libc `printf` never moves to `-Wunsafe-buffer-usage-in-format-attr-call`.
Fixes #127849.
AI usage: Claude Code helped with the test runs and test scaffolding. I traced the issue, made the fix, and validated every change.
>From ddf9544580a546e7ab56df206a9e154817229783 Mon Sep 17 00:00:00 2001
From: Ilyas Mallah <github at ilyasm.dev>
Date: Thu, 15 Oct 2026 00:00:13 +0200
Subject: [PATCH 1/2] [clang][-Wunsafe-buffer-usage] Keep format-attr-call
warnings independent of libc-call
---
clang/docs/ReleaseNotes.md | 5 +++
.../Analysis/Analyses/UnsafeBufferUsage.h | 5 +++
clang/lib/Analysis/UnsafeBufferUsage.cpp | 42 +++++++++++++++----
clang/lib/Sema/AnalysisBasedWarnings.cpp | 10 ++++-
...nsafe-buffer-usage-in-format-attr-call.cpp | 36 ++++++++++++++++
5 files changed, 89 insertions(+), 9 deletions(-)
create mode 100644 clang/test/SemaCXX/warn-unsafe-buffer-usage-in-format-attr-call.cpp
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index aba1dba5854fea..43fff181491c44 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -679,6 +679,11 @@ features cannot lower the translation-unit ABI level;
`-Wunsafe-buffer-usage` to control warnings on `main`'s `argv` parameter,
allowing users to suppress them with `-Wno-unsafe-buffer-usage-main-argv`.
+- `-Wno-unsafe-buffer-usage-in-libc-call` no longer turns off
+ `-Wunsafe-buffer-usage-in-format-attr-call`, and
+ `-Wunsafe-buffer-usage-in-format-attr-call` now works when it is the only
+ Safe Buffers warning enabled.
+
### Improvements to Clang's time-trace
### Improvements to Coverage Mapping
diff --git a/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h b/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h
index 05a580bed5c39a..032ccca0297ace 100644
--- a/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h
+++ b/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h
@@ -183,6 +183,11 @@ class UnsafeBufferUsageHandler {
virtual bool
ignoreUnsafeBufferInLibcCall(const SourceLocation &Loc) const = 0;
+ /// \return true iff unsafe calls to functions with the format attribute
+ /// should NOT be reported at `Loc`
+ virtual bool
+ ignoreUnsafeBufferInFormatAttrCall(const SourceLocation &Loc) const = 0;
+
/// \return true iff array subscript accesses on fixed size arrays should NOT
/// be reported at `Loc`
virtual bool
diff --git a/clang/lib/Analysis/UnsafeBufferUsage.cpp b/clang/lib/Analysis/UnsafeBufferUsage.cpp
index 09f610358ca65e..e60a46c5aae7ea 100644
--- a/clang/lib/Analysis/UnsafeBufferUsage.cpp
+++ b/clang/lib/Analysis/UnsafeBufferUsage.cpp
@@ -299,6 +299,14 @@ static bool ignoreUnsafeLibcCall(const ASTContext &Ctx, const Stmt &Node,
return true; /* Only warn about libc calls for C++ */
}
+static bool
+ignoreUnsafeFormatAttrCall(const ASTContext &Ctx, const Stmt &Node,
+ const UnsafeBufferUsageHandler *Handler) {
+ if (Ctx.getLangOpts().CPlusPlus)
+ return Handler->ignoreUnsafeBufferInFormatAttrCall(Node.getBeginLoc());
+ return true; /* Only warn about format attribute calls for C++ */
+}
+
// Finds any expression 'e' such that `OnResult`
// matches 'e' and 'e' is in an Unspecified Lvalue Context.
static void findStmtsInUnspecifiedLvalueContext(
@@ -1433,6 +1441,23 @@ static bool hasUnsafeSnprintfBuffer(const CallExpr &Node, ASTContext &Ctx) {
return !isPtrBufferSafe(Buf, Size, Ctx);
}
+
+// A libc function must either be in the std:: namespace or a global function
+// that is not in any namespace.
+static bool isInLibcScope(const FunctionDecl &FD) {
+ const bool IsGlobalAndNotInAnyNamespace =
+ FD.isGlobal() && !FD.getEnclosingNamespaceContext()->isNamespace();
+
+ return FD.isInStdNamespace() || IsGlobalAndNotInAnyNamespace;
+}
+
+// Matches a function that UnsafeLibcFunctionCallGadget treats as a libc
+// function: in the libc scope, with a libc name.
+static bool isLibcFunction(const FunctionDecl &FD) {
+ return isInLibcScope(FD) &&
+ (isPredefinedUnsafeLibcFunc(FD) || isUnsafeVaListPrintfFunc(FD) ||
+ isUnsafeSprintfFunc(FD) || isNormalPrintfFunc(FD));
+}
} // namespace libc_func_matchers
namespace {
@@ -2504,12 +2529,7 @@ class UnsafeLibcFunctionCallGadget : public WarningGadget {
if (ignoreUnsafeLibcCall(Ctx, *S, Handler))
return false;
- const bool IsGlobalAndNotInAnyNamespace =
- FD->isGlobal() && !FD->getEnclosingNamespaceContext()->isNamespace();
-
- // A libc function must either be in the std:: namespace or a global
- // function that is not in any namespace:
- if (!FD->isInStdNamespace() && !IsGlobalAndNotInAnyNamespace)
+ if (!libc_func_matchers::isInLibcScope(*FD))
return false;
// If the call has a sole null-terminated argument, e.g., strlen,
// printf, atoi, we consider it safe:
@@ -2592,7 +2612,7 @@ class UnsafeFormatAttributedFunctionCallGadget : public WarningGadget {
const FunctionDecl *FD = CE->getDirectCallee();
if (!FD)
return false;
- if (ignoreUnsafeLibcCall(Ctx, *S, Handler))
+ if (ignoreUnsafeFormatAttrCall(Ctx, *S, Handler))
return false;
const FormatAttr *Attr = nullptr;
@@ -2613,6 +2633,10 @@ class UnsafeFormatAttributedFunctionCallGadget : public WarningGadget {
if (!AnyAttr)
return false;
+ // Calls to libc functions belong to -Wunsafe-buffer-usage-in-libc-call.
+ if (ignoreUnsafeLibcCall(Ctx, *S, Handler) &&
+ libc_func_matchers::isLibcFunction(*FD))
+ return false;
// FormatAttribute indexes are 1-based:
unsigned FmtIdx = Attr->getFormatIdx() - 1;
@@ -5025,6 +5049,10 @@ bool clang::matchUnsafePointers(const DynTypedNode &N, ASTContext &Ctx,
bool ignoreUnsafeBufferInLibcCall(const SourceLocation &) const override {
return false;
}
+ bool
+ ignoreUnsafeBufferInFormatAttrCall(const SourceLocation &) const override {
+ return false;
+ }
bool ignoreUnsafeBufferInStaticSizedArray(
const SourceLocation &Loc) const override {
return false;
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 21af6ed5527ffc..a2f7581473caf0 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2742,6 +2742,11 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
return S.Diags.isIgnored(diag::warn_unsafe_buffer_libc_call, Loc);
}
+ bool
+ ignoreUnsafeBufferInFormatAttrCall(const SourceLocation &Loc) const override {
+ return S.Diags.isIgnored(diag::warn_unsafe_buffer_format_attr_call, Loc);
+ }
+
bool ignoreUnsafeBufferInStaticSizedArray(
const SourceLocation &Loc) const override {
return S.Diags.isIgnored(
@@ -3058,8 +3063,9 @@ static bool shouldRunUnsafeBufferUsageAnalysis(const Sema &S,
return !Diags.isIgnored(diag::warn_unsafe_buffer_operation, Loc) ||
!Diags.isIgnored(diag::warn_unsafe_buffer_variable, Loc) ||
!Diags.isIgnored(diag::warn_unsafe_buffer_usage_in_container, Loc) ||
- (!Diags.isIgnored(diag::warn_unsafe_buffer_libc_call, Loc) &&
- S.getLangOpts().CPlusPlus);
+ (S.getLangOpts().CPlusPlus &&
+ (!Diags.isIgnored(diag::warn_unsafe_buffer_libc_call, Loc) ||
+ !Diags.isIgnored(diag::warn_unsafe_buffer_format_attr_call, Loc)));
}
/// \return true iff fix-its should be emitted along with -Wunsafe-buffer-usage
diff --git a/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-format-attr-call.cpp b/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-format-attr-call.cpp
new file mode 100644
index 00000000000000..b68aacc045b494
--- /dev/null
+++ b/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-format-attr-call.cpp
@@ -0,0 +1,36 @@
+// RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage \
+// RUN: -verify=expected,libc %s
+// RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage \
+// RUN: -Wno-unsafe-buffer-usage-in-libc-call -verify %s
+// RUN: %clang_cc1 -std=c++20 -Wno-all \
+// RUN: -Wunsafe-buffer-usage-in-format-attr-call -verify %s
+// RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage \
+// RUN: -Wno-unsafe-buffer-usage-in-format-attr-call -verify=libc %s
+
+typedef __SIZE_TYPE__ size_t;
+
+int printf(const char *, ...) __attribute__((__format__(__printf__, 1, 2)));
+size_t strlen(const char *);
+void log_message(const char *, ...)
+ __attribute__((__format__(__printf__, 1, 2)));
+void read_input(const char *, ...)
+ __attribute__((__format__(__scanf__, 1, 2)));
+
+void f(char *s, int *x) {
+ log_message("%s", s); // expected-warning{{formatting function 'log_message' is unsafe}} \
+ expected-note{{string argument is not guaranteed to be null-terminated}}
+ read_input("%d", x); // expected-warning{{formatting function 'read_input' is unsafe}}
+ printf("%s", s); // libc-warning{{function 'printf' is unsafe}} \
+ libc-note{{string argument is not guaranteed to be null-terminated}}
+ strlen(s); // libc-warning{{function 'strlen' is unsafe}}
+}
+
+void g(char *s) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
+ log_message("%s", s); // expected-warning{{formatting function 'log_message' is unsafe}} \
+ expected-note{{string argument is not guaranteed to be null-terminated}}
+ printf("%s", s);
+ strlen(s);
+#pragma clang diagnostic pop
+}
>From beb9398e044535d85bb5e4b475b2bf449bf47712 Mon Sep 17 00:00:00 2001
From: Ilyas Mallah <github at ilyasm.dev>
Date: Thu, 15 Oct 2026 00:16:18 +0200
Subject: [PATCH 2/2] [clang][-Wunsafe-buffer-usage] Add per-family subgroups
of -Wunsafe-buffer-usage-in-libc-call
---
clang/docs/ReleaseNotes.md | 7 +
.../Analysis/Analyses/UnsafeBufferUsage.h | 19 +-
clang/include/clang/Basic/DiagnosticGroups.td | 9 +-
.../clang/Basic/DiagnosticSemaKinds.td | 21 +-
clang/lib/Analysis/UnsafeBufferUsage.cpp | 183 ++++++++++--------
clang/lib/Sema/AnalysisBasedWarnings.cpp | 47 ++++-
...fe-buffer-usage-in-libc-call-subgroups.cpp | 66 +++++++
7 files changed, 267 insertions(+), 85 deletions(-)
create mode 100644 clang/test/SemaCXX/warn-unsafe-buffer-usage-in-libc-call-subgroups.cpp
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 43fff181491c44..c8247f2e91562d 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -684,6 +684,13 @@ features cannot lower the translation-unit ABI level;
`-Wunsafe-buffer-usage-in-format-attr-call` now works when it is the only
Safe Buffers warning enabled.
+- Added one diagnostic group per family of libc functions under
+ `-Wunsafe-buffer-usage-in-libc-call`, named by the suffixes
+ `-numeric-conversion`, `-string-manipulation`, `-string-examination`,
+ `-memory`, `-io`, `-printf` and `-scanf`. For example,
+ `-Wno-unsafe-buffer-usage-in-libc-call-string-examination` silences `strlen`
+ while `memcpy` is still diagnosed. (#GH127849)
+
### Improvements to Clang's time-trace
### Improvements to Coverage Mapping
diff --git a/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h b/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h
index 032ccca0297ace..f9fb731e489ed4 100644
--- a/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h
+++ b/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h
@@ -82,6 +82,19 @@ class FixitStrategy {
}
};
+/// The family of an unsafe libc function. Each family except `Other` has its
+/// own subgroup of -Wunsafe-buffer-usage-in-libc-call.
+enum class UnsafeLibcFunctionFamily {
+ Other,
+ NumericConversion,
+ StringManipulation,
+ StringExamination,
+ Memory,
+ IO,
+ Printf,
+ Scanf,
+};
+
/// The interface that lets the caller handle unsafe buffer usage analysis
/// results by overriding this class's handle... methods.
class UnsafeBufferUsageHandler {
@@ -118,9 +131,12 @@ class UnsafeBufferUsageHandler {
/// 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 Family the family of the callee, ignored when `PrintfInfo` has the
+ /// format attribute bit set
/// \param UnsafeArg one of the actual arguments that is unsafe, non-null
/// only when `2 <= PrintfInfo <= 3 (ignoring the "format attribute" bit)`
virtual void handleUnsafeLibcCall(const CallExpr *Call, unsigned PrintfInfo,
+ UnsafeLibcFunctionFamily Family,
ASTContext &Ctx,
const Expr *UnsafeArg = nullptr) = 0;
@@ -179,7 +195,8 @@ class UnsafeBufferUsageHandler {
virtual bool
ignoreUnsafeBufferInContainer(const SourceLocation &Loc) const = 0;
- /// \return true iff unsafe libc call should NOT be reported at `Loc`
+ /// \return true iff every kind of unsafe libc call warning should NOT be
+ /// reported at `Loc`
virtual bool
ignoreUnsafeBufferInLibcCall(const SourceLocation &Loc) const = 0;
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index b6c7ff7276260f..c025b4e0366690 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -2004,7 +2004,14 @@ 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 UnsafeBufferUsageInLibcCallNumericConversion : DiagGroup<"unsafe-buffer-usage-in-libc-call-numeric-conversion">;
+def UnsafeBufferUsageInLibcCallStringManipulation : DiagGroup<"unsafe-buffer-usage-in-libc-call-string-manipulation">;
+def UnsafeBufferUsageInLibcCallStringExamination : DiagGroup<"unsafe-buffer-usage-in-libc-call-string-examination">;
+def UnsafeBufferUsageInLibcCallMemory : DiagGroup<"unsafe-buffer-usage-in-libc-call-memory">;
+def UnsafeBufferUsageInLibcCallIO : DiagGroup<"unsafe-buffer-usage-in-libc-call-io">;
+def UnsafeBufferUsageInLibcCallPrintf : DiagGroup<"unsafe-buffer-usage-in-libc-call-printf">;
+def UnsafeBufferUsageInLibcCallScanf : DiagGroup<"unsafe-buffer-usage-in-libc-call-scanf">;
+def UnsafeBufferUsageInLibcCall : DiagGroup<"unsafe-buffer-usage-in-libc-call", [UnsafeBufferUsageInLibcCallNumericConversion, UnsafeBufferUsageInLibcCallStringManipulation, UnsafeBufferUsageInLibcCallStringExamination, UnsafeBufferUsageInLibcCallMemory, UnsafeBufferUsageInLibcCallIO, UnsafeBufferUsageInLibcCallPrintf, UnsafeBufferUsageInLibcCallScanf]>;
def UnsafeBufferUsageInUniquePtrArrayAccess : DiagGroup<"unsafe-buffer-usage-in-unique-ptr-array-access">;
def UnsafeBufferUsageInStaticSizedArray : DiagGroup<"unsafe-buffer-usage-in-static-sized-array">;
def UnsafeBufferUsageInFormatAttrCall : DiagGroup<"unsafe-buffer-usage-in-format-attr-call">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 48b166d2a48a05..2084bedcf38991 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -13988,6 +13988,7 @@ defvar UnsafeBufferOperationMsg =
"%select{unsafe pointer operation|unsafe pointer arithmetic|"
"unsafe buffer access|function introduces unsafe buffer manipulation|"
"unsafe invocation of %1|field %1 prone to unsafe buffer manipulation}0";
+defvar UnsafeBufferLibcCallMsg = "function %0 is unsafe";
def warn_unsafe_buffer_variable : Warning<UnsafeBufferVariableMsg>,
InGroup<UnsafeBufferUsage>, DefaultIgnore;
def warn_unsafe_buffer_variable_main_argv : Warning<UnsafeBufferVariableMsg>,
@@ -13996,9 +13997,25 @@ def warn_unsafe_buffer_operation : Warning<UnsafeBufferOperationMsg>,
InGroup<UnsafeBufferUsage>, DefaultIgnore;
def warn_unsafe_buffer_operation_main_argv : Warning<UnsafeBufferOperationMsg>,
InGroup<UnsafeBufferUsageMainArgv>, DefaultIgnore;
-def warn_unsafe_buffer_libc_call : Warning<
- "function %0 is unsafe">,
+def warn_unsafe_buffer_libc_call : Warning<UnsafeBufferLibcCallMsg>,
InGroup<UnsafeBufferUsageInLibcCall>, DefaultIgnore;
+def warn_unsafe_buffer_libc_call_numeric_conversion :
+ Warning<UnsafeBufferLibcCallMsg>,
+ InGroup<UnsafeBufferUsageInLibcCallNumericConversion>, DefaultIgnore;
+def warn_unsafe_buffer_libc_call_string_manipulation :
+ Warning<UnsafeBufferLibcCallMsg>,
+ InGroup<UnsafeBufferUsageInLibcCallStringManipulation>, DefaultIgnore;
+def warn_unsafe_buffer_libc_call_string_examination :
+ Warning<UnsafeBufferLibcCallMsg>,
+ InGroup<UnsafeBufferUsageInLibcCallStringExamination>, DefaultIgnore;
+def warn_unsafe_buffer_libc_call_memory : Warning<UnsafeBufferLibcCallMsg>,
+ InGroup<UnsafeBufferUsageInLibcCallMemory>, DefaultIgnore;
+def warn_unsafe_buffer_libc_call_io : Warning<UnsafeBufferLibcCallMsg>,
+ InGroup<UnsafeBufferUsageInLibcCallIO>, DefaultIgnore;
+def warn_unsafe_buffer_libc_call_printf : Warning<UnsafeBufferLibcCallMsg>,
+ InGroup<UnsafeBufferUsageInLibcCallPrintf>, DefaultIgnore;
+def warn_unsafe_buffer_libc_call_scanf : Warning<UnsafeBufferLibcCallMsg>,
+ InGroup<UnsafeBufferUsageInLibcCallScanf>, DefaultIgnore;
def warn_unsafe_buffer_format_attr_call : Warning<
"formatting function %0 is unsafe">,
InGroup<UnsafeBufferUsageInFormatAttrCall>, DefaultIgnore;
diff --git a/clang/lib/Analysis/UnsafeBufferUsage.cpp b/clang/lib/Analysis/UnsafeBufferUsage.cpp
index e60a46c5aae7ea..ce8d8e63b39674 100644
--- a/clang/lib/Analysis/UnsafeBufferUsage.cpp
+++ b/clang/lib/Analysis/UnsafeBufferUsage.cpp
@@ -31,6 +31,7 @@
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include <cstddef>
#include <optional>
@@ -1148,93 +1149,97 @@ hasUnsafeFormatOrSArg(ASTContext &Ctx, const CallExpr *Call,
// 1. It's name, after stripping off predefined prefix and suffix, is
// `CoreName`; and
// 2. `CoreName` or `CoreName[str/wcs]` is one of the `PredefinedNames`, which
-// is a set of libc function names.
+// maps libc function names to their families.
//
// Note: For predefined prefix and suffix, see `matchName()`.
// The notation `CoreName[str/wcs]` means a new name obtained from replace
// string "wcs" with "str" in `CoreName`.
-static bool isPredefinedUnsafeLibcFunc(const FunctionDecl &Node) {
- static const std::set<StringRef> PredefinedNames = {
+//
+// Returns the family of the matched function, or std::nullopt if it does not
+// match.
+static std::optional<UnsafeLibcFunctionFamily>
+getPredefinedUnsafeLibcFuncFamily(const FunctionDecl &Node) {
+ static const llvm::StringMap<UnsafeLibcFunctionFamily> PredefinedNames = {
// numeric conversion:
- "atof",
- "atoi",
- "atol",
- "atoll",
- "strtol",
- "strtoll",
- "strtoul",
- "strtoull",
- "strtof",
- "strtod",
- "strtold",
- "strtoimax",
- "strtoumax",
+ {"atof", UnsafeLibcFunctionFamily::NumericConversion},
+ {"atoi", UnsafeLibcFunctionFamily::NumericConversion},
+ {"atol", UnsafeLibcFunctionFamily::NumericConversion},
+ {"atoll", UnsafeLibcFunctionFamily::NumericConversion},
+ {"strtol", UnsafeLibcFunctionFamily::NumericConversion},
+ {"strtoll", UnsafeLibcFunctionFamily::NumericConversion},
+ {"strtoul", UnsafeLibcFunctionFamily::NumericConversion},
+ {"strtoull", UnsafeLibcFunctionFamily::NumericConversion},
+ {"strtof", UnsafeLibcFunctionFamily::NumericConversion},
+ {"strtod", UnsafeLibcFunctionFamily::NumericConversion},
+ {"strtold", UnsafeLibcFunctionFamily::NumericConversion},
+ {"strtoimax", UnsafeLibcFunctionFamily::NumericConversion},
+ {"strtoumax", UnsafeLibcFunctionFamily::NumericConversion},
// "strfromf", "strfromd", "strfroml", // C23?
// string manipulation:
- "strcpy",
- "strncpy",
- "strlcpy",
- "strcat",
- "strncat",
- "strlcat",
- "strxfrm",
- "strdup",
- "strndup",
+ {"strcpy", UnsafeLibcFunctionFamily::StringManipulation},
+ {"strncpy", UnsafeLibcFunctionFamily::StringManipulation},
+ {"strlcpy", UnsafeLibcFunctionFamily::StringManipulation},
+ {"strcat", UnsafeLibcFunctionFamily::StringManipulation},
+ {"strncat", UnsafeLibcFunctionFamily::StringManipulation},
+ {"strlcat", UnsafeLibcFunctionFamily::StringManipulation},
+ {"strxfrm", UnsafeLibcFunctionFamily::StringManipulation},
+ {"strdup", UnsafeLibcFunctionFamily::StringManipulation},
+ {"strndup", UnsafeLibcFunctionFamily::StringManipulation},
// string examination:
- "strlen",
- "strnlen",
- "strcmp",
- "strncmp",
- "stricmp",
- "strcasecmp",
- "strcoll",
- "strchr",
- "strrchr",
- "strspn",
- "strcspn",
- "strpbrk",
- "strstr",
- "strtok",
+ {"strlen", UnsafeLibcFunctionFamily::StringExamination},
+ {"strnlen", UnsafeLibcFunctionFamily::StringExamination},
+ {"strcmp", UnsafeLibcFunctionFamily::StringExamination},
+ {"strncmp", UnsafeLibcFunctionFamily::StringExamination},
+ {"stricmp", UnsafeLibcFunctionFamily::StringExamination},
+ {"strcasecmp", UnsafeLibcFunctionFamily::StringExamination},
+ {"strcoll", UnsafeLibcFunctionFamily::StringExamination},
+ {"strchr", UnsafeLibcFunctionFamily::StringExamination},
+ {"strrchr", UnsafeLibcFunctionFamily::StringExamination},
+ {"strspn", UnsafeLibcFunctionFamily::StringExamination},
+ {"strcspn", UnsafeLibcFunctionFamily::StringExamination},
+ {"strpbrk", UnsafeLibcFunctionFamily::StringExamination},
+ {"strstr", UnsafeLibcFunctionFamily::StringExamination},
+ {"strtok", UnsafeLibcFunctionFamily::StringExamination},
// "mem-" functions
- "memchr",
- "wmemchr",
- "memcmp",
- "wmemcmp",
- "memcpy",
- "memccpy",
- "mempcpy",
- "wmemcpy",
- "memmove",
- "wmemmove",
- "wmemset",
+ {"memchr", UnsafeLibcFunctionFamily::Memory},
+ {"wmemchr", UnsafeLibcFunctionFamily::Memory},
+ {"memcmp", UnsafeLibcFunctionFamily::Memory},
+ {"wmemcmp", UnsafeLibcFunctionFamily::Memory},
+ {"memcpy", UnsafeLibcFunctionFamily::Memory},
+ {"memccpy", UnsafeLibcFunctionFamily::Memory},
+ {"mempcpy", UnsafeLibcFunctionFamily::Memory},
+ {"wmemcpy", UnsafeLibcFunctionFamily::Memory},
+ {"memmove", UnsafeLibcFunctionFamily::Memory},
+ {"wmemmove", UnsafeLibcFunctionFamily::Memory},
+ {"wmemset", UnsafeLibcFunctionFamily::Memory},
+ {"bcopy", UnsafeLibcFunctionFamily::Memory},
+ {"bzero", UnsafeLibcFunctionFamily::Memory},
// IO:
- "fread",
- "fwrite",
- "fgets",
- "fgetws",
- "gets",
- "fputs",
- "fputws",
- "puts",
+ {"fread", UnsafeLibcFunctionFamily::IO},
+ {"fwrite", UnsafeLibcFunctionFamily::IO},
+ {"fgets", UnsafeLibcFunctionFamily::IO},
+ {"fgetws", UnsafeLibcFunctionFamily::IO},
+ {"gets", UnsafeLibcFunctionFamily::IO},
+ {"fputs", UnsafeLibcFunctionFamily::IO},
+ {"fputws", UnsafeLibcFunctionFamily::IO},
+ {"puts", UnsafeLibcFunctionFamily::IO},
// others
- "strerror_s",
- "strerror_r",
- "bcopy",
- "bzero",
- "bsearch",
- "qsort",
+ {"strerror_s", UnsafeLibcFunctionFamily::Other},
+ {"strerror_r", UnsafeLibcFunctionFamily::Other},
+ {"bsearch", UnsafeLibcFunctionFamily::Other},
+ {"qsort", UnsafeLibcFunctionFamily::Other},
};
auto *II = Node.getIdentifier();
if (!II)
- return false;
+ return std::nullopt;
StringRef Name = matchName(II->getName(), Node.getBuiltinID());
// Match predefined names:
- if (PredefinedNames.count(Name))
- return true;
+ if (auto It = PredefinedNames.find(Name); It != PredefinedNames.end())
+ return It->second;
std::string NameWCS = Name.str();
size_t WcsPos = NameWCS.find("wcs");
@@ -1245,11 +1250,26 @@ static bool isPredefinedUnsafeLibcFunc(const FunctionDecl &Node) {
NameWCS[WcsPos++] = 'r';
WcsPos = NameWCS.find("wcs", WcsPos);
}
- if (PredefinedNames.count(NameWCS))
- return true;
+ if (auto It = PredefinedNames.find(NameWCS); It != PredefinedNames.end())
+ return It->second;
// All `scanf` functions are unsafe (including `sscanf`, `vsscanf`, etc.. They
// all should end with "scanf"):
- return Name.ends_with("scanf");
+ if (Name.ends_with("scanf"))
+ return UnsafeLibcFunctionFamily::Scanf;
+ return std::nullopt;
+}
+
+static bool isPredefinedUnsafeLibcFunc(const FunctionDecl &Node) {
+ return getPredefinedUnsafeLibcFuncFamily(Node).has_value();
+}
+
+// Returns the family of a callee that isPredefinedUnsafeLibcFunc or
+// isUnsafeMemset matched. Only memset is not in PredefinedNames.
+static UnsafeLibcFunctionFamily
+getUnsafeLibcFunctionFamily(const FunctionDecl &Node) {
+ if (auto Family = getPredefinedUnsafeLibcFuncFamily(Node))
+ return *Family;
+ return UnsafeLibcFunctionFamily::Memory;
}
// Returns true if this is an unsafe call to `memset`.
@@ -2501,6 +2521,7 @@ class UnsafeLibcFunctionCallGadget : public WarningGadget {
// considered unsafe as it is not compile-time check
FORMAT_ATTR = 8, // flag: the callee has the format attribute
} WarnedFunKind = OTHERS;
+ UnsafeLibcFunctionFamily Family = UnsafeLibcFunctionFamily::Other;
UnsafeLibcFunctionCallGadget(const MatchResult &Result)
: WarningGadget(Kind::UnsafeLibcFunctionCall),
@@ -2515,6 +2536,12 @@ class UnsafeLibcFunctionCallGadget : public WarningGadget {
UnsafeArg = Call->getArg(0);
} else if (Result.getNodeAs<Decl>(UnsafeVaListTag))
WarnedFunKind = VA_LIST;
+ // Only the predefined-name and memset matches leave `WarnedFunKind` at
+ // `OTHERS`. The printf matches always set a kind.
+ Family = WarnedFunKind != OTHERS
+ ? UnsafeLibcFunctionFamily::Printf
+ : libc_func_matchers::getUnsafeLibcFunctionFamily(
+ *Call->getDirectCallee());
}
static bool matches(const Stmt *S, ASTContext &Ctx,
@@ -2526,11 +2553,12 @@ class UnsafeLibcFunctionCallGadget : public WarningGadget {
const auto *FD = CE->getDirectCallee();
if (!FD)
return false;
- if (ignoreUnsafeLibcCall(Ctx, *S, Handler))
- return false;
-
if (!libc_func_matchers::isInLibcScope(*FD))
return false;
+ // The check looks up every libc-call warning, so it runs after the cheaper
+ // scope test.
+ if (ignoreUnsafeLibcCall(Ctx, *S, Handler))
+ return false;
// If the call has a sole null-terminated argument, e.g., strlen,
// printf, atoi, we consider it safe:
if (CE->getNumArgs() == 1 && isNullTermPointer(CE->getArg(0), Ctx))
@@ -2582,7 +2610,7 @@ class UnsafeLibcFunctionCallGadget : public WarningGadget {
void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
bool IsRelatedToDecl,
ASTContext &Ctx) const override {
- Handler.handleUnsafeLibcCall(Call, WarnedFunKind, Ctx, UnsafeArg);
+ Handler.handleUnsafeLibcCall(Call, WarnedFunKind, Family, Ctx, UnsafeArg);
}
DeclUseList getClaimedVarUseSites() const override { return {}; }
@@ -2689,13 +2717,13 @@ class UnsafeFormatAttributedFunctionCallGadget : public WarningGadget {
Call,
UnsafeLibcFunctionCallGadget::UnsafeKind::STRING |
UnsafeLibcFunctionCallGadget::UnsafeKind::FORMAT_ATTR,
- Ctx, UnsafeArg);
+ UnsafeLibcFunctionFamily::Other, Ctx, UnsafeArg);
else
Handler.handleUnsafeLibcCall(
Call,
UnsafeLibcFunctionCallGadget::UnsafeKind::OTHERS |
UnsafeLibcFunctionCallGadget::UnsafeKind::FORMAT_ATTR,
- Ctx);
+ UnsafeLibcFunctionFamily::Other, Ctx);
}
DeclUseList getClaimedVarUseSites() const override { return {}; }
@@ -5027,7 +5055,8 @@ bool clang::matchUnsafePointers(const DynTypedNode &N, ASTContext &Ctx,
public:
MockReporter() {}
void handleUnsafeOperation(const Stmt *, bool, ASTContext &) override {}
- void handleUnsafeLibcCall(const CallExpr *, unsigned, ASTContext &,
+ void handleUnsafeLibcCall(const CallExpr *, unsigned,
+ UnsafeLibcFunctionFamily, ASTContext &,
const Expr *UnsafeArg = nullptr) override {}
void handleUnsafeOperationInContainer(const Stmt *, bool,
ASTContext &) override {}
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index a2f7581473caf0..f532d885ca19fd 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2437,6 +2437,41 @@ bool refersMainParam(const Expr *E) {
return false;
}
+// The base diagnostic comes first, so that the common case of checking whether
+// all of them are ignored needs one lookup.
+static constexpr unsigned UnsafeLibcCallDiagIDs[] = {
+ diag::warn_unsafe_buffer_libc_call,
+ diag::warn_unsafe_buffer_libc_call_numeric_conversion,
+ diag::warn_unsafe_buffer_libc_call_string_manipulation,
+ diag::warn_unsafe_buffer_libc_call_string_examination,
+ diag::warn_unsafe_buffer_libc_call_memory,
+ diag::warn_unsafe_buffer_libc_call_io,
+ diag::warn_unsafe_buffer_libc_call_printf,
+ diag::warn_unsafe_buffer_libc_call_scanf,
+};
+
+static unsigned getUnsafeLibcCallDiagID(UnsafeLibcFunctionFamily Family) {
+ switch (Family) {
+ case UnsafeLibcFunctionFamily::Other:
+ return diag::warn_unsafe_buffer_libc_call;
+ case UnsafeLibcFunctionFamily::NumericConversion:
+ return diag::warn_unsafe_buffer_libc_call_numeric_conversion;
+ case UnsafeLibcFunctionFamily::StringManipulation:
+ return diag::warn_unsafe_buffer_libc_call_string_manipulation;
+ case UnsafeLibcFunctionFamily::StringExamination:
+ return diag::warn_unsafe_buffer_libc_call_string_examination;
+ case UnsafeLibcFunctionFamily::Memory:
+ return diag::warn_unsafe_buffer_libc_call_memory;
+ case UnsafeLibcFunctionFamily::IO:
+ return diag::warn_unsafe_buffer_libc_call_io;
+ case UnsafeLibcFunctionFamily::Printf:
+ return diag::warn_unsafe_buffer_libc_call_printf;
+ case UnsafeLibcFunctionFamily::Scanf:
+ return diag::warn_unsafe_buffer_libc_call_scanf;
+ }
+ llvm_unreachable("unknown UnsafeLibcFunctionFamily");
+}
+
class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
Sema &S;
bool SuggestSuggestions; // Recommend -fsafe-buffer-usage-suggestions?
@@ -2586,9 +2621,9 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
}
void handleUnsafeLibcCall(const CallExpr *Call, unsigned PrintfInfo,
- ASTContext &Ctx,
+ UnsafeLibcFunctionFamily Family, ASTContext &Ctx,
const Expr *UnsafeArg = nullptr) override {
- unsigned DiagID = diag::warn_unsafe_buffer_libc_call;
+ unsigned DiagID = getUnsafeLibcCallDiagID(Family);
if (PrintfInfo & 0x8) {
// The callee is a function with the format attribute. See the
// documentation of PrintfInfo in UnsafeBufferUsageHandler, and
@@ -2739,7 +2774,9 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
}
bool ignoreUnsafeBufferInLibcCall(const SourceLocation &Loc) const override {
- return S.Diags.isIgnored(diag::warn_unsafe_buffer_libc_call, Loc);
+ return llvm::all_of(UnsafeLibcCallDiagIDs, [&](unsigned ID) {
+ return S.Diags.isIgnored(ID, Loc);
+ });
}
bool
@@ -3064,7 +3101,9 @@ static bool shouldRunUnsafeBufferUsageAnalysis(const Sema &S,
!Diags.isIgnored(diag::warn_unsafe_buffer_variable, Loc) ||
!Diags.isIgnored(diag::warn_unsafe_buffer_usage_in_container, Loc) ||
(S.getLangOpts().CPlusPlus &&
- (!Diags.isIgnored(diag::warn_unsafe_buffer_libc_call, Loc) ||
+ (llvm::any_of(
+ UnsafeLibcCallDiagIDs,
+ [&](unsigned ID) { return !Diags.isIgnored(ID, Loc); }) ||
!Diags.isIgnored(diag::warn_unsafe_buffer_format_attr_call, Loc)));
}
diff --git a/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-libc-call-subgroups.cpp b/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-libc-call-subgroups.cpp
new file mode 100644
index 00000000000000..e11a35898b6ca0
--- /dev/null
+++ b/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-libc-call-subgroups.cpp
@@ -0,0 +1,66 @@
+// RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage-in-libc-call \
+// RUN: -verify=expected,exam %s
+// RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage \
+// RUN: -Wno-unsafe-buffer-usage-in-libc-call-string-examination \
+// RUN: -verify=expected %s
+// RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage-in-libc-call-memory \
+// RUN: -verify=mem %s
+// RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage \
+// RUN: -Wno-unsafe-buffer-usage-in-libc-call \
+// RUN: -Wunsafe-buffer-usage-in-libc-call-memory -verify=mem %s
+// RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage \
+// RUN: -Wno-unsafe-buffer-usage-in-libc-call -verify=none %s
+// RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage-in-libc-call \
+// RUN: %s 2>&1 | FileCheck %s
+
+// none-no-diagnostics
+
+typedef __SIZE_TYPE__ size_t;
+struct FILE;
+
+int atoi(const char *);
+char *strcpy(char *, const char *);
+size_t strlen(const char *);
+size_t wcslen(const wchar_t *);
+void *memcpy(void *, const void *, size_t);
+void *memset(void *, int, size_t);
+void bzero(void *, size_t);
+char *fgets(char *, int, FILE *);
+int sprintf(char *, const char *, ...);
+int sscanf(const char *, const char *, ...);
+void qsort(void *, size_t, size_t, int (*)(const void *, const void *));
+
+void calls(char *p, const char *q, const wchar_t *w, size_t n, FILE *f,
+ int *x) {
+ atoi(q); // expected-warning{{function 'atoi' is unsafe}}
+ strcpy(p, q); // expected-warning{{function 'strcpy' is unsafe}}
+ strlen(q); // exam-warning{{function 'strlen' is unsafe}}
+ wcslen(w); // exam-warning{{function 'wcslen' is unsafe}}
+ memcpy(p, q, n); // expected-warning{{function 'memcpy' is unsafe}} mem-warning{{function 'memcpy' is unsafe}}
+ memset(p, 0, n); // expected-warning{{function 'memset' is unsafe}} mem-warning{{function 'memset' is unsafe}}
+ bzero(p, n); // expected-warning{{function 'bzero' is unsafe}} mem-warning{{function 'bzero' is unsafe}}
+ fgets(p, 4, f); // expected-warning{{function 'fgets' is unsafe}}
+ sprintf(p, "%d", 1); // expected-warning{{function 'sprintf' is unsafe}} \
+ expected-note{{change to 'snprintf' for explicit bounds checking}}
+ sscanf(q, "%d", x); // expected-warning{{function 'sscanf' is unsafe}}
+ qsort(p, n, 1, nullptr); // expected-warning{{function 'qsort' is unsafe}}
+}
+
+void pragma(char *p, const char *q, size_t n) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call-string-examination"
+ strlen(q);
+ memcpy(p, q, n); // expected-warning{{function 'memcpy' is unsafe}} mem-warning{{function 'memcpy' is unsafe}}
+#pragma clang diagnostic pop
+ strlen(q); // exam-warning{{function 'strlen' is unsafe}}
+}
+
+// CHECK-DAG: function 'atoi' is unsafe [-Wunsafe-buffer-usage-in-libc-call-numeric-conversion]
+// CHECK-DAG: function 'strcpy' is unsafe [-Wunsafe-buffer-usage-in-libc-call-string-manipulation]
+// CHECK-DAG: function 'wcslen' is unsafe [-Wunsafe-buffer-usage-in-libc-call-string-examination]
+// CHECK-DAG: function 'memset' is unsafe [-Wunsafe-buffer-usage-in-libc-call-memory]
+// CHECK-DAG: function 'bzero' is unsafe [-Wunsafe-buffer-usage-in-libc-call-memory]
+// CHECK-DAG: function 'fgets' is unsafe [-Wunsafe-buffer-usage-in-libc-call-io]
+// CHECK-DAG: function 'sprintf' is unsafe [-Wunsafe-buffer-usage-in-libc-call-printf]
+// CHECK-DAG: function 'sscanf' is unsafe [-Wunsafe-buffer-usage-in-libc-call-scanf]
+// CHECK-DAG: function 'qsort' is unsafe [-Wunsafe-buffer-usage-in-libc-call]
More information about the cfe-commits
mailing list