1use pyo3_ffi::PyType_IS_GC;
2
3use crate::{
4 exceptions::PyTypeError,
5 ffi,
6 impl_::pyclass::{
7 assign_sequence_item_from_mapping, get_sequence_item_from_mapping, tp_dealloc,
8 tp_dealloc_with_gc, PyClassItemsIter,
9 },
10 impl_::{
11 pymethods::{get_doc, get_name, Getter, Setter},
12 trampoline::trampoline,
13 },
14 types::PyType,
15 Py, PyCell, PyClass, PyGetterDef, PyMethodDefType, PyResult, PySetterDef, PyTypeInfo, Python,
16};
17use std::{
18 borrow::Cow,
19 collections::HashMap,
20 ffi::{CStr, CString},
21 os::raw::{c_char, c_int, c_ulong, c_void},
22 ptr,
23};
24
25pub(crate) struct PyClassTypeObject {
26 pub type_object: Py<PyType>,
27 #[allow(dead_code)] // This is purely a cache that must live as long as the type object
28 getset_destructors: Vec<GetSetDefDestructor>,
29}
30
31pub(crate) fn create_type_object<T>(py: Python<'_>) -> PyResult<PyClassTypeObject>
32where
33 T: PyClass,
34{
35 // Written this way to monomorphize the majority of the logic.
36 #[allow(clippy::too_many_arguments)]
37 unsafe fn inner(
38 py: Python<'_>,
39 base: *mut ffi::PyTypeObject,
40 dealloc: unsafe extern "C" fn(*mut ffi::PyObject),
41 dealloc_with_gc: unsafe extern "C" fn(*mut ffi::PyObject),
42 is_mapping: bool,
43 is_sequence: bool,
44 doc: &'static CStr,
45 dict_offset: Option<ffi::Py_ssize_t>,
46 weaklist_offset: Option<ffi::Py_ssize_t>,
47 is_basetype: bool,
48 items_iter: PyClassItemsIter,
49 name: &'static str,
50 module: Option<&'static str>,
51 size_of: usize,
52 ) -> PyResult<PyClassTypeObject> {
53 PyTypeBuilder {
54 slots: Vec::new(),
55 method_defs: Vec::new(),
56 getset_builders: HashMap::new(),
57 cleanup: Vec::new(),
58 tp_base: base,
59 tp_dealloc: dealloc,
60 tp_dealloc_with_gc: dealloc_with_gc,
61 is_mapping,
62 is_sequence,
63 has_new: false,
64 has_dealloc: false,
65 has_getitem: false,
66 has_setitem: false,
67 has_traverse: false,
68 has_clear: false,
69 has_dict: false,
70 class_flags: 0,
71 #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
72 buffer_procs: Default::default(),
73 }
74 .type_doc(doc)
75 .offsets(dict_offset, weaklist_offset)
76 .set_is_basetype(is_basetype)
77 .class_items(items_iter)
78 .build(py, name, module, size_of)
79 }
80
81 unsafe {
82 inner(
83 py,
84 T::BaseType::type_object_raw(py),
85 tp_dealloc::<T>,
86 tp_dealloc_with_gc::<T>,
87 T::IS_MAPPING,
88 T::IS_SEQUENCE,
89 T::doc(py)?,
90 T::dict_offset(),
91 T::weaklist_offset(),
92 T::IS_BASETYPE,
93 T::items_iter(),
94 T::NAME,
95 T::MODULE,
96 std::mem::size_of::<PyCell<T>>(),
97 )
98 }
99}
100
101type PyTypeBuilderCleanup = Box<dyn Fn(&PyTypeBuilder, *mut ffi::PyTypeObject)>;
102
103struct PyTypeBuilder {
104 slots: Vec<ffi::PyType_Slot>,
105 method_defs: Vec<ffi::PyMethodDef>,
106 getset_builders: HashMap<&'static str, GetSetDefBuilder>,
107 /// Used to patch the type objects for the things there's no
108 /// PyType_FromSpec API for... there's no reason this should work,
109 /// except for that it does and we have tests.
110 cleanup: Vec<PyTypeBuilderCleanup>,
111 tp_base: *mut ffi::PyTypeObject,
112 tp_dealloc: ffi::destructor,
113 tp_dealloc_with_gc: ffi::destructor,
114 is_mapping: bool,
115 is_sequence: bool,
116 has_new: bool,
117 has_dealloc: bool,
118 has_getitem: bool,
119 has_setitem: bool,
120 has_traverse: bool,
121 has_clear: bool,
122 has_dict: bool,
123 class_flags: c_ulong,
124 // Before Python 3.9, need to patch in buffer methods manually (they don't work in slots)
125 #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
126 buffer_procs: ffi::PyBufferProcs,
127}
128
129impl PyTypeBuilder {
130 /// # Safety
131 /// The given pointer must be of the correct type for the given slot
132 unsafe fn push_slot<T>(&mut self, slot: c_int, pfunc: *mut T) {
133 match slot {
134 ffi::Py_tp_new => self.has_new = true,
135 ffi::Py_tp_dealloc => self.has_dealloc = true,
136 ffi::Py_mp_subscript => self.has_getitem = true,
137 ffi::Py_mp_ass_subscript => self.has_setitem = true,
138 ffi::Py_tp_traverse => {
139 self.has_traverse = true;
140 self.class_flags |= ffi::Py_TPFLAGS_HAVE_GC;
141 }
142 ffi::Py_tp_clear => self.has_clear = true,
143 #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
144 ffi::Py_bf_getbuffer => {
145 // Safety: slot.pfunc is a valid function pointer
146 self.buffer_procs.bf_getbuffer = Some(std::mem::transmute(pfunc));
147 }
148 #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
149 ffi::Py_bf_releasebuffer => {
150 // Safety: slot.pfunc is a valid function pointer
151 self.buffer_procs.bf_releasebuffer = Some(std::mem::transmute(pfunc));
152 }
153 _ => {}
154 }
155
156 self.slots.push(ffi::PyType_Slot {
157 slot,
158 pfunc: pfunc as _,
159 });
160 }
161
162 /// # Safety
163 /// It is the caller's responsibility that `data` is of the correct type for the given slot.
164 unsafe fn push_raw_vec_slot<T>(&mut self, slot: c_int, mut data: Vec<T>) {
165 if !data.is_empty() {
166 // Python expects a zeroed entry to mark the end of the defs
167 data.push(std::mem::zeroed());
168 self.push_slot(slot, Box::into_raw(data.into_boxed_slice()) as *mut c_void);
169 }
170 }
171
172 fn pymethod_def(&mut self, def: &PyMethodDefType) {
173 match def {
174 PyMethodDefType::Getter(getter) => {
175 self.getset_builders
176 .entry(getter.name)
177 .or_default()
178 .add_getter(getter);
179 }
180 PyMethodDefType::Setter(setter) => {
181 self.getset_builders
182 .entry(setter.name)
183 .or_default()
184 .add_setter(setter);
185 }
186 PyMethodDefType::Method(def)
187 | PyMethodDefType::Class(def)
188 | PyMethodDefType::Static(def) => {
189 let (def, destructor) = def.as_method_def().unwrap();
190 // FIXME: stop leaking destructor
191 std::mem::forget(destructor);
192 self.method_defs.push(def);
193 }
194 // These class attributes are added after the type gets created by LazyStaticType
195 PyMethodDefType::ClassAttribute(_) => {}
196 }
197 }
198
199 fn finalize_methods_and_properties(&mut self) -> PyResult<Vec<GetSetDefDestructor>> {
200 let method_defs: Vec<pyo3_ffi::PyMethodDef> = std::mem::take(&mut self.method_defs);
201 // Safety: Py_tp_methods expects a raw vec of PyMethodDef
202 unsafe { self.push_raw_vec_slot(ffi::Py_tp_methods, method_defs) };
203
204 let mut getset_destructors = Vec::with_capacity(self.getset_builders.len());
205
206 #[allow(unused_mut)]
207 let mut property_defs: Vec<_> = self
208 .getset_builders
209 .iter()
210 .map(|(name, builder)| {
211 let (def, destructor) = builder.as_get_set_def(name)?;
212 getset_destructors.push(destructor);
213 Ok(def)
214 })
215 .collect::<PyResult<_>>()?;
216
217 // PyPy doesn't automatically add __dict__ getter / setter.
218 // PyObject_GenericGetDict not in the limited API until Python 3.10.
219 if self.has_dict {
220 #[cfg(not(any(PyPy, all(Py_LIMITED_API, not(Py_3_10)))))]
221 property_defs.push(ffi::PyGetSetDef {
222 name: "__dict__\0".as_ptr().cast(),
223 get: Some(ffi::PyObject_GenericGetDict),
224 set: Some(ffi::PyObject_GenericSetDict),
225 doc: ptr::null(),
226 closure: ptr::null_mut(),
227 });
228 }
229
230 // Safety: Py_tp_members expects a raw vec of PyGetSetDef
231 unsafe { self.push_raw_vec_slot(ffi::Py_tp_getset, property_defs) };
232
233 // If mapping methods implemented, define sequence methods get implemented too.
234 // CPython does the same for Python `class` statements.
235
236 // NB we don't implement sq_length to avoid annoying CPython behaviour of automatically adding
237 // the length to negative indices.
238
239 // Don't add these methods for "pure" mappings.
240
241 if !self.is_mapping && self.has_getitem {
242 // Safety: This is the correct slot type for Py_sq_item
243 unsafe {
244 self.push_slot(
245 ffi::Py_sq_item,
246 get_sequence_item_from_mapping as *mut c_void,
247 )
248 }
249 }
250
251 if !self.is_mapping && self.has_setitem {
252 // Safety: This is the correct slot type for Py_sq_ass_item
253 unsafe {
254 self.push_slot(
255 ffi::Py_sq_ass_item,
256 assign_sequence_item_from_mapping as *mut c_void,
257 )
258 }
259 }
260
261 Ok(getset_destructors)
262 }
263
264 fn set_is_basetype(mut self, is_basetype: bool) -> Self {
265 if is_basetype {
266 self.class_flags |= ffi::Py_TPFLAGS_BASETYPE;
267 }
268 self
269 }
270
271 /// # Safety
272 /// All slots in the PyClassItemsIter should be correct
273 unsafe fn class_items(mut self, iter: PyClassItemsIter) -> Self {
274 for items in iter {
275 for slot in items.slots {
276 self.push_slot(slot.slot, slot.pfunc);
277 }
278 for method in items.methods {
279 self.pymethod_def(method);
280 }
281 }
282 self
283 }
284
285 fn type_doc(mut self, type_doc: &'static CStr) -> Self {
286 let slice = type_doc.to_bytes();
287 if !slice.is_empty() {
288 unsafe { self.push_slot(ffi::Py_tp_doc, type_doc.as_ptr() as *mut c_char) }
289
290 // Running this causes PyPy to segfault.
291 #[cfg(all(not(PyPy), not(Py_LIMITED_API), not(Py_3_10)))]
292 {
293 // Until CPython 3.10, tp_doc was treated specially for
294 // heap-types, and it removed the text_signature value from it.
295 // We go in after the fact and replace tp_doc with something
296 // that _does_ include the text_signature value!
297 self.cleanup
298 .push(Box::new(move |_self, type_object| unsafe {
299 ffi::PyObject_Free((*type_object).tp_doc as _);
300 let data = ffi::PyMem_Malloc(slice.len());
301 data.copy_from(slice.as_ptr() as _, slice.len());
302 (*type_object).tp_doc = data as _;
303 }))
304 }
305 }
306 self
307 }
308
309 fn offsets(
310 mut self,
311 dict_offset: Option<ffi::Py_ssize_t>,
312 #[allow(unused_variables)] weaklist_offset: Option<ffi::Py_ssize_t>,
313 ) -> Self {
314 self.has_dict = dict_offset.is_some();
315
316 #[cfg(Py_3_9)]
317 {
318 #[inline(always)]
319 fn offset_def(
320 name: &'static str,
321 offset: ffi::Py_ssize_t,
322 ) -> ffi::structmember::PyMemberDef {
323 ffi::structmember::PyMemberDef {
324 name: name.as_ptr() as _,
325 type_code: ffi::structmember::T_PYSSIZET,
326 offset,
327 flags: ffi::structmember::READONLY,
328 doc: std::ptr::null_mut(),
329 }
330 }
331
332 let mut members = Vec::new();
333
334 // __dict__ support
335 if let Some(dict_offset) = dict_offset {
336 members.push(offset_def("__dictoffset__\0", dict_offset));
337 }
338
339 // weakref support
340 if let Some(weaklist_offset) = weaklist_offset {
341 members.push(offset_def("__weaklistoffset__\0", weaklist_offset));
342 }
343
344 // Safety: Py_tp_members expects a raw vec of PyMemberDef
345 unsafe { self.push_raw_vec_slot(ffi::Py_tp_members, members) };
346 }
347
348 // Setting buffer protocols, tp_dictoffset and tp_weaklistoffset via slots doesn't work until
349 // Python 3.9, so on older versions we must manually fixup the type object.
350 #[cfg(all(not(Py_LIMITED_API), not(Py_3_9)))]
351 {
352 self.cleanup
353 .push(Box::new(move |builder, type_object| unsafe {
354 (*(*type_object).tp_as_buffer).bf_getbuffer = builder.buffer_procs.bf_getbuffer;
355 (*(*type_object).tp_as_buffer).bf_releasebuffer =
356 builder.buffer_procs.bf_releasebuffer;
357
358 if let Some(dict_offset) = dict_offset {
359 (*type_object).tp_dictoffset = dict_offset;
360 }
361
362 if let Some(weaklist_offset) = weaklist_offset {
363 (*type_object).tp_weaklistoffset = weaklist_offset;
364 }
365 }));
366 }
367 self
368 }
369
370 fn build(
371 mut self,
372 py: Python<'_>,
373 name: &'static str,
374 module_name: Option<&'static str>,
375 basicsize: usize,
376 ) -> PyResult<PyClassTypeObject> {
377 // `c_ulong` and `c_uint` have the same size
378 // on some platforms (like windows)
379 #![allow(clippy::useless_conversion)]
380
381 let getset_destructors = self.finalize_methods_and_properties()?;
382
383 unsafe { self.push_slot(ffi::Py_tp_base, self.tp_base) }
384
385 if !self.has_new {
386 // Safety: This is the correct slot type for Py_tp_new
387 unsafe { self.push_slot(ffi::Py_tp_new, no_constructor_defined as *mut c_void) }
388 }
389
390 let tp_dealloc = if self.has_traverse || unsafe { PyType_IS_GC(self.tp_base) == 1 } {
391 self.tp_dealloc_with_gc
392 } else {
393 self.tp_dealloc
394 };
395 unsafe { self.push_slot(ffi::Py_tp_dealloc, tp_dealloc as *mut c_void) }
396
397 if self.has_clear && !self.has_traverse {
398 return Err(PyTypeError::new_err(format!(
399 "`#[pyclass]` {} implements __clear__ without __traverse__",
400 name
401 )));
402 }
403
404 // For sequences, implement sq_length instead of mp_length
405 if self.is_sequence {
406 for slot in &mut self.slots {
407 if slot.slot == ffi::Py_mp_length {
408 slot.slot = ffi::Py_sq_length;
409 }
410 }
411 }
412
413 // Add empty sentinel at the end
414 // Safety: python expects this empty slot
415 unsafe { self.push_slot(0, ptr::null_mut::<c_void>()) }
416
417 let class_name = py_class_qualified_name(module_name, name)?;
418 let mut spec = ffi::PyType_Spec {
419 name: class_name.as_ptr() as _,
420 basicsize: basicsize as c_int,
421 itemsize: 0,
422
423 flags: (ffi::Py_TPFLAGS_DEFAULT | self.class_flags)
424 .try_into()
425 .unwrap(),
426 slots: self.slots.as_mut_ptr(),
427 };
428
429 // Safety: We've correctly setup the PyType_Spec at this point
430 let type_object: Py<PyType> =
431 unsafe { Py::from_owned_ptr_or_err(py, ffi::PyType_FromSpec(&mut spec))? };
432
433 #[cfg(not(Py_3_11))]
434 bpo_45315_workaround(py, class_name);
435
436 for cleanup in std::mem::take(&mut self.cleanup) {
437 cleanup(&self, type_object.as_ref(py).as_type_ptr());
438 }
439
440 Ok(PyClassTypeObject {
441 type_object,
442 getset_destructors,
443 })
444 }
445}
446
447fn py_class_qualified_name(module_name: Option<&str>, class_name: &str) -> PyResult<CString> {
448 Ok(CString::new(format!(
449 "{}.{}",
450 module_name.unwrap_or("builtins"),
451 class_name
452 ))?)
453}
454
455/// Workaround for Python issue 45315; no longer necessary in Python 3.11
456#[inline]
457#[cfg(not(Py_3_11))]
458fn bpo_45315_workaround(py: Python<'_>, class_name: CString) {
459 #[cfg(Py_LIMITED_API)]
460 {
461 // Must check version at runtime for abi3 wheels - they could run against a higher version
462 // than the build config suggests.
463 use crate::sync::GILOnceCell;
464 static IS_PYTHON_3_11: GILOnceCell<bool> = GILOnceCell::new();
465
466 if *IS_PYTHON_3_11.get_or_init(py, || py.version_info() >= (3, 11)) {
467 // No fix needed - the wheel is running on a sufficiently new interpreter.
468 return;
469 }
470 }
471 #[cfg(not(Py_LIMITED_API))]
472 {
473 // suppress unused variable warning
474 let _ = py;
475 }
476
477 std::mem::forget(class_name);
478}
479
480/// Default new implementation
481unsafe extern "C" fn no_constructor_defined(
482 _subtype: *mut ffi::PyTypeObject,
483 _args: *mut ffi::PyObject,
484 _kwds: *mut ffi::PyObject,
485) -> *mut ffi::PyObject {
486 trampoline(|_| {
487 Err(crate::exceptions::PyTypeError::new_err(
488 args:"No constructor defined",
489 ))
490 })
491}
492
493#[derive(Default)]
494struct GetSetDefBuilder {
495 doc: Option<&'static str>,
496 getter: Option<Getter>,
497 setter: Option<Setter>,
498}
499
500impl GetSetDefBuilder {
501 fn add_getter(&mut self, getter: &PyGetterDef) {
502 // TODO: be smarter about merging getter and setter docs
503 if self.doc.is_none() {
504 self.doc = Some(getter.doc);
505 }
506 // TODO: return an error if getter already defined?
507 self.getter = Some(getter.meth.0)
508 }
509
510 fn add_setter(&mut self, setter: &PySetterDef) {
511 // TODO: be smarter about merging getter and setter docs
512 if self.doc.is_none() {
513 self.doc = Some(setter.doc);
514 }
515 // TODO: return an error if setter already defined?
516 self.setter = Some(setter.meth.0)
517 }
518
519 fn as_get_set_def(
520 &self,
521 name: &'static str,
522 ) -> PyResult<(ffi::PyGetSetDef, GetSetDefDestructor)> {
523 let name = get_name(name)?;
524 let doc = self.doc.map(get_doc).transpose()?;
525
526 let getset_type = match (self.getter, self.setter) {
527 (Some(getter), None) => GetSetDefType::Getter(getter),
528 (None, Some(setter)) => GetSetDefType::Setter(setter),
529 (Some(getter), Some(setter)) => {
530 GetSetDefType::GetterAndSetter(Box::new(GetterAndSetter { getter, setter }))
531 }
532 (None, None) => {
533 unreachable!("GetSetDefBuilder expected to always have either getter or setter")
534 }
535 };
536
537 let getset_def = getset_type.create_py_get_set_def(&name, doc.as_deref());
538 let destructor = GetSetDefDestructor {
539 name,
540 doc,
541 closure: getset_type,
542 };
543 Ok((getset_def, destructor))
544 }
545}
546
547#[allow(dead_code)] // a stack of fields which are purely to cache until dropped
548struct GetSetDefDestructor {
549 name: Cow<'static, CStr>,
550 doc: Option<Cow<'static, CStr>>,
551 closure: GetSetDefType,
552}
553
554/// Possible forms of property - either a getter, setter, or both
555enum GetSetDefType {
556 Getter(Getter),
557 Setter(Setter),
558 // The box is here so that the `GetterAndSetter` has a stable
559 // memory address even if the `GetSetDefType` enum is moved
560 GetterAndSetter(Box<GetterAndSetter>),
561}
562
563pub(crate) struct GetterAndSetter {
564 getter: Getter,
565 setter: Setter,
566}
567
568impl GetSetDefType {
569 /// Fills a PyGetSetDef structure
570 /// It is only valid for as long as this GetSetDefType remains alive,
571 /// as well as name and doc members
572 pub(crate) fn create_py_get_set_def(
573 &self,
574 name: &CStr,
575 doc: Option<&CStr>,
576 ) -> ffi::PyGetSetDef {
577 let (get, set, closure): (Option<ffi::getter>, Option<ffi::setter>, *mut c_void) =
578 match self {
579 &Self::Getter(closure) => {
580 unsafe extern "C" fn getter(
581 slf: *mut ffi::PyObject,
582 closure: *mut c_void,
583 ) -> *mut ffi::PyObject {
584 // Safety: PyO3 sets the closure when constructing the ffi getter so this cast should always be valid
585 let getter: Getter = std::mem::transmute(closure);
586 trampoline(|py| getter(py, slf))
587 }
588 (Some(getter), None, closure as Getter as _)
589 }
590 &Self::Setter(closure) => {
591 unsafe extern "C" fn setter(
592 slf: *mut ffi::PyObject,
593 value: *mut ffi::PyObject,
594 closure: *mut c_void,
595 ) -> c_int {
596 // Safety: PyO3 sets the closure when constructing the ffi setter so this cast should always be valid
597 let setter: Setter = std::mem::transmute(closure);
598 trampoline(|py| setter(py, slf, value))
599 }
600 (None, Some(setter), closure as Setter as _)
601 }
602 Self::GetterAndSetter(closure) => {
603 unsafe extern "C" fn getset_getter(
604 slf: *mut ffi::PyObject,
605 closure: *mut c_void,
606 ) -> *mut ffi::PyObject {
607 let getset: &GetterAndSetter = &*(closure as *const GetterAndSetter);
608 trampoline(|py| (getset.getter)(py, slf))
609 }
610
611 unsafe extern "C" fn getset_setter(
612 slf: *mut ffi::PyObject,
613 value: *mut ffi::PyObject,
614 closure: *mut c_void,
615 ) -> c_int {
616 let getset: &GetterAndSetter = &*(closure as *const GetterAndSetter);
617 trampoline(|py| (getset.setter)(py, slf, value))
618 }
619 (
620 Some(getset_getter),
621 Some(getset_setter),
622 closure.as_ref() as *const GetterAndSetter as _,
623 )
624 }
625 };
626 ffi::PyGetSetDef {
627 name: name.as_ptr(),
628 doc: doc.map_or(ptr::null(), CStr::as_ptr),
629 get,
630 set,
631 closure,
632 }
633 }
634}
635

Provided by KDAB

Privacy Policy