Author: Bill Wendling Date: 2024-11-07T22:03:55Z New Revision: 7475156d49406785a974b1205d11fe3de9c1553e
URL: https://github.com/llvm/llvm-project/commit/7475156d49406785a974b1205d11fe3de9c1553e DIFF: https://github.com/llvm/llvm-project/commit/7475156d49406785a974b1205d11fe3de9c1553e.diff LOG: [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 <isanb...@gmail.com> Co-authored-by: Aaron Ballman <aa...@aaronballman.com> Added: clang/test/AST/ast-print-builtin-counted-by-ref.c clang/test/CodeGen/builtin-counted-by-ref.c clang/test/Sema/builtin-counted-by-ref.c clang/test/Sema/builtin-counted-by-ref.cpp Modified: clang/docs/LanguageExtensions.rst clang/docs/ReleaseNotes.rst clang/include/clang/Basic/Builtins.td clang/include/clang/Basic/DiagnosticSemaKinds.td clang/include/clang/Sema/Sema.h clang/lib/AST/Decl.cpp clang/lib/CodeGen/CGBuiltin.cpp clang/lib/CodeGen/CGExpr.cpp clang/lib/CodeGen/CodeGenFunction.h clang/lib/Sema/SemaChecking.cpp clang/lib/Sema/SemaExpr.cpp Removed: ################################################################################ diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst index f00422cd8b8045..f7285352b9deb9 100644 --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -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 ---------------------------------- diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 46beb3fe39dec8..0b0f2053f634ee 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -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 ------------------ diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index e484c3969fe228..4360e0bf9840f1 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -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(...)"; +} diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index c96a3f6d6e157f..6a244c276facd6 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -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. diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index aa2f5ff3ef7207..fad446a05e782f 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2510,6 +2510,8 @@ class Sema final : public SemaBase { bool BuiltinNonDeterministicValue(CallExpr *TheCall); + bool BuiltinCountedByRef(CallExpr *TheCall); + // Matrix builtin handling. ExprResult BuiltinMatrixTranspose(CallExpr *TheCall, ExprResult CallResult); ExprResult BuiltinMatrixColumnMajorLoad(CallExpr *TheCall, diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 8204e3509dd563..047f354b200745 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -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; diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index 0ef9058640db6a..1b4891d94eee77 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -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? diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index 3388a6df466d45..096f4c4f550435 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -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, diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index 3ff4458fb32024..90dc399f1341f3 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -3305,6 +3305,10 @@ class CodeGenFunction : public CodeGenTypeCache { 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, diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index d78968179b1fdc..96008b14225a4c 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -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 { diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index df8f025030e2b1..68527d9da8c799 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -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(); diff --git a/clang/test/AST/ast-print-builtin-counted-by-ref.c b/clang/test/AST/ast-print-builtin-counted-by-ref.c new file mode 100644 index 00000000000000..c0ff7515fc8208 --- /dev/null +++ b/clang/test/AST/ast-print-builtin-counted-by-ref.c @@ -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 +} diff --git a/clang/test/CodeGen/builtin-counted-by-ref.c b/clang/test/CodeGen/builtin-counted-by-ref.c new file mode 100644 index 00000000000000..8ad715879aa767 --- /dev/null +++ b/clang/test/CodeGen/builtin-counted-by-ref.c @@ -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; +} diff --git a/clang/test/Sema/builtin-counted-by-ref.c b/clang/test/Sema/builtin-counted-by-ref.c new file mode 100644 index 00000000000000..5a7ecefcb78976 --- /dev/null +++ b/clang/test/Sema/builtin-counted-by-ref.c @@ -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"); +} diff --git a/clang/test/Sema/builtin-counted-by-ref.cpp b/clang/test/Sema/builtin-counted-by-ref.cpp new file mode 100644 index 00000000000000..b9ec9c908dcaa6 --- /dev/null +++ b/clang/test/Sema/builtin-counted-by-ref.cpp @@ -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}} +}; + _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits