Skip to content

[Clang] Handle structs with inner structs and no fields #89126

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 9 commits into from
Apr 19, 2024

Conversation

bwendling
Copy link
Collaborator

@bwendling bwendling commented Apr 17, 2024

A struct that declares an inner struct, but no fields, won't have a field count. So getting the offset of the inner struct fails. This happens in both C and C++:

struct foo {
struct bar {
int Quantizermatrix[];
};
};

Here 'struct foo' has no fields.

Closes: #88931

A struct that declares an inner struct, but no fields, won't have a
field count. So getting the offset of the inner struct fails. This
happens in both C and C++:

  struct foo {
    struct bar {
      int Quantizermatrix[];
    };
  };

Here 'struct foo' has no fields.
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:codegen IR generation bugs: mangling, exceptions, etc. labels Apr 17, 2024
@llvmbot
Copy link
Member

llvmbot commented Apr 17, 2024

@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-clang

Author: Bill Wendling (bwendling)

Changes

A struct that declares an inner struct, but no fields, won't have a field count. So getting the offset of the inner struct fails. This happens in both C and C++:

struct foo {
struct bar {
int Quantizermatrix[];
};
};

Here 'struct foo' has no fields.

Fixes: #88931


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

3 Files Affected:

  • (modified) clang/lib/CodeGen/CGBuiltin.cpp (+14-1)
  • (added) clang/test/CodeGen/attr-counted-by-pr88931.c (+22)
  • (added) clang/test/CodeGen/attr-counted-by-pr88931.cpp (+21)
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index a05874e63c73c2..bee12c4469e7f7 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -829,6 +829,9 @@ const FieldDecl *CodeGenFunction::FindFlexibleArrayMemberField(
   unsigned FieldNo = 0;
   bool IsUnion = RD->isUnion();
 
+  if (RD->isImplicit())
+    return nullptr;
+
   for (const Decl *D : RD->decls()) {
     if (const auto *Field = dyn_cast<FieldDecl>(D);
         Field && (Name.empty() || Field->getNameAsString() == Name) &&
@@ -844,7 +847,17 @@ const FieldDecl *CodeGenFunction::FindFlexibleArrayMemberField(
       if (const FieldDecl *Field =
               FindFlexibleArrayMemberField(Ctx, Record, Name, Offset)) {
         const ASTRecordLayout &Layout = Ctx.getASTRecordLayout(RD);
-        Offset += Layout.getFieldOffset(FieldNo);
+        if (Layout.getFieldCount()) {
+          // A struct that holds only an inner struct won't have any fields. E.g.
+          //
+          //     struct foo {
+          //         struct bar {
+          //             int count;
+          //             int array[];
+          //         };
+          //     };
+          Offset += Layout.getFieldOffset(FieldNo);
+        }
         return Field;
       }
 
diff --git a/clang/test/CodeGen/attr-counted-by-pr88931.c b/clang/test/CodeGen/attr-counted-by-pr88931.c
new file mode 100644
index 00000000000000..baa1bbc8397456
--- /dev/null
+++ b/clang/test/CodeGen/attr-counted-by-pr88931.c
@@ -0,0 +1,22 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 4
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wno-missing-declarations -emit-llvm -o - %s | FileCheck %s
+
+struct foo {
+  struct bar {
+    int count;
+    int array[] __attribute__((counted_by(count)));
+  };
+};
+
+void init(void * __attribute__((pass_dynamic_object_size(0))));
+
+// CHECK-LABEL: define dso_local void @test1(
+// CHECK-SAME: ptr noundef [[P:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT:  entry:
+// CHECK-NEXT:    [[ARRAY:%.*]] = getelementptr inbounds i8, ptr [[P]], i64 4
+// CHECK-NEXT:    tail call void @init(ptr noundef nonnull [[ARRAY]], i64 noundef -1) #[[ATTR2:[0-9]+]]
+// CHECK-NEXT:    ret void
+//
+void test1(struct bar *p) {
+  init(p->array);
+}
diff --git a/clang/test/CodeGen/attr-counted-by-pr88931.cpp b/clang/test/CodeGen/attr-counted-by-pr88931.cpp
new file mode 100644
index 00000000000000..2a8cc1d07e50d9
--- /dev/null
+++ b/clang/test/CodeGen/attr-counted-by-pr88931.cpp
@@ -0,0 +1,21 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 4
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wall -emit-llvm -o - %s | FileCheck %s
+
+struct foo {
+  struct bar {
+    int array[];
+    bar();
+  };
+};
+
+void init(void * __attribute__((pass_dynamic_object_size(0))));
+
+// CHECK-LABEL: define dso_local void @_ZN3foo3barC1Ev(
+// CHECK-SAME: ptr noundef nonnull align 4 dereferenceable(1) [[THIS:%.*]]) unnamed_addr #[[ATTR0:[0-9]+]] align 2 {
+// CHECK-NEXT:  entry:
+// CHECK-NEXT:    tail call void @_Z4initPvU25pass_dynamic_object_size0(ptr noundef nonnull [[THIS]], i64 noundef -1) #[[ATTR2:[0-9]+]]
+// CHECK-NEXT:    ret void
+//
+foo::bar::bar() {
+  init(array);
+}

Copy link

github-actions bot commented Apr 17, 2024

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

@kees kees self-requested a review April 18, 2024 00:31
Copy link
Contributor

@kees kees left a comment

Choose a reason for hiding this comment

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

Tests and logic adjustment looks good to me.

@@ -844,7 +847,18 @@ const FieldDecl *CodeGenFunction::FindFlexibleArrayMemberField(
if (const FieldDecl *Field =
Copy link
Collaborator

Choose a reason for hiding this comment

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

FieldNo and Layout are referring to fields of "RD"; the "Field" found in the recursive visit is a member of Record (or some subobject of Record). So the code is doing math on completely unrelated offsets. Checking getFieldCount is just masking the issue.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe instead of looking for RecordDecls, this code should be looking for fields where the type of the field is an anonymous struct/union.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

FieldNo and Layout are referring to fields of "RD"; the "Field" found in the recursive visit is a member of Record (or some subobject of Record). So the code is doing math on completely unrelated offsets. Checking getFieldCount is just masking the issue.

That's not the issue. What's happening is when an inner struct is declared/defined, we recurse within it to try to find the Field offset. If we do find it, then Offset has the offset value within Record. At this point, what we need is the offset up to the RecordDecl, but since those may or may not have a field number associated with them, we use the last FieldNo to get that offset.

A bit clearer:

struct foo {        /* <- Passed in as RD */
  struct bar {      /* <- Not a field, so FieldNo isn't incremented. Recurse on struct bar */
    int array[];    /* <- FAM found at offset 0 of struct bar */
  };
                    /* <- Returning with the array FieldDecl, we want to add on any
                          offset associated with the placement of the struct bar
                          definition, but there are no FieldDecls, and so we can't
                          call 'Layout.getFieldOffset()' */
};

This has obvious issues with virtual classes and the like, which is why C++ doesn't officially support FAMs (I believe it's an extension).

To be fair, I had the same question you had. I should document this better.

Maybe instead of looking for RecordDecls, this code should be looking for fields where the type of the field is an anonymous struct/union.

We want to look into all inner structs to find a FAM that may be lurking deep down within the bowels of the struct, which may involve non-anonymous structs.

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 did several tests, and it looks like if there's an inner struct that's not accessible, that doesn't affect the offsets of fields outside of that inner struct.

struct foo {
  struct inner_1 {
    /* fields */
  };
  struct inner_2 {
    int a;               /* __builtin_offsetof is 0 */
  };
  int b;                 /* __builtin_offsetof is 0 */
};

@efriedma-quic Do you have any thoughts?

Copy link
Collaborator

@efriedma-quic efriedma-quic Apr 18, 2024

Choose a reason for hiding this comment

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

if there's an inner struct that's not accessible, that doesn't affect the offsets of fields outside of that inner struct.

Yes, the only thing that's relevant to offsets in a struct is the fields. A struct definition inside another struct declaration has exactly the same semantics as a struct definition anywhere else (unless it's an anonymous struct/union).


Maybe also try the following testcase.

struct foo {
  int x,y,z;
  struct bar {
    int count;
    int array[] __attribute__((counted_by(count)));
  };
};
void init(void * __attribute__((pass_dynamic_object_size(0))));
void test1(struct bar *p) {
  init(p->array);
}

Copy link
Collaborator

@efriedma-quic efriedma-quic left a comment

Choose a reason for hiding this comment

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

We should probably apply the same fix to CountCountedByAttrs.

Along those lines, we should be able to handle:

struct bar {
  int count;
  int array[] __attribute__((counted_by(count)));
};
struct foo { struct bar x; };
void init(void * __attribute__((pass_dynamic_object_size(0))));
void test1(struct foo *p) {
  init(p);
}

struct foo {
struct bar {
int count;
int array[] __attribute__((counted_by(count)));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ideally, we should be able to compute the size here; would need to change the way we compute the "outer" type.

Can leave that for a followup, I guess.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We can, if handling struct bar on its own. It's just there's seemingly no way in C to access the fields in struct bar from struct foo...

Copy link
Collaborator

Choose a reason for hiding this comment

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

The only reason foo is relevant in the first place is that getOuterLexicalRecordContext() skips over bar.

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 see what you mean. I'd like to leave it for a followup, since this change is meant to fix an ICE.

@bwendling
Copy link
Collaborator Author

We should probably apply the same fix to CountCountedByAttrs.

Along those lines, we should be able to handle:

struct bar {
  int count;
  int array[] __attribute__((counted_by(count)));
};
struct foo { struct bar x; };
void init(void * __attribute__((pass_dynamic_object_size(0))));
void test1(struct foo *p) {
  init(p);
}

I can support that. I'll add it as a testcase.

// CHECK-NEXT: tail call void @init(ptr noundef [[P]], i64 noundef -1) #[[ATTR2]]
// CHECK-NEXT: ret void
//
void test2(struct foo *p) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Supposed to be bux*?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Doh! Yes. Done.

Copy link
Collaborator

@efriedma-quic efriedma-quic left a comment

Choose a reason for hiding this comment

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

LGTM

Please don't forget to fix CountCountedByAttrs... but it's fine if it's in a followup.

@bwendling bwendling merged commit c32712d into llvm:main Apr 19, 2024
3 of 4 checks passed
@bwendling bwendling deleted the pr88931 branch April 19, 2024 19:48
llvmbot pushed a commit to llvmbot/llvm-project that referenced this pull request Apr 19, 2024
A struct that declares an inner struct, but no fields, won't have a
field count. So getting the offset of the inner struct fails. This
happens in both C and C++:

  struct foo {
    struct bar {
      int Quantizermatrix[];
    };
  };

Here 'struct foo' has no fields.

Closes: llvm#88931
(cherry picked from commit c32712d)
return nullptr;

for (const FieldDecl *FD : RD->fields()) {
if ((Name.empty() || FD->getNameAsString() == Name) &&
Copy link
Collaborator

Choose a reason for hiding this comment

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

Minor question I thought of looking at this one more time for the backport; why are we checking the name of the FieldDecl, instead just checking pointer equality? The caller has a FieldDecl, I think.

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 I did it this way to support searching from either a pointer to the whole struct or pointer to the FAM. I suppose running this when we know we're pointing to the FAM is a bit redundant. And yes, using a FieldDecl pointer instead is almost certainly better here.

I think what I want to do though is rework some of the code so that we support __builtin_dynamic_object_size for more than just pointing to either the FAM or struct. I'll make that change as well.

aniplcc pushed a commit to aniplcc/llvm-project that referenced this pull request Apr 21, 2024
A struct that declares an inner struct, but no fields, won't have a
field count. So getting the offset of the inner struct fails. This
happens in both C and C++:

  struct foo {
    struct bar {
      int Quantizermatrix[];
    };
  };

Here 'struct foo' has no fields.

Closes: llvm#88931
@bwendling
Copy link
Collaborator Author

@tstellar PR #90118 is the fix here.

bwendling added a commit that referenced this pull request Apr 25, 2024
A struct that declares an inner struct, but no fields, won't have a
field count. So getting the offset of the inner struct fails. This
happens in both C and C++:

  struct foo {
    struct bar {
      int Quantizermatrix[];
    };
  };

Here 'struct foo' has no fields.

Closes: #88931
tstellar pushed a commit that referenced this pull request Apr 29, 2024
A struct that declares an inner struct, but no fields, won't have a
field count. So getting the offset of the inner struct fails. This
happens in both C and C++:

  struct foo {
    struct bar {
      int Quantizermatrix[];
    };
  };

Here 'struct foo' has no fields.

Closes: #88931
@pointhex pointhex mentioned this pull request May 7, 2024
ahatanaka pushed a commit to swiftlang/llvm-project that referenced this pull request Jun 25, 2024
A struct that declares an inner struct, but no fields, won't have a
field count. So getting the offset of the inner struct fails. This
happens in both C and C++:

  struct foo {
    struct bar {
      int Quantizermatrix[];
    };
  };

Here 'struct foo' has no fields.

Closes: llvm#88931
(cherry picked from commit c32712d)
ahatanaka pushed a commit to swiftlang/llvm-project that referenced this pull request Jun 25, 2024
A struct that declares an inner struct, but no fields, won't have a
field count. So getting the offset of the inner struct fails. This
happens in both C and C++:

  struct foo {
    struct bar {
      int Quantizermatrix[];
    };
  };

Here 'struct foo' has no fields.

Closes: llvm#88931
(cherry picked from commit c32712d)
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.

clang 18.1.3 crashes when building intel-media-driver 24.2.0
4 participants