Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Include/ceval.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ PyAPI_FUNC(PyObject *) PyEval_CallMethod(PyObject *obj,
#ifndef Py_LIMITED_API
PyAPI_FUNC(void) PyEval_SetProfile(Py_tracefunc, PyObject *);
PyAPI_FUNC(void) PyEval_SetTrace(Py_tracefunc, PyObject *);
PyAPI_FUNC(void) _PyEval_SetTraceEx(Py_tracefunc, PyObject *, int);
PyAPI_FUNC(void) _PyEval_SetCoroutineWrapper(PyObject *);
PyAPI_FUNC(PyObject *) _PyEval_GetCoroutineWrapper(void);
PyAPI_FUNC(void) _PyEval_SetAsyncGenFirstiter(PyObject *);
Expand Down
2 changes: 2 additions & 0 deletions Include/pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ typedef int (*Py_tracefunc)(PyObject *, struct _frame *, int, PyObject *);
#define PyTrace_C_CALL 4
#define PyTrace_C_EXCEPTION 5
#define PyTrace_C_RETURN 6
#define PyTrace_INSTRUCTION 7
#endif

#ifdef Py_LIMITED_API
Expand All @@ -124,6 +125,7 @@ typedef struct _ts {
the trace/profile. */
int tracing;
int use_tracing;
int trace_instructions; /* whether to trace every instruction. */

Py_tracefunc c_profilefunc;
Py_tracefunc c_tracefunc;
Expand Down
69 changes: 67 additions & 2 deletions Lib/test/test_sys_settrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,12 @@ def compare_events(self, line_offset, events, expected_events):
"\n".join(difflib.ndiff([str(x) for x in expected_events],
[str(x) for x in events])))

def run_and_compare(self, func, events):
def run_and_compare(self, func, events, **kwargs):
tracer = Tracer()
sys.settrace(tracer.trace)
if kwargs:
sys.settracestate(tracer.trace, **kwargs)
else: # test the older settrace function if no flags are specified.
sys.settrace(tracer.trace)
func()
sys.settrace(None)
self.compare_events(func.__code__.co_firstlineno,
Expand Down Expand Up @@ -397,6 +400,68 @@ def func():
[(0, 'call'),
(1, 'line')])

def test_18_settracestate_kwargs(self):
def tracer(frame, event, arg):
pass
# 'tracefunc' parameter is mandatory.
self.assertRaises(TypeError, sys.settracestate)
# 'trace_instructions' is a keyword-only parameter.
self.assertRaises(TypeError, sys.settracestate, tracer, True)

def test_18_settrace_gettracestate(self):
def tracer(frame, event, arg):
pass
sys.settrace(tracer)
state = sys.gettracestate()
exp_state = {
'trace_func': tracer,
'trace_instructions': False }
self.assertDictEqual(exp_state, state)

def test_19_settracestate_gettracestate(self):
def tracer(frame, event, arg):
pass
exp_state = {
'trace_func': tracer,
'trace_instructions': True }
sys.settracestate(**exp_state)
state = sys.gettracestate()
self.assertDictEqual(exp_state, state)

def test_20_settracestate_gettrace_raises(self):
def tracer(frame, event, arg):
pass
sys.settracestate(tracer, trace_instructions=False)
self.assertEqual(tracer, sys.gettrace())
# gettrace raises whenever the exteneded state is out of defaults;
# this protects against the old save/restore idiom whenever it does not
# capture the full state.
sys.settracestate(tracer, trace_instructions=True)
self.assertRaises(ValueError, sys.gettrace)

def test_21_trace_instructions(self):
def func():
cond = True
if cond: x = 1
else: x = 2
return x
# TODO: we could define an InstTracer that captures instruction/offset.
# This would show exactly what is being traced, but makes the test more
# dependent on bytecode internals. Opinions?
events = [
(0, 'call'),
(1, 'line'),
(1, 'instruction'),
(2, 'line'),
(2, 'instruction'),
(2, 'instruction'),
(2, 'instruction'),
(2, 'instruction'),
(4, 'line'),
(4, 'instruction'),
(4, 'return')]
self.run_and_compare(func, events, trace_instructions=True)


class RaisingTraceFuncTestCase(unittest.TestCase):
def setUp(self):
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,7 @@ Jan Kim
Taek Joo Kim
Sam Kimbrel
Tomohiko Kinebuchi
George King
James King
W. Trevor King
Paul Kippes
Expand Down
25 changes: 19 additions & 6 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -4587,6 +4587,10 @@ maybe_call_line_trace(Py_tracefunc func, PyObject *obj,
frame->f_lineno = line;
result = call_trace(func, obj, tstate, frame, PyTrace_LINE, Py_None);
}
else if (tstate->trace_instructions) {
result = call_trace(func, obj, tstate, frame, PyTrace_INSTRUCTION,
Py_None);
}
*instr_prev = frame->f_lasti;
return result;
}
Expand All @@ -4609,24 +4613,33 @@ PyEval_SetProfile(Py_tracefunc func, PyObject *arg)
}

