Skip to content

Commit 8a90ee3

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 42c2f21 commit 8a90ee3

File tree

3 files changed

+77
-71
lines changed

3 files changed

+77
-71
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

+72-70
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,53 @@ CTracer_set_pdata_stack(CTracer *self)
296296
* Parts of the trace function.
297297
*/
298298

299+
static void
300+
CTracer_line_number_range(CTracer *self, PyFrameObject *frame, int lineno, int *lineno_from, int *lineno_to)
301+
{
302+
if (self->pcur_entry->file_tracer == Py_None) {
303+
*lineno_from = *lineno_to = lineno;
304+
return;
305+
}
306+
307+
PyObject * from_to = NULL;
308+
PyObject * pyint = NULL;
309+
310+
int f_lineno = PyFrame_GetLineNumber(frame);
311+
if (lineno != f_lineno) {
312+
/* Tracers look at f_lineno, so that's where we store the line number we want them to see. */
313+
frame->f_lineno = lineno;
314+
}
315+
316+
STATS( self->stats.pycalls++; )
317+
from_to = PyObject_CallMethodObjArgs(self->pcur_entry->file_tracer, str_line_number_range, frame, NULL);
318+
frame->f_lineno = f_lineno;
319+
if (from_to == NULL) {
320+
goto error;
321+
}
322+
if (!PyTuple_Check(from_to) || PyTuple_Size(from_to) != 2) {
323+
PyErr_SetString(
324+
PyExc_TypeError,
325+
"line_number_range must return 2-tuple"
326+
);
327+
goto error;
328+
}
329+
pyint = PyTuple_GetItem(from_to, 0);
330+
if (pyint == NULL || pyint_as_int(pyint, lineno_from) < 0) {
331+
goto error;
332+
}
333+
pyint = PyTuple_GetItem(from_to, 1);
334+
if (pyint == NULL || pyint_as_int(pyint, lineno_to) < 0) {
335+
goto error;
336+
}
337+
goto cleanup;
338+
339+
error:
340+
CTracer_disable_plugin(self, self->pcur_entry->disposition);
341+
342+
cleanup:
343+
Py_XDECREF(from_to);
344+
}
345+
299346
static int
300347
CTracer_handle_call(CTracer *self, PyFrameObject *frame)
301348
{
@@ -542,11 +589,16 @@ CTracer_handle_call(CTracer *self, PyFrameObject *frame)
542589
real_call = (MyFrame_GetLasti(frame) < 0);
543590
#endif
544591

545-
if (real_call) {
546-
self->pcur_entry->last_line = -MyFrame_GetCode(frame)->co_firstlineno;
547-
}
548-
else {
549-
self->pcur_entry->last_line = PyFrame_GetLineNumber(frame);
592+
if (self->tracing_arcs && self->pcur_entry->file_data) {
593+
int lineno_from = 0, lineno_to = 0;
594+
if (real_call) {
595+
CTracer_line_number_range(self, frame, MyFrame_GetCode(frame)->co_firstlineno, &lineno_from, &lineno_to);
596+
} else {
597+
CTracer_line_number_range(self, frame, PyFrame_GetLineNumber(frame), &lineno_from, &lineno_to);
598+
}
599+
if (lineno_from > 0) {
600+
self->pcur_entry->last_line = real_call ? -lineno_from : lineno_from;
601+
}
550602
}
551603

552604
ok:
@@ -571,6 +623,9 @@ CTracer_disable_plugin(CTracer *self, PyObject * disposition)
571623
PyObject * ret;
572624
PyErr_Print();
573625

626+
self->pcur_entry->file_data = NULL;
627+
self->pcur_entry->file_tracer = Py_None;
628+
574629
STATS( self->stats.pycalls++; )
575630
ret = PyObject_CallFunctionObjArgs(self->disable_plugin, disposition, NULL);
576631
if (ret == NULL) {
@@ -588,40 +643,6 @@ CTracer_disable_plugin(CTracer *self, PyObject * disposition)
588643
PyErr_Print();
589644
}
590645

591-
592-
static int
593-
CTracer_unpack_pair(CTracer *self, PyObject *pair, int *p_one, int *p_two)
594-
{
595-
int ret = RET_ERROR;
596-
int the_int;
597-
PyObject * pyint = NULL;
598-
int index;
599-
600-
if (!PyTuple_Check(pair) || PyTuple_Size(pair) != 2) {
601-
PyErr_SetString(
602-
PyExc_TypeError,
603-
"line_number_range must return 2-tuple"
604-
);
605-
goto error;
606-
}
607-
608-
for (index = 0; index < 2; index++) {
609-
pyint = PyTuple_GetItem(pair, index);
610-
if (pyint == NULL) {
611-
goto error;
612-
}
613-
if (pyint_as_int(pyint, &the_int) < 0) {
614-
goto error;
615-
}
616-
*(index == 0 ? p_one : p_two) = the_int;
617-
}
618-
619-
ret = RET_OK;
620-
621-
error:
622-
return ret;
623-
}
624-
625646
static int
626647
CTracer_handle_line(CTracer *self, PyFrameObject *frame)
627648
{
@@ -632,36 +653,17 @@ CTracer_handle_line(CTracer *self, PyFrameObject *frame)
632653
if (self->pdata_stack->depth >= 0) {
633654
SHOWLOG(PyFrame_GetLineNumber(frame), MyFrame_GetCode(frame)->co_filename, "line");
634655
if (self->pcur_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->pcur_entry->file_tracer != Py_None) {
640-
PyObject * from_to = NULL;
641-
STATS( self->stats.pycalls++; )
642-
from_to = PyObject_CallMethodObjArgs(self->pcur_entry->file_tracer, str_line_number_range, frame, NULL);
643-
if (from_to == NULL) {
644-
CTracer_disable_plugin(self, self->pcur_entry->disposition);
645-
goto ok;
646-
}
647-
ret2 = CTracer_unpack_pair(self, from_to, &lineno_from, &lineno_to);
648-
Py_DECREF(from_to);
649-
if (ret2 < 0) {
650-
CTracer_disable_plugin(self, self->pcur_entry->disposition);
651-
goto ok;
652-
}
653-
}
654-
else {
655-
lineno_from = lineno_to = PyFrame_GetLineNumber(frame);
656-
}
657-
658-
if (lineno_from != -1) {
656+
/* We're tracing in this frame: try to record something. */
657+
int lineno_from = 0, lineno_to = 0;
658+
CTracer_line_number_range(self, frame, PyFrame_GetLineNumber(frame), &lineno_from, &lineno_to);
659+
if (lineno_from > 0) {
659660
for (; lineno_from <= lineno_to; lineno_from++) {
660661
if (self->tracing_arcs) {
661662
/* Tracing arcs: key is (last_line,this_line). */
662663
if (CTracer_record_pair(self, self->pcur_entry->last_line, lineno_from) < 0) {
663664
goto error;
664665
}
666+
self->pcur_entry->last_line = lineno_from;
665667
}
666668
else {
667669
/* Tracing lines: key is simply this_line. */
@@ -676,14 +678,11 @@ CTracer_handle_line(CTracer *self, PyFrameObject *frame)
676678
goto error;
677679
}
678680
}
679-
680-
self->pcur_entry->last_line = lineno_from;
681681
}
682682
}
683683
}
684684
}
685685

686-
ok:
687686
ret = RET_OK;
688687

689688
error:
@@ -737,9 +736,12 @@ CTracer_handle_return(CTracer *self, PyFrameObject *frame)
737736
real_return = !(is_yield || is_yield_from);
738737
#endif
739738
if (real_return) {
740-
int first = MyFrame_GetCode(frame)->co_firstlineno;
741-
if (CTracer_record_pair(self, self->pcur_entry->last_line, -first) < 0) {
742-
goto error;
739+
int lineno_from = 0, lineno_to = 0;
740+
CTracer_line_number_range(self, frame, MyFrame_GetCode(frame)->co_firstlineno, &lineno_from, &lineno_to);
741+
if (lineno_from > 0) {
742+
if (CTracer_record_pair(self, self->pcur_entry->last_line, -lineno_from) < 0) {
743+
goto error;
744+
}
743745
}
744746
}
745747
}
@@ -920,7 +922,7 @@ CTracer_call(CTracer *self, PyObject *args, PyObject *kwds)
920922
#endif
921923

922924
/* Save off the frame's lineno, and use the forced one, if provided. */
923-
orig_lineno = frame->f_lineno;
925+
orig_lineno = PyFrame_GetLineNumber(frame);
924926
if (lineno > 0) {
925927
frame->f_lineno = lineno;
926928
}

0 commit comments

Comments
 (0)