1 //! Determining the sizedness of types (as base classes and otherwise). 2 3 use super::{ 4 generate_dependencies, ConstrainResult, HasVtable, MonotoneFramework, 5 }; 6 use crate::ir::context::{BindgenContext, TypeId}; 7 use crate::ir::item::IsOpaque; 8 use crate::ir::traversal::EdgeKind; 9 use crate::ir::ty::TypeKind; 10 use crate::{Entry, HashMap}; 11 use std::{cmp, ops}; 12 13 /// The result of the `Sizedness` analysis for an individual item. 14 /// 15 /// This is a chain lattice of the form: 16 /// 17 /// ```ignore 18 /// NonZeroSized 19 /// | 20 /// DependsOnTypeParam 21 /// | 22 /// ZeroSized 23 /// ``` 24 /// 25 /// We initially assume that all types are `ZeroSized` and then update our 26 /// understanding as we learn more about each type. 27 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] 28 pub enum SizednessResult { 29 /// The type is zero-sized. 30 /// 31 /// This means that if it is a C++ type, and is not being used as a base 32 /// member, then we must add an `_address` byte to enforce the 33 /// unique-address-per-distinct-object-instance rule. 34 ZeroSized, 35 36 /// Whether this type is zero-sized or not depends on whether a type 37 /// parameter is zero-sized or not. 38 /// 39 /// For example, given these definitions: 40 /// 41 /// ```c++ 42 /// template<class T> 43 /// class Flongo : public T {}; 44 /// 45 /// class Empty {}; 46 /// 47 /// class NonEmpty { int x; }; 48 /// ``` 49 /// 50 /// Then `Flongo<Empty>` is zero-sized, and needs an `_address` byte 51 /// inserted, while `Flongo<NonEmpty>` is *not* zero-sized, and should *not* 52 /// have an `_address` byte inserted. 53 /// 54 /// We don't properly handle this situation correctly right now: 55 /// https://github.com/rust-lang/rust-bindgen/issues/586 56 DependsOnTypeParam, 57 58 /// Has some size that is known to be greater than zero. That doesn't mean 59 /// it has a static size, but it is not zero sized for sure. In other words, 60 /// it might contain an incomplete array or some other dynamically sized 61 /// type. 62 NonZeroSized, 63 } 64 65 impl Default for SizednessResult { default() -> Self66 fn default() -> Self { 67 SizednessResult::ZeroSized 68 } 69 } 70 71 impl SizednessResult { 72 /// Take the least upper bound of `self` and `rhs`. join(self, rhs: Self) -> Self73 pub fn join(self, rhs: Self) -> Self { 74 cmp::max(self, rhs) 75 } 76 } 77 78 impl ops::BitOr for SizednessResult { 79 type Output = Self; 80 bitor(self, rhs: SizednessResult) -> Self::Output81 fn bitor(self, rhs: SizednessResult) -> Self::Output { 82 self.join(rhs) 83 } 84 } 85 86 impl ops::BitOrAssign for SizednessResult { bitor_assign(&mut self, rhs: SizednessResult)87 fn bitor_assign(&mut self, rhs: SizednessResult) { 88 *self = self.join(rhs) 89 } 90 } 91 92 /// An analysis that computes the sizedness of all types. 93 /// 94 /// * For types with known sizes -- for example pointers, scalars, etc... -- 95 /// they are assigned `NonZeroSized`. 96 /// 97 /// * For compound structure types with one or more fields, they are assigned 98 /// `NonZeroSized`. 99 /// 100 /// * For compound structure types without any fields, the results of the bases 101 /// are `join`ed. 102 /// 103 /// * For type parameters, `DependsOnTypeParam` is assigned. 104 #[derive(Debug)] 105 pub struct SizednessAnalysis<'ctx> { 106 ctx: &'ctx BindgenContext, 107 dependencies: HashMap<TypeId, Vec<TypeId>>, 108 // Incremental results of the analysis. Missing entries are implicitly 109 // considered `ZeroSized`. 110 sized: HashMap<TypeId, SizednessResult>, 111 } 112 113 impl<'ctx> SizednessAnalysis<'ctx> { consider_edge(kind: EdgeKind) -> bool114 fn consider_edge(kind: EdgeKind) -> bool { 115 match kind { 116 // These are the only edges that can affect whether a type is 117 // zero-sized or not. 118 EdgeKind::TemplateArgument | 119 EdgeKind::TemplateParameterDefinition | 120 EdgeKind::TemplateDeclaration | 121 EdgeKind::TypeReference | 122 EdgeKind::BaseMember | 123 EdgeKind::Field => true, 124 _ => false, 125 } 126 } 127 128 /// Insert an incremental result, and return whether this updated our 129 /// knowledge of types and we should continue the analysis. insert( &mut self, id: TypeId, result: SizednessResult, ) -> ConstrainResult130 fn insert( 131 &mut self, 132 id: TypeId, 133 result: SizednessResult, 134 ) -> ConstrainResult { 135 trace!("inserting {:?} for {:?}", result, id); 136 137 if let SizednessResult::ZeroSized = result { 138 return ConstrainResult::Same; 139 } 140 141 match self.sized.entry(id) { 142 Entry::Occupied(mut entry) => { 143 if *entry.get() < result { 144 entry.insert(result); 145 ConstrainResult::Changed 146 } else { 147 ConstrainResult::Same 148 } 149 } 150 Entry::Vacant(entry) => { 151 entry.insert(result); 152 ConstrainResult::Changed 153 } 154 } 155 } 156 forward(&mut self, from: TypeId, to: TypeId) -> ConstrainResult157 fn forward(&mut self, from: TypeId, to: TypeId) -> ConstrainResult { 158 match self.sized.get(&from).cloned() { 159 None => ConstrainResult::Same, 160 Some(r) => self.insert(to, r), 161 } 162 } 163 } 164 165 impl<'ctx> MonotoneFramework for SizednessAnalysis<'ctx> { 166 type Node = TypeId; 167 type Extra = &'ctx BindgenContext; 168 type Output = HashMap<TypeId, SizednessResult>; 169 new(ctx: &'ctx BindgenContext) -> SizednessAnalysis<'ctx>170 fn new(ctx: &'ctx BindgenContext) -> SizednessAnalysis<'ctx> { 171 let dependencies = generate_dependencies(ctx, Self::consider_edge) 172 .into_iter() 173 .filter_map(|(id, sub_ids)| { 174 id.as_type_id(ctx).map(|id| { 175 ( 176 id, 177 sub_ids 178 .into_iter() 179 .filter_map(|s| s.as_type_id(ctx)) 180 .collect::<Vec<_>>(), 181 ) 182 }) 183 }) 184 .collect(); 185 186 let sized = HashMap::default(); 187 188 SizednessAnalysis { 189 ctx, 190 dependencies, 191 sized, 192 } 193 } 194 initial_worklist(&self) -> Vec<TypeId>195 fn initial_worklist(&self) -> Vec<TypeId> { 196 self.ctx 197 .allowlisted_items() 198 .iter() 199 .cloned() 200 .filter_map(|id| id.as_type_id(self.ctx)) 201 .collect() 202 } 203 constrain(&mut self, id: TypeId) -> ConstrainResult204 fn constrain(&mut self, id: TypeId) -> ConstrainResult { 205 trace!("constrain {:?}", id); 206 207 if let Some(SizednessResult::NonZeroSized) = 208 self.sized.get(&id).cloned() 209 { 210 trace!(" already know it is not zero-sized"); 211 return ConstrainResult::Same; 212 } 213 214 if id.has_vtable_ptr(self.ctx) { 215 trace!(" has an explicit vtable pointer, therefore is not zero-sized"); 216 return self.insert(id, SizednessResult::NonZeroSized); 217 } 218 219 let ty = self.ctx.resolve_type(id); 220 221 if id.is_opaque(self.ctx, &()) { 222 trace!(" type is opaque; checking layout..."); 223 let result = 224 ty.layout(self.ctx).map_or(SizednessResult::ZeroSized, |l| { 225 if l.size == 0 { 226 trace!(" ...layout has size == 0"); 227 SizednessResult::ZeroSized 228 } else { 229 trace!(" ...layout has size > 0"); 230 SizednessResult::NonZeroSized 231 } 232 }); 233 return self.insert(id, result); 234 } 235 236 match *ty.kind() { 237 TypeKind::Void => { 238 trace!(" void is zero-sized"); 239 self.insert(id, SizednessResult::ZeroSized) 240 } 241 242 TypeKind::TypeParam => { 243 trace!( 244 " type params sizedness depends on what they're \ 245 instantiated as" 246 ); 247 self.insert(id, SizednessResult::DependsOnTypeParam) 248 } 249 250 TypeKind::Int(..) | 251 TypeKind::Float(..) | 252 TypeKind::Complex(..) | 253 TypeKind::Function(..) | 254 TypeKind::Enum(..) | 255 TypeKind::Reference(..) | 256 TypeKind::NullPtr | 257 TypeKind::ObjCId | 258 TypeKind::ObjCSel | 259 TypeKind::Pointer(..) => { 260 trace!(" {:?} is known not to be zero-sized", ty.kind()); 261 self.insert(id, SizednessResult::NonZeroSized) 262 } 263 264 TypeKind::ObjCInterface(..) => { 265 trace!(" obj-c interfaces always have at least the `isa` pointer"); 266 self.insert(id, SizednessResult::NonZeroSized) 267 } 268 269 TypeKind::TemplateAlias(t, _) | 270 TypeKind::Alias(t) | 271 TypeKind::BlockPointer(t) | 272 TypeKind::ResolvedTypeRef(t) => { 273 trace!(" aliases and type refs forward to their inner type"); 274 self.forward(t, id) 275 } 276 277 TypeKind::TemplateInstantiation(ref inst) => { 278 trace!( 279 " template instantiations are zero-sized if their \ 280 definition is zero-sized" 281 ); 282 self.forward(inst.template_definition(), id) 283 } 284 285 TypeKind::Array(_, 0) => { 286 trace!(" arrays of zero elements are zero-sized"); 287 self.insert(id, SizednessResult::ZeroSized) 288 } 289 TypeKind::Array(..) => { 290 trace!(" arrays of > 0 elements are not zero-sized"); 291 self.insert(id, SizednessResult::NonZeroSized) 292 } 293 TypeKind::Vector(..) => { 294 trace!(" vectors are not zero-sized"); 295 self.insert(id, SizednessResult::NonZeroSized) 296 } 297 298 TypeKind::Comp(ref info) => { 299 trace!(" comp considers its own fields and bases"); 300 301 if !info.fields().is_empty() { 302 return self.insert(id, SizednessResult::NonZeroSized); 303 } 304 305 let result = info 306 .base_members() 307 .iter() 308 .filter_map(|base| self.sized.get(&base.ty)) 309 .fold(SizednessResult::ZeroSized, |a, b| a.join(*b)); 310 311 self.insert(id, result) 312 } 313 314 TypeKind::Opaque => { 315 unreachable!("covered by the .is_opaque() check above") 316 } 317 318 TypeKind::UnresolvedTypeRef(..) => { 319 unreachable!("Should have been resolved after parsing!"); 320 } 321 } 322 } 323 each_depending_on<F>(&self, id: TypeId, mut f: F) where F: FnMut(TypeId),324 fn each_depending_on<F>(&self, id: TypeId, mut f: F) 325 where 326 F: FnMut(TypeId), 327 { 328 if let Some(edges) = self.dependencies.get(&id) { 329 for ty in edges { 330 trace!("enqueue {:?} into worklist", ty); 331 f(*ty); 332 } 333 } 334 } 335 } 336 337 impl<'ctx> From<SizednessAnalysis<'ctx>> for HashMap<TypeId, SizednessResult> { from(analysis: SizednessAnalysis<'ctx>) -> Self338 fn from(analysis: SizednessAnalysis<'ctx>) -> Self { 339 // We let the lack of an entry mean "ZeroSized" to save space. 340 extra_assert!(analysis 341 .sized 342 .values() 343 .all(|v| { *v != SizednessResult::ZeroSized })); 344 345 analysis.sized 346 } 347 } 348 349 /// A convenience trait for querying whether some type or id is sized. 350 /// 351 /// This is not for _computing_ whether the thing is sized, it is for looking up 352 /// the results of the `Sizedness` analysis's computations for a specific thing. 353 pub trait Sizedness { 354 /// Get the sizedness of this type. sizedness(&self, ctx: &BindgenContext) -> SizednessResult355 fn sizedness(&self, ctx: &BindgenContext) -> SizednessResult; 356 357 /// Is the sizedness for this type `SizednessResult::ZeroSized`? is_zero_sized(&self, ctx: &BindgenContext) -> bool358 fn is_zero_sized(&self, ctx: &BindgenContext) -> bool { 359 self.sizedness(ctx) == SizednessResult::ZeroSized 360 } 361 } 362