Repository: libbpf/libbpf-rs (language: rust)

Issue:
libbpf-cargo: Build fails when a C struct contains an empty union
I am using `libbpf-cargo` version `0.24.6`. I'm trying to use `struct bio` like this in my `.bpf.c`:

```c
SEC("fentry/submit_bio")
int BPF_PROG(submit_bio, struct bio *bio)
{
...
	vcnt = bio->bi_vcnt;
...
}
```

However `cargo build` fails with:

```
  thread 'main' panicked at /home/jrc/.cargo/registry/src/index.crates.io-6f17d22bba15001f/libbpf-cargo-0.24.6/src/gen/btf.rs:769:49:
  index out of bounds: the len is 0 but the index is 0
```

The error refers to [this line](https://github.com/libbpf/libbpf-rs/blob/f73d75c02e4f8fc33dc4809b3b69f4a660c0f90a/libbpf-cargo/src/gen/btf.rs#L769), which is part of the code to "write a Default implementation for a union".

At [vmlinux_601.h#L7735](https://github.com/libbpf/vmlinux.h/blob/172793d6a409d98d1cfb843c80df73733e9f832f/include/x86/vmlinux_601.h#L7735), we find this:

```c
struct bio {
...
	union {};
...
};
```

It looks like empty unions cause problems for `libbpf-cargo`. Arguably this is an issue of `vmlinux.h`, and I am aware that the `vmlinux.h` repo is advertised as "Development-only". This issue could be fixed by removing the empty union in `vmlinux.h`. However, it seems more convenient to me to add support for empty unions to `libbpf-cargo`. Is that reasonable?


Discussion / hints:
Thanks for the report.

> However, it seems more convenient to me to add support for empty unions to libbpf-cargo. Is that reasonable?

Yes, that seems reasonable to me. Will you take a look at fixing this problem?

Fix patch:
diff --git a/libbpf-cargo/CHANGELOG.md b/libbpf-cargo/CHANGELOG.md
index 0c1634a2..acefe2b2 100644
--- a/libbpf-cargo/CHANGELOG.md
+++ b/libbpf-cargo/CHANGELOG.md
@@ -1,3 +1,8 @@
+Unreleased
+----------
+- Fixed handling of empty unions in BPF types
+
+
 0.24.6
 ------
 - Fixed incorrect Cargo environment variable query when used in build
diff --git a/libbpf-cargo/src/gen/btf.rs b/libbpf-cargo/src/gen/btf.rs
index d3724db4..4acdbf3c 100644
--- a/libbpf-cargo/src/gen/btf.rs
+++ b/libbpf-cargo/src/gen/btf.rs
@@ -578,6 +578,11 @@ impl<'s> GenBtf<'s> {
         t: types::Composite<'_>,
         opts: &TypeDeclOpts,
     ) -> Result<()> {
+        if t.is_empty_union() {
+            // Ignore empty union; Rust does not allow unions with no fields.
+            return Ok(());
+        }
+
         let packed = is_struct_packed(&t, &self.btf)?;
 
         // fields in the aggregate
@@ -602,6 +607,14 @@ impl<'s> GenBtf<'s> {
                 .type_by_id::<BtfType<'_>>(member.ty)
                 .unwrap()
                 .skip_mods_and_typedefs();
+
+            if let Ok(composite) = TryInto::<types::Composite<'_>>::try_into(field_ty) {
+                if composite.is_empty_union() {
+                    // Skip empty union field; we do not generate a type for them.
+                    continue;
+                }
+            }
+
             if let Some(next_ty_id) = next_type(field_ty)? {
                 dependent_types.push(next_ty_id);
             }
diff --git a/libbpf-rs/src/btf/types.rs b/libbpf-rs/src/btf/types.rs
index f931c3e3..f784c136 100644
--- a/libbpf-rs/src/btf/types.rs
+++ b/libbpf-rs/src/btf/types.rs
@@ -579,6 +579,13 @@ impl<'btf> TryFrom<Composite<'btf>> for Union<'btf> {
     }
 }
 
+impl Composite<'_> {
+    /// Returns whether this composite type is a `union {}`.
+    pub fn is_empty_union(&self) -> bool {
+        !self.is_struct && self.is_empty()
+    }
+}
+
 // Composite
 gen_collection_members_concrete_type! {
     btf_member as Composite with HasSize;


Test patch:
diff --git a/libbpf-cargo/src/test.rs b/libbpf-cargo/src/test.rs
index 501e65ca..4c8f7009 100644
--- a/libbpf-cargo/src/test.rs
+++ b/libbpf-cargo/src/test.rs
@@ -2733,6 +2733,36 @@ pub struct __anon_4 {
     assert_definition(&btf, &struct_bpf_sock_tuple, expected_output);
 }
 
+#[test]
+fn test_btf_dump_definition_empty_union() {
+    let prog_text = r#"
+#include "vmlinux.h"
+
+struct struct_with_empty_union {
+    int member;
+    union {};
+};
+struct struct_with_empty_union s;
+"#;
+
+    let expected_output = r#"
+#[derive(Debug, Default, Copy, Clone)]
+#[repr(C)]
+pub struct struct_with_empty_union {
+    pub member: i32,
+}
+"#;
+
+    let mmap = build_btf_mmap(prog_text);
+    let btf = btf_from_mmap(&mmap);
+
+    // Find the struct
+    let struct_with_empty_union =
+        find_type_in_btf!(btf, types::Struct<'_>, "struct_with_empty_union");
+
+    assert_definition(&btf, &struct_with_empty_union, expected_output);
+}
+
 #[test]
 fn test_btf_dump_definition_struct_ops_mixed() {
     let prog_text = r#"