From 379bb1663402c3dfb173b5077bc5bba01a91beed Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Wed, 25 Oct 2017 10:18:19 -0700 Subject: [PATCH 1/3] Compute sizedness with a fixed-point analysis This fixes a couple bugs where we weren't properly adding an `_address` byte. It also helps pave the way for computing implicit fields (such as padding, `_address`, vtable pointers, etc) in its own pass, before codegen. Fixes #768 --- src/codegen/mod.rs | 10 +- src/ir/analysis/mod.rs | 3 + src/ir/analysis/sizedness.rs | 360 ++++++++++++++++++ src/ir/comp.rs | 20 +- src/ir/context.rs | 42 +- src/ir/item.rs | 19 +- src/ir/ty.rs | 44 --- .../tests/opaque-template-inst-member-2.rs | 4 +- .../tests/opaque-template-inst-member.rs | 4 +- tests/expectations/tests/opaque_pointer.rs | 4 +- tests/expectations/tests/template.rs | 4 +- 11 files changed, 439 insertions(+), 75 deletions(-) create mode 100644 src/ir/analysis/sizedness.rs diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index 4dbd2fc692..6c1ed02fbb 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 6caf33139b..44ca427983 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 0000000000..e82c1798e1 --- /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 Flongo : public T {}; + /// + /// class Empty {}; + /// + /// class NonEmpty { int x; }; + /// ``` + /// + /// Then `Flongo` is zero-sized, and needs an `_address` byte + /// inserted, while `Flongo` 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 { + 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>, + // Incremental results of the analysis. Missing entries are implicitly + // considered `ZeroSized`. + sized: HashMap, +} + +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; + + 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::>() + ) + }) + }) + .collect(); + + let sized = HashMap::new(); + + SizednessAnalysis { + ctx, + dependencies, + sized, + } + } + + fn initial_worklist(&self) -> Vec { + 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(&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> for HashMap { + 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 3c041e7c02..56e41d527b 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 1979e34da9..e8f82b3d16 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>, + /// The sizedness of types. + /// + /// This is populated by `compute_sizedness` and is always `None` before + /// that function is invoked and `Some` afterwards. + sizedness: Option>, + /// 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::(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 ac50ef3b81..9ed7267d7f 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 Sizedness for T +where + T: Copy + Into +{ + 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 HasTypeParamInArray for T where T: Copy + Into diff --git a/src/ir/ty.rs b/src/ir/ty.rs index 12ecbc6609..bfb4c48e63 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/opaque-template-inst-member-2.rs b/tests/expectations/tests/opaque-template-inst-member-2.rs index 6cac967e7d..3e00e0edf7 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 f686150c9f..60c6d846e1 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 cc8e247fc4..6543c7c2d9 100644 --- a/tests/expectations/tests/opaque_pointer.rs +++ b/tests/expectations/tests/opaque_pointer.rs @@ -27,7 +27,9 @@ fn bindgen_test_layout_OtherOpaque() { ///
#[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 c00fb17fd8..999b81c725 100644 --- a/tests/expectations/tests/template.rs +++ b/tests/expectations/tests/template.rs @@ -336,7 +336,9 @@ impl Default for PODButContainsDtor { ///
#[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 { From d018f421338318f5c21bc1809acc5b5dd14b6d98 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Wed, 25 Oct 2017 12:54:41 -0700 Subject: [PATCH 2/3] Add a couple sanity tests for zero sized types --- .../tests/array-of-zero-sized-types.rs | 56 +++++++++++ .../tests/contains-vs-inherits-zero-sized.rs | 97 +++++++++++++++++++ tests/headers/array-of-zero-sized-types.hpp | 12 +++ .../contains-vs-inherits-zero-sized.hpp | 21 ++++ 4 files changed, 186 insertions(+) create mode 100644 tests/expectations/tests/array-of-zero-sized-types.rs create mode 100644 tests/expectations/tests/contains-vs-inherits-zero-sized.rs create mode 100644 tests/headers/array-of-zero-sized-types.hpp create mode 100644 tests/headers/contains-vs-inherits-zero-sized.hpp 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 0000000000..92fbeadfc5 --- /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::(), + 1usize, + concat!("Size of: ", stringify!(Empty)) + ); + assert_eq!( + ::std::mem::align_of::(), + 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::(), + 10usize, + concat!("Size of: ", stringify!(HasArrayOfEmpty)) + ); + assert_eq!( + ::std::mem::align_of::(), + 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 0000000000..b567bbbd71 --- /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::(), + 1usize, + concat!("Size of: ", stringify!(Empty)) + ); + assert_eq!( + ::std::mem::align_of::(), + 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::(), + 1usize, + concat!("Size of: ", stringify!(Inherits)) + ); + assert_eq!( + ::std::mem::align_of::(), + 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::(), + 2usize, + concat!("Size of: ", stringify!(Contains)) + ); + assert_eq!( + ::std::mem::align_of::(), + 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/headers/array-of-zero-sized-types.hpp b/tests/headers/array-of-zero-sized-types.hpp new file mode 100644 index 0000000000..87b36d415c --- /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 0000000000..d354b0a2e5 --- /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; +}; From fd7afb63bdcd85c1aaeb4338d1f0f9508b2081eb Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Wed, 25 Oct 2017 13:18:13 -0700 Subject: [PATCH 3/3] Sanity tests for zero-sized and flexible arrays --- tests/expectations/tests/zero-sized-array.rs | 159 +++++++++++++++++++ tests/headers/zero-sized-array.hpp | 45 ++++++ 2 files changed, 204 insertions(+) create mode 100644 tests/expectations/tests/zero-sized-array.rs create mode 100644 tests/headers/zero-sized-array.hpp diff --git a/tests/expectations/tests/zero-sized-array.rs b/tests/expectations/tests/zero-sized-array.rs new file mode 100644 index 0000000000..15e5bc1f44 --- /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(::std::marker::PhantomData); +impl __IncompleteArrayField { + #[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 ::std::fmt::Debug for __IncompleteArrayField { + fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + fmt.write_str("__IncompleteArrayField") + } +} +impl ::std::clone::Clone for __IncompleteArrayField { + #[inline] + fn clone(&self) -> Self { + Self::new() + } +} +impl ::std::marker::Copy for __IncompleteArrayField {} +/// 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::(), + 0usize, + concat!("Size of: ", stringify!(ZeroSizedArray)) + ); + assert_eq!( + ::std::mem::align_of::(), + 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::(), + 0usize, + concat!("Size of: ", stringify!(ContainsZeroSizedArray)) + ); + assert_eq!( + ::std::mem::align_of::(), + 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::(), + 0usize, + concat!("Size of: ", stringify!(InheritsZeroSizedArray)) + ); + assert_eq!( + ::std::mem::align_of::(), + 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::(), + 0usize, + concat!("Size of: ", stringify!(DynamicallySizedArray)) + ); + assert_eq!( + ::std::mem::align_of::(), + 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::(), + 0usize, + concat!("Size of: ", stringify!(ContainsDynamicallySizedArray)) + ); + assert_eq!( + ::std::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(ContainsDynamicallySizedArray)) + ); +} diff --git a/tests/headers/zero-sized-array.hpp b/tests/headers/zero-sized-array.hpp new file mode 100644 index 0000000000..ae6d05549c --- /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 {};