• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #pragma once
2 /*
3     tests/constructor_stats.h -- framework for printing and tracking object
4     instance lifetimes in example/test code.
5 
6     Copyright (c) 2016 Jason Rhinelander <jason@imaginary.ca>
7 
8     All rights reserved. Use of this source code is governed by a
9     BSD-style license that can be found in the LICENSE file.
10 
11 This header provides a few useful tools for writing examples or tests that want to check and/or
12 display object instance lifetimes.  It requires that you include this header and add the following
13 function calls to constructors:
14 
15     class MyClass {
16         MyClass() { ...; print_default_created(this); }
17         ~MyClass() { ...; print_destroyed(this); }
18         MyClass(const MyClass &c) { ...; print_copy_created(this); }
19         MyClass(MyClass &&c) { ...; print_move_created(this); }
20         MyClass(int a, int b) { ...; print_created(this, a, b); }
21         MyClass &operator=(const MyClass &c) { ...; print_copy_assigned(this); }
22         MyClass &operator=(MyClass &&c) { ...; print_move_assigned(this); }
23 
24         ...
25     }
26 
27 You can find various examples of these in several of the existing testing .cpp files.  (Of course
28 you don't need to add any of the above constructors/operators that you don't actually have, except
29 for the destructor).
30 
31 Each of these will print an appropriate message such as:
32 
33     ### MyClass @ 0x2801910 created via default constructor
34     ### MyClass @ 0x27fa780 created 100 200
35     ### MyClass @ 0x2801910 destroyed
36     ### MyClass @ 0x27fa780 destroyed
37 
38 You can also include extra arguments (such as the 100, 200 in the output above, coming from the
39 value constructor) for all of the above methods which will be included in the output.
40 
41 For testing, each of these also keeps track the created instances and allows you to check how many
42 of the various constructors have been invoked from the Python side via code such as:
43 
44     from pybind11_tests import ConstructorStats
45     cstats = ConstructorStats.get(MyClass)
46     print(cstats.alive())
47     print(cstats.default_constructions)
48 
49 Note that `.alive()` should usually be the first thing you call as it invokes Python's garbage
50 collector to actually destroy objects that aren't yet referenced.
51 
52 For everything except copy and move constructors and destructors, any extra values given to the
53 print_...() function is stored in a class-specific values list which you can retrieve and inspect
54 from the ConstructorStats instance `.values()` method.
55 
56 In some cases, when you need to track instances of a C++ class not registered with pybind11, you
57 need to add a function returning the ConstructorStats for the C++ class; this can be done with:
58 
59     m.def("get_special_cstats", &ConstructorStats::get<SpecialClass>, py::return_value_policy::reference)
60 
61 Finally, you can suppress the output messages, but keep the constructor tracking (for
62 inspection/testing in python) by using the functions with `print_` replaced with `track_` (e.g.
63 `track_copy_created(this)`).
64 
65 */
66 
67 #include "pybind11_tests.h"
68 #include <unordered_map>
69 #include <list>
70 #include <typeindex>
71 #include <sstream>
72 
73 class ConstructorStats {
74 protected:
75     std::unordered_map<void*, int> _instances; // Need a map rather than set because members can shared address with parents
76     std::list<std::string> _values; // Used to track values (e.g. of value constructors)
77 public:
78     int default_constructions = 0;
79     int copy_constructions = 0;
80     int move_constructions = 0;
81     int copy_assignments = 0;
82     int move_assignments = 0;
83 
copy_created(void * inst)84     void copy_created(void *inst) {
85         created(inst);
86         copy_constructions++;
87     }
88 
move_created(void * inst)89     void move_created(void *inst) {
90         created(inst);
91         move_constructions++;
92     }
93 
default_created(void * inst)94     void default_created(void *inst) {
95         created(inst);
96         default_constructions++;
97     }
98 
created(void * inst)99     void created(void *inst) {
100         ++_instances[inst];
101     }
102 
destroyed(void * inst)103     void destroyed(void *inst) {
104         if (--_instances[inst] < 0)
105             throw std::runtime_error("cstats.destroyed() called with unknown "
106                                      "instance; potential double-destruction "
107                                      "or a missing cstats.created()");
108     }
109 
gc()110     static void gc() {
111         // Force garbage collection to ensure any pending destructors are invoked:
112 #if defined(PYPY_VERSION)
113         PyObject *globals = PyEval_GetGlobals();
114         PyObject *result = PyRun_String(
115             "import gc\n"
116             "for i in range(2):"
117             "    gc.collect()\n",
118             Py_file_input, globals, globals);
119         if (result == nullptr)
120             throw py::error_already_set();
121         Py_DECREF(result);
122 #else
123         py::module_::import("gc").attr("collect")();
124 #endif
125     }
126 
alive()127     int alive() {
128         gc();
129         int total = 0;
130         for (const auto &p : _instances)
131             if (p.second > 0)
132                 total += p.second;
133         return total;
134     }
135 
value()136     void value() {} // Recursion terminator
137     // Takes one or more values, converts them to strings, then stores them.
value(const T & v,Tmore &&...args)138     template <typename T, typename... Tmore> void value(const T &v, Tmore &&...args) {
139         std::ostringstream oss;
140         oss << v;
141         _values.push_back(oss.str());
142         value(std::forward<Tmore>(args)...);
143     }
144 
145     // Move out stored values
values()146     py::list values() {
147         py::list l;
148         for (const auto &v : _values) l.append(py::cast(v));
149         _values.clear();
150         return l;
151     }
152 
153     // Gets constructor stats from a C++ type index
get(std::type_index type)154     static ConstructorStats& get(std::type_index type) {
155         static std::unordered_map<std::type_index, ConstructorStats> all_cstats;
156         return all_cstats[type];
157     }
158 
159     // Gets constructor stats from a C++ type
get()160     template <typename T> static ConstructorStats& get() {
161 #if defined(PYPY_VERSION)
162         gc();
163 #endif
164         return get(typeid(T));
165     }
166 
167     // Gets constructor stats from a Python class
get(py::object class_)168     static ConstructorStats& get(py::object class_) {
169         auto &internals = py::detail::get_internals();
170         const std::type_index *t1 = nullptr, *t2 = nullptr;
171         try {
172             auto *type_info = internals.registered_types_py.at((PyTypeObject *) class_.ptr()).at(0);
173             for (auto &p : internals.registered_types_cpp) {
174                 if (p.second == type_info) {
175                     if (t1) {
176                         t2 = &p.first;
177                         break;
178                     }
179                     t1 = &p.first;
180                 }
181             }
182         }
183         catch (const std::out_of_range&) {}
184         if (!t1) throw std::runtime_error("Unknown class passed to ConstructorStats::get()");
185         auto &cs1 = get(*t1);
186         // If we have both a t1 and t2 match, one is probably the trampoline class; return whichever
187         // has more constructions (typically one or the other will be 0)
188         if (t2) {
189             auto &cs2 = get(*t2);
190             int cs1_total = cs1.default_constructions + cs1.copy_constructions + cs1.move_constructions + (int) cs1._values.size();
191             int cs2_total = cs2.default_constructions + cs2.copy_constructions + cs2.move_constructions + (int) cs2._values.size();
192             if (cs2_total > cs1_total) return cs2;
193         }
194         return cs1;
195     }
196 };
197 
198 // To track construction/destruction, you need to call these methods from the various
199 // constructors/operators.  The ones that take extra values record the given values in the
200 // constructor stats values for later inspection.
track_copy_created(T * inst)201 template <class T> void track_copy_created(T *inst) { ConstructorStats::get<T>().copy_created(inst); }
track_move_created(T * inst)202 template <class T> void track_move_created(T *inst) { ConstructorStats::get<T>().move_created(inst); }
track_copy_assigned(T *,Values &&...values)203 template <class T, typename... Values> void track_copy_assigned(T *, Values &&...values) {
204     auto &cst = ConstructorStats::get<T>();
205     cst.copy_assignments++;
206     cst.value(std::forward<Values>(values)...);
207 }
track_move_assigned(T *,Values &&...values)208 template <class T, typename... Values> void track_move_assigned(T *, Values &&...values) {
209     auto &cst = ConstructorStats::get<T>();
210     cst.move_assignments++;
211     cst.value(std::forward<Values>(values)...);
212 }
track_default_created(T * inst,Values &&...values)213 template <class T, typename... Values> void track_default_created(T *inst, Values &&...values) {
214     auto &cst = ConstructorStats::get<T>();
215     cst.default_created(inst);
216     cst.value(std::forward<Values>(values)...);
217 }
track_created(T * inst,Values &&...values)218 template <class T, typename... Values> void track_created(T *inst, Values &&...values) {
219     auto &cst = ConstructorStats::get<T>();
220     cst.created(inst);
221     cst.value(std::forward<Values>(values)...);
222 }
track_destroyed(T * inst)223 template <class T, typename... Values> void track_destroyed(T *inst) {
224     ConstructorStats::get<T>().destroyed(inst);
225 }
track_values(T *,Values &&...values)226 template <class T, typename... Values> void track_values(T *, Values &&...values) {
227     ConstructorStats::get<T>().value(std::forward<Values>(values)...);
228 }
229 
230 /// Don't cast pointers to Python, print them as strings
format_ptrs(const char * p)231 inline const char *format_ptrs(const char *p) { return p; }
232 template <typename T>
format_ptrs(T * p)233 py::str format_ptrs(T *p) { return "{:#x}"_s.format(reinterpret_cast<std::uintptr_t>(p)); }
234 template <typename T>
235 auto format_ptrs(T &&x) -> decltype(std::forward<T>(x)) { return std::forward<T>(x); }
236 
237 template <class T, typename... Output>
print_constr_details(T * inst,const std::string & action,Output &&...output)238 void print_constr_details(T *inst, const std::string &action, Output &&...output) {
239     py::print("###", py::type_id<T>(), "@", format_ptrs(inst), action,
240               format_ptrs(std::forward<Output>(output))...);
241 }
242 
243 // Verbose versions of the above:
print_copy_created(T * inst,Values &&...values)244 template <class T, typename... Values> void print_copy_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values
245     print_constr_details(inst, "created via copy constructor", values...);
246     track_copy_created(inst);
247 }
print_move_created(T * inst,Values &&...values)248 template <class T, typename... Values> void print_move_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values
249     print_constr_details(inst, "created via move constructor", values...);
250     track_move_created(inst);
251 }
print_copy_assigned(T * inst,Values &&...values)252 template <class T, typename... Values> void print_copy_assigned(T *inst, Values &&...values) {
253     print_constr_details(inst, "assigned via copy assignment", values...);
254     track_copy_assigned(inst, values...);
255 }
print_move_assigned(T * inst,Values &&...values)256 template <class T, typename... Values> void print_move_assigned(T *inst, Values &&...values) {
257     print_constr_details(inst, "assigned via move assignment", values...);
258     track_move_assigned(inst, values...);
259 }
print_default_created(T * inst,Values &&...values)260 template <class T, typename... Values> void print_default_created(T *inst, Values &&...values) {
261     print_constr_details(inst, "created via default constructor", values...);
262     track_default_created(inst, values...);
263 }
print_created(T * inst,Values &&...values)264 template <class T, typename... Values> void print_created(T *inst, Values &&...values) {
265     print_constr_details(inst, "created", values...);
266     track_created(inst, values...);
267 }
print_destroyed(T * inst,Values &&...values)268 template <class T, typename... Values> void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values
269     print_constr_details(inst, "destroyed", values...);
270     track_destroyed(inst);
271 }
print_values(T * inst,Values &&...values)272 template <class T, typename... Values> void print_values(T *inst, Values &&...values) {
273     print_constr_details(inst, ":", values...);
274     track_values(inst, values...);
275 }
276