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