[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