1 /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
2 /* For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt */
3
4 /* C-based Tracer for coverage.py. */
5
6 #include "util.h"
7 #include "datastack.h"
8 #include "filedisp.h"
9 #include "tracer.h"
10
11 /* Python C API helpers. */
12
13 static int
pyint_as_int(PyObject * pyint,int * pint)14 pyint_as_int(PyObject * pyint, int *pint)
15 {
16 int the_int = MyInt_AsInt(pyint);
17 if (the_int == -1 && PyErr_Occurred()) {
18 return RET_ERROR;
19 }
20
21 *pint = the_int;
22 return RET_OK;
23 }
24
25
26 /* Interned strings to speed GetAttr etc. */
27
28 static PyObject *str_trace;
29 static PyObject *str_file_tracer;
30 static PyObject *str__coverage_enabled;
31 static PyObject *str__coverage_plugin;
32 static PyObject *str__coverage_plugin_name;
33 static PyObject *str_dynamic_source_filename;
34 static PyObject *str_line_number_range;
35
36 int
CTracer_intern_strings(void)37 CTracer_intern_strings(void)
38 {
39 int ret = RET_ERROR;
40
41 #define INTERN_STRING(v, s) \
42 v = MyText_InternFromString(s); \
43 if (v == NULL) { \
44 goto error; \
45 }
46
47 INTERN_STRING(str_trace, "trace")
48 INTERN_STRING(str_file_tracer, "file_tracer")
49 INTERN_STRING(str__coverage_enabled, "_coverage_enabled")
50 INTERN_STRING(str__coverage_plugin, "_coverage_plugin")
51 INTERN_STRING(str__coverage_plugin_name, "_coverage_plugin_name")
52 INTERN_STRING(str_dynamic_source_filename, "dynamic_source_filename")
53 INTERN_STRING(str_line_number_range, "line_number_range")
54
55 ret = RET_OK;
56
57 error:
58 return ret;
59 }
60
61 static void CTracer_disable_plugin(CTracer *self, PyObject * disposition);
62
63 static int
CTracer_init(CTracer * self,PyObject * args_unused,PyObject * kwds_unused)64 CTracer_init(CTracer *self, PyObject *args_unused, PyObject *kwds_unused)
65 {
66 int ret = RET_ERROR;
67
68 if (DataStack_init(&self->stats, &self->data_stack) < 0) {
69 goto error;
70 }
71
72 self->pdata_stack = &self->data_stack;
73
74 self->cur_entry.last_line = -1;
75
76 ret = RET_OK;
77 goto ok;
78
79 error:
80 STATS( self->stats.errors++; )
81
82 ok:
83 return ret;
84 }
85
86 static void
CTracer_dealloc(CTracer * self)87 CTracer_dealloc(CTracer *self)
88 {
89 int i;
90
91 if (self->started) {
92 PyEval_SetTrace(NULL, NULL);
93 }
94
95 Py_XDECREF(self->should_trace);
96 Py_XDECREF(self->check_include);
97 Py_XDECREF(self->warn);
98 Py_XDECREF(self->concur_id_func);
99 Py_XDECREF(self->data);
100 Py_XDECREF(self->file_tracers);
101 Py_XDECREF(self->should_trace_cache);
102
103 DataStack_dealloc(&self->stats, &self->data_stack);
104 if (self->data_stacks) {
105 for (i = 0; i < self->data_stacks_used; i++) {
106 DataStack_dealloc(&self->stats, self->data_stacks + i);
107 }
108 PyMem_Free(self->data_stacks);
109 }
110
111 Py_XDECREF(self->data_stack_index);
112
113 Py_TYPE(self)->tp_free((PyObject*)self);
114 }
115
116 #if TRACE_LOG
117 static const char *
indent(int n)118 indent(int n)
119 {
120 static const char * spaces =
121 " "
122 " "
123 " "
124 " "
125 ;
126 return spaces + strlen(spaces) - n*2;
127 }
128
129 static int logging = 0;
130 /* Set these constants to be a file substring and line number to start logging. */
131 static const char * start_file = "tests/views";
132 static int start_line = 27;
133
134 static void
showlog(int depth,int lineno,PyObject * filename,const char * msg)135 showlog(int depth, int lineno, PyObject * filename, const char * msg)
136 {
137 if (logging) {
138 printf("%s%3d ", indent(depth), depth);
139 if (lineno) {
140 printf("%4d", lineno);
141 }
142 else {
143 printf(" ");
144 }
145 if (filename) {
146 PyObject *ascii = MyText_AS_BYTES(filename);
147 printf(" %s", MyBytes_AS_STRING(ascii));
148 Py_DECREF(ascii);
149 }
150 if (msg) {
151 printf(" %s", msg);
152 }
153 printf("\n");
154 }
155 }
156
157 #define SHOWLOG(a,b,c,d) showlog(a,b,c,d)
158 #else
159 #define SHOWLOG(a,b,c,d)
160 #endif /* TRACE_LOG */
161
162 #if WHAT_LOG
163 static const char * what_sym[] = {"CALL", "EXC ", "LINE", "RET "};
164 #endif
165
166 /* Record a pair of integers in self->cur_entry.file_data. */
167 static int
CTracer_record_pair(CTracer * self,int l1,int l2)168 CTracer_record_pair(CTracer *self, int l1, int l2)
169 {
170 int ret = RET_ERROR;
171
172 PyObject * t = NULL;
173
174 t = Py_BuildValue("(ii)", l1, l2);
175 if (t == NULL) {
176 goto error;
177 }
178
179 if (PyDict_SetItem(self->cur_entry.file_data, t, Py_None) < 0) {
180 goto error;
181 }
182
183 ret = RET_OK;
184
185 error:
186 Py_XDECREF(t);
187
188 return ret;
189 }
190
191 /* Set self->pdata_stack to the proper data_stack to use. */
192 static int
CTracer_set_pdata_stack(CTracer * self)193 CTracer_set_pdata_stack(CTracer *self)
194 {
195 int ret = RET_ERROR;
196 PyObject * co_obj = NULL;
197 PyObject * stack_index = NULL;
198
199 if (self->concur_id_func != Py_None) {
200 int the_index = 0;
201
202 if (self->data_stack_index == NULL) {
203 PyObject * weakref = NULL;
204
205 weakref = PyImport_ImportModule("weakref");
206 if (weakref == NULL) {
207 goto error;
208 }
209 STATS( self->stats.pycalls++; )
210 self->data_stack_index = PyObject_CallMethod(weakref, "WeakKeyDictionary", NULL);
211 Py_XDECREF(weakref);
212
213 if (self->data_stack_index == NULL) {
214 goto error;
215 }
216 }
217
218 STATS( self->stats.pycalls++; )
219 co_obj = PyObject_CallObject(self->concur_id_func, NULL);
220 if (co_obj == NULL) {
221 goto error;
222 }
223 stack_index = PyObject_GetItem(self->data_stack_index, co_obj);
224 if (stack_index == NULL) {
225 /* PyObject_GetItem sets an exception if it didn't find the thing. */
226 PyErr_Clear();
227
228 /* A new concurrency object. Make a new data stack. */
229 the_index = self->data_stacks_used;
230 stack_index = MyInt_FromInt(the_index);
231 if (stack_index == NULL) {
232 goto error;
233 }
234 if (PyObject_SetItem(self->data_stack_index, co_obj, stack_index) < 0) {
235 goto error;
236 }
237 self->data_stacks_used++;
238 if (self->data_stacks_used >= self->data_stacks_alloc) {
239 int bigger = self->data_stacks_alloc + 10;
240 DataStack * bigger_stacks = PyMem_Realloc(self->data_stacks, bigger * sizeof(DataStack));
241 if (bigger_stacks == NULL) {
242 PyErr_NoMemory();
243 goto error;
244 }
245 self->data_stacks = bigger_stacks;
246 self->data_stacks_alloc = bigger;
247 }
248 DataStack_init(&self->stats, &self->data_stacks[the_index]);
249 }
250 else {
251 if (pyint_as_int(stack_index, &the_index) < 0) {
252 goto error;
253 }
254 }
255
256 self->pdata_stack = &self->data_stacks[the_index];
257 }
258 else {
259 self->pdata_stack = &self->data_stack;
260 }
261
262 ret = RET_OK;
263
264 error:
265
266 Py_XDECREF(co_obj);
267 Py_XDECREF(stack_index);
268
269 return ret;
270 }
271
272 /*
273 * Parts of the trace function.
274 */
275
276 static int
CTracer_check_missing_return(CTracer * self,PyFrameObject * frame)277 CTracer_check_missing_return(CTracer *self, PyFrameObject *frame)
278 {
279 int ret = RET_ERROR;
280
281 if (self->last_exc_back) {
282 if (frame == self->last_exc_back) {
283 /* Looks like someone forgot to send a return event. We'll clear
284 the exception state and do the RETURN code here. Notice that the
285 frame we have in hand here is not the correct frame for the RETURN,
286 that frame is gone. Our handling for RETURN doesn't need the
287 actual frame, but we do log it, so that will look a little off if
288 you're looking at the detailed log.
289
290 If someday we need to examine the frame when doing RETURN, then
291 we'll need to keep more of the missed frame's state.
292 */
293 STATS( self->stats.missed_returns++; )
294 if (CTracer_set_pdata_stack(self) < 0) {
295 goto error;
296 }
297 if (self->pdata_stack->depth >= 0) {
298 if (self->tracing_arcs && self->cur_entry.file_data) {
299 if (CTracer_record_pair(self, self->cur_entry.last_line, -self->last_exc_firstlineno) < 0) {
300 goto error;
301 }
302 }
303 SHOWLOG(self->pdata_stack->depth, frame->f_lineno, frame->f_code->co_filename, "missedreturn");
304 self->cur_entry = self->pdata_stack->stack[self->pdata_stack->depth];
305 self->pdata_stack->depth--;
306 }
307 }
308 self->last_exc_back = NULL;
309 }
310
311 ret = RET_OK;
312
313 error:
314
315 return ret;
316 }
317
318 static int
CTracer_handle_call(CTracer * self,PyFrameObject * frame)319 CTracer_handle_call(CTracer *self, PyFrameObject *frame)
320 {
321 int ret = RET_ERROR;
322 int ret2;
323
324 /* Owned references that we clean up at the very end of the function. */
325 PyObject * disposition = NULL;
326 PyObject * plugin = NULL;
327 PyObject * plugin_name = NULL;
328 PyObject * next_tracename = NULL;
329
330 /* Borrowed references. */
331 PyObject * filename = NULL;
332 PyObject * disp_trace = NULL;
333 PyObject * tracename = NULL;
334 PyObject * file_tracer = NULL;
335 PyObject * has_dynamic_filename = NULL;
336
337 CFileDisposition * pdisp = NULL;
338
339
340 STATS( self->stats.calls++; )
341 /* Grow the stack. */
342 if (CTracer_set_pdata_stack(self) < 0) {
343 goto error;
344 }
345 if (DataStack_grow(&self->stats, self->pdata_stack) < 0) {
346 goto error;
347 }
348
349 /* Push the current state on the stack. */
350 self->pdata_stack->stack[self->pdata_stack->depth] = self->cur_entry;
351
352 /* Check if we should trace this line. */
353 filename = frame->f_code->co_filename;
354 disposition = PyDict_GetItem(self->should_trace_cache, filename);
355 if (disposition == NULL) {
356 if (PyErr_Occurred()) {
357 goto error;
358 }
359 STATS( self->stats.new_files++; )
360
361 /* We've never considered this file before. */
362 /* Ask should_trace about it. */
363 STATS( self->stats.pycalls++; )
364 disposition = PyObject_CallFunctionObjArgs(self->should_trace, filename, frame, NULL);
365 if (disposition == NULL) {
366 /* An error occurred inside should_trace. */
367 goto error;
368 }
369 if (PyDict_SetItem(self->should_trace_cache, filename, disposition) < 0) {
370 goto error;
371 }
372 }
373 else {
374 Py_INCREF(disposition);
375 }
376
377 if (disposition == Py_None) {
378 /* A later check_include returned false, so don't trace it. */
379 disp_trace = Py_False;
380 }
381 else {
382 /* The object we got is a CFileDisposition, use it efficiently. */
383 pdisp = (CFileDisposition *) disposition;
384 disp_trace = pdisp->trace;
385 if (disp_trace == NULL) {
386 goto error;
387 }
388 }
389
390 if (disp_trace == Py_True) {
391 /* If tracename is a string, then we're supposed to trace. */
392 tracename = pdisp->source_filename;
393 if (tracename == NULL) {
394 goto error;
395 }
396 file_tracer = pdisp->file_tracer;
397 if (file_tracer == NULL) {
398 goto error;
399 }
400 if (file_tracer != Py_None) {
401 plugin = PyObject_GetAttr(file_tracer, str__coverage_plugin);
402 if (plugin == NULL) {
403 goto error;
404 }
405 plugin_name = PyObject_GetAttr(plugin, str__coverage_plugin_name);
406 if (plugin_name == NULL) {
407 goto error;
408 }
409 }
410 has_dynamic_filename = pdisp->has_dynamic_filename;
411 if (has_dynamic_filename == NULL) {
412 goto error;
413 }
414 if (has_dynamic_filename == Py_True) {
415 STATS( self->stats.pycalls++; )
416 next_tracename = PyObject_CallMethodObjArgs(
417 file_tracer, str_dynamic_source_filename,
418 tracename, frame, NULL
419 );
420 if (next_tracename == NULL) {
421 /* An exception from the function. Alert the user with a
422 * warning and a traceback.
423 */
424 CTracer_disable_plugin(self, disposition);
425 /* Because we handled the error, goto ok. */
426 goto ok;
427 }
428 tracename = next_tracename;
429
430 if (tracename != Py_None) {
431 /* Check the dynamic source filename against the include rules. */
432 PyObject * included = NULL;
433 int should_include;
434 included = PyDict_GetItem(self->should_trace_cache, tracename);
435 if (included == NULL) {
436 PyObject * should_include_bool;
437 if (PyErr_Occurred()) {
438 goto error;
439 }
440 STATS( self->stats.new_files++; )
441 STATS( self->stats.pycalls++; )
442 should_include_bool = PyObject_CallFunctionObjArgs(self->check_include, tracename, frame, NULL);
443 if (should_include_bool == NULL) {
444 goto error;
445 }
446 should_include = (should_include_bool == Py_True);
447 Py_DECREF(should_include_bool);
448 if (PyDict_SetItem(self->should_trace_cache, tracename, should_include ? disposition : Py_None) < 0) {
449 goto error;
450 }
451 }
452 else {
453 should_include = (included != Py_None);
454 }
455 if (!should_include) {
456 tracename = Py_None;
457 }
458 }
459 }
460 }
461 else {
462 tracename = Py_None;
463 }
464
465 if (tracename != Py_None) {
466 PyObject * file_data = PyDict_GetItem(self->data, tracename);
467
468 if (file_data == NULL) {
469 if (PyErr_Occurred()) {
470 goto error;
471 }
472 file_data = PyDict_New();
473 if (file_data == NULL) {
474 goto error;
475 }
476 ret2 = PyDict_SetItem(self->data, tracename, file_data);
477 Py_DECREF(file_data);
478 if (ret2 < 0) {
479 goto error;
480 }
481
482 /* If the disposition mentions a plugin, record that. */
483 if (file_tracer != Py_None) {
484 ret2 = PyDict_SetItem(self->file_tracers, tracename, plugin_name);
485 if (ret2 < 0) {
486 goto error;
487 }
488 }
489 }
490
491 self->cur_entry.file_data = file_data;
492 self->cur_entry.file_tracer = file_tracer;
493
494 /* Make the frame right in case settrace(gettrace()) happens. */
495 Py_INCREF(self);
496 frame->f_trace = (PyObject*)self;
497 SHOWLOG(self->pdata_stack->depth, frame->f_lineno, filename, "traced");
498 }
499 else {
500 self->cur_entry.file_data = NULL;
501 self->cur_entry.file_tracer = Py_None;
502 SHOWLOG(self->pdata_stack->depth, frame->f_lineno, filename, "skipped");
503 }
504
505 self->cur_entry.disposition = disposition;
506
507 /* A call event is really a "start frame" event, and can happen for
508 * re-entering a generator also. f_lasti is -1 for a true call, and a
509 * real byte offset for a generator re-entry.
510 */
511 self->cur_entry.last_line = (frame->f_lasti < 0) ? -1 : frame->f_lineno;
512
513 ok:
514 ret = RET_OK;
515
516 error:
517 Py_XDECREF(next_tracename);
518 Py_XDECREF(disposition);
519 Py_XDECREF(plugin);
520 Py_XDECREF(plugin_name);
521
522 return ret;
523 }
524
525
526 static void
CTracer_disable_plugin(CTracer * self,PyObject * disposition)527 CTracer_disable_plugin(CTracer *self, PyObject * disposition)
528 {
529 PyObject * file_tracer = NULL;
530 PyObject * plugin = NULL;
531 PyObject * plugin_name = NULL;
532 PyObject * msg = NULL;
533 PyObject * ignored = NULL;
534
535 PyErr_Print();
536
537 file_tracer = PyObject_GetAttr(disposition, str_file_tracer);
538 if (file_tracer == NULL) {
539 goto error;
540 }
541 if (file_tracer == Py_None) {
542 /* This shouldn't happen... */
543 goto ok;
544 }
545 plugin = PyObject_GetAttr(file_tracer, str__coverage_plugin);
546 if (plugin == NULL) {
547 goto error;
548 }
549 plugin_name = PyObject_GetAttr(plugin, str__coverage_plugin_name);
550 if (plugin_name == NULL) {
551 goto error;
552 }
553 msg = MyText_FromFormat(
554 "Disabling plugin '%s' due to previous exception",
555 MyText_AsString(plugin_name)
556 );
557 if (msg == NULL) {
558 goto error;
559 }
560 STATS( self->stats.pycalls++; )
561 ignored = PyObject_CallFunctionObjArgs(self->warn, msg, NULL);
562 if (ignored == NULL) {
563 goto error;
564 }
565
566 /* Disable the plugin for future files, and stop tracing this file. */
567 if (PyObject_SetAttr(plugin, str__coverage_enabled, Py_False) < 0) {
568 goto error;
569 }
570 if (PyObject_SetAttr(disposition, str_trace, Py_False) < 0) {
571 goto error;
572 }
573
574 goto ok;
575
576 error:
577 /* This function doesn't return a status, so if an error happens, print it,
578 * but don't interrupt the flow. */
579 /* PySys_WriteStderr is nicer, but is not in the public API. */
580 fprintf(stderr, "Error occurred while disabling plugin:\n");
581 PyErr_Print();
582
583 ok:
584 Py_XDECREF(file_tracer);
585 Py_XDECREF(plugin);
586 Py_XDECREF(plugin_name);
587 Py_XDECREF(msg);
588 Py_XDECREF(ignored);
589 }
590
591
592 static int
CTracer_unpack_pair(CTracer * self,PyObject * pair,int * p_one,int * p_two)593 CTracer_unpack_pair(CTracer *self, PyObject *pair, int *p_one, int *p_two)
594 {
595 int ret = RET_ERROR;
596 int the_int;
597 PyObject * pyint = NULL;
598 int index;
599
600 if (!PyTuple_Check(pair) || PyTuple_Size(pair) != 2) {
601 PyErr_SetString(
602 PyExc_TypeError,
603 "line_number_range must return 2-tuple"
604 );
605 goto error;
606 }
607
608 for (index = 0; index < 2; index++) {
609 pyint = PyTuple_GetItem(pair, index);
610 if (pyint == NULL) {
611 goto error;
612 }
613 if (pyint_as_int(pyint, &the_int) < 0) {
614 goto error;
615 }
616 *(index == 0 ? p_one : p_two) = the_int;
617 }
618
619 ret = RET_OK;
620
621 error:
622 return ret;
623 }
624
625 static int
CTracer_handle_line(CTracer * self,PyFrameObject * frame)626 CTracer_handle_line(CTracer *self, PyFrameObject *frame)
627 {
628 int ret = RET_ERROR;
629 int ret2;
630
631 STATS( self->stats.lines++; )
632 if (self->pdata_stack->depth >= 0) {
633 SHOWLOG(self->pdata_stack->depth, frame->f_lineno, frame->f_code->co_filename, "line");
634 if (self->cur_entry.file_data) {
635 int lineno_from = -1;
636 int lineno_to = -1;
637
638 /* We're tracing in this frame: record something. */
639 if (self->cur_entry.file_tracer != Py_None) {
640 PyObject * from_to = NULL;
641 STATS( self->stats.pycalls++; )
642 from_to = PyObject_CallMethodObjArgs(self->cur_entry.file_tracer, str_line_number_range, frame, NULL);
643 if (from_to == NULL) {
644 goto error;
645 }
646 ret2 = CTracer_unpack_pair(self, from_to, &lineno_from, &lineno_to);
647 Py_DECREF(from_to);
648 if (ret2 < 0) {
649 CTracer_disable_plugin(self, self->cur_entry.disposition);
650 goto ok;
651 }
652 }
653 else {
654 lineno_from = lineno_to = frame->f_lineno;
655 }
656
657 if (lineno_from != -1) {
658 for (; lineno_from <= lineno_to; lineno_from++) {
659 if (self->tracing_arcs) {
660 /* Tracing arcs: key is (last_line,this_line). */
661 if (CTracer_record_pair(self, self->cur_entry.last_line, lineno_from) < 0) {
662 goto error;
663 }
664 }
665 else {
666 /* Tracing lines: key is simply this_line. */
667 PyObject * this_line = MyInt_FromInt(lineno_from);
668 if (this_line == NULL) {
669 goto error;
670 }
671
672 ret2 = PyDict_SetItem(self->cur_entry.file_data, this_line, Py_None);
673 Py_DECREF(this_line);
674 if (ret2 < 0) {
675 goto error;
676 }
677 }
678
679 self->cur_entry.last_line = lineno_from;
680 }
681 }
682 }
683 }
684
685 ok:
686 ret = RET_OK;
687
688 error:
689
690 return ret;
691 }
692
693 static int
CTracer_handle_return(CTracer * self,PyFrameObject * frame)694 CTracer_handle_return(CTracer *self, PyFrameObject *frame)
695 {
696 int ret = RET_ERROR;
697
698 STATS( self->stats.returns++; )
699 /* A near-copy of this code is above in the missing-return handler. */
700 if (CTracer_set_pdata_stack(self) < 0) {
701 goto error;
702 }
703 if (self->pdata_stack->depth >= 0) {
704 if (self->tracing_arcs && self->cur_entry.file_data) {
705 /* Need to distinguish between RETURN_VALUE and YIELD_VALUE. Read
706 * the current bytecode to see what it is. In unusual circumstances
707 * (Cython code), co_code can be the empty string, so range-check
708 * f_lasti before reading the byte.
709 */
710 int bytecode = RETURN_VALUE;
711 PyObject * pCode = frame->f_code->co_code;
712 int lasti = frame->f_lasti;
713
714 if (lasti < MyBytes_GET_SIZE(pCode)) {
715 bytecode = MyBytes_AS_STRING(pCode)[lasti];
716 }
717 if (bytecode != YIELD_VALUE) {
718 int first = frame->f_code->co_firstlineno;
719 if (CTracer_record_pair(self, self->cur_entry.last_line, -first) < 0) {
720 goto error;
721 }
722 }
723 }
724
725 SHOWLOG(self->pdata_stack->depth, frame->f_lineno, frame->f_code->co_filename, "return");
726 self->cur_entry = self->pdata_stack->stack[self->pdata_stack->depth];
727 self->pdata_stack->depth--;
728 }
729
730 ret = RET_OK;
731
732 error:
733
734 return ret;
735 }
736
737 static int
CTracer_handle_exception(CTracer * self,PyFrameObject * frame)738 CTracer_handle_exception(CTracer *self, PyFrameObject *frame)
739 {
740 /* Some code (Python 2.3, and pyexpat anywhere) fires an exception event
741 without a return event. To detect that, we'll keep a copy of the
742 parent frame for an exception event. If the next event is in that
743 frame, then we must have returned without a return event. We can
744 synthesize the missing event then.
745
746 Python itself fixed this problem in 2.4. Pyexpat still has the bug.
747 I've reported the problem with pyexpat as http://bugs.python.org/issue6359 .
748 If it gets fixed, this code should still work properly. Maybe some day
749 the bug will be fixed everywhere coverage.py is supported, and we can
750 remove this missing-return detection.
751
752 More about this fix: http://nedbatchelder.com/blog/200907/a_nasty_little_bug.html
753 */
754 STATS( self->stats.exceptions++; )
755 self->last_exc_back = frame->f_back;
756 self->last_exc_firstlineno = frame->f_code->co_firstlineno;
757
758 return RET_OK;
759 }
760
761 /*
762 * The Trace Function
763 */
764 static int
CTracer_trace(CTracer * self,PyFrameObject * frame,int what,PyObject * arg_unused)765 CTracer_trace(CTracer *self, PyFrameObject *frame, int what, PyObject *arg_unused)
766 {
767 int ret = RET_ERROR;
768
769 #if WHAT_LOG || TRACE_LOG
770 PyObject * ascii = NULL;
771 #endif
772
773 #if WHAT_LOG
774 if (what <= (int)(sizeof(what_sym)/sizeof(const char *))) {
775 ascii = MyText_AS_BYTES(frame->f_code->co_filename);
776 printf("trace: %s @ %s %d\n", what_sym[what], MyBytes_AS_STRING(ascii), frame->f_lineno);
777 Py_DECREF(ascii);
778 }
779 #endif
780
781 #if TRACE_LOG
782 ascii = MyText_AS_BYTES(frame->f_code->co_filename);
783 if (strstr(MyBytes_AS_STRING(ascii), start_file) && frame->f_lineno == start_line) {
784 logging = 1;
785 }
786 Py_DECREF(ascii);
787 #endif
788
789 /* See below for details on missing-return detection. */
790 if (CTracer_check_missing_return(self, frame) < 0) {
791 goto error;
792 }
793
794 switch (what) {
795 case PyTrace_CALL:
796 if (CTracer_handle_call(self, frame) < 0) {
797 goto error;
798 }
799 break;
800
801 case PyTrace_RETURN:
802 if (CTracer_handle_return(self, frame) < 0) {
803 goto error;
804 }
805 break;
806
807 case PyTrace_LINE:
808 if (CTracer_handle_line(self, frame) < 0) {
809 goto error;
810 }
811 break;
812
813 case PyTrace_EXCEPTION:
814 if (CTracer_handle_exception(self, frame) < 0) {
815 goto error;
816 }
817 break;
818
819 default:
820 STATS( self->stats.others++; )
821 break;
822 }
823
824 ret = RET_OK;
825 goto cleanup;
826
827 error:
828 STATS( self->stats.errors++; )
829
830 cleanup:
831 return ret;
832 }
833
834
835 /*
836 * Python has two ways to set the trace function: sys.settrace(fn), which
837 * takes a Python callable, and PyEval_SetTrace(func, obj), which takes
838 * a C function and a Python object. The way these work together is that
839 * sys.settrace(pyfn) calls PyEval_SetTrace(builtin_func, pyfn), using the
840 * Python callable as the object in PyEval_SetTrace. So sys.gettrace()
841 * simply returns the Python object used as the second argument to
842 * PyEval_SetTrace. So sys.gettrace() will return our self parameter, which
843 * means it must be callable to be used in sys.settrace().
844 *
845 * So we make our self callable, equivalent to invoking our trace function.
846 *
847 * To help with the process of replaying stored frames, this function has an
848 * optional keyword argument:
849 *
850 * def CTracer_call(frame, event, arg, lineno=0)
851 *
852 * If provided, the lineno argument is used as the line number, and the
853 * frame's f_lineno member is ignored.
854 */
855 static PyObject *
CTracer_call(CTracer * self,PyObject * args,PyObject * kwds)856 CTracer_call(CTracer *self, PyObject *args, PyObject *kwds)
857 {
858 PyFrameObject *frame;
859 PyObject *what_str;
860 PyObject *arg;
861 int lineno = 0;
862 int what;
863 int orig_lineno;
864 PyObject *ret = NULL;
865 PyObject * ascii = NULL;
866
867 static char *what_names[] = {
868 "call", "exception", "line", "return",
869 "c_call", "c_exception", "c_return",
870 NULL
871 };
872
873 static char *kwlist[] = {"frame", "event", "arg", "lineno", NULL};
874
875 if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O!O|i:Tracer_call", kwlist,
876 &PyFrame_Type, &frame, &MyText_Type, &what_str, &arg, &lineno)) {
877 goto done;
878 }
879
880 /* In Python, the what argument is a string, we need to find an int
881 for the C function. */
882 for (what = 0; what_names[what]; what++) {
883 int should_break;
884 ascii = MyText_AS_BYTES(what_str);
885 should_break = !strcmp(MyBytes_AS_STRING(ascii), what_names[what]);
886 Py_DECREF(ascii);
887 if (should_break) {
888 break;
889 }
890 }
891
892 #if WHAT_LOG
893 ascii = MyText_AS_BYTES(frame->f_code->co_filename);
894 printf("pytrace: %s @ %s %d\n", what_sym[what], MyBytes_AS_STRING(ascii), frame->f_lineno);
895 Py_DECREF(ascii);
896 #endif
897
898 /* Save off the frame's lineno, and use the forced one, if provided. */
899 orig_lineno = frame->f_lineno;
900 if (lineno > 0) {
901 frame->f_lineno = lineno;
902 }
903
904 /* Invoke the C function, and return ourselves. */
905 if (CTracer_trace(self, frame, what, arg) == RET_OK) {
906 Py_INCREF(self);
907 ret = (PyObject *)self;
908 }
909
910 /* Clean up. */
911 frame->f_lineno = orig_lineno;
912
913 /* For better speed, install ourselves the C way so that future calls go
914 directly to CTracer_trace, without this intermediate function.
915
916 Only do this if this is a CALL event, since new trace functions only
917 take effect then. If we don't condition it on CALL, then we'll clobber
918 the new trace function before it has a chance to get called. To
919 understand why, there are three internal values to track: frame.f_trace,
920 c_tracefunc, and c_traceobj. They are explained here:
921 http://nedbatchelder.com/text/trace-function.html
922
923 Without the conditional on PyTrace_CALL, this is what happens:
924
925 def func(): # f_trace c_tracefunc c_traceobj
926 # -------------- -------------- --------------
927 # CTracer CTracer.trace CTracer
928 sys.settrace(my_func)
929 # CTracer trampoline my_func
930 # Now Python calls trampoline(CTracer), which calls this function
931 # which calls PyEval_SetTrace below, setting us as the tracer again:
932 # CTracer CTracer.trace CTracer
933 # and it's as if the settrace never happened.
934 */
935 if (what == PyTrace_CALL) {
936 PyEval_SetTrace((Py_tracefunc)CTracer_trace, (PyObject*)self);
937 }
938
939 done:
940 return ret;
941 }
942
943 static PyObject *
CTracer_start(CTracer * self,PyObject * args_unused)944 CTracer_start(CTracer *self, PyObject *args_unused)
945 {
946 PyEval_SetTrace((Py_tracefunc)CTracer_trace, (PyObject*)self);
947 self->started = 1;
948 self->tracing_arcs = self->trace_arcs && PyObject_IsTrue(self->trace_arcs);
949 self->cur_entry.last_line = -1;
950
951 /* start() returns a trace function usable with sys.settrace() */
952 Py_INCREF(self);
953 return (PyObject *)self;
954 }
955
956 static PyObject *
CTracer_stop(CTracer * self,PyObject * args_unused)957 CTracer_stop(CTracer *self, PyObject *args_unused)
958 {
959 if (self->started) {
960 PyEval_SetTrace(NULL, NULL);
961 self->started = 0;
962 }
963
964 Py_RETURN_NONE;
965 }
966
967 static PyObject *
CTracer_get_stats(CTracer * self)968 CTracer_get_stats(CTracer *self)
969 {
970 #if COLLECT_STATS
971 return Py_BuildValue(
972 "{sI,sI,sI,sI,sI,sI,sI,sI,si,sI,sI}",
973 "calls", self->stats.calls,
974 "lines", self->stats.lines,
975 "returns", self->stats.returns,
976 "exceptions", self->stats.exceptions,
977 "others", self->stats.others,
978 "new_files", self->stats.new_files,
979 "missed_returns", self->stats.missed_returns,
980 "stack_reallocs", self->stats.stack_reallocs,
981 "stack_alloc", self->pdata_stack->alloc,
982 "errors", self->stats.errors,
983 "pycalls", self->stats.pycalls
984 );
985 #else
986 Py_RETURN_NONE;
987 #endif /* COLLECT_STATS */
988 }
989
990 static PyMemberDef
991 CTracer_members[] = {
992 { "should_trace", T_OBJECT, offsetof(CTracer, should_trace), 0,
993 PyDoc_STR("Function indicating whether to trace a file.") },
994
995 { "check_include", T_OBJECT, offsetof(CTracer, check_include), 0,
996 PyDoc_STR("Function indicating whether to include a file.") },
997
998 { "warn", T_OBJECT, offsetof(CTracer, warn), 0,
999 PyDoc_STR("Function for issuing warnings.") },
1000
1001 { "concur_id_func", T_OBJECT, offsetof(CTracer, concur_id_func), 0,
1002 PyDoc_STR("Function for determining concurrency context") },
1003
1004 { "data", T_OBJECT, offsetof(CTracer, data), 0,
1005 PyDoc_STR("The raw dictionary of trace data.") },
1006
1007 { "file_tracers", T_OBJECT, offsetof(CTracer, file_tracers), 0,
1008 PyDoc_STR("Mapping from file name to plugin name.") },
1009
1010 { "should_trace_cache", T_OBJECT, offsetof(CTracer, should_trace_cache), 0,
1011 PyDoc_STR("Dictionary caching should_trace results.") },
1012
1013 { "trace_arcs", T_OBJECT, offsetof(CTracer, trace_arcs), 0,
1014 PyDoc_STR("Should we trace arcs, or just lines?") },
1015
1016 { NULL }
1017 };
1018
1019 static PyMethodDef
1020 CTracer_methods[] = {
1021 { "start", (PyCFunction) CTracer_start, METH_VARARGS,
1022 PyDoc_STR("Start the tracer") },
1023
1024 { "stop", (PyCFunction) CTracer_stop, METH_VARARGS,
1025 PyDoc_STR("Stop the tracer") },
1026
1027 { "get_stats", (PyCFunction) CTracer_get_stats, METH_VARARGS,
1028 PyDoc_STR("Get statistics about the tracing") },
1029
1030 { NULL }
1031 };
1032
1033 PyTypeObject
1034 CTracerType = {
1035 MyType_HEAD_INIT
1036 "coverage.CTracer", /*tp_name*/
1037 sizeof(CTracer), /*tp_basicsize*/
1038 0, /*tp_itemsize*/
1039 (destructor)CTracer_dealloc, /*tp_dealloc*/
1040 0, /*tp_print*/
1041 0, /*tp_getattr*/
1042 0, /*tp_setattr*/
1043 0, /*tp_compare*/
1044 0, /*tp_repr*/
1045 0, /*tp_as_number*/
1046 0, /*tp_as_sequence*/
1047 0, /*tp_as_mapping*/
1048 0, /*tp_hash */
1049 (ternaryfunc)CTracer_call, /*tp_call*/
1050 0, /*tp_str*/
1051 0, /*tp_getattro*/
1052 0, /*tp_setattro*/
1053 0, /*tp_as_buffer*/
1054 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
1055 "CTracer objects", /* tp_doc */
1056 0, /* tp_traverse */
1057 0, /* tp_clear */
1058 0, /* tp_richcompare */
1059 0, /* tp_weaklistoffset */
1060 0, /* tp_iter */
1061 0, /* tp_iternext */
1062 CTracer_methods, /* tp_methods */
1063 CTracer_members, /* tp_members */
1064 0, /* tp_getset */
1065 0, /* tp_base */
1066 0, /* tp_dict */
1067 0, /* tp_descr_get */
1068 0, /* tp_descr_set */
1069 0, /* tp_dictoffset */
1070 (initproc)CTracer_init, /* tp_init */
1071 0, /* tp_alloc */
1072 0, /* tp_new */
1073 };
1074