Add a Notification Settings Button to all web notifications behind the web platform...
[chromium-blink-merge.git] / third_party / pycoverage / coverage / tracer.c
blob97dd113b8b3a814dbaa39d7fd0f7e1822f7a699f
1 /* C-based Tracer for Coverage. */
3 #include "Python.h"
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. */
14 #if COLLECT_STATS
15 #define STATS(x) x
16 #else
17 #define STATS(x)
18 #endif
20 /* Py 2.x and 3.x compatibility */
22 #ifndef Py_TYPE
23 #define Py_TYPE(o) (((PyObject*)(o))->ob_type)
24 #endif
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)
36 #else
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,
46 #endif /* Py3k */
48 /* The values returned to indicate ok or error. */
49 #define RET_OK 0
50 #define RET_ERROR -1
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
54 frame.
56 typedef struct {
57 PyObject * file_data; /* PyMem_Malloc'ed, a borrowed ref. */
58 int last_line;
59 } DataStackEntry;
61 /* The CTracer type. */
63 typedef struct {
64 PyObject_HEAD
66 /* Python objects manipulated directly by the Collector class. */
67 PyObject * should_trace;
68 PyObject * warn;
69 PyObject * data;
70 PyObject * should_trace_cache;
71 PyObject * arcs;
73 /* Has the tracer been started? */
74 int started;
75 /* Are we tracing arcs, or just lines? */
76 int tracing_arcs;
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
87 (None).
89 /* The index of the last-used entry in data_stack. */
90 int depth;
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.
101 int last_line;
103 /* The parent frame for the last exception event, to fix missing returns. */
104 PyFrameObject * last_exc_back;
105 int last_exc_firstlineno;
107 #if COLLECT_STATS
108 struct {
109 unsigned int calls;
110 unsigned int lines;
111 unsigned int returns;
112 unsigned int exceptions;
113 unsigned int others;
114 unsigned int new_files;
115 unsigned int missed_returns;
116 unsigned int stack_reallocs;
117 unsigned int errors;
118 } stats;
119 #endif /* COLLECT_STATS */
120 } CTracer;
122 #define STACK_DELTA 100
124 static int
125 CTracer_init(CTracer *self, PyObject *args_unused, PyObject *kwds_unused)
127 #if COLLECT_STATS
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;
140 self->warn = NULL;
141 self->data = NULL;
142 self->should_trace_cache = NULL;
143 self->arcs = NULL;
145 self->started = 0;
146 self->tracing_arcs = 0;
148 self->depth = -1;
149 self->data_stack = PyMem_Malloc(STACK_DELTA*sizeof(DataStackEntry));
150 if (self->data_stack == NULL) {
151 STATS( self->stats.errors++; )
152 PyErr_NoMemory();
153 return RET_ERROR;
155 self->data_stack_alloc = STACK_DELTA;
157 self->cur_file_data = NULL;
158 self->last_line = -1;
160 self->last_exc_back = NULL;
162 return RET_OK;
165 static void
166 CTracer_dealloc(CTracer *self)
168 if (self->started) {
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);
182 #if TRACE_LOG
183 static const char *
184 indent(int n)
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;
200 static void
201 showlog(int depth, int lineno, PyObject * filename, const char * msg)
203 if (logging) {
204 printf("%s%3d ", indent(depth), depth);
205 if (lineno) {
206 printf("%4d", lineno);
208 else {
209 printf(" ");
211 if (filename) {
212 PyObject *ascii = MyText_AS_BYTES(filename);
213 printf(" %s", MyText_AS_STRING(ascii));
214 Py_DECREF(ascii);
216 if (msg) {
217 printf(" %s", msg);
219 printf("\n");
223 #define SHOWLOG(a,b,c,d) showlog(a,b,c,d)
224 #else
225 #define SHOWLOG(a,b,c,d)
226 #endif /* TRACE_LOG */
228 #if WHAT_LOG
229 static const char * what_sym[] = {"CALL", "EXC ", "LINE", "RET "};
230 #endif
232 /* Record a pair of integers in self->cur_file_data. */
233 static int
234 CTracer_record_pair(CTracer *self, int l1, int l2)
236 int ret = RET_OK;
238 PyObject * t = Py_BuildValue("(ii)", l1, l2);
239 if (t != NULL) {
240 if (PyDict_SetItem(self->cur_file_data, t, Py_None) < 0) {
241 STATS( self->stats.errors++; )
242 ret = RET_ERROR;
244 Py_DECREF(t);
246 else {
247 STATS( self->stats.errors++; )
248 ret = RET_ERROR;
250 return ret;
254 * The Trace Function
256 static int
257 CTracer_trace(CTracer *self, PyFrameObject *frame, int what, PyObject *arg_unused)
259 int ret = RET_OK;
260 PyObject * filename = NULL;
261 PyObject * tracename = NULL;
262 #if WHAT_LOG || TRACE_LOG
263 PyObject * ascii = NULL;
264 #endif
266 #if WHAT_LOG
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);
270 Py_DECREF(ascii);
272 #endif
274 #if TRACE_LOG
275 ascii = MyText_AS_BYTES(frame->f_code->co_filename);
276 if (strstr(MyText_AS_STRING(ascii), start_file) && frame->f_lineno == start_line) {
277 logging = 1;
279 Py_DECREF(ascii);
280 #endif
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) {
299 return RET_ERROR;
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;
305 self->depth--;
308 self->last_exc_back = NULL;
312 switch (what) {
313 case PyTrace_CALL: /* 0 */
314 STATS( self->stats.calls++; )
315 /* Grow the stack. */
316 self->depth++;
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++; )
324 PyErr_NoMemory();
325 self->depth--;
326 return RET_ERROR;
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);
345 Py_DECREF(args);
346 if (tracename == NULL) {
347 /* An error occurred inside should_trace. */
348 STATS( self->stats.errors++; )
349 return RET_ERROR;
351 if (PyDict_SetItem(self->should_trace_cache, filename, tracename) < 0) {
352 STATS( self->stats.errors++; )
353 return RET_ERROR;
356 else {
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++; )
367 return RET_ERROR;
369 ret = PyDict_SetItem(self->data, tracename, file_data);
370 Py_DECREF(file_data);
371 if (ret < 0) {
372 STATS( self->stats.errors++; )
373 return RET_ERROR;
376 self->cur_file_data = file_data;
377 /* Make the frame right in case settrace(gettrace()) happens. */
378 Py_INCREF(self);
379 frame->f_trace = (PyObject*)self;
380 SHOWLOG(self->depth, frame->f_lineno, filename, "traced");
382 else {
383 self->cur_file_data = NULL;
384 SHOWLOG(self->depth, frame->f_lineno, filename, "skipped");
387 Py_DECREF(tracename);
389 self->last_line = -1;
390 break;
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) {
399 return RET_ERROR;
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;
406 self->depth--;
408 break;
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) {
419 return RET_ERROR;
422 else {
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++; )
427 return RET_ERROR;
429 ret = PyDict_SetItem(self->cur_file_data, this_line, Py_None);
430 Py_DECREF(this_line);
431 if (ret < 0) {
432 STATS( self->stats.errors++; )
433 return RET_ERROR;
437 self->last_line = frame->f_lineno;
439 break;
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;
459 break;
461 default:
462 STATS( self->stats.others++; )
463 break;
466 return RET_OK;
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.
489 static PyObject *
490 CTracer_call(CTracer *self, PyObject *args, PyObject *kwds)
492 PyFrameObject *frame;
493 PyObject *what_str;
494 PyObject *arg;
495 int lineno = 0;
496 int what;
497 int orig_lineno;
498 PyObject *ret = NULL;
500 static char *what_names[] = {
501 "call", "exception", "line", "return",
502 "c_call", "c_exception", "c_return",
503 NULL
506 #if WHAT_LOG
507 printf("pytrace\n");
508 #endif
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)) {
514 goto done;
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]);
522 Py_DECREF(ascii);
523 if (should_break) {
524 break;
528 /* Save off the frame's lineno, and use the forced one, if provided. */
529 orig_lineno = frame->f_lineno;
530 if (lineno > 0) {
531 frame->f_lineno = lineno;
534 /* Invoke the C function, and return ourselves. */
535 if (CTracer_trace(self, frame, what, arg) == RET_OK) {
536 Py_INCREF(self);
537 ret = (PyObject *)self;
540 /* Clean up. */
541 frame->f_lineno = orig_lineno;
543 done:
544 return ret;
547 static PyObject *
548 CTracer_start(CTracer *self, PyObject *args_unused)
550 PyEval_SetTrace((Py_tracefunc)CTracer_trace, (PyObject*)self);
551 self->started = 1;
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() */
556 Py_INCREF(self);
557 return (PyObject *)self;
560 static PyObject *
561 CTracer_stop(CTracer *self, PyObject *args_unused)
563 if (self->started) {
564 PyEval_SetTrace(NULL, NULL);
565 self->started = 0;
568 return Py_BuildValue("");
571 static PyObject *
572 CTracer_get_stats(CTracer *self)
574 #if COLLECT_STATS
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
588 #else
589 return Py_BuildValue("");
590 #endif /* COLLECT_STATS */
593 static PyMemberDef
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?") },
610 { NULL }
613 static PyMethodDef
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") },
624 { NULL }
627 static PyTypeObject
628 CTracerType = {
629 MyType_HEAD_INIT
630 "coverage.CTracer", /*tp_name*/
631 sizeof(CTracer), /*tp_basicsize*/
632 0, /*tp_itemsize*/
633 (destructor)CTracer_dealloc, /*tp_dealloc*/
634 0, /*tp_print*/
635 0, /*tp_getattr*/
636 0, /*tp_setattr*/
637 0, /*tp_compare*/
638 0, /*tp_repr*/
639 0, /*tp_as_number*/
640 0, /*tp_as_sequence*/
641 0, /*tp_as_mapping*/
642 0, /*tp_hash */
643 (ternaryfunc)CTracer_call, /*tp_call*/
644 0, /*tp_str*/
645 0, /*tp_getattro*/
646 0, /*tp_setattro*/
647 0, /*tp_as_buffer*/
648 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
649 "CTracer objects", /* tp_doc */
650 0, /* tp_traverse */
651 0, /* tp_clear */
652 0, /* tp_richcompare */
653 0, /* tp_weaklistoffset */
654 0, /* tp_iter */
655 0, /* tp_iternext */
656 CTracer_methods, /* tp_methods */
657 CTracer_members, /* tp_members */
658 0, /* tp_getset */
659 0, /* tp_base */
660 0, /* tp_dict */
661 0, /* tp_descr_get */
662 0, /* tp_descr_set */
663 0, /* tp_dictoffset */
664 (initproc)CTracer_init, /* tp_init */
665 0, /* tp_alloc */
666 0, /* tp_new */
669 /* Module definition */
671 #define MODULE_DOC PyDoc_STR("Fast coverage tracer.")
673 #if PY_MAJOR_VERSION >= 3
675 static PyModuleDef
676 moduledef = {
677 PyModuleDef_HEAD_INIT,
678 "coverage.tracer",
679 MODULE_DOC,
681 NULL, /* methods */
682 NULL,
683 NULL, /* traverse */
684 NULL, /* clear */
685 NULL
689 PyObject *
690 PyInit_tracer(void)
692 PyObject * mod = PyModule_Create(&moduledef);
693 if (mod == NULL) {
694 return NULL;
697 CTracerType.tp_new = PyType_GenericNew;
698 if (PyType_Ready(&CTracerType) < 0) {
699 Py_DECREF(mod);
700 return NULL;
703 Py_INCREF(&CTracerType);
704 PyModule_AddObject(mod, "CTracer", (PyObject *)&CTracerType);
706 return mod;
709 #else
711 void
712 inittracer(void)
714 PyObject * mod;
716 mod = Py_InitModule3("coverage.tracer", NULL, MODULE_DOC);
717 if (mod == NULL) {
718 return;
721 CTracerType.tp_new = PyType_GenericNew;
722 if (PyType_Ready(&CTracerType) < 0) {
723 return;
726 Py_INCREF(&CTracerType);
727 PyModule_AddObject(mod, "CTracer", (PyObject *)&CTracerType);
730 #endif /* Py3k */