Skip to content

Commit e27fc99

Browse files
committed
fix the recording of arcs in CTracer
This patch modifies the C code so that it no longer mixes mapped and unmapped line numbers. In other words, the line numbers are now always passed through the tracer's `line_number_range` method before being recorded. This patch also modifies the C code to clear the `DataStackEntry` structs before reusing them. Not clearing their `last_line` attribute can result in bogus arcs being recorded.
1 parent b0710b1 commit e27fc99

File tree

3 files changed

+83
-73
lines changed

3 files changed

+83
-73
lines changed

coverage/ctracer/datastack.c

+3
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ DataStack_grow(Stats *pstats, DataStack *pdata_stack)
4545

4646
pdata_stack->stack = bigger_data_stack;
4747
pdata_stack->alloc = bigger;
48+
} else {
49+
/* Zero the entry, because it may have been previously used and can still contain data. */
50+
memset(pdata_stack->stack + pdata_stack->depth, 0, sizeof(DataStackEntry));
4851
}
4952
return RET_OK;
5053
}

coverage/ctracer/datastack.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ typedef struct DataStackEntry {
2222
PyObject * file_tracer;
2323

2424
/* The line number of the last line recorded, for tracing arcs.
25-
-1 means there was no previous line, as when entering a code object.
25+
0 means there was no previous line.
26+
A negative number -N means we've just entered the code object which starts at line N.
2627
*/
2728
int last_line;
2829

coverage/ctracer/tracer.c

+78-72
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,53 @@ CTracer_set_pdata_stack(CTracer *self)
278278
* Parts of the trace function.
279279
*/
280280

281+
static void
282+
CTracer_line_number_range(CTracer *self, PyFrameObject *frame, int lineno, int *lineno_from, int *lineno_to)
283+
{
284+
if (self->pcur_entry->file_tracer == Py_None) {
285+
*lineno_from = *lineno_to = lineno;
286+
return;
287+
}
288+
289+
PyObject * from_to = NULL;
290+
PyObject * pyint = NULL;
291+
292+
int f_lineno = PyFrame_GetLineNumber(frame);
293+
if (lineno != f_lineno) {
294+
/* Tracers look at f_lineno, so that's where we store the line number we want them to see. */
295+
frame->f_lineno = lineno;
296+
}
297+
298+
STATS( self->stats.pycalls++; )
299+
from_to = PyObject_CallMethodObjArgs(self->pcur_entry->file_tracer, str_line_number_range, frame, NULL);
300+
frame->f_lineno = f_lineno;
301+
if (from_to == NULL) {
302+
goto error;
303+
}
304+
if (!PyTuple_Check(from_to) || PyTuple_Size(from_to) != 2) {
305+
PyErr_SetString(
306+
PyExc_TypeError,
307+
"line_number_range must return 2-tuple"
308+
);
309+
goto error;
310+
}
311+
pyint = PyTuple_GetItem(from_to, 0);
312+
if (pyint == NULL || pyint_as_int(pyint, lineno_from) < 0) {
313+
goto error;
314+
}
315+
pyint = PyTuple_GetItem(from_to, 1);
316+
if (pyint == NULL || pyint_as_int(pyint, lineno_to) < 0) {
317+
goto error;
318+
}
319+
goto cleanup;
320+
321+
error:
322+
CTracer_disable_plugin(self, self->pcur_entry->disposition);
323+
324+
cleanup:
325+
Py_XDECREF(from_to);
326+
}
327+
281328
static int
282329
CTracer_check_missing_return(CTracer *self, PyFrameObject *frame)
283330
{
@@ -301,8 +348,12 @@ CTracer_check_missing_return(CTracer *self, PyFrameObject *frame)
301348
}
302349
if (self->pdata_stack->depth >= 0) {
303350
if (self->tracing_arcs && self->pcur_entry->file_data) {
304-
if (CTracer_record_pair(self, self->pcur_entry->last_line, -self->last_exc_firstlineno) < 0) {
305-
goto error;
351+
int lineno_from = 0, lineno_to = 0;
352+
CTracer_line_number_range(self, frame, self->last_exc_firstlineno, &lineno_from, &lineno_to);
353+
if (lineno_from > 0) {
354+
if (CTracer_record_pair(self, self->pcur_entry->last_line, -lineno_from) < 0) {
355+
goto error;
356+
}
306357
}
307358
}
308359
SHOWLOG(self->pdata_stack->depth, PyFrame_GetLineNumber(frame), frame->f_code->co_filename, "missedreturn");
@@ -548,11 +599,16 @@ CTracer_handle_call(CTracer *self, PyFrameObject *frame)
548599
* re-entering a generator also. f_lasti is -1 for a true call, and a
549600
* real byte offset for a generator re-entry.
550601
*/
551-
if (frame->f_lasti < 0) {
552-
self->pcur_entry->last_line = -frame->f_code->co_firstlineno;
553-
}
554-
else {
555-
self->pcur_entry->last_line = PyFrame_GetLineNumber(frame);
602+
if (self->tracing_arcs && self->pcur_entry->file_data) {
603+
int lineno_from = 0, lineno_to = 0;
604+
if (frame->f_lasti < 0) {
605+
CTracer_line_number_range(self, frame, frame->f_code->co_firstlineno, &lineno_from, &lineno_to);
606+
} else {
607+
CTracer_line_number_range(self, frame, PyFrame_GetLineNumber(frame), &lineno_from, &lineno_to);
608+
}
609+
if (lineno_from > 0) {
610+
self->pcur_entry->last_line = frame->f_lasti < 0 ? -lineno_from : lineno_from;
611+
}
556612
}
557613

558614
ok:
@@ -574,6 +630,9 @@ CTracer_disable_plugin(CTracer *self, PyObject * disposition)
574630
PyObject * ret;
575631
PyErr_Print();
576632

633+
self->pcur_entry->file_data = NULL;
634+
self->pcur_entry->file_tracer = Py_None;
635+
577636
STATS( self->stats.pycalls++; )
578637
ret = PyObject_CallFunctionObjArgs(self->disable_plugin, disposition, NULL);
579638
if (ret == NULL) {
@@ -591,40 +650,6 @@ CTracer_disable_plugin(CTracer *self, PyObject * disposition)
591650
PyErr_Print();
592651
}
593652

594-
595-
static int
596-
CTracer_unpack_pair(CTracer *self, PyObject *pair, int *p_one, int *p_two)
597-
{
598-
int ret = RET_ERROR;
599-
int the_int;
600-
PyObject * pyint = NULL;
601-
int index;
602-
603-
if (!PyTuple_Check(pair) || PyTuple_Size(pair) != 2) {
604-
PyErr_SetString(
605-
PyExc_TypeError,
606-
"line_number_range must return 2-tuple"
607-
);
608-
goto error;
609-
}
610-
611-
for (index = 0; index < 2; index++) {
612-
pyint = PyTuple_GetItem(pair, index);
613-
if (pyint == NULL) {
614-
goto error;
615-
}
616-
if (pyint_as_int(pyint, &the_int) < 0) {
617-
goto error;
618-
}
619-
*(index == 0 ? p_one : p_two) = the_int;
620-
}
621-
622-
ret = RET_OK;
623-
624-
error:
625-
return ret;
626-
}
627-
628653
static int
629654
CTracer_handle_line(CTracer *self, PyFrameObject *frame)
630655
{
@@ -635,36 +660,17 @@ CTracer_handle_line(CTracer *self, PyFrameObject *frame)
635660
if (self->pdata_stack->depth >= 0) {
636661
SHOWLOG(self->pdata_stack->depth, PyFrame_GetLineNumber(frame), frame->f_code->co_filename, "line");
637662
if (self->pcur_entry->file_data) {
638-
int lineno_from = -1;
639-
int lineno_to = -1;
640-
641-
/* We're tracing in this frame: record something. */
642-
if (self->pcur_entry->file_tracer != Py_None) {
643-
PyObject * from_to = NULL;
644-
STATS( self->stats.pycalls++; )
645-
from_to = PyObject_CallMethodObjArgs(self->pcur_entry->file_tracer, str_line_number_range, frame, NULL);
646-
if (from_to == NULL) {
647-
CTracer_disable_plugin(self, self->pcur_entry->disposition);
648-
goto ok;
649-
}
650-
ret2 = CTracer_unpack_pair(self, from_to, &lineno_from, &lineno_to);
651-
Py_DECREF(from_to);
652-
if (ret2 < 0) {
653-
CTracer_disable_plugin(self, self->pcur_entry->disposition);
654-
goto ok;
655-
}
656-
}
657-
else {
658-
lineno_from = lineno_to = PyFrame_GetLineNumber(frame);
659-
}
660-
661-
if (lineno_from != -1) {
663+
/* We're tracing in this frame: try to record something. */
664+
int lineno_from = 0, lineno_to = 0;
665+
CTracer_line_number_range(self, frame, PyFrame_GetLineNumber(frame), &lineno_from, &lineno_to);
666+
if (lineno_from > 0) {
662667
for (; lineno_from <= lineno_to; lineno_from++) {
663668
if (self->tracing_arcs) {
664669
/* Tracing arcs: key is (last_line,this_line). */
665670
if (CTracer_record_pair(self, self->pcur_entry->last_line, lineno_from) < 0) {
666671
goto error;
667672
}
673+
self->pcur_entry->last_line = lineno_from;
668674
}
669675
else {
670676
/* Tracing lines: key is simply this_line. */
@@ -679,14 +685,11 @@ CTracer_handle_line(CTracer *self, PyFrameObject *frame)
679685
goto error;
680686
}
681687
}
682-
683-
self->pcur_entry->last_line = lineno_from;
684688
}
685689
}
686690
}
687691
}
688692

689-
ok:
690693
ret = RET_OK;
691694

692695
error:
@@ -721,9 +724,12 @@ CTracer_handle_return(CTracer *self, PyFrameObject *frame)
721724
bytecode = MyBytes_AS_STRING(pCode)[lasti];
722725
}
723726
if (bytecode != YIELD_VALUE) {
724-
int first = frame->f_code->co_firstlineno;
725-
if (CTracer_record_pair(self, self->pcur_entry->last_line, -first) < 0) {
726-
goto error;
727+
int lineno_from = 0, lineno_to = 0;
728+
CTracer_line_number_range(self, frame, frame->f_code->co_firstlineno, &lineno_from, &lineno_to);
729+
if (lineno_from > 0) {
730+
if (CTracer_record_pair(self, self->pcur_entry->last_line, -lineno_from) < 0) {
731+
goto error;
732+
}
727733
}
728734
}
729735
}
@@ -936,7 +942,7 @@ CTracer_call(CTracer *self, PyObject *args, PyObject *kwds)
936942
#endif
937943

938944
/* Save off the frame's lineno, and use the forced one, if provided. */
939-
orig_lineno = frame->f_lineno;
945+
orig_lineno = PyFrame_GetLineNumber(frame);
940946
if (lineno > 0) {
941947
frame->f_lineno = lineno;
942948
}

0 commit comments

Comments
 (0)