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