Back to home page

EIC code displayed by LXR

 
 

    


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