Repository: rust-lang/rust-bindgen
Pull request #2772 (2024-02-26 00:17:48)

add option to use DST structs for flex arrays
Add `--flexarray-dst` option to allow use of a dynamically sized struct to model a C flexible array member. For example, given the C struct:
```
struct msg {
    unsigned int tag;
    unsigned int len;
    char payload[];
};
```
generate the corresponding Rust type:
```rust
#[repr(C)]
#[derive(Debug)]
pub struct msg<FAM: ?Sized = [::std::os::raw::c_char; 0]> {
    pub tag: ::std::os::raw::c_uint,
    pub len: ::std::os::raw::c_uint,
    pub payload: FAM,
}
```
This single definition is used for both Sized (with a zero-length array as the FAM type) and DST (with a slice).

This also generates the helper methods to convert a raw pointer into a Rust view of the C type, with a given length.
```rust
// Sized implementations
impl msg<[::std::os::raw::c_char; 0]> {
    // Construct a DST reference from a Sized object.
    // SAFETY: there must be at least `len` initialized elements in the underlying storage
    pub unsafe fn flex_ref(
        &self,
        len: usize,
    ) -> &msg<[::std::os::raw::c_char]> {
        unsafe { Self::flex_ptr(self, len) }
    }

    // Same, but mutable
    pub unsafe fn flex_mut_ref(
        &mut self,
        len: usize,
    ) -> &mut msg<[::std::os::raw::c_char]> {
        unsafe { Self::flex_ptr_mut(self, len).assume_init() }
    }

    // Raw pointer variants
    pub unsafe fn flex_ptr<'unbounded>(
        ptr: *const Self,
        len: usize,
    ) -> &'unbounded msg<[::std::os::raw::c_char]> {
        unsafe { &*::std::ptr::from_raw_parts(ptr as *const (), len) }
    }

    pub unsafe fn flex_ptr_mut<'unbounded>(
        ptr: *mut Self,
        len: usize,
    ) -> ::std::mem::MaybeUninit<&'unbounded mut msg<[::std::os::raw::c_char]>>
    {
        unsafe {
            let mut uninit = ::std::mem::MaybeUninit::<
                &mut msg<[::std::os::raw::c_char]>,
            >::uninit();
            (uninit.as_mut_ptr() as *mut *mut msg<[::std::os::raw::c_char]>)
                .write(::std::ptr::from_raw_parts_mut(ptr as *mut (), len));
            uninit
        }
    }
}

// DST implementations
impl msg<[::std::os::raw::c_char]> {
    // Memory size & alignment for allocation
    pub fn layout(len: usize) -> ::std::alloc::Layout {
        unsafe {
            let p: *const Self =
                ::std::ptr::from_raw_parts(::std::ptr::null(), len);
            ::std::alloc::Layout::for_value_raw(p)
        }
    }
    // return the Sized variant along with the length
    pub fn fixed(&self) -> (&msg<[::std::os::raw::c_char; 0]>, usize) {
        unsafe {
            let (ptr, len) = (self as *const Self).to_raw_parts();
            (&*(ptr as *const msg<[::std::os::raw::c_char; 0]>), len)
        }
    }
    pub fn fixed_mut(
        &mut self,
    ) -> (&mut msg<[::std::os::raw::c_char; 0]>, usize) {
        unsafe {
            let (ptr, len) = (self as *mut Self).to_raw_parts();
            (&mut *(ptr as *mut msg<[::std::os::raw::c_char; 0]>), len)
        }
    }
}
```

Upside:
- The flexible array member is directly expressed in a fairly conventional Rust type, with no need to interact with the bindgen-specific `__IncompleteArrayField` type
  - In particular, normal `size_of` will work, taking into account the dynamically sized extension

Problems/TODO:
- depends on unstable `ptr_metadata` and `layout_for_ptr` features
- `from_ptr(_mut)` return references with unbounded lifetimes - it would be up to the caller to constrain them to the underlying storage's lifetime
  - I experimented with a variant which also takes a "lifetime witness" parameter `_lt: &'a LT` which can be used to bound the returned lifetime, but I'll need to try it in practice to see if it works.

This is a prototype for #2771 

jsgf: Not sure why, but when I run `cargo fmt` locally it wants to change things all over the place.
jsgf: This doesn't handle the case of a C struct pointing to a DST object (ie thin vs fat pointers).
jsgf: I haven't checked out the test failures yet - will do so shortly.

