11#include " cInstrumenter.hpp"
22#include " events.hpp"
33#include " pythonHelpers.hpp"
4+ #include < algorithm>
5+ #include < array>
46#include < string>
57
68namespace scorepy
@@ -16,10 +18,6 @@ static const std::string& make_region_name(const char* moduleName, const char* n
1618
1719void CInstrumenter::enable_instrumenter ()
1820{
19- // TODO: Known issue: `sys.getprofile()` returns the user object (2nd arg)
20- // So `sys.setprofile(sys.getprofile())` will not round-trip as it will try to call the
21- // 2nd arg. If it is nullptr (here) it means it will be disabled completely
22- // See https://nedbatchelder.com/text/trace-function.html for details
2321 const auto callback = [](PyObject* obj, PyFrameObject* frame, int what, PyObject* arg) -> int {
2422 return fromPyObject (obj)->onEvent (*frame, what, arg) ? 0 : -1 ;
2523 };
@@ -41,6 +39,34 @@ void CInstrumenter::disable_instrumenter()
4139 PyEval_SetProfile (nullptr , nullptr );
4240}
4341
42+ // / Mapping of PyTrace_* to it's string representations
43+ // / List taken from CPythons sysmodule.c
44+ static const std::array<std::string, 8 > whatStrings = { " call" , " exception" , " line" ,
45+ " return" , " c_call" , " c_exception" ,
46+ " c_return" , " opcode" };
47+
48+ // Required because: `sys.getprofile()` returns the user object (2nd arg to PyEval_SetTrace)
49+ // So `sys.setprofile(sys.getprofile())` will not round-trip as it will try to call the
50+ // 2nd arg through pythons dispatch function. Hence make the object callable.
51+ // See https://nedbatchelder.com/text/trace-function.html for details
52+ PyObject* CInstrumenter::operator ()(PyFrameObject& frame, const char * what, PyObject* arg)
53+ {
54+ const auto itWhat = std::find (whatStrings.begin (), whatStrings.end (), what);
55+ const int iWhat = itWhat == whatStrings.end () ? -1 : std::distance (whatStrings.begin (), itWhat);
56+ // To speed up further event processing install this class directly as the handler
57+ // But we might be inside a `sys.settrace` call where the user wanted to set another function
58+ // which would then be overwritten here. Hence use the CALL event which avoids the problem
59+ if (iWhat == PyTrace_CALL)
60+ enable_instrumenter ();
61+ if (onEvent (frame, iWhat, arg))
62+ {
63+ Py_INCREF (toPyObject ());
64+ return toPyObject ();
65+ }
66+ else
67+ return nullptr ;
68+ }
69+
4470bool CInstrumenter::onEvent (PyFrameObject& frame, int what, PyObject*)
4571{
4672 switch (what)
0 commit comments