111986Sandreas.sandberg@arm.com#pragma once 211986Sandreas.sandberg@arm.com/* 311986Sandreas.sandberg@arm.com tests/constructor_stats.h -- framework for printing and tracking object 411986Sandreas.sandberg@arm.com instance lifetimes in example/test code. 511986Sandreas.sandberg@arm.com 611986Sandreas.sandberg@arm.com Copyright (c) 2016 Jason Rhinelander <jason@imaginary.ca> 711986Sandreas.sandberg@arm.com 811986Sandreas.sandberg@arm.com All rights reserved. Use of this source code is governed by a 911986Sandreas.sandberg@arm.com BSD-style license that can be found in the LICENSE file. 1011986Sandreas.sandberg@arm.com 1111986Sandreas.sandberg@arm.comThis header provides a few useful tools for writing examples or tests that want to check and/or 1211986Sandreas.sandberg@arm.comdisplay object instance lifetimes. It requires that you include this header and add the following 1311986Sandreas.sandberg@arm.comfunction calls to constructors: 1411986Sandreas.sandberg@arm.com 1511986Sandreas.sandberg@arm.com class MyClass { 1611986Sandreas.sandberg@arm.com MyClass() { ...; print_default_created(this); } 1711986Sandreas.sandberg@arm.com ~MyClass() { ...; print_destroyed(this); } 1811986Sandreas.sandberg@arm.com MyClass(const MyClass &c) { ...; print_copy_created(this); } 1911986Sandreas.sandberg@arm.com MyClass(MyClass &&c) { ...; print_move_created(this); } 2011986Sandreas.sandberg@arm.com MyClass(int a, int b) { ...; print_created(this, a, b); } 2111986Sandreas.sandberg@arm.com MyClass &operator=(const MyClass &c) { ...; print_copy_assigned(this); } 2211986Sandreas.sandberg@arm.com MyClass &operator=(MyClass &&c) { ...; print_move_assigned(this); } 2311986Sandreas.sandberg@arm.com 2411986Sandreas.sandberg@arm.com ... 2511986Sandreas.sandberg@arm.com } 2611986Sandreas.sandberg@arm.com 2712037Sandreas.sandberg@arm.comYou can find various examples of these in several of the existing testing .cpp files. (Of course 2811986Sandreas.sandberg@arm.comyou don't need to add any of the above constructors/operators that you don't actually have, except 2911986Sandreas.sandberg@arm.comfor the destructor). 3011986Sandreas.sandberg@arm.com 3111986Sandreas.sandberg@arm.comEach of these will print an appropriate message such as: 3211986Sandreas.sandberg@arm.com 3311986Sandreas.sandberg@arm.com ### MyClass @ 0x2801910 created via default constructor 3411986Sandreas.sandberg@arm.com ### MyClass @ 0x27fa780 created 100 200 3511986Sandreas.sandberg@arm.com ### MyClass @ 0x2801910 destroyed 3611986Sandreas.sandberg@arm.com ### MyClass @ 0x27fa780 destroyed 3711986Sandreas.sandberg@arm.com 3811986Sandreas.sandberg@arm.comYou can also include extra arguments (such as the 100, 200 in the output above, coming from the 3911986Sandreas.sandberg@arm.comvalue constructor) for all of the above methods which will be included in the output. 4011986Sandreas.sandberg@arm.com 4111986Sandreas.sandberg@arm.comFor testing, each of these also keeps track the created instances and allows you to check how many 4211986Sandreas.sandberg@arm.comof the various constructors have been invoked from the Python side via code such as: 4311986Sandreas.sandberg@arm.com 4412037Sandreas.sandberg@arm.com from pybind11_tests import ConstructorStats 4511986Sandreas.sandberg@arm.com cstats = ConstructorStats.get(MyClass) 4611986Sandreas.sandberg@arm.com print(cstats.alive()) 4711986Sandreas.sandberg@arm.com print(cstats.default_constructions) 4811986Sandreas.sandberg@arm.com 4911986Sandreas.sandberg@arm.comNote that `.alive()` should usually be the first thing you call as it invokes Python's garbage 5011986Sandreas.sandberg@arm.comcollector to actually destroy objects that aren't yet referenced. 5111986Sandreas.sandberg@arm.com 5211986Sandreas.sandberg@arm.comFor everything except copy and move constructors and destructors, any extra values given to the 5311986Sandreas.sandberg@arm.comprint_...() function is stored in a class-specific values list which you can retrieve and inspect 5411986Sandreas.sandberg@arm.comfrom the ConstructorStats instance `.values()` method. 5511986Sandreas.sandberg@arm.com 5611986Sandreas.sandberg@arm.comIn some cases, when you need to track instances of a C++ class not registered with pybind11, you 5711986Sandreas.sandberg@arm.comneed to add a function returning the ConstructorStats for the C++ class; this can be done with: 5811986Sandreas.sandberg@arm.com 5911986Sandreas.sandberg@arm.com m.def("get_special_cstats", &ConstructorStats::get<SpecialClass>, py::return_value_policy::reference) 6011986Sandreas.sandberg@arm.com 6111986Sandreas.sandberg@arm.comFinally, you can suppress the output messages, but keep the constructor tracking (for 6211986Sandreas.sandberg@arm.cominspection/testing in python) by using the functions with `print_` replaced with `track_` (e.g. 6311986Sandreas.sandberg@arm.com`track_copy_created(this)`). 6411986Sandreas.sandberg@arm.com 6511986Sandreas.sandberg@arm.com*/ 6611986Sandreas.sandberg@arm.com 6711986Sandreas.sandberg@arm.com#include "pybind11_tests.h" 6811986Sandreas.sandberg@arm.com#include <unordered_map> 6911986Sandreas.sandberg@arm.com#include <list> 7011986Sandreas.sandberg@arm.com#include <typeindex> 7111986Sandreas.sandberg@arm.com#include <sstream> 7211986Sandreas.sandberg@arm.com 7311986Sandreas.sandberg@arm.comclass ConstructorStats { 7411986Sandreas.sandberg@arm.comprotected: 7511986Sandreas.sandberg@arm.com std::unordered_map<void*, int> _instances; // Need a map rather than set because members can shared address with parents 7611986Sandreas.sandberg@arm.com std::list<std::string> _values; // Used to track values (e.g. of value constructors) 7711986Sandreas.sandberg@arm.compublic: 7811986Sandreas.sandberg@arm.com int default_constructions = 0; 7911986Sandreas.sandberg@arm.com int copy_constructions = 0; 8011986Sandreas.sandberg@arm.com int move_constructions = 0; 8111986Sandreas.sandberg@arm.com int copy_assignments = 0; 8211986Sandreas.sandberg@arm.com int move_assignments = 0; 8311986Sandreas.sandberg@arm.com 8411986Sandreas.sandberg@arm.com void copy_created(void *inst) { 8511986Sandreas.sandberg@arm.com created(inst); 8611986Sandreas.sandberg@arm.com copy_constructions++; 8711986Sandreas.sandberg@arm.com } 8812037Sandreas.sandberg@arm.com 8911986Sandreas.sandberg@arm.com void move_created(void *inst) { 9011986Sandreas.sandberg@arm.com created(inst); 9111986Sandreas.sandberg@arm.com move_constructions++; 9211986Sandreas.sandberg@arm.com } 9312037Sandreas.sandberg@arm.com 9411986Sandreas.sandberg@arm.com void default_created(void *inst) { 9511986Sandreas.sandberg@arm.com created(inst); 9611986Sandreas.sandberg@arm.com default_constructions++; 9711986Sandreas.sandberg@arm.com } 9812037Sandreas.sandberg@arm.com 9911986Sandreas.sandberg@arm.com void created(void *inst) { 10011986Sandreas.sandberg@arm.com ++_instances[inst]; 10112037Sandreas.sandberg@arm.com } 10212037Sandreas.sandberg@arm.com 10311986Sandreas.sandberg@arm.com void destroyed(void *inst) { 10411986Sandreas.sandberg@arm.com if (--_instances[inst] < 0) 10512037Sandreas.sandberg@arm.com throw std::runtime_error("cstats.destroyed() called with unknown " 10612037Sandreas.sandberg@arm.com "instance; potential double-destruction " 10712037Sandreas.sandberg@arm.com "or a missing cstats.created()"); 10812037Sandreas.sandberg@arm.com } 10912037Sandreas.sandberg@arm.com 11012037Sandreas.sandberg@arm.com static void gc() { 11112037Sandreas.sandberg@arm.com // Force garbage collection to ensure any pending destructors are invoked: 11212037Sandreas.sandberg@arm.com#if defined(PYPY_VERSION) 11312037Sandreas.sandberg@arm.com PyObject *globals = PyEval_GetGlobals(); 11412037Sandreas.sandberg@arm.com PyObject *result = PyRun_String( 11512037Sandreas.sandberg@arm.com "import gc\n" 11612037Sandreas.sandberg@arm.com "for i in range(2):" 11712037Sandreas.sandberg@arm.com " gc.collect()\n", 11812037Sandreas.sandberg@arm.com Py_file_input, globals, globals); 11912037Sandreas.sandberg@arm.com if (result == nullptr) 12012037Sandreas.sandberg@arm.com throw py::error_already_set(); 12112037Sandreas.sandberg@arm.com Py_DECREF(result); 12212037Sandreas.sandberg@arm.com#else 12312037Sandreas.sandberg@arm.com py::module::import("gc").attr("collect")(); 12412037Sandreas.sandberg@arm.com#endif 12511986Sandreas.sandberg@arm.com } 12611986Sandreas.sandberg@arm.com 12711986Sandreas.sandberg@arm.com int alive() { 12812037Sandreas.sandberg@arm.com gc(); 12911986Sandreas.sandberg@arm.com int total = 0; 13012037Sandreas.sandberg@arm.com for (const auto &p : _instances) 13112037Sandreas.sandberg@arm.com if (p.second > 0) 13212037Sandreas.sandberg@arm.com total += p.second; 13311986Sandreas.sandberg@arm.com return total; 13411986Sandreas.sandberg@arm.com } 13511986Sandreas.sandberg@arm.com 13611986Sandreas.sandberg@arm.com void value() {} // Recursion terminator 13711986Sandreas.sandberg@arm.com // Takes one or more values, converts them to strings, then stores them. 13811986Sandreas.sandberg@arm.com template <typename T, typename... Tmore> void value(const T &v, Tmore &&...args) { 13911986Sandreas.sandberg@arm.com std::ostringstream oss; 14011986Sandreas.sandberg@arm.com oss << v; 14111986Sandreas.sandberg@arm.com _values.push_back(oss.str()); 14211986Sandreas.sandberg@arm.com value(std::forward<Tmore>(args)...); 14311986Sandreas.sandberg@arm.com } 14411986Sandreas.sandberg@arm.com 14511986Sandreas.sandberg@arm.com // Move out stored values 14611986Sandreas.sandberg@arm.com py::list values() { 14711986Sandreas.sandberg@arm.com py::list l; 14811986Sandreas.sandberg@arm.com for (const auto &v : _values) l.append(py::cast(v)); 14911986Sandreas.sandberg@arm.com _values.clear(); 15011986Sandreas.sandberg@arm.com return l; 15111986Sandreas.sandberg@arm.com } 15211986Sandreas.sandberg@arm.com 15311986Sandreas.sandberg@arm.com // Gets constructor stats from a C++ type index 15411986Sandreas.sandberg@arm.com static ConstructorStats& get(std::type_index type) { 15511986Sandreas.sandberg@arm.com static std::unordered_map<std::type_index, ConstructorStats> all_cstats; 15611986Sandreas.sandberg@arm.com return all_cstats[type]; 15711986Sandreas.sandberg@arm.com } 15811986Sandreas.sandberg@arm.com 15911986Sandreas.sandberg@arm.com // Gets constructor stats from a C++ type 16011986Sandreas.sandberg@arm.com template <typename T> static ConstructorStats& get() { 16112037Sandreas.sandberg@arm.com#if defined(PYPY_VERSION) 16212037Sandreas.sandberg@arm.com gc(); 16312037Sandreas.sandberg@arm.com#endif 16411986Sandreas.sandberg@arm.com return get(typeid(T)); 16511986Sandreas.sandberg@arm.com } 16611986Sandreas.sandberg@arm.com 16711986Sandreas.sandberg@arm.com // Gets constructor stats from a Python class 16811986Sandreas.sandberg@arm.com static ConstructorStats& get(py::object class_) { 16911986Sandreas.sandberg@arm.com auto &internals = py::detail::get_internals(); 17011986Sandreas.sandberg@arm.com const std::type_index *t1 = nullptr, *t2 = nullptr; 17111986Sandreas.sandberg@arm.com try { 17212391Sjason@lowepower.com auto *type_info = internals.registered_types_py.at((PyTypeObject *) class_.ptr()).at(0); 17311986Sandreas.sandberg@arm.com for (auto &p : internals.registered_types_cpp) { 17411986Sandreas.sandberg@arm.com if (p.second == type_info) { 17511986Sandreas.sandberg@arm.com if (t1) { 17611986Sandreas.sandberg@arm.com t2 = &p.first; 17711986Sandreas.sandberg@arm.com break; 17811986Sandreas.sandberg@arm.com } 17911986Sandreas.sandberg@arm.com t1 = &p.first; 18011986Sandreas.sandberg@arm.com } 18111986Sandreas.sandberg@arm.com } 18211986Sandreas.sandberg@arm.com } 18314299Sbbruce@ucdavis.edu catch (const std::out_of_range &) {} 18411986Sandreas.sandberg@arm.com if (!t1) throw std::runtime_error("Unknown class passed to ConstructorStats::get()"); 18511986Sandreas.sandberg@arm.com auto &cs1 = get(*t1); 18611986Sandreas.sandberg@arm.com // If we have both a t1 and t2 match, one is probably the trampoline class; return whichever 18711986Sandreas.sandberg@arm.com // has more constructions (typically one or the other will be 0) 18811986Sandreas.sandberg@arm.com if (t2) { 18911986Sandreas.sandberg@arm.com auto &cs2 = get(*t2); 19011986Sandreas.sandberg@arm.com int cs1_total = cs1.default_constructions + cs1.copy_constructions + cs1.move_constructions + (int) cs1._values.size(); 19111986Sandreas.sandberg@arm.com int cs2_total = cs2.default_constructions + cs2.copy_constructions + cs2.move_constructions + (int) cs2._values.size(); 19211986Sandreas.sandberg@arm.com if (cs2_total > cs1_total) return cs2; 19311986Sandreas.sandberg@arm.com } 19411986Sandreas.sandberg@arm.com return cs1; 19511986Sandreas.sandberg@arm.com } 19611986Sandreas.sandberg@arm.com}; 19711986Sandreas.sandberg@arm.com 19811986Sandreas.sandberg@arm.com// To track construction/destruction, you need to call these methods from the various 19911986Sandreas.sandberg@arm.com// constructors/operators. The ones that take extra values record the given values in the 20011986Sandreas.sandberg@arm.com// constructor stats values for later inspection. 20111986Sandreas.sandberg@arm.comtemplate <class T> void track_copy_created(T *inst) { ConstructorStats::get<T>().copy_created(inst); } 20211986Sandreas.sandberg@arm.comtemplate <class T> void track_move_created(T *inst) { ConstructorStats::get<T>().move_created(inst); } 20311986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void track_copy_assigned(T *, Values &&...values) { 20411986Sandreas.sandberg@arm.com auto &cst = ConstructorStats::get<T>(); 20511986Sandreas.sandberg@arm.com cst.copy_assignments++; 20611986Sandreas.sandberg@arm.com cst.value(std::forward<Values>(values)...); 20711986Sandreas.sandberg@arm.com} 20811986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void track_move_assigned(T *, Values &&...values) { 20911986Sandreas.sandberg@arm.com auto &cst = ConstructorStats::get<T>(); 21011986Sandreas.sandberg@arm.com cst.move_assignments++; 21111986Sandreas.sandberg@arm.com cst.value(std::forward<Values>(values)...); 21211986Sandreas.sandberg@arm.com} 21311986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void track_default_created(T *inst, Values &&...values) { 21411986Sandreas.sandberg@arm.com auto &cst = ConstructorStats::get<T>(); 21511986Sandreas.sandberg@arm.com cst.default_created(inst); 21611986Sandreas.sandberg@arm.com cst.value(std::forward<Values>(values)...); 21711986Sandreas.sandberg@arm.com} 21811986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void track_created(T *inst, Values &&...values) { 21911986Sandreas.sandberg@arm.com auto &cst = ConstructorStats::get<T>(); 22011986Sandreas.sandberg@arm.com cst.created(inst); 22111986Sandreas.sandberg@arm.com cst.value(std::forward<Values>(values)...); 22211986Sandreas.sandberg@arm.com} 22311986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void track_destroyed(T *inst) { 22411986Sandreas.sandberg@arm.com ConstructorStats::get<T>().destroyed(inst); 22511986Sandreas.sandberg@arm.com} 22611986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void track_values(T *, Values &&...values) { 22711986Sandreas.sandberg@arm.com ConstructorStats::get<T>().value(std::forward<Values>(values)...); 22811986Sandreas.sandberg@arm.com} 22911986Sandreas.sandberg@arm.com 23011986Sandreas.sandberg@arm.com/// Don't cast pointers to Python, print them as strings 23111986Sandreas.sandberg@arm.cominline const char *format_ptrs(const char *p) { return p; } 23211986Sandreas.sandberg@arm.comtemplate <typename T> 23311986Sandreas.sandberg@arm.compy::str format_ptrs(T *p) { return "{:#x}"_s.format(reinterpret_cast<std::uintptr_t>(p)); } 23411986Sandreas.sandberg@arm.comtemplate <typename T> 23511986Sandreas.sandberg@arm.comauto format_ptrs(T &&x) -> decltype(std::forward<T>(x)) { return std::forward<T>(x); } 23611986Sandreas.sandberg@arm.com 23711986Sandreas.sandberg@arm.comtemplate <class T, typename... Output> 23811986Sandreas.sandberg@arm.comvoid print_constr_details(T *inst, const std::string &action, Output &&...output) { 23911986Sandreas.sandberg@arm.com py::print("###", py::type_id<T>(), "@", format_ptrs(inst), action, 24011986Sandreas.sandberg@arm.com format_ptrs(std::forward<Output>(output))...); 24111986Sandreas.sandberg@arm.com} 24211986Sandreas.sandberg@arm.com 24311986Sandreas.sandberg@arm.com// Verbose versions of the above: 24411986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void print_copy_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values 24511986Sandreas.sandberg@arm.com print_constr_details(inst, "created via copy constructor", values...); 24611986Sandreas.sandberg@arm.com track_copy_created(inst); 24711986Sandreas.sandberg@arm.com} 24811986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void print_move_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values 24911986Sandreas.sandberg@arm.com print_constr_details(inst, "created via move constructor", values...); 25011986Sandreas.sandberg@arm.com track_move_created(inst); 25111986Sandreas.sandberg@arm.com} 25211986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void print_copy_assigned(T *inst, Values &&...values) { 25311986Sandreas.sandberg@arm.com print_constr_details(inst, "assigned via copy assignment", values...); 25411986Sandreas.sandberg@arm.com track_copy_assigned(inst, values...); 25511986Sandreas.sandberg@arm.com} 25611986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void print_move_assigned(T *inst, Values &&...values) { 25711986Sandreas.sandberg@arm.com print_constr_details(inst, "assigned via move assignment", values...); 25811986Sandreas.sandberg@arm.com track_move_assigned(inst, values...); 25911986Sandreas.sandberg@arm.com} 26011986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void print_default_created(T *inst, Values &&...values) { 26111986Sandreas.sandberg@arm.com print_constr_details(inst, "created via default constructor", values...); 26211986Sandreas.sandberg@arm.com track_default_created(inst, values...); 26311986Sandreas.sandberg@arm.com} 26411986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void print_created(T *inst, Values &&...values) { 26511986Sandreas.sandberg@arm.com print_constr_details(inst, "created", values...); 26611986Sandreas.sandberg@arm.com track_created(inst, values...); 26711986Sandreas.sandberg@arm.com} 26811986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values 26911986Sandreas.sandberg@arm.com print_constr_details(inst, "destroyed", values...); 27011986Sandreas.sandberg@arm.com track_destroyed(inst); 27111986Sandreas.sandberg@arm.com} 27211986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void print_values(T *inst, Values &&...values) { 27311986Sandreas.sandberg@arm.com print_constr_details(inst, ":", values...); 27411986Sandreas.sandberg@arm.com track_values(inst, values...); 27511986Sandreas.sandberg@arm.com} 27611986Sandreas.sandberg@arm.com 277