From 59ed277879cf203c690de4dd5f731f0cb110636e Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sun, 14 Jul 2019 16:35:06 +0200 Subject: [PATCH 1/3] Enforce GC contract at compile time PyGCProtocol must be implemented for anything registered as tracked by the garbage collector. This modifies the `pyclass` macro to enforce this at compile time. --- pyo3-derive-backend/src/pyclass.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/pyo3-derive-backend/src/pyclass.rs b/pyo3-derive-backend/src/pyclass.rs index 0d9a334a574..5e7841e6045 100644 --- a/pyo3-derive-backend/src/pyclass.rs +++ b/pyo3-derive-backend/src/pyclass.rs @@ -296,12 +296,15 @@ fn impl_class( // insert space for weak ref let mut has_weakref = false; let mut has_dict = false; + let mut has_gc = false; for f in attr.flags.iter() { if let syn::Expr::Path(ref epath) = f { if epath.path == parse_quote! {pyo3::type_object::PY_TYPE_FLAG_WEAKREF} { has_weakref = true; } else if epath.path == parse_quote! {pyo3::type_object::PY_TYPE_FLAG_DICT} { has_dict = true; + } else if epath.path == parse_quote! {pyo3::type_object::PY_TYPE_FLAG_GC} { + has_gc = true; } } } @@ -321,6 +324,22 @@ fn impl_class( quote! { None } }; + // Enforce at compile time that PyGCProtocol is implemented + let gc_impl = if has_gc { + let closure_name = format!("__assertion_closure_{}", cls.to_string()); + let closure_token = syn::Ident::new(&closure_name, Span::call_site()); + quote! { + fn #closure_token() { + use pyo3::class; + + fn _assert_implements_protocol<'p, T: pyo3::class::PyGCProtocol<'p>>() {} + _assert_implements_protocol::<#cls>(); + } + } + } else { + quote! {} + }; + let inventory_impl = impl_inventory(&cls); let base = &attr.base; @@ -365,6 +384,9 @@ fn impl_class( #inventory_impl #extra + + #gc_impl + } } From ca07c48931d2937fddfe8d49ddb59b835dbc5eba Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sun, 14 Jul 2019 17:56:36 +0200 Subject: [PATCH 2/3] Add documentation for new PyGCProtocol behavior --- guide/src/class.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/guide/src/class.md b/guide/src/class.md index 90e0a3ff822..988ff314612 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -573,9 +573,16 @@ impl PyGCProtocol for ClassWithGCSupport { Special protocol trait implementations have to be annotated with the `#[pyproto]` attribute. -It is also possible to enable GC for custom class using the `gc` parameter of the `pyclass` attribute. +It is also possible to enable GC for custom classes using the `gc` parameter of the `pyclass` attribute. i.e. `#[pyclass(gc)]`. In that case instances of custom class participate in Python garbage -collection, and it is possible to track them with `gc` module methods. +collection, and it is possible to track them with `gc` module methods. When using the `gc` parameter, +it is *required* to implement the `PyGCProtocol` trait, failure to do so will result in an error +at compile time: + +```compile_fail +#[pyclass(gc)] +struct GCTracked {} // Fails because it does not implement PyGCProtocol +``` ### Iterator Types From e633ae25eb62893c2f4ef1e688cba3acbbd10fcb Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sun, 14 Jul 2019 18:00:16 +0200 Subject: [PATCH 3/3] Add changelog error for PR #532 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab58727a839..fafc604021c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed + * Implementing the Using the `gc` parameter for `pyclass` (e.g. `#[pyclass(gc)]`) without implementing the `class::PyGCProtocol` trait is now a compile-time error. Failing to implement this trait could lead to segfaults. [#532](https://github.com/PyO3/pyo3/pull/532) + ## [0.7.0] - 2018-05-26 ### Added