Skip to content

[CodeGen] Revamp counted_by calculations #70606

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Nov 9, 2023

Conversation

bwendling
Copy link
Collaborator

@bwendling bwendling commented Oct 29, 2023

Break down the counted_by calculations so that they correctly handle
anonymous structs, which are specified internally as IndirectFieldDecls.

Improves the calculation of __bdos on a different field member in the struct.
And also improves support for __bdos in an index into the FAM. If the index
is further out than the length of the FAM, then we return __bdos's "can't
determine the size" value (zero or negative one, depending on type).

Also simplify the code to use helper methods to get the field referenced
by counted_by and the flexible array member itself, which also had some
issues with FAMs in sub-structs.

Break down the counted_by calculations so that they correctly handle
anonymous structs, which are specified internally as IndirectFieldDecls.
Also simplify the code to use helper methods to get the field referenced
by counted_by and the flexible array member itself, which also had some
issues with FAMs in sub-structs.
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:codegen IR generation bugs: mangling, exceptions, etc. labels Oct 29, 2023
@llvmbot
Copy link
Member

llvmbot commented Oct 29, 2023

@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-clang

Author: Bill Wendling (bwendling)

Changes

Break down the counted_by calculations so that they correctly handle
anonymous structs, which are specified internally as IndirectFieldDecls.
Also simplify the code to use helper methods to get the field referenced
by counted_by and the flexible array member itself, which also had some
issues with FAMs in sub-structs.


Full diff: https://github.com/llvm/llvm-project/pull/70606.diff

