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`. |
68 | pub(crate) fn join(self, rhs: Self) -> Self { |
69 | cmp::max(self, v2:rhs) |
70 | } |
71 | } |
72 | |
73 | impl ops::BitOr for SizednessResult { |
74 | type Output = Self; |
75 | |
76 | fn bitor(self, rhs: SizednessResult) -> Self::Output { |
77 | self.join(rhs) |
78 | } |
79 | } |
80 | |
81 | impl ops::BitOrAssign for 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> { |
109 | 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. |
125 | 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 | |
152 | 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 | |
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 | |
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 | |
199 | 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 | |
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> { |
333 | 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. |
350 | fn sizedness(&self, ctx: &BindgenContext) -> SizednessResult; |
351 | |
352 | /// Is the sizedness for this type `SizednessResult::ZeroSized`? |
353 | fn is_zero_sized(&self, ctx: &BindgenContext) -> bool { |
354 | self.sizedness(ctx) == SizednessResult::ZeroSized |
355 | } |
356 | } |
357 | |