Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4313,9 +4313,9 @@ as ``unsigned __int128`` and C23 ``unsigned _BitInt(N)``.
``__builtin_counted_by_ref`` returns a pointer to the count field from the
``counted_by`` attribute.

The argument must be a flexible array member. If the argument isn't a flexible
array member or doesn't have the ``counted_by`` attribute, the builtin returns
``(void *)0``.
The argument must be a flexible array member or a pointer with the ``counted_by``
attribute. If the argument doesn't have the ``counted_by`` attribute, the builtin
returns ``(void *)0``.

**Syntax**:

Expand Down Expand Up @@ -4346,9 +4346,9 @@ array member or doesn't have the ``counted_by`` attribute, the builtin returns
The ``__builtin_counted_by_ref`` builtin allows the programmer to prevent a
common error associated with the ``counted_by`` attribute. When using the
``counted_by`` attribute, the ``count`` field **must** be set before the
flexible array member can be accessed. Otherwise, the sanitizers may view such
accesses as false positives. For instance, it's not uncommon for programmers to
initialize the flexible array before setting the ``count`` field:
flexible array member or pointer can be accessed. Otherwise, the sanitizers may
view such accesses as false positives. For instance, it's not uncommon for
programmers to initialize the flexible array before setting the ``count`` field:

.. code-block:: c

Expand All @@ -4366,10 +4366,9 @@ initialize the flexible array before setting the ``count`` field:
ptr->count = COUNT;

Enforcing the rule that ``ptr->count = COUNT;`` must occur after every
allocation of a struct with a flexible array member with the ``counted_by``
attribute is prone to failure in large code bases. This builtin mitigates this
for allocators (like in Linux) that are implemented in a way where the counter
assignment can happen automatically.
allocation of a struct with a ``counted_by`` member is prone to failure in large
code bases. This builtin mitigates this for allocators (like in Linux) that are
implemented in a way where the counter assignment can happen automatically.

**Note:** The value returned by ``__builtin_counted_by_ref`` cannot be assigned
to a variable, have its address taken, or passed into or returned from a
Expand Down
5 changes: 3 additions & 2 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -7021,8 +7021,9 @@ def warn_counted_by_attr_elt_type_unknown_size :
InGroup<BoundsSafetyCountedByEltTyUnknownSize>;

// __builtin_counted_by_ref diagnostics:
def err_builtin_counted_by_ref_must_be_flex_array_member : Error<
"'__builtin_counted_by_ref' argument must reference a flexible array member">;
def err_builtin_counted_by_ref_invalid_arg
: Error<"'__builtin_counted_by_ref' argument must reference a member with "
"the 'counted_by' attribute">;
def err_builtin_counted_by_ref_has_side_effects : Error<
"'__builtin_counted_by_ref' argument cannot have side-effects">;

