summaryrefslogtreecommitdiff
path: root/coverage/tracer.c
diff options
context:
space:
mode:
Diffstat (limited to 'coverage/tracer.c')
-rw-r--r--coverage/tracer.c515
1 files changed, 296 insertions, 219 deletions
diff --git a/coverage/tracer.c b/coverage/tracer.c
index a31340fc..6942e854 100644
--- a/coverage/tracer.c
+++ b/coverage/tracer.c
@@ -402,44 +402,14 @@ error:
}
/*
- * The Trace Function
+ * Parts of the trace function.
*/
+
static int
-CTracer_trace(CTracer *self, PyFrameObject *frame, int what, PyObject *arg_unused)
+CTracer_check_missing_return(CTracer *self, PyFrameObject *frame)
{
int ret = RET_ERROR;
- /* Owned references that we clean up at the very end of the function. */
- PyObject * tracename = NULL;
- PyObject * disposition = NULL;
- PyObject * disp_trace = NULL;
- PyObject * disp_file_tracer = NULL;
- PyObject * has_dynamic_filename = NULL;
-
- /* Borrowed references. */
- PyObject * filename = NULL;
-
- #if WHAT_LOG || TRACE_LOG
- PyObject * ascii = NULL;
- #endif
-
- #if WHAT_LOG
- if (what <= sizeof(what_sym)/sizeof(const char *)) {
- ascii = MyText_AS_BYTES(frame->f_code->co_filename);
- printf("trace: %s @ %s %d\n", what_sym[what], MyText_AS_STRING(ascii), frame->f_lineno);
- Py_DECREF(ascii);
- }
- #endif
-
- #if TRACE_LOG
- ascii = MyText_AS_BYTES(frame->f_code->co_filename);
- if (strstr(MyText_AS_STRING(ascii), start_file) && frame->f_lineno == start_line) {
- logging = 1;
- }
- Py_DECREF(ascii);
- #endif
-
- /* See below for details on missing-return detection. */
if (self->last_exc_back) {
if (frame == self->last_exc_back) {
/* Looks like someone forgot to send a return event. We'll clear
@@ -470,233 +440,346 @@ CTracer_trace(CTracer *self, PyFrameObject *frame, int what, PyObject *arg_unuse
self->last_exc_back = NULL;
}
+ ret = RET_OK;
- switch (what) {
- case PyTrace_CALL: /* 0 */
- STATS( self->stats.calls++; )
- /* Grow the stack. */
- if (CTracer_set_pdata_stack(self)) {
+error:
+
+ return ret;
+}
+
+static int
+CTracer_handle_call(CTracer *self, PyFrameObject *frame)
+{
+ int ret = RET_ERROR;
+
+ /* Owned references that we clean up at the very end of the function. */
+ PyObject * tracename = NULL;
+ PyObject * disposition = NULL;
+ PyObject * disp_trace = NULL;
+ PyObject * disp_file_tracer = NULL;
+ PyObject * has_dynamic_filename = NULL;
+
+ /* Borrowed references. */
+ PyObject * filename = NULL;
+
+
+ STATS( self->stats.calls++; )
+ /* Grow the stack. */
+ if (CTracer_set_pdata_stack(self)) {
+ goto error;
+ }
+ if (DataStack_grow(self, self->pdata_stack)) {
+ goto error;
+ }
+
+ /* Push the current state on the stack. */
+ self->pdata_stack->stack[self->pdata_stack->depth] = self->cur_entry;
+
+ /* Check if we should trace this line. */
+ filename = frame->f_code->co_filename;
+ disposition = PyDict_GetItem(self->should_trace_cache, filename);
+ if (disposition == NULL) {
+ STATS( self->stats.new_files++; )
+ /* We've never considered this file before. */
+ /* Ask should_trace about it. */
+ disposition = PyObject_CallFunctionObjArgs(self->should_trace, filename, frame, NULL);
+ if (disposition == NULL) {
+ /* An error occurred inside should_trace. */
goto error;
}
- if (DataStack_grow(self, self->pdata_stack)) {
+ if (PyDict_SetItem(self->should_trace_cache, filename, disposition) < 0) {
goto error;
}
+ }
+ else {
+ Py_INCREF(disposition);
+ }
- /* Push the current state on the stack. */
- self->pdata_stack->stack[self->pdata_stack->depth] = self->cur_entry;
+ disp_trace = PyObject_GetAttrString(disposition, "trace");
+ if (disp_trace == NULL) {
+ goto error;
+ }
- /* Check if we should trace this line. */
- filename = frame->f_code->co_filename;
- disposition = PyDict_GetItem(self->should_trace_cache, filename);
- if (disposition == NULL) {
- STATS( self->stats.new_files++; )
- /* We've never considered this file before. */
- /* Ask should_trace about it. */
- disposition = PyObject_CallFunctionObjArgs(self->should_trace, filename, frame, NULL);
- if (disposition == NULL) {
- /* An error occurred inside should_trace. */
- goto error;
- }
- if (PyDict_SetItem(self->should_trace_cache, filename, disposition) < 0) {
- goto error;
- }
+ if (disp_trace == Py_True) {
+ /* If tracename is a string, then we're supposed to trace. */
+ tracename = PyObject_GetAttrString(disposition, "source_filename");
+ if (tracename == NULL) {
+ goto error;
}
- else {
- Py_INCREF(disposition);
+ disp_file_tracer = PyObject_GetAttrString(disposition, "file_tracer");
+ if (disp_file_tracer == NULL) {
+ goto error;
}
-
- disp_trace = PyObject_GetAttrString(disposition, "trace");
- if (disp_trace == NULL) {
+ has_dynamic_filename = PyObject_GetAttrString(disposition, "has_dynamic_filename");
+ if (has_dynamic_filename == NULL) {
goto error;
}
-
- if (disp_trace == Py_True) {
- /* If tracename is a string, then we're supposed to trace. */
- tracename = PyObject_GetAttrString(disposition, "source_filename");
- if (tracename == NULL) {
- goto error;
- }
- disp_file_tracer = PyObject_GetAttrString(disposition, "file_tracer");
- if (disp_file_tracer == NULL) {
- goto error;
- }
- has_dynamic_filename = PyObject_GetAttrString(disposition, "has_dynamic_filename");
- if (has_dynamic_filename == NULL) {
+ if (has_dynamic_filename == Py_True) {
+ PyObject * next_tracename = NULL;
+ next_tracename = PyObject_CallMethod(
+ disp_file_tracer, "dynamic_source_filename",
+ "OO", tracename, frame
+ );
+ if (next_tracename == NULL) {
goto error;
}
- if (has_dynamic_filename == Py_True) {
- PyObject * next_tracename = NULL;
- next_tracename = PyObject_CallMethod(
- disp_file_tracer, "dynamic_source_filename",
- "OO", tracename, frame
- );
- if (next_tracename == NULL) {
- goto error;
- }
- Py_DECREF(tracename);
- tracename = next_tracename;
-
- if (tracename != Py_None) {
- /* Check the dynamic source filename against the include rules. */
- PyObject * included = NULL;
- included = PyDict_GetItem(self->should_trace_cache, tracename);
+ Py_DECREF(tracename);
+ tracename = next_tracename;
+
+ if (tracename != Py_None) {
+ /* Check the dynamic source filename against the include rules. */
+ PyObject * included = NULL;
+ included = PyDict_GetItem(self->should_trace_cache, tracename);
+ if (included == NULL) {
+ STATS( self->stats.new_files++; )
+ included = PyObject_CallFunctionObjArgs(self->check_include, tracename, frame, NULL);
if (included == NULL) {
- STATS( self->stats.new_files++; )
- included = PyObject_CallFunctionObjArgs(self->check_include, tracename, frame, NULL);
- if (included == NULL) {
- goto error;
- }
- if (PyDict_SetItem(self->should_trace_cache, tracename, included) < 0) {
- goto error;
- }
+ goto error;
}
- if (included != Py_True) {
- Py_DECREF(tracename);
- tracename = Py_None;
- Py_INCREF(tracename);
+ if (PyDict_SetItem(self->should_trace_cache, tracename, included) < 0) {
+ goto error;
}
}
+ if (included != Py_True) {
+ Py_DECREF(tracename);
+ tracename = Py_None;
+ Py_INCREF(tracename);
+ }
}
}
- else {
- tracename = Py_None;
- Py_INCREF(tracename);
- }
+ }
+ else {
+ tracename = Py_None;
+ Py_INCREF(tracename);
+ }
- if (tracename != Py_None) {
- PyObject * file_data = PyDict_GetItem(self->data, tracename);
- PyObject * disp_plugin_name = NULL;
+ if (tracename != Py_None) {
+ PyObject * file_data = PyDict_GetItem(self->data, tracename);
+ PyObject * disp_plugin_name = NULL;
+ if (file_data == NULL) {
+ file_data = PyDict_New();
if (file_data == NULL) {
- file_data = PyDict_New();
- if (file_data == NULL) {
+ goto error;
+ }
+ ret = PyDict_SetItem(self->data, tracename, file_data);
+ Py_DECREF(file_data);
+ if (ret < 0) {
+ goto error;
+ }
+
+ /* If the disposition mentions a plugin, record that. */
+ if (disp_file_tracer != Py_None) {
+ disp_plugin_name = PyObject_GetAttrString(disp_file_tracer, "_coverage_plugin_name");
+ if (disp_plugin_name == NULL) {
goto error;
}
- ret = PyDict_SetItem(self->data, tracename, file_data);
- Py_DECREF(file_data);
+ ret = PyDict_SetItem(self->plugin_data, tracename, disp_plugin_name);
+ Py_DECREF(disp_plugin_name);
if (ret < 0) {
goto error;
}
+ }
+ }
- /* If the disposition mentions a plugin, record that. */
- if (disp_file_tracer != Py_None) {
- disp_plugin_name = PyObject_GetAttrString(disp_file_tracer, "_coverage_plugin_name");
- if (disp_plugin_name == NULL) {
+ self->cur_entry.file_data = file_data;
+ self->cur_entry.file_tracer = disp_file_tracer;
+
+ /* Make the frame right in case settrace(gettrace()) happens. */
+ Py_INCREF(self);
+ frame->f_trace = (PyObject*)self;
+ SHOWLOG(self->pdata_stack->depth, frame->f_lineno, filename, "traced");
+ }
+ else {
+ self->cur_entry.file_data = NULL;
+ self->cur_entry.file_tracer = Py_None;
+ SHOWLOG(self->pdata_stack->depth, frame->f_lineno, filename, "skipped");
+ }
+
+ self->cur_entry.last_line = -1;
+
+ ret = RET_OK;
+
+error:
+ Py_XDECREF(tracename);
+ Py_XDECREF(disposition);
+ Py_XDECREF(disp_trace);
+ Py_XDECREF(disp_file_tracer);
+ Py_XDECREF(has_dynamic_filename);
+
+ return ret;
+}
+
+static int
+CTracer_handle_line(CTracer *self, PyFrameObject *frame)
+{
+ int ret = RET_ERROR;
+
+ STATS( self->stats.lines++; )
+ if (self->pdata_stack->depth >= 0) {
+ SHOWLOG(self->pdata_stack->depth, frame->f_lineno, frame->f_code->co_filename, "line");
+ if (self->cur_entry.file_data) {
+ int lineno_from, lineno_to;
+
+ /* We're tracing in this frame: record something. */
+ if (self->cur_entry.file_tracer != Py_None) {
+ PyObject * from_to = NULL;
+ from_to = PyObject_CallMethod(self->cur_entry.file_tracer, "line_number_range", "O", frame);
+ /* TODO: error check bad returns. */
+ lineno_from = MyInt_AsInt(PyTuple_GetItem(from_to, 0));
+ lineno_to = MyInt_AsInt(PyTuple_GetItem(from_to, 1));
+ Py_DECREF(from_to);
+ }
+ else {
+ lineno_from = lineno_to = frame->f_lineno;
+ }
+
+ if (lineno_from != -1) {
+ if (self->tracing_arcs) {
+ /* Tracing arcs: key is (last_line,this_line). */
+ /* TODO: this needs to deal with lineno_to also. */
+ if (CTracer_record_pair(self, self->cur_entry.last_line, lineno_from) < 0) {
goto error;
}
- ret = PyDict_SetItem(self->plugin_data, tracename, disp_plugin_name);
- Py_DECREF(disp_plugin_name);
- if (ret < 0) {
- goto error;
+ }
+ else {
+ /* Tracing lines: key is simply this_line. */
+ while (lineno_from <= lineno_to) {
+ PyObject * this_line = MyInt_FromInt(lineno_from);
+ if (this_line == NULL) {
+ goto error;
+ }
+ ret = PyDict_SetItem(self->cur_entry.file_data, this_line, Py_None);
+ Py_DECREF(this_line);
+ if (ret < 0) {
+ goto error;
+ }
+ lineno_from++;
}
}
}
- self->cur_entry.file_data = file_data;
- self->cur_entry.file_tracer = disp_file_tracer;
-
- /* Make the frame right in case settrace(gettrace()) happens. */
- Py_INCREF(self);
- frame->f_trace = (PyObject*)self;
- SHOWLOG(self->pdata_stack->depth, frame->f_lineno, filename, "traced");
+ self->cur_entry.last_line = lineno_to;
}
- else {
- self->cur_entry.file_data = NULL;
- self->cur_entry.file_tracer = Py_None;
- SHOWLOG(self->pdata_stack->depth, frame->f_lineno, filename, "skipped");
+ }
+
+ ret = RET_OK;
+
+error:
+
+ return ret;
+}
+
+static int
+CTracer_handle_return(CTracer *self, PyFrameObject *frame)
+{
+ int ret = RET_ERROR;
+
+ STATS( self->stats.returns++; )
+ /* A near-copy of this code is above in the missing-return handler. */
+ if (CTracer_set_pdata_stack(self)) {
+ goto error;
+ }
+ if (self->pdata_stack->depth >= 0) {
+ if (self->tracing_arcs && self->cur_entry.file_data) {
+ int first = frame->f_code->co_firstlineno;
+ if (CTracer_record_pair(self, self->cur_entry.last_line, -first) < 0) {
+ goto error;
+ }
}
- self->cur_entry.last_line = -1;
- break;
+ SHOWLOG(self->pdata_stack->depth, frame->f_lineno, frame->f_code->co_filename, "return");
+ self->cur_entry = self->pdata_stack->stack[self->pdata_stack->depth];
+ self->pdata_stack->depth--;
+ }
+
+ ret = RET_OK;
+
+error:
+
+ return ret;
+}
+
+static int
+CTracer_handle_exception(CTracer *self, PyFrameObject *frame)
+{
+ /* Some code (Python 2.3, and pyexpat anywhere) fires an exception event
+ without a return event. To detect that, we'll keep a copy of the
+ parent frame for an exception event. If the next event is in that
+ frame, then we must have returned without a return event. We can
+ synthesize the missing event then.
+
+ Python itself fixed this problem in 2.4. Pyexpat still has the bug.
+ I've reported the problem with pyexpat as http://bugs.python.org/issue6359 .
+ If it gets fixed, this code should still work properly. Maybe some day
+ the bug will be fixed everywhere coverage.py is supported, and we can
+ remove this missing-return detection.
+
+ More about this fix: http://nedbatchelder.com/blog/200907/a_nasty_little_bug.html
+ */
+ STATS( self->stats.exceptions++; )
+ self->last_exc_back = frame->f_back;
+ self->last_exc_firstlineno = frame->f_code->co_firstlineno;
+
+ return RET_OK;
+}
+
+/*
+ * The Trace Function
+ */
+static int
+CTracer_trace(CTracer *self, PyFrameObject *frame, int what, PyObject *arg_unused)
+{
+ int ret = RET_ERROR;
+
+ #if WHAT_LOG || TRACE_LOG
+ PyObject * ascii = NULL;
+ #endif
+
+ #if WHAT_LOG
+ if (what <= sizeof(what_sym)/sizeof(const char *)) {
+ ascii = MyText_AS_BYTES(frame->f_code->co_filename);
+ printf("trace: %s @ %s %d\n", what_sym[what], MyText_AS_STRING(ascii), frame->f_lineno);
+ Py_DECREF(ascii);
+ }
+ #endif
+
+ #if TRACE_LOG
+ ascii = MyText_AS_BYTES(frame->f_code->co_filename);
+ if (strstr(MyText_AS_STRING(ascii), start_file) && frame->f_lineno == start_line) {
+ logging = 1;
+ }
+ Py_DECREF(ascii);
+ #endif
+
+ /* See below for details on missing-return detection. */
+ if (CTracer_check_missing_return(self, frame)) {
+ goto error;
+ }
- case PyTrace_RETURN: /* 3 */
- STATS( self->stats.returns++; )
- /* A near-copy of this code is above in the missing-return handler. */
- if (CTracer_set_pdata_stack(self)) {
+ switch (what) {
+ case PyTrace_CALL:
+ if (CTracer_handle_call(self, frame)) {
goto error;
}
- if (self->pdata_stack->depth >= 0) {
- if (self->tracing_arcs && self->cur_entry.file_data) {
- int first = frame->f_code->co_firstlineno;
- if (CTracer_record_pair(self, self->cur_entry.last_line, -first) < 0) {
- goto error;
- }
- }
+ break;
- SHOWLOG(self->pdata_stack->depth, frame->f_lineno, frame->f_code->co_filename, "return");
- self->cur_entry = self->pdata_stack->stack[self->pdata_stack->depth];
- self->pdata_stack->depth--;
+ case PyTrace_RETURN:
+ if (CTracer_handle_return(self, frame)) {
+ goto error;
}
break;
- case PyTrace_LINE: /* 2 */
- STATS( self->stats.lines++; )
- if (self->pdata_stack->depth >= 0) {
- SHOWLOG(self->pdata_stack->depth, frame->f_lineno, frame->f_code->co_filename, "line");
- if (self->cur_entry.file_data) {
- int lineno_from, lineno_to;
-
- /* We're tracing in this frame: record something. */
- if (self->cur_entry.file_tracer != Py_None) {
- PyObject * from_to = NULL;
- from_to = PyObject_CallMethod(self->cur_entry.file_tracer, "line_number_range", "O", frame);
- /* TODO: error check bad returns. */
- lineno_from = MyInt_AsInt(PyTuple_GetItem(from_to, 0));
- lineno_to = MyInt_AsInt(PyTuple_GetItem(from_to, 1));
- Py_DECREF(from_to);
- }
- else {
- lineno_from = lineno_to = frame->f_lineno;
- }
-
- if (lineno_from != -1) {
- if (self->tracing_arcs) {
- /* Tracing arcs: key is (last_line,this_line). */
- /* TODO: this needs to deal with lineno_to also. */
- if (CTracer_record_pair(self, self->cur_entry.last_line, lineno_from) < 0) {
- goto error;
- }
- }
- else {
- /* Tracing lines: key is simply this_line. */
- while (lineno_from <= lineno_to) {
- PyObject * this_line = MyInt_FromInt(lineno_from);
- if (this_line == NULL) {
- goto error;
- }
- ret = PyDict_SetItem(self->cur_entry.file_data, this_line, Py_None);
- Py_DECREF(this_line);
- if (ret < 0) {
- goto error;
- }
- lineno_from++;
- }
- }
- }
-
- self->cur_entry.last_line = lineno_to;
- }
+ case PyTrace_LINE:
+ if (CTracer_handle_line(self, frame)) {
+ goto error;
}
break;
case PyTrace_EXCEPTION:
- /* Some code (Python 2.3, and pyexpat anywhere) fires an exception event
- without a return event. To detect that, we'll keep a copy of the
- parent frame for an exception event. If the next event is in that
- frame, then we must have returned without a return event. We can
- synthesize the missing event then.
-
- Python itself fixed this problem in 2.4. Pyexpat still has the bug.
- I've reported the problem with pyexpat as http://bugs.python.org/issue6359 .
- If it gets fixed, this code should still work properly. Maybe some day
- the bug will be fixed everywhere coverage.py is supported, and we can
- remove this missing-return detection.
-
- More about this fix: http://nedbatchelder.com/blog/200907/a_nasty_little_bug.html
- */
- STATS( self->stats.exceptions++; )
- self->last_exc_back = frame->f_back;
- self->last_exc_firstlineno = frame->f_code->co_firstlineno;
+ if (CTracer_handle_exception(self, frame)) {
+ goto error;
+ }
break;
default:
@@ -711,16 +794,10 @@ error:
STATS( self->stats.errors++; )
cleanup:
-
- Py_XDECREF(tracename);
- Py_XDECREF(disposition);
- Py_XDECREF(disp_trace);
- Py_XDECREF(disp_file_tracer);
- Py_XDECREF(has_dynamic_filename);
-
return ret;
}
+
/*
* Python has two ways to set the trace function: sys.settrace(fn), which
* takes a Python callable, and PyEval_SetTrace(func, obj), which takes