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
13static int
14pyint_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
28static PyObject *str_trace;
29static PyObject *str_file_tracer;
30static PyObject *str__coverage_enabled;
31static PyObject *str__coverage_plugin;
32static PyObject *str__coverage_plugin_name;
33static PyObject *str_dynamic_source_filename;
34static PyObject *str_line_number_range;
35
36int
37CTracer_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
57error:
58    return ret;
59}
60
61static void CTracer_disable_plugin(CTracer *self, PyObject * disposition);
62
63static int
64CTracer_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
79error:
80    STATS( self->stats.errors++; )
81
82ok:
83    return ret;
84}
85
86static void
87CTracer_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
117static const char *
118indent(int n)
119{
120    static const char * spaces =
121        "                                                                    "
122        "                                                                    "
123        "                                                                    "
124        "                                                                    "
125        ;
126    return spaces + strlen(spaces) - n*2;
127}
128
129static int logging = 0;
130/* Set these constants to be a file substring and line number to start logging. */
131static const char * start_file = "tests/views";
132static int start_line = 27;
133
134static void
135showlog(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
163static const char * what_sym[] = {"CALL", "EXC ", "LINE", "RET "};
164#endif
165
166/* Record a pair of integers in self->cur_entry.file_data. */
167static int
168CTracer_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
185error:
186    Py_XDECREF(t);
187
188    return ret;
189}
190
191/* Set self->pdata_stack to the proper data_stack to use. */
192static int
193CTracer_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
264error:
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
276static int
277CTracer_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
313error:
314
315    return ret;
316}
317
318static int
319CTracer_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
513ok:
514    ret = RET_OK;
515
516error:
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
526static void
527CTracer_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
576error:
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
583ok:
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
592static int
593CTracer_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
621error:
622    return ret;
623}
624
625static int
626CTracer_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
685ok:
686    ret = RET_OK;
687
688error:
689
690    return ret;
691}
692
693static int
694CTracer_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
732error:
733
734    return ret;
735}
736
737static int
738CTracer_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 */
764static int
765CTracer_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
827error:
828    STATS( self->stats.errors++; )
829
830cleanup:
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 */
855static PyObject *
856CTracer_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
939done:
940    return ret;
941}
942
943static PyObject *
944CTracer_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
956static PyObject *
957CTracer_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
967static PyObject *
968CTracer_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
990static PyMemberDef
991CTracer_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
1019static PyMethodDef
1020CTracer_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
1033PyTypeObject
1034CTracerType = {
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