• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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