3 Files Affected:

  • (modified) clang/lib/CodeGen/CGBuiltin.cpp (+49-42)
  • (modified) clang/lib/CodeGen/CGExpr.cpp (+76-17)
  • (modified) clang/lib/CodeGen/CodeGenFunction.h (+9-3)
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index dce5ee5888c458e..acee2c1af1ab368 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -859,53 +859,60 @@ CodeGenFunction::emitBuiltinObjectSize(const Expr *E, unsigned Type,
   }
 
   if (IsDynamic) {
-    LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel =
-        getLangOpts().getStrictFlexArraysLevel();
-    const Expr *Base = E->IgnoreParenImpCasts();
-
-    if (FieldDecl *FD = FindCountedByField(Base, StrictFlexArraysLevel)) {
-      const auto *ME = dyn_cast<MemberExpr>(Base);
-      llvm::Value *ObjectSize = nullptr;
-
-      if (!ME) {
-        const auto *DRE = dyn_cast<DeclRefExpr>(Base);
-        ValueDecl *VD = nullptr;
-
-        ObjectSize = ConstantInt::get(
-            ResType,
-            getContext().getTypeSize(DRE->getType()->getPointeeType()) / 8,
-            true);
-
-        if (auto *RD = DRE->getType()->getPointeeType()->getAsRecordDecl())
-          VD = RD->getLastField();
-
-        Expr *ICE = ImplicitCastExpr::Create(
-            getContext(), DRE->getType(), CK_LValueToRValue,
-            const_cast<Expr *>(cast<Expr>(DRE)), nullptr, VK_PRValue,
-            FPOptionsOverride());
-        ME = MemberExpr::CreateImplicit(getContext(), ICE, true, VD,
-                                        VD->getType(), VK_LValue, OK_Ordinary);
-      }
-
-      // At this point, we know that \p ME is a flexible array member.
-      const auto *ArrayTy = getContext().getAsArrayType(ME->getType());
+    // The code generated here calculates the size of a struct with a flexible
+    // array member that uses the counted_by attribute. There are two instances
+    // we handle:
+    //
+    //       struct s {
+    //         unsigned long flags;
+    //         int count;
+    //         int array[] __attribute__((counted_by(count)));
+    //       }
+    //
+    //   1) bdos of the flexible array itself:
+    //
+    //     __builtin_dynamic_object_size(p->array, 1) ==
+    //         p->count * sizeof(*p->array)
+    //
+    //   2) bdos of the whole struct, including the flexible array:
+    //
+    //     __builtin_dynamic_object_size(p, 1) ==
+    //        sizeof(*p) + p->count * sizeof(*p->array)
+    //
+    if (const ValueDecl *CountedByFD = FindCountedByField(E)) {
+      // Find the flexible array member.
+      const RecordDecl *OuterRD =
+        CountedByFD->getDeclContext()->getOuterLexicalRecordContext();
+      const ValueDecl *FAM = FindFlexibleArrayMemberField(getContext(),
+                                                          OuterRD);
+
+      // Get the size of the flexible array member's base type.
+      const auto *ArrayTy = getContext().getAsArrayType(FAM->getType());
       unsigned Size = getContext().getTypeSize(ArrayTy->getElementType());
 
-      llvm::Value *CountField =
-          EmitAnyExprToTemp(MemberExpr::CreateImplicit(
-                                getContext(), const_cast<Expr *>(ME->getBase()),
-                                ME->isArrow(), FD, FD->getType(), VK_LValue,
-                                OK_Ordinary))
-              .getScalarVal();
+      // Find the outer struct expr (i.e. p in p->a.b.c.d).
+      Expr *CountedByExpr = BuildCountedByFieldExpr(const_cast<Expr *>(E),
+                                                    CountedByFD);
+
+      llvm::Value *CountedByInstr =
+        EmitAnyExprToTemp(CountedByExpr).getScalarVal();
 
-      llvm::Value *Mul = Builder.CreateMul(
-          CountField, llvm::ConstantInt::get(CountField->getType(), Size / 8));
-      Mul = Builder.CreateZExtOrTrunc(Mul, ResType);
+      llvm::Constant *ArraySize =
+        llvm::ConstantInt::get(CountedByInstr->getType(), Size / 8);
 
-      if (ObjectSize)
-        return Builder.CreateAdd(ObjectSize, Mul);
+      llvm::Value *ObjectSize = Builder.CreateMul(CountedByInstr, ArraySize);
+      ObjectSize = Builder.CreateZExtOrTrunc(ObjectSize, ResType);
+
+      if (const auto *DRE = dyn_cast<DeclRefExpr>(E->IgnoreImpCasts())) {
+        // The whole struct is specificed in the __bdos.
+        QualType StructTy = DRE->getType()->getPointeeType();
+        llvm::Value *StructSize = ConstantInt::get(
+            ResType, getContext().getTypeSize(StructTy) / 8, true);
+        ObjectSize = Builder.CreateAdd(StructSize, ObjectSize);
+      }
 
-      return Mul;
+      // PULL THE STRING!!
+      return ObjectSize;
     }
   }
 
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 54a1d300a9ac738..2b39194e18ed861 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -944,14 +944,10 @@ static llvm::Value *getArrayIndexingBound(CodeGenFunction &CGF,
       // Ignore pass_object_size here. It's not applicable on decayed pointers.
     }
 
-    if (FieldDecl *FD = CGF.FindCountedByField(Base, StrictFlexArraysLevel)) {
-      const auto *ME = dyn_cast<MemberExpr>(CE->getSubExpr());
+    if (const ValueDecl *VD = CGF.FindCountedByField(Base)) {
       IndexedType = Base->getType();
-      return CGF
-          .EmitAnyExprToTemp(MemberExpr::CreateImplicit(
-              CGF.getContext(), const_cast<Expr *>(ME->getBase()),
-              ME->isArrow(), FD, FD->getType(), VK_LValue, OK_Ordinary))
-          .getScalarVal();
+      Expr *E = CGF.BuildCountedByFieldExpr(const_cast<Expr *>(Base), VD);
+      return CGF.EmitAnyExprToTemp(E).getScalarVal();
     }
   }
 
@@ -966,9 +962,68 @@ static llvm::Value *getArrayIndexingBound(CodeGenFunction &CGF,
   return nullptr;
 }
 
-FieldDecl *CodeGenFunction::FindCountedByField(
-    const Expr *Base,
-    LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel) {
+Expr *CodeGenFunction::BuildCountedByFieldExpr(Expr *Base,
+                                               const ValueDecl *CountedByVD) {
+  // Find the outer struct expr (i.e. p in p->a.b.c.d).
+  Base = Base->IgnoreImpCasts();
+  Base = Base->IgnoreParenNoopCasts(getContext());
+
+  // Work our way up the expression until we reach the DeclRefExpr.
+  while (!isa<DeclRefExpr>(Base))
+    if (auto *ME = dyn_cast<MemberExpr>(Base->IgnoreImpCasts())) {
+      Base = ME->getBase()->IgnoreImpCasts();
+      Base = Base->IgnoreParenNoopCasts(getContext());
+    }
+
+  // Add back an implicit cast to create the required pr-value.
+  Base = ImplicitCastExpr::Create(
+      getContext(), Base->getType(), CK_LValueToRValue, Base,
+      nullptr, VK_PRValue, FPOptionsOverride());
+
+  Expr *CountedByExpr = Base;
+
+  if (const auto *IFD = dyn_cast<IndirectFieldDecl>(CountedByVD)) {
+    // The counted_by field is inside an anonymous struct / union. The
+    // IndirectFieldDecl has the correct order of FieldDecls to build this
+    // easily. (Yay!)
+    for (NamedDecl *ND : IFD->chain()) {
+      ValueDecl *VD = cast<ValueDecl>(ND);
+      CountedByExpr = MemberExpr::CreateImplicit(
+          getContext(), CountedByExpr,
+          CountedByExpr->getType()->isPointerType(), VD, VD->getType(),
+          VK_LValue, OK_Ordinary);
+    }
+  } else {
+    CountedByExpr = MemberExpr::CreateImplicit(
+        getContext(), CountedByExpr,
+        CountedByExpr->getType()->isPointerType(),
+        const_cast<ValueDecl *>(CountedByVD), CountedByVD->getType(),
+        VK_LValue, OK_Ordinary);
+  }
+
+  return CountedByExpr;
+}
+
+const ValueDecl *CodeGenFunction::FindFlexibleArrayMemberField(
+    ASTContext &Ctx, const RecordDecl *RD) {
+  LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel =
+      getLangOpts().getStrictFlexArraysLevel();
+
+  for (const Decl *D : RD->decls()) {
+    if (const ValueDecl *VD = dyn_cast<ValueDecl>(D);
+        VD && Decl::isFlexibleArrayMemberLike(Ctx, VD, VD->getType(),
+                                              StrictFlexArraysLevel, true))
+      return VD;
+
+    if (const auto *Record = dyn_cast<RecordDecl>(D))
+      if (const ValueDecl *VD = FindFlexibleArrayMemberField(Ctx, Record))
+        return VD;
+  }
+
+  return nullptr;
+}
+
+const ValueDecl *CodeGenFunction::FindCountedByField(const Expr *Base) {
   const ValueDecl *VD = nullptr;
 
   Base = Base->IgnoreParenImpCasts();
@@ -984,12 +1039,14 @@ FieldDecl *CodeGenFunction::FindCountedByField(
       Ty = Ty->getPointeeType();
 
     if (const auto *RD = Ty->getAsRecordDecl())
-      VD = RD->getLastField();
+      VD = FindFlexibleArrayMemberField(getContext(), RD);
   } else if (const auto *CE = dyn_cast<CastExpr>(Base)) {
     if (const auto *ME = dyn_cast<MemberExpr>(CE->getSubExpr()))
       VD = dyn_cast<ValueDecl>(ME->getMemberDecl());
   }
 
+  LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel =
+      getLangOpts().getStrictFlexArraysLevel();
   const auto *FD = dyn_cast_if_present<FieldDecl>(VD);
   if (!FD || !FD->getParent() ||
       !Decl::isFlexibleArrayMemberLike(getContext(), FD, FD->getType(),
@@ -1000,12 +1057,14 @@ FieldDecl *CodeGenFunction::FindCountedByField(
   if (!CBA)
     return nullptr;
 
-  StringRef FieldName = CBA->getCountedByField()->getName();
-  auto It =
-      llvm::find_if(FD->getParent()->fields(), [&](const FieldDecl *Field) {
-        return FieldName == Field->getName();
-      });
-  return It != FD->getParent()->field_end() ? *It : nullptr;
+  const RecordDecl *RD = FD->getDeclContext()->getOuterLexicalRecordContext();
+  DeclarationName DName(CBA->getCountedByField());
+  DeclContext::lookup_result Lookup = RD->lookup(DName);
+
+  if (Lookup.empty())
+    return nullptr;
+
+  return dyn_cast<ValueDecl>(Lookup.front());
 }
 
 void CodeGenFunction::EmitBoundsCheck(const Expr *E, const Expr *Base,
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index e82115e2d706cf1..64f192037ec8ce5 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -3022,11 +3022,17 @@ class CodeGenFunction : public CodeGenTypeCache {
   void EmitBoundsCheck(const Expr *E, const Expr *Base, llvm::Value *Index,
                        QualType IndexType, bool Accessed);
 
+  // Find a struct's flexible array member. It may be embedded inside multiple
+  // sub-structs, but must still be the last field.
+  const ValueDecl *FindFlexibleArrayMemberField(ASTContext &Ctx,
+                                                const RecordDecl *RD);
+
   /// Find the FieldDecl specified in a FAM's "counted_by" attribute. Returns
   /// \p nullptr if either the attribute or the field doesn't exist.
-  FieldDecl *FindCountedByField(
-      const Expr *Base,
-      LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel);
+  const ValueDecl *FindCountedByField(const Expr *Base);
+
+  /// Build an expression accessing the "counted_by" field.
+  Expr *BuildCountedByFieldExpr(Expr *Base, const ValueDecl *CountedByVD);
 
   llvm::Value *EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
                                        bool isInc, bool isPre);

@bwendling
Copy link
Collaborator Author

To answer @nickdesaulniers 's question, the testcases are in the works. :-)

@github-actions
Copy link

github-actions bot commented Oct 29, 2023

✅ With the latest revision this PR passed the C/C++ code formatter.


// Find the outer struct expr (i.e. p in p->a.b.c.d).
Expr *CountedByExpr =
BuildCountedByFieldExpr(const_cast<Expr *>(E), CountedByFD);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The amount of const_cast in handling this attribute is making me uneasy.

I guess I should have caught the first time around that this is literally building a new Expr for the member access, rather than building the GEPs in IR that I guess I would have expected for clang's Codegen to perform.

Is there any way to cut down the number of const_casts this code is doing?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it's gross.

The const_cast s are needed, because EmitAnyExprToTemp requires it to be non-const. It's gross, but unfortunately necessary. I put the cast here to keep the BuildCountedByFieldExpr nicer with regard to const_casts. To be honest, I'm not sure why the Emit*Expr methods use non-const Expr pointers. Seems unnecessary to me... :-/

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small correction. It's the MemberExpr::CreateImplicit c'tor that requires a non-const Expr, not the emit instructions.

// 2) bdos of the whole struct, including the flexible array:
//
// __builtin_dynamic_object_size(p, 1) ==
// sizeof(*p) + p->count * sizeof(*p->array)
Copy link
Contributor

@rapidsna rapidsna Oct 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to the alignment and padding, the size of struct s isn't necessarily the start offset of the flexible array member. FYI https://godbolt.org/z/bMb19n9Tz

I think the equation should be something like this:
max (offset(struct s, array) + p->count * sizeof(*p->array), sizeof(*p))

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand the issue involved here. From what I can tell, the getContext().getTypeSize(StructTy) returns the number of bits in the whole structure, which I divide by 8 to get bytes.

Copy link
Contributor

@rapidsna rapidsna Oct 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is about the semantics.

int array[__counted_by(count)] means you have count elements starting right from the flexible member array offset, which is offsetof(struct s, array).

Whereas, sizeof(*p) + p->count * sizeof(*p->array) indicates you have count elements starting from at the end of the struct, which isn't always the same as the offset of array because of the padding and alignment rule. https://godbolt.org/z/bMb19n9Tz shows when sizeof(struct s) and offsetof(struct s, array) aren't the same. The problem is that this can result in the object size bigger than what it should be.

Copy link
Collaborator Author

@bwendling bwendling Oct 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I never want offsetof here. The sizeof(*p) will report the correct size of the array without adding on any size for the flexible array member, when it has no value (size that is). So sizeof(*p) for the array in the example is 16, which is correct. Possibly the only issue may be when the flexible array member isn't strict, i.e. it has something like int array[0] or int array[1].

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was why I suggested to have max here: max (offset(struct s, array) + p->count * sizeof(*p->array), sizeof(*p)) so that it can return the correct size when the struct size is bigger than the end of the array due to the padding.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don’t understand what we don’t mutually understand. In the same situation:

#include <string.h>

struct flex {
    double dummy;
    char c;
    char fam[__counted_by(c)];
};

struct flex f = { 123.0, 2, { 1, 2 } };

int main() {
    memset(&f, 0, sizeof(f) + f.c * sizeof(*f.fam));
}

We have an ASAN trap. Here are all the ways I can think of to reconcile this fact with the model that you propose:

  • 2 is the wrong count value for a 2-element array;
  • Clang is under-allocating for f;
  • this is in-bounds and ASAN has a false positive;
  • it's actually OK to write out of bounds when relying on __builtin_dynamic_object_size(p, 1);
  • the definition chosen for __builtin_dynamic_object_size is wrong.

The only one that makes sense to me is that the definition chosen for __builtin_dynamic_object_size. Which one is it to you? Do you see another way out?

Copy link
Contributor

@rapidsna rapidsna Oct 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I think we are talking past to each other a little bit. That comment I was responding to this:

Why wouldn't it include the FAM part if the full struct pointer is specified to __bdos?

I'm saying "the full struct size" isn't exactly struct + fam because fam doesn't always exactly start from sizeof(struct) when there is a padding in the struct due to the alignment. And sizeof(*p) + p->count * sizeof(*p->array) gives us value that's more than the full struct size.

As we all see in the previous examples, the full struct size is offsetof(struct s, fam) + sizeof(*p->array) * p->count with the result being round up to alignof(struct s).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rapidsna Ah! Okay. That makes more sense, thank you. I'll see what I can do here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that it's okay for the count * sizeof(fam) to be less than the actual size of the fam. It's there to ensure the index doesn't go over what's expected.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I added dc8f0df, which should take care of this issue here. Sorry about my confusion. PTAL.

@bwendling
Copy link
Collaborator Author

@kees, @nickdesaulniers, @rapidsna, and @apple-fcloutier Should this feature support a __bdos to an address inside the FAM?

#include <stdio.h>
#include <stdlib.h>

struct flex {
        double dummy;
        char count;
        char fam[] __attribute__((counted_by(count)));
};

int main() {
        struct flex *f = malloc(sizeof(struct flex) + 42 * sizeof(char));

        f->count = 42;
        printf("__bdos(&f->fam[3], 1) == %lu\n", __builtin_dynamic_object_size(&f->fam[3], 1));
        return 0;
}

@rapidsna
Copy link
Contributor

rapidsna commented Nov 3, 2023

@kees, @nickdesaulniers, @rapidsna, and @apple-fcloutier Should this feature support a __bdos to an address inside the FAM?

#include <stdio.h>
#include <stdlib.h>

struct flex {
        double dummy;
        char count;
        char fam[] __attribute__((counted_by(count)));
};

int main() {
        struct flex *f = malloc(sizeof(struct flex) + 42 * sizeof(char));

        f->count = 42;
        printf("__bdos(&f->fam[3], 1) == %lu\n", __builtin_dynamic_object_size(&f->fam[3], 1));
        return 0;
}

Yes. Supporting it similar to how const-sized arrays are currently handled makes sense to me.

@rapidsna
Copy link
Contributor

rapidsna commented Nov 3, 2023

#include <stdio.h>
#include <stdlib.h>
struct flex {
    int c;
    int fam[] __attribute__((counted_by(c)));
};

int main() {
    struct flex *p = (struct flex *)malloc(sizeof(struct flex) + sizeof(int) * 10);
    p->c = 100;
    printf("%lu\n", __builtin_dynamic_object_size(&p->fam[0], 0)); // 40 : size from malloc, but it only contains the array part. Shouldn't it be 44 to include the entire object size?
    printf("%lu\n", __builtin_dynamic_object_size(&p->fam[0], 1)); // 40 : size from malloc;
    printf("%lu\n", __builtin_dynamic_object_size(p, 0));          // 404 : size from counted_by
}

@bwendling It could be tracked as a separate issue, but there seems to be some inconsistencies in where bdos is derived from. For p it seems the counted_by wins over malloc. But for &->fam[0] malloc seems to win.

https://godbolt.org/z/G7WfY4faE

@bwendling
Copy link
Collaborator Author

#include <stdio.h>
#include <stdlib.h>
struct flex {
    int c;
    int fam[] __attribute__((counted_by(c)));
};

int main() {
    struct flex *p = (struct flex *)malloc(sizeof(struct flex) + sizeof(int) * 10);
    p->c = 100;
    printf("%lu\n", __builtin_dynamic_object_size(&p->fam[0], 0)); // 40 : size from malloc, but it only contains the array part. Shouldn't it be 44 to include the entire object size?
    printf("%lu\n", __builtin_dynamic_object_size(&p->fam[0], 1)); // 40 : size from malloc;
    printf("%lu\n", __builtin_dynamic_object_size(p, 0));          // 404 : size from counted_by
}

@bwendling It could be tracked as a separate issue, but there seems to be some inconsistencies in where bdos is derived from. For p it seems the counted_by wins over malloc. But for &->fam[0] malloc seems to win.

https://godbolt.org/z/G7WfY4faE

I think the first two printf s in your code seem correct with 40, because they're both looking at only the FAM, not the entire struct. The fact that they're not reporting 400 here is because godbolt doesn't have this PR. With this PR I get:

$ clang -O2 ~/llvm/bdos.c
$ ./a.out
400
400
404

Which seems correct to me, given the incorrect value in p->c. The documentation does make it very clear that the user is responsible for supplying the correct value to the FAM counter. The GCC people are making that language even stricter, so I'll do that as well. (They also have a dependency issue between the FAM and the counter field that they're solving with a compiler-only pseudo-function call. I'll create an intrinsic on our side to do the same once they land on a solution.)

@rapidsna
Copy link
Contributor

rapidsna commented Nov 3, 2023

With this PR I get:

$ clang -O2 ~/llvm/bdos.c
$ ./a.out
400
400
404

Oh I see.

struct flex *p = (struct flex *)malloc(sizeof(struct flex) + sizeof(int) * 10);

I just wanted to call out that this case is interesting because the size derived from malloc and __counted_by compete each other. And __counted_by always wins. This conforms to how -fbounds-safety will see the size of objects, so sounds like we are all happy with it.

Similarly, this will also mean that , when we support __counted_by for pointers, the following will be the case.

int arr[10];
int *__counted_by(4) p = arr;

__bdos(arr, 0); // returns 4 * sizeof(int) instead of 10 * sizeof(int)

@bwendling
Copy link
Collaborator Author

I just wanted to call out that this case is interesting because the size derived from malloc and __counted_by compete each other. And __counted_by always wins. This conforms to how -fbounds-safety will see the size of objects, so sounds like we are all happy with it.

Great! :-)

As for the updating the docs and adding the intrinsic, I'd like to do that after GCC settles on their implementation. So I'd do it as a follow-up to this.

Copy link
Member

@nickdesaulniers nickdesaulniers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe @rapidsna has parting thoughts?

@bwendling
Copy link
Collaborator Author

@rapidsna Friendly ping. :-)

return Builder.CreateAdd(ObjectSize, Mul);
// Get the size of the flexible array member's base type.
const ValueDecl *FAM = FindFlexibleArrayMemberField(Ctx, OuterRD);
const ArrayType *ArrayTy = Ctx.getAsArrayType(FAM->getType());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we handle the case where FAM == nullptr?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point, we know that FAM can't be null. The FindCountedByField call above can only return non-null if there's a FAM.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. So sounds like this relies on the fact that counted_by can currently be added to a flexible array member only? Should we add an assertion here to make it easier to deal with when that assumption breaks in future?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a sema error check to ensure that it's applied only to a FAM. I suppose it can't hurt to add an assert though.

llvm::Constant *ElemSize =
llvm::ConstantInt::get(CountedByInst->getType(), Size.getQuantity());

llvm::Value *FAMSize = Builder.CreateMul(CountedByInst, ElemSize);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if i > count so CountedByInst (count - i) becomes negative? Or in general, what should__bdos return if &a[i] is pointing to outside the array bounds (either it's fam or just an array)? Is it defined?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's more a sanitizer check, which should abort. This code is just dealing with the __bdos calls. If it returns a negative value, then that's badness. I know that it can return -1 (if the second argument is 1, otherwise 0) if it can't determine the size of the object. I don't know if it's completely invalid, per se. For instance, if you have this:

struct s {
  char count;
  int fam[];
};

And you want >128 elements in fam, then count is going to resemble a negative number. I know this is contrived, but it's a possibility.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm...I think maybe I should put a check in to see if the types are signed and then perform the appropriate [SU]Ext operations... Thoughts?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. I converted the instructions to carry the signedness of the counted_by field's type. PTAL.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, it seems like __bdos returns 0 when it's pointing to an OOB object. https://godbolt.org/z/eT4c7aPWr
At least from what I'm seeing here. WDYT?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need to use pointers here. See https://godbolt.org/z/qWjs4TeW6

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, yes it seems like I'll have to ensure that we're not pointing outside of the struct here.

@bwendling
Copy link
Collaborator Author

@rapidsna My recent commits try to address a lot of the issues you brought up. If the FAM's array index is negative or out of bounds, it should now catch it and return an appropriate value. There may still be some corner cases that have to be hammered out, but I'd like to get this in if you feel it's ready, as I think the corner cases will occur infrequently.

Copy link
Contributor

@rapidsna rapidsna left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! LGTM.

@bwendling bwendling merged commit bc09ec6 into llvm:main Nov 9, 2023
@bwendling bwendling deleted the counted-by-expansion branch November 9, 2023 18:18
@nathanchance
Copy link
Member

This change introduces a crash with -fsanitize=array-bounds. A reproducer from cvise:

struct irq_data {
  struct irq_domain *domain;
} irq_domain_fix_revmap_d;
struct irq_domain {
  struct irq_domain *parent;
  int revmap_size;
  struct irq_data *revmap[] __attribute__((__counted_by__(revmap_size)));
};
long irq_domain_fix_revmap_d_0;
int irq_domain_pop_irq() {
  irq_domain_fix_revmap_d.domain->revmap[irq_domain_fix_revmap_d_0] = 0;
  return 0;
}
clang: /mnt/nvme/tmp/cvise.buvTN27aMk/src/llvm/include/llvm/IR/DataLayout.h:652: TypeSize llvm::StructLayout::getElementOffset(unsigned int) const: Assertion `Idx < NumElements && "Invalid element idx!"' failed.
PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace, preprocessed source, and associated run script.
Stack dump:
0.	Program arguments: clang -O2 -fsanitize=array-bounds -c -o /dev/null irqdomain.i
1.	<eof> parser at end of file
2.	irqdomain.i:10:5: LLVM IR generation of declaration 'irq_domain_pop_irq'
3.	irqdomain.i:10:5: Generating code for declaration 'irq_domain_pop_irq'
 #0 0x00005622f687d9e8 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x41069e8)
 #1 0x00005622f687b61e llvm::sys::RunSignalHandlers() (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x410461e)
 #2 0x00005622f6800926 CrashRecoverySignalHandler(int) CrashRecoveryContext.cpp:0:0
 #3 0x00007f1204079710 (/usr/lib/libc.so.6+0x3e710)
 #4 0x00007f12040c983c (/usr/lib/libc.so.6+0x8e83c)
 #5 0x00007f1204079668 gsignal (/usr/lib/libc.so.6+0x3e668)
 #6 0x00007f12040614b8 abort (/usr/lib/libc.so.6+0x264b8)
 #7 0x00007f12040613dc (/usr/lib/libc.so.6+0x263dc)
 #8 0x00007f1204071d26 (/usr/lib/libc.so.6+0x36d26)
 #9 0x00005622f6ae11bb clang::CodeGen::CGBuilderTy::CreateStructGEP(clang::CodeGen::Address, unsigned int, llvm::Twine const&) CGCall.cpp:0:0
#10 0x00005622f6bcd204 emitAddrOfFieldStorage(clang::CodeGen::CodeGenFunction&, clang::CodeGen::Address, clang::FieldDecl const*) CGExpr.cpp:0:0
#11 0x00005622f6bb0082 clang::CodeGen::CodeGenFunction::EmitLValueForField(clang::CodeGen::LValue, clang::FieldDecl const*) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x4439082)
#12 0x00005622f6bbef07 clang::CodeGen::CodeGenFunction::EmitMemberExpr(clang::MemberExpr const*) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x4447f07)
#13 0x00005622f6bb7c9f clang::CodeGen::CodeGenFunction::EmitLValueHelper(clang::Expr const*, clang::CodeGen::KnownNonNull_t) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x4440c9f)
#14 0x00005622f6bb62ad clang::CodeGen::CodeGenFunction::EmitCheckedLValue(clang::Expr const*, clang::CodeGen::CodeGenFunction::TypeCheckKind) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x443f2ad)
#15 0x00005622f6be69ed (anonymous namespace)::ScalarExprEmitter::VisitMemberExpr(clang::MemberExpr*) CGExprScalar.cpp:0:0
#16 0x00005622f6bd2fad clang::CodeGen::CodeGenFunction::EmitScalarExpr(clang::Expr const*, bool) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x445bfad)
#17 0x00005622f6baba93 clang::CodeGen::CodeGenFunction::EmitAnyExpr(clang::Expr const*, clang::CodeGen::AggValueSlot, bool) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x4434a93)
#18 0x00005622f6bac39d clang::CodeGen::CodeGenFunction::EmitAnyExprToTemp(clang::Expr const*) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x443539d)
#19 0x00005622f6bb481f clang::CodeGen::CodeGenFunction::EmitBoundsCheck(clang::Expr const*, clang::Expr const*, llvm::Value*, clang::QualType, bool) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x443d81f)
#20 0x00005622f6bcb0e3 clang::CodeGen::CodeGenFunction::EmitArraySubscriptExpr(clang::ArraySubscriptExpr const*, bool)::$_0::operator()(bool) const CGExpr.cpp:0:0
#21 0x00005622f6bb7286 clang::CodeGen::CodeGenFunction::EmitArraySubscriptExpr(clang::ArraySubscriptExpr const*, bool) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x4440286)
#22 0x00005622f6bb629b clang::CodeGen::CodeGenFunction::EmitCheckedLValue(clang::Expr const*, clang::CodeGen::CodeGenFunction::TypeCheckKind) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x443f29b)
#23 0x00005622f6bdf6cb (anonymous namespace)::ScalarExprEmitter::VisitBinAssign(clang::BinaryOperator const*) CGExprScalar.cpp:0:0
#24 0x00005622f6bd2fad clang::CodeGen::CodeGenFunction::EmitScalarExpr(clang::Expr const*, bool) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x445bfad)
#25 0x00005622f6baba93 clang::CodeGen::CodeGenFunction::EmitAnyExpr(clang::Expr const*, clang::CodeGen::AggValueSlot, bool) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x4434a93)
#26 0x00005622f6baba1c clang::CodeGen::CodeGenFunction::EmitIgnoredExpr(clang::Expr const*) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x4434a1c)
#27 0x00005622f6c914db clang::CodeGen::CodeGenFunction::EmitStmt(clang::Stmt const*, llvm::ArrayRef<clang::Attr const*>) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x451a4db)
#28 0x00005622f6c9ef40 clang::CodeGen::CodeGenFunction::EmitCompoundStmtWithoutScope(clang::CompoundStmt const&, bool, clang::CodeGen::AggValueSlot) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x4527f40)
#29 0x00005622f6b98fe5 clang::CodeGen::CodeGenFunction::EmitFunctionBody(clang::Stmt const*) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x4421fe5)
#30 0x00005622f6b99cb6 clang::CodeGen::CodeGenFunction::GenerateCode(clang::GlobalDecl, llvm::Function*, clang::CodeGen::CGFunctionInfo const&) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x4422cb6)
#31 0x00005622f6a77d7c clang::CodeGen::CodeGenModule::EmitGlobalFunctionDefinition(clang::GlobalDecl, llvm::GlobalValue*) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x4300d7c)
#32 0x00005622f6a70043 clang::CodeGen::CodeGenModule::EmitGlobalDefinition(clang::GlobalDecl, llvm::GlobalValue*) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x42f9043)
#33 0x00005622f6a74952 clang::CodeGen::CodeGenModule::EmitGlobal(clang::GlobalDecl) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x42fd952)
#34 0x00005622f6a6ec91 clang::CodeGen::CodeGenModule::EmitTopLevelDecl(clang::Decl*) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x42f7c91)
#35 0x00005622f703715c (anonymous namespace)::CodeGeneratorImpl::HandleTopLevelDecl(clang::DeclGroupRef) ModuleBuilder.cpp:0:0
#36 0x00005622f702dc56 clang::BackendConsumer::HandleTopLevelDecl(clang::DeclGroupRef) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x48b6c56)
#37 0x00005622f82db83a clang::ParseAST(clang::Sema&, bool, bool) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x5b6483a)
#38 0x00005622f742bd8f clang::FrontendAction::Execute() (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x4cb4d8f)
#39 0x00005622f739d7bd clang::CompilerInstance::ExecuteAction(clang::FrontendAction&) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x4c267bd)
#40 0x00005622f74f5178 clang::ExecuteCompilerInvocation(clang::CompilerInstance*) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x4d7e178)
#41 0x00005622f51e8af2 cc1_main(llvm::ArrayRef<char const*>, char const*, void*) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x2a71af2)
#42 0x00005622f51e4f3d ExecuteCC1Tool(llvm::SmallVectorImpl<char const*>&, llvm::ToolContext const&) driver.cpp:0:0
#43 0x00005622f71fde09 void llvm::function_ref<void ()>::callback_fn<clang::driver::CC1Command::Execute(llvm::ArrayRef<std::optional<llvm::StringRef>>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>*, bool*) const::$_0>(long) Job.cpp:0:0
#44 0x00005622f68006a6 llvm::CrashRecoveryContext::RunSafely(llvm::function_ref<void ()>) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x40896a6)
#45 0x00005622f71fd512 clang::driver::CC1Command::Execute(llvm::ArrayRef<std::optional<llvm::StringRef>>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>*, bool*) const (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x4a86512)
#46 0x00005622f71b86c7 clang::driver::Compilation::ExecuteCommand(clang::driver::Command const&, clang::driver::Command const*&, bool) const (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x4a416c7)
#47 0x00005622f71b8c07 clang::driver::Compilation::ExecuteJobs(clang::driver::JobList const&, llvm::SmallVectorImpl<std::pair<int, clang::driver::Command const*>>&, bool) const (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x4a41c07)
#48 0x00005622f71d8bc9 clang::driver::Driver::ExecuteCompilation(clang::driver::Compilation&, llvm::SmallVectorImpl<std::pair<int, clang::driver::Command const*>>&) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x4a61bc9)
#49 0x00005622f51e43f6 clang_main(int, char**, llvm::ToolContext const&) (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x2a6d3f6)
#50 0x00005622f51f5241 main (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x2a7e241)
#51 0x00007f1204062cd0 (/usr/lib/libc.so.6+0x27cd0)
#52 0x00007f1204062d8a __libc_start_main (/usr/lib/libc.so.6+0x27d8a)
#53 0x00005622f51e14e5 _start (/mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin/clang-18+0x2a6a4e5)
clang: error: clang frontend command failed with exit code 134 (use -v to see invocation)
ClangBuiltLinux clang version 18.0.0 (https://github.com/llvm/llvm-project bc09ec696209b3aea74d49767b15c2f34e363933)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /mnt/nvme/tmp/cvise.buvTN27aMk/install/llvm-bad/bin
clang: note: diagnostic msg: Error generating preprocessed source(s) - no preprocessable inputs.

@bwendling
Copy link
Collaborator Author

@nathanchance Could you try the patch in https://github.com/bwendling/llvm-project/tree/counted-by-offsets to see if it helps? I'll take a look, but it's going to compile slowly on my machine...

@bwendling
Copy link
Collaborator Author

zahiraam pushed a commit to zahiraam/llvm-project that referenced this pull request Nov 20, 2023
Break down the counted_by calculations so that they correctly handle
anonymous structs, which are specified internally as IndirectFieldDecls.

Improves the calculation of __bdos on a different field member in the struct.
And also improves support for __bdos in an index into the FAM. If the index
is further out than the length of the FAM, then we return __bdos's "can't
determine the size" value (zero or negative one, depending on type).

Also simplify the code to use helper methods to get the field referenced
by counted_by and the flexible array member itself, which also had some
issues with FAMs in sub-structs.
bwendling added a commit to bwendling/llvm-project that referenced this pull request Dec 18, 2023
There are many issues that popped up with the counted_by feature. The
patch has grown too large and approval is blocking Linux testing.

Includes reverts of:
commit 769bc11 ("[Clang] Implement the 'counted_by' attribute (llvm#68750)")
commit bc09ec6 ("[CodeGen] Revamp counted_by calculations (llvm#70606)")
commit 1a09cfb ("[Clang] counted_by attr can apply only to C99 flexible array members (llvm#72347)")
commit a76adfb ("[NFC][Clang] Refactor code to calculate flexible array member size (llvm#72790)")
commit d8447c7 ("[Clang] Correct handling of negative and out-of-bounds indices (llvm#71877)")
Partial commit b31cd07 ("[Clang] Regenerate test checks (NFC)")
bwendling added a commit that referenced this pull request Dec 18, 2023
There are many issues that popped up with the counted_by feature. The
patch #73730 has grown too large and approval is blocking Linux testing.

Includes reverts of:
commit 769bc11 ("[Clang] Implement the 'counted_by' attribute
(#68750)")
commit bc09ec6 ("[CodeGen] Revamp counted_by calculations
(#70606)")
commit 1a09cfb ("[Clang] counted_by attr can apply only to C99
flexible array members (#72347)")
commit a76adfb ("[NFC][Clang] Refactor code to calculate flexible
array member size (#72790)")
commit d8447c7 ("[Clang] Correct handling of negative and
out-of-bounds indices (#71877)")
Partial commit b31cd07 ("[Clang] Regenerate test checks (NFC)")

Closes #73168
Closes #75173
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:codegen IR generation bugs: mangling, exceptions, etc. clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants