323 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
		
		
			
		
	
	
			323 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
|  | #pragma once
 | ||
|  | /*
 | ||
|  |     tests/constructor_stats.h -- framework for printing and tracking object | ||
|  |     instance lifetimes in example/test code. | ||
|  | 
 | ||
|  |     Copyright (c) 2016 Jason Rhinelander <jason@imaginary.ca> | ||
|  | 
 | ||
|  |     All rights reserved. Use of this source code is governed by a | ||
|  |     BSD-style license that can be found in the LICENSE file. | ||
|  | 
 | ||
|  | This header provides a few useful tools for writing examples or tests that want to check and/or | ||
|  | display object instance lifetimes.  It requires that you include this header and add the following | ||
|  | function calls to constructors: | ||
|  | 
 | ||
|  |     class MyClass { | ||
|  |         MyClass() { ...; print_default_created(this); } | ||
|  |         ~MyClass() { ...; print_destroyed(this); } | ||
|  |         MyClass(const MyClass &c) { ...; print_copy_created(this); } | ||
|  |         MyClass(MyClass &&c) { ...; print_move_created(this); } | ||
|  |         MyClass(int a, int b) { ...; print_created(this, a, b); } | ||
|  |         MyClass &operator=(const MyClass &c) { ...; print_copy_assigned(this); } | ||
|  |         MyClass &operator=(MyClass &&c) { ...; print_move_assigned(this); } | ||
|  | 
 | ||
|  |         ... | ||
|  |     } | ||
|  | 
 | ||
|  | You can find various examples of these in several of the existing testing .cpp files.  (Of course | ||
|  | you don't need to add any of the above constructors/operators that you don't actually have, except | ||
|  | for the destructor). | ||
|  | 
 | ||
|  | Each of these will print an appropriate message such as: | ||
|  | 
 | ||
|  |     ### MyClass @ 0x2801910 created via default constructor
 | ||
|  |     ### MyClass @ 0x27fa780 created 100 200
 | ||
|  |     ### MyClass @ 0x2801910 destroyed
 | ||
|  |     ### MyClass @ 0x27fa780 destroyed
 | ||
|  | 
 | ||
|  | You can also include extra arguments (such as the 100, 200 in the output above, coming from the | ||
|  | value constructor) for all of the above methods which will be included in the output. | ||
|  | 
 | ||
|  | For testing, each of these also keeps track the created instances and allows you to check how many | ||
|  | of the various constructors have been invoked from the Python side via code such as: | ||
|  | 
 | ||
|  |     from pybind11_tests import ConstructorStats | ||
|  |     cstats = ConstructorStats.get(MyClass) | ||
|  |     print(cstats.alive()) | ||
|  |     print(cstats.default_constructions) | ||
|  | 
 | ||
|  | Note that `.alive()` should usually be the first thing you call as it invokes Python's garbage | ||
|  | collector to actually destroy objects that aren't yet referenced. | ||
|  | 
 | ||
|  | For everything except copy and move constructors and destructors, any extra values given to the | ||
|  | print_...() function is stored in a class-specific values list which you can retrieve and inspect | ||
|  | from the ConstructorStats instance `.values()` method. | ||
|  | 
 | ||
|  | In some cases, when you need to track instances of a C++ class not registered with pybind11, you | ||
|  | need to add a function returning the ConstructorStats for the C++ class; this can be done with: | ||
|  | 
 | ||
|  |     m.def("get_special_cstats", &ConstructorStats::get<SpecialClass>, | ||
|  | py::return_value_policy::reference) | ||
|  | 
 | ||
|  | Finally, you can suppress the output messages, but keep the constructor tracking (for | ||
|  | inspection/testing in python) by using the functions with `print_` replaced with `track_` (e.g. | ||
|  | `track_copy_created(this)`). | ||
|  | 
 | ||
|  | */ | ||
|  | 
 | ||
|  | #include "pybind11_tests.h"
 | ||
|  | 
 | ||
|  | #include <list>
 | ||
|  | #include <sstream>
 | ||
|  | #include <typeindex>
 | ||
|  | #include <unordered_map>
 | ||
|  | 
 | ||
|  | class ConstructorStats { | ||
|  | protected: | ||
|  |     std::unordered_map<void *, int> _instances; // Need a map rather than set because members can
 | ||
|  |                                                 // shared address with parents
 | ||
|  |     std::list<std::string> _values;             // Used to track values
 | ||
|  |                                                 // (e.g. of value constructors)
 | ||
|  | public: | ||
|  |     int default_constructions = 0; | ||
|  |     int copy_constructions = 0; | ||
|  |     int move_constructions = 0; | ||
|  |     int copy_assignments = 0; | ||
|  |     int move_assignments = 0; | ||
|  | 
 | ||
|  |     void copy_created(void *inst) { | ||
|  |         created(inst); | ||
|  |         copy_constructions++; | ||
|  |     } | ||
|  | 
 | ||
|  |     void move_created(void *inst) { | ||
|  |         created(inst); | ||
|  |         move_constructions++; | ||
|  |     } | ||
|  | 
 | ||
|  |     void default_created(void *inst) { | ||
|  |         created(inst); | ||
|  |         default_constructions++; | ||
|  |     } | ||
|  | 
 | ||
|  |     void created(void *inst) { ++_instances[inst]; } | ||
|  | 
 | ||
|  |     void destroyed(void *inst) { | ||
|  |         if (--_instances[inst] < 0) { | ||
|  |             throw std::runtime_error("cstats.destroyed() called with unknown " | ||
|  |                                      "instance; potential double-destruction " | ||
|  |                                      "or a missing cstats.created()"); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     static void gc() { | ||
|  |         // Force garbage collection to ensure any pending destructors are invoked:
 | ||
|  | #if defined(PYPY_VERSION)
 | ||
|  |         PyObject *globals = PyEval_GetGlobals(); | ||
|  |         PyObject *result = PyRun_String("import gc\n" | ||
|  |                                         "for i in range(2):\n" | ||
|  |                                         "    gc.collect()\n", | ||
|  |                                         Py_file_input, | ||
|  |                                         globals, | ||
|  |                                         globals); | ||
|  |         if (result == nullptr) | ||
|  |             throw py::error_already_set(); | ||
|  |         Py_DECREF(result); | ||
|  | #else
 | ||
|  |         py::module_::import("gc").attr("collect")(); | ||
|  | #endif
 | ||
|  |     } | ||
|  | 
 | ||
|  |     int alive() { | ||
|  |         gc(); | ||
|  |         int total = 0; | ||
|  |         for (const auto &p : _instances) { | ||
|  |             if (p.second > 0) { | ||
|  |                 total += p.second; | ||
|  |             } | ||
|  |         } | ||
|  |         return total; | ||
|  |     } | ||
|  | 
 | ||
|  |     void value() {} // Recursion terminator
 | ||
|  |     // Takes one or more values, converts them to strings, then stores them.
 | ||
|  |     template <typename T, typename... Tmore> | ||
|  |     void value(const T &v, Tmore &&...args) { | ||
|  |         std::ostringstream oss; | ||
|  |         oss << v; | ||
|  |         _values.push_back(oss.str()); | ||
|  |         value(std::forward<Tmore>(args)...); | ||
|  |     } | ||
|  | 
 | ||
|  |     // Move out stored values
 | ||
|  |     py::list values() { | ||
|  |         py::list l; | ||
|  |         for (const auto &v : _values) { | ||
|  |             l.append(py::cast(v)); | ||
|  |         } | ||
|  |         _values.clear(); | ||
|  |         return l; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Gets constructor stats from a C++ type index
 | ||
|  |     static ConstructorStats &get(std::type_index type) { | ||
|  |         static std::unordered_map<std::type_index, ConstructorStats> all_cstats; | ||
|  |         return all_cstats[type]; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Gets constructor stats from a C++ type
 | ||
|  |     template <typename T> | ||
|  |     static ConstructorStats &get() { | ||
|  | #if defined(PYPY_VERSION)
 | ||
|  |         gc(); | ||
|  | #endif
 | ||
|  |         return get(typeid(T)); | ||
|  |     } | ||
|  | 
 | ||
|  |     // Gets constructor stats from a Python class
 | ||
|  |     static ConstructorStats &get(py::object class_) { | ||
|  |         auto &internals = py::detail::get_internals(); | ||
|  |         const std::type_index *t1 = nullptr, *t2 = nullptr; | ||
|  |         try { | ||
|  |             auto *type_info | ||
|  |                 = internals.registered_types_py.at((PyTypeObject *) class_.ptr()).at(0); | ||
|  |             for (auto &p : internals.registered_types_cpp) { | ||
|  |                 if (p.second == type_info) { | ||
|  |                     if (t1) { | ||
|  |                         t2 = &p.first; | ||
|  |                         break; | ||
|  |                     } | ||
|  |                     t1 = &p.first; | ||
|  |                 } | ||
|  |             } | ||
|  |         } catch (const std::out_of_range &) { | ||
|  |         } | ||
|  |         if (!t1) { | ||
|  |             throw std::runtime_error("Unknown class passed to ConstructorStats::get()"); | ||
|  |         } | ||
|  |         auto &cs1 = get(*t1); | ||
|  |         // If we have both a t1 and t2 match, one is probably the trampoline class; return
 | ||
|  |         // whichever has more constructions (typically one or the other will be 0)
 | ||
|  |         if (t2) { | ||
|  |             auto &cs2 = get(*t2); | ||
|  |             int cs1_total = cs1.default_constructions + cs1.copy_constructions | ||
|  |                             + cs1.move_constructions + (int) cs1._values.size(); | ||
|  |             int cs2_total = cs2.default_constructions + cs2.copy_constructions | ||
|  |                             + cs2.move_constructions + (int) cs2._values.size(); | ||
|  |             if (cs2_total > cs1_total) { | ||
|  |                 return cs2; | ||
|  |             } | ||
|  |         } | ||
|  |         return cs1; | ||
|  |     } | ||
|  | }; | ||
|  | 
 | ||
|  | // To track construction/destruction, you need to call these methods from the various
 | ||
|  | // constructors/operators.  The ones that take extra values record the given values in the
 | ||
|  | // constructor stats values for later inspection.
 | ||
|  | template <class T> | ||
|  | void track_copy_created(T *inst) { | ||
|  |     ConstructorStats::get<T>().copy_created(inst); | ||
|  | } | ||
|  | template <class T> | ||
|  | void track_move_created(T *inst) { | ||
|  |     ConstructorStats::get<T>().move_created(inst); | ||
|  | } | ||
|  | template <class T, typename... Values> | ||
|  | void track_copy_assigned(T *, Values &&...values) { | ||
|  |     auto &cst = ConstructorStats::get<T>(); | ||
|  |     cst.copy_assignments++; | ||
|  |     cst.value(std::forward<Values>(values)...); | ||
|  | } | ||
|  | template <class T, typename... Values> | ||
|  | void track_move_assigned(T *, Values &&...values) { | ||
|  |     auto &cst = ConstructorStats::get<T>(); | ||
|  |     cst.move_assignments++; | ||
|  |     cst.value(std::forward<Values>(values)...); | ||
|  | } | ||
|  | template <class T, typename... Values> | ||
|  | void track_default_created(T *inst, Values &&...values) { | ||
|  |     auto &cst = ConstructorStats::get<T>(); | ||
|  |     cst.default_created(inst); | ||
|  |     cst.value(std::forward<Values>(values)...); | ||
|  | } | ||
|  | template <class T, typename... Values> | ||
|  | void track_created(T *inst, Values &&...values) { | ||
|  |     auto &cst = ConstructorStats::get<T>(); | ||
|  |     cst.created(inst); | ||
|  |     cst.value(std::forward<Values>(values)...); | ||
|  | } | ||
|  | template <class T, typename... Values> | ||
|  | void track_destroyed(T *inst) { | ||
|  |     ConstructorStats::get<T>().destroyed(inst); | ||
|  | } | ||
|  | template <class T, typename... Values> | ||
|  | void track_values(T *, Values &&...values) { | ||
|  |     ConstructorStats::get<T>().value(std::forward<Values>(values)...); | ||
|  | } | ||
|  | 
 | ||
|  | /// Don't cast pointers to Python, print them as strings
 | ||
|  | inline const char *format_ptrs(const char *p) { return p; } | ||
|  | template <typename T> | ||
|  | py::str format_ptrs(T *p) { | ||
|  |     return "{:#x}"_s.format(reinterpret_cast<std::uintptr_t>(p)); | ||
|  | } | ||
|  | template <typename T> | ||
|  | auto format_ptrs(T &&x) -> decltype(std::forward<T>(x)) { | ||
|  |     return std::forward<T>(x); | ||
|  | } | ||
|  | 
 | ||
|  | template <class T, typename... Output> | ||
|  | void print_constr_details(T *inst, const std::string &action, Output &&...output) { | ||
|  |     py::print("###", | ||
|  |               py::type_id<T>(), | ||
|  |               "@", | ||
|  |               format_ptrs(inst), | ||
|  |               action, | ||
|  |               format_ptrs(std::forward<Output>(output))...); | ||
|  | } | ||
|  | 
 | ||
|  | // Verbose versions of the above:
 | ||
|  | template <class T, typename... Values> | ||
|  | void print_copy_created(T *inst, | ||
|  |                         Values &&...values) { // NB: this prints, but doesn't store, given values
 | ||
|  |     print_constr_details(inst, "created via copy constructor", values...); | ||
|  |     track_copy_created(inst); | ||
|  | } | ||
|  | template <class T, typename... Values> | ||
|  | void print_move_created(T *inst, | ||
|  |                         Values &&...values) { // NB: this prints, but doesn't store, given values
 | ||
|  |     print_constr_details(inst, "created via move constructor", values...); | ||
|  |     track_move_created(inst); | ||
|  | } | ||
|  | template <class T, typename... Values> | ||
|  | void print_copy_assigned(T *inst, Values &&...values) { | ||
|  |     print_constr_details(inst, "assigned via copy assignment", values...); | ||
|  |     track_copy_assigned(inst, values...); | ||
|  | } | ||
|  | template <class T, typename... Values> | ||
|  | void print_move_assigned(T *inst, Values &&...values) { | ||
|  |     print_constr_details(inst, "assigned via move assignment", values...); | ||
|  |     track_move_assigned(inst, values...); | ||
|  | } | ||
|  | template <class T, typename... Values> | ||
|  | void print_default_created(T *inst, Values &&...values) { | ||
|  |     print_constr_details(inst, "created via default constructor", values...); | ||
|  |     track_default_created(inst, values...); | ||
|  | } | ||
|  | template <class T, typename... Values> | ||
|  | void print_created(T *inst, Values &&...values) { | ||
|  |     print_constr_details(inst, "created", values...); | ||
|  |     track_created(inst, values...); | ||
|  | } | ||
|  | template <class T, typename... Values> | ||
|  | void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values
 | ||
|  |     print_constr_details(inst, "destroyed", values...); | ||
|  |     track_destroyed(inst); | ||
|  | } | ||
|  | template <class T, typename... Values> | ||
|  | void print_values(T *inst, Values &&...values) { | ||
|  |     print_constr_details(inst, ":", values...); | ||
|  |     track_values(inst, values...); | ||
|  | } |