void
PyEval_SetTrace(Py_tracefunc func, PyObject *arg)
_PyEval_SetTraceEx(Py_tracefunc func, PyObject *traceobj,
int trace_instructions)
{
PyThreadState *tstate = PyThreadState_GET();
PyObject *temp = tstate->c_traceobj;
PyObject *old_traceobj = tstate->c_traceobj;
_Py_TracingPossible += (func != NULL) - (tstate->c_tracefunc != NULL);
Py_XINCREF(arg);
Py_XINCREF(traceobj);
tstate->c_tracefunc = NULL;
tstate->c_traceobj = NULL;
/* Must make sure that profiling is not ignored if 'temp' is freed */
/* Make sure that profiling is not ignored if 'old_traceobj' is freed */
tstate->use_tracing = tstate->c_profilefunc != NULL;
Py_XDECREF(temp);
Py_XDECREF(old_traceobj);
tstate->c_tracefunc = func;
tstate->c_traceobj = arg;
tstate->c_traceobj = traceobj;
tstate->trace_instructions = trace_instructions;
/* Flag that tracing or profiling is turned on */
tstate->use_tracing = ((func != NULL)
|| (tstate->c_profilefunc != NULL));
}

void
PyEval_SetTrace(Py_tracefunc func, PyObject *arg)
{
/* trace_instructions=False. */
_PyEval_SetTraceEx(func, arg, 0);
}

