|
|
|||
File indexing completed on 2026-11-07 09:44:17
0001 // This file is part of the ACTS project. 0002 // 0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project 0004 // 0005 // This Source Code Form is subject to the terms of the Mozilla Public 0006 // License, v. 2.0. If a copy of the MPL was not distributed with this 0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/. 0008 0009 #pragma once 0010 0011 #include <cstddef> 0012 #include <functional> 0013 0014 #include <pybind11/pybind11.h> 0015 0016 namespace ActsPython { 0017 0018 /// Several EventData proxies/iterators hold a raw `Container*` + index and are 0019 /// handed to Python. When the backing container is transferred to the 0020 /// whiteboard, pybind11's smart_holder disowns the Python wrapper and the C++ 0021 /// object is moved out and freed, leaving the proxy dangling (a later access is 0022 /// a use-after-free -> SIGSEGV). 0023 /// 0024 /// `keep_alive` cannot help: it tracks the Python object's lifetime, but after 0025 /// the transfer the wrapper is a live-but-disowned shell. The data genuinely 0026 /// leaves, so the correct behaviour is to fail loud. pybind11 already maintains 0027 /// the authoritative "is it still owned" signal (set when the transfer does 0028 /// `py::cast<std::unique_ptr<T>>`), and we consult it by tethering each 0029 /// proxy/iterator to its parent container's `py::object` and re-validating on 0030 /// access. Holding the `py::object` also keeps the container alive while the 0031 /// proxy lives, which subsumes `keep_alive` for the garbage-collection path. 0032 0033 /// Return whether `owner`'s wrapped C++ container is still owned (not disowned 0034 /// by a whiteboard transfer). Implemented by attempting to re-acquire the C++ 0035 /// reference: this throws iff the instance was disowned. Any pending Python 0036 /// error from the failed cast is cleared so the caller can raise a clean one. 0037 template <typename Container> 0038 bool ownerAlive(const pybind11::object& owner) { 0039 try { 0040 owner.cast<const Container&>(); 0041 return true; 0042 } catch (...) { 0043 PyErr_Clear(); 0044 return false; 0045 } 0046 } 0047 0048 /// Tethers a C++ proxy to its parent container's Python object. The disown 0049 /// check is type-erased via a function pointer (`&ownerAlive<Container>`) 0050 /// rather than a Container template parameter, so a single bound proxy type can 0051 /// be produced by several different container types (e.g. a measurement proxy 0052 /// returned by both MeasurementContainer and MeasurementSubset). 0053 template <typename Proxy, typename Owner = void> 0054 struct ProxyTether { 0055 using AliveFn = bool (*)(const pybind11::object&); 0056 0057 pybind11::object owner; 0058 Proxy proxy; 0059 AliveFn aliveFn; 0060 0061 /// Single-owner constructor: alive check is derived from Owner at compile 0062 /// time. Use when there is exactly one container type that can own the proxy. 0063 ProxyTether(pybind11::object owner_, Proxy proxy_) 0064 requires(!std::is_void_v<Owner>) 0065 : owner{std::move(owner_)}, 0066 proxy{std::move(proxy_)}, 0067 aliveFn{&ownerAlive<Owner>} {} 0068 0069 /// Multi-owner constructor: alive check is supplied explicitly. Use when the 0070 /// same bound proxy type can be produced by more than one container type 0071 /// (e.g. MeasurementContainer and MeasurementSubset both yield the same 0072 /// ConstVariableBoundMeasurementProxy). 0073 ProxyTether(pybind11::object owner_, Proxy proxy_, AliveFn aliveFn_) 0074 : owner{std::move(owner_)}, proxy{std::move(proxy_)}, aliveFn{aliveFn_} {} 0075 0076 /// Validate the container is still owned, then return the proxy. Throws 0077 /// pybind11::value_error (a Python ValueError) if it was consumed. 0078 const Proxy& checked() const { 0079 validate(); 0080 return proxy; 0081 } 0082 Proxy& checked() { 0083 validate(); 0084 return proxy; 0085 } 0086 0087 private: 0088 void validate() const { 0089 if (!aliveFn(owner)) { 0090 throw pybind11::value_error( 0091 "proxy is no longer valid: its container was consumed (moved to the " 0092 "whiteboard)"); 0093 } 0094 } 0095 }; 0096 0097 /// Build a read accessor for a tethered proxy: validate the tether, then call 0098 /// `access` with the inner proxy. `Tether` is given explicitly at the call 0099 /// site. 0100 template <typename Tether, typename Access> 0101 auto tetheredRead(Access access) { 0102 return [access = std::move(access)](const Tether& self) { 0103 return access(self.checked()); 0104 }; 0105 } 0106 0107 /// Build a write accessor for a tethered proxy. `V` is the concrete value type, 0108 /// given explicitly at the call site so pybind11 sees a concrete setter 0109 /// signature (never `auto`). 0110 template <typename Tether, typename V, typename Assign> 0111 auto tetheredWrite(Assign assign) { 0112 return [assign = std::move(assign)](Tether& self, const V& value) { 0113 assign(self.checked(), value); 0114 }; 0115 } 0116 0117 /// A Python iterator that gates an underlying pybind11 iterator on the owner 0118 /// still being alive. Used for containers whose iteration yields values/pairs 0119 /// whose Python conversion is still registered (e.g. the flat multimaps). 0120 template <typename Container> 0121 struct IteratorTether { 0122 pybind11::object owner; 0123 pybind11::iterator inner; 0124 0125 pybind11::object next() { 0126 if (!ownerAlive<Container>(owner)) { 0127 throw pybind11::value_error( 0128 "iterator is no longer valid: its container was consumed (moved to " 0129 "the whiteboard)"); 0130 } 0131 return inner.attr("__next__")(); 0132 } 0133 }; 0134 0135 /// Register a IteratorTether type. MUST be called before any container's 0136 /// `__iter__` that returns it, or pybind11 raises an unregistered-type error. 0137 template <typename Container> 0138 void bindIteratorTether(pybind11::module_& m, const char* name) { 0139 pybind11::class_<IteratorTether<Container>>(m, name) 0140 .def("__iter__", [](pybind11::object self) { return self; }) 0141 .def("__next__", &IteratorTether<Container>::next); 0142 } 0143 0144 /// A Python iterator over an index-accessible container that yields freshly 0145 /// tethered proxies. Used for containers whose proxy type is itself bound as a 0146 /// ProxyTether (so a plain make_iterator over raw proxies would hit an 0147 /// unregistered type). `maker(owner, container, index)` builds the tethered 0148 /// proxy for each element. 0149 template <typename Container> 0150 struct IndexIteratorTether { 0151 using Maker = std::function<pybind11::object(const pybind11::object&, 0152 Container&, std::size_t)>; 0153 pybind11::object owner; 0154 std::size_t index{0}; 0155 Maker maker; 0156 0157 pybind11::object next() { 0158 Container* container = nullptr; 0159 try { 0160 container = &owner.cast<Container&>(); 0161 } catch (...) { 0162 PyErr_Clear(); 0163 throw pybind11::value_error( 0164 "iterator is no longer valid: its container was consumed (moved to " 0165 "the whiteboard)"); 0166 } 0167 if (index >= container->size()) { 0168 throw pybind11::stop_iteration(); 0169 } 0170 return maker(owner, *container, index++); 0171 } 0172 }; 0173 0174 /// Register a IndexIteratorTether type. MUST be called before any container's 0175 /// `__iter__` that returns it. 0176 template <typename Container> 0177 void bindIndexIteratorTether(pybind11::module_& m, const char* name) { 0178 pybind11::class_<IndexIteratorTether<Container>>(m, name) 0179 .def("__iter__", [](pybind11::object self) { return self; }) 0180 .def("__next__", &IndexIteratorTether<Container>::next); 0181 } 0182 0183 } // namespace ActsPython
| [ Source navigation ] | [ Diff markup ] | [ Identifier search ] | [ general search ] |
|
This page was automatically generated by the 2.3.7 LXR engine. The LXR team |
|