[Clang] Add __builtin_counted_by_ref builtin (#114495)

The __builtin_counted_by_ref builtin is used on a flexible array
pointer and returns a pointer to the "counted_by" attribute's COUNT
argument, which is a field in the same non-anonymous struct as the
flexible array member. This is useful for automatically setting the
count field without needing the programmer's intervention. Otherwise
it's possible to get this anti-pattern:
    
      ptr = alloc(<ty>, ..., COUNT);
      ptr->FAM[9] = 42; /* <<< Sanitizer will complain */
      ptr->count = COUNT;
    
To prevent this anti-pattern, the user can create an allocator that
automatically performs the assignment:
    
      #define alloc(TY, FAM, COUNT) ({ \
          TY __p = alloc(get_size(TY, COUNT));             \
          if (__builtin_counted_by_ref(__p->FAM))          \
              *__builtin_counted_by_ref(__p->FAM) = COUNT; \
          __p;                                             \
      })

The builtin's behavior is heavily dependent upon the "counted_by"
attribute existing. It's main utility is during allocation to avoid
the above anti-pattern. If the flexible array member doesn't have that
attribute, the builtin becomes a no-op. Therefore, if the flexible
array member has a "count" field not referenced by "counted_by", it
must be set explicitly after the allocation as this builtin will
return a "nullptr" and the assignment will most likely be elided.

---------

Co-authored-by: Bill Wendling <isanbard@gmail.com>
Co-authored-by: Aaron Ballman <aaron@aaronballman.com>
This commit is contained in:
Bill Wendling 2024-11-07 22:03:55 +00:00 committed by GitHub
parent ae9d0623ad
commit 7475156d49
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 618 additions and 12 deletions

View File

@ -3774,6 +3774,74 @@ type-generic alternative to the ``__builtin_clz{,l,ll}`` (respectively
``__builtin_ctz{,l,ll}``) builtins, with support for other integer types, such
as ``unsigned __int128`` and C23 ``unsigned _BitInt(N)``.
``__builtin_counted_by_ref``
----------------------------
``__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``.
**Syntax**:
.. code-block:: c
T *__builtin_counted_by_ref(void *array)
**Examples**:
.. code-block:: c
#define alloc(P, FAM, COUNT) ({ \
size_t __ignored_assignment; \
typeof(P) __p = NULL; \
__p = malloc(MAX(sizeof(*__p), \
sizeof(*__p) + sizeof(*__p->FAM) * COUNT)); \
\
*_Generic( \
__builtin_counted_by_ref(__p->FAM), \
void *: &__ignored_assignment, \
default: __builtin_counted_by_ref(__p->FAM)) = COUNT; \
\
__p; \
})
**Description**:
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:
.. code-block:: c
struct s {
int dummy;
short count;
long array[] __attribute__((counted_by(count)));
};
struct s *ptr = malloc(sizeof(struct s) + sizeof(long) * COUNT);
for (int i = 0; i < COUNT; ++i)
ptr->array[i] = i;
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.
**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
function, because doing so violates bounds safety conventions.
Multiprecision Arithmetic Builtins
----------------------------------

View File

@ -313,6 +313,29 @@ Non-comprehensive list of changes in this release
as well as declarations.
- ``__builtin_abs`` function can now be used in constant expressions.
- The new builtin ``__builtin_counted_by_ref`` was added. In contexts where the
programmer needs access to the ``counted_by`` attribute's field, but it's not
available --- e.g. in macros. For instace, it can be used to automatically
set the counter during allocation in the Linux kernel:
.. code-block:: c
/* A simplified version of Linux allocation macros */
#define alloc(PTR, FAM, COUNT) ({ \
sizeof_t __ignored_assignment; \
typeof(P) __p; \
size_t __size = sizeof(*P) + sizeof(*P->FAM) * COUNT; \
__p = malloc(__size); \
*_Generic( \
__builtin_counted_by_ref(__p->FAM), \
void *: &__ignored_assignment, \
default: __builtin_counted_by_ref(__p->FAM)) = COUNT; \
__p; \
})
The flexible array member (FAM) can now be accessed immediately without causing
issues with the sanitizer because the counter is automatically set.
New Compiler Flags
------------------

View File

