[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:
parent
ae9d0623ad
commit
7475156d49
@ -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
|
||||
----------------------------------
|
||||
|
||||
|
@ -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
|
||||
------------------
|
||||
|
||||
|
@ -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(...)";
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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?
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
|
23
clang/test/AST/ast-print-builtin-counted-by-ref.c
Normal file
23
clang/test/AST/ast-print-builtin-counted-by-ref.c
Normal 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
|
||||
}
|
177
clang/test/CodeGen/builtin-counted-by-ref.c
Normal file
177
clang/test/CodeGen/builtin-counted-by-ref.c
Normal 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;
|
||||
}
|
123
clang/test/Sema/builtin-counted-by-ref.c
Normal file
123
clang/test/Sema/builtin-counted-by-ref.c
Normal 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");
|
||||
}
|
8
clang/test/Sema/builtin-counted-by-ref.cpp
Normal file
8
clang/test/Sema/builtin-counted-by-ref.cpp
Normal 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}}
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user