Thu, 10 Apr 2014 23:02:20 +0200
updated coverage to 3.7.1
3495 | 1 | /* C-based Tracer for Coverage. */ |
2 | ||
3 | #include "Python.h" | |
4 | #include "compile.h" /* in 2.3, this wasn't part of Python.h */ | |
5 | #include "eval.h" /* or this. */ | |
6 | #include "structmember.h" | |
7 | #include "frameobject.h" | |
8 | ||
9 | /* Compile-time debugging helpers */ | |
10 | #undef WHAT_LOG /* Define to log the WHAT params in the trace function. */ | |
11 | #undef TRACE_LOG /* Define to log our bookkeeping. */ | |
12 | #undef COLLECT_STATS /* Collect counters: stats are printed when tracer is stopped. */ | |
13 | ||
14 | #if COLLECT_STATS | |
15 | #define STATS(x) x | |
16 | #else | |
17 | #define STATS(x) | |
18 | #endif | |
19 | ||
20 | /* Py 2.x and 3.x compatibility */ | |
21 | ||
22 | #ifndef Py_TYPE | |
23 | #define Py_TYPE(o) (((PyObject*)(o))->ob_type) | |
24 | #endif | |
25 | ||
26 | #if PY_MAJOR_VERSION >= 3 | |
27 | ||
28 | #define MyText_Type PyUnicode_Type | |
29 | #define MyText_Check(o) PyUnicode_Check(o) | |
30 | #define MyText_AS_BYTES(o) PyUnicode_AsASCIIString(o) | |
31 | #define MyText_AS_STRING(o) PyBytes_AS_STRING(o) | |
32 | #define MyInt_FromLong(l) PyLong_FromLong(l) | |
33 | ||
34 | #define MyType_HEAD_INIT PyVarObject_HEAD_INIT(NULL, 0) | |
35 | ||
36 | #else | |
37 | ||
38 | #define MyText_Type PyString_Type | |
39 | #define MyText_Check(o) PyString_Check(o) | |
40 | #define MyText_AS_BYTES(o) (Py_INCREF(o), o) | |
41 | #define MyText_AS_STRING(o) PyString_AS_STRING(o) | |
42 | #define MyInt_FromLong(l) PyInt_FromLong(l) | |
43 | ||
44 | #define MyType_HEAD_INIT PyObject_HEAD_INIT(NULL) 0, | |
45 | ||
46 | #endif /* Py3k */ | |
47 | ||
48 | /* The values returned to indicate ok or error. */ | |
49 | #define RET_OK 0 | |
50 | #define RET_ERROR -1 | |
51 | ||
52 | /* An entry on the data stack. For each call frame, we need to record the | |
53 | dictionary to capture data, and the last line number executed in that | |
54 | frame. | |
55 | */ | |
56 | typedef struct { | |
57 | PyObject * file_data; /* PyMem_Malloc'ed, a borrowed ref. */ | |
58 | int last_line; | |
59 | } DataStackEntry; | |
60 | ||
61 | /* The CTracer type. */ | |
62 | ||
63 | typedef struct { | |
64 | PyObject_HEAD | |
65 | ||
66 | /* Python objects manipulated directly by the Collector class. */ | |
67 | PyObject * should_trace; | |
68 | PyObject * warn; | |
69 | PyObject * data; | |
70 | PyObject * should_trace_cache; | |
71 | PyObject * arcs; | |
72 | ||
73 | /* Has the tracer been started? */ | |
74 | int started; | |
75 | /* Are we tracing arcs, or just lines? */ | |
76 | int tracing_arcs; | |
77 | ||
78 | /* | |
79 | The data stack is a stack of dictionaries. Each dictionary collects | |
80 | data for a single source file. The data stack parallels the call stack: | |
81 | each call pushes the new frame's file data onto the data stack, and each | |
82 | return pops file data off. | |
83 | ||
84 | The file data is a dictionary whose form depends on the tracing options. | |
85 | If tracing arcs, the keys are line number pairs. If not tracing arcs, | |
86 | the keys are line numbers. In both cases, the value is irrelevant | |
87 | (None). | |
88 | */ | |
89 | /* The index of the last-used entry in data_stack. */ | |
90 | int depth; | |
91 | /* The file data at each level, or NULL if not recording. */ | |
92 | DataStackEntry * data_stack; | |
93 | int data_stack_alloc; /* number of entries allocated at data_stack. */ | |
94 | ||
95 | /* The current file_data dictionary. Borrowed. */ | |
96 | PyObject * cur_file_data; | |
97 | ||
98 | /* The line number of the last line recorded, for tracing arcs. | |
99 | -1 means there was no previous line, as when entering a code object. | |
100 | */ | |
101 | int last_line; | |
102 | ||
103 | /* The parent frame for the last exception event, to fix missing returns. */ | |
104 | PyFrameObject * last_exc_back; | |
105 | int last_exc_firstlineno; | |
106 | ||
107 | #if COLLECT_STATS | |
108 | struct { | |
109 | unsigned int calls; | |
110 | unsigned int lines; | |
111 | unsigned int returns; | |
112 | unsigned int exceptions; | |
113 | unsigned int others; | |
114 | unsigned int new_files; | |
115 | unsigned int missed_returns; | |
116 | unsigned int stack_reallocs; | |
117 | unsigned int errors; | |
118 | } stats; | |
119 | #endif /* COLLECT_STATS */ | |
120 | } CTracer; | |
121 | ||
122 | #define STACK_DELTA 100 | |
123 | ||
124 | static int | |
125 | CTracer_init(CTracer *self, PyObject *args_unused, PyObject *kwds_unused) | |
126 | { | |
127 | #if COLLECT_STATS | |
128 | self->stats.calls = 0; | |
129 | self->stats.lines = 0; | |
130 | self->stats.returns = 0; | |
131 | self->stats.exceptions = 0; | |
132 | self->stats.others = 0; | |
133 | self->stats.new_files = 0; | |
134 | self->stats.missed_returns = 0; | |
135 | self->stats.stack_reallocs = 0; | |
136 | self->stats.errors = 0; | |
137 | #endif /* COLLECT_STATS */ | |
138 | ||
139 | self->should_trace = NULL; | |
140 | self->warn = NULL; | |
141 | self->data = NULL; | |
142 | self->should_trace_cache = NULL; | |
143 | self->arcs = NULL; | |
144 | ||
145 | self->started = 0; | |
146 | self->tracing_arcs = 0; | |
147 | ||
148 | self->depth = -1; | |
149 | self->data_stack = PyMem_Malloc(STACK_DELTA*sizeof(DataStackEntry)); | |
150 | if (self->data_stack == NULL) { | |
151 | STATS( self->stats.errors++; ) | |
152 | PyErr_NoMemory(); | |
153 | return RET_ERROR; | |
154 | } | |
155 | self->data_stack_alloc = STACK_DELTA; | |
156 | ||
157 | self->cur_file_data = NULL; | |
158 | self->last_line = -1; | |
159 | ||
160 | self->last_exc_back = NULL; | |
161 | ||
162 | return RET_OK; | |
163 | } | |
164 | ||
165 | static void | |
166 | CTracer_dealloc(CTracer *self) | |
167 | { | |
168 | if (self->started) { | |
169 | PyEval_SetTrace(NULL, NULL); | |
170 | } | |
171 | ||
172 | Py_XDECREF(self->should_trace); | |
173 | Py_XDECREF(self->warn); | |
174 | Py_XDECREF(self->data); | |
175 | Py_XDECREF(self->should_trace_cache); | |
176 | ||
177 | PyMem_Free(self->data_stack); | |
178 | ||
179 | Py_TYPE(self)->tp_free((PyObject*)self); | |
180 | } | |
181 | ||
182 | #if TRACE_LOG | |
183 | static const char * | |
184 | indent(int n) | |
185 | { | |
186 | static const char * spaces = | |
187 | " " | |
188 | " " | |
189 | " " | |
190 | " " | |
191 | ; | |
192 | return spaces + strlen(spaces) - n*2; | |
193 | } | |
194 | ||
195 | static int logging = 0; | |
196 | /* Set these constants to be a file substring and line number to start logging. */ | |
197 | static const char * start_file = "tests/views"; | |
198 | static int start_line = 27; | |
199 | ||
200 | static void | |
201 | showlog(int depth, int lineno, PyObject * filename, const char * msg) | |
202 | { | |
203 | if (logging) { | |
204 | printf("%s%3d ", indent(depth), depth); | |
205 | if (lineno) { | |
206 | printf("%4d", lineno); | |
207 | } | |
208 | else { | |
209 | printf(" "); | |
210 | } | |
211 | if (filename) { | |
212 | PyObject *ascii = MyText_AS_BYTES(filename); | |
213 | printf(" %s", MyText_AS_STRING(ascii)); | |
214 | Py_DECREF(ascii); | |
215 | } | |
216 | if (msg) { | |
217 | printf(" %s", msg); | |
218 | } | |
219 | printf("\n"); | |
220 | } | |
221 | } | |
222 | ||
223 | #define SHOWLOG(a,b,c,d) showlog(a,b,c,d) | |
224 | #else | |
225 | #define SHOWLOG(a,b,c,d) | |
226 | #endif /* TRACE_LOG */ | |
227 | ||
228 | #if WHAT_LOG | |
229 | static const char * what_sym[] = {"CALL", "EXC ", "LINE", "RET "}; | |
230 | #endif | |
231 | ||
232 | /* Record a pair of integers in self->cur_file_data. */ | |
233 | static int | |
234 | CTracer_record_pair(CTracer *self, int l1, int l2) | |
235 | { | |
236 | int ret = RET_OK; | |
237 | ||
238 | PyObject * t = Py_BuildValue("(ii)", l1, l2); | |
239 | if (t != NULL) { | |
240 | if (PyDict_SetItem(self->cur_file_data, t, Py_None) < 0) { | |
241 | STATS( self->stats.errors++; ) | |
242 | ret = RET_ERROR; | |
243 | } | |
244 | Py_DECREF(t); | |
245 | } | |
246 | else { | |
247 | STATS( self->stats.errors++; ) | |
248 | ret = RET_ERROR; | |
249 | } | |
250 | return ret; | |
251 | } | |
252 | ||
253 | /* | |
254 | * The Trace Function | |
255 | */ | |
256 | static int | |
257 | CTracer_trace(CTracer *self, PyFrameObject *frame, int what, PyObject *arg_unused) | |
258 | { | |
259 | int ret = RET_OK; | |
260 | PyObject * filename = NULL; | |
261 | PyObject * tracename = NULL; | |
262 | #if WHAT_LOG || TRACE_LOG | |
263 | PyObject * ascii = NULL; | |
264 | #endif | |
265 | ||
266 | #if WHAT_LOG | |
267 | if (what <= sizeof(what_sym)/sizeof(const char *)) { | |
268 | ascii = MyText_AS_BYTES(frame->f_code->co_filename); | |
269 | printf("trace: %s @ %s %d\n", what_sym[what], MyText_AS_STRING(ascii), frame->f_lineno); | |
270 | Py_DECREF(ascii); | |
271 | } | |
272 | #endif | |
273 | ||
274 | #if TRACE_LOG | |
275 | ascii = MyText_AS_BYTES(frame->f_code->co_filename); | |
276 | if (strstr(MyText_AS_STRING(ascii), start_file) && frame->f_lineno == start_line) { | |
277 | logging = 1; | |
278 | } | |
279 | Py_DECREF(ascii); | |
280 | #endif | |
281 | ||
282 | /* See below for details on missing-return detection. */ | |
283 | if (self->last_exc_back) { | |
284 | if (frame == self->last_exc_back) { | |
285 | /* Looks like someone forgot to send a return event. We'll clear | |
286 | the exception state and do the RETURN code here. Notice that the | |
287 | frame we have in hand here is not the correct frame for the RETURN, | |
288 | that frame is gone. Our handling for RETURN doesn't need the | |
289 | actual frame, but we do log it, so that will look a little off if | |
290 | you're looking at the detailed log. | |
291 | ||
292 | If someday we need to examine the frame when doing RETURN, then | |
293 | we'll need to keep more of the missed frame's state. | |
294 | */ | |
295 | STATS( self->stats.missed_returns++; ) | |
296 | if (self->depth >= 0) { | |
297 | if (self->tracing_arcs && self->cur_file_data) { | |
298 | if (CTracer_record_pair(self, self->last_line, -self->last_exc_firstlineno) < 0) { | |
299 | return RET_ERROR; | |
300 | } | |
301 | } | |
302 | SHOWLOG(self->depth, frame->f_lineno, frame->f_code->co_filename, "missedreturn"); | |
303 | self->cur_file_data = self->data_stack[self->depth].file_data; | |
304 | self->last_line = self->data_stack[self->depth].last_line; | |
305 | self->depth--; | |
306 | } | |
307 | } | |
308 | self->last_exc_back = NULL; | |
309 | } | |
310 | ||
311 | ||
312 | switch (what) { | |
313 | case PyTrace_CALL: /* 0 */ | |
314 | STATS( self->stats.calls++; ) | |
315 | /* Grow the stack. */ | |
316 | self->depth++; | |
317 | if (self->depth >= self->data_stack_alloc) { | |
318 | STATS( self->stats.stack_reallocs++; ) | |
319 | /* We've outgrown our data_stack array: make it bigger. */ | |
320 | int bigger = self->data_stack_alloc + STACK_DELTA; | |
321 | DataStackEntry * bigger_data_stack = PyMem_Realloc(self->data_stack, bigger * sizeof(DataStackEntry)); | |
322 | if (bigger_data_stack == NULL) { | |
323 | STATS( self->stats.errors++; ) | |
324 | PyErr_NoMemory(); | |
325 | self->depth--; | |
326 | return RET_ERROR; | |
327 | } | |
328 | self->data_stack = bigger_data_stack; | |
329 | self->data_stack_alloc = bigger; | |
330 | } | |
331 | ||
332 | /* Push the current state on the stack. */ | |
333 | self->data_stack[self->depth].file_data = self->cur_file_data; | |
334 | self->data_stack[self->depth].last_line = self->last_line; | |
335 | ||
336 | /* Check if we should trace this line. */ | |
337 | filename = frame->f_code->co_filename; | |
338 | tracename = PyDict_GetItem(self->should_trace_cache, filename); | |
339 | if (tracename == NULL) { | |
340 | STATS( self->stats.new_files++; ) | |
341 | /* We've never considered this file before. */ | |
342 | /* Ask should_trace about it. */ | |
343 | PyObject * args = Py_BuildValue("(OO)", filename, frame); | |
344 | tracename = PyObject_Call(self->should_trace, args, NULL); | |
345 | Py_DECREF(args); | |
346 | if (tracename == NULL) { | |
347 | /* An error occurred inside should_trace. */ | |
348 | STATS( self->stats.errors++; ) | |
349 | return RET_ERROR; | |
350 | } | |
351 | if (PyDict_SetItem(self->should_trace_cache, filename, tracename) < 0) { | |
352 | STATS( self->stats.errors++; ) | |
353 | return RET_ERROR; | |
354 | } | |
355 | } | |
356 | else { | |
357 | Py_INCREF(tracename); | |
358 | } | |
359 | ||
360 | /* If tracename is a string, then we're supposed to trace. */ | |
361 | if (MyText_Check(tracename)) { | |
362 | PyObject * file_data = PyDict_GetItem(self->data, tracename); | |
363 | if (file_data == NULL) { | |
364 | file_data = PyDict_New(); | |
365 | if (file_data == NULL) { | |
366 | STATS( self->stats.errors++; ) | |
367 | return RET_ERROR; | |
368 | } | |
369 | ret = PyDict_SetItem(self->data, tracename, file_data); | |
370 | Py_DECREF(file_data); | |
371 | if (ret < 0) { | |
372 | STATS( self->stats.errors++; ) | |
373 | return RET_ERROR; | |
374 | } | |
375 | } | |
376 | self->cur_file_data = file_data; | |
377 | /* Make the frame right in case settrace(gettrace()) happens. */ | |
378 | Py_INCREF(self); | |
379 | frame->f_trace = (PyObject*)self; | |
380 | SHOWLOG(self->depth, frame->f_lineno, filename, "traced"); | |
381 | } | |
382 | else { | |
383 | self->cur_file_data = NULL; | |
384 | SHOWLOG(self->depth, frame->f_lineno, filename, "skipped"); | |
385 | } | |
386 | ||
387 | Py_DECREF(tracename); | |
388 | ||
389 | self->last_line = -1; | |
390 | break; | |
391 | ||
392 | case PyTrace_RETURN: /* 3 */ | |
393 | STATS( self->stats.returns++; ) | |
394 | /* A near-copy of this code is above in the missing-return handler. */ | |
395 | if (self->depth >= 0) { | |
396 | if (self->tracing_arcs && self->cur_file_data) { | |
397 | int first = frame->f_code->co_firstlineno; | |
398 | if (CTracer_record_pair(self, self->last_line, -first) < 0) { | |
399 | return RET_ERROR; | |
400 | } | |
401 | } | |
402 | ||
403 | SHOWLOG(self->depth, frame->f_lineno, frame->f_code->co_filename, "return"); | |
404 | self->cur_file_data = self->data_stack[self->depth].file_data; | |
405 | self->last_line = self->data_stack[self->depth].last_line; | |
406 | self->depth--; | |
407 | } | |
408 | break; | |
409 | ||
410 | case PyTrace_LINE: /* 2 */ | |
411 | STATS( self->stats.lines++; ) | |
412 | if (self->depth >= 0) { | |
413 | SHOWLOG(self->depth, frame->f_lineno, frame->f_code->co_filename, "line"); | |
414 | if (self->cur_file_data) { | |
415 | /* We're tracing in this frame: record something. */ | |
416 | if (self->tracing_arcs) { | |
417 | /* Tracing arcs: key is (last_line,this_line). */ | |
418 | if (CTracer_record_pair(self, self->last_line, frame->f_lineno) < 0) { | |
419 | return RET_ERROR; | |
420 | } | |
421 | } | |
422 | else { | |
423 | /* Tracing lines: key is simply this_line. */ | |
424 | PyObject * this_line = MyInt_FromLong(frame->f_lineno); | |
425 | if (this_line == NULL) { | |
426 | STATS( self->stats.errors++; ) | |
427 | return RET_ERROR; | |
428 | } | |
429 | ret = PyDict_SetItem(self->cur_file_data, this_line, Py_None); | |
430 | Py_DECREF(this_line); | |
431 | if (ret < 0) { | |
432 | STATS( self->stats.errors++; ) | |
433 | return RET_ERROR; | |
434 | } | |
435 | } | |
436 | } | |
437 | self->last_line = frame->f_lineno; | |
438 | } | |
439 | break; | |
440 | ||
441 | case PyTrace_EXCEPTION: | |
442 | /* Some code (Python 2.3, and pyexpat anywhere) fires an exception event | |
443 | without a return event. To detect that, we'll keep a copy of the | |
444 | parent frame for an exception event. If the next event is in that | |
445 | frame, then we must have returned without a return event. We can | |
446 | synthesize the missing event then. | |
447 | ||
448 | Python itself fixed this problem in 2.4. Pyexpat still has the bug. | |
449 | I've reported the problem with pyexpat as http://bugs.python.org/issue6359 . | |
450 | If it gets fixed, this code should still work properly. Maybe some day | |
451 | the bug will be fixed everywhere coverage.py is supported, and we can | |
452 | remove this missing-return detection. | |
453 | ||
454 | More about this fix: http://nedbatchelder.com/blog/200907/a_nasty_little_bug.html | |
455 | */ | |
456 | STATS( self->stats.exceptions++; ) | |
457 | self->last_exc_back = frame->f_back; | |
458 | self->last_exc_firstlineno = frame->f_code->co_firstlineno; | |
459 | break; | |
460 | ||
461 | default: | |
462 | STATS( self->stats.others++; ) | |
463 | break; | |
464 | } | |
465 | ||
466 | return RET_OK; | |
467 | } | |
468 | ||
469 | /* | |
470 | * Python has two ways to set the trace function: sys.settrace(fn), which | |
471 | * takes a Python callable, and PyEval_SetTrace(func, obj), which takes | |
472 | * a C function and a Python object. The way these work together is that | |
473 | * sys.settrace(pyfn) calls PyEval_SetTrace(builtin_func, pyfn), using the | |
474 | * Python callable as the object in PyEval_SetTrace. So sys.gettrace() | |
475 | * simply returns the Python object used as the second argument to | |
476 | * PyEval_SetTrace. So sys.gettrace() will return our self parameter, which | |
477 | * means it must be callable to be used in sys.settrace(). | |
478 | * | |
479 | * So we make our self callable, equivalent to invoking our trace function. | |
480 | * | |
481 | * To help with the process of replaying stored frames, this function has an | |
482 | * optional keyword argument: | |
483 | * | |
484 | * def CTracer_call(frame, event, arg, lineno=0) | |
485 | * | |
486 | * If provided, the lineno argument is used as the line number, and the | |
487 | * frame's f_lineno member is ignored. | |
488 | */ | |
489 | static PyObject * | |
490 | CTracer_call(CTracer *self, PyObject *args, PyObject *kwds) | |
491 | { | |
492 | PyFrameObject *frame; | |
493 | PyObject *what_str; | |
494 | PyObject *arg; | |
495 | int lineno = 0; | |
496 | int what; | |
497 | int orig_lineno; | |
498 | PyObject *ret = NULL; | |
499 | ||
500 | static char *what_names[] = { | |
501 | "call", "exception", "line", "return", | |
502 | "c_call", "c_exception", "c_return", | |
503 | NULL | |
504 | }; | |
505 | ||
506 | #if WHAT_LOG | |
507 | printf("pytrace\n"); | |
508 | #endif | |
509 | ||
510 | static char *kwlist[] = {"frame", "event", "arg", "lineno", NULL}; | |
511 | ||
512 | if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O!O|i:Tracer_call", kwlist, | |
513 | &PyFrame_Type, &frame, &MyText_Type, &what_str, &arg, &lineno)) { | |
514 | goto done; | |
515 | } | |
516 | ||
517 | /* In Python, the what argument is a string, we need to find an int | |
518 | for the C function. */ | |
519 | for (what = 0; what_names[what]; what++) { | |
520 | PyObject *ascii = MyText_AS_BYTES(what_str); | |
521 | int should_break = !strcmp(MyText_AS_STRING(ascii), what_names[what]); | |
522 | Py_DECREF(ascii); | |
523 | if (should_break) { | |
524 | break; | |
525 | } | |
526 | } | |
527 | ||
528 | /* Save off the frame's lineno, and use the forced one, if provided. */ | |
529 | orig_lineno = frame->f_lineno; | |
530 | if (lineno > 0) { | |
531 | frame->f_lineno = lineno; | |
532 | } | |
533 | ||
534 | /* Invoke the C function, and return ourselves. */ | |
535 | if (CTracer_trace(self, frame, what, arg) == RET_OK) { | |
536 | Py_INCREF(self); | |
537 | ret = (PyObject *)self; | |
538 | } | |
539 | ||
540 | /* Clean up. */ | |
541 | frame->f_lineno = orig_lineno; | |
542 | ||
543 | done: | |
544 | return ret; | |
545 | } | |
546 | ||
547 | static PyObject * | |
548 | CTracer_start(CTracer *self, PyObject *args_unused) | |
549 | { | |
550 | PyEval_SetTrace((Py_tracefunc)CTracer_trace, (PyObject*)self); | |
551 | self->started = 1; | |
552 | self->tracing_arcs = self->arcs && PyObject_IsTrue(self->arcs); | |
553 | self->last_line = -1; | |
554 | ||
555 | /* start() returns a trace function usable with sys.settrace() */ | |
556 | Py_INCREF(self); | |
557 | return (PyObject *)self; | |
558 | } | |
559 | ||
560 | static PyObject * | |
561 | CTracer_stop(CTracer *self, PyObject *args_unused) | |
562 | { | |
563 | if (self->started) { | |
564 | PyEval_SetTrace(NULL, NULL); | |
565 | self->started = 0; | |
566 | } | |
567 | ||
568 | return Py_BuildValue(""); | |
569 | } | |
570 | ||
571 | static PyObject * | |
572 | CTracer_get_stats(CTracer *self) | |
573 | { | |
574 | #if COLLECT_STATS | |
575 | return Py_BuildValue( | |
576 | "{sI,sI,sI,sI,sI,sI,sI,sI,si,sI}", | |
577 | "calls", self->stats.calls, | |
578 | "lines", self->stats.lines, | |
579 | "returns", self->stats.returns, | |
580 | "exceptions", self->stats.exceptions, | |
581 | "others", self->stats.others, | |
582 | "new_files", self->stats.new_files, | |
583 | "missed_returns", self->stats.missed_returns, | |
584 | "stack_reallocs", self->stats.stack_reallocs, | |
585 | "stack_alloc", self->data_stack_alloc, | |
586 | "errors", self->stats.errors | |
587 | ); | |
588 | #else | |
589 | return Py_BuildValue(""); | |
590 | #endif /* COLLECT_STATS */ | |
591 | } | |
592 | ||
593 | static PyMemberDef | |
594 | CTracer_members[] = { | |
595 | { "should_trace", T_OBJECT, offsetof(CTracer, should_trace), 0, | |
596 | PyDoc_STR("Function indicating whether to trace a file.") }, | |
597 | ||
598 | { "warn", T_OBJECT, offsetof(CTracer, warn), 0, | |
599 | PyDoc_STR("Function for issuing warnings.") }, | |
600 | ||
601 | { "data", T_OBJECT, offsetof(CTracer, data), 0, | |
602 | PyDoc_STR("The raw dictionary of trace data.") }, | |
603 | ||
604 | { "should_trace_cache", T_OBJECT, offsetof(CTracer, should_trace_cache), 0, | |
605 | PyDoc_STR("Dictionary caching should_trace results.") }, | |
606 | ||
607 | { "arcs", T_OBJECT, offsetof(CTracer, arcs), 0, | |
608 | PyDoc_STR("Should we trace arcs, or just lines?") }, | |
609 | ||
610 | { NULL } | |
611 | }; | |
612 | ||
613 | static PyMethodDef | |
614 | CTracer_methods[] = { | |
615 | { "start", (PyCFunction) CTracer_start, METH_VARARGS, | |
616 | PyDoc_STR("Start the tracer") }, | |
617 | ||
618 | { "stop", (PyCFunction) CTracer_stop, METH_VARARGS, | |
619 | PyDoc_STR("Stop the tracer") }, | |
620 | ||
621 | { "get_stats", (PyCFunction) CTracer_get_stats, METH_VARARGS, | |
622 | PyDoc_STR("Get statistics about the tracing") }, | |
623 | ||
624 | { NULL } | |
625 | }; | |
626 | ||
627 | static PyTypeObject | |
628 | CTracerType = { | |
629 | MyType_HEAD_INIT | |
630 | "coverage.CTracer", /*tp_name*/ | |
631 | sizeof(CTracer), /*tp_basicsize*/ | |
632 | 0, /*tp_itemsize*/ | |
633 | (destructor)CTracer_dealloc, /*tp_dealloc*/ | |
634 | 0, /*tp_print*/ | |
635 | 0, /*tp_getattr*/ | |
636 | 0, /*tp_setattr*/ | |
637 | 0, /*tp_compare*/ | |
638 | 0, /*tp_repr*/ | |
639 | 0, /*tp_as_number*/ | |
640 | 0, /*tp_as_sequence*/ | |
641 | 0, /*tp_as_mapping*/ | |
642 | 0, /*tp_hash */ | |
643 | (ternaryfunc)CTracer_call, /*tp_call*/ | |
644 | 0, /*tp_str*/ | |
645 | 0, /*tp_getattro*/ | |
646 | 0, /*tp_setattro*/ | |
647 | 0, /*tp_as_buffer*/ | |
648 | Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ | |
649 | "CTracer objects", /* tp_doc */ | |
650 | 0, /* tp_traverse */ | |
651 | 0, /* tp_clear */ | |
652 | 0, /* tp_richcompare */ | |
653 | 0, /* tp_weaklistoffset */ | |
654 | 0, /* tp_iter */ | |
655 | 0, /* tp_iternext */ | |
656 | CTracer_methods, /* tp_methods */ | |
657 | CTracer_members, /* tp_members */ | |
658 | 0, /* tp_getset */ | |
659 | 0, /* tp_base */ | |
660 | 0, /* tp_dict */ | |
661 | 0, /* tp_descr_get */ | |
662 | 0, /* tp_descr_set */ | |
663 | 0, /* tp_dictoffset */ | |
664 | (initproc)CTracer_init, /* tp_init */ | |
665 | 0, /* tp_alloc */ | |
666 | 0, /* tp_new */ | |
667 | }; | |
668 | ||
669 | /* Module definition */ | |
670 | ||
671 | #define MODULE_DOC PyDoc_STR("Fast coverage tracer.") | |
672 | ||
673 | #if PY_MAJOR_VERSION >= 3 | |
674 | ||
675 | static PyModuleDef | |
676 | moduledef = { | |
677 | PyModuleDef_HEAD_INIT, | |
678 | "coverage.tracer", | |
679 | MODULE_DOC, | |
680 | -1, | |
681 | NULL, /* methods */ | |
682 | NULL, | |
683 | NULL, /* traverse */ | |
684 | NULL, /* clear */ | |
685 | NULL | |
686 | }; | |
687 | ||
688 | ||
689 | PyObject * | |
690 | PyInit_tracer(void) | |
691 | { | |
692 | PyObject * mod = PyModule_Create(&moduledef); | |
693 | if (mod == NULL) { | |
694 | return NULL; | |
695 | } | |
696 | ||
697 | CTracerType.tp_new = PyType_GenericNew; | |
698 | if (PyType_Ready(&CTracerType) < 0) { | |
699 | Py_DECREF(mod); | |
700 | return NULL; | |
701 | } | |
702 | ||
703 | Py_INCREF(&CTracerType); | |
704 | PyModule_AddObject(mod, "CTracer", (PyObject *)&CTracerType); | |
705 | ||
706 | return mod; | |
707 | } | |
708 | ||
709 | #else | |
710 | ||
711 | void | |
712 | inittracer(void) | |
713 | { | |
714 | PyObject * mod; | |
715 | ||
716 | mod = Py_InitModule3("coverage.tracer", NULL, MODULE_DOC); | |
717 | if (mod == NULL) { | |
718 | return; | |
719 | } | |
720 | ||
721 | CTracerType.tp_new = PyType_GenericNew; | |
722 | if (PyType_Ready(&CTracerType) < 0) { | |
723 | return; | |
724 | } | |
725 | ||
726 | Py_INCREF(&CTracerType); | |
727 | PyModule_AddObject(mod, "CTracer", (PyObject *)&CTracerType); | |
728 | } | |
729 | ||
730 | #endif /* Py3k */ |