blob: c546f5fba0dfb036e324dd6f9e75d00f1d9a35b8 [file] [log] [blame] [edit]
/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
/* C-based Tracer for coverage.py. */
#include "util.h"
#include "datastack.h"
#include "filedisp.h"
#include "tracer.h"
/* Python C API helpers. */
static int
pyint_as_int(PyObject * pyint, int *pint)
{
int the_int = (int)PyLong_AsLong(pyint);
if (the_int == -1 && PyErr_Occurred()) {
return RET_ERROR;
}
*pint = the_int;
return RET_OK;
}
/* Interned strings to speed GetAttr etc. */
static PyObject *str__coverage_plugin;
static PyObject *str__coverage_plugin_name;
static PyObject *str_dynamic_source_filename;
static PyObject *str_line_number_range;
int
CTracer_intern_strings(void)
{
int ret = RET_ERROR;
#define INTERN_STRING(v, s) \
v = PyUnicode_InternFromString(s); \
if (v == NULL) { \
goto error; \
}
INTERN_STRING(str__coverage_plugin, "_coverage_plugin")
INTERN_STRING(str__coverage_plugin_name, "_coverage_plugin_name")
INTERN_STRING(str_dynamic_source_filename, "dynamic_source_filename")
INTERN_STRING(str_line_number_range, "line_number_range")
ret = RET_OK;
error:
return ret;
}
static void CTracer_disable_plugin(CTracer *self, PyObject * disposition);
static int
CTracer_init(CTracer *self, PyObject *args_unused, PyObject *kwds_unused)
{
int ret = RET_ERROR;
if (DataStack_init(&self->stats, &self->data_stack) < 0) {
goto error;
}
self->pdata_stack = &self->data_stack;
self->context = Py_None;
Py_INCREF(self->context);
ret = RET_OK;
goto ok;
error:
STATS( self->stats.errors++; )
ok:
return ret;
}
static void
CTracer_dealloc(CTracer *self)
{
int i;
if (self->started) {
PyEval_SetTrace(NULL, NULL);
}
Py_XDECREF(self->should_trace);
Py_XDECREF(self->check_include);
Py_XDECREF(self->warn);
Py_XDECREF(self->concur_id_func);
Py_XDECREF(self->data);
Py_XDECREF(self->file_tracers);
Py_XDECREF(self->should_trace_cache);
Py_XDECREF(self->should_start_context);
Py_XDECREF(self->switch_context);
Py_XDECREF(self->lock_data);
Py_XDECREF(self->unlock_data);
Py_XDECREF(self->context);
Py_XDECREF(self->disable_plugin);
DataStack_dealloc(&self->stats, &self->data_stack);
if (self->data_stacks) {
for (i = 0; i < self->data_stacks_used; i++) {
DataStack_dealloc(&self->stats, self->data_stacks + i);
}
PyMem_Free(self->data_stacks);
}
Py_XDECREF(self->data_stack_index);
Py_TYPE(self)->tp_free((PyObject*)self);
}
#if TRACE_LOG
/* Set debugging constants: a file substring and line number to start logging. */
static const char * start_file = "badasync.py";
static int start_line = 1;
static const char *
indent(int n)
{
static const char * spaces =
" "
" "
" "
" "
;
return spaces + strlen(spaces) - n*2;
}
static BOOL logging = FALSE;
static void
CTracer_showlog(CTracer * self, int lineno, PyObject * filename, const char * msg)
{
if (logging) {
int depth = self->pdata_stack->depth;
printf("%x: %s%3d ", (int)self, indent(depth), depth);
if (lineno) {
printf("%4d", lineno);
}
else {
printf(" ");
}
if (filename) {
PyObject *ascii = PyUnicode_AsASCIIString(filename);
printf(" %s", PyBytes_AS_STRING(ascii));
Py_DECREF(ascii);
}
if (msg) {
printf(" %s", msg);
}
printf("\n");
}
}
#define SHOWLOG(l,f,m) CTracer_showlog(self,l,f,m)
#else
#define SHOWLOG(l,f,m)
#endif /* TRACE_LOG */
#if WHAT_LOG
static const char * what_sym[] = {"CALL", "EXC ", "LINE", "RET "};
#endif
/* Record a pair of integers in self->pcur_entry->file_data. */
static int
CTracer_record_pair(CTracer *self, int l1, int l2)
{
int ret = RET_ERROR;
PyObject * packed_obj = NULL;
uint64 packed = 0;
// Conceptually, data is a set of tuples (l1, l2), but that literally
// making a set of tuples would require us to construct a tuple just to
// see if we'd already recorded an arc. On many-times-executed code,
// that would mean we construct a tuple, find the tuple is already in the
// set, then discard the tuple. We can avoid that overhead by packing
// the two line numbers into one integer instead.
// See collector.py:flush_data for the Python code that unpacks this.
if (l1 < 0) {
packed |= (1LL << 40);
l1 = -l1;
}
if (l2 < 0) {
packed |= (1LL << 41);
l2 = -l2;
}
packed |= (((uint64)l2) << 20) + (uint64)l1;
packed_obj = PyLong_FromUnsignedLongLong(packed);
if (packed_obj == NULL) {
goto error;
}
if (PySet_Add(self->pcur_entry->file_data, packed_obj) < 0) {
goto error;
}
ret = RET_OK;
error:
Py_XDECREF(packed_obj);
return ret;
}
/* Set self->pdata_stack to the proper data_stack to use. */
static int
CTracer_set_pdata_stack(CTracer *self)
{
int ret = RET_ERROR;
PyObject * co_obj = NULL;
PyObject * stack_index = NULL;
if (self->concur_id_func != Py_None) {
int the_index = 0;
if (self->data_stack_index == NULL) {
PyObject * weakref = NULL;
weakref = PyImport_ImportModule("weakref");
if (weakref == NULL) {
goto error;
}
STATS( self->stats.pycalls++; )
self->data_stack_index = PyObject_CallMethod(weakref, "WeakKeyDictionary", NULL);
Py_XDECREF(weakref);
if (self->data_stack_index == NULL) {
goto error;
}
}
STATS( self->stats.pycalls++; )
co_obj = PyObject_CallObject(self->concur_id_func, NULL);
if (co_obj == NULL) {
goto error;
}
stack_index = PyObject_GetItem(self->data_stack_index, co_obj);
if (stack_index == NULL) {
/* PyObject_GetItem sets an exception if it didn't find the thing. */
PyErr_Clear();
/* A new concurrency object. Make a new data stack. */
the_index = self->data_stacks_used;
stack_index = PyLong_FromLong((long)the_index);
if (stack_index == NULL) {
goto error;
}
if (PyObject_SetItem(self->data_stack_index, co_obj, stack_index) < 0) {
goto error;
}
self->data_stacks_used++;
if (self->data_stacks_used >= self->data_stacks_alloc) {
int bigger = self->data_stacks_alloc + 10;
DataStack * bigger_stacks = PyMem_Realloc(self->data_stacks, bigger * sizeof(DataStack));
if (bigger_stacks == NULL) {
PyErr_NoMemory();
goto error;
}
self->data_stacks = bigger_stacks;
self->data_stacks_alloc = bigger;
}
DataStack_init(&self->stats, &self->data_stacks[the_index]);
}
else {
if (pyint_as_int(stack_index, &the_index) < 0) {
goto error;
}
}
self->pdata_stack = &self->data_stacks[the_index];
}
else {
self->pdata_stack = &self->data_stack;
}
ret = RET_OK;
error:
Py_XDECREF(co_obj);
Py_XDECREF(stack_index);
return ret;
}
/*
* Parts of the trace function.
*/
static int
CTracer_handle_call(CTracer *self, PyFrameObject *frame)
{
int ret = RET_ERROR;
int ret2;
/* Owned references that we clean up at the very end of the function. */
PyObject * disposition = NULL;
PyObject * plugin = NULL;
PyObject * plugin_name = NULL;
PyObject * next_tracename = NULL;
#ifdef RESUME
PyObject * pCode = NULL;
#endif
/* Borrowed references. */
PyObject * filename = NULL;
PyObject * disp_trace = NULL;
PyObject * tracename = NULL;
PyObject * file_tracer = NULL;
PyObject * has_dynamic_filename = NULL;
CFileDisposition * pdisp = NULL;
STATS( self->stats.calls++; )
/* Grow the stack. */
if (CTracer_set_pdata_stack(self) < 0) {
goto error;
}
if (DataStack_grow(&self->stats, self->pdata_stack) < 0) {
goto error;
}
self->pcur_entry = &self->pdata_stack->stack[self->pdata_stack->depth];
/* See if this frame begins a new context. */
if (self->should_start_context != Py_None && self->context == Py_None) {
PyObject * context;
/* We're looking for our context, ask should_start_context if this is the start. */
STATS( self->stats.start_context_calls++; )
STATS( self->stats.pycalls++; )
context = PyObject_CallFunctionObjArgs(self->should_start_context, frame, NULL);
if (context == NULL) {
goto error;
}
if (context != Py_None) {
PyObject * val;
Py_DECREF(self->context);
self->context = context;
self->pcur_entry->started_context = TRUE;
STATS( self->stats.pycalls++; )
val = PyObject_CallFunctionObjArgs(self->switch_context, context, NULL);
if (val == NULL) {
goto error;
}
Py_DECREF(val);
}
else {
Py_DECREF(context);
self->pcur_entry->started_context = FALSE;
}
}
else {
self->pcur_entry->started_context = FALSE;
}
/* Check if we should trace this line. */
filename = MyFrame_GetCode(frame)->co_filename;
disposition = PyDict_GetItem(self->should_trace_cache, filename);
if (disposition == NULL) {
if (PyErr_Occurred()) {
goto error;
}
STATS( self->stats.files++; )
/* We've never considered this file before. */
/* Ask should_trace about it. */
STATS( self->stats.pycalls++; )
disposition = PyObject_CallFunctionObjArgs(self->should_trace, filename, frame, NULL);
if (disposition == NULL) {
/* An error occurred inside should_trace. */
goto error;
}
if (PyDict_SetItem(self->should_trace_cache, filename, disposition) < 0) {
goto error;
}
}
else {
Py_INCREF(disposition);
}
if (disposition == Py_None) {
/* A later check_include returned false, so don't trace it. */
disp_trace = Py_False;
}
else {
/* The object we got is a CFileDisposition, use it efficiently. */
pdisp = (CFileDisposition *) disposition;
disp_trace = pdisp->trace;
if (disp_trace == NULL) {
goto error;
}
}
if (disp_trace == Py_True) {
/* If tracename is a string, then we're supposed to trace. */
tracename = pdisp->source_filename;
if (tracename == NULL) {
goto error;
}
file_tracer = pdisp->file_tracer;
if (file_tracer == NULL) {
goto error;
}
if (file_tracer != Py_None) {
plugin = PyObject_GetAttr(file_tracer, str__coverage_plugin);
if (plugin == NULL) {
goto error;
}
plugin_name = PyObject_GetAttr(plugin, str__coverage_plugin_name);
if (plugin_name == NULL) {
goto error;
}
}
has_dynamic_filename = pdisp->has_dynamic_filename;
if (has_dynamic_filename == NULL) {
goto error;
}
if (has_dynamic_filename == Py_True) {
STATS( self->stats.pycalls++; )
next_tracename = PyObject_CallMethodObjArgs(
file_tracer, str_dynamic_source_filename,
tracename, frame, NULL
);
if (next_tracename == NULL) {
/* An exception from the function. Alert the user with a
* warning and a traceback.
*/
CTracer_disable_plugin(self, disposition);
/* Because we handled the error, goto ok. */
goto ok;
}
tracename = next_tracename;
if (tracename != Py_None) {
/* Check the dynamic source filename against the include rules. */
PyObject * included = NULL;
int should_include;
included = PyDict_GetItem(self->should_trace_cache, tracename);
if (included == NULL) {
PyObject * should_include_bool;
if (PyErr_Occurred()) {
goto error;
}
STATS( self->stats.files++; )
STATS( self->stats.pycalls++; )
should_include_bool = PyObject_CallFunctionObjArgs(self->check_include, tracename, frame, NULL);
if (should_include_bool == NULL) {
goto error;
}
should_include = (should_include_bool == Py_True);
Py_DECREF(should_include_bool);
if (PyDict_SetItem(self->should_trace_cache, tracename, should_include ? disposition : Py_None) < 0) {
goto error;
}
}
else {
should_include = (included != Py_None);
}
if (!should_include) {
tracename = Py_None;
}
}
}
}
else {
tracename = Py_None;
}
if (tracename != Py_None) {
PyObject * file_data;
BOOL had_error = FALSE;
PyObject * res;
res = PyObject_CallFunctionObjArgs(self->lock_data, NULL);
if (res == NULL) {
goto error;
}
file_data = PyDict_GetItem(self->data, tracename);
if (file_data == NULL) {
if (PyErr_Occurred()) {
had_error = TRUE;
goto unlock;
}
file_data = PySet_New(NULL);
if (file_data == NULL) {
had_error = TRUE;
goto unlock;
}
ret2 = PyDict_SetItem(self->data, tracename, file_data);
if (ret2 < 0) {
had_error = TRUE;
goto unlock;
}
/* If the disposition mentions a plugin, record that. */
if (file_tracer != Py_None) {
ret2 = PyDict_SetItem(self->file_tracers, tracename, plugin_name);
if (ret2 < 0) {
had_error = TRUE;
goto unlock;
}
}
}
else {
/* PyDict_GetItem gives a borrowed reference. Own it. */
Py_INCREF(file_data);
}
unlock:
res = PyObject_CallFunctionObjArgs(self->unlock_data, NULL);
if (res == NULL) {
goto error;
}
if (had_error) {
goto error;
}
Py_XDECREF(self->pcur_entry->file_data);
self->pcur_entry->file_data = file_data;
self->pcur_entry->file_tracer = file_tracer;
SHOWLOG(PyFrame_GetLineNumber(frame), filename, "traced");
}
else {
Py_XDECREF(self->pcur_entry->file_data);
self->pcur_entry->file_data = NULL;
self->pcur_entry->file_tracer = Py_None;
MyFrame_NoTraceLines(frame);
SHOWLOG(PyFrame_GetLineNumber(frame), filename, "skipped");
}
self->pcur_entry->disposition = disposition;
/* Make the frame right in case settrace(gettrace()) happens. */
MyFrame_SetTrace(frame, self);
/* A call event is really a "start frame" event, and can happen for
* re-entering a generator also. How we tell the difference depends on
* the version of Python.
*/
BOOL real_call = FALSE;
#ifdef RESUME
/*
* The current opcode is guaranteed to be RESUME. The argument
* determines what kind of resume it is.
*/
pCode = MyCode_GetCode(MyFrame_GetCode(frame));
real_call = (PyBytes_AS_STRING(pCode)[MyFrame_GetLasti(frame) + 1] == 0);
#else
// f_lasti is -1 for a true call, and a real byte offset for a generator re-entry.
real_call = (MyFrame_GetLasti(frame) < 0);
#endif
if (real_call) {
self->pcur_entry->last_line = -MyFrame_GetCode(frame)->co_firstlineno;
}
else {
self->pcur_entry->last_line = PyFrame_GetLineNumber(frame);
}
ok:
ret = RET_OK;
error:
#ifdef RESUME
MyCode_FreeCode(pCode);
#endif
Py_XDECREF(next_tracename);
Py_XDECREF(disposition);
Py_XDECREF(plugin);
Py_XDECREF(plugin_name);
return ret;
}
static void
CTracer_disable_plugin(CTracer *self, PyObject * disposition)
{
PyObject * ret;
PyErr_Print();
STATS( self->stats.pycalls++; )
ret = PyObject_CallFunctionObjArgs(self->disable_plugin, disposition, NULL);
if (ret == NULL) {
goto error;
}
Py_DECREF(ret);
return;
error:
/* This function doesn't return a status, so if an error happens, print it,
* but don't interrupt the flow. */
/* PySys_WriteStderr is nicer, but is not in the public API. */
fprintf(stderr, "Error occurred while disabling plug-in:\n");
PyErr_Print();
}
static int
CTracer_unpack_pair(CTracer *self, PyObject *pair, int *p_one, int *p_two)
{
int ret = RET_ERROR;
int the_int;
PyObject * pyint = NULL;
int index;
if (!PyTuple_Check(pair) || PyTuple_Size(pair) != 2) {
PyErr_SetString(
PyExc_TypeError,
"line_number_range must return 2-tuple"
);
goto error;
}
for (index = 0; index < 2; index++) {
pyint = PyTuple_GetItem(pair, index);
if (pyint == NULL) {
goto error;
}
if (pyint_as_int(pyint, &the_int) < 0) {
goto error;
}
*(index == 0 ? p_one : p_two) = the_int;
}
ret = RET_OK;
error:
return ret;
}
static int
CTracer_handle_line(CTracer *self, PyFrameObject *frame)
{
int ret = RET_ERROR;
int ret2;
STATS( self->stats.lines++; )
if (self->pdata_stack->depth >= 0) {
SHOWLOG(PyFrame_GetLineNumber(frame), MyFrame_GetCode(frame)->co_filename, "line");
if (self->pcur_entry->file_data) {
int lineno_from = -1;
int lineno_to = -1;
/* We're tracing in this frame: record something. */
if (self->pcur_entry->file_tracer != Py_None) {
PyObject * from_to = NULL;
STATS( self->stats.pycalls++; )
from_to = PyObject_CallMethodObjArgs(self->pcur_entry->file_tracer, str_line_number_range, frame, NULL);
if (from_to == NULL) {
CTracer_disable_plugin(self, self->pcur_entry->disposition);
goto ok;
}
ret2 = CTracer_unpack_pair(self, from_to, &lineno_from, &lineno_to);
Py_DECREF(from_to);
if (ret2 < 0) {
CTracer_disable_plugin(self, self->pcur_entry->disposition);
goto ok;
}
}
else {
lineno_from = lineno_to = PyFrame_GetLineNumber(frame);
}
if (lineno_from != -1) {
for (; lineno_from <= lineno_to; lineno_from++) {
if (self->tracing_arcs) {
/* Tracing arcs: key is (last_line,this_line). */
if (CTracer_record_pair(self, self->pcur_entry->last_line, lineno_from) < 0) {
goto error;
}
}
else {
/* Tracing lines: key is simply this_line. */
PyObject * this_line = PyLong_FromLong((long)lineno_from);
if (this_line == NULL) {
goto error;
}
ret2 = PySet_Add(self->pcur_entry->file_data, this_line);
Py_DECREF(this_line);
if (ret2 < 0) {
goto error;
}
}
self->pcur_entry->last_line = lineno_from;
}
}
}
}
ok:
ret = RET_OK;
error:
return ret;
}
static int
CTracer_handle_return(CTracer *self, PyFrameObject *frame)
{
int ret = RET_ERROR;
PyObject * pCode = NULL;
STATS( self->stats.returns++; )
/* A near-copy of this code is above in the missing-return handler. */
if (CTracer_set_pdata_stack(self) < 0) {
goto error;
}
self->pcur_entry = &self->pdata_stack->stack[self->pdata_stack->depth];
if (self->pdata_stack->depth >= 0) {
if (self->tracing_arcs && self->pcur_entry->file_data) {
BOOL real_return = FALSE;
pCode = MyCode_GetCode(MyFrame_GetCode(frame));
int lasti = MyFrame_GetLasti(frame);
Py_ssize_t code_size = PyBytes_GET_SIZE(pCode);
unsigned char * code_bytes = (unsigned char *)PyBytes_AS_STRING(pCode);
#ifdef RESUME
if (lasti == code_size - 2) {
real_return = TRUE;
}
else {
#if ENV_LASTI_IS_YIELD
lasti += 2;
#endif
real_return = (code_bytes[lasti] != RESUME);
}
#else
/* Need to distinguish between RETURN_VALUE and YIELD_VALUE. Read
* the current bytecode to see what it is. In unusual circumstances
* (Cython code), co_code can be the empty string, so range-check
* f_lasti before reading the byte.
*/
BOOL is_yield = FALSE;
BOOL is_yield_from = FALSE;
if (lasti < code_size) {
is_yield = (code_bytes[lasti] == YIELD_VALUE);
if (lasti + 2 < code_size) {
is_yield_from = (code_bytes[lasti + 2] == YIELD_FROM);
}
}
real_return = !(is_yield || is_yield_from);
#endif
if (real_return) {
int first = MyFrame_GetCode(frame)->co_firstlineno;
if (CTracer_record_pair(self, self->pcur_entry->last_line, -first) < 0) {
goto error;
}
}
}
/* If this frame started a context, then returning from it ends the context. */
if (self->pcur_entry->started_context) {
PyObject * val;
Py_DECREF(self->context);
self->context = Py_None;
Py_INCREF(self->context);
STATS( self->stats.pycalls++; )
val = PyObject_CallFunctionObjArgs(self->switch_context, self->context, NULL);
if (val == NULL) {
goto error;
}
Py_DECREF(val);
}
/* Pop the stack. */
SHOWLOG(PyFrame_GetLineNumber(frame), MyFrame_GetCode(frame)->co_filename, "return");
self->pdata_stack->depth--;
self->pcur_entry = &self->pdata_stack->stack[self->pdata_stack->depth];
}
ret = RET_OK;
error:
MyCode_FreeCode(pCode);
return ret;
}
/*
* The Trace Function
*/
static int
CTracer_trace(CTracer *self, PyFrameObject *frame, int what, PyObject *arg_unused)
{
int ret = RET_ERROR;
#if DO_NOTHING
return RET_OK;
#endif
if (!self->started) {
/* If CTracer.stop() has been called from another thread, the tracer
is still active in the current thread. Let's deactivate ourselves
now. */
PyEval_SetTrace(NULL, NULL);
return RET_OK;
}
#if WHAT_LOG || TRACE_LOG
PyObject * ascii = NULL;
#endif
#if WHAT_LOG
const char * w = "XXX ";
if (what <= (int)(sizeof(what_sym)/sizeof(const char *))) {
w = what_sym[what];
}
ascii = PyUnicode_AsASCIIString(MyFrame_GetCode(frame)->co_filename);
printf("%x trace: f:%x %s @ %s %d\n", (int)self, (int)frame, what_sym[what], PyBytes_AS_STRING(ascii), PyFrame_GetLineNumber(frame));
Py_DECREF(ascii);
#endif
#if TRACE_LOG
ascii = PyUnicode_AsASCIIString(MyFrame_GetCode(frame)->co_filename);
if (strstr(PyBytes_AS_STRING(ascii), start_file) && PyFrame_GetLineNumber(frame) == start_line) {
logging = TRUE;
}
Py_DECREF(ascii);
#endif
self->activity = TRUE;
switch (what) {
case PyTrace_CALL:
if (CTracer_handle_call(self, frame) < 0) {
goto error;
}
break;
case PyTrace_RETURN:
if (CTracer_handle_return(self, frame) < 0) {
goto error;
}
break;
case PyTrace_LINE:
if (CTracer_handle_line(self, frame) < 0) {
goto error;
}
break;
default:
STATS( self->stats.others++; )
break;
}
ret = RET_OK;
goto cleanup;
error:
STATS( self->stats.errors++; )
cleanup:
return ret;
}
/*
* Python has two ways to set the trace function: sys.settrace(fn), which
* takes a Python callable, and PyEval_SetTrace(func, obj), which takes
* a C function and a Python object. The way these work together is that
* sys.settrace(pyfn) calls PyEval_SetTrace(builtin_func, pyfn), using the
* Python callable as the object in PyEval_SetTrace. So sys.gettrace()
* simply returns the Python object used as the second argument to
* PyEval_SetTrace. So sys.gettrace() will return our self parameter, which
* means it must be callable to be used in sys.settrace().
*
* So we make ourself callable, equivalent to invoking our trace function.
*/
static PyObject *
CTracer_call(CTracer *self, PyObject *args, PyObject *kwds)
{
PyFrameObject *frame;
PyObject *what_str;
PyObject *arg;
int what;
PyObject *ret = NULL;
PyObject * ascii = NULL;
#if DO_NOTHING
CRASH
#endif
static char *what_names[] = {
"call", "exception", "line", "return",
"c_call", "c_exception", "c_return",
NULL
};
static char *kwlist[] = {"frame", "event", "arg", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O!O|i:Tracer_call", kwlist,
&PyFrame_Type, &frame, &PyUnicode_Type, &what_str, &arg)) {
goto done;
}
/* In Python, the what argument is a string, we need to find an int
for the C function. */
for (what = 0; what_names[what]; what++) {
int should_break;
ascii = PyUnicode_AsASCIIString(what_str);
should_break = !strcmp(PyBytes_AS_STRING(ascii), what_names[what]);
Py_DECREF(ascii);
if (should_break) {
break;
}
}
#if WHAT_LOG
ascii = PyUnicode_AsASCIIString(MyFrame_GetCode(frame)->co_filename);
printf("pytrace: %s @ %s %d\n", what_sym[what], PyBytes_AS_STRING(ascii), PyFrame_GetLineNumber(frame));
Py_DECREF(ascii);
#endif
/* Invoke the C function, and return ourselves. */
if (CTracer_trace(self, frame, what, arg) == RET_OK) {
Py_INCREF(self);
ret = (PyObject *)self;
}
/* For better speed, install ourselves the C way so that future calls go
directly to CTracer_trace, without this intermediate function.
Only do this if this is a CALL event, since new trace functions only
take effect then. If we don't condition it on CALL, then we'll clobber
the new trace function before it has a chance to get called. To
understand why, there are three internal values to track: frame.f_trace,
c_tracefunc, and c_traceobj. They are explained here:
https://nedbatchelder.com/text/trace-function.html
Without the conditional on PyTrace_CALL, this is what happens:
def func(): # f_trace c_tracefunc c_traceobj
# -------------- -------------- --------------
# CTracer CTracer.trace CTracer
sys.settrace(my_func)
# CTracer trampoline my_func
# Now Python calls trampoline(CTracer), which calls this function
# which calls PyEval_SetTrace below, setting us as the tracer again:
# CTracer CTracer.trace CTracer
# and it's as if the settrace never happened.
*/
if (what == PyTrace_CALL) {
PyEval_SetTrace((Py_tracefunc)CTracer_trace, (PyObject*)self);
}
done:
return ret;
}
static PyObject *
CTracer_start(CTracer *self, PyObject *args_unused)
{
PyEval_SetTrace((Py_tracefunc)CTracer_trace, (PyObject*)self);
self->started = TRUE;
self->tracing_arcs = self->trace_arcs && PyObject_IsTrue(self->trace_arcs);
/* start() returns a trace function usable with sys.settrace() */
Py_INCREF(self);
return (PyObject *)self;
}
static PyObject *
CTracer_stop(CTracer *self, PyObject *args_unused)
{
if (self->started) {
/* Set the started flag only. The actual call to
PyEval_SetTrace(NULL, NULL) is delegated to the callback
itself to ensure that it called from the right thread.
*/
self->started = FALSE;
}
Py_RETURN_NONE;
}
static PyObject *
CTracer_activity(CTracer *self, PyObject *args_unused)
{
if (self->activity) {
Py_RETURN_TRUE;
}
else {
Py_RETURN_FALSE;
}
}
static PyObject *
CTracer_reset_activity(CTracer *self, PyObject *args_unused)
{
self->activity = FALSE;
Py_RETURN_NONE;
}
static PyObject *
CTracer_get_stats(CTracer *self, PyObject *args_unused)
{
#if COLLECT_STATS
return Py_BuildValue(
"{sI,sI,sI,sI,sI,sI,si,sI,sI,sI}",
"calls", self->stats.calls,
"lines", self->stats.lines,
"returns", self->stats.returns,
"others", self->stats.others,
"files", self->stats.files,
"stack_reallocs", self->stats.stack_reallocs,
"stack_alloc", self->pdata_stack->alloc,
"errors", self->stats.errors,
"pycalls", self->stats.pycalls,
"start_context_calls", self->stats.start_context_calls
);
#else
Py_RETURN_NONE;
#endif /* COLLECT_STATS */
}
static PyMemberDef
CTracer_members[] = {
{ "should_trace", T_OBJECT, offsetof(CTracer, should_trace), 0,
PyDoc_STR("Function indicating whether to trace a file.") },
{ "check_include", T_OBJECT, offsetof(CTracer, check_include), 0,
PyDoc_STR("Function indicating whether to include a file.") },
{ "warn", T_OBJECT, offsetof(CTracer, warn), 0,
PyDoc_STR("Function for issuing warnings.") },
{ "concur_id_func", T_OBJECT, offsetof(CTracer, concur_id_func), 0,
PyDoc_STR("Function for determining concurrency context") },
{ "data", T_OBJECT, offsetof(CTracer, data), 0,
PyDoc_STR("The raw dictionary of trace data.") },
{ "file_tracers", T_OBJECT, offsetof(CTracer, file_tracers), 0,
PyDoc_STR("Mapping from file name to plugin name.") },
{ "should_trace_cache", T_OBJECT, offsetof(CTracer, should_trace_cache), 0,
PyDoc_STR("Dictionary caching should_trace results.") },
{ "trace_arcs", T_OBJECT, offsetof(CTracer, trace_arcs), 0,
PyDoc_STR("Should we trace arcs, or just lines?") },
{ "should_start_context", T_OBJECT, offsetof(CTracer, should_start_context), 0,
PyDoc_STR("Function for starting contexts.") },
{ "switch_context", T_OBJECT, offsetof(CTracer, switch_context), 0,
PyDoc_STR("Function for switching to a new context.") },
{ "lock_data", T_OBJECT, offsetof(CTracer, lock_data), 0,
PyDoc_STR("Function for locking access to self.data.") },
{ "unlock_data", T_OBJECT, offsetof(CTracer, unlock_data), 0,
PyDoc_STR("Function for unlocking access to self.data.") },
{ "disable_plugin", T_OBJECT, offsetof(CTracer, disable_plugin), 0,
PyDoc_STR("Function for disabling a plugin.") },
{ NULL }
};
static PyMethodDef
CTracer_methods[] = {
{ "start", (PyCFunction) CTracer_start, METH_VARARGS,
PyDoc_STR("Start the tracer") },
{ "stop", (PyCFunction) CTracer_stop, METH_VARARGS,
PyDoc_STR("Stop the tracer") },
{ "get_stats", (PyCFunction) CTracer_get_stats, METH_VARARGS,
PyDoc_STR("Get statistics about the tracing") },
{ "activity", (PyCFunction) CTracer_activity, METH_VARARGS,
PyDoc_STR("Has there been any activity?") },
{ "reset_activity", (PyCFunction) CTracer_reset_activity, METH_VARARGS,
PyDoc_STR("Reset the activity flag") },
{ NULL }
};
PyTypeObject
CTracerType = {
PyVarObject_HEAD_INIT(NULL, 0)
"coverage.CTracer", /*tp_name*/
sizeof(CTracer), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor)CTracer_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash */
(ternaryfunc)CTracer_call, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
"CTracer objects", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
CTracer_methods, /* tp_methods */
CTracer_members, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)CTracer_init, /* tp_init */
0, /* tp_alloc */
0, /* tp_new */
};