1 /* C-based Tracer for Coverage. */
4 #include "compile.h" /* in 2.3, this wasn't part of Python.h */
5 #include "eval.h" /* or this. */
6 #include "structmember.h"
7 #include "frameobject.h"
9 /* Compile-time debugging helpers */
10 #undef WHAT_LOG /* Define to log the WHAT params in the trace function. */
11 #undef TRACE_LOG /* Define to log our bookkeeping. */
12 #undef COLLECT_STATS /* Collect counters: stats are printed when tracer is stopped. */
20 /* Py 2.x and 3.x compatibility */
23 #define Py_TYPE(o) (((PyObject*)(o))->ob_type)
26 #if PY_MAJOR_VERSION >= 3
28 #define MyText_Type PyUnicode_Type
29 #define MyText_Check(o) PyUnicode_Check(o)
30 #define MyText_AS_BYTES(o) PyUnicode_AsASCIIString(o)
31 #define MyText_AS_STRING(o) PyBytes_AS_STRING(o)
32 #define MyInt_FromLong(l) PyLong_FromLong(l)
34 #define MyType_HEAD_INIT PyVarObject_HEAD_INIT(NULL, 0)
38 #define MyText_Type PyString_Type
39 #define MyText_Check(o) PyString_Check(o)
40 #define MyText_AS_BYTES(o) (Py_INCREF(o), o)
41 #define MyText_AS_STRING(o) PyString_AS_STRING(o)
42 #define MyInt_FromLong(l) PyInt_FromLong(l)
44 #define MyType_HEAD_INIT PyObject_HEAD_INIT(NULL) 0,
48 /* The values returned to indicate ok or error. */
52 /* An entry on the data stack. For each call frame, we need to record the
53 dictionary to capture data, and the last line number executed in that
57 PyObject
* file_data
; /* PyMem_Malloc'ed, a borrowed ref. */
61 /* The CTracer type. */
66 /* Python objects manipulated directly by the Collector class. */
67 PyObject
* should_trace
;
70 PyObject
* should_trace_cache
;
73 /* Has the tracer been started? */
75 /* Are we tracing arcs, or just lines? */
79 The data stack is a stack of dictionaries. Each dictionary collects
80 data for a single source file. The data stack parallels the call stack:
81 each call pushes the new frame's file data onto the data stack, and each
82 return pops file data off.
84 The file data is a dictionary whose form depends on the tracing options.
85 If tracing arcs, the keys are line number pairs. If not tracing arcs,
86 the keys are line numbers. In both cases, the value is irrelevant
89 /* The index of the last-used entry in data_stack. */
91 /* The file data at each level, or NULL if not recording. */
92 DataStackEntry
* data_stack
;
93 int data_stack_alloc
; /* number of entries allocated at data_stack. */
95 /* The current file_data dictionary. Borrowed. */
96 PyObject
* cur_file_data
;
98 /* The line number of the last line recorded, for tracing arcs.
99 -1 means there was no previous line, as when entering a code object.
103 /* The parent frame for the last exception event, to fix missing returns. */
104 PyFrameObject
* last_exc_back
;
105 int last_exc_firstlineno
;
111 unsigned int returns
;
112 unsigned int exceptions
;
114 unsigned int new_files
;
115 unsigned int missed_returns
;
116 unsigned int stack_reallocs
;
119 #endif /* COLLECT_STATS */
122 #define STACK_DELTA 100
125 CTracer_init(CTracer
*self
, PyObject
*args_unused
, PyObject
*kwds_unused
)
128 self
->stats
.calls
= 0;
129 self
->stats
.lines
= 0;
130 self
->stats
.returns
= 0;
131 self
->stats
.exceptions
= 0;
132 self
->stats
.others
= 0;
133 self
->stats
.new_files
= 0;
134 self
->stats
.missed_returns
= 0;
135 self
->stats
.stack_reallocs
= 0;
136 self
->stats
.errors
= 0;
137 #endif /* COLLECT_STATS */
139 self
->should_trace
= NULL
;
142 self
->should_trace_cache
= NULL
;
146 self
->tracing_arcs
= 0;
149 self
->data_stack
= PyMem_Malloc(STACK_DELTA
*sizeof(DataStackEntry
));
150 if (self
->data_stack
== NULL
) {
151 STATS( self
->stats
.errors
++; )
155 self
->data_stack_alloc
= STACK_DELTA
;
157 self
->cur_file_data
= NULL
;
158 self
->last_line
= -1;
160 self
->last_exc_back
= NULL
;
166 CTracer_dealloc(CTracer
*self
)
169 PyEval_SetTrace(NULL
, NULL
);
172 Py_XDECREF(self
->should_trace
);
173 Py_XDECREF(self
->warn
);
174 Py_XDECREF(self
->data
);
175 Py_XDECREF(self
->should_trace_cache
);
177 PyMem_Free(self
->data_stack
);
179 Py_TYPE(self
)->tp_free((PyObject
*)self
);
186 static const char * spaces
=
192 return spaces
+ strlen(spaces
) - n
*2;
195 static int logging
= 0;
196 /* Set these constants to be a file substring and line number to start logging. */
197 static const char * start_file
= "tests/views";
198 static int start_line
= 27;
201 showlog(int depth
, int lineno
, PyObject
* filename
, const char * msg
)
204 printf("%s%3d ", indent(depth
), depth
);
206 printf("%4d", lineno
);
212 PyObject
*ascii
= MyText_AS_BYTES(filename
);
213 printf(" %s", MyText_AS_STRING(ascii
));
223 #define SHOWLOG(a,b,c,d) showlog(a,b,c,d)
225 #define SHOWLOG(a,b,c,d)
226 #endif /* TRACE_LOG */
229 static const char * what_sym
[] = {"CALL", "EXC ", "LINE", "RET "};
232 /* Record a pair of integers in self->cur_file_data. */
234 CTracer_record_pair(CTracer
*self
, int l1
, int l2
)
238 PyObject
* t
= Py_BuildValue("(ii)", l1
, l2
);
240 if (PyDict_SetItem(self
->cur_file_data
, t
, Py_None
) < 0) {
241 STATS( self
->stats
.errors
++; )
247 STATS( self
->stats
.errors
++; )
257 CTracer_trace(CTracer
*self
, PyFrameObject
*frame
, int what
, PyObject
*arg_unused
)
260 PyObject
* filename
= NULL
;
261 PyObject
* tracename
= NULL
;
262 #if WHAT_LOG || TRACE_LOG
263 PyObject
* ascii
= NULL
;
267 if (what
<= sizeof(what_sym
)/sizeof(const char *)) {
268 ascii
= MyText_AS_BYTES(frame
->f_code
->co_filename
);
269 printf("trace: %s @ %s %d\n", what_sym
[what
], MyText_AS_STRING(ascii
), frame
->f_lineno
);
275 ascii
= MyText_AS_BYTES(frame
->f_code
->co_filename
);
276 if (strstr(MyText_AS_STRING(ascii
), start_file
) && frame
->f_lineno
== start_line
) {
282 /* See below for details on missing-return detection. */
283 if (self
->last_exc_back
) {
284 if (frame
== self
->last_exc_back
) {
285 /* Looks like someone forgot to send a return event. We'll clear
286 the exception state and do the RETURN code here. Notice that the
287 frame we have in hand here is not the correct frame for the RETURN,
288 that frame is gone. Our handling for RETURN doesn't need the
289 actual frame, but we do log it, so that will look a little off if
290 you're looking at the detailed log.
292 If someday we need to examine the frame when doing RETURN, then
293 we'll need to keep more of the missed frame's state.
295 STATS( self
->stats
.missed_returns
++; )
296 if (self
->depth
>= 0) {
297 if (self
->tracing_arcs
&& self
->cur_file_data
) {
298 if (CTracer_record_pair(self
, self
->last_line
, -self
->last_exc_firstlineno
) < 0) {
302 SHOWLOG(self
->depth
, frame
->f_lineno
, frame
->f_code
->co_filename
, "missedreturn");
303 self
->cur_file_data
= self
->data_stack
[self
->depth
].file_data
;
304 self
->last_line
= self
->data_stack
[self
->depth
].last_line
;
308 self
->last_exc_back
= NULL
;
313 case PyTrace_CALL
: /* 0 */
314 STATS( self
->stats
.calls
++; )
315 /* Grow the stack. */
317 if (self
->depth
>= self
->data_stack_alloc
) {
318 STATS( self
->stats
.stack_reallocs
++; )
319 /* We've outgrown our data_stack array: make it bigger. */
320 int bigger
= self
->data_stack_alloc
+ STACK_DELTA
;
321 DataStackEntry
* bigger_data_stack
= PyMem_Realloc(self
->data_stack
, bigger
* sizeof(DataStackEntry
));
322 if (bigger_data_stack
== NULL
) {
323 STATS( self
->stats
.errors
++; )
328 self
->data_stack
= bigger_data_stack
;
329 self
->data_stack_alloc
= bigger
;
332 /* Push the current state on the stack. */
333 self
->data_stack
[self
->depth
].file_data
= self
->cur_file_data
;
334 self
->data_stack
[self
->depth
].last_line
= self
->last_line
;
336 /* Check if we should trace this line. */
337 filename
= frame
->f_code
->co_filename
;
338 tracename
= PyDict_GetItem(self
->should_trace_cache
, filename
);
339 if (tracename
== NULL
) {
340 STATS( self
->stats
.new_files
++; )
341 /* We've never considered this file before. */
342 /* Ask should_trace about it. */
343 PyObject
* args
= Py_BuildValue("(OO)", filename
, frame
);
344 tracename
= PyObject_Call(self
->should_trace
, args
, NULL
);
346 if (tracename
== NULL
) {
347 /* An error occurred inside should_trace. */
348 STATS( self
->stats
.errors
++; )
351 if (PyDict_SetItem(self
->should_trace_cache
, filename
, tracename
) < 0) {
352 STATS( self
->stats
.errors
++; )
357 Py_INCREF(tracename
);
360 /* If tracename is a string, then we're supposed to trace. */
361 if (MyText_Check(tracename
)) {
362 PyObject
* file_data
= PyDict_GetItem(self
->data
, tracename
);
363 if (file_data
== NULL
) {
364 file_data
= PyDict_New();
365 if (file_data
== NULL
) {
366 STATS( self
->stats
.errors
++; )
369 ret
= PyDict_SetItem(self
->data
, tracename
, file_data
);
370 Py_DECREF(file_data
);
372 STATS( self
->stats
.errors
++; )
376 self
->cur_file_data
= file_data
;
377 /* Make the frame right in case settrace(gettrace()) happens. */
379 frame
->f_trace
= (PyObject
*)self
;
380 SHOWLOG(self
->depth
, frame
->f_lineno
, filename
, "traced");
383 self
->cur_file_data
= NULL
;
384 SHOWLOG(self
->depth
, frame
->f_lineno
, filename
, "skipped");
387 Py_DECREF(tracename
);
389 self
->last_line
= -1;
392 case PyTrace_RETURN
: /* 3 */
393 STATS( self
->stats
.returns
++; )
394 /* A near-copy of this code is above in the missing-return handler. */
395 if (self
->depth
>= 0) {
396 if (self
->tracing_arcs
&& self
->cur_file_data
) {
397 int first
= frame
->f_code
->co_firstlineno
;
398 if (CTracer_record_pair(self
, self
->last_line
, -first
) < 0) {
403 SHOWLOG(self
->depth
, frame
->f_lineno
, frame
->f_code
->co_filename
, "return");
404 self
->cur_file_data
= self
->data_stack
[self
->depth
].file_data
;
405 self
->last_line
= self
->data_stack
[self
->depth
].last_line
;
410 case PyTrace_LINE
: /* 2 */
411 STATS( self
->stats
.lines
++; )
412 if (self
->depth
>= 0) {
413 SHOWLOG(self
->depth
, frame
->f_lineno
, frame
->f_code
->co_filename
, "line");
414 if (self
->cur_file_data
) {
415 /* We're tracing in this frame: record something. */
416 if (self
->tracing_arcs
) {
417 /* Tracing arcs: key is (last_line,this_line). */
418 if (CTracer_record_pair(self
, self
->last_line
, frame
->f_lineno
) < 0) {
423 /* Tracing lines: key is simply this_line. */
424 PyObject
* this_line
= MyInt_FromLong(frame
->f_lineno
);
425 if (this_line
== NULL
) {
426 STATS( self
->stats
.errors
++; )
429 ret
= PyDict_SetItem(self
->cur_file_data
, this_line
, Py_None
);
430 Py_DECREF(this_line
);
432 STATS( self
->stats
.errors
++; )
437 self
->last_line
= frame
->f_lineno
;
441 case PyTrace_EXCEPTION
:
442 /* Some code (Python 2.3, and pyexpat anywhere) fires an exception event
443 without a return event. To detect that, we'll keep a copy of the
444 parent frame for an exception event. If the next event is in that
445 frame, then we must have returned without a return event. We can
446 synthesize the missing event then.
448 Python itself fixed this problem in 2.4. Pyexpat still has the bug.
449 I've reported the problem with pyexpat as http://bugs.python.org/issue6359 .
450 If it gets fixed, this code should still work properly. Maybe some day
451 the bug will be fixed everywhere coverage.py is supported, and we can
452 remove this missing-return detection.
454 More about this fix: http://nedbatchelder.com/blog/200907/a_nasty_little_bug.html
456 STATS( self
->stats
.exceptions
++; )
457 self
->last_exc_back
= frame
->f_back
;
458 self
->last_exc_firstlineno
= frame
->f_code
->co_firstlineno
;
462 STATS( self
->stats
.others
++; )
470 * Python has two ways to set the trace function: sys.settrace(fn), which
471 * takes a Python callable, and PyEval_SetTrace(func, obj), which takes
472 * a C function and a Python object. The way these work together is that
473 * sys.settrace(pyfn) calls PyEval_SetTrace(builtin_func, pyfn), using the
474 * Python callable as the object in PyEval_SetTrace. So sys.gettrace()
475 * simply returns the Python object used as the second argument to
476 * PyEval_SetTrace. So sys.gettrace() will return our self parameter, which
477 * means it must be callable to be used in sys.settrace().
479 * So we make our self callable, equivalent to invoking our trace function.
481 * To help with the process of replaying stored frames, this function has an
482 * optional keyword argument:
484 * def CTracer_call(frame, event, arg, lineno=0)
486 * If provided, the lineno argument is used as the line number, and the
487 * frame's f_lineno member is ignored.
490 CTracer_call(CTracer
*self
, PyObject
*args
, PyObject
*kwds
)
492 PyFrameObject
*frame
;
498 PyObject
*ret
= NULL
;
500 static char *what_names
[] = {
501 "call", "exception", "line", "return",
502 "c_call", "c_exception", "c_return",
510 static char *kwlist
[] = {"frame", "event", "arg", "lineno", NULL
};
512 if (!PyArg_ParseTupleAndKeywords(args
, kwds
, "O!O!O|i:Tracer_call", kwlist
,
513 &PyFrame_Type
, &frame
, &MyText_Type
, &what_str
, &arg
, &lineno
)) {
517 /* In Python, the what argument is a string, we need to find an int
518 for the C function. */
519 for (what
= 0; what_names
[what
]; what
++) {
520 PyObject
*ascii
= MyText_AS_BYTES(what_str
);
521 int should_break
= !strcmp(MyText_AS_STRING(ascii
), what_names
[what
]);
528 /* Save off the frame's lineno, and use the forced one, if provided. */
529 orig_lineno
= frame
->f_lineno
;
531 frame
->f_lineno
= lineno
;
534 /* Invoke the C function, and return ourselves. */
535 if (CTracer_trace(self
, frame
, what
, arg
) == RET_OK
) {
537 ret
= (PyObject
*)self
;
541 frame
->f_lineno
= orig_lineno
;
548 CTracer_start(CTracer
*self
, PyObject
*args_unused
)
550 PyEval_SetTrace((Py_tracefunc
)CTracer_trace
, (PyObject
*)self
);
552 self
->tracing_arcs
= self
->arcs
&& PyObject_IsTrue(self
->arcs
);
553 self
->last_line
= -1;
555 /* start() returns a trace function usable with sys.settrace() */
557 return (PyObject
*)self
;
561 CTracer_stop(CTracer
*self
, PyObject
*args_unused
)
564 PyEval_SetTrace(NULL
, NULL
);
568 return Py_BuildValue("");
572 CTracer_get_stats(CTracer
*self
)
575 return Py_BuildValue(
576 "{sI,sI,sI,sI,sI,sI,sI,sI,si,sI}",
577 "calls", self
->stats
.calls
,
578 "lines", self
->stats
.lines
,
579 "returns", self
->stats
.returns
,
580 "exceptions", self
->stats
.exceptions
,
581 "others", self
->stats
.others
,
582 "new_files", self
->stats
.new_files
,
583 "missed_returns", self
->stats
.missed_returns
,
584 "stack_reallocs", self
->stats
.stack_reallocs
,
585 "stack_alloc", self
->data_stack_alloc
,
586 "errors", self
->stats
.errors
589 return Py_BuildValue("");
590 #endif /* COLLECT_STATS */
594 CTracer_members
[] = {
595 { "should_trace", T_OBJECT
, offsetof(CTracer
, should_trace
), 0,
596 PyDoc_STR("Function indicating whether to trace a file.") },
598 { "warn", T_OBJECT
, offsetof(CTracer
, warn
), 0,
599 PyDoc_STR("Function for issuing warnings.") },
601 { "data", T_OBJECT
, offsetof(CTracer
, data
), 0,
602 PyDoc_STR("The raw dictionary of trace data.") },
604 { "should_trace_cache", T_OBJECT
, offsetof(CTracer
, should_trace_cache
), 0,
605 PyDoc_STR("Dictionary caching should_trace results.") },
607 { "arcs", T_OBJECT
, offsetof(CTracer
, arcs
), 0,
608 PyDoc_STR("Should we trace arcs, or just lines?") },
614 CTracer_methods
[] = {
615 { "start", (PyCFunction
) CTracer_start
, METH_VARARGS
,
616 PyDoc_STR("Start the tracer") },
618 { "stop", (PyCFunction
) CTracer_stop
, METH_VARARGS
,
619 PyDoc_STR("Stop the tracer") },
621 { "get_stats", (PyCFunction
) CTracer_get_stats
, METH_VARARGS
,
622 PyDoc_STR("Get statistics about the tracing") },
630 "coverage.CTracer", /*tp_name*/
631 sizeof(CTracer
), /*tp_basicsize*/
633 (destructor
)CTracer_dealloc
, /*tp_dealloc*/
640 0, /*tp_as_sequence*/
643 (ternaryfunc
)CTracer_call
, /*tp_call*/
648 Py_TPFLAGS_DEFAULT
| Py_TPFLAGS_BASETYPE
, /*tp_flags*/
649 "CTracer objects", /* tp_doc */
652 0, /* tp_richcompare */
653 0, /* tp_weaklistoffset */
656 CTracer_methods
, /* tp_methods */
657 CTracer_members
, /* tp_members */
661 0, /* tp_descr_get */
662 0, /* tp_descr_set */
663 0, /* tp_dictoffset */
664 (initproc
)CTracer_init
, /* tp_init */
669 /* Module definition */
671 #define MODULE_DOC PyDoc_STR("Fast coverage tracer.")
673 #if PY_MAJOR_VERSION >= 3
677 PyModuleDef_HEAD_INIT
,
692 PyObject
* mod
= PyModule_Create(&moduledef
);
697 CTracerType
.tp_new
= PyType_GenericNew
;
698 if (PyType_Ready(&CTracerType
) < 0) {
703 Py_INCREF(&CTracerType
);
704 PyModule_AddObject(mod
, "CTracer", (PyObject
*)&CTracerType
);
716 mod
= Py_InitModule3("coverage.tracer", NULL
, MODULE_DOC
);
721 CTracerType
.tp_new
= PyType_GenericNew
;
722 if (PyType_Ready(&CTracerType
) < 0) {
726 Py_INCREF(&CTracerType
);
727 PyModule_AddObject(mod
, "CTracer", (PyObject
*)&CTracerType
);