Skip to content

Commit 7220268

Browse files
authored
[Clang] Extend __builtin_counted_by_ref to support pointers with 'counted_by' (#170750)
The __builtin_counted_by_ref builtin was previously limited to flexible array members (FAMs). This change extends it to also support pointer members that have the 'counted_by' attribute. The 'counted_by' attribute can be applied to both FAMs and pointer members: struct fam_struct { int count; int array[] __attribute__((counted_by(count))); }; struct ptr_struct { int count; int *buf __attribute__((counted_by(count))); }; With this change, __builtin_counted_by_ref works with both: *__builtin_counted_by_ref(p->array) = size; // FAM - already worked *__builtin_counted_by_ref(p->buf) = size; // pointer - now works This enables the same allocation pattern for pointer members that was previously only available for FAMs: #define alloc_buf(P, MEMBER, COUNT) ({ \ typeof(P) __p = malloc(sizeof(*__p)); \ __p->MEMBER = malloc(sizeof(*__p->MEMBER) * COUNT); \ *_Generic(__builtin_counted_by_ref(__p->MEMBER), \ void *: &(size_t){0}, \ default: __builtin_counted_by_ref(__p->MEMBER)) = COUNT; \ __p; \ }) The builtin returns: - A pointer to the count field if the member has 'counted_by' - void* (null) if the member is an array or pointer without 'counted_by' - An error for other member types (e.g., int, struct) This was requested by upstream Linux kernel devs: https://lore.kernel.org/linux-hardening/202512041215.44484FCACD@keescook/
1 parent 5911754 commit 7220268

File tree

6 files changed

+167
-43
lines changed

6 files changed

+167
-43
lines changed

clang/docs/LanguageExtensions.rst

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4313,9 +4313,9 @@ as ``unsigned __int128`` and C23 ``unsigned _BitInt(N)``.
43134313
``__builtin_counted_by_ref`` returns a pointer to the count field from the
43144314
``counted_by`` attribute.
43154315
4316-
The argument must be a flexible array member. If the argument isn't a flexible
4317-
array member or doesn't have the ``counted_by`` attribute, the builtin returns
4318-
``(void *)0``.
4316+
The argument must be a flexible array member or a pointer with the ``counted_by``
4317+
attribute. If the argument doesn't have the ``counted_by`` attribute, the builtin
4318+
returns ``(void *)0``.
43194319
43204320
**Syntax**:
43214321
@@ -4346,9 +4346,9 @@ array member or doesn't have the ``counted_by`` attribute, the builtin returns
43464346
The ``__builtin_counted_by_ref`` builtin allows the programmer to prevent a
43474347
common error associated with the ``counted_by`` attribute. When using the
43484348
``counted_by`` attribute, the ``count`` field **must** be set before the
4349-
flexible array member can be accessed. Otherwise, the sanitizers may view such
4350-
accesses as false positives. For instance, it's not uncommon for programmers to
4351-
initialize the flexible array before setting the ``count`` field:
4349+
flexible array member or pointer can be accessed. Otherwise, the sanitizers may
4350+
view such accesses as false positives. For instance, it's not uncommon for
4351+
programmers to initialize the flexible array before setting the ``count`` field:
43524352
43534353
.. code-block:: c
43544354
@@ -4366,10 +4366,9 @@ initialize the flexible array before setting the ``count`` field:
43664366
ptr->count = COUNT;
43674367
43684368
Enforcing the rule that ``ptr->count = COUNT;`` must occur after every
4369-
allocation of a struct with a flexible array member with the ``counted_by``
4370-
attribute is prone to failure in large code bases. This builtin mitigates this
4371-
for allocators (like in Linux) that are implemented in a way where the counter
4372-
assignment can happen automatically.
4369+
allocation of a struct with a ``counted_by`` member is prone to failure in large
4370+
code bases. This builtin mitigates this for allocators (like in Linux) that are
4371+
implemented in a way where the counter assignment can happen automatically.
43734372
43744373
**Note:** The value returned by ``__builtin_counted_by_ref`` cannot be assigned
43754374
to a variable, have its address taken, or passed into or returned from a

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7021,8 +7021,9 @@ def warn_counted_by_attr_elt_type_unknown_size :
70217021
InGroup<BoundsSafetyCountedByEltTyUnknownSize>;
70227022

70237023
// __builtin_counted_by_ref diagnostics:
7024-
def err_builtin_counted_by_ref_must_be_flex_array_member : Error<
7025-
"'__builtin_counted_by_ref' argument must reference a flexible array member">;
7024+
def err_builtin_counted_by_ref_invalid_arg
7025+
: Error<"'__builtin_counted_by_ref' argument must reference a member with "
7026+
"the 'counted_by' attribute">;
70267027
def err_builtin_counted_by_ref_has_side_effects : Error<
70277028
"'__builtin_counted_by_ref' argument cannot have side-effects">;
70287029

clang/lib/CodeGen/CGBuiltin.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3693,9 +3693,9 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
36933693
if (auto *CATy =
36943694
ME->getMemberDecl()->getType()->getAs<CountAttributedType>();
36953695
CATy && CATy->getKind() == CountAttributedType::CountedBy) {
3696-
const auto *FAMDecl = cast<FieldDecl>(ME->getMemberDecl());
3697-
if (const FieldDecl *CountFD = FAMDecl->findCountedByField())
3698-
Result = GetCountedByFieldExprGEP(Arg, FAMDecl, CountFD);
3696+
const auto *MemberDecl = cast<FieldDecl>(ME->getMemberDecl());
3697+
if (const FieldDecl *CountFD = MemberDecl->findCountedByField())
3698+
Result = GetCountedByFieldExprGEP(Arg, MemberDecl, CountFD);
36993699
else
37003700
llvm::report_fatal_error("Cannot find the counted_by 'count' field");
37013701
}

clang/lib/Sema/SemaChecking.cpp

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6362,13 +6362,13 @@ bool Sema::BuiltinCountedByRef(CallExpr *TheCall) {
63626362
return true;
63636363

63646364
// For simplicity, we support only limited expressions for the argument.
6365-
// Specifically a pointer to a flexible array member:'ptr->array'. This
6366-
// allows us to reject arguments with complex casting, which really shouldn't
6367-
// be a huge problem.
6365+
// Specifically a flexible array member or a pointer with counted_by:
6366+
// 'ptr->array' or 'ptr->pointer'. This allows us to reject arguments with
6367+
// complex casting, which really shouldn't be a huge problem.
63686368
const Expr *Arg = ArgRes.get()->IgnoreParenImpCasts();
6369-
if (!isa<PointerType>(Arg->getType()) && !Arg->getType()->isArrayType())
6369+
if (!Arg->getType()->isPointerType() && !Arg->getType()->isArrayType())
63706370
return Diag(Arg->getBeginLoc(),
6371-
diag::err_builtin_counted_by_ref_must_be_flex_array_member)
6371+
diag::err_builtin_counted_by_ref_invalid_arg)
63726372
<< Arg->getSourceRange();
63736373

63746374
if (Arg->HasSideEffects(Context))
@@ -6377,24 +6377,27 @@ bool Sema::BuiltinCountedByRef(CallExpr *TheCall) {
63776377
<< Arg->getSourceRange();
63786378

63796379
if (const auto *ME = dyn_cast<MemberExpr>(Arg)) {
6380-
if (!ME->isFlexibleArrayMemberLike(
6381-
Context, getLangOpts().getStrictFlexArraysLevel()))
6382-
return Diag(Arg->getBeginLoc(),
6383-
diag::err_builtin_counted_by_ref_must_be_flex_array_member)
6384-
<< Arg->getSourceRange();
6380+
const auto *CATy =
6381+
ME->getMemberDecl()->getType()->getAs<CountAttributedType>();
63856382

6386-
if (auto *CATy =
6387-
ME->getMemberDecl()->getType()->getAs<CountAttributedType>();
6388-
CATy && CATy->getKind() == CountAttributedType::CountedBy) {
6389-
const auto *FAMDecl = cast<FieldDecl>(ME->getMemberDecl());
6390-
if (const FieldDecl *CountFD = FAMDecl->findCountedByField()) {
6383+
if (CATy && CATy->getKind() == CountAttributedType::CountedBy) {
6384+
// Member has counted_by attribute - return pointer to count field
6385+
const auto *MemberDecl = cast<FieldDecl>(ME->getMemberDecl());
6386+
if (const FieldDecl *CountFD = MemberDecl->findCountedByField()) {
63916387
TheCall->setType(Context.getPointerType(CountFD->getType()));
63926388
return false;
63936389
}
63946390
}
6391+
6392+
// FAMs and pointers without counted_by return void*
6393+
QualType MemberTy = ME->getMemberDecl()->getType();
6394+
if (!MemberTy->isArrayType() && !MemberTy->isPointerType())
6395+
return Diag(Arg->getBeginLoc(),
6396+
diag::err_builtin_counted_by_ref_invalid_arg)
6397+
<< Arg->getSourceRange();
63956398
} else {
63966399
return Diag(Arg->getBeginLoc(),
6397-
diag::err_builtin_counted_by_ref_must_be_flex_array_member)
6400+
diag::err_builtin_counted_by_ref_invalid_arg)
63986401
<< Arg->getSourceRange();
63996402
}
64006403

clang/test/CodeGen/builtin-counted-by-ref.c

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,61 @@ struct c *test3(int size) {
175175

176176
return p;
177177
}
178+
179+
// Test for pointer member with counted_by attribute
180+
struct d {
181+
int x;
182+
short count;
183+
int *ptr __attribute__((counted_by(count)));
184+
};
185+
186+
// X86_64-LABEL: define dso_local ptr @test4(
187+
// X86_64-SAME: i32 noundef [[SIZE:%.*]], ptr noundef [[DATA:%.*]]) #[[ATTR0]] {
188+
// X86_64-NEXT: [[ENTRY:.*:]]
189+
// X86_64-NEXT: [[SIZE_ADDR:%.*]] = alloca i32, align 4
190+
// X86_64-NEXT: [[DATA_ADDR:%.*]] = alloca ptr, align 8
191+
// X86_64-NEXT: [[P:%.*]] = alloca ptr, align 8
192+
// X86_64-NEXT: store i32 [[SIZE]], ptr [[SIZE_ADDR]], align 4
193+
// X86_64-NEXT: store ptr [[DATA]], ptr [[DATA_ADDR]], align 8
194+
// X86_64-NEXT: [[CALL:%.*]] = call ptr @malloc(i64 noundef 16) #[[ATTR2]]
195+
// X86_64-NEXT: store ptr [[CALL]], ptr [[P]], align 8
196+
// X86_64-NEXT: [[TMP0:%.*]] = load ptr, ptr [[DATA_ADDR]], align 8
197+
// X86_64-NEXT: [[TMP1:%.*]] = load ptr, ptr [[P]], align 8
198+
// X86_64-NEXT: [[PTR:%.*]] = getelementptr inbounds nuw [[STRUCT_D:%.*]], ptr [[TMP1]], i32 0, i32 2
199+
// X86_64-NEXT: store ptr [[TMP0]], ptr [[PTR]], align 8
200+
// X86_64-NEXT: [[TMP2:%.*]] = load i32, ptr [[SIZE_ADDR]], align 4
201+
// X86_64-NEXT: [[CONV:%.*]] = trunc i32 [[TMP2]] to i16
202+
// X86_64-NEXT: [[TMP3:%.*]] = load ptr, ptr [[P]], align 8
203+
// X86_64-NEXT: [[DOT_COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_D]], ptr [[TMP3]], i32 0, i32 1
204+
// X86_64-NEXT: store i16 [[CONV]], ptr [[DOT_COUNTED_BY_GEP]], align 2
205+
// X86_64-NEXT: [[TMP4:%.*]] = load ptr, ptr [[P]], align 8
206+
// X86_64-NEXT: ret ptr [[TMP4]]
207+
//
208+
// I386-LABEL: define dso_local ptr @test4(
209+
// I386-SAME: i32 noundef [[SIZE:%.*]], ptr noundef [[DATA:%.*]]) #[[ATTR0]] {
210+
// I386-NEXT: [[ENTRY:.*:]]
211+
// I386-NEXT: [[SIZE_ADDR:%.*]] = alloca i32, align 4
212+
// I386-NEXT: [[DATA_ADDR:%.*]] = alloca ptr, align 4
213+
// I386-NEXT: [[P:%.*]] = alloca ptr, align 4
214+
// I386-NEXT: store i32 [[SIZE]], ptr [[SIZE_ADDR]], align 4
215+
// I386-NEXT: store ptr [[DATA]], ptr [[DATA_ADDR]], align 4
216+
// I386-NEXT: [[CALL:%.*]] = call ptr @malloc(i32 noundef 12) #[[ATTR2]]
217+
// I386-NEXT: store ptr [[CALL]], ptr [[P]], align 4
218+
// I386-NEXT: [[TMP0:%.*]] = load ptr, ptr [[DATA_ADDR]], align 4
219+
// I386-NEXT: [[TMP1:%.*]] = load ptr, ptr [[P]], align 4
220+
// I386-NEXT: [[PTR:%.*]] = getelementptr inbounds nuw [[STRUCT_D:%.*]], ptr [[TMP1]], i32 0, i32 2
221+
// I386-NEXT: store ptr [[TMP0]], ptr [[PTR]], align 4
222+
// I386-NEXT: [[TMP2:%.*]] = load i32, ptr [[SIZE_ADDR]], align 4
223+
// I386-NEXT: [[CONV:%.*]] = trunc i32 [[TMP2]] to i16
224+
// I386-NEXT: [[TMP3:%.*]] = load ptr, ptr [[P]], align 4
225+
// I386-NEXT: [[DOT_COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_D]], ptr [[TMP3]], i32 0, i32 1
226+
// I386-NEXT: store i16 [[CONV]], ptr [[DOT_COUNTED_BY_GEP]], align 2
227+
// I386-NEXT: [[TMP4:%.*]] = load ptr, ptr [[P]], align 4
228+
// I386-NEXT: ret ptr [[TMP4]]
229+
//
230+
struct d *test4(int size, int *data) {
231+
struct d *p = __builtin_malloc(sizeof(struct d));
232+
p->ptr = data;
233+
*__builtin_counted_by_ref(p->ptr) = size;
234+
return p;
235+
}

clang/test/Sema/builtin-counted-by-ref.c

Lines changed: 75 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,14 @@ void test2(struct fam_struct *ptr, int idx) {
3232
}
3333

3434
void test3(struct fam_struct *ptr, int idx) {
35-
__builtin_counted_by_ref(&ptr->array[0]); // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}}
36-
__builtin_counted_by_ref(&ptr->array[idx]); // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}}
37-
__builtin_counted_by_ref(&ptr->array); // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}}
38-
__builtin_counted_by_ref(ptr->x); // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}}
39-
__builtin_counted_by_ref(&ptr->x); // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}}
40-
__builtin_counted_by_ref(global_array); // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}}
41-
__builtin_counted_by_ref(global_int); // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}}
42-
__builtin_counted_by_ref(&global_int); // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}}
35+
__builtin_counted_by_ref(&ptr->array[0]); // expected-error {{'__builtin_counted_by_ref' argument must reference a member with the 'counted_by' attribute}}
36+
__builtin_counted_by_ref(&ptr->array[idx]); // expected-error {{'__builtin_counted_by_ref' argument must reference a member with the 'counted_by' attribute}}
37+
__builtin_counted_by_ref(&ptr->array); // expected-error {{'__builtin_counted_by_ref' argument must reference a member with the 'counted_by' attribute}}
38+
__builtin_counted_by_ref(ptr->x); // expected-error {{'__builtin_counted_by_ref' argument must reference a member with the 'counted_by' attribute}}
39+
__builtin_counted_by_ref(&ptr->x); // expected-error {{'__builtin_counted_by_ref' argument must reference a member with the 'counted_by' attribute}}
40+
__builtin_counted_by_ref(global_array); // expected-error {{'__builtin_counted_by_ref' argument must reference a member with the 'counted_by' attribute}}
41+
__builtin_counted_by_ref(global_int); // expected-error {{'__builtin_counted_by_ref' argument must reference a member with the 'counted_by' attribute}}
42+
__builtin_counted_by_ref(&global_int); // expected-error {{'__builtin_counted_by_ref' argument must reference a member with the 'counted_by' attribute}}
4343
}
4444

4545
void test4(struct fam_struct *ptr, int idx) {
@@ -78,10 +78,12 @@ struct non_fam_struct {
7878
};
7979

8080
void *test7(struct non_fam_struct *ptr, int size) {
81-
*__builtin_counted_by_ref(ptr->array) = size // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}}
82-
*__builtin_counted_by_ref(&ptr->array[0]) = size; // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}}
83-
*__builtin_counted_by_ref(ptr->pointer) = size; // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}}
84-
*__builtin_counted_by_ref(&ptr->pointer[0]) = size; // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}}
81+
// Arrays and pointers without counted_by return void*
82+
_Static_assert(_Generic(__builtin_counted_by_ref(ptr->array), void * : 1, default : 0) == 1, "should be void*");
83+
_Static_assert(_Generic(__builtin_counted_by_ref(ptr->pointer), void * : 1, default : 0) == 1, "should be void*");
84+
// These are not direct member accesses, so they error
85+
__builtin_counted_by_ref(&ptr->array[0]); // expected-error {{'__builtin_counted_by_ref' argument must reference a member with the 'counted_by' attribute}}
86+
__builtin_counted_by_ref(&ptr->pointer[0]); // expected-error {{'__builtin_counted_by_ref' argument must reference a member with the 'counted_by' attribute}}
8587
}
8688

8789
struct char_count {
@@ -122,3 +124,64 @@ void test8(void) {
122124
_Static_assert(_Generic(__builtin_counted_by_ref(lp->array), long * : 1, default : 0) == 1, "wrong return type");
123125
_Static_assert(_Generic(__builtin_counted_by_ref(ulp->array), unsigned long * : 1, default : 0) == 1, "wrong return type");
124126
}
127+
128+
// Tests for pointer members with counted_by attribute
129+
struct ptr_char_count {
130+
char count;
131+
int *ptr __attribute__((counted_by(count)));
132+
} *pcp;
133+
134+
struct ptr_short_count {
135+
short count;
136+
int *ptr __attribute__((counted_by(count)));
137+
} *psp;
138+
139+
struct ptr_int_count {
140+
int count;
141+
int *ptr __attribute__((counted_by(count)));
142+
} *pip;
143+
144+
struct ptr_unsigned_count {
145+
unsigned count;
146+
int *ptr __attribute__((counted_by(count)));
147+
} *pup;
148+
149+
struct ptr_long_count {
150+
long count;
151+
int *ptr __attribute__((counted_by(count)));
152+
} *plp;
153+
154+
struct ptr_unsigned_long_count {
155+
unsigned long count;
156+
int *ptr __attribute__((counted_by(count)));
157+
} *pulp;
158+
159+
void test9(struct ptr_int_count *ptr, int size) {
160+
size_t size_of = sizeof(__builtin_counted_by_ref(ptr->ptr)); // ok
161+
int align_of = __alignof(__builtin_counted_by_ref(ptr->ptr)); // ok
162+
size_t __ignored_assignment;
163+
164+
*__builtin_counted_by_ref(ptr->ptr) = size; // ok
165+
*_Generic(__builtin_counted_by_ref(ptr->ptr),
166+
void *: &__ignored_assignment,
167+
default: __builtin_counted_by_ref(ptr->ptr)) = 42; // ok
168+
}
169+
170+
void test10(void) {
171+
_Static_assert(_Generic(__builtin_counted_by_ref(pcp->ptr), char * : 1, default : 0) == 1, "wrong return type");
172+
_Static_assert(_Generic(__builtin_counted_by_ref(psp->ptr), short * : 1, default : 0) == 1, "wrong return type");
173+
_Static_assert(_Generic(__builtin_counted_by_ref(pip->ptr), int * : 1, default : 0) == 1, "wrong return type");
174+
_Static_assert(_Generic(__builtin_counted_by_ref(pup->ptr), unsigned int * : 1, default : 0) == 1, "wrong return type");
175+
_Static_assert(_Generic(__builtin_counted_by_ref(plp->ptr), long * : 1, default : 0) == 1, "wrong return type");
176+
_Static_assert(_Generic(__builtin_counted_by_ref(pulp->ptr), unsigned long * : 1, default : 0) == 1, "wrong return type");
177+
}
178+
179+
// Pointer without counted_by returns void*
180+
struct ptr_no_attr {
181+
int count;
182+
int *ptr; // No counted_by attribute
183+
};
184+
185+
void test11(struct ptr_no_attr *p) {
186+
_Static_assert(_Generic(__builtin_counted_by_ref(p->ptr), void * : 1, default : 0) == 1, "should be void*");
187+
}

0 commit comments

Comments
 (0)