I've been using this API for one of my own projects, and so far it feels pretty comfortable.
jsgf: OK updated with comments (and still on top of #2783)
bors-servo: :umbrella: The latest upstream changes (presumably 3b5ce9c5861cd2e97e9789f5b686238656abd2d6) made this pull request unmergeable. Please resolve the merge conflicts.
pvdrz: yeah we literally have the `experimental` flag on the CLI and a feature as well. I think other features are gated behind it already.
jsgf: Does `--experimental` do anything? I couldn't see anywhere that referenced it.
jsgf: @emilio @pvdrz Ping? Are there any outstanding concerns about this?
jsgf: Fixed the consistency of the `flex_mut_ref` vs `flex_ptr_mut` names (went with trailing `_mut`) and inlined the helpers since they should have effectively no runtime implementation (they're really typesystem operations).
jsgf: @emilio @pvdrz Ping?
bors-servo: :umbrella: The latest upstream changes (presumably fb39a30a052bbc3c4e7afe78c5c1655622094d9d) made this pull request unmergeable. Please resolve the merge conflicts.
emilio: Please rebase and ping, happy to merge. Hopefully it's just a matter of regenerating test expectations. Sorry, this didn't get into the merge queue because it got configured right after I pressed the "Merge when ready" button :/
jsgf: Do you want me to squash the commits?
emilio: > Do you want me to squash the commits?

Nah, all good, thanks!

Patch:
diff --git a/bindgen-cli/options.rs b/bindgen-cli/options.rs
index 815a28f4..5edc8c95 100644
--- a/bindgen-cli/options.rs
+++ b/bindgen-cli/options.rs
@@ -406,6 +406,9 @@ struct BindgenCommand {
     /// Set path for temporary files generated by fallback for clang macro parsing.
     #[arg(long)]
     clang_macro_fallback_build_dir: Option<PathBuf>,
+    /// Use DSTs to represent structures with flexible array members.
+    #[arg(long)]
+    flexarray_dst: bool,
     /// Derive custom traits on any kind of type. The CUSTOM value must be of the shape REGEX=DERIVE where DERIVE is a coma-separated list of derive macros.
     #[arg(long, value_name = "CUSTOM", value_parser = parse_custom_derive)]
     with_derive_custom: Vec<(Vec<String>, String)>,
@@ -562,6 +565,7 @@ where
         wrap_unsafe_ops,
         clang_macro_fallback,
         clang_macro_fallback_build_dir,
+        flexarray_dst,
         with_derive_custom,
         with_derive_custom_struct,
         with_derive_custom_enum,
@@ -1039,6 +1043,10 @@ where
         builder = builder.clang_macro_fallback_build_dir(path);
     }
 
+    if flexarray_dst {
+        builder = builder.flexarray_dst(true);
+    }
+
     #[derive(Debug)]
     struct CustomDeriveCallback {
         derives: Vec<String>,
diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs
index b6f915a8..070d9dec 100644
--- a/bindgen/codegen/mod.rs
+++ b/bindgen/codegen/mod.rs
@@ -1333,6 +1333,7 @@ trait FieldCodegen<'a> {
         accessor_kind: FieldAccessorKind,
         parent: &CompInfo,
         parent_item: &Item,
+        last_field: bool,
         result: &mut CodegenResult,
         struct_layout: &mut StructLayoutTracker,
         fields: &mut F,
@@ -1353,6 +1354,7 @@ impl<'a> FieldCodegen<'a> for Field {
         accessor_kind: FieldAccessorKind,
         parent: &CompInfo,
         parent_item: &Item,
+        last_field: bool,
         result: &mut CodegenResult,
         struct_layout: &mut StructLayoutTracker,
         fields: &mut F,
@@ -1370,6 +1372,7 @@ impl<'a> FieldCodegen<'a> for Field {
                     accessor_kind,
                     parent,
                     parent_item,
+                    last_field,
                     result,
                     struct_layout,
                     fields,
@@ -1384,6 +1387,7 @@ impl<'a> FieldCodegen<'a> for Field {
                     accessor_kind,
                     parent,
                     parent_item,
+                    last_field,
                     result,
                     struct_layout,
                     fields,
@@ -1428,6 +1432,7 @@ impl<'a> FieldCodegen<'a> for FieldData {
         accessor_kind: FieldAccessorKind,
         parent: &CompInfo,
         parent_item: &Item,
+        last_field: bool,
         result: &mut CodegenResult,
         struct_layout: &mut StructLayoutTracker,
         fields: &mut F,
@@ -1453,14 +1458,20 @@ impl<'a> FieldCodegen<'a> for FieldData {
         let ty = if parent.is_union() {
             wrap_union_field_if_needed(ctx, struct_layout, ty, result)
         } else if let Some(item) = field_ty.is_incomplete_array(ctx) {
-            result.saw_incomplete_array();
+            // Only FAM if its the last field
+            if ctx.options().flexarray_dst && last_field {
+                struct_layout.saw_flexible_array();
+                syn::parse_quote! { FAM }
+            } else {
+                result.saw_incomplete_array();
 
-            let inner = item.to_rust_ty_or_opaque(ctx, &());
+                let inner = item.to_rust_ty_or_opaque(ctx, &());
 
-            if ctx.options().enable_cxx_namespaces {
-                syn::parse_quote! { root::__IncompleteArrayField<#inner> }
-            } else {
-                syn::parse_quote! { __IncompleteArrayField<#inner> }
+                if ctx.options().enable_cxx_namespaces {
+                    syn::parse_quote! { root::__IncompleteArrayField<#inner> }
+                } else {
+                    syn::parse_quote! { __IncompleteArrayField<#inner> }
+                }
             }
         } else {
             ty
@@ -1682,6 +1693,7 @@ impl<'a> FieldCodegen<'a> for BitfieldUnit {
         accessor_kind: FieldAccessorKind,
         parent: &CompInfo,
         parent_item: &Item,
+        last_field: bool,
         result: &mut CodegenResult,
         struct_layout: &mut StructLayoutTracker,
         fields: &mut F,
@@ -1742,7 +1754,8 @@ impl<'a> FieldCodegen<'a> for BitfieldUnit {
         let mut generate_ctor = layout.size <= RUST_DERIVE_IN_ARRAY_LIMIT;
 
         let mut unit_visibility = visibility_kind;
-        for bf in self.bitfields() {
+        let bfields = self.bitfields();
+        for (idx, bf) in bfields.iter().enumerate() {
             // Codegen not allowed for anonymous bitfields
             if bf.name().is_none() {
                 continue;
@@ -1762,6 +1775,7 @@ impl<'a> FieldCodegen<'a> for BitfieldUnit {
                 accessor_kind,
                 parent,
                 parent_item,
+                last_field && idx == bfields.len() - 1,
                 result,
                 struct_layout,
                 fields,
@@ -1844,6 +1858,7 @@ impl<'a> FieldCodegen<'a> for Bitfield {
         _accessor_kind: FieldAccessorKind,
         parent: &CompInfo,
         parent_item: &Item,
+        _last_field: bool,
         _result: &mut CodegenResult,
         struct_layout: &mut StructLayoutTracker,
         _fields: &mut F,
@@ -2077,13 +2092,15 @@ impl CodeGenerator for CompInfo {
                 .annotations()
                 .accessor_kind()
                 .unwrap_or(FieldAccessorKind::None);
-            for field in self.fields() {
+            let field_decls = self.fields();
+            for (idx, field) in field_decls.iter().enumerate() {
                 field.codegen(
                     ctx,
                     visibility,
                     struct_accessor_kind,
                     self,
                     item,
+                    idx == field_decls.len() - 1,
                     result,
                     &mut struct_layout,
                     &mut fields,
@@ -2208,20 +2225,60 @@ impl CodeGenerator for CompInfo {
             });
         }
 
-        let generics = if !generic_param_names.is_empty() {
-            let generic_param_names = generic_param_names.clone();
-            quote! {
-                < #( #generic_param_names ),* >
+        let (flex_array_generic, flex_inner_ty) = if ctx.options().flexarray_dst
+        {
+            match self.flex_array_member(ctx) {
+                Some(ty) => {
+                    let inner = ty.to_rust_ty_or_opaque(ctx, &());
+                    (
+                        Some(quote! { FAM: ?Sized = [ #inner; 0 ] }),
+                        Some(quote! { #inner }),
+                    )
+                }
+                None => (None, None),
             }
         } else {
-            quote! {}
+            (None, None)
         };
 
+        // Generics, including the flexible array member.
+        //
+        // generics - generic parameters for the struct declaration
+        // impl_generics_labels - generic parameters for `impl<...>`
+        // impl_generics_params - generic parameters for `impl structname<...>`
+        //
+        // `impl` blocks are for non-FAM related impls like Default, etc
+        let (generics, impl_generics_labels, impl_generics_params) =
+            if !generic_param_names.is_empty() || flex_array_generic.is_some() {
+                let (flex_sized, flex_fam) = match flex_inner_ty.as_ref() {
+                    None => (None, None),
+                    Some(ty) => (
+                        Some(quote! { [ #ty; 0 ] }),
+                        Some(quote! { FAM: ?Sized = [ #ty; 0 ] }),
+                    ),
+                };
+
+                (
+                    quote! {
+                        < #( #generic_param_names , )* #flex_fam >
+                    },
+                    quote! {
+                        < #( #generic_param_names , )* >
+                    },
+                    quote! {
+                        < #( #generic_param_names , )* #flex_sized >
+                    },
+                )
+            } else {
+                (quote! {}, quote! {}, quote! {})
+            };
+
         let mut attributes = vec![];
         let mut needs_clone_impl = false;
         let mut needs_default_impl = false;
         let mut needs_debug_impl = false;
         let mut needs_partialeq_impl = false;
+        let needs_flexarray_impl = flex_array_generic.is_some();
         if let Some(comment) = item.comment(ctx) {
             attributes.push(attributes::doc(comment));
         }
@@ -2525,17 +2582,27 @@ impl CodeGenerator for CompInfo {
         // NB: We can't use to_rust_ty here since for opaque types this tries to
         // use the specialization knowledge to generate a blob field.
         let ty_for_impl = quote! {
-            #canonical_ident #generics
+            #canonical_ident #impl_generics_params
         };
 
         if needs_clone_impl {
             result.push(quote! {
-                impl #generics Clone for #ty_for_impl {
+                impl #impl_generics_labels Clone for #ty_for_impl {
                     fn clone(&self) -> Self { *self }
                 }
             });
         }
 
+        if needs_flexarray_impl {
+            result.push(self.generate_flexarray(
+                ctx,
+                &canonical_ident,
+                flex_inner_ty,
+                &*generic_param_names,
+                &impl_generics_labels,
+            ));
+        }
+
         if needs_default_impl {
             let prefix = ctx.trait_prefix();
             let body = if ctx.options().rust_features().maybe_uninit {
@@ -2560,7 +2627,7 @@ impl CodeGenerator for CompInfo {
             // non-zero padding bytes, especially when forwards/backwards compatibility is
             // involved.
             result.push(quote! {
-                impl #generics Default for #ty_for_impl {
+                impl #impl_generics_labels Default for #ty_for_impl {
                     fn default() -> Self {
                         #body
                     }
@@ -2579,7 +2646,7 @@ impl CodeGenerator for CompInfo {
             let prefix = ctx.trait_prefix();
 
             result.push(quote! {
-                impl #generics ::#prefix::fmt::Debug for #ty_for_impl {
+                impl #impl_generics_labels ::#prefix::fmt::Debug for #ty_for_impl {
                     #impl_
                 }
             });
@@ -2603,7 +2670,7 @@ impl CodeGenerator for CompInfo {
 
                 let prefix = ctx.trait_prefix();
                 result.push(quote! {
-                    impl #generics ::#prefix::cmp::PartialEq for #ty_for_impl #partialeq_bounds {
+                    impl #impl_generics_labels ::#prefix::cmp::PartialEq for #ty_for_impl #partialeq_bounds {
                         #impl_
                     }
                 });
@@ -2612,7 +2679,7 @@ impl CodeGenerator for CompInfo {
 
         if !methods.is_empty() {
             result.push(quote! {
-                impl #generics #ty_for_impl {
+                impl #impl_generics_labels #ty_for_impl {
                     #( #methods )*
                 }
             });
@@ -2620,6 +2687,142 @@ impl CodeGenerator for CompInfo {
     }
 }
 
+impl CompInfo {
+    fn generate_flexarray(
+        &self,
+        ctx: &BindgenContext,
+        canonical_ident: &Ident,
+        flex_inner_ty: Option<proc_macro2::TokenStream>,
+        generic_param_names: &[Ident],
+        impl_generics_labels: &proc_macro2::TokenStream,
+    ) -> proc_macro2::TokenStream {
+        let prefix = ctx.trait_prefix();
+
+        let flex_array = flex_inner_ty.as_ref().map(|ty| quote! { [ #ty ] });
+
+        let dst_ty_for_impl = quote! {
+            #canonical_ident < #( #generic_param_names , )* #flex_array >
+
+        };
+        let sized_ty_for_impl = quote! {
+            #canonical_ident < #( #generic_param_names , )* [ #flex_inner_ty; 0 ] >
+        };
+
+        let layout = if ctx.options().rust_features().layout_for_ptr {
+            quote! {
+                pub fn layout(len: usize) -> ::#prefix::alloc::Layout {
+                    // SAFETY: Null pointers are OK if we don't deref them
+                    unsafe {
+                        let p: *const Self = ::#prefix::ptr::from_raw_parts(::#prefix::ptr::null(), len);
+                        ::#prefix::alloc::Layout::for_value_raw(p)
+                    }
+                }
+            }
+        } else {
+            quote!()
+        };
+
+        let (from_ptr_dst, from_ptr_sized) = if ctx
+            .options()
+            .rust_features()
+            .ptr_metadata
+        {
+            let flex_ref_inner = ctx.wrap_unsafe_ops(quote! {
+                Self::flex_ptr(self, len)
+            });
+            let flex_ref_mut_inner = ctx.wrap_unsafe_ops(quote! {
+                Self::flex_ptr_mut(self, len).assume_init()
+            });
+            let flex_ptr_inner = ctx.wrap_unsafe_ops(quote! {
+                &*::#prefix::ptr::from_raw_parts(ptr as *const (), len)
+            });
+            let flex_ptr_mut_inner = ctx.wrap_unsafe_ops(quote! {
+                // Initialize reference without ever exposing it, as its possibly uninitialized
+                let mut uninit = ::#prefix::mem::MaybeUninit::<&mut #dst_ty_for_impl>::uninit();
+                (uninit.as_mut_ptr() as *mut *mut #dst_ty_for_impl)
+                    .write(::#prefix::ptr::from_raw_parts_mut(ptr as *mut (), len));
+
+                uninit
+            });
+
+            (
+                quote! {
+                    #[inline]
+                    pub fn fixed(&self) -> (& #sized_ty_for_impl, usize) {
+                        unsafe {
+                            let (ptr, len) = (self as *const Self).to_raw_parts();
+                            (&*(ptr as *const #sized_ty_for_impl), len)
+                        }
+                    }
+
+                    #[inline]
+                    pub fn fixed_mut(&mut self) -> (&mut #sized_ty_for_impl, usize) {
+                        unsafe {
+                            let (ptr, len) = (self as *mut Self).to_raw_parts();
+                            (&mut *(ptr as *mut #sized_ty_for_impl), len)
+                        }
+                    }
+                },
+                quote! {
+                    /// Convert a sized prefix to an unsized structure with the given length.
+                    ///
+                    /// SAFETY: Underlying storage is initialized up to at least `len` elements.
+                    pub unsafe fn flex_ref(&self, len: usize) -> &#dst_ty_for_impl {
+                        // SAFETY: Reference is always valid as pointer. Caller is guaranteeing `len`.
+                        #flex_ref_inner
+                    }
+
+                    /// Convert a mutable sized prefix to an unsized structure with the given length.
+                    ///
+                    /// SAFETY: Underlying storage is initialized up to at least `len` elements.
+                    #[inline]
+                    pub unsafe fn flex_ref_mut(&mut self, len: usize) -> &mut #dst_ty_for_impl {
+                        // SAFETY: Reference is always valid as pointer. Caller is guaranteeing `len`.
+                        #flex_ref_mut_inner
+                    }
+
+                    /// Construct DST variant from a pointer and a size.
+                    ///
+                    /// NOTE: lifetime of returned reference is not tied to any underlying storage.
+                    /// SAFETY: `ptr` is valid. Underlying storage is fully initialized up to at least `len` elements.
+                    #[inline]
+                    pub unsafe fn flex_ptr<'unbounded>(ptr: *const Self, len: usize) -> &'unbounded #dst_ty_for_impl {
+                       #flex_ptr_inner
+                    }
+
+                    /// Construct mutable DST variant from a pointer and a
+                    /// size. The returned `&mut` reference is initialized
+                    /// pointing to memory referenced by `ptr`, but there's
+                    /// no requirement that that memory be initialized.
+                    ///
+                    /// NOTE: lifetime of returned reference is not tied to any underlying storage.
+                    /// SAFETY: `ptr` is valid. Underlying storage has space for at least `len` elements.
+                    #[inline]
+                    pub unsafe fn flex_ptr_mut<'unbounded>(
+                        ptr: *mut Self,
+                        len: usize,
+                    ) -> ::#prefix::mem::MaybeUninit<&'unbounded mut #dst_ty_for_impl> {
+                        #flex_ptr_mut_inner
+                    }
+                },
+            )
+        } else {
+            (quote!(), quote!())
+        };
+
+        quote! {
+            impl #impl_generics_labels #dst_ty_for_impl {
+                #layout
+                #from_ptr_dst
+            }
+
+            impl #impl_generics_labels #sized_ty_for_impl {
+                #from_ptr_sized
+            }
+        }
+    }
+}
+
 impl Method {
     fn codegen_method(
         &self,
diff --git a/bindgen/codegen/struct_layout.rs b/bindgen/codegen/struct_layout.rs
index 73496698..507c8d40 100644
--- a/bindgen/codegen/struct_layout.rs
+++ b/bindgen/codegen/struct_layout.rs
@@ -28,6 +28,7 @@ pub(crate) struct StructLayoutTracker<'a> {
     max_field_align: usize,
     last_field_was_bitfield: bool,
     visibility: FieldVisibilityKind,
+    last_field_was_flexible_array: bool,
 }
 
 /// Returns a size aligned to a given value.
@@ -110,6 +111,7 @@ impl<'a> StructLayoutTracker<'a> {
             latest_field_layout: None,
             max_field_align: 0,
             last_field_was_bitfield: false,
+            last_field_was_flexible_array: false,
         }
     }
 
@@ -121,6 +123,10 @@ impl<'a> StructLayoutTracker<'a> {
         self.is_rust_union
     }
 
+    pub(crate) fn saw_flexible_array(&mut self) {
+        self.last_field_was_flexible_array = true;
+    }
+
     pub(crate) fn saw_vtable(&mut self) {
         debug!("saw vtable for {}", self.name);
 
@@ -295,6 +301,11 @@ impl<'a> StructLayoutTracker<'a> {
             return None;
         }
 
+        // Also doesn't make sense for structs with flexible array members
+        if self.last_field_was_flexible_array {
+            return None;
+        }
+
         if self.latest_offset == comp_layout.size {
             // This struct does not contain tail padding.
             return None;
diff --git a/bindgen/features.rs b/bindgen/features.rs
index 6ac1d6a1..c07318c5 100644
--- a/bindgen/features.rs
+++ b/bindgen/features.rs
@@ -96,6 +96,8 @@ macro_rules! define_rust_targets {
 define_rust_targets! {
     Nightly => {
         vectorcall_abi,
+        ptr_metadata: #81513,
+        layout_for_ptr: #69835,
     },
     Stable_1_77(77) => { offset_of: #106655 },
     Stable_1_73(73) => { thiscall_abi: #42202 },
diff --git a/bindgen/ir/comp.rs b/bindgen/ir/comp.rs
index bd4d0162..f6c9e629 100644
--- a/bindgen/ir/comp.rs
+++ b/bindgen/ir/comp.rs
@@ -825,6 +825,23 @@ impl CompFields {
             }
         }
     }
+
+    /// Return the flex array member for the struct/class, if any.
+    fn flex_array_member(&self, ctx: &BindgenContext) -> Option<TypeId> {
+        let fields = match self {
+            CompFields::Before(_) => panic!("raw fields"),
+            CompFields::After { fields, .. } => fields,
+            CompFields::Error => return None, // panic?
+        };
+
+        match fields.last()? {
+            Field::Bitfields(..) => None,
+            Field::DataMember(FieldData { ty, .. }) => ctx
+                .resolve_type(*ty)
+                .is_incomplete_array(ctx)
+                .map(|item| item.expect_type_id(ctx)),
+        }
+    }
 }
 
 impl Trace for CompFields {
@@ -1122,6 +1139,14 @@ impl CompInfo {
         }
     }
 
+    /// Return the flex array member and its element type if any
+    pub(crate) fn flex_array_member(
+        &self,
+        ctx: &BindgenContext,
+    ) -> Option<TypeId> {
+        self.fields.flex_array_member(ctx)
+    }
+
     fn has_fields(&self) -> bool {
         match self.fields {
             CompFields::Error => false,
diff --git a/bindgen/options/mod.rs b/bindgen/options/mod.rs
index f374d207..d1486397 100644
--- a/bindgen/options/mod.rs
+++ b/bindgen/options/mod.rs
@@ -1981,6 +1981,19 @@ options! {
         },
         as_args: "--wrap-unsafe-ops",
     },
+    /// Use DSTs to represent structures with flexible array members.
+    flexarray_dst: bool {
+        methods: {
+            /// Use DSTs to represent structures with flexible array members.
+            ///
+            /// This option is disabled by default.
+            pub fn flexarray_dst(mut self, doit: bool) -> Self {
+                self.options.flexarray_dst = doit;
+                self
+            }
+        },
+        as_args: "--flexarray-dst",
+    },
     /// Patterns for functions whose ABI should be overridden.
     abi_overrides: HashMap<Abi, RegexSet> {
         methods: {
diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md
index dbdf3f70..daaed040 100644
--- a/book/src/SUMMARY.md
+++ b/book/src/SUMMARY.md
@@ -27,4 +27,5 @@
 - [Generating Bindings to Objective-c](./objc.md)
 - [Using Unions](./using-unions.md)
 - [Using Bitfields](./using-bitfields.md)
+- [Using Flexible Array Members](./using-fam.md)
 - [FAQ](./faq.md)
diff --git a/book/src/using-fam.md b/book/src/using-fam.md
new file mode 100644
index 00000000..aaf19c90
--- /dev/null
+++ b/book/src/using-fam.md
@@ -0,0 +1,163 @@
+# Using C structures with Flexible Array Members
+
+Since time immemorial, C programmers have been using what was called "the struct
+hack". This is a technique for packing a fixed-size structure and a
+variable-sized tail within the same memory allocation. Typically this looks
+like:
+
+```c
+struct MyRecord {
+    time_t timestamp;
+    unsigned seq;
+    size_t len;
+    char payload[0];
+};
+```
+
+Because this is so useful, it was standardized in C99 as "flexible array
+members", using almost identical syntax:
+```c
+struct MyRecord {
+    time_t timestamp;
+    unsigned seq;
+    size_t len;
+    char payload[]; // NOTE: empty []
+};
+```
+
+Bindgen supports these structures in two different ways.
+
+## `__IncompleteArrayField`
+
+By default, bindgen will generate the corresponding Rust structure:
+```rust,ignore
+#[repr(C)]
+struct MyRecord {
+    pub timestamp: time_t,
+    pub seq: ::std::os::raw::c_uint,
+    pub len: usize,
+    pub payload: __IncompleteArrayField<::std::os::raw::c_char>,
+}
+```
+
+The `__IncompleteArrayField` type is zero-sized, so this structure represents
+the prefix without any trailing data. In order to access that data, it provides
+the `as_slice` unsafe method:
+```rust,ignore
+    // SAFETY: there's at least `len` bytes allocated and initialized after `myrecord`
+    let payload = unsafe { myrecord.payload.as_slice(myrecord.len) };
+```
+There's also `as_mut_slice` which does the obvious.
+
+These are `unsafe` simply because it's up to you to provide the right length (in
+elements of whatever type `payload` is) as there's no way for Rust or Bindgen to
+know. In this example, the length is a very straightforward `len` field in the
+structure, but it could be encoded in any number of ways within the structure,
+or come from somewhere else entirely.
+
+One big caveat with this technique is that `std::mem::size_of` (or
+`size_of_val`) will *only* include the size of the prefix structure. if you're
+working out how much storage the whole structure is using, you'll need to add
+the suffix yourself.
+
+## Using Dynamically Sized Types
+
+If you invoke bindgen with the `--flexarray-dst` option, it will generate
+something not quite like this:
+
+```rust,ignore
+#[repr(C)]
+struct MyRecord {
+    pub timestamp: time_t,
+    pub seq: ::std::os::raw::c_uint,
+    pub len: usize,
+    pub payload: [::std::os::raw::c_char],
+}
+```
+Rust has a set of types which are almost exact analogs for these Flexible Array
+Member types: the Dynamically Sized Type ("DST").
+
+This looks almost identical to a normal Rust structure, except that you'll note
+the type of the `payload` field is a raw slice `[...]` rather than the usual
+reference to slice `&[...]`.
+
+That `payload: [c_char]` is telling Rust that it can't directly know the total
+size of this structure - the `payload` field takes an amount of space that's
+determined at runtime. This means you can't directly use values of this type,
+only references: `&MyRecord`.
+
+In practice, this is very awkward. So instead, bindgen generates:
+```rust,ignore
+#[repr(C)]
+struct MyRecord<FAM: ?Sized = [::std::os::raw::c_char; 0]> {
+    pub timestamp: time_t,
+    pub seq: ::std::os::raw::c_uint,
+    pub len: usize,
+    pub payload: FAM,
+}
+```
+
+That is:
+1. a type parameter `FAM` which represents the type of the `payload` field,
+2. it's `?Sized` meaning it can be unsized (ie, a DST)
+3. it has the default type of `[c_char; 0]` - that is a zero-sized array of characters
+
+This means that referencing plain `MyRecord` will be exactly like `MyRecord`
+with `__IncompleteArrayField`: it is a fixed-sized structure which you can
+manipulate like a normal Rust value.
+
+But how do you get to the DST part?
+
+Bindgen will also implement a set of helper methods for this:
+
+```rust,ignore
+// Static sized variant
+impl MyRecord<[::std::os::raw::c_char; 0]> {
+    pub unsafe fn flex_ref(&self, len: usize) -> &MyRecord<[::std::os::raw::c_char]> { ... }
+    pub unsafe fn flex_mut_ref(&mut self, len: usize) -> &mut MyRecord<[::std::os::raw::c_char]> { ... }
+    // And some raw pointer variants
+}
+```
+These will take a sized `MyRecord<[c_char; 0]>` and a length in elements, and
+return a reference to a DST `MyRecord<[c_char]>` where the `payload` field is a
+fully usable slice of `len` characters.
+
+The magic here is that the reference is a fat pointer, which not only encodes
+the address, but also the dynamic size of the final field, just like a reference
+to a slice is. This means that you get full bounds checked access to the
+`payload` field like any other Rust slice.
+
+It also means that doing `mem::size_of_val(myrecord)` will return the *complete*
+size of this structure, including the suffix.
+
+You can go the other way:
+```rust,ignore
+// Dynamic sized variant
+impl MyRecord<[::std::os::raw::c_char]> {
+    pub fn fixed(&self) -> (&MyRecord<[::std::os::raw::c_char; 0]>, usize) { ... }
+    pub fn fixed_mut(&mut self) -> (&mut MyRecord<[::std::os::raw::c_char; 0]>, usize) { ... }
+    pub fn layout(len: usize) -> std::alloc::Layout { ... }
+}
+```
+which takes the DST variant of the structure and returns the sized variant,
+along with the number of elements are after it. These are all completely safe
+because all the information needed is part of the fat `&self` reference.
+
+The `layout` function takes a length and returns the `Layout` - that is, size
+and alignment, so that you can allocate memory for the structure (for example,
+using `malloc` so you can pass it to a C function).
+
+Unfortunately the language features needed to support these methods are still unstable:
+- [ptr_metadata](https://doc.rust-lang.org/beta/unstable-book/library-features/ptr-metadata.html),
+  which enables all the fixed<->DST conversions, and
+- [layout_for_ptr](https://doc.rust-lang.org/beta/unstable-book/library-features/layout-for-ptr.html),
+  which allows he `layout` method
+
+As a result, if you don't specify `--rust-target nightly` you'll just get the
+bare type definitions, but no real way to use them. It's often convenient to add
+the
+```bash
+--raw-line '#![feature(ptr_metadata,layout_for_ptr)]'
+```
+option if you're generating Rust as a stand-alone crate. Otherwise you'll need
+to add the feature line to your containing crate.


Test patch:
diff --git a/bindgen-tests/tests/expectations/tests/flexarray.rs b/bindgen-tests/tests/expectations/tests/flexarray.rs
new file mode 100644
index 00000000..2c47f0ec
--- /dev/null
+++ b/bindgen-tests/tests/expectations/tests/flexarray.rs
@@ -0,0 +1,535 @@
+#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)]
+#![cfg(feature = "nightly")]
+#![feature(ptr_metadata, layout_for_ptr)]
+#[repr(C)]
+#[derive(Default)]
+pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>, [T; 0]);
+impl<T> __IncompleteArrayField<T> {
+    #[inline]
+    pub const fn new() -> Self {
+        __IncompleteArrayField(::std::marker::PhantomData, [])
+    }
+    #[inline]
+    pub fn as_ptr(&self) -> *const T {
+        self as *const _ as *const T
+    }
+    #[inline]
+    pub fn as_mut_ptr(&mut self) -> *mut T {
+        self as *mut _ as *mut T
+    }
+    #[inline]
+    pub unsafe fn as_slice(&self, len: usize) -> &[T] {
+        ::std::slice::from_raw_parts(self.as_ptr(), len)
+    }
+    #[inline]
+    pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] {
+        ::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len)
+    }
+}
+impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
+    fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        fmt.write_str("__IncompleteArrayField")
+    }
+}
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct flexarray<FAM: ?Sized = [::std::os::raw::c_int; 0]> {
+    pub count: ::std::os::raw::c_int,
+    pub data: FAM,
+}
+const _: () = {
+    ["Size of flexarray"][::std::mem::size_of::<flexarray>() - 4usize];
+    ["Alignment of flexarray"][::std::mem::align_of::<flexarray>() - 4usize];
+    [
+        "Offset of field: flexarray::count",
+    ][::std::mem::offset_of!(flexarray, count) - 0usize];
+    [
+        "Offset of field: flexarray::data",
+    ][::std::mem::offset_of!(flexarray, data) - 4usize];
+};
+impl flexarray<[::std::os::raw::c_int]> {
+    pub fn layout(len: usize) -> ::std::alloc::Layout {
+        unsafe {
+            let p: *const Self = ::std::ptr::from_raw_parts(::std::ptr::null(), len);
+            ::std::alloc::Layout::for_value_raw(p)
+        }
+    }
+    #[inline]
+    pub fn fixed(&self) -> (&flexarray<[::std::os::raw::c_int; 0]>, usize) {
+        unsafe {
+            let (ptr, len) = (self as *const Self).to_raw_parts();
+            (&*(ptr as *const flexarray<[::std::os::raw::c_int; 0]>), len)
+        }
+    }
+    #[inline]
+    pub fn fixed_mut(&mut self) -> (&mut flexarray<[::std::os::raw::c_int; 0]>, usize) {
+        unsafe {
+            let (ptr, len) = (self as *mut Self).to_raw_parts();
+            (&mut *(ptr as *mut flexarray<[::std::os::raw::c_int; 0]>), len)
+        }
+    }
+}
+impl flexarray<[::std::os::raw::c_int; 0]> {
+    /// Convert a sized prefix to an unsized structure with the given length.
+    ///
+    /// SAFETY: Underlying storage is initialized up to at least `len` elements.
+    pub unsafe fn flex_ref(&self, len: usize) -> &flexarray<[::std::os::raw::c_int]> {
+        Self::flex_ptr(self, len)
+    }
+    /// Convert a mutable sized prefix to an unsized structure with the given length.
+    ///
+    /// SAFETY: Underlying storage is initialized up to at least `len` elements.
+    #[inline]
+    pub unsafe fn flex_ref_mut(
+        &mut self,
+        len: usize,
+    ) -> &mut flexarray<[::std::os::raw::c_int]> {
+        Self::flex_ptr_mut(self, len).assume_init()
+    }
+    /// Construct DST variant from a pointer and a size.
+    ///
+    /// NOTE: lifetime of returned reference is not tied to any underlying storage.
+    /// SAFETY: `ptr` is valid. Underlying storage is fully initialized up to at least `len` elements.
+    #[inline]
+    pub unsafe fn flex_ptr<'unbounded>(
+        ptr: *const Self,
+        len: usize,
+    ) -> &'unbounded flexarray<[::std::os::raw::c_int]> {
+        &*::std::ptr::from_raw_parts(ptr as *const (), len)
+    }
+    /// Construct mutable DST variant from a pointer and a
+    /// size. The returned `&mut` reference is initialized
+    /// pointing to memory referenced by `ptr`, but there's
+    /// no requirement that that memory be initialized.
+    ///
+    /// NOTE: lifetime of returned reference is not tied to any underlying storage.
+    /// SAFETY: `ptr` is valid. Underlying storage has space for at least `len` elements.
+    #[inline]
+    pub unsafe fn flex_ptr_mut<'unbounded>(
+        ptr: *mut Self,
+        len: usize,
+    ) -> ::std::mem::MaybeUninit<&'unbounded mut flexarray<[::std::os::raw::c_int]>> {
+        let mut uninit = ::std::mem::MaybeUninit::<
+            &mut flexarray<[::std::os::raw::c_int]>,
+        >::uninit();
+        (uninit.as_mut_ptr() as *mut *mut flexarray<[::std::os::raw::c_int]>)
+            .write(::std::ptr::from_raw_parts_mut(ptr as *mut (), len));
+        uninit
+    }
+}
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct flexarray_zero<FAM: ?Sized = [::std::os::raw::c_int; 0]> {
+    pub count: ::std::os::raw::c_int,
+    pub data: FAM,
+}
+const _: () = {
+    ["Size of flexarray_zero"][::std::mem::size_of::<flexarray_zero>() - 4usize];
+    ["Alignment of flexarray_zero"][::std::mem::align_of::<flexarray_zero>() - 4usize];
+    [
+        "Offset of field: flexarray_zero::count",
+    ][::std::mem::offset_of!(flexarray_zero, count) - 0usize];
+    [
+        "Offset of field: flexarray_zero::data",
+    ][::std::mem::offset_of!(flexarray_zero, data) - 4usize];
+};
+impl flexarray_zero<[::std::os::raw::c_int]> {
+    pub fn layout(len: usize) -> ::std::alloc::Layout {
+        unsafe {
+            let p: *const Self = ::std::ptr::from_raw_parts(::std::ptr::null(), len);
+            ::std::alloc::Layout::for_value_raw(p)
+        }
+    }
+    #[inline]
+    pub fn fixed(&self) -> (&flexarray_zero<[::std::os::raw::c_int; 0]>, usize) {
+        unsafe {
+            let (ptr, len) = (self as *const Self).to_raw_parts();
+            (&*(ptr as *const flexarray_zero<[::std::os::raw::c_int; 0]>), len)
+        }
+    }
+    #[inline]
+    pub fn fixed_mut(
+        &mut self,
+    ) -> (&mut flexarray_zero<[::std::os::raw::c_int; 0]>, usize) {
+        unsafe {
+            let (ptr, len) = (self as *mut Self).to_raw_parts();
+            (&mut *(ptr as *mut flexarray_zero<[::std::os::raw::c_int; 0]>), len)
+        }
+    }
+}
+impl flexarray_zero<[::std::os::raw::c_int; 0]> {
+    /// Convert a sized prefix to an unsized structure with the given length.
+    ///
+    /// SAFETY: Underlying storage is initialized up to at least `len` elements.
+    pub unsafe fn flex_ref(
+        &self,
+        len: usize,
+    ) -> &flexarray_zero<[::std::os::raw::c_int]> {
+        Self::flex_ptr(self, len)
+    }
+    /// Convert a mutable sized prefix to an unsized structure with the given length.
+    ///
+    /// SAFETY: Underlying storage is initialized up to at least `len` elements.
+    #[inline]
+    pub unsafe fn flex_ref_mut(
+        &mut self,
+        len: usize,
+    ) -> &mut flexarray_zero<[::std::os::raw::c_int]> {
+        Self::flex_ptr_mut(self, len).assume_init()
+    }
+    /// Construct DST variant from a pointer and a size.
+    ///
+    /// NOTE: lifetime of returned reference is not tied to any underlying storage.
+    /// SAFETY: `ptr` is valid. Underlying storage is fully initialized up to at least `len` elements.
+    #[inline]
+    pub unsafe fn flex_ptr<'unbounded>(
+        ptr: *const Self,
+        len: usize,
+    ) -> &'unbounded flexarray_zero<[::std::os::raw::c_int]> {
+        &*::std::ptr::from_raw_parts(ptr as *const (), len)
+    }
+    /// Construct mutable DST variant from a pointer and a
+    /// size. The returned `&mut` reference is initialized
+    /// pointing to memory referenced by `ptr`, but there's
+    /// no requirement that that memory be initialized.
+    ///
+    /// NOTE: lifetime of returned reference is not tied to any underlying storage.
+    /// SAFETY: `ptr` is valid. Underlying storage has space for at least `len` elements.
+    #[inline]
+    pub unsafe fn flex_ptr_mut<'unbounded>(
+        ptr: *mut Self,
+        len: usize,
+    ) -> ::std::mem::MaybeUninit<
+        &'unbounded mut flexarray_zero<[::std::os::raw::c_int]>,
+    > {
+        let mut uninit = ::std::mem::MaybeUninit::<
+            &mut flexarray_zero<[::std::os::raw::c_int]>,
+        >::uninit();
+        (uninit.as_mut_ptr() as *mut *mut flexarray_zero<[::std::os::raw::c_int]>)
+            .write(::std::ptr::from_raw_parts_mut(ptr as *mut (), len));
+        uninit
+    }
+}
+#[repr(C)]
+#[derive(Debug)]
+pub struct flexarray_template<T, FAM: ?Sized = [T; 0]> {
+    pub _phantom_0: ::std::marker::PhantomData<::std::cell::UnsafeCell<T>>,
+    pub count: ::std::os::raw::c_int,
+    pub data: FAM,
+}
+impl<T> flexarray_template<T, [T]> {
+    pub fn layout(len: usize) -> ::std::alloc::Layout {
+        unsafe {
+            let p: *const Self = ::std::ptr::from_raw_parts(::std::ptr::null(), len);
+            ::std::alloc::Layout::for_value_raw(p)
+        }
+    }
+    #[inline]
+    pub fn fixed(&self) -> (&flexarray_template<T, [T; 0]>, usize) {
+        unsafe {
+            let (ptr, len) = (self as *const Self).to_raw_parts();
+            (&*(ptr as *const flexarray_template<T, [T; 0]>), len)
+        }
+    }
+    #[inline]
+    pub fn fixed_mut(&mut self) -> (&mut flexarray_template<T, [T; 0]>, usize) {
+        unsafe {
+            let (ptr, len) = (self as *mut Self).to_raw_parts();
+            (&mut *(ptr as *mut flexarray_template<T, [T; 0]>), len)
+        }
+    }
+}
+impl<T> flexarray_template<T, [T; 0]> {
+    /// Convert a sized prefix to an unsized structure with the given length.
+    ///
+    /// SAFETY: Underlying storage is initialized up to at least `len` elements.
+    pub unsafe fn flex_ref(&self, len: usize) -> &flexarray_template<T, [T]> {
+        Self::flex_ptr(self, len)
+    }
+    /// Convert a mutable sized prefix to an unsized structure with the given length.
+    ///
+    /// SAFETY: Underlying storage is initialized up to at least `len` elements.
+    #[inline]
+    pub unsafe fn flex_ref_mut(
+        &mut self,
+        len: usize,
+    ) -> &mut flexarray_template<T, [T]> {
+        Self::flex_ptr_mut(self, len).assume_init()
+    }
+    /// Construct DST variant from a pointer and a size.
+    ///
+    /// NOTE: lifetime of returned reference is not tied to any underlying storage.
+    /// SAFETY: `ptr` is valid. Underlying storage is fully initialized up to at least `len` elements.
+    #[inline]
+    pub unsafe fn flex_ptr<'unbounded>(
+        ptr: *const Self,
+        len: usize,
+    ) -> &'unbounded flexarray_template<T, [T]> {
+        &*::std::ptr::from_raw_parts(ptr as *const (), len)
+    }
+    /// Construct mutable DST variant from a pointer and a
+    /// size. The returned `&mut` reference is initialized
+    /// pointing to memory referenced by `ptr`, but there's
+    /// no requirement that that memory be initialized.
+    ///
+    /// NOTE: lifetime of returned reference is not tied to any underlying storage.
+    /// SAFETY: `ptr` is valid. Underlying storage has space for at least `len` elements.
+    #[inline]
+    pub unsafe fn flex_ptr_mut<'unbounded>(
+        ptr: *mut Self,
+        len: usize,
+    ) -> ::std::mem::MaybeUninit<&'unbounded mut flexarray_template<T, [T]>> {
+        let mut uninit = ::std::mem::MaybeUninit::<
+            &mut flexarray_template<T, [T]>,
+        >::uninit();
+        (uninit.as_mut_ptr() as *mut *mut flexarray_template<T, [T]>)
+            .write(::std::ptr::from_raw_parts_mut(ptr as *mut (), len));
+        uninit
+    }
+}
+impl<T> Default for flexarray_template<T, [T; 0]> {
+    fn default() -> Self {
+        let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+        unsafe {
+            ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+            s.assume_init()
+        }
+    }
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct flexarray_ref {
+    pub things: *mut flexarray,
+}
+const _: () = {
+    ["Size of flexarray_ref"][::std::mem::size_of::<flexarray_ref>() - 8usize];
+    ["Alignment of flexarray_ref"][::std::mem::align_of::<flexarray_ref>() - 8usize];
+    [
+        "Offset of field: flexarray_ref::things",
+    ][::std::mem::offset_of!(flexarray_ref, things) - 0usize];
+};
+impl Default for flexarray_ref {
+    fn default() -> Self {
+        let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+        unsafe {
+            ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+            s.assume_init()
+        }
+    }
+}
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct flexarray_bogus_zero_fam<FAM: ?Sized = [::std::os::raw::c_char; 0]> {
+    pub count: ::std::os::raw::c_int,
+    pub data1: __IncompleteArrayField<::std::os::raw::c_int>,
+    pub data2: FAM,
+}
+const _: () = {
+    [
+        "Size of flexarray_bogus_zero_fam",
+    ][::std::mem::size_of::<flexarray_bogus_zero_fam>() - 4usize];
+    [
+        "Alignment of flexarray_bogus_zero_fam",
+    ][::std::mem::align_of::<flexarray_bogus_zero_fam>() - 4usize];
+    [
+        "Offset of field: flexarray_bogus_zero_fam::count",
+    ][::std::mem::offset_of!(flexarray_bogus_zero_fam, count) - 0usize];
+    [
+        "Offset of field: flexarray_bogus_zero_fam::data1",
+    ][::std::mem::offset_of!(flexarray_bogus_zero_fam, data1) - 4usize];
+    [
+        "Offset of field: flexarray_bogus_zero_fam::data2",
+    ][::std::mem::offset_of!(flexarray_bogus_zero_fam, data2) - 4usize];
+};
+impl flexarray_bogus_zero_fam<[::std::os::raw::c_char]> {
+    pub fn layout(len: usize) -> ::std::alloc::Layout {
+        unsafe {
+            let p: *const Self = ::std::ptr::from_raw_parts(::std::ptr::null(), len);
+            ::std::alloc::Layout::for_value_raw(p)
+        }
+    }
+    #[inline]
+    pub fn fixed(
+        &self,
+    ) -> (&flexarray_bogus_zero_fam<[::std::os::raw::c_char; 0]>, usize) {
+        unsafe {
+            let (ptr, len) = (self as *const Self).to_raw_parts();
+            (
+                &*(ptr as *const flexarray_bogus_zero_fam<[::std::os::raw::c_char; 0]>),
+                len,
+            )
+        }
+    }
+    #[inline]
+    pub fn fixed_mut(
+        &mut self,
+    ) -> (&mut flexarray_bogus_zero_fam<[::std::os::raw::c_char; 0]>, usize) {
+        unsafe {
+            let (ptr, len) = (self as *mut Self).to_raw_parts();
+            (
+                &mut *(ptr
+                    as *mut flexarray_bogus_zero_fam<[::std::os::raw::c_char; 0]>),
+                len,
+            )
+        }
+    }
+}
+impl flexarray_bogus_zero_fam<[::std::os::raw::c_char; 0]> {
+    /// Convert a sized prefix to an unsized structure with the given length.
+    ///
+    /// SAFETY: Underlying storage is initialized up to at least `len` elements.
+    pub unsafe fn flex_ref(
+        &self,
+        len: usize,
+    ) -> &flexarray_bogus_zero_fam<[::std::os::raw::c_char]> {
+        Self::flex_ptr(self, len)
+    }
+    /// Convert a mutable sized prefix to an unsized structure with the given length.
+    ///
+    /// SAFETY: Underlying storage is initialized up to at least `len` elements.
+    #[inline]
+    pub unsafe fn flex_ref_mut(
+        &mut self,
+        len: usize,
+    ) -> &mut flexarray_bogus_zero_fam<[::std::os::raw::c_char]> {
+        Self::flex_ptr_mut(self, len).assume_init()
+    }
+    /// Construct DST variant from a pointer and a size.
+    ///
+    /// NOTE: lifetime of returned reference is not tied to any underlying storage.
+    /// SAFETY: `ptr` is valid. Underlying storage is fully initialized up to at least `len` elements.
+    #[inline]
+    pub unsafe fn flex_ptr<'unbounded>(
+        ptr: *const Self,
+        len: usize,
+    ) -> &'unbounded flexarray_bogus_zero_fam<[::std::os::raw::c_char]> {
+        &*::std::ptr::from_raw_parts(ptr as *const (), len)
+    }
+    /// Construct mutable DST variant from a pointer and a
+    /// size. The returned `&mut` reference is initialized
+    /// pointing to memory referenced by `ptr`, but there's
+    /// no requirement that that memory be initialized.
+    ///
+    /// NOTE: lifetime of returned reference is not tied to any underlying storage.
+    /// SAFETY: `ptr` is valid. Underlying storage has space for at least `len` elements.
+    #[inline]
+    pub unsafe fn flex_ptr_mut<'unbounded>(
+        ptr: *mut Self,
+        len: usize,
+    ) -> ::std::mem::MaybeUninit<
+        &'unbounded mut flexarray_bogus_zero_fam<[::std::os::raw::c_char]>,
+    > {
+        let mut uninit = ::std::mem::MaybeUninit::<
+            &mut flexarray_bogus_zero_fam<[::std::os::raw::c_char]>,
+        >::uninit();
+        (uninit.as_mut_ptr()
+            as *mut *mut flexarray_bogus_zero_fam<[::std::os::raw::c_char]>)
+            .write(::std::ptr::from_raw_parts_mut(ptr as *mut (), len));
+        uninit
+    }
+}
+#[repr(C)]
+#[repr(align(128))]
+#[derive(Debug)]
+pub struct flexarray_align<FAM: ?Sized = [::std::os::raw::c_int; 0]> {
+    pub count: ::std::os::raw::c_int,
+    pub data: FAM,
+}
+const _: () = {
+    ["Size of flexarray_align"][::std::mem::size_of::<flexarray_align>() - 128usize];
+    [
+        "Alignment of flexarray_align",
+    ][::std::mem::align_of::<flexarray_align>() - 128usize];
+    [
+        "Offset of field: flexarray_align::count",
+    ][::std::mem::offset_of!(flexarray_align, count) - 0usize];
+    [
+        "Offset of field: flexarray_align::data",
+    ][::std::mem::offset_of!(flexarray_align, data) - 4usize];
+};
+impl flexarray_align<[::std::os::raw::c_int]> {
+    pub fn layout(len: usize) -> ::std::alloc::Layout {
+        unsafe {
+            let p: *const Self = ::std::ptr::from_raw_parts(::std::ptr::null(), len);
+            ::std::alloc::Layout::for_value_raw(p)
+        }
+    }
+    #[inline]
+    pub fn fixed(&self) -> (&flexarray_align<[::std::os::raw::c_int; 0]>, usize) {
+        unsafe {
+            let (ptr, len) = (self as *const Self).to_raw_parts();
+            (&*(ptr as *const flexarray_align<[::std::os::raw::c_int; 0]>), len)
+        }
+    }
+    #[inline]
+    pub fn fixed_mut(
+        &mut self,
+    ) -> (&mut flexarray_align<[::std::os::raw::c_int; 0]>, usize) {
+        unsafe {
+            let (ptr, len) = (self as *mut Self).to_raw_parts();
+            (&mut *(ptr as *mut flexarray_align<[::std::os::raw::c_int; 0]>), len)
+        }
+    }
+}
+impl flexarray_align<[::std::os::raw::c_int; 0]> {
+    /// Convert a sized prefix to an unsized structure with the given length.
+    ///
+    /// SAFETY: Underlying storage is initialized up to at least `len` elements.
+    pub unsafe fn flex_ref(
+        &self,
+        len: usize,
+    ) -> &flexarray_align<[::std::os::raw::c_int]> {
+        Self::flex_ptr(self, len)
+    }
+    /// Convert a mutable sized prefix to an unsized structure with the given length.
+    ///
+    /// SAFETY: Underlying storage is initialized up to at least `len` elements.
+    #[inline]
+    pub unsafe fn flex_ref_mut(
+        &mut self,
+        len: usize,
+    ) -> &mut flexarray_align<[::std::os::raw::c_int]> {
+        Self::flex_ptr_mut(self, len).assume_init()
+    }
+    /// Construct DST variant from a pointer and a size.
+    ///
+    /// NOTE: lifetime of returned reference is not tied to any underlying storage.
+    /// SAFETY: `ptr` is valid. Underlying storage is fully initialized up to at least `len` elements.
+    #[inline]
+    pub unsafe fn flex_ptr<'unbounded>(
+        ptr: *const Self,
+        len: usize,
+    ) -> &'unbounded flexarray_align<[::std::os::raw::c_int]> {
+        &*::std::ptr::from_raw_parts(ptr as *const (), len)
+    }
+    /// Construct mutable DST variant from a pointer and a
+    /// size. The returned `&mut` reference is initialized
+    /// pointing to memory referenced by `ptr`, but there's
+    /// no requirement that that memory be initialized.
+    ///
+    /// NOTE: lifetime of returned reference is not tied to any underlying storage.
+    /// SAFETY: `ptr` is valid. Underlying storage has space for at least `len` elements.
+    #[inline]
+    pub unsafe fn flex_ptr_mut<'unbounded>(
+        ptr: *mut Self,
+        len: usize,
+    ) -> ::std::mem::MaybeUninit<
+        &'unbounded mut flexarray_align<[::std::os::raw::c_int]>,
+    > {
+        let mut uninit = ::std::mem::MaybeUninit::<
+            &mut flexarray_align<[::std::os::raw::c_int]>,
+        >::uninit();
+        (uninit.as_mut_ptr() as *mut *mut flexarray_align<[::std::os::raw::c_int]>)
+            .write(::std::ptr::from_raw_parts_mut(ptr as *mut (), len));
+        uninit
+    }
+}
+impl Default for flexarray_align<[::std::os::raw::c_int; 0]> {
+    fn default() -> Self {
+        let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+        unsafe {
+            ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+            s.assume_init()
+        }
+    }
+}
diff --git a/bindgen-tests/tests/headers/flexarray.hpp b/bindgen-tests/tests/headers/flexarray.hpp
new file mode 100644
index 00000000..b698c38d
--- /dev/null
+++ b/bindgen-tests/tests/headers/flexarray.hpp
@@ -0,0 +1,32 @@
+// bindgen-flags: --rust-target nightly --flexarray-dst --raw-line '#![cfg(feature = "nightly")]' --raw-line '#![feature(ptr_metadata, layout_for_ptr)]'
+
+struct flexarray {
+    int count;
+    int data[];
+};
+
+struct flexarray_zero {
+    int count;
+    int data[0];
+};
+
+template<typename T>
+struct flexarray_template {
+    int count;
+    T data[];
+};
+
+struct flexarray_ref {
+    flexarray *things;
+};
+
+struct flexarray_bogus_zero_fam {
+    int count;
+    int data1[0];
+    char data2[];
+};
+
+struct flexarray_align {
+    int count;
+    int data[];
+} __attribute__((aligned(128)));