void
_PyEval_SetCoroutineWrapper(PyObject *wrapper)
{
Expand Down
2 changes: 2 additions & 0 deletions Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ new_threadstate(PyInterpreterState *interp, int init)
tstate->recursion_critical = 0;
tstate->tracing = 0;
tstate->use_tracing = 0;
tstate->trace_instructions = 0;
tstate->gilstate_counter = 0;
tstate->async_exc = NULL;
#ifdef WITH_THREAD
Expand Down Expand Up @@ -461,6 +462,7 @@ PyThreadState_Clear(PyThreadState *tstate)

tstate->c_profilefunc = NULL;
tstate->c_tracefunc = NULL;

Py_CLEAR(tstate->c_profileobj);
Py_CLEAR(tstate->c_traceobj);

Expand Down
82 changes: 72 additions & 10 deletions Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -351,18 +351,20 @@ same value.");
* Cached interned string objects used for calling the profile and
* trace functions. Initialized by trace_init().
*/
static PyObject *whatstrings[7] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
static PyObject *whatstrings[8] = {
NULL, NULL, NULL, NULL, NULL, NULL, NULL,NULL};

static int
trace_init(void)
{
static const char * const whatnames[7] = {
static const char * const whatnames[8] = {
"call", "exception", "line", "return",
"c_call", "c_exception", "c_return"
"c_call", "c_exception", "c_return",
"instruction"
};
PyObject *name;
int i;
for (i = 0; i < 7; ++i) {
for (i = 0; i < 8; ++i) {
if (whatstrings[i] == NULL) {
name = PyUnicode_InternFromString(whatnames[i]);
if (name == NULL)
Expand Down Expand Up @@ -432,7 +434,7 @@ trace_trampoline(PyObject *self, PyFrameObject *frame,
return 0;
result = call_trampoline(callback, frame, what, arg);
if (result == NULL) {
PyEval_SetTrace(NULL, NULL);
_PyEval_SetTraceEx(NULL, NULL, 0);
Py_CLEAR(frame->f_trace);
return -1;
}
Expand Down Expand Up @@ -464,16 +466,49 @@ Set the global debug tracing function. It will be called on each\n\
function call. See the debugger chapter in the library manual."
);

static PyObject *
sys_settracestate(PyObject *self, PyObject *args, PyObject *kwargs)
{
PyObject *trace_func = NULL;
int trace_instructions;
static char *keywords[] = {"trace_func", "trace_instructions", 0};
if (trace_init() == -1)
return NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|$p:settracestate",
keywords, &trace_func,
&trace_instructions))
return NULL;

if (trace_func == Py_None)
_PyEval_SetTraceEx(NULL, NULL, 0);
else
_PyEval_SetTraceEx(trace_trampoline, trace_func, trace_instructions);
Py_RETURN_NONE;
}

PyDoc_STRVAR(settracestate_doc,
"settracestate(function)\n\
\n\
Set the global debug tracing function, and enable instruction tracing. \n\
TODO: refer to the detailed documentation (also fix settrace doc above)."
);

static PyObject *
sys_gettrace(PyObject *self, PyObject *args)
{
PyThreadState *tstate = PyThreadState_GET();
PyObject *temp = tstate->c_traceobj;
PyObject *trace_func = tstate->c_traceobj;

if (temp == NULL)
temp = Py_None;
Py_INCREF(temp);
return temp;
if (tstate->trace_instructions) {
PyErr_Format(PyExc_ValueError,
"trace_instructions has been set to True; "
"use sys.getttracestate to capture the complete state");
return NULL;
}
if (trace_func == NULL)
trace_func = Py_None;
Py_INCREF(trace_func);
return trace_func;
}

PyDoc_STRVAR(gettrace_doc,
Expand All @@ -483,6 +518,30 @@ Return the global debug tracing function set with sys.settrace.\n\
See the debugger chapter in the library manual."
);

static PyObject *
sys_gettracestate(PyObject *self, PyObject *args)
{
PyThreadState *tstate = PyThreadState_GET();
PyObject *trace_func = tstate->c_traceobj;
PyObject *trace_instructions = PyBool_FromLong(tstate->trace_instructions);

if (trace_func == NULL)
trace_func = Py_None;
Py_INCREF(trace_func);

return Py_BuildValue("{s:O,s:O}", "trace_func", trace_func,
"trace_instructions", trace_instructions);
}

PyDoc_STRVAR(gettracestate_doc,
"gettracestate()\n\
\n\
Returns a dictionary containing the complete tracing state. \n\
Currently this consists of \"tracefunc\", the global tracing function, \n\
and \"trace_instructions\", a boolean indicating whether instruction \n\
tracing is enabled."
);

static PyObject *
sys_setprofile(PyObject *self, PyObject *args)
{
Expand Down Expand Up @@ -1435,7 +1494,10 @@ static PyMethodDef sys_methods[] = {
{"setrecursionlimit", sys_setrecursionlimit, METH_VARARGS,
setrecursionlimit_doc},
{"settrace", sys_settrace, METH_O, settrace_doc},
{"settracestate", (PyCFunction)sys_settracestate, METH_VARARGS | METH_KEYWORDS,
settracestate_doc},
{"gettrace", sys_gettrace, METH_NOARGS, gettrace_doc},
{"gettracestate", sys_gettracestate, METH_NOARGS, gettracestate_doc},
{"call_tracing", sys_call_tracing, METH_VARARGS, call_tracing_doc},
{"_debugmallocstats", sys_debugmallocstats, METH_NOARGS,
debugmallocstats_doc},
Expand Down