Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 10:17:51

0001 #pragma once
0002 /*
0003     tests/constructor_stats.h -- framework for printing and tracking object
0004     instance lifetimes in example/test code.
0005 
0006     Copyright (c) 2016 Jason Rhinelander <jason@imaginary.ca>
0007 
0008     All rights reserved. Use of this source code is governed by a
0009     BSD-style license that can be found in the LICENSE file.
0010 
0011 This header provides a few useful tools for writing examples or tests that want to check and/or
0012 display object instance lifetimes.  It requires that you include this header and add the following
0013 function calls to constructors:
0014 
0015     class MyClass {
0016         MyClass() { ...; print_default_created(this); }
0017         ~MyClass() { ...; print_destroyed(this); }
0018         MyClass(const MyClass &c) { ...; print_copy_created(this); }
0019         MyClass(MyClass &&c) { ...; print_move_created(this); }
0020         MyClass(int a, int b) { ...; print_created(this, a, b); }
0021         MyClass &operator=(const MyClass &c) { ...; print_copy_assigned(this); }
0022         MyClass &operator=(MyClass &&c) { ...; print_move_assigned(this); }
0023 
0024         ...
0025     }
0026 
0027 You can find various examples of these in several of the existing testing .cpp files.  (Of course
0028 you don't need to add any of the above constructors/operators that you don't actually have, except
0029 for the destructor).
0030 
0031 Each of these will print an appropriate message such as:
0032 
0033     ### MyClass @ 0x2801910 created via default constructor
0034     ### MyClass @ 0x27fa780 created 100 200
0035     ### MyClass @ 0x2801910 destroyed
0036     ### MyClass @ 0x27fa780 destroyed
0037 
0038 You can also include extra arguments (such as the 100, 200 in the output above, coming from the
0039 value constructor) for all of the above methods which will be included in the output.
0040 
0041 For testing, each of these also keeps track the created instances and allows you to check how many
0042 of the various constructors have been invoked from the Python side via code such as:
0043 
0044     from pybind11_tests import ConstructorStats
0045     cstats = ConstructorStats.get(MyClass)
0046     print(cstats.alive())
0047     print(cstats.default_constructions)
0048 
0049 Note that `.alive()` should usually be the first thing you call as it invokes Python's garbage
0050 collector to actually destroy objects that aren't yet referenced.
0051 
0052 For everything except copy and move constructors and destructors, any extra values given to the
0053 print_...() function is stored in a class-specific values list which you can retrieve and inspect
0054 from the ConstructorStats instance `.values()` method.
0055 
0056 In some cases, when you need to track instances of a C++ class not registered with pybind11, you
0057 need to add a function returning the ConstructorStats for the C++ class; this can be done with:
0058 
0059     m.def("get_special_cstats", &ConstructorStats::get<SpecialClass>,
0060 py::return_value_policy::reference)
0061 
0062 Finally, you can suppress the output messages, but keep the constructor tracking (for
0063 inspection/testing in python) by using the functions with `print_` replaced with `track_` (e.g.
0064 `track_copy_created(this)`).
0065 
0066 */
0067 
0068 #include "pybind11_tests.h"
0069 
0070 #include <list>
0071 #include <sstream>
0072 #include <typeindex>
0073 #include <unordered_map>
0074 
0075 class ConstructorStats {
0076 protected:
0077     std::unordered_map<void *, int> _instances; // Need a map rather than set because members can
0078                                                 // shared address with parents
0079     std::list<std::string> _values;             // Used to track values
0080                                                 // (e.g. of value constructors)
0081 public:
0082     int default_constructions = 0;
0083     int copy_constructions = 0;
0084     int move_constructions = 0;
0085     int copy_assignments = 0;
0086     int move_assignments = 0;
0087 
0088     void copy_created(void *inst) {
0089         created(inst);
0090         copy_constructions++;
0091     }
0092 
0093     void move_created(void *inst) {
0094         created(inst);
0095         move_constructions++;
0096     }
0097 
0098     void default_created(void *inst) {
0099         created(inst);
0100         default_constructions++;
0101     }
0102 
0103     void created(void *inst) { ++_instances[inst]; }
0104 
0105     void destroyed(void *inst) {
0106         if (--_instances[inst] < 0) {
0107             throw std::runtime_error("cstats.destroyed() called with unknown "
0108                                      "instance; potential double-destruction "
0109                                      "or a missing cstats.created()");
0110         }
0111     }
0112 
0113     static void gc() {
0114         // Force garbage collection to ensure any pending destructors are invoked:
0115 #if defined(PYPY_VERSION)
0116         PyObject *globals = PyEval_GetGlobals();
0117         PyObject *result = PyRun_String("import gc\n"
0118                                         "for i in range(2):\n"
0119                                         "    gc.collect()\n",
0120                                         Py_file_input,
0121                                         globals,
0122                                         globals);
0123         if (result == nullptr)
0124             throw py::error_already_set();
0125         Py_DECREF(result);
0126 #else
0127         py::module_::import("gc").attr("collect")();
0128 #endif
0129     }
0130 
0131     int alive() {
0132         gc();
0133         int total = 0;
0134         for (const auto &p : _instances) {
0135             if (p.second > 0) {
0136                 total += p.second;
0137             }
0138         }
0139         return total;
0140     }
0141 
0142     void value() {} // Recursion terminator
0143     // Takes one or more values, converts them to strings, then stores them.
0144     template <typename T, typename... Tmore>
0145     void value(const T &v, Tmore &&...args) {
0146         std::ostringstream oss;
0147         oss << v;
0148         _values.push_back(oss.str());
0149         value(std::forward<Tmore>(args)...);
0150     }
0151 
0152     // Move out stored values
0153     py::list values() {
0154         py::list l;
0155         for (const auto &v : _values) {
0156             l.append(py::cast(v));
0157         }
0158         _values.clear();
0159         return l;
0160     }
0161 
0162     // Gets constructor stats from a C++ type index
0163     static ConstructorStats &get(std::type_index type) {
0164         static std::unordered_map<std::type_index, ConstructorStats> all_cstats;
0165         return all_cstats[type];
0166     }
0167 
0168     // Gets constructor stats from a C++ type
0169     template <typename T>
0170     static ConstructorStats &get() {
0171 #if defined(PYPY_VERSION)
0172         gc();
0173 #endif
0174         return get(typeid(T));
0175     }
0176 
0177     // Gets constructor stats from a Python class
0178     static ConstructorStats &get(py::object class_) {
0179         auto &internals = py::detail::get_internals();
0180         const std::type_index *t1 = nullptr, *t2 = nullptr;
0181         try {
0182             auto *type_info
0183                 = internals.registered_types_py.at((PyTypeObject *) class_.ptr()).at(0);
0184             for (auto &p : internals.registered_types_cpp) {
0185                 if (p.second == type_info) {
0186                     if (t1) {
0187                         t2 = &p.first;
0188                         break;
0189                     }
0190                     t1 = &p.first;
0191                 }
0192             }
0193         } catch (const std::out_of_range &) {
0194         }
0195         if (!t1) {
0196             throw std::runtime_error("Unknown class passed to ConstructorStats::get()");
0197         }
0198         auto &cs1 = get(*t1);
0199         // If we have both a t1 and t2 match, one is probably the trampoline class; return
0200         // whichever has more constructions (typically one or the other will be 0)
0201         if (t2) {
0202             auto &cs2 = get(*t2);
0203             int cs1_total = cs1.default_constructions + cs1.copy_constructions
0204                             + cs1.move_constructions + (int) cs1._values.size();
0205             int cs2_total = cs2.default_constructions + cs2.copy_constructions
0206                             + cs2.move_constructions + (int) cs2._values.size();
0207             if (cs2_total > cs1_total) {
0208                 return cs2;
0209             }
0210         }
0211         return cs1;
0212     }
0213 };
0214 
0215 // To track construction/destruction, you need to call these methods from the various
0216 // constructors/operators.  The ones that take extra values record the given values in the
0217 // constructor stats values for later inspection.
0218 template <class T>
0219 void track_copy_created(T *inst) {
0220     ConstructorStats::get<T>().copy_created(inst);
0221 }
0222 template <class T>
0223 void track_move_created(T *inst) {
0224     ConstructorStats::get<T>().move_created(inst);
0225 }
0226 template <class T, typename... Values>
0227 void track_copy_assigned(T *, Values &&...values) {
0228     auto &cst = ConstructorStats::get<T>();
0229     cst.copy_assignments++;
0230     cst.value(std::forward<Values>(values)...);
0231 }
0232 template <class T, typename... Values>
0233 void track_move_assigned(T *, Values &&...values) {
0234     auto &cst = ConstructorStats::get<T>();
0235     cst.move_assignments++;
0236     cst.value(std::forward<Values>(values)...);
0237 }
0238 template <class T, typename... Values>
0239 void track_default_created(T *inst, Values &&...values) {
0240     auto &cst = ConstructorStats::get<T>();
0241     cst.default_created(inst);
0242     cst.value(std::forward<Values>(values)...);
0243 }
0244 template <class T, typename... Values>
0245 void track_created(T *inst, Values &&...values) {
0246     auto &cst = ConstructorStats::get<T>();
0247     cst.created(inst);
0248     cst.value(std::forward<Values>(values)...);
0249 }
0250 template <class T, typename... Values>
0251 void track_destroyed(T *inst) {
0252     ConstructorStats::get<T>().destroyed(inst);
0253 }
0254 template <class T, typename... Values>
0255 void track_values(T *, Values &&...values) {
0256     ConstructorStats::get<T>().value(std::forward<Values>(values)...);
0257 }
0258 
0259 /// Don't cast pointers to Python, print them as strings
0260 inline const char *format_ptrs(const char *p) { return p; }
0261 template <typename T>
0262 py::str format_ptrs(T *p) {
0263     return "{:#x}"_s.format(reinterpret_cast<std::uintptr_t>(p));
0264 }
0265 template <typename T>
0266 auto format_ptrs(T &&x) -> decltype(std::forward<T>(x)) {
0267     return std::forward<T>(x);
0268 }
0269 
0270 template <class T, typename... Output>
0271 void print_constr_details(T *inst, const std::string &action, Output &&...output) {
0272     py::print("###",
0273               py::type_id<T>(),
0274               "@",
0275               format_ptrs(inst),
0276               action,
0277               format_ptrs(std::forward<Output>(output))...);
0278 }
0279 
0280 // Verbose versions of the above:
0281 template <class T, typename... Values>
0282 void print_copy_created(T *inst,
0283                         Values &&...values) { // NB: this prints, but doesn't store, given values
0284     print_constr_details(inst, "created via copy constructor", values...);
0285     track_copy_created(inst);
0286 }
0287 template <class T, typename... Values>
0288 void print_move_created(T *inst,
0289                         Values &&...values) { // NB: this prints, but doesn't store, given values
0290     print_constr_details(inst, "created via move constructor", values...);
0291     track_move_created(inst);
0292 }
0293 template <class T, typename... Values>
0294 void print_copy_assigned(T *inst, Values &&...values) {
0295     print_constr_details(inst, "assigned via copy assignment", values...);
0296     track_copy_assigned(inst, values...);
0297 }
0298 template <class T, typename... Values>
0299 void print_move_assigned(T *inst, Values &&...values) {
0300     print_constr_details(inst, "assigned via move assignment", values...);
0301     track_move_assigned(inst, values...);
0302 }
0303 template <class T, typename... Values>
0304 void print_default_created(T *inst, Values &&...values) {
0305     print_constr_details(inst, "created via default constructor", values...);
0306     track_default_created(inst, values...);
0307 }
0308 template <class T, typename... Values>
0309 void print_created(T *inst, Values &&...values) {
0310     print_constr_details(inst, "created", values...);
0311     track_created(inst, values...);
0312 }
0313 template <class T, typename... Values>
0314 void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values
0315     print_constr_details(inst, "destroyed", values...);
0316     track_destroyed(inst);
0317 }
0318 template <class T, typename... Values>
0319 void print_values(T *inst, Values &&...values) {
0320     print_constr_details(inst, ":", values...);
0321     track_values(inst, values...);
0322 }