summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/codegen/mod.rs10
-rw-r--r--src/ir/analysis/mod.rs3
-rw-r--r--src/ir/analysis/sizedness.rs360
-rw-r--r--src/ir/comp.rs20
-rw-r--r--src/ir/context.rs42
-rw-r--r--src/ir/item.rs19
-rw-r--r--src/ir/ty.rs44
-rw-r--r--tests/expectations/tests/array-of-zero-sized-types.rs56
-rw-r--r--tests/expectations/tests/contains-vs-inherits-zero-sized.rs97
-rw-r--r--tests/expectations/tests/opaque-template-inst-member-2.rs4
-rw-r--r--tests/expectations/tests/opaque-template-inst-member.rs4
-rw-r--r--tests/expectations/tests/opaque_pointer.rs4
-rw-r--r--tests/expectations/tests/template.rs4
-rw-r--r--tests/expectations/tests/zero-sized-array.rs159
-rw-r--r--tests/headers/array-of-zero-sized-types.hpp12
-rw-r--r--tests/headers/contains-vs-inherits-zero-sized.hpp21
-rw-r--r--tests/headers/zero-sized-array.hpp45
17 files changed, 829 insertions, 75 deletions
diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs
index 4dbd2fc6..6c1ed02f 100644
--- a/src/codegen/mod.rs
+++ b/src/codegen/mod.rs
@@ -9,7 +9,7 @@ use self::struct_layout::StructLayoutTracker;
use super::BindgenOptions;
-use ir::analysis::HasVtable;
+use ir::analysis::{HasVtable, Sizedness};
use ir::annotations::FieldAccessorKind;
use ir::comment;
use ir::comp::{Base, Bitfield, BitfieldUnit, CompInfo, CompKind, Field,
@@ -1534,7 +1534,7 @@ impl CodeGenerator for CompInfo {
warn!("Opaque type without layout! Expect dragons!");
}
}
- } else if !is_union && !self.is_unsized(ctx, item.id().expect_type_id(ctx)) {
+ } else if !is_union && !item.is_zero_sized(ctx) {
if let Some(padding_field) =
layout.and_then(|layout| struct_layout.pad_struct(layout))
{
@@ -1565,7 +1565,7 @@ impl CodeGenerator for CompInfo {
//
// NOTE: This check is conveniently here to avoid the dummy fields we
// may add for unused template parameters.
- if self.is_unsized(ctx, item.id().expect_type_id(ctx)) {
+ if item.is_zero_sized(ctx) {
let has_address = if is_opaque {
// Generate the address field if it's an opaque type and
// couldn't determine the layout of the blob.
@@ -1643,8 +1643,8 @@ impl CodeGenerator for CompInfo {
{
derives.push("Copy");
- if ctx.options().rust_features().builtin_clone_impls() ||
- used_template_params.is_some()
+ if ctx.options().rust_features().builtin_clone_impls() ||
+ used_template_params.is_some()
{
// FIXME: This requires extra logic if you have a big array in a
// templated struct. The reason for this is that the magic:
diff --git a/src/ir/analysis/mod.rs b/src/ir/analysis/mod.rs
index 6caf3313..44ca4279 100644
--- a/src/ir/analysis/mod.rs
+++ b/src/ir/analysis/mod.rs
@@ -58,6 +58,9 @@ mod derive_partial_eq_or_partial_ord;
pub use self::derive_partial_eq_or_partial_ord::CannotDerivePartialEqOrPartialOrd;
mod has_float;
pub use self::has_float::HasFloat;
+mod sizedness;
+pub use self::sizedness::{Sizedness, SizednessAnalysis, SizednessResult};
+
use ir::context::{BindgenContext, ItemId};
use ir::traversal::{EdgeKind, Trace};
diff --git a/src/ir/analysis/sizedness.rs b/src/ir/analysis/sizedness.rs
new file mode 100644
index 00000000..e82c1798
--- /dev/null
+++ b/src/ir/analysis/sizedness.rs
@@ -0,0 +1,360 @@
+//! Determining the sizedness of types (as base classes and otherwise).
+
+use super::{ConstrainResult, MonotoneFramework, HasVtable, generate_dependencies};
+use ir::context::{BindgenContext, TypeId};
+use ir::item::IsOpaque;
+use ir::traversal::EdgeKind;
+use ir::ty::TypeKind;
+use std::cmp;
+use std::collections::HashMap;
+use std::collections::hash_map::Entry;
+use std::ops;
+
+/// The result of the `Sizedness` analysis for an individual item.
+///
+/// This is a chain lattice of the form:
+///
+/// ```ignore
+/// NonZeroSized
+/// |
+/// DependsOnTypeParam
+/// |
+/// ZeroSized
+/// ```
+///
+/// We initially assume that all types are `ZeroSized` and then update our
+/// understanding as we learn more about each type.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord)]
+pub enum SizednessResult {
+ /// Has some size that is known to be greater than zero. That doesn't mean
+ /// it has a static size, but it is not zero sized for sure. In other words,
+ /// it might contain an incomplete array or some other dynamically sized
+ /// type.
+ NonZeroSized,
+
+ /// Whether this type is zero-sized or not depends on whether a type
+ /// parameter is zero-sized or not.
+ ///
+ /// For example, given these definitions:
+ ///
+ /// ```c++
+ /// template<class T>
+ /// class Flongo : public T {};
+ ///
+ /// class Empty {};
+ ///
+ /// class NonEmpty { int x; };
+ /// ```
+ ///
+ /// Then `Flongo<Empty>` is zero-sized, and needs an `_address` byte
+ /// inserted, while `Flongo<NonEmpty>` is *not* zero-sized, and should *not*
+ /// have an `_address` byte inserted.
+ ///
+ /// We don't properly handle this situation correctly right now:
+ /// https://github.com/rust-lang-nursery/rust-bindgen/issues/586
+ DependsOnTypeParam,
+
+ /// The type is zero-sized.
+ ///
+ /// This means that if it is a C++ type, and is not being used as a base
+ /// member, then we must add an `_address` byte to enforce the
+ /// unique-address-per-distinct-object-instance rule.
+ ZeroSized,
+}
+
+impl Default for SizednessResult {
+ fn default() -> Self {
+ SizednessResult::ZeroSized
+ }
+}
+
+impl cmp::PartialOrd for SizednessResult {
+ fn partial_cmp(&self, rhs: &Self) -> Option<cmp::Ordering> {
+ use self::SizednessResult::*;
+
+ match (*self, *rhs) {
+ (x, y) if x == y => Some(cmp::Ordering::Equal),
+ (NonZeroSized, _) => Some(cmp::Ordering::Greater),
+ (_, NonZeroSized) => Some(cmp::Ordering::Less),
+ (DependsOnTypeParam, _) => Some(cmp::Ordering::Greater),
+ (_, DependsOnTypeParam) => Some(cmp::Ordering::Less),
+ _ => unreachable!(),
+ }
+ }
+}
+
+impl SizednessResult {
+ /// Take the least upper bound of `self` and `rhs`.
+ pub fn join(self, rhs: Self) -> Self {
+ cmp::max(self, rhs)
+ }
+}
+
+impl ops::BitOr for SizednessResult {
+ type Output = Self;
+
+ fn bitor(self, rhs: SizednessResult) -> Self::Output {
+ self.join(rhs)
+ }
+}
+
+impl ops::BitOrAssign for SizednessResult {
+ fn bitor_assign(&mut self, rhs: SizednessResult) {
+ *self = self.join(rhs)
+ }
+}
+
+/// An analysis that computes the sizedness of all types.
+///
+/// * For types with known sizes -- for example pointers, scalars, etc... --
+/// they are assigned `NonZeroSized`.
+///
+/// * For compound structure types with one or more fields, they are assigned
+/// `NonZeroSized`.
+///
+/// * For compound structure types without any fields, the results of the bases
+/// are `join`ed.
+///
+/// * For type parameters, `DependsOnTypeParam` is assigned.
+#[derive(Debug)]
+pub struct SizednessAnalysis<'ctx> {
+ ctx: &'ctx BindgenContext,
+ dependencies: HashMap<TypeId, Vec<TypeId>>,
+ // Incremental results of the analysis. Missing entries are implicitly
+ // considered `ZeroSized`.
+ sized: HashMap<TypeId, SizednessResult>,
+}
+
+impl<'ctx> SizednessAnalysis<'ctx> {
+ fn consider_edge(kind: EdgeKind) -> bool {
+ match kind {
+ // These are the only edges that can affect whether a type is
+ // zero-sized or not.
+ EdgeKind::TemplateArgument |
+ EdgeKind::TemplateParameterDefinition |
+ EdgeKind::TemplateDeclaration |
+ EdgeKind::TypeReference |
+ EdgeKind::BaseMember |
+ EdgeKind::Field => true,
+ _ => false,
+ }
+ }
+
+ /// Insert an incremental result, and return whether this updated our
+ /// knowledge of types and we should continue the analysis.
+ fn insert(&mut self, id: TypeId, result: SizednessResult) -> ConstrainResult {
+ trace!("inserting {:?} for {:?}", result, id);
+
+ if let SizednessResult::ZeroSized = result {
+ return ConstrainResult::Same;
+ }
+
+ match self.sized.entry(id) {
+ Entry::Occupied(mut entry) => {
+ if *entry.get() < result {
+ entry.insert(result);
+ ConstrainResult::Changed
+ } else {
+ ConstrainResult::Same
+ }
+ }
+ Entry::Vacant(entry) => {
+ entry.insert(result);
+ ConstrainResult::Changed
+ }
+ }
+ }
+
+ fn forward(&mut self, from: TypeId, to: TypeId) -> ConstrainResult {
+ match self.sized.get(&from).cloned() {
+ None => ConstrainResult::Same,
+ Some(r) => self.insert(to, r),
+ }
+ }
+}
+
+impl<'ctx> MonotoneFramework for SizednessAnalysis<'ctx> {
+ type Node = TypeId;
+ type Extra = &'ctx BindgenContext;
+ type Output = HashMap<TypeId, SizednessResult>;
+
+ fn new(ctx: &'ctx BindgenContext) -> SizednessAnalysis<'ctx> {
+ let dependencies = generate_dependencies(ctx, Self::consider_edge)
+ .into_iter()
+ .filter_map(|(id, sub_ids)| {
+ id.as_type_id(ctx)
+ .map(|id| {
+ (
+ id,
+ sub_ids.into_iter()
+ .filter_map(|s| s.as_type_id(ctx))
+ .collect::<Vec<_>>()
+ )
+ })
+ })
+ .collect();
+
+ let sized = HashMap::new();
+
+ SizednessAnalysis {
+ ctx,
+ dependencies,
+ sized,
+ }
+ }
+
+ fn initial_worklist(&self) -> Vec<TypeId> {
+ self.ctx
+ .whitelisted_items()
+ .iter()
+ .cloned()
+ .filter_map(|id| id.as_type_id(self.ctx))
+ .collect()
+ }
+
+ fn constrain(&mut self, id: TypeId) -> ConstrainResult {
+ trace!("constrain {:?}", id);
+
+ if let Some(SizednessResult::NonZeroSized) = self.sized.get(&id).cloned() {
+ trace!(" already know it is not zero-sized");
+ return ConstrainResult::Same;
+ }
+
+ if id.has_vtable_ptr(self.ctx) {
+ trace!(" has an explicit vtable pointer, therefore is not zero-sized");
+ return self.insert(id, SizednessResult::NonZeroSized);
+ }
+
+ let ty = self.ctx.resolve_type(id);
+
+ if id.is_opaque(self.ctx, &()) {
+ trace!(" type is opaque; checking layout...");
+ let result = ty.layout(self.ctx)
+ .map_or(SizednessResult::ZeroSized, |l| {
+ if l.size == 0 {
+ trace!(" ...layout has size == 0");
+ SizednessResult::ZeroSized
+ } else {
+ trace!(" ...layout has size > 0");
+ SizednessResult::NonZeroSized
+ }
+ });
+ return self.insert(id, result);
+ }
+
+ match *ty.kind() {
+ TypeKind::Void => {
+ trace!(" void is zero-sized");
+ self.insert(id, SizednessResult::ZeroSized)
+ }
+
+ TypeKind::TypeParam => {
+ trace!(" type params sizedness depends on what they're \
+ instantiated as");
+ self.insert(id, SizednessResult::DependsOnTypeParam)
+ }
+
+ TypeKind::Int(..) |
+ TypeKind::Float(..) |
+ TypeKind::Complex(..) |
+ TypeKind::Function(..) |
+ TypeKind::Enum(..) |
+ TypeKind::Reference(..) |
+ TypeKind::NullPtr |
+ TypeKind::BlockPointer |
+ TypeKind::ObjCId |
+ TypeKind::ObjCSel |
+ TypeKind::Pointer(..) => {
+ trace!(" {:?} is known not to be zero-sized", ty.kind());
+ self.insert(id, SizednessResult::NonZeroSized)
+ }
+
+ TypeKind::ObjCInterface(..) => {
+ trace!(" obj-c interfaces always have at least the `isa` pointer");
+ self.insert(id, SizednessResult::NonZeroSized)
+ }
+
+ TypeKind::TemplateAlias(t, _) |
+ TypeKind::Alias(t) |
+ TypeKind::ResolvedTypeRef(t) => {
+ trace!(" aliases and type refs forward to their inner type");
+ self.forward(t, id)
+ }
+
+ TypeKind::TemplateInstantiation(ref inst) => {
+ trace!(" template instantiations are zero-sized if their \
+ definition is zero-sized");
+ self.forward(inst.template_definition(), id)
+ }
+
+ TypeKind::Array(_, 0) => {
+ trace!(" arrays of zero elements are zero-sized");
+ self.insert(id, SizednessResult::ZeroSized)
+ }
+ TypeKind::Array(..) => {
+ trace!(" arrays of > 0 elements are not zero-sized");
+ self.insert(id, SizednessResult::NonZeroSized)
+ }
+
+ TypeKind::Comp(ref info) => {
+ trace!(" comp considers its own fields and bases");
+
+ if !info.fields().is_empty() {
+ return self.insert(id, SizednessResult::NonZeroSized);
+ }
+
+ let result = info.base_members()
+ .iter()
+ .filter_map(|base| self.sized.get(&base.ty))
+ .fold(SizednessResult::ZeroSized, |a, b| a.join(*b));
+
+ self.insert(id, result)
+ }
+
+ TypeKind::Opaque => {
+ unreachable!("covered by the .is_opaque() check above")
+ }
+
+ TypeKind::UnresolvedTypeRef(..) => {
+ unreachable!("Should have been resolved after parsing!");
+ }
+ }
+ }
+
+ fn each_depending_on<F>(&self, id: TypeId, mut f: F)
+ where
+ F: FnMut(TypeId),
+ {
+ if let Some(edges) = self.dependencies.get(&id) {
+ for ty in edges {
+ trace!("enqueue {:?} into worklist", ty);
+ f(*ty);
+ }
+ }
+ }
+}
+
+impl<'ctx> From<SizednessAnalysis<'ctx>> for HashMap<TypeId, SizednessResult> {
+ fn from(analysis: SizednessAnalysis<'ctx>) -> Self {
+ // We let the lack of an entry mean "ZeroSized" to save space.
+ extra_assert!(analysis.sized.values().all(|v| {
+ *v != SizednessResult::ZeroSized
+ }));
+
+ analysis.sized
+ }
+}
+
+/// A convenience trait for querying whether some type or id is sized.
+///
+/// This is not for _computing_ whether the thing is sized, it is for looking up
+/// the results of the `Sizedness` analysis's computations for a specific thing.
+pub trait Sizedness {
+ /// Get the sizedness of this type.
+ fn sizedness(&self, ctx: &BindgenContext) -> SizednessResult;
+
+ /// Is the sizedness for this type `SizednessResult::ZeroSized`?
+ fn is_zero_sized(&self, ctx: &BindgenContext) -> bool {
+ self.sizedness(ctx) == SizednessResult::ZeroSized
+ }
+}
diff --git a/src/ir/comp.rs b/src/ir/comp.rs
index 3c041e7c..56e41d52 100644
--- a/src/ir/comp.rs
+++ b/src/ir/comp.rs
@@ -1,6 +1,6 @@
//! Compound types (unions and structs) in our intermediate representation.
-use super::analysis::HasVtable;
+use super::analysis::Sizedness;
use super::annotations::Annotations;
use super::context::{BindgenContext, FunctionId, ItemId, TypeId, VarId};
use super::dot::DotAttributes;
@@ -896,11 +896,10 @@ impl Base {
return false;
}
- let base_ty = ctx.resolve_type(self.ty);
- // NB: We won't include unsized types in our base chain because they
+ // NB: We won't include zero-sized types in our base chain because they
// would contribute to our size given the dummy field we insert for
- // unsized types.
- if base_ty.is_unsized(ctx, self.ty) {
+ // zero-sized types.
+ if self.ty.is_zero_sized(ctx) {
return false;
}
@@ -1010,17 +1009,6 @@ impl CompInfo {
}
}
- /// Is this compound type unsized?
- pub fn is_unsized(&self, ctx: &BindgenContext, id: TypeId) -> bool {
- !id.has_vtable(ctx) && self.fields().is_empty() &&
- self.base_members.iter().all(|base| {
- ctx.resolve_type(base.ty).canonical_type(ctx).is_unsized(
- ctx,
- base.ty,
- )
- })
- }
-
/// Compute the layout of this type.
///
/// This is called as a fallback under some circumstances where LLVM doesn't
diff --git a/src/ir/context.rs b/src/ir/context.rs
index 1979e34d..e8f82b3d 100644
--- a/src/ir/context.rs
+++ b/src/ir/context.rs
@@ -1,10 +1,11 @@
//! Common context that is passed around during parsing and codegen.
-use super::analysis::{CannotDeriveCopy, CannotDeriveDebug,
- CannotDeriveDefault, CannotDeriveHash,
- CannotDerivePartialEqOrPartialOrd, HasTypeParameterInArray,
- HasVtableAnalysis, HasVtableResult, HasDestructorAnalysis,
- UsedTemplateParameters, HasFloat, analyze};
+use super::analysis::{CannotDeriveCopy, CannotDeriveDebug, CannotDeriveDefault,
+ CannotDeriveHash, CannotDerivePartialEqOrPartialOrd,
+ HasTypeParameterInArray, HasVtableAnalysis,
+ HasVtableResult, HasDestructorAnalysis,
+ UsedTemplateParameters, HasFloat, SizednessAnalysis,
+ SizednessResult, analyze};
use super::derive::{CanDeriveCopy, CanDeriveDebug, CanDeriveDefault,
CanDeriveHash, CanDerivePartialOrd, CanDeriveOrd,
CanDerivePartialEq, CanDeriveEq, CannotDeriveReason};
@@ -428,6 +429,12 @@ pub struct BindgenContext {
/// before that and `Some` after.
cannot_derive_partialeq_or_partialord: Option<HashMap<ItemId, CannotDeriveReason>>,
+ /// The sizedness of types.
+ ///
+ /// This is populated by `compute_sizedness` and is always `None` before
+ /// that function is invoked and `Some` afterwards.
+ sizedness: Option<HashMap<TypeId, SizednessResult>>,
+
/// The set of (`ItemId's of`) types that has vtable.
///
/// Populated when we enter codegen by `compute_has_vtable`; always `None`
@@ -586,6 +593,7 @@ impl BindgenContext {
cannot_derive_copy_in_array: None,
cannot_derive_hash: None,
cannot_derive_partialeq_or_partialord: None,
+ sizedness: None,
have_vtable: None,
have_destructor: None,
has_type_param_in_array: None,
@@ -1177,6 +1185,7 @@ impl BindgenContext {
self.assert_every_item_in_a_module();
self.compute_has_vtable();
+ self.compute_sizedness();
self.compute_has_destructor();
self.find_used_template_parameters();
self.compute_cannot_derive_debug();
@@ -1259,6 +1268,29 @@ impl BindgenContext {
}
}
+ /// Compute for every type whether it is sized or not, and whether it is
+ /// sized or not as a base class.
+ fn compute_sizedness(&mut self) {
+ let _t = self.timer("compute_sizedness");
+ assert!(self.sizedness.is_none());
+ self.sizedness = Some(analyze::<SizednessAnalysis>(self));
+ }
+
+ /// Look up whether the type with the given id is sized or not.
+ pub fn lookup_sizedness(&self, id: TypeId) -> SizednessResult {
+ assert!(
+ self.in_codegen_phase(),
+ "We only compute sizedness after we've entered codegen"
+ );
+
+ self.sizedness
+ .as_ref()
+ .unwrap()
+ .get(&id)
+ .cloned()
+ .unwrap_or(SizednessResult::ZeroSized)
+ }
+
/// Compute whether the type has vtable.
fn compute_has_vtable(&mut self) {
let _t = self.timer("compute_has_vtable");
diff --git a/src/ir/item.rs b/src/ir/item.rs
index ac50ef3b..9ed7267d 100644
--- a/src/ir/item.rs
+++ b/src/ir/item.rs
@@ -1,6 +1,6 @@
//! Bindgen's core intermediate representation type.
-use super::analysis::{HasVtable, HasVtableResult};
+use super::analysis::{HasVtable, HasVtableResult, Sizedness, SizednessResult};
use super::annotations::Annotations;
use super::comment;
use super::comp::MethodKind;
@@ -1027,6 +1027,23 @@ impl HasVtable for Item {
}
}
+impl<T> Sizedness for T
+where
+ T: Copy + Into<ItemId>
+{
+ fn sizedness(&self, ctx: &BindgenContext) -> SizednessResult {
+ let id: ItemId = (*self).into();
+ id.as_type_id(ctx)
+ .map_or(SizednessResult::default(), |id| ctx.lookup_sizedness(id))
+ }
+}
+
+impl Sizedness for Item {
+ fn sizedness(&self, ctx: &BindgenContext) -> SizednessResult {
+ self.id().sizedness(ctx)
+ }
+}
+
impl<T> HasTypeParamInArray for T
where
T: Copy + Into<ItemId>
diff --git a/src/ir/ty.rs b/src/ir/ty.rs
index 12ecbc66..bfb4c48e 100644
--- a/src/ir/ty.rs
+++ b/src/ir/ty.rs
@@ -698,50 +698,6 @@ pub enum TypeKind {
}
impl Type {
- /// Whether this type is unsized, that is, has no members. This is used to
- /// derive whether we should generate a dummy `_address` field for structs,
- /// to comply to the C and C++ layouts, that specify that every type needs
- /// to be addressable.
- pub fn is_unsized(&self, ctx: &BindgenContext, id: TypeId) -> bool {
- debug_assert!(ctx.in_codegen_phase(), "Not yet");
-
- match self.kind {
- TypeKind::Void => true,
- TypeKind::Comp(ref ci) => ci.is_unsized(ctx, id),
- TypeKind::Opaque => self.layout.map_or(true, |l| l.size == 0),
- TypeKind::Array(inner, size) => {
- size == 0 || ctx.resolve_type(inner).is_unsized(ctx, inner)
- }
- TypeKind::ResolvedTypeRef(inner) |
- TypeKind::Alias(inner) |
- TypeKind::TemplateAlias(inner, _) => {
- ctx.resolve_type(inner).is_unsized(ctx, inner)
- }
- TypeKind::TemplateInstantiation(ref inst) => {
- let definition = inst.template_definition();
- ctx.resolve_type(definition).is_unsized(ctx, definition)
- }
- TypeKind::TypeParam |
- TypeKind::Int(..) |
- TypeKind::Float(..) |
- TypeKind::Complex(..) |
- TypeKind::Function(..) |
- TypeKind::Enum(..) |
- TypeKind::Reference(..) |
- TypeKind::NullPtr |
- TypeKind::BlockPointer |
- TypeKind::ObjCId |
- TypeKind::ObjCSel |
- TypeKind::Pointer(..) => false,
-
- TypeKind::ObjCInterface(..) => true, // dunno?
-
- TypeKind::UnresolvedTypeRef(..) => {
- unreachable!("Should have been resolved after parsing!");
- }
- }
- }
-
/// This is another of the nasty methods. This one is the one that takes
/// care of the core logic of converting a clang type to a `Type`.
///
diff --git a/tests/expectations/tests/array-of-zero-sized-types.rs b/tests/expectations/tests/array-of-zero-sized-types.rs
new file mode 100644
index 00000000..92fbeadf
--- /dev/null
+++ b/tests/expectations/tests/array-of-zero-sized-types.rs
@@ -0,0 +1,56 @@
+/* automatically generated by rust-bindgen */
+
+
+#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)]
+
+
+
+/// This should get an `_address` byte.
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct Empty {
+ pub _address: u8,
+}
+#[test]
+fn bindgen_test_layout_Empty() {
+ assert_eq!(
+ ::std::mem::size_of::<Empty>(),
+ 1usize,
+ concat!("Size of: ", stringify!(Empty))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<Empty>(),
+ 1usize,
+ concat!("Alignment of ", stringify!(Empty))
+ );
+}
+/// This should not get an `_address` byte, since each `Empty` gets one, meaning
+/// that this object is addressable.
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct HasArrayOfEmpty {
+ pub empties: [Empty; 10usize],
+}
+#[test]
+fn bindgen_test_layout_HasArrayOfEmpty() {
+ assert_eq!(
+ ::std::mem::size_of::<HasArrayOfEmpty>(),
+ 10usize,
+ concat!("Size of: ", stringify!(HasArrayOfEmpty))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<HasArrayOfEmpty>(),
+ 1usize,
+ concat!("Alignment of ", stringify!(HasArrayOfEmpty))
+ );
+ assert_eq!(
+ unsafe { &(*(0 as *const HasArrayOfEmpty)).empties as *const _ as usize },
+ 0usize,
+ concat!(
+ "Alignment of field: ",
+ stringify!(HasArrayOfEmpty),
+ "::",
+ stringify!(empties)
+ )
+ );
+}
diff --git a/tests/expectations/tests/contains-vs-inherits-zero-sized.rs b/tests/expectations/tests/contains-vs-inherits-zero-sized.rs
new file mode 100644
index 00000000..b567bbbd
--- /dev/null
+++ b/tests/expectations/tests/contains-vs-inherits-zero-sized.rs
@@ -0,0 +1,97 @@
+/* automatically generated by rust-bindgen */
+
+
+#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)]
+
+
+
+/// This should get an `_address` byte.
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct Empty {
+ pub _address: u8,
+}
+#[test]
+fn bindgen_test_layout_Empty() {
+ assert_eq!(
+ ::std::mem::size_of::<Empty>(),
+ 1usize,
+ concat!("Size of: ", stringify!(Empty))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<Empty>(),
+ 1usize,
+ concat!("Alignment of ", stringify!(Empty))
+ );
+}
+/// This should not get an `_address` byte, so `sizeof(Inherits)` should be
+/// `1`.
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct Inherits {
+ pub b: bool,
+}
+#[test]
+fn bindgen_test_layout_Inherits() {
+ assert_eq!(
+ ::std::mem::size_of::<Inherits>(),
+ 1usize,
+ concat!("Size of: ", stringify!(Inherits))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<Inherits>(),
+ 1usize,
+ concat!("Alignment of ", stringify!(Inherits))
+ );
+ assert_eq!(
+ unsafe { &(*(0 as *const Inherits)).b as *const _ as usize },
+ 0usize,
+ concat!(
+ "Alignment of field: ",
+ stringify!(Inherits),
+ "::",
+ stringify!(b)
+ )
+ );
+}
+/// This should not get an `_address` byte, but contains `Empty` which *does* get
+/// one, so `sizeof(Contains)` should be `1 + 1`.
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct Contains {
+ pub empty: Empty,
+ pub b: bool,
+}
+#[test]
+fn bindgen_test_layout_Contains() {
+ assert_eq!(
+ ::std::mem::size_of::<Contains>(),
+ 2usize,
+ concat!("Size of: ", stringify!(Contains))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<Contains>(),
+ 1usize,
+ concat!("Alignment of ", stringify!(Contains))
+ );
+ assert_eq!(
+ unsafe { &(*(0 as *const Contains)).empty as *const _ as usize },
+ 0usize,
+ concat!(
+ "Alignment of field: ",
+ stringify!(Contains),
+ "::",
+ stringify!(empty)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(0 as *const Contains)).b as *const _ as usize },
+ 1usize,
+ concat!(
+ "Alignment of field: ",
+ stringify!(Contains),
+ "::",
+ stringify!(b)
+ )
+ );
+}
diff --git a/tests/expectations/tests/opaque-template-inst-member-2.rs b/tests/expectations/tests/opaque-template-inst-member-2.rs
index 6cac967e..3e00e0ed 100644
--- a/tests/expectations/tests/opaque-template-inst-member-2.rs
+++ b/tests/expectations/tests/opaque-template-inst-member-2.rs
@@ -9,7 +9,9 @@
/// where we are OK to derive Debug/Hash/PartialEq.
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq)]
-pub struct OpaqueTemplate {}
+pub struct OpaqueTemplate {
+ pub _address: u8,
+}
/// Should derive Debug/Hash/PartialEq.
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq)]
diff --git a/tests/expectations/tests/opaque-template-inst-member.rs b/tests/expectations/tests/opaque-template-inst-member.rs
index f686150c..60c6d846 100644
--- a/tests/expectations/tests/opaque-template-inst-member.rs
+++ b/tests/expectations/tests/opaque-template-inst-member.rs
@@ -6,7 +6,9 @@
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq)]
-pub struct OpaqueTemplate {}
+pub struct OpaqueTemplate {
+ pub _address: u8,
+}
/// This should not end up deriving Debug/Hash because its `mBlah` field cannot derive
/// Debug/Hash because the instantiation's definition cannot derive Debug/Hash.
#[repr(C)]
diff --git a/tests/expectations/tests/opaque_pointer.rs b/tests/expectations/tests/opaque_pointer.rs
index cc8e247f..6543c7c2 100644
--- a/tests/expectations/tests/opaque_pointer.rs
+++ b/tests/expectations/tests/opaque_pointer.rs
@@ -27,7 +27,9 @@ fn bindgen_test_layout_OtherOpaque() {
/// <div rustbindgen opaque></div>
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq)]
-pub struct Opaque {}
+pub struct Opaque {
+ pub _address: u8,
+}
#[repr(C)]
#[derive(Debug, Copy, Clone, Hash, PartialEq)]
pub struct WithOpaquePtr {
diff --git a/tests/expectations/tests/template.rs b/tests/expectations/tests/template.rs
index c00fb17f..999b81c7 100644
--- a/tests/expectations/tests/template.rs
+++ b/tests/expectations/tests/template.rs
@@ -336,7 +336,9 @@ impl Default for PODButContainsDtor {
/// <div rustbindgen opaque>
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq)]
-pub struct Opaque {}
+pub struct Opaque {
+ pub _address: u8,
+}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq)]
pub struct POD {
diff --git a/tests/expectations/tests/zero-sized-array.rs b/tests/expectations/tests/zero-sized-array.rs
new file mode 100644
index 00000000..15e5bc1f
--- /dev/null
+++ b/tests/expectations/tests/zero-sized-array.rs
@@ -0,0 +1,159 @@
+/* automatically generated by rust-bindgen */
+
+
+#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)]
+
+
+#[repr(C)]
+#[derive(Default)]
+pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>);
+impl<T> __IncompleteArrayField<T> {
+ #[inline]
+ pub fn new() -> Self {
+ __IncompleteArrayField(::std::marker::PhantomData)
+ }
+ #[inline]
+ pub unsafe fn as_ptr(&self) -> *const T {
+ ::std::mem::transmute(self)
+ }
+ #[inline]
+ pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
+ ::std::mem::transmute(self)
+ }
+ #[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")
+ }
+}
+impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
+ #[inline]
+ fn clone(&self) -> Self {
+ Self::new()
+ }
+}
+impl<T> ::std::marker::Copy for __IncompleteArrayField<T> {}
+/// Bizarrely enough, this should *not* get an `_address` field.
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct ZeroSizedArray {
+ pub arr: __IncompleteArrayField<::std::os::raw::c_char>,
+}
+#[test]
+fn bindgen_test_layout_ZeroSizedArray() {
+ assert_eq!(
+ ::std::mem::size_of::<ZeroSizedArray>(),
+ 0usize,
+ concat!("Size of: ", stringify!(ZeroSizedArray))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<ZeroSizedArray>(),
+ 1usize,
+ concat!("Alignment of ", stringify!(ZeroSizedArray))
+ );
+ assert_eq!(
+ unsafe { &(*(0 as *const ZeroSizedArray)).arr as *const _ as usize },
+ 0usize,
+ concat!(
+ "Alignment of field: ",
+ stringify!(ZeroSizedArray),
+ "::",
+ stringify!(arr)
+ )
+ );
+}
+/// And nor should this get an `_address` field.
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct ContainsZeroSizedArray {
+ pub zsa: ZeroSizedArray,
+}
+#[test]
+fn bindgen_test_layout_ContainsZeroSizedArray() {
+ assert_eq!(
+ ::std::mem::size_of::<ContainsZeroSizedArray>(),
+ 0usize,
+ concat!("Size of: ", stringify!(ContainsZeroSizedArray))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<ContainsZeroSizedArray>(),
+ 1usize,
+ concat!("Alignment of ", stringify!(ContainsZeroSizedArray))
+ );
+ assert_eq!(
+ unsafe { &(*(0 as *const ContainsZeroSizedArray)).zsa as *const _ as usize },
+ 0usize,
+ concat!(
+ "Alignment of field: ",
+ stringify!(ContainsZeroSizedArray),
+ "::",
+ stringify!(zsa)
+ )
+ );
+}
+/// Inheriting from ZeroSizedArray shouldn't cause an `_address` to be inserted
+/// either.
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct InheritsZeroSizedArray {
+ pub _base: ZeroSizedArray,
+}
+#[test]
+fn bindgen_test_layout_InheritsZeroSizedArray() {
+ assert_eq!(
+ ::std::mem::size_of::<InheritsZeroSizedArray>(),
+ 0usize,
+ concat!("Size of: ", stringify!(InheritsZeroSizedArray))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<InheritsZeroSizedArray>(),
+ 1usize,
+ concat!("Alignment of ", stringify!(InheritsZeroSizedArray))
+ );
+}
+/// And this should not get an `_address` field either.
+#[repr(C, packed)]
+#[derive(Debug, Default)]
+pub struct DynamicallySizedArray {
+ pub arr: __IncompleteArrayField<::std::os::raw::c_char>,
+}
+#[test]
+fn bindgen_test_layout_DynamicallySizedArray() {
+ assert_eq!(
+ ::std::mem::size_of::<DynamicallySizedArray>(),
+ 0usize,
+ concat!("Size of: ", stringify!(DynamicallySizedArray))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<DynamicallySizedArray>(),
+ 1usize,
+ concat!("Alignment of ", stringify!(DynamicallySizedArray))
+ );
+}
+/// No `_address` field here either.
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct ContainsDynamicallySizedArray {
+ pub dsa: DynamicallySizedArray,
+}
+#[test]
+fn bindgen_test_layout_ContainsDynamicallySizedArray() {
+ assert_eq!(
+ ::std::mem::size_of::<ContainsDynamicallySizedArray>(),
+ 0usize,
+ concat!("Size of: ", stringify!(ContainsDynamicallySizedArray))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<ContainsDynamicallySizedArray>(),
+ 1usize,
+ concat!("Alignment of ", stringify!(ContainsDynamicallySizedArray))
+ );
+}
diff --git a/tests/headers/array-of-zero-sized-types.hpp b/tests/headers/array-of-zero-sized-types.hpp
new file mode 100644
index 00000000..87b36d41
--- /dev/null
+++ b/tests/headers/array-of-zero-sized-types.hpp
@@ -0,0 +1,12 @@
+/**
+ * This should get an `_address` byte.
+ */
+struct Empty {};
+
+/**
+ * This should not get an `_address` byte, since each `Empty` gets one, meaning
+ * that this object is addressable.
+ */
+struct HasArrayOfEmpty {
+ Empty empties[10];
+};
diff --git a/tests/headers/contains-vs-inherits-zero-sized.hpp b/tests/headers/contains-vs-inherits-zero-sized.hpp
new file mode 100644
index 00000000..d354b0a2
--- /dev/null
+++ b/tests/headers/contains-vs-inherits-zero-sized.hpp
@@ -0,0 +1,21 @@
+/**
+ * This should get an `_address` byte.
+ */
+struct Empty {};
+
+/**
+ * This should not get an `_address` byte, so `sizeof(Inherits)` should be
+ * `1`.
+ */
+struct Inherits : public Empty {
+ bool b;
+};
+
+/**
+ * This should not get an `_address` byte, but contains `Empty` which *does* get
+ * one, so `sizeof(Contains)` should be `1 + 1`.
+ */
+struct Contains {
+ Empty empty;
+ bool b;
+};
diff --git a/tests/headers/zero-sized-array.hpp b/tests/headers/zero-sized-array.hpp
new file mode 100644
index 00000000..ae6d0554
--- /dev/null
+++ b/tests/headers/zero-sized-array.hpp
@@ -0,0 +1,45 @@
+// These classes are technically zero-sized, but despite that they still don't
+// get an `_address` field inserted.
+
+/**
+ * Bizarrely enough, this should *not* get an `_address` field.
+ */
+class ZeroSizedArray {
+ char arr[0];
+};
+
+/**
+ * And nor should this get an `_address` field.
+ */
+class ContainsZeroSizedArray {
+ ZeroSizedArray zsa;
+};
+
+/**
+ * Inheriting from ZeroSizedArray shouldn't cause an `_address` to be inserted
+ * either.
+ */
+class InheritsZeroSizedArray : ZeroSizedArray {};
+
+// These are dynamically sized, which means that `sizeof` yields `0` but it
+// isn't really true. We shouldn't add an `_address` field to them.
+
+/**
+ * And this should not get an `_address` field either.
+ */
+class DynamicallySizedArray {
+ char arr[];
+};
+
+/**
+ * No `_address` field here either.
+ */
+class ContainsDynamicallySizedArray {
+ DynamicallySizedArray dsa;
+};
+
+// Note: this is disallowed:
+//
+// error: base class 'DynamicallySizedArray' has a flexible array member
+//
+// class InheritsDynamicallySizedArray : DynamicallySizedArray {};