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 <[email protected]> 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 <[email protected]> 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}} } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