Expand Down
6 changes: 3 additions & 3 deletions clang/lib/CodeGen/CGBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3693,9 +3693,9 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
if (auto *CATy =
ME->getMemberDecl()->getType()->getAs<CountAttributedType>();
CATy && CATy->getKind() == CountAttributedType::CountedBy) {
const auto *FAMDecl = cast<FieldDecl>(ME->getMemberDecl());
if (const FieldDecl *CountFD = FAMDecl->findCountedByField())
Result = GetCountedByFieldExprGEP(Arg, FAMDecl, CountFD);
const auto *MemberDecl = cast<FieldDecl>(ME->getMemberDecl());
if (const FieldDecl *CountFD = MemberDecl->findCountedByField())
Result = GetCountedByFieldExprGEP(Arg, MemberDecl, CountFD);
else
llvm::report_fatal_error("Cannot find the counted_by 'count' field");
}
Expand Down
35 changes: 19 additions & 16 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6362,13 +6362,13 @@ bool Sema::BuiltinCountedByRef(CallExpr *TheCall) {
return true;

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

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

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

if (auto *CATy =
ME->getMemberDecl()->getType()->getAs<CountAttributedType>();
CATy && CATy->getKind() == CountAttributedType::CountedBy) {
const auto *FAMDecl = cast<FieldDecl>(ME->getMemberDecl());
if (const FieldDecl *CountFD = FAMDecl->findCountedByField()) {
if (CATy && CATy->getKind() == CountAttributedType::CountedBy) {
// Member has counted_by attribute - return pointer to count field
const auto *MemberDecl = cast<FieldDecl>(ME->getMemberDecl());
if (const FieldDecl *CountFD = MemberDecl->findCountedByField()) {
TheCall->setType(Context.getPointerType(CountFD->getType()));
return false;
}
}

// FAMs and pointers without counted_by return void*
QualType MemberTy = ME->getMemberDecl()->getType();
if (!MemberTy->isArrayType() && !MemberTy->isPointerType())
return Diag(Arg->getBeginLoc(),
diag::err_builtin_counted_by_ref_invalid_arg)
<< Arg->getSourceRange();
} else {
return Diag(Arg->getBeginLoc(),
diag::err_builtin_counted_by_ref_must_be_flex_array_member)
diag::err_builtin_counted_by_ref_invalid_arg)
<< Arg->getSourceRange();
}

Expand Down
58 changes: 58 additions & 0 deletions clang/test/CodeGen/builtin-counted-by-ref.c
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,61 @@ struct c *test3(int size) {

return p;
}

// Test for pointer member with counted_by attribute
struct d {
int x;
short count;
int *ptr __attribute__((counted_by(count)));
};

// X86_64-LABEL: define dso_local ptr @test4(
// X86_64-SAME: i32 noundef [[SIZE:%.*]], ptr noundef [[DATA:%.*]]) #[[ATTR0]] {
// X86_64-NEXT: [[ENTRY:.*:]]
// X86_64-NEXT: [[SIZE_ADDR:%.*]] = alloca i32, align 4
// X86_64-NEXT: [[DATA_ADDR:%.*]] = alloca ptr, align 8
// X86_64-NEXT: [[P:%.*]] = alloca ptr, align 8
// X86_64-NEXT: store i32 [[SIZE]], ptr [[SIZE_ADDR]], align 4
// X86_64-NEXT: store ptr [[DATA]], ptr [[DATA_ADDR]], align 8
// X86_64-NEXT: [[CALL:%.*]] = call ptr @malloc(i64 noundef 16) #[[ATTR2]]
// X86_64-NEXT: store ptr [[CALL]], ptr [[P]], align 8
// X86_64-NEXT: [[TMP0:%.*]] = load ptr, ptr [[DATA_ADDR]], align 8
// X86_64-NEXT: [[TMP1:%.*]] = load ptr, ptr [[P]], align 8
// X86_64-NEXT: [[PTR:%.*]] = getelementptr inbounds nuw [[STRUCT_D:%.*]], ptr [[TMP1]], i32 0, i32 2
// X86_64-NEXT: store ptr [[TMP0]], ptr [[PTR]], align 8
// X86_64-NEXT: [[TMP2:%.*]] = load i32, ptr [[SIZE_ADDR]], align 4
// X86_64-NEXT: [[CONV:%.*]] = trunc i32 [[TMP2]] to i16
// X86_64-NEXT: [[TMP3:%.*]] = load ptr, ptr [[P]], align 8
// X86_64-NEXT: [[DOT_COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_D]], ptr [[TMP3]], i32 0, i32 1
// X86_64-NEXT: store i16 [[CONV]], ptr [[DOT_COUNTED_BY_GEP]], align 2
// X86_64-NEXT: [[TMP4:%.*]] = load ptr, ptr [[P]], align 8
// X86_64-NEXT: ret ptr [[TMP4]]
//
// I386-LABEL: define dso_local ptr @test4(
// I386-SAME: i32 noundef [[SIZE:%.*]], ptr noundef [[DATA:%.*]]) #[[ATTR0]] {
// I386-NEXT: [[ENTRY:.*:]]
// I386-NEXT: [[SIZE_ADDR:%.*]] = alloca i32, align 4
// I386-NEXT: [[DATA_ADDR:%.*]] = alloca ptr, align 4
// I386-NEXT: [[P:%.*]] = alloca ptr, align 4
// I386-NEXT: store i32 [[SIZE]], ptr [[SIZE_ADDR]], align 4
// I386-NEXT: store ptr [[DATA]], ptr [[DATA_ADDR]], align 4
// I386-NEXT: [[CALL:%.*]] = call ptr @malloc(i32 noundef 12) #[[ATTR2]]
// I386-NEXT: store ptr [[CALL]], ptr [[P]], align 4
// I386-NEXT: [[TMP0:%.*]] = load ptr, ptr [[DATA_ADDR]], align 4
// I386-NEXT: [[TMP1:%.*]] = load ptr, ptr [[P]], align 4
// I386-NEXT: [[PTR:%.*]] = getelementptr inbounds nuw [[STRUCT_D:%.*]], ptr [[TMP1]], i32 0, i32 2
// I386-NEXT: store ptr [[TMP0]], ptr [[PTR]], align 4
// I386-NEXT: [[TMP2:%.*]] = load i32, ptr [[SIZE_ADDR]], align 4
// I386-NEXT: [[CONV:%.*]] = trunc i32 [[TMP2]] to i16
// I386-NEXT: [[TMP3:%.*]] = load ptr, ptr [[P]], align 4
// I386-NEXT: [[DOT_COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_D]], ptr [[TMP3]], i32 0, i32 1
// I386-NEXT: store i16 [[CONV]], ptr [[DOT_COUNTED_BY_GEP]], align 2
// I386-NEXT: [[TMP4:%.*]] = load ptr, ptr [[P]], align 4
// I386-NEXT: ret ptr [[TMP4]]
//
struct d *test4(int size, int *data) {
struct d *p = __builtin_malloc(sizeof(struct d));
p->ptr = data;
*__builtin_counted_by_ref(p->ptr) = size;
return p;
}
87 changes: 75 additions & 12 deletions clang/test/Sema/builtin-counted-by-ref.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ void test2(struct fam_struct *ptr, int idx) {
}

void test3(struct fam_struct *ptr, int idx) {
__builtin_counted_by_ref(&ptr->array[0]); // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}}
__builtin_counted_by_ref(&ptr->array[idx]); // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}}
__builtin_counted_by_ref(&ptr->array); // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}}
__builtin_counted_by_ref(ptr->x); // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}}
__builtin_counted_by_ref(&ptr->x); // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}}
__builtin_counted_by_ref(global_array); // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}}
__builtin_counted_by_ref(global_int); // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}}
__builtin_counted_by_ref(&global_int); // expected-error {{'__builtin_counted_by_ref' argument must reference a flexible array member}}
__builtin_counted_by_ref(&ptr->array[0]); // expected-error {{'__builtin_counted_by_ref' argument must reference a member with the 'counted_by' attribute}}
__builtin_counted_by_ref(&ptr->array[idx]); // expected-error {{'__builtin_counted_by_ref' argument must reference a member with the 'counted_by' attribute}}
__builtin_counted_by_ref(&ptr->array); // expected-error {{'__builtin_counted_by_ref' argument must reference a member with the 'counted_by' attribute}}
__builtin_counted_by_ref(ptr->x); // expected-error {{'__builtin_counted_by_ref' argument must reference a member with the 'counted_by' attribute}}
__builtin_counted_by_ref(&ptr->x); // expected-error {{'__builtin_counted_by_ref' argument must reference a member with the 'counted_by' attribute}}
__builtin_counted_by_ref(global_array); // expected-error {{'__builtin_counted_by_ref' argument must reference a member with the 'counted_by' attribute}}
__builtin_counted_by_ref(global_int); // expected-error {{'__builtin_counted_by_ref' argument must reference a member with the 'counted_by' attribute}}
__builtin_counted_by_ref(&global_int); // expected-error {{'__builtin_counted_by_ref' argument must reference a member with the 'counted_by' attribute}}
}

void test4(struct fam_struct *ptr, int idx) {
Expand Down Expand Up @@ -78,10 +78,12 @@ struct non_fam_struct {
};

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

struct char_count {
Expand Down Expand Up @@ -122,3 +124,64 @@ void test8(void) {
_Static_assert(_Generic(__builtin_counted_by_ref(lp->array), long * : 1, default : 0) == 1, "wrong return type");
_Static_assert(_Generic(__builtin_counted_by_ref(ulp->array), unsigned long * : 1, default : 0) == 1, "wrong return type");
}

// Tests for pointer members with counted_by attribute
struct ptr_char_count {
char count;
int *ptr __attribute__((counted_by(count)));
} *pcp;

struct ptr_short_count {
short count;
int *ptr __attribute__((counted_by(count)));
} *psp;

struct ptr_int_count {
int count;
int *ptr __attribute__((counted_by(count)));
} *pip;

struct ptr_unsigned_count {
unsigned count;
int *ptr __attribute__((counted_by(count)));
} *pup;

struct ptr_long_count {
long count;
int *ptr __attribute__((counted_by(count)));
} *plp;

struct ptr_unsigned_long_count {
unsigned long count;
int *ptr __attribute__((counted_by(count)));
} *pulp;

void test9(struct ptr_int_count *ptr, int size) {
size_t size_of = sizeof(__builtin_counted_by_ref(ptr->ptr)); // ok
int align_of = __alignof(__builtin_counted_by_ref(ptr->ptr)); // ok
size_t __ignored_assignment;

*__builtin_counted_by_ref(ptr->ptr) = size; // ok
*_Generic(__builtin_counted_by_ref(ptr->ptr),
void *: &__ignored_assignment,
default: __builtin_counted_by_ref(ptr->ptr)) = 42; // ok
}

void test10(void) {
_Static_assert(_Generic(__builtin_counted_by_ref(pcp->ptr), char * : 1, default : 0) == 1, "wrong return type");
_Static_assert(_Generic(__builtin_counted_by_ref(psp->ptr), short * : 1, default : 0) == 1, "wrong return type");
_Static_assert(_Generic(__builtin_counted_by_ref(pip->ptr), int * : 1, default : 0) == 1, "wrong return type");
_Static_assert(_Generic(__builtin_counted_by_ref(pup->ptr), unsigned int * : 1, default : 0) == 1, "wrong return type");
_Static_assert(_Generic(__builtin_counted_by_ref(plp->ptr), long * : 1, default : 0) == 1, "wrong return type");
_Static_assert(_Generic(__builtin_counted_by_ref(pulp->ptr), unsigned long * : 1, default : 0) == 1, "wrong return type");
}

// Pointer without counted_by returns void*
struct ptr_no_attr {
int count;
int *ptr; // No counted_by attribute
};

void test11(struct ptr_no_attr *p) {
_Static_assert(_Generic(__builtin_counted_by_ref(p->ptr), void * : 1, default : 0) == 1, "should be void*");
}
Loading