[clang] 7475156 - [Clang] Add __builtin_counted_by_ref builtin (#114495)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Nov 7 14:03:59 PST 2024
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 <isanbard at gmail.com>
Co-authored-by: Aaron Ballman <aaron at 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}}
+};
+
More information about the cfe-commits
mailing list