@ -4932,3 +4932,9 @@ def ArithmeticFence : LangBuiltin<"ALL_LANGUAGES"> {
let Attributes = [CustomTypeChecking, Constexpr];
let Prototype = "void(...)";
}
def CountedByRef : Builtin {
let Spellings = ["__builtin_counted_by_ref"];
let Attributes = [NoThrow, CustomTypeChecking];
let Prototype = "int(...)";
}

View File

@ -6652,6 +6652,18 @@ def warn_counted_by_attr_elt_type_unknown_size :
Warning<err_counted_by_attr_pointee_unknown_size.Summary>,
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_cannot_leak_reference : Error<
"value returned by '__builtin_counted_by_ref' cannot be assigned to a "
"variable, have its address taken, or passed into or returned from a function">;
def err_builtin_counted_by_ref_invalid_lhs_use : Error<
"value returned by '__builtin_counted_by_ref' cannot be used in "
"%select{an array subscript|a binary}0 expression">;
def err_builtin_counted_by_ref_has_side_effects : Error<
"'__builtin_counted_by_ref' argument cannot have side-effects">;
let CategoryName = "ARC Semantic Issue" in {
// ARC-mode diagnostics.

View File

@ -2510,6 +2510,8 @@ private:
bool BuiltinNonDeterministicValue(CallExpr *TheCall);
bool BuiltinCountedByRef(CallExpr *TheCall);
// Matrix builtin handling.
ExprResult BuiltinMatrixTranspose(CallExpr *TheCall, ExprResult CallResult);
ExprResult BuiltinMatrixColumnMajorLoad(CallExpr *TheCall,

View File

@ -3657,6 +3657,10 @@ unsigned FunctionDecl::getBuiltinID(bool ConsiderWrapperFunctions) const {
(!hasAttr<ArmBuiltinAliasAttr>() && !hasAttr<BuiltinAliasAttr>()))
return 0;
if (getASTContext().getLangOpts().CPlusPlus &&
BuiltinID == Builtin::BI__builtin_counted_by_ref)
return 0;
const ASTContext &Context = getASTContext();
if (!Context.BuiltinInfo.isPredefinedLibFunction(BuiltinID))
return BuiltinID;

View File

@ -3691,6 +3691,35 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
return RValue::get(emitBuiltinObjectSize(E->getArg(0), Type, ResType,
/*EmittedE=*/nullptr, IsDynamic));
}
case Builtin::BI__builtin_counted_by_ref: {
// Default to returning '(void *) 0'.
llvm::Value *Result = llvm::ConstantPointerNull::get(
llvm::PointerType::getUnqual(getLLVMContext()));
const Expr *Arg = E->getArg(0)->IgnoreParenImpCasts();
if (auto *UO = dyn_cast<UnaryOperator>(Arg);
UO && UO->getOpcode() == UO_AddrOf) {
Arg = UO->getSubExpr()->IgnoreParenImpCasts();
if (auto *ASE = dyn_cast<ArraySubscriptExpr>(Arg))
Arg = ASE->getBase()->IgnoreParenImpCasts();
}
if (const MemberExpr *ME = dyn_cast_if_present<MemberExpr>(Arg)) {
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);
else
llvm::report_fatal_error("Cannot find the counted_by 'count' field");
}
}
return RValue::get(Result);
}
case Builtin::BI__builtin_prefetch: {
Value *Locality, *RW, *Address = EmitScalarExpr(E->getArg(0));
// FIXME: Technically these constants should of type 'int', yes?

View File

@ -1145,15 +1145,7 @@ static bool getGEPIndicesToField(CodeGenFunction &CGF, const RecordDecl *RD,
return false;
}
/// This method is typically called in contexts where we can't generate
/// side-effects, like in __builtin_dynamic_object_size. When finding
/// expressions, only choose those that have either already been emitted or can
/// be loaded without side-effects.
///
/// - \p FAMDecl: the \p Decl for the flexible array member. It may not be
/// within the top-level struct.
/// - \p CountDecl: must be within the same non-anonymous struct as \p FAMDecl.
llvm::Value *CodeGenFunction::EmitLoadOfCountedByField(
llvm::Value *CodeGenFunction::GetCountedByFieldExprGEP(
const Expr *Base, const FieldDecl *FAMDecl, const FieldDecl *CountDecl) {
const RecordDecl *RD = CountDecl->getParent()->getOuterLexicalRecordContext();
@ -1182,12 +1174,25 @@ llvm::Value *CodeGenFunction::EmitLoadOfCountedByField(
return nullptr;
Indices.push_back(Builder.getInt32(0));
Res = Builder.CreateInBoundsGEP(
return Builder.CreateInBoundsGEP(
ConvertType(QualType(RD->getTypeForDecl(), 0)), Res,
RecIndicesTy(llvm::reverse(Indices)), "..counted_by.gep");
}
return Builder.CreateAlignedLoad(ConvertType(CountDecl->getType()), Res,
getIntAlign(), "..counted_by.load");
/// This method is typically called in contexts where we can't generate
/// side-effects, like in __builtin_dynamic_object_size. When finding
/// expressions, only choose those that have either already been emitted or can
/// be loaded without side-effects.
///
/// - \p FAMDecl: the \p Decl for the flexible array member. It may not be
/// within the top-level struct.
/// - \p CountDecl: must be within the same non-anonymous struct as \p FAMDecl.
llvm::Value *CodeGenFunction::EmitLoadOfCountedByField(
const Expr *Base, const FieldDecl *FAMDecl, const FieldDecl *CountDecl) {
if (llvm::Value *GEP = GetCountedByFieldExprGEP(Base, FAMDecl, CountDecl))
return Builder.CreateAlignedLoad(ConvertType(CountDecl->getType()), GEP,
getIntAlign(), "..counted_by.load");
return nullptr;
}
void CodeGenFunction::EmitBoundsCheck(const Expr *E, const Expr *Base,

View File

@ -3305,6 +3305,10 @@ public:
const FieldDecl *FAMDecl,
uint64_t &Offset);
llvm::Value *GetCountedByFieldExprGEP(const Expr *Base,
const FieldDecl *FAMDecl,
const FieldDecl *CountDecl);
/// Build an expression accessing the "counted_by" field.
llvm::Value *EmitLoadOfCountedByField(const Expr *Base,
const FieldDecl *FAMDecl,

View File

@ -2973,6 +2973,10 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
}
break;
}
case Builtin::BI__builtin_counted_by_ref:
if (BuiltinCountedByRef(TheCall))
return ExprError();
break;
}
if (getLangOpts().HLSL && HLSL().CheckBuiltinFunctionCall(BuiltinID, TheCall))
@ -5575,6 +5579,55 @@ bool Sema::BuiltinSetjmp(CallExpr *TheCall) {
return false;
}
bool Sema::BuiltinCountedByRef(CallExpr *TheCall) {
if (checkArgCount(TheCall, 1))
return true;
ExprResult ArgRes = UsualUnaryConversions(TheCall->getArg(0));
if (ArgRes.isInvalid())
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.
const Expr *Arg = ArgRes.get()->IgnoreParenImpCasts();
if (!isa<PointerType>(Arg->getType()) && !Arg->getType()->isArrayType())
return Diag(Arg->getBeginLoc(),
diag::err_builtin_counted_by_ref_must_be_flex_array_member)
<< Arg->getSourceRange();
if (Arg->HasSideEffects(Context))
return Diag(Arg->getBeginLoc(),
diag::err_builtin_counted_by_ref_has_side_effects)
<< 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();
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()) {
TheCall->setType(Context.getPointerType(CountFD->getType()));
return false;
}
}
} else {
return Diag(Arg->getBeginLoc(),
diag::err_builtin_counted_by_ref_must_be_flex_array_member)
<< Arg->getSourceRange();
}
TheCall->setType(Context.getPointerType(Context.VoidTy));
return false;
}
namespace {
class UncoveredArgHandler {

View File

@ -9209,6 +9209,38 @@ Sema::CheckAssignmentConstraints(QualType LHSType, ExprResult &RHS,
LHSType = Context.getCanonicalType(LHSType).getUnqualifiedType();
RHSType = Context.getCanonicalType(RHSType).getUnqualifiedType();
// __builtin_counted_by_ref cannot be assigned to a variable, used in
// function call, or in a return.
auto FindBuiltinCountedByRefExpr = [&](Expr *E) -> CallExpr * {
struct BuiltinCountedByRefVisitor
: public RecursiveASTVisitor<BuiltinCountedByRefVisitor> {
CallExpr *TheCall = nullptr;
bool VisitCallExpr(CallExpr *CE) {
if (CE->getBuiltinCallee() == Builtin::BI__builtin_counted_by_ref) {
TheCall = CE;
return false;
}
return true;
}
bool VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *UE) {
// A UnaryExprOrTypeTraitExpr---e.g. sizeof, __alignof, etc.---isn't
// the same as a CallExpr, so if we find a __builtin_counted_by_ref()
// call in one, ignore it.
return false;
}
} V;
V.TraverseStmt(E);
return V.TheCall;
};
static llvm::SmallPtrSet<CallExpr *, 4> Diagnosed;
if (auto *CE = FindBuiltinCountedByRefExpr(RHS.get());
CE && !Diagnosed.count(CE)) {
Diagnosed.insert(CE);
Diag(CE->getExprLoc(),
diag::err_builtin_counted_by_ref_cannot_leak_reference)
<< CE->getSourceRange();
}
// Common case: no conversion required.
if (LHSType == RHSType) {
Kind = CK_NoOp;
@ -13757,6 +13789,43 @@ QualType Sema::CheckAssignmentOperands(Expr *LHSExpr, ExprResult &RHS,
ConvTy = CheckAssignmentConstraints(Loc, LHSType, RHSType);
}
// __builtin_counted_by_ref can't be used in a binary expression or array
// subscript on the LHS.
int DiagOption = -1;
auto FindInvalidUseOfBoundsSafetyCounter = [&](Expr *E) -> CallExpr * {
struct BuiltinCountedByRefVisitor
: public RecursiveASTVisitor<BuiltinCountedByRefVisitor> {
CallExpr *CE = nullptr;
bool InvalidUse = false;
int Option = -1;
bool VisitCallExpr(CallExpr *E) {
if (E->getBuiltinCallee() == Builtin::BI__builtin_counted_by_ref) {
CE = E;
return false;
}
return true;
}
bool VisitArraySubscriptExpr(ArraySubscriptExpr *E) {
InvalidUse = true;
Option = 0; // report 'array expression' in diagnostic.
return true;
}
bool VisitBinaryOperator(BinaryOperator *E) {
InvalidUse = true;
Option = 1; // report 'binary expression' in diagnostic.
return true;
}
} V;
V.TraverseStmt(E);
DiagOption = V.Option;
return V.InvalidUse ? V.CE : nullptr;
};
if (auto *CE = FindInvalidUseOfBoundsSafetyCounter(LHSExpr))
Diag(CE->getExprLoc(), diag::err_builtin_counted_by_ref_invalid_lhs_use)
<< DiagOption << CE->getSourceRange();
if (DiagnoseAssignmentResult(ConvTy, Loc, LHSType, RHSType, RHS.get(),
AssignmentAction::Assigning))
return QualType();

View File

@ -0,0 +1,23 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux -ast-print %s -o - | FileCheck %s
typedef unsigned long int size_t;
int global_array[42];
int global_int;
struct fam_struct {
int x;
char count;
int array[] __attribute__((counted_by(count)));
};
// CHECK-LABEL: void test1(struct fam_struct *ptr, int size) {
// CHECK-NEXT: size_t __ignored_assignment;
// CHECK-NEXT: *_Generic(__builtin_counted_by_ref(ptr->array), void *: &__ignored_assignment, default: __builtin_counted_by_ref(ptr->array)) = 42;
void test1(struct fam_struct *ptr, int size) {
size_t __ignored_assignment;
*_Generic(__builtin_counted_by_ref(ptr->array),
void *: &__ignored_assignment,
default: __builtin_counted_by_ref(ptr->array)) = 42; // ok
}

View File

@ -0,0 +1,177 @@
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 5
// RUN: %clang_cc1 -triple x86_64-unknown-unknown -emit-llvm -o - %s | FileCheck %s --check-prefix=X86_64
// RUN: %clang_cc1 -triple i386-unknown-unknown -emit-llvm -o - %s | FileCheck %s --check-prefix=I386
struct a {
char x;
short count;
int array[] __attribute__((counted_by(count)));
};
// X86_64-LABEL: define dso_local ptr @test1(
// X86_64-SAME: i32 noundef [[SIZE:%.*]]) #[[ATTR0:[0-9]+]] {
// X86_64-NEXT: [[ENTRY:.*:]]
// X86_64-NEXT: [[SIZE_ADDR:%.*]] = alloca i32, align 4
// X86_64-NEXT: [[P:%.*]] = alloca ptr, align 8
// X86_64-NEXT: store i32 [[SIZE]], ptr [[SIZE_ADDR]], align 4
// X86_64-NEXT: [[TMP0:%.*]] = load i32, ptr [[SIZE_ADDR]], align 4
// X86_64-NEXT: [[CONV:%.*]] = sext i32 [[TMP0]] to i64
// X86_64-NEXT: [[MUL:%.*]] = mul i64 4, [[CONV]]
// X86_64-NEXT: [[ADD:%.*]] = add i64 4, [[MUL]]
// X86_64-NEXT: [[CALL:%.*]] = call ptr @malloc(i64 noundef [[ADD]]) #[[ATTR2:[0-9]+]]
// X86_64-NEXT: store ptr [[CALL]], ptr [[P]], align 8
// X86_64-NEXT: [[TMP1:%.*]] = load i32, ptr [[SIZE_ADDR]], align 4
// X86_64-NEXT: [[CONV1:%.*]] = trunc i32 [[TMP1]] to i16
// X86_64-NEXT: [[TMP2:%.*]] = load ptr, ptr [[P]], align 8
// X86_64-NEXT: [[DOT_COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_A:%.*]], ptr [[TMP2]], i32 0, i32 1
// X86_64-NEXT: store i16 [[CONV1]], ptr [[DOT_COUNTED_BY_GEP]], align 2
// X86_64-NEXT: [[TMP3:%.*]] = load ptr, ptr [[P]], align 8
// X86_64-NEXT: ret ptr [[TMP3]]
//
// I386-LABEL: define dso_local ptr @test1(
// I386-SAME: i32 noundef [[SIZE:%.*]]) #[[ATTR0:[0-9]+]] {
// I386-NEXT: [[ENTRY:.*:]]
// I386-NEXT: [[SIZE_ADDR:%.*]] = alloca i32, align 4
// I386-NEXT: [[P:%.*]] = alloca ptr, align 4
// I386-NEXT: store i32 [[SIZE]], ptr [[SIZE_ADDR]], align 4
// I386-NEXT: [[TMP0:%.*]] = load i32, ptr [[SIZE_ADDR]], align 4
// I386-NEXT: [[MUL:%.*]] = mul i32 4, [[TMP0]]
// I386-NEXT: [[ADD:%.*]] = add i32 4, [[MUL]]
// I386-NEXT: [[CALL:%.*]] = call ptr @malloc(i32 noundef [[ADD]]) #[[ATTR2:[0-9]+]]
// I386-NEXT: store ptr [[CALL]], ptr [[P]], align 4
// I386-NEXT: [[TMP1:%.*]] = load i32, ptr [[SIZE_ADDR]], align 4
// I386-NEXT: [[CONV:%.*]] = trunc i32 [[TMP1]] to i16
// I386-NEXT: [[TMP2:%.*]] = load ptr, ptr [[P]], align 4
// I386-NEXT: [[DOT_COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_A:%.*]], ptr [[TMP2]], i32 0, i32 1
// I386-NEXT: store i16 [[CONV]], ptr [[DOT_COUNTED_BY_GEP]], align 2
// I386-NEXT: [[TMP3:%.*]] = load ptr, ptr [[P]], align 4
// I386-NEXT: ret ptr [[TMP3]]
//
struct a *test1(int size) {
struct a *p = __builtin_malloc(sizeof(struct a) + sizeof(int) * size);
*__builtin_counted_by_ref(p->array) = size;
return p;
}
struct b {
int _filler;
struct {
int __filler;
struct {
int ___filler;
struct {
char count;
};
};
};
struct {
int filler_;
struct {
int filler__;
struct {
long array[] __attribute__((counted_by(count)));
};
};
};
};
// X86_64-LABEL: define dso_local ptr @test2(
// X86_64-SAME: i32 noundef [[SIZE:%.*]]) #[[ATTR0]] {
// X86_64-NEXT: [[ENTRY:.*:]]
// X86_64-NEXT: [[SIZE_ADDR:%.*]] = alloca i32, align 4
// X86_64-NEXT: [[P:%.*]] = alloca ptr, align 8
// X86_64-NEXT: store i32 [[SIZE]], ptr [[SIZE_ADDR]], align 4
// X86_64-NEXT: [[TMP0:%.*]] = load i32, ptr [[SIZE_ADDR]], align 4
// X86_64-NEXT: [[CONV:%.*]] = sext i32 [[TMP0]] to i64
// X86_64-NEXT: [[MUL:%.*]] = mul i64 4, [[CONV]]
// X86_64-NEXT: [[ADD:%.*]] = add i64 4, [[MUL]]
// X86_64-NEXT: [[CALL:%.*]] = call ptr @malloc(i64 noundef [[ADD]]) #[[ATTR2]]
// X86_64-NEXT: store ptr [[CALL]], ptr [[P]], align 8
// X86_64-NEXT: [[TMP1:%.*]] = load i32, ptr [[SIZE_ADDR]], align 4
// X86_64-NEXT: [[CONV1:%.*]] = trunc i32 [[TMP1]] to i8
// X86_64-NEXT: [[TMP2:%.*]] = load ptr, ptr [[P]], align 8
// X86_64-NEXT: [[DOT_COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_B:%.*]], ptr [[TMP2]], i32 0, i32 1, i32 1, i32 1, i32 0
// X86_64-NEXT: store i8 [[CONV1]], ptr [[DOT_COUNTED_BY_GEP]], align 1
// X86_64-NEXT: [[TMP3:%.*]] = load ptr, ptr [[P]], align 8
// X86_64-NEXT: ret ptr [[TMP3]]
//
// I386-LABEL: define dso_local ptr @test2(
// I386-SAME: i32 noundef [[SIZE:%.*]]) #[[ATTR0]] {
// I386-NEXT: [[ENTRY:.*:]]
// I386-NEXT: [[SIZE_ADDR:%.*]] = alloca i32, align 4
// I386-NEXT: [[P:%.*]] = alloca ptr, align 4
// I386-NEXT: store i32 [[SIZE]], ptr [[SIZE_ADDR]], align 4
// I386-NEXT: [[TMP0:%.*]] = load i32, ptr [[SIZE_ADDR]], align 4
// I386-NEXT: [[MUL:%.*]] = mul i32 4, [[TMP0]]
// I386-NEXT: [[ADD:%.*]] = add i32 4, [[MUL]]
// I386-NEXT: [[CALL:%.*]] = call ptr @malloc(i32 noundef [[ADD]]) #[[ATTR2]]
// I386-NEXT: store ptr [[CALL]], ptr [[P]], align 4
// I386-NEXT: [[TMP1:%.*]] = load i32, ptr [[SIZE_ADDR]], align 4
// I386-NEXT: [[CONV:%.*]] = trunc i32 [[TMP1]] to i8
// I386-NEXT: [[TMP2:%.*]] = load ptr, ptr [[P]], align 4
// I386-NEXT: [[DOT_COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_B:%.*]], ptr [[TMP2]], i32 0, i32 1, i32 1, i32 1, i32 0
// I386-NEXT: store i8 [[CONV]], ptr [[DOT_COUNTED_BY_GEP]], align 1
// I386-NEXT: [[TMP3:%.*]] = load ptr, ptr [[P]], align 4
// I386-NEXT: ret ptr [[TMP3]]
//
struct b *test2(int size) {
struct b *p = __builtin_malloc(sizeof(struct a) + sizeof(int) * size);
*__builtin_counted_by_ref(p->array) = size;
return p;
}
struct c {
char x;
short count;
int array[];
};
// X86_64-LABEL: define dso_local ptr @test3(
// X86_64-SAME: i32 noundef [[SIZE:%.*]]) #[[ATTR0]] {
// X86_64-NEXT: [[ENTRY:.*:]]
// X86_64-NEXT: [[SIZE_ADDR:%.*]] = alloca i32, align 4
// X86_64-NEXT: [[P:%.*]] = alloca ptr, align 8
// X86_64-NEXT: [[__IGNORED:%.*]] = alloca i64, align 8
// X86_64-NEXT: store i32 [[SIZE]], ptr [[SIZE_ADDR]], align 4
// X86_64-NEXT: [[TMP0:%.*]] = load i32, ptr [[SIZE_ADDR]], align 4
// X86_64-NEXT: [[CONV:%.*]] = sext i32 [[TMP0]] to i64
// X86_64-NEXT: [[MUL:%.*]] = mul i64 4, [[CONV]]
// X86_64-NEXT: [[ADD:%.*]] = add i64 4, [[MUL]]
// X86_64-NEXT: [[CALL:%.*]] = call ptr @malloc(i64 noundef [[ADD]]) #[[ATTR2]]
// X86_64-NEXT: store ptr [[CALL]], ptr [[P]], align 8
// X86_64-NEXT: [[TMP1:%.*]] = load i32, ptr [[SIZE_ADDR]], align 4
// X86_64-NEXT: [[CONV1:%.*]] = sext i32 [[TMP1]] to i64
// X86_64-NEXT: store i64 [[CONV1]], ptr [[__IGNORED]], align 8
// X86_64-NEXT: [[TMP2:%.*]] = load ptr, ptr [[P]], align 8
// X86_64-NEXT: ret ptr [[TMP2]]
//
// I386-LABEL: define dso_local ptr @test3(
// I386-SAME: i32 noundef [[SIZE:%.*]]) #[[ATTR0]] {
// I386-NEXT: [[ENTRY:.*:]]
// I386-NEXT: [[SIZE_ADDR:%.*]] = alloca i32, align 4
// I386-NEXT: [[P:%.*]] = alloca ptr, align 4
// I386-NEXT: [[__IGNORED:%.*]] = alloca i32, align 4
// I386-NEXT: store i32 [[SIZE]], ptr [[SIZE_ADDR]], align 4
// I386-NEXT: [[TMP0:%.*]] = load i32, ptr [[SIZE_ADDR]], align 4
// I386-NEXT: [[MUL:%.*]] = mul i32 4, [[TMP0]]
// I386-NEXT: [[ADD:%.*]] = add i32 4, [[MUL]]
// I386-NEXT: [[CALL:%.*]] = call ptr @malloc(i32 noundef [[ADD]]) #[[ATTR2]]
// I386-NEXT: store ptr [[CALL]], ptr [[P]], align 4
// I386-NEXT: [[TMP1:%.*]] = load i32, ptr [[SIZE_ADDR]], align 4
// I386-NEXT: store i32 [[TMP1]], ptr [[__IGNORED]], align 4
// I386-NEXT: [[TMP2:%.*]] = load ptr, ptr [[P]], align 4
// I386-NEXT: ret ptr [[TMP2]]
//
struct c *test3(int size) {
struct c *p = __builtin_malloc(sizeof(struct c) + sizeof(int) * size);
unsigned long int __ignored;
*_Generic(
__builtin_counted_by_ref(p->array),
void *: &__ignored,
default: __builtin_counted_by_ref(p->array)) = size;
return p;
}

View File

@ -0,0 +1,123 @@
// RUN: %clang_cc1 -std=c99 -fsyntax-only -verify %s
typedef unsigned long int size_t;
int global_array[42];
int global_int;
struct fam_struct {
int x;
char count;
int array[] __attribute__((counted_by(count)));
};
void test1(struct fam_struct *ptr, int size, int idx) {
size_t size_of = sizeof(__builtin_counted_by_ref(ptr->array)); // ok
*__builtin_counted_by_ref(ptr->array) = size; // ok
{
size_t __ignored_assignment;
*_Generic(__builtin_counted_by_ref(ptr->array),
void *: &__ignored_assignment,
default: __builtin_counted_by_ref(ptr->array)) = 42; // ok
}
}
void test2(struct fam_struct *ptr, int idx) {
__builtin_counted_by_ref(); // expected-error {{too few arguments to function call, expected 1, have 0}}
__builtin_counted_by_ref(ptr->array, ptr->x, ptr->count); // expected-error {{too many arguments to function call, expected 1, have 3}}
}
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}}
}
void test4(struct fam_struct *ptr, int idx) {
__builtin_counted_by_ref(ptr++->array); // expected-error {{'__builtin_counted_by_ref' argument cannot have side-effects}}
__builtin_counted_by_ref(&ptr->array[idx++]); // expected-error {{'__builtin_counted_by_ref' argument cannot have side-effects}}
}
void foo(char *);
void *test5(struct fam_struct *ptr, int size, int idx) {
char *ref = __builtin_counted_by_ref(ptr->array); // expected-error {{value returned by '__builtin_counted_by_ref' cannot be assigned to a variable, have its address taken, or passed into or returned from a function}}
ref = __builtin_counted_by_ref(ptr->array); // expected-error {{value returned by '__builtin_counted_by_ref' cannot be assigned to a variable, have its address taken, or passed into or returned from a function}}
ref = (char *)(int *)(42 + &*__builtin_counted_by_ref(ptr->array)); // expected-error {{value returned by '__builtin_counted_by_ref' cannot be assigned to a variable, have its address taken, or passed into or returned from a function}}
foo(__builtin_counted_by_ref(ptr->array)); // expected-error {{value returned by '__builtin_counted_by_ref' cannot be assigned to a variable, have its address taken, or passed into or returned from a function}}
foo(ref = __builtin_counted_by_ref(ptr->array)); // expected-error {{value returned by '__builtin_counted_by_ref' cannot be assigned to a variable, have its address taken, or passed into or returned from a function}}
if ((ref = __builtin_counted_by_ref(ptr->array))) // expected-error {{value returned by '__builtin_counted_by_ref' cannot be assigned to a variable, have its address taken, or passed into or returned from a function}}
;
for (char *p = __builtin_counted_by_ref(ptr->array); p && *p; ++p) // expected-error {{value returned by '__builtin_counted_by_ref' cannot be assigned to a variable, have its address taken, or passed into or returned from a function}}
;
return __builtin_counted_by_ref(ptr->array); // expected-error {{value returned by '__builtin_counted_by_ref' cannot be assigned to a variable, have its address taken, or passed into or returned from a function}}
}
void test6(struct fam_struct *ptr, int size, int idx) {
*(__builtin_counted_by_ref(ptr->array) + 4) = 37; // expected-error {{value returned by '__builtin_counted_by_ref' cannot be used in a binary expression}}
__builtin_counted_by_ref(ptr->array)[3] = 37; // expected-error {{value returned by '__builtin_counted_by_ref' cannot be used in an array subscript expression}}
}
struct non_fam_struct {
char x;
long *pointer;
int array[42];
short count;
};
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}}
}
struct char_count {
char count;
int array[] __attribute__((counted_by(count)));
} *cp;
struct short_count {
short count;
int array[] __attribute__((counted_by(count)));
} *sp;
struct int_count {
int count;
int array[] __attribute__((counted_by(count)));
} *ip;
struct unsigned_count {
unsigned count;
int array[] __attribute__((counted_by(count)));
} *up;
struct long_count {
long count;
int array[] __attribute__((counted_by(count)));
} *lp;
struct unsigned_long_count {
unsigned long count;
int array[] __attribute__((counted_by(count)));
} *ulp;
void test8(void) {
_Static_assert(_Generic(__builtin_counted_by_ref(cp->array), char * : 1, default : 0) == 1, "wrong return type");
_Static_assert(_Generic(__builtin_counted_by_ref(sp->array), short * : 1, default : 0) == 1, "wrong return type");
_Static_assert(_Generic(__builtin_counted_by_ref(ip->array), int * : 1, default : 0) == 1, "wrong return type");
_Static_assert(_Generic(__builtin_counted_by_ref(up->array), unsigned int * : 1, default : 0) == 1, "wrong return type");
_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");
}

View File

@ -0,0 +1,8 @@
// RUN: %clang_cc1 -x c++ -fsyntax-only -verify %s
struct fam_struct {
int x;
char count;
int array[] __attribute__((counted_by(count))); // expected-warning {{'counted_by' attribute ignored}}
};