[clang] [clang][-Wunsafe-buffer-usage] Don't opt out flexible-array-member-like subscripts (PR #228444)

Chris Kennelly via cfe-commits cfe-commits at lists.llvm.org
Fri Oct 2 07:01:37 PDT 2026


https://github.com/ckennelly created https://github.com/llvm/llvm-project/pull/228444

-Wno-unsafe-buffer-usage-in-static-sized-array (https://github.com/llvm/llvm-project/commit/762a44f2c3cac98b8d3823936a620ea173cfef96) exists for code built with -fsanitize=array-bounds: subscripts on an array of known size are not reported because the sanitizer bounds-checks them.  The sanitizer does not check every constant-size array, though.  CodeGen's getArrayIndexingBound refuses to trust the declared size of a trailing array member that -fstrict-flex-arrays treats as a flexible array member (under the default level 0, any trailing array member), so `s->buf[idx]` in

  struct S { int len; int buf[16]; };

has no runtime check, yet the opt-out silenced it.

Gate the opt-out on Expr::isFlexibleArrayMemberLike with the current -fstrict-flex-arrays level, the same predicate CodeGen uses.  Flexible array member-like subscripts fall through to the existing static checks, so a constant index within the declared size stays quiet; the constant-offset pointer arithmetic case (https://github.com/llvm/llvm-project/commit/d6a265a477d2feba1d58f97c26ad14683fb8d1ca) is unaffected because it never relied on the sanitizer in the first place.

This only affects users of the opt-out, for whom it means more warnings.

>From 23c435138592b0e06f2d34cc841aa4566da7fb4b Mon Sep 17 00:00:00 2001
From: Chris Kennelly <ckennelly at ckennelly.com>
Date: Thu, 1 Oct 2026 14:27:58 +0000
Subject: [PATCH 1/2] [clang][-Wunsafe-buffer-usage] Add tests for the
 static-sized-array opt-out on trailing array members (NFC)

Subscripts on a trailing array member at each -fstrict-flex-arrays level,
with the warnings -Wno-unsafe-buffer-usage-in-static-sized-array currently
leaves: none, although -fsanitize=array-bounds does not check them.

Assisted-by: Claude Code
---
 ...sage-in-static-sized-array-flex-arrays.cpp | 89 +++++++++++++++++++
 ...fer-usage-in-static-sized-array-unsafe.cpp | 12 +++
 ...afe-buffer-usage-in-static-sized-array.cpp | 10 +++
 3 files changed, 111 insertions(+)
 create mode 100644 clang/test/SemaCXX/warn-unsafe-buffer-usage-in-static-sized-array-flex-arrays.cpp

diff --git a/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-static-sized-array-flex-arrays.cpp b/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-static-sized-array-flex-arrays.cpp
new file mode 100644
index 00000000000000..73bd816ad1116c
--- /dev/null
+++ b/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-static-sized-array-flex-arrays.cpp
@@ -0,0 +1,89 @@
+// RUN: %clang_cc1 -std=c++20 -Wno-everything -Wunsafe-buffer-usage \
+// RUN:            -Wno-unsafe-buffer-usage-in-static-sized-array \
+// RUN:            -fsafe-buffer-usage-suggestions \
+// RUN:            -fstrict-flex-arrays=0 -verify=expected,level0,level01,level012 %s
+// RUN: %clang_cc1 -std=c++20 -Wno-everything -Wunsafe-buffer-usage \
+// RUN:            -Wno-unsafe-buffer-usage-in-static-sized-array \
+// RUN:            -fsafe-buffer-usage-suggestions \
+// RUN:            -fstrict-flex-arrays=1 -verify=expected,level01,level012 %s
+// RUN: %clang_cc1 -std=c++20 -Wno-everything -Wunsafe-buffer-usage \
+// RUN:            -Wno-unsafe-buffer-usage-in-static-sized-array \
+// RUN:            -fsafe-buffer-usage-suggestions \
+// RUN:            -fstrict-flex-arrays=2 -verify=expected,level012 %s
+// RUN: %clang_cc1 -std=c++20 -Wno-everything -Wunsafe-buffer-usage \
+// RUN:            -Wno-unsafe-buffer-usage-in-static-sized-array \
+// RUN:            -fsafe-buffer-usage-suggestions \
+// RUN:            -fstrict-flex-arrays=3 -verify=expected %s
+
+// -Wno-unsafe-buffer-usage-in-static-sized-array exists for code built with
+// -fsanitize=array-bounds, which bounds-checks subscripts on arrays of known
+// size.  The sanitizer does not trust the declared size of a trailing array
+// member that -fstrict-flex-arrays treats as a flexible array member, but the
+// opt-out silences accesses to those too.
+
+struct Zero {
+  int len;
+  int buf[0];
+};
+
+struct One {
+  int len;
+  int buf[1];
+};
+
+struct Many {
+  int len;
+  int buf[16];
+};
+
+struct Incomplete {
+  int len;
+  int buf[];
+};
+
+struct NotTrailing {
+  int buf[16];
+  int len;
+};
+
+union U {
+  int x;
+  int buf[1];
+};
+
+void zero(Zero *z, unsigned idx) {
+  z->buf[idx] = 0;
+}
+
+void one(One *o, unsigned idx) {
+  o->buf[idx] = 0;
+  // The struct hack: a constant index past the declared size.
+  o->buf[1] = 0;
+}
+
+void many(Many *m, unsigned idx) {
+  m->buf[idx] = 0;
+  m->buf[3] = 0;   // a constant index within the declared size is always safe
+  m->buf[20] = 0;
+}
+
+void incomplete(Incomplete *i, unsigned idx) {
+  i->buf[idx] = 0; // expected-warning{{unsafe buffer access}}
+}
+
+void not_trailing(NotTrailing *n, unsigned idx) {
+  n->buf[idx] = 0;
+}
+
+void union_member(U *u, unsigned idx) {
+  u->buf[idx] = 0;
+}
+
+struct Method {
+  int len;
+  int buf[16];
+
+  void set(unsigned idx) {
+    buf[idx] = 0;
+  }
+};
diff --git a/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-static-sized-array-unsafe.cpp b/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-static-sized-array-unsafe.cpp
index 1165c586994562..1eb21a5ce25ca9 100644
--- a/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-static-sized-array-unsafe.cpp
+++ b/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-static-sized-array-unsafe.cpp
@@ -13,3 +13,15 @@ void unsafe_pointer_arithmetic(int idx) {
 
   int *u4 = buffer + idx; // expected-note {{used in pointer arithmetic here}}
 }
+
+struct Trailing {
+  int len;
+  int buffer[10];
+};
+
+// A trailing array member is a flexible array member under the default
+// -fstrict-flex-arrays=0, so -fsanitize=array-bounds does not check it, but
+// the opt-out silences it.
+void unsafe_trailing_member(Trailing *t, int idx) {
+  t->buffer[idx] = 0;
+}
diff --git a/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-static-sized-array.cpp b/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-static-sized-array.cpp
index 9bc49525efdb97..4785096cc5cd2f 100644
--- a/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-static-sized-array.cpp
+++ b/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-static-sized-array.cpp
@@ -21,6 +21,16 @@ struct Foo {
 
 void foo2(Foo &f, unsigned idx) { f.member_buffer[idx] = 0; }
 
+struct Trailing {
+  int len;
+  int buffer[10];
+};
+
+// The trailing member is a flexible array member under the default
+// -fstrict-flex-arrays=0 (see the -flex-arrays.cpp test), but a constant index
+// within its declared size is safe regardless.
+void trailing_constant_idx(Trailing *t) { t->buffer[9] = 0; }
+
 void constant_idx_safe(unsigned idx) {
   int buffer[10];
   buffer[9] = 0;

>From 2e548c68fbefca070049cc8846b39848dfc96b83 Mon Sep 17 00:00:00 2001
From: Chris Kennelly <ckennelly at ckennelly.com>
Date: Wed, 23 Sep 2026 04:13:56 +0000
Subject: [PATCH 2/2] [clang][-Wunsafe-buffer-usage] Don't opt out
 flexible-array-member-like subscripts

-Wno-unsafe-buffer-usage-in-static-sized-array (762a44f2c3ca) exists for code
built with -fsanitize=array-bounds: subscripts on an array of known size are
not reported because the sanitizer bounds-checks them.  The sanitizer does not
check every constant-size array, though.  CodeGen's getArrayIndexingBound
refuses to trust the declared size of a trailing array member that
-fstrict-flex-arrays treats as a flexible array member (under the default
level 0, any trailing array member), so `s->buf[idx]` in

  struct S { int len; int buf[16]; };

has no runtime check, yet the opt-out silenced it.

Gate the opt-out on Expr::isFlexibleArrayMemberLike with the current
-fstrict-flex-arrays level, the same predicate CodeGen uses.  Flexible
array member-like subscripts fall through to the existing static checks, so a
constant index within the declared size stays quiet; the constant-offset
pointer arithmetic case (d6a265a477d2) is unaffected because it never relied
on the sanitizer in the first place.

This only affects users of the opt-out, for whom it means more warnings.

Assisted-by: Claude Code
---
 clang/docs/ReleaseNotes.md                    |  5 ++++
 clang/lib/Analysis/UnsafeBufferUsage.cpp      | 24 ++++++++++++++-----
 ...sage-in-static-sized-array-flex-arrays.cpp | 18 +++++++-------
 ...fer-usage-in-static-sized-array-unsafe.cpp |  5 ++--
 4 files changed, 34 insertions(+), 18 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index bf190df9769ddf..24fc0aeff24e52 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -502,6 +502,11 @@ features cannot lower the translation-unit ABI level;
   for pointer arithmetic on statically-sized arrays when the offset is a
   non-negative constant within the array bounds.
 
+- `-Wno-unsafe-buffer-usage-in-static-sized-array` no longer suppresses warnings
+  for subscripts on a trailing array member that `-fstrict-flex-arrays` treats
+  as a flexible array member, since `-fsanitize=array-bounds` does not check
+  those accesses.
+
 - `-Wc++98-compat` now diagnoses explicit conversion functions in C++20 and
   later, matching the behavior in C++11 through C++17. (#GH161689)
 
diff --git a/clang/lib/Analysis/UnsafeBufferUsage.cpp b/clang/lib/Analysis/UnsafeBufferUsage.cpp
index 9a4269acb1cdb8..28704008f38569 100644
--- a/clang/lib/Analysis/UnsafeBufferUsage.cpp
+++ b/clang/lib/Analysis/UnsafeBufferUsage.cpp
@@ -767,6 +767,21 @@ static bool isSafeStringViewTwoParamConstruct(const CXXConstructExpr &Node,
   return false; // Default to unsafe
 }
 
+// Returns true iff `Node` subscripts an array whose size is known at the
+// access, so that `-fsanitize=array-bounds` bounds-checks it.  This is what
+// `-Wno-unsafe-buffer-usage-in-static-sized-array` opts out of reporting, and
+// it mirrors `getArrayIndexingBound` in CodeGen: a trailing array member that
+// `-fstrict-flex-arrays` treats as a flexible array member is not checked
+// because its declared size is not trusted.
+static bool isSubscriptOnSizedArray(const ArraySubscriptExpr &Node,
+                                    const ASTContext &Ctx) {
+  const Expr *Base = Node.getBase()->IgnoreParenImpCasts();
+  if (!isa<ConstantArrayType>(Base->getType()->getUnqualifiedDesugaredType()))
+    return false;
+  return !Base->isFlexibleArrayMemberLike(
+      Ctx, Ctx.getLangOpts().getStrictFlexArraysLevel());
+}
+
 static bool isSafeArraySubscript(const ArraySubscriptExpr &Node,
                                  const ASTContext &Ctx,
                                  const bool IgnoreStaticSizedArrays) {
@@ -777,6 +792,9 @@ static bool isSafeArraySubscript(const ArraySubscriptExpr &Node,
   //    already duplicated
   //  - call both from Sema and from here
 
+  if (IgnoreStaticSizedArrays && isSubscriptOnSizedArray(Node, Ctx))
+    return true;
+
   uint64_t limit;
   if (const auto *CATy =
           dyn_cast<ConstantArrayType>(Node.getBase()
@@ -791,12 +809,6 @@ static bool isSafeArraySubscript(const ArraySubscriptExpr &Node,
     return false;
   }
 
-  if (IgnoreStaticSizedArrays) {
-    // If we made it here, it means a size was found for the var being accessed
-    // (either string literal or array). If it's fixed size, we can ignore it.
-    return true;
-  }
-
   Expr::EvalResult EVResult;
   const Expr *IndexExpr = Node.getIdx();
   if (!IndexExpr->isValueDependent() &&
diff --git a/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-static-sized-array-flex-arrays.cpp b/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-static-sized-array-flex-arrays.cpp
index 73bd816ad1116c..33098da185c27b 100644
--- a/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-static-sized-array-flex-arrays.cpp
+++ b/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-static-sized-array-flex-arrays.cpp
@@ -18,8 +18,8 @@
 // -Wno-unsafe-buffer-usage-in-static-sized-array exists for code built with
 // -fsanitize=array-bounds, which bounds-checks subscripts on arrays of known
 // size.  The sanitizer does not trust the declared size of a trailing array
-// member that -fstrict-flex-arrays treats as a flexible array member, but the
-// opt-out silences accesses to those too.
+// member that -fstrict-flex-arrays treats as a flexible array member, so the
+// opt-out must not silence accesses to those either.
 
 struct Zero {
   int len;
@@ -52,19 +52,19 @@ union U {
 };
 
 void zero(Zero *z, unsigned idx) {
-  z->buf[idx] = 0;
+  z->buf[idx] = 0; // level012-warning{{unsafe buffer access}}
 }
 
 void one(One *o, unsigned idx) {
-  o->buf[idx] = 0;
+  o->buf[idx] = 0; // level01-warning{{unsafe buffer access}}
   // The struct hack: a constant index past the declared size.
-  o->buf[1] = 0;
+  o->buf[1] = 0; // level01-warning{{unsafe buffer access}}
 }
 
 void many(Many *m, unsigned idx) {
-  m->buf[idx] = 0;
+  m->buf[idx] = 0; // level0-warning{{unsafe buffer access}}
   m->buf[3] = 0;   // a constant index within the declared size is always safe
-  m->buf[20] = 0;
+  m->buf[20] = 0; // level0-warning{{unsafe buffer access}}
 }
 
 void incomplete(Incomplete *i, unsigned idx) {
@@ -76,7 +76,7 @@ void not_trailing(NotTrailing *n, unsigned idx) {
 }
 
 void union_member(U *u, unsigned idx) {
-  u->buf[idx] = 0;
+  u->buf[idx] = 0; // level01-warning{{unsafe buffer access}}
 }
 
 struct Method {
@@ -84,6 +84,6 @@ struct Method {
   int buf[16];
 
   void set(unsigned idx) {
-    buf[idx] = 0;
+    buf[idx] = 0; // level0-warning{{unsafe buffer access}}
   }
 };
diff --git a/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-static-sized-array-unsafe.cpp b/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-static-sized-array-unsafe.cpp
index 1eb21a5ce25ca9..7152146b2887ad 100644
--- a/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-static-sized-array-unsafe.cpp
+++ b/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-static-sized-array-unsafe.cpp
@@ -20,8 +20,7 @@ struct Trailing {
 };
 
 // A trailing array member is a flexible array member under the default
-// -fstrict-flex-arrays=0, so -fsanitize=array-bounds does not check it, but
-// the opt-out silences it.
+// -fstrict-flex-arrays=0, so -fsanitize=array-bounds does not check it.
 void unsafe_trailing_member(Trailing *t, int idx) {
-  t->buffer[idx] = 0;
+  t->buffer[idx] = 0; // expected-warning {{unsafe buffer access}}
 }



More information about the cfe-commits mailing list