This source file includes following definitions.
- CTracer_init
- CTracer_dealloc
- indent
- showlog
- CTracer_record_pair
- CTracer_trace
- CTracer_call
- CTracer_start
- CTracer_stop
- CTracer_get_stats
- PyInit_tracer
- inittracer
#include "Python.h"
#include "compile.h"
#include "eval.h"
#include "structmember.h"
#include "frameobject.h"
#undef WHAT_LOG
#undef TRACE_LOG
#undef COLLECT_STATS
#if COLLECT_STATS
#define STATS(x) x
#else
#define STATS(x)
#endif
#ifndef Py_TYPE
#define Py_TYPE(o) (((PyObject*)(o))->ob_type)
#endif
#if PY_MAJOR_VERSION >= 3
#define MyText_Type PyUnicode_Type
#define MyText_Check(o) PyUnicode_Check(o)
#define MyText_AS_STRING(o) PyBytes_AS_STRING(PyUnicode_AsASCIIString(o))
#define MyInt_FromLong(l) PyLong_FromLong(l)
#define MyType_HEAD_INIT PyVarObject_HEAD_INIT(NULL, 0)
#else
#define MyText_Type PyString_Type
#define MyText_Check(o) PyString_Check(o)
#define MyText_AS_STRING(o) PyString_AS_STRING(o)
#define MyInt_FromLong(l) PyInt_FromLong(l)
#define MyType_HEAD_INIT PyObject_HEAD_INIT(NULL) 0,
#endif
#define RET_OK 0
#define RET_ERROR -1
typedef struct {
PyObject * file_data;
int last_line;
} DataStackEntry;
typedef struct {
PyObject_HEAD
PyObject * should_trace;
PyObject * warn;
PyObject * data;
PyObject * should_trace_cache;
PyObject * arcs;
int started;
int tracing_arcs;
int depth;
DataStackEntry * data_stack;
int data_stack_alloc;
PyObject * cur_file_data;
int last_line;
PyFrameObject * last_exc_back;
int last_exc_firstlineno;
#if COLLECT_STATS
struct {
unsigned calls;
unsigned lines;
unsigned returns;
unsigned exceptions;
unsigned others;
unsigned new_files;
unsigned missed_returns;
unsigned stack_reallocs;
unsigned errors;
} stats;
#endif
} CTracer;
#define STACK_DELTA 100
static int
CTracer_init(CTracer *self, PyObject *args_unused, PyObject *kwds_unused)
{
#if COLLECT_STATS
self->stats.calls = 0;
self->stats.lines = 0;
self->stats.returns = 0;
self->stats.exceptions = 0;
self->stats.others = 0;
self->stats.new_files = 0;
self->stats.missed_returns = 0;
self->stats.stack_reallocs = 0;
self->stats.errors = 0;
#endif
self->should_trace = NULL;
self->warn = NULL;
self->data = NULL;
self->should_trace_cache = NULL;
self->arcs = NULL;
self->started = 0;
self->tracing_arcs = 0;
self->depth = -1;
self->data_stack = PyMem_Malloc(STACK_DELTA*sizeof(DataStackEntry));
if (self->data_stack == NULL) {
STATS( self->stats.errors++; )
PyErr_NoMemory();
return RET_ERROR;
}
self->data_stack_alloc = STACK_DELTA;
self->cur_file_data = NULL;
self->last_line = -1;
self->last_exc_back = NULL;
return RET_OK;
}
static void
CTracer_dealloc(CTracer *self)
{
if (self->started) {
PyEval_SetTrace(NULL, NULL);
}
Py_XDECREF(self->should_trace);
Py_XDECREF(self->warn);
Py_XDECREF(self->data);
Py_XDECREF(self->should_trace_cache);
PyMem_Free(self->data_stack);
Py_TYPE(self)->tp_free((PyObject*)self);
}
#if TRACE_LOG
static const char *
indent(int n)
{
static const char * spaces =
" "
" "
" "
" "
;
return spaces + strlen(spaces) - n*2;
}
static int logging = 0;
static const char * start_file = "tests/views";
static int start_line = 27;
static void
showlog(int depth, int lineno, PyObject * filename, const char * msg)
{
if (logging) {
printf("%s%3d ", indent(depth), depth);
if (lineno) {
printf("%4d", lineno);
}
else {
printf(" ");
}
if (filename) {
printf(" %s", MyText_AS_STRING(filename));
}
if (msg) {
printf(" %s", msg);
}
printf("\n");
}
}
#define SHOWLOG(a,b,c,d) showlog(a,b,c,d)
#else
#define SHOWLOG(a,b,c,d)
#endif
#if WHAT_LOG
static const char * what_sym[] = {"CALL", "EXC ", "LINE", "RET "};
#endif
static int
CTracer_record_pair(CTracer *self, int l1, int l2)
{
int ret = RET_OK;
PyObject * t = PyTuple_New(2);
if (t != NULL) {
PyTuple_SET_ITEM(t, 0, MyInt_FromLong(l1));
PyTuple_SET_ITEM(t, 1, MyInt_FromLong(l2));
if (PyDict_SetItem(self->cur_file_data, t, Py_None) < 0) {
STATS( self->stats.errors++; )
ret = RET_ERROR;
}
Py_DECREF(t);
}
else {
STATS( self->stats.errors++; )
ret = RET_ERROR;
}
return ret;
}
static int
CTracer_trace(CTracer *self, PyFrameObject *frame, int what, PyObject *arg_unused)
{
int ret = RET_OK;
PyObject * filename = NULL;
PyObject * tracename = NULL;
#if WHAT_LOG
if (what <= sizeof(what_sym)/sizeof(const char *)) {
printf("trace: %s @ %s %d\n", what_sym[what], MyText_AS_STRING(frame->f_code->co_filename), frame->f_lineno);
}
#endif
#if TRACE_LOG
if (strstr(MyText_AS_STRING(frame->f_code->co_filename), start_file) && frame->f_lineno == start_line) {
logging = 1;
}
#endif
if (self->last_exc_back) {
if (frame == self->last_exc_back) {
STATS( self->stats.missed_returns++; )
if (self->depth >= 0) {
if (self->tracing_arcs && self->cur_file_data) {
if (CTracer_record_pair(self, self->last_line, -self->last_exc_firstlineno) < 0) {
return RET_ERROR;
}
}
SHOWLOG(self->depth, frame->f_lineno, frame->f_code->co_filename, "missedreturn");
self->cur_file_data = self->data_stack[self->depth].file_data;
self->last_line = self->data_stack[self->depth].last_line;
self->depth--;
}
}
self->last_exc_back = NULL;
}
switch (what) {
case PyTrace_CALL:
STATS( self->stats.calls++; )
self->depth++;
if (self->depth >= self->data_stack_alloc) {
STATS( self->stats.stack_reallocs++; )
int bigger = self->data_stack_alloc + STACK_DELTA;
DataStackEntry * bigger_data_stack = PyMem_Realloc(self->data_stack, bigger * sizeof(DataStackEntry));
if (bigger_data_stack == NULL) {
STATS( self->stats.errors++; )
PyErr_NoMemory();
self->depth--;
return RET_ERROR;
}
self->data_stack = bigger_data_stack;
self->data_stack_alloc = bigger;
}
self->data_stack[self->depth].file_data = self->cur_file_data;
self->data_stack[self->depth].last_line = self->last_line;
filename = frame->f_code->co_filename;
tracename = PyDict_GetItem(self->should_trace_cache, filename);
if (tracename == NULL) {
STATS( self->stats.new_files++; )
PyObject * args = Py_BuildValue("(OO)", filename, frame);
tracename = PyObject_Call(self->should_trace, args, NULL);
Py_DECREF(args);
if (tracename == NULL) {
STATS( self->stats.errors++; )
return RET_ERROR;
}
if (PyDict_SetItem(self->should_trace_cache, filename, tracename) < 0) {
STATS( self->stats.errors++; )
return RET_ERROR;
}
}
else {
Py_INCREF(tracename);
}
if (MyText_Check(tracename)) {
PyObject * file_data = PyDict_GetItem(self->data, tracename);
if (file_data == NULL) {
file_data = PyDict_New();
if (file_data == NULL) {
STATS( self->stats.errors++; )
return RET_ERROR;
}
ret = PyDict_SetItem(self->data, tracename, file_data);
Py_DECREF(file_data);
if (ret < 0) {
STATS( self->stats.errors++; )
return RET_ERROR;
}
}
self->cur_file_data = file_data;
Py_INCREF(self);
frame->f_trace = (PyObject*)self;
SHOWLOG(self->depth, frame->f_lineno, filename, "traced");
}
else {
self->cur_file_data = NULL;
SHOWLOG(self->depth, frame->f_lineno, filename, "skipped");
}
Py_DECREF(tracename);
self->last_line = -1;
break;
case PyTrace_RETURN:
STATS( self->stats.returns++; )
if (self->depth >= 0) {
if (self->tracing_arcs && self->cur_file_data) {
int first = frame->f_code->co_firstlineno;
if (CTracer_record_pair(self, self->last_line, -first) < 0) {
return RET_ERROR;
}
}
SHOWLOG(self->depth, frame->f_lineno, frame->f_code->co_filename, "return");
self->cur_file_data = self->data_stack[self->depth].file_data;
self->last_line = self->data_stack[self->depth].last_line;
self->depth--;
}
break;
case PyTrace_LINE:
STATS( self->stats.lines++; )
if (self->depth >= 0) {
SHOWLOG(self->depth, frame->f_lineno, frame->f_code->co_filename, "line");
if (self->cur_file_data) {
if (self->tracing_arcs) {
if (CTracer_record_pair(self, self->last_line, frame->f_lineno) < 0) {
return RET_ERROR;
}
}
else {
PyObject * this_line = MyInt_FromLong(frame->f_lineno);
if (this_line == NULL) {
STATS( self->stats.errors++; )
return RET_ERROR;
}
ret = PyDict_SetItem(self->cur_file_data, this_line, Py_None);
Py_DECREF(this_line);
if (ret < 0) {
STATS( self->stats.errors++; )
return RET_ERROR;
}
}
}
self->last_line = frame->f_lineno;
}
break;
case PyTrace_EXCEPTION:
STATS( self->stats.exceptions++; )
self->last_exc_back = frame->f_back;
self->last_exc_firstlineno = frame->f_code->co_firstlineno;
break;
default:
STATS( self->stats.others++; )
break;
}
return RET_OK;
}
static PyObject *
CTracer_call(CTracer *self, PyObject *args, PyObject *kwds)
{
PyFrameObject *frame;
PyObject *what_str;
PyObject *arg;
int lineno = 0;
int what;
int orig_lineno;
PyObject *ret = NULL;
static char *what_names[] = {
"call", "exception", "line", "return",
"c_call", "c_exception", "c_return",
NULL
};
#if WHAT_LOG
printf("pytrace\n");
#endif
static char *kwlist[] = {"frame", "event", "arg", "lineno", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O!O|i:Tracer_call", kwlist,
&PyFrame_Type, &frame, &MyText_Type, &what_str, &arg, &lineno)) {
goto done;
}
for (what = 0; what_names[what]; what++) {
if (!strcmp(MyText_AS_STRING(what_str), what_names[what])) {
break;
}
}
orig_lineno = frame->f_lineno;
if (lineno > 0) {
frame->f_lineno = lineno;
}
if (CTracer_trace(self, frame, what, arg) == RET_OK) {
Py_INCREF(self);
ret = (PyObject *)self;
}
frame->f_lineno = orig_lineno;
done:
return ret;
}
static PyObject *
CTracer_start(CTracer *self, PyObject *args_unused)
{
PyEval_SetTrace((Py_tracefunc)CTracer_trace, (PyObject*)self);
self->started = 1;
self->tracing_arcs = self->arcs && PyObject_IsTrue(self->arcs);
self->last_line = -1;
Py_INCREF(self);
return (PyObject *)self;
}
static PyObject *
CTracer_stop(CTracer *self, PyObject *args_unused)
{
if (self->started) {
PyEval_SetTrace(NULL, NULL);
self->started = 0;
}
return Py_BuildValue("");
}
static PyObject *
CTracer_get_stats(CTracer *self)
{
#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,
"exceptions", self->stats.exceptions,
"others", self->stats.others,
"new_files", self->stats.new_files,
"missed_returns", self->stats.missed_returns,
"stack_reallocs", self->stats.stack_reallocs,
"stack_alloc", self->data_stack_alloc,
"errors", self->stats.errors
);
#else
return Py_BuildValue("");
#endif
}
static PyMemberDef
CTracer_members[] = {
{ "should_trace", T_OBJECT, offsetof(CTracer, should_trace), 0,
PyDoc_STR("Function indicating whether to trace a file.") },
{ "warn", T_OBJECT, offsetof(CTracer, warn), 0,
PyDoc_STR("Function for issuing warnings.") },
{ "data", T_OBJECT, offsetof(CTracer, data), 0,
PyDoc_STR("The raw dictionary of trace data.") },
{ "should_trace_cache", T_OBJECT, offsetof(CTracer, should_trace_cache), 0,
PyDoc_STR("Dictionary caching should_trace results.") },
{ "arcs", T_OBJECT, offsetof(CTracer, arcs), 0,
PyDoc_STR("Should we trace arcs, or just lines?") },
{ 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") },
{ NULL }
};
static PyTypeObject
CTracerType = {
MyType_HEAD_INIT
"coverage.CTracer",
sizeof(CTracer),
0,
(destructor)CTracer_dealloc,
0,
0,
0,
0,
0,
0,
0,
0,
0,
(ternaryfunc)CTracer_call,
0,
0,
0,
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
"CTracer objects",
0,
0,
0,
0,
0,
0,
CTracer_methods,
CTracer_members,
0,
0,
0,
0,
0,
0,
(initproc)CTracer_init,
0,
0,
};
#define MODULE_DOC PyDoc_STR("Fast coverage tracer.")
#if PY_MAJOR_VERSION >= 3
static PyModuleDef
moduledef = {
PyModuleDef_HEAD_INIT,
"coverage.tracer",
MODULE_DOC,
-1,
NULL,
NULL,
NULL,
NULL,
NULL
};
PyObject *
PyInit_tracer(void)
{
PyObject * mod = PyModule_Create(&moduledef);
if (mod == NULL) {
return NULL;
}
CTracerType.tp_new = PyType_GenericNew;
if (PyType_Ready(&CTracerType) < 0) {
Py_DECREF(mod);
return NULL;
}
Py_INCREF(&CTracerType);
PyModule_AddObject(mod, "CTracer", (PyObject *)&CTracerType);
return mod;
}
#else
void
inittracer(void)
{
PyObject * mod;
mod = Py_InitModule3("coverage.tracer", NULL, MODULE_DOC);
if (mod == NULL) {
return;
}
CTracerType.tp_new = PyType_GenericNew;
if (PyType_Ready(&CTracerType) < 0) {
return;
}
Py_INCREF(&CTracerType);
PyModule_AddObject(mod, "CTracer", (PyObject *)&CTracerType);
}
#endif