https://github.com/davidmenggx updated https://github.com/llvm/llvm-project/pull/206760
>From 8e9caafb90100a74060177bd1b12756102864912 Mon Sep 17 00:00:00 2001 From: David Meng <[email protected]> Date: Tue, 30 Jun 2026 09:01:01 -0700 Subject: [PATCH 1/3] [Clang] Add -Wcounted-by-addrof to warn when &fam discards __counted_by Taking the address of a whole `__counted_by` flexible array member (`&p->fam`) bypasses `emitCountedBySize()` in CodeGen and falls through to layout-derived `llvm.objectsize`, silently discarding the annotation. Under `-fbounds-safety` this is already an error. This adds a warning `-Wcounted-by-addrof` (default off) for non-bounds-safety mode. The warning fires when unary `&` is applied to the FAM as a whole. `&fam[idx]`, the decayed `fam`, and `__counted_by` pointer fields are excluded. A fix-it is offered only when the object's allocation is not statically known (pointer bases, opaque returns, etc.), where the count-derived bound is the useful answer. Closes #206536 --- clang/docs/ReleaseNotes.md | 5 ++ clang/include/clang/Basic/DiagnosticGroups.td | 1 + .../clang/Basic/DiagnosticSemaKinds.td | 6 ++ clang/lib/Sema/SemaExpr.cpp | 66 ++++++++++++++++ clang/test/Sema/warn-counted-by-addrof.c | 77 +++++++++++++++++++ 5 files changed, 155 insertions(+) create mode 100644 clang/test/Sema/warn-counted-by-addrof.c diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 9301745b9628e..1a246419b7b0a 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -290,6 +290,11 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the against or converted to a null pointer, the same as a bare function name. (#GH46362) +- Added `-Wcounted-by-addrof` (default off) warning when unary `&` is applied to a `__counted_by` + flexible array member as a whole (e.g. `&p->fam`). This form bypasses the count when lowering + `__builtin_dynamic_object_size` and falls back to the object's static layout, silently discarding + the annotation. A fix-it removes the `&` when the object's allocation is not statically known, + where the count-derived bound is the useful answer. ### Improvements to Clang's time-trace diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 79583534b9bbd..bb3c2a8f57436 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1869,6 +1869,7 @@ def NoDeref : DiagGroup<"noderef">; // -fbounds-safety and bounds annotation related warnings def BoundsSafetyCountedByEltTyUnknownSize : DiagGroup<"bounds-safety-counted-by-elt-type-unknown-size">; +def CountedByAddrof : DiagGroup<"counted-by-addrof">; // A group for cross translation unit static analysis related warnings. def CrossTU : DiagGroup<"ctu">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 89e2f956971b3..b4cb0ff4e274b 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -7230,6 +7230,12 @@ def note_counted_by_consider_using_sized_by : Note< def warn_counted_by_attr_elt_type_unknown_size : Warning<err_counted_by_attr_pointee_unknown_size.Summary>, InGroup<BoundsSafetyCountedByEltTyUnknownSize>; +def warn_counted_by_addrof_discards_count : Warning< + "taking the address of flexible array member %0 discards the " + "'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}1' bound; " + "'__builtin_dynamic_object_size' on the resulting pointer uses the static " + "layout of the object, not the annotated count">, + InGroup<CountedByAddrof>, DefaultIgnore; // __builtin_counted_by_ref diagnostics: def err_builtin_counted_by_ref_invalid_arg diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index b844670543a55..d6173a75fc24e 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -14970,6 +14970,68 @@ bool Sema::CheckUseOfCXXMethodAsAddressOfOperand(SourceLocation OpLoc, << FixItHint::CreateInsertion(DRE->getSourceRange().getBegin(), Qual); } +/// Returns true if the object holding the FAM reached by \p ME has a +/// statically-known allocation (automatic/static/global). There the layout +/// answer __bdos already gives is the real bound, so we offer no fix-it. Only +/// when the object is reached through a pointer does the count carry the useful +/// bound and the "drop the '&'" fix-it apply. +static bool flexibleArrayMemberHasFixedStorage(const MemberExpr *ME) { + const Expr *E = ME; + while (true) { + if (const auto *M = dyn_cast<MemberExpr>(E)) { + if (M->isArrow()) + return false; // Reached through a pointer. + E = M->getBase()->IgnoreParenImpCasts(); + continue; + } + if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(E)) { + const Expr *Base = ASE->getBase()->IgnoreParenImpCasts(); + // Array subscript keeps the storage, pointer subscript escapes it. + if (Base->getType()->isPointerType()) + return false; + E = Base; + continue; + } + if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) { + const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()); + // Locals, params, statics and globals all have a fixed allocation. + return VD && (VD->hasLocalStorage() || VD->hasGlobalStorage()); + } + return false; // Call temporaries, opaque returns, etc. + } +} + +/// -Wcounted-by-addrof: warn when unary '&' is applied to a whole +/// '__counted_by' flexible array member. CodeGen bypasses emitCountedBySize() +/// for this shape and falls back to layout-derived llvm.objectsize, discarding +/// the count. +static void DiagnoseCountedByAddrOf(Sema &S, SourceLocation OpLoc, Expr *Op) { + if (S.getLangOpts().BoundsSafety) + return; // Already an error under -fbounds-safety. + + const auto *ME = dyn_cast<MemberExpr>(Op->IgnoreParens()); + if (!ME) + return; + + const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl()); + if (!FD) + return; + + // The "whole FAM" shape is an incomplete array carrying a bounds attribute; + // this excludes &fam[idx], the decayed bare fam, and __counted_by pointers. + const auto *CATy = FD->getType()->getAs<CountAttributedType>(); + if (!CATy || !CATy->desugar()->isIncompleteArrayType()) + return; + + auto DB = S.Diag(OpLoc, diag::warn_counted_by_addrof_discards_count) + << FD << static_cast<unsigned>(CATy->getKind()) + << Op->getSourceRange(); + + // Offer "remove the '&'" only when the count is the useful bound. + if (!flexibleArrayMemberHasFixedStorage(ME)) + DB << FixItHint::CreateRemoval(SourceRange(OpLoc, OpLoc)); +} + QualType Sema::CheckAddressOfOperand(ExprResult &OrigOp, SourceLocation OpLoc) { if (const BuiltinType *PTy = OrigOp.get()->getType()->getAsPlaceholderType()){ if (PTy->getKind() == BuiltinType::Overload) { @@ -15013,6 +15075,10 @@ QualType Sema::CheckAddressOfOperand(ExprResult &OrigOp, SourceLocation OpLoc) { // Make sure to ignore parentheses in subsequent checks Expr *op = OrigOp.get()->IgnoreParens(); + // -Wcounted-by-addrof: '&' on a __counted_by flexible array member silently + // discards the count in CodeGen. + DiagnoseCountedByAddrOf(*this, OpLoc, op); + // In OpenCL captures for blocks called as lambda functions // are located in the private address space. Blocks used in // enqueue_kernel can be located in a different address space diff --git a/clang/test/Sema/warn-counted-by-addrof.c b/clang/test/Sema/warn-counted-by-addrof.c new file mode 100644 index 0000000000000..3b241ada42816 --- /dev/null +++ b/clang/test/Sema/warn-counted-by-addrof.c @@ -0,0 +1,77 @@ +// RUN: %clang_cc1 -fsyntax-only -verify=enabled -Wcounted-by-addrof %s +// RUN: %clang_cc1 -fsyntax-only -verify=disabled %s +// RUN: %clang_cc1 -fsyntax-only -Wcounted-by-addrof -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s + +// disabled-no-diagnostics + +#define __counted_by(f) __attribute__((counted_by(f))) + +typedef __SIZE_TYPE__ size_t; + +struct annotated_flex { + size_t count; + char induce_padding; + char fam[] __counted_by(count); +}; + +struct plain_flex { + size_t count; + char fam[]; +}; + +struct annotated_flex *get_ptr(void); + +size_t ptr_addrof(struct annotated_flex *p) { + return __builtin_dynamic_object_size(&p->fam, 1); // enabled-warning {{taking the address of flexible array member 'fam' discards the 'counted_by' bound}} +} + +// Opaque pointer return. Allocation not statically known. fix-it offered. +size_t ret_addrof(void) { + return __builtin_dynamic_object_size(&get_ptr()->fam, 1); // enabled-warning {{taking the address of flexible array member 'fam' discards the 'counted_by' bound}} +} + +// Subscripting a pointer escapes to an unknown allocation. fix-it offered. +size_t ptr_subscript_addrof(struct annotated_flex *parr, int i) { + return __builtin_dynamic_object_size(&parr[i].fam, 1); // enabled-warning {{taking the address of flexible array member 'fam' discards the 'counted_by' bound}} +} + +struct annotated_flex gaf; + +// Global (static storage). No fix-it. +size_t global_addrof(void) { + return __builtin_dynamic_object_size(&gaf.fam, 1); // enabled-warning {{taking the address of flexible array member 'fam' discards the 'counted_by' bound}} +} + +// Local (automatic storage). No fix-it. +size_t local_addrof(size_t n) { + struct annotated_flex af; + af.count = n; + return __builtin_dynamic_object_size(&af.fam, 1); // enabled-warning {{taking the address of flexible array member 'fam' discards the 'counted_by' bound}} +} + +// Static local (static storage, local scope). No fix-it. +size_t static_local_addrof(void) { + static struct annotated_flex saf; + return __builtin_dynamic_object_size(&saf.fam, 1); // enabled-warning {{taking the address of flexible array member 'fam' discards the 'counted_by' bound}} +} + +size_t decayed(struct annotated_flex *p) { + // Decayed pointer-to-element honors the count. There is no '&'. + return __builtin_dynamic_object_size(p->fam, 1); +} + +size_t element_addrof(struct annotated_flex *p, int i) { + // Address of an element, not the FAM-as-a-whole. + return __builtin_dynamic_object_size(&p->fam[i], 1); +} + +char *count_addrof(struct annotated_flex *p) { + return (char *)&p->count; // non-FAM field. +} + +char (*plain_addrof(struct plain_flex *p))[] { + return &p->fam; // FAM without counted_by. +} + +// CHECK-COUNT-3: fix-it:{{.*}}:"" +// CHECK-NOT: fix-it: \ No newline at end of file >From 6f69ddc29a6e371e0d4cd80395a90a847f490a54 Mon Sep 17 00:00:00 2001 From: David Meng <[email protected]> Date: Sun, 19 Jul 2026 10:36:45 -0700 Subject: [PATCH 2/3] Switch to default-on and narrow the cases --- clang/docs/ReleaseNotes.md | 10 +-- .../clang/Basic/DiagnosticSemaKinds.td | 2 +- clang/lib/Sema/SemaChecking.cpp | 83 +++++++++++++++++++ clang/lib/Sema/SemaExpr.cpp | 66 --------------- clang/test/Sema/warn-counted-by-addrof.c | 40 +++++---- 5 files changed, 115 insertions(+), 86 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 1a246419b7b0a..40bf338c18564 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -290,11 +290,11 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the against or converted to a null pointer, the same as a bare function name. (#GH46362) -- Added `-Wcounted-by-addrof` (default off) warning when unary `&` is applied to a `__counted_by` - flexible array member as a whole (e.g. `&p->fam`). This form bypasses the count when lowering - `__builtin_dynamic_object_size` and falls back to the object's static layout, silently discarding - the annotation. A fix-it removes the `&` when the object's allocation is not statically known, - where the count-derived bound is the useful answer. +- Added the `-Wcounted-by-addrof` warning (on by default) for + `__builtin_dynamic_object_size(&p->fam, 1)` where `fam` is a `__counted_by` flexible array member. + Taking the array's address makes the builtin ignore the count and use the object's static layout + instead. The warning fires only when the array is reached through a pointer, where the count is the + useful bound; a fix-it removes the `&`. ### Improvements to Clang's time-trace diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index b4cb0ff4e274b..9e74c528cfed3 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -7235,7 +7235,7 @@ def warn_counted_by_addrof_discards_count : Warning< "'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}1' bound; " "'__builtin_dynamic_object_size' on the resulting pointer uses the static " "layout of the object, not the annotated count">, - InGroup<CountedByAddrof>, DefaultIgnore; + InGroup<CountedByAddrof>; // __builtin_counted_by_ref diagnostics: def err_builtin_counted_by_ref_invalid_arg diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index ef07e8c6133ed..2c21e11fcbb1c 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -3044,6 +3044,85 @@ static QualType getVectorElementType(ASTContext &Context, QualType VecTy) { return QualType(); } +/// Returns true when the object holding the flexible array member reached by +/// \p ME has a fixed allocation (a local, parameter, static, or global +/// variable). A count cannot enlarge a fixed allocation, so '&fam' loses +/// nothing useful there. Returns false when the member is reached through a +/// pointer, where the count is the bound the layout fallback would lose. +static bool flexibleArrayMemberHasFixedStorage(const MemberExpr *ME) { + const Expr *E = ME; + while (true) { + if (const auto *M = dyn_cast<MemberExpr>(E)) { + if (M->isArrow()) + return false; // Reached through a pointer. + E = M->getBase()->IgnoreParenImpCasts(); + continue; + } + if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(E)) { + const Expr *Base = ASE->getBase()->IgnoreParenImpCasts(); + // Array subscript keeps the storage, pointer subscript escapes it. + if (Base->getType()->isPointerType()) + return false; + E = Base; + continue; + } + if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) { + const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()); + // Locals, params, statics and globals all have a fixed allocation. + return VD && (VD->hasLocalStorage() || VD->hasGlobalStorage()); + } + return false; // Call temporaries, opaque returns, etc. + } +} + +/// -Wcounted-by-addrof: warn when the pointer argument of +/// '__builtin_dynamic_object_size' is '&' applied to a whole '__counted_by' +/// flexible array member (e.g. '__bdos(&p->fam, 1)'). Taking the array's +/// address makes the builtin ignore the count and use the object's static +/// layout instead. The warning fires only when the member is reached through a +/// pointer: there the layout gives -1 ("unknown") and the count is the useful +/// bound. When the object has a fixed allocation the layout answer is already +/// correct, so we stay quiet. +static void DiagnoseCountedByAddrOfDynamicObjectSize(Sema &S, + const CallExpr *Call) { + if (S.getLangOpts().BoundsSafety) + return; // Already an error under -fbounds-safety. + + if (Call->getNumArgs() < 1) + return; + + // The pointer argument must be the address of an lvalue: '&<expr>'. + const auto *UO = dyn_cast<UnaryOperator>( + Call->getArg(0)->IgnoreParenImpCasts()); + if (!UO || UO->getOpcode() != UO_AddrOf) + return; + + const auto *ME = dyn_cast<MemberExpr>(UO->getSubExpr()->IgnoreParens()); + if (!ME) + return; + + const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl()); + if (!FD) + return; + + // Match a whole flexible array member: an incomplete array (T[]) with a + // __counted_by attribute. Excludes &fam[idx], the decayed 'fam', and + // __counted_by pointer fields. + const auto *CATy = FD->getType()->getAs<CountAttributedType>(); + if (!CATy || !CATy->desugar()->isIncompleteArrayType()) + return; + + // A fixed allocation already gives the correct layout answer; nothing lost. + if (flexibleArrayMemberHasFixedStorage(ME)) + return; + + // Warn and offer to drop the '&' so the count-honoring decayed form is used. + S.Diag(UO->getOperatorLoc(), diag::warn_counted_by_addrof_discards_count) + << FD << static_cast<unsigned>(CATy->getKind()) << UO->getSourceRange() + << FixItHint::CreateRemoval( + SourceRange(UO->getOperatorLoc(), UO->getOperatorLoc())); +} + ExprResult Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID, CallExpr *TheCall) { @@ -3267,6 +3346,10 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID, return ExprError(); break; case Builtin::BI__builtin_dynamic_object_size: + if (BuiltinConstantArgRange(TheCall, 1, 0, 3)) + return ExprError(); + DiagnoseCountedByAddrOfDynamicObjectSize(*this, TheCall); + break; case Builtin::BI__builtin_object_size: if (BuiltinConstantArgRange(TheCall, 1, 0, 3)) return ExprError(); diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index d6173a75fc24e..b844670543a55 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -14970,68 +14970,6 @@ bool Sema::CheckUseOfCXXMethodAsAddressOfOperand(SourceLocation OpLoc, << FixItHint::CreateInsertion(DRE->getSourceRange().getBegin(), Qual); } -/// Returns true if the object holding the FAM reached by \p ME has a -/// statically-known allocation (automatic/static/global). There the layout -/// answer __bdos already gives is the real bound, so we offer no fix-it. Only -/// when the object is reached through a pointer does the count carry the useful -/// bound and the "drop the '&'" fix-it apply. -static bool flexibleArrayMemberHasFixedStorage(const MemberExpr *ME) { - const Expr *E = ME; - while (true) { - if (const auto *M = dyn_cast<MemberExpr>(E)) { - if (M->isArrow()) - return false; // Reached through a pointer. - E = M->getBase()->IgnoreParenImpCasts(); - continue; - } - if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(E)) { - const Expr *Base = ASE->getBase()->IgnoreParenImpCasts(); - // Array subscript keeps the storage, pointer subscript escapes it. - if (Base->getType()->isPointerType()) - return false; - E = Base; - continue; - } - if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) { - const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()); - // Locals, params, statics and globals all have a fixed allocation. - return VD && (VD->hasLocalStorage() || VD->hasGlobalStorage()); - } - return false; // Call temporaries, opaque returns, etc. - } -} - -/// -Wcounted-by-addrof: warn when unary '&' is applied to a whole -/// '__counted_by' flexible array member. CodeGen bypasses emitCountedBySize() -/// for this shape and falls back to layout-derived llvm.objectsize, discarding -/// the count. -static void DiagnoseCountedByAddrOf(Sema &S, SourceLocation OpLoc, Expr *Op) { - if (S.getLangOpts().BoundsSafety) - return; // Already an error under -fbounds-safety. - - const auto *ME = dyn_cast<MemberExpr>(Op->IgnoreParens()); - if (!ME) - return; - - const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl()); - if (!FD) - return; - - // The "whole FAM" shape is an incomplete array carrying a bounds attribute; - // this excludes &fam[idx], the decayed bare fam, and __counted_by pointers. - const auto *CATy = FD->getType()->getAs<CountAttributedType>(); - if (!CATy || !CATy->desugar()->isIncompleteArrayType()) - return; - - auto DB = S.Diag(OpLoc, diag::warn_counted_by_addrof_discards_count) - << FD << static_cast<unsigned>(CATy->getKind()) - << Op->getSourceRange(); - - // Offer "remove the '&'" only when the count is the useful bound. - if (!flexibleArrayMemberHasFixedStorage(ME)) - DB << FixItHint::CreateRemoval(SourceRange(OpLoc, OpLoc)); -} - QualType Sema::CheckAddressOfOperand(ExprResult &OrigOp, SourceLocation OpLoc) { if (const BuiltinType *PTy = OrigOp.get()->getType()->getAsPlaceholderType()){ if (PTy->getKind() == BuiltinType::Overload) { @@ -15075,10 +15013,6 @@ QualType Sema::CheckAddressOfOperand(ExprResult &OrigOp, SourceLocation OpLoc) { // Make sure to ignore parentheses in subsequent checks Expr *op = OrigOp.get()->IgnoreParens(); - // -Wcounted-by-addrof: '&' on a __counted_by flexible array member silently - // discards the count in CodeGen. - DiagnoseCountedByAddrOf(*this, OpLoc, op); - // In OpenCL captures for blocks called as lambda functions // are located in the private address space. Blocks used in // enqueue_kernel can be located in a different address space diff --git a/clang/test/Sema/warn-counted-by-addrof.c b/clang/test/Sema/warn-counted-by-addrof.c index 3b241ada42816..0fa27bbd38fc7 100644 --- a/clang/test/Sema/warn-counted-by-addrof.c +++ b/clang/test/Sema/warn-counted-by-addrof.c @@ -1,6 +1,6 @@ -// RUN: %clang_cc1 -fsyntax-only -verify=enabled -Wcounted-by-addrof %s -// RUN: %clang_cc1 -fsyntax-only -verify=disabled %s -// RUN: %clang_cc1 -fsyntax-only -Wcounted-by-addrof -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s +// RUN: %clang_cc1 -fsyntax-only -verify %s +// RUN: %clang_cc1 -fsyntax-only -verify=disabled -Wno-counted-by-addrof %s +// RUN: %clang_cc1 -fsyntax-only -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s // disabled-no-diagnostics @@ -22,37 +22,37 @@ struct plain_flex { struct annotated_flex *get_ptr(void); size_t ptr_addrof(struct annotated_flex *p) { - return __builtin_dynamic_object_size(&p->fam, 1); // enabled-warning {{taking the address of flexible array member 'fam' discards the 'counted_by' bound}} + return __builtin_dynamic_object_size(&p->fam, 1); // expected-warning {{taking the address of flexible array member 'fam' discards the 'counted_by' bound}} } // Opaque pointer return. Allocation not statically known. fix-it offered. size_t ret_addrof(void) { - return __builtin_dynamic_object_size(&get_ptr()->fam, 1); // enabled-warning {{taking the address of flexible array member 'fam' discards the 'counted_by' bound}} + return __builtin_dynamic_object_size(&get_ptr()->fam, 1); // expected-warning {{taking the address of flexible array member 'fam' discards the 'counted_by' bound}} } // Subscripting a pointer escapes to an unknown allocation. fix-it offered. size_t ptr_subscript_addrof(struct annotated_flex *parr, int i) { - return __builtin_dynamic_object_size(&parr[i].fam, 1); // enabled-warning {{taking the address of flexible array member 'fam' discards the 'counted_by' bound}} + return __builtin_dynamic_object_size(&parr[i].fam, 1); // expected-warning {{taking the address of flexible array member 'fam' discards the 'counted_by' bound}} } struct annotated_flex gaf; -// Global (static storage). No fix-it. +// Global: fixed allocation, so the layout answer is already correct. No warning. size_t global_addrof(void) { - return __builtin_dynamic_object_size(&gaf.fam, 1); // enabled-warning {{taking the address of flexible array member 'fam' discards the 'counted_by' bound}} + return __builtin_dynamic_object_size(&gaf.fam, 1); } -// Local (automatic storage). No fix-it. +// Local: fixed allocation. No warning. size_t local_addrof(size_t n) { struct annotated_flex af; af.count = n; - return __builtin_dynamic_object_size(&af.fam, 1); // enabled-warning {{taking the address of flexible array member 'fam' discards the 'counted_by' bound}} + return __builtin_dynamic_object_size(&af.fam, 1); } -// Static local (static storage, local scope). No fix-it. +// Static local (static storage, local scope). No warning. size_t static_local_addrof(void) { static struct annotated_flex saf; - return __builtin_dynamic_object_size(&saf.fam, 1); // enabled-warning {{taking the address of flexible array member 'fam' discards the 'counted_by' bound}} + return __builtin_dynamic_object_size(&saf.fam, 1); } size_t decayed(struct annotated_flex *p) { @@ -61,10 +61,22 @@ size_t decayed(struct annotated_flex *p) { } size_t element_addrof(struct annotated_flex *p, int i) { - // Address of an element, not the FAM-as-a-whole. + // Address of an element, not the whole array. return __builtin_dynamic_object_size(&p->fam[i], 1); } +// __builtin_object_size (non-dynamic) never consults the count, so &fam +// discards nothing here. +size_t static_object_size(struct annotated_flex *p) { + return __builtin_object_size(&p->fam, 1); +} + +// Taking the address outside of __builtin_dynamic_object_size is not flagged: +// the count is only silently discarded in the __bdos lowering. +char (*bare_addrof(struct annotated_flex *p))[] { + return &p->fam; +} + char *count_addrof(struct annotated_flex *p) { return (char *)&p->count; // non-FAM field. } @@ -74,4 +86,4 @@ char (*plain_addrof(struct plain_flex *p))[] { } // CHECK-COUNT-3: fix-it:{{.*}}:"" -// CHECK-NOT: fix-it: \ No newline at end of file +// CHECK-NOT: fix-it: >From 3898b10a92fc1a2a9849b0e6926e2c329beddcac Mon Sep 17 00:00:00 2001 From: David Meng <[email protected]> Date: Sun, 19 Jul 2026 13:06:27 -0700 Subject: [PATCH 3/3] Formatting --- clang/lib/Sema/SemaChecking.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 2c21e11fcbb1c..e643f5edfe782 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -3092,8 +3092,8 @@ static void DiagnoseCountedByAddrOfDynamicObjectSize(Sema &S, return; // The pointer argument must be the address of an lvalue: '&<expr>'. - const auto *UO = dyn_cast<UnaryOperator>( - Call->getArg(0)->IgnoreParenImpCasts()); + const auto *UO = + dyn_cast<UnaryOperator>(Call->getArg(0)->IgnoreParenImpCasts()); if (!UO || UO->getOpcode() != UO_AddrOf) return; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
