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