[gcc-python-plugin] Report on where objects were allocated when reporting on an ob_refcnt problem
by David Hugh Malcolm
commit b43056019cab94a20bb8e14d0014bde615399242
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Wed Jul 13 17:56:27 2011 -0400
Report on where objects were allocated when reporting on an ob_refcnt problem
Introduce RegionOnHeap subclass, to track where objects were allocated (along
with a descriptive name).
libcpychecker/absinterp.py | 22 ++++++--
libcpychecker/refcounts.py | 53 ++++++++++++++------
.../refcounts/correct_object_ctor/stdout.txt | 2 +-
.../refcounts/fold_conditional/stdout.txt | 2 +-
.../cpychecker/refcounts/missing_decref/stderr.txt | 3 +-
.../cpychecker/refcounts/missing_decref/stdout.txt | 16 +++---
tests/cpychecker/refcounts/object_leak/stderr.txt | 3 +-
.../refcounts/too_many_increfs/stderr.txt | 1 +
.../refcounts/too_many_increfs/stdout.txt | 2 +-
9 files changed, 70 insertions(+), 34 deletions(-)
---
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index 8194684..07bece6 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -177,8 +177,20 @@ class RegionForGlobal(Region):
def __repr__(self):
return 'RegionForGlobal(%r)' % self.vardecl
-# Used for making unique IDs:
-num_heap_regions = 0
+class RegionOnHeap(Region):
+ """
+ Represents an area of memory allocated on the heap
+ """
+ def __init__(self, name, alloc_stmt):
+ assert isinstance(alloc_stmt, gcc.Gimple)
+ Region.__init__(self, name, None)
+ self.alloc_stmt = alloc_stmt
+
+ def __repr__(self):
+ return 'RegionOnHeap(%r, %r)' % (self.name, self.alloc_stmt.loc)
+
+ def __str__(self):
+ return '%s allocated at %s' % (self.name, self.alloc_stmt.loc)
class State:
"""A Location with memory state"""
@@ -335,10 +347,8 @@ class State:
# self.log(log, 0)
return self.value_for_region[region]
- def make_heap_region(self):
- global num_heap_regions
- region = Region('heap_region_%i' % num_heap_regions, None)
- num_heap_regions += 1
+ def make_heap_region(self, name, stmt):
+ region = RegionOnHeap(name, stmt)
# it is its own region:
self.region_for_var[region] = region
return region
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index c24a18a..e8d6b08 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -127,7 +127,7 @@ class MyState(State):
else:
raise "foo"
- def impl_object_ctor(self, stmt, typename):
+ def impl_object_ctor(self, stmt, typename, typeobjname):
"""
Given a gcc.GimpleCall to a Python API function that returns a
PyObject*, generate a
@@ -137,10 +137,13 @@ class MyState(State):
assert isinstance(stmt, gcc.GimpleCall)
assert isinstance(stmt.fn.operand, gcc.FunctionDecl)
assert isinstance(typename, str)
+ # the C struct for the type
+
+ assert isinstance(typeobjname, str)
# the C identifier of the global PyTypeObject for the type
# Get the gcc.VarDecl for the global PyTypeObject
- typeobjdecl = gccutils.get_global_vardecl_by_name(typename)
+ typeobjdecl = gccutils.get_global_vardecl_by_name(typeobjname)
assert isinstance(typeobjdecl, gcc.VarDecl)
fnname = stmt.fn.operand.name
@@ -149,7 +152,7 @@ class MyState(State):
success = self.copy()
success.loc = self.loc.next_loc()
typeobjregion = success.var_region(typeobjdecl)
- nonnull = success.make_heap_region()
+ nonnull = success.make_heap_region(typename, stmt)
ob_refcnt = success.make_field_region(nonnull, 'ob_refcnt') # FIXME: this should be a memref and fieldref
success.value_for_region[ob_refcnt] = RefcountValue(1)
ob_type = success.make_field_region(nonnull, 'ob_type')
@@ -193,14 +196,16 @@ class MyState(State):
# PyObject* PyList_New(Py_ssize_t len)
# Returns a new reference, or raises MemoryError
lenarg = self.eval_expr(stmt.args[0])
- newobj, success, failure = self.impl_object_ctor(stmt, 'PyList_Type')
+ newobj, success, failure = self.impl_object_ctor(stmt,
+ 'PyListObject', 'PyList_Type')
# Set ob_size:
ob_size = success.dest.make_field_region(newobj, 'ob_size')
success.dest.value_for_region[ob_size] = lenarg
return [success, failure]
def impl_PyLong_FromLong(self, stmt):
- newobj, success, failure = self.impl_object_ctor(stmt, 'PyLong_Type')
+ newobj, success, failure = self.impl_object_ctor(stmt,
+ 'PyLongObject', 'PyLong_Type')
return [success, failure]
def impl_PyList_SetItem(self, stmt):
@@ -538,21 +543,39 @@ def check_refcounts(fun, dump_traces=False, show_traces=False):
exp_refcnt = 1
else:
desc = 'PyObject'
+ # We may have a more descriptive name within the region:
+ if isinstance(region, RegionOnHeap):
+ desc = region.name
exp_refcnt = 0
log('exp_refcnt: %r' % exp_refcnt, 0)
+
+ # Helper function for when ob_refcnt is wrong:
+ def emit_refcount_error(msg):
+ gcc.error(endstate.get_gcc_loc(fun), msg)
+
+ # For dynamically-allocated objects, indicate where they
+ # were allocated:
+ if isinstance(region, RegionOnHeap):
+ alloc_loc = region.alloc_stmt.loc
+ if alloc_loc:
+ gcc.inform(region.alloc_stmt.loc,
+ ('%s allocated at: %s'
+ % (region.name, get_src_for_loc(alloc_loc))))
+
+ # Summarize the control flow we followed through the function:
+ describe_trace(trace, fun)
+
+ # Here's where we verify the refcount:
if isinstance(ob_refcnt, RefcountValue):
if ob_refcnt.relvalue > exp_refcnt:
- # too high
- # FIXME: Better to give the loc where it was allocated? (for dynalloc; for others, then the return location?)
- gcc.error(endstate.get_gcc_loc(fun),
- 'ob_refcnt of %s is %i too high' % (desc, ob_refcnt.relvalue - exp_refcnt))
- describe_trace(trace, fun)
+ # Refcount is too high:
+ emit_refcount_error('ob_refcnt of %s is %i too high'
+ % (desc, ob_refcnt.relvalue - exp_refcnt))
elif ob_refcnt.relvalue < exp_refcnt:
- # too low
- # FIXME: Better to give the loc where it was allocated?
- gcc.error(endstate.get_gcc_loc(fun),
- 'ob_refcnt of %s is %i too low' % (desc, exp_refcnt - ob_refcnt.relvalue))
- describe_trace(trace, fun)
+ # Refcount is too low:
+ emit_refcount_error('ob_refcnt of %s is %i too low'
+ % (desc, exp_refcnt - ob_refcnt.relvalue))
+ # Special-case hint for when None has too low a refcount:
if isinstance(return_value, RegionForGlobal):
if return_value.vardecl.name == '_Py_NoneStruct':
gcc.inform(endstate.get_gcc_loc(fun),
diff --git a/tests/cpychecker/refcounts/correct_object_ctor/stdout.txt b/tests/cpychecker/refcounts/correct_object_ctor/stdout.txt
index 36697c0..e315a93 100644
--- a/tests/cpychecker/refcounts/correct_object_ctor/stdout.txt
+++ b/tests/cpychecker/refcounts/correct_object_ctor/stdout.txt
@@ -1,5 +1,5 @@
Trace 0:
- returned: Region('heap_region_0')
+ returned: RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/correct_object_ctor/input.c', line=30))
r->ob_refcnt: RefcountValue(1)
Trace 1:
returned: ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/correct_object_ctor/input.c', line=30), value=0)
diff --git a/tests/cpychecker/refcounts/fold_conditional/stdout.txt b/tests/cpychecker/refcounts/fold_conditional/stdout.txt
index 37ed904..70c0719 100644
--- a/tests/cpychecker/refcounts/fold_conditional/stdout.txt
+++ b/tests/cpychecker/refcounts/fold_conditional/stdout.txt
@@ -1,5 +1,5 @@
Trace 0:
- returned: Region('heap_region_1')
+ returned: RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/fold_conditional/input.c', line=26))
r->ob_refcnt: RefcountValue(1)
r->ob_size: ConcreteValue(gcctype='Py_ssize_t', loc=None, value=5L)
r->ob_type: RegionForGlobal(gcc.VarDecl('PyList_Type'))
diff --git a/tests/cpychecker/refcounts/missing_decref/stderr.txt b/tests/cpychecker/refcounts/missing_decref/stderr.txt
index 3007a9d..0b09f4a 100644
--- a/tests/cpychecker/refcounts/missing_decref/stderr.txt
+++ b/tests/cpychecker/refcounts/missing_decref/stderr.txt
@@ -1,5 +1,6 @@
tests/cpychecker/refcounts/missing_decref/input.c: In function ‘missing_decref’:
-tests/cpychecker/refcounts/missing_decref/input.c:38:1: error: ob_refcnt of PyObject is 1 too high
+tests/cpychecker/refcounts/missing_decref/input.c:38:1: error: ob_refcnt of PyListObject is 1 too high
+tests/cpychecker/refcounts/missing_decref/input.c:27:10: note: PyListObject allocated at: list = PyList_New(1);
tests/cpychecker/refcounts/missing_decref/input.c:27:10: note: when PyList_New() succeeds at: list = PyList_New(1);
tests/cpychecker/refcounts/missing_decref/input.c:28:8: note: when taking False path at: if (!list)
tests/cpychecker/refcounts/missing_decref/input.c:30:10: note: reaching: item = PyLong_FromLong(42);
diff --git a/tests/cpychecker/refcounts/missing_decref/stdout.txt b/tests/cpychecker/refcounts/missing_decref/stdout.txt
index 9cf024d..7dc45b4 100644
--- a/tests/cpychecker/refcounts/missing_decref/stdout.txt
+++ b/tests/cpychecker/refcounts/missing_decref/stdout.txt
@@ -7,13 +7,13 @@ Trace 0:
'PyList_SetItem() succeeds'
'returning'
Return value:
- repr(): Region('heap_region_0')
- str(): Region('heap_region_0')
+ repr(): RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/missing_decref/input.c', line=27))
+ str(): PyListObject allocated at tests/cpychecker/refcounts/missing_decref/input.c:27
r->ob_refcnt: RefcountValue(1)
r->ob_type: RegionForGlobal(gcc.VarDecl('PyList_Type'))
- Region('heap_region_1'):
- repr(): Region('heap_region_1')
- str(): Region('heap_region_1')
+ PyLongObject allocated at tests/cpychecker/refcounts/missing_decref/input.c:30:
+ repr(): RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/missing_decref/input.c', line=30))
+ str(): PyLongObject allocated at tests/cpychecker/refcounts/missing_decref/input.c:30
r->ob_refcnt: RefcountValue(0)
r->ob_type: RegionForGlobal(gcc.VarDecl('PyLong_Type'))
@@ -27,9 +27,9 @@ Trace 1:
Return value:
repr(): ConcreteValue(gcctype='struct PyObject *', loc=None, value=0L)
str(): (struct PyObject *)0L
- Region('heap_region_0'):
- repr(): Region('heap_region_0')
- str(): Region('heap_region_0')
+ PyListObject allocated at tests/cpychecker/refcounts/missing_decref/input.c:27:
+ repr(): RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/missing_decref/input.c', line=27))
+ str(): PyListObject allocated at tests/cpychecker/refcounts/missing_decref/input.c:27
r->ob_refcnt: RefcountValue(1)
r->ob_type: RegionForGlobal(gcc.VarDecl('PyList_Type'))
diff --git a/tests/cpychecker/refcounts/object_leak/stderr.txt b/tests/cpychecker/refcounts/object_leak/stderr.txt
index 352d380..a3076bb 100644
--- a/tests/cpychecker/refcounts/object_leak/stderr.txt
+++ b/tests/cpychecker/refcounts/object_leak/stderr.txt
@@ -1,4 +1,5 @@
tests/cpychecker/refcounts/object_leak/input.c: In function ‘object_leak’:
-tests/cpychecker/refcounts/object_leak/input.c:29:5: error: ob_refcnt of PyObject is 1 too high
+tests/cpychecker/refcounts/object_leak/input.c:29:5: error: ob_refcnt of PyLongObject is 1 too high
+tests/cpychecker/refcounts/object_leak/input.c:26:15: note: PyLongObject allocated at: PyObject *tmp = PyLong_FromLong(0x1000);
tests/cpychecker/refcounts/object_leak/input.c:26:15: note: when PyLong_FromLong() succeeds at: PyObject *tmp = PyLong_FromLong(0x1000);
tests/cpychecker/refcounts/object_leak/input.c:29:5: note: when returning at: return NULL;
diff --git a/tests/cpychecker/refcounts/too_many_increfs/stderr.txt b/tests/cpychecker/refcounts/too_many_increfs/stderr.txt
index ff6b056..05befb5 100644
--- a/tests/cpychecker/refcounts/too_many_increfs/stderr.txt
+++ b/tests/cpychecker/refcounts/too_many_increfs/stderr.txt
@@ -1,5 +1,6 @@
tests/cpychecker/refcounts/too_many_increfs/input.c: In function ‘too_many_increfs’:
tests/cpychecker/refcounts/too_many_increfs/input.c:31:5: error: ob_refcnt of return value is 1 too high
+tests/cpychecker/refcounts/too_many_increfs/input.c:26:9: note: PyLongObject allocated at: tmp = PyLong_FromLong(0x1000);
tests/cpychecker/refcounts/too_many_increfs/input.c:26:9: note: when PyLong_FromLong() succeeds at: tmp = PyLong_FromLong(0x1000);
tests/cpychecker/refcounts/too_many_increfs/input.c:31:5: note: when returning at: return tmp;
tests/cpychecker/refcounts/too_many_increfs/input.c:30:5: error: dereferencing NULL (tmp->ob_refcnt) at tests/cpychecker/refcounts/too_many_increfs/input.c:30
diff --git a/tests/cpychecker/refcounts/too_many_increfs/stdout.txt b/tests/cpychecker/refcounts/too_many_increfs/stdout.txt
index 75d8063..a7654dc 100644
--- a/tests/cpychecker/refcounts/too_many_increfs/stdout.txt
+++ b/tests/cpychecker/refcounts/too_many_increfs/stdout.txt
@@ -1,5 +1,5 @@
Trace 0:
- returned: Region('heap_region_1')
+ returned: RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/too_many_increfs/input.c', line=26))
r->ob_refcnt: RefcountValue(2)
Trace 1:
error: NullPtrDereference()
12 years, 10 months
[gcc-python-plugin] Add support for stolen references
by David Hugh Malcolm
commit f33f455ab65932bff77e5363ef6f980bd303460a
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Wed Jul 13 16:06:56 2011 -0400
Add support for stolen references
Add MyState.steal_reference() method
Use it to implement PyList_SetItem's effect on the ob_refcnt of the "item"
parameter (for the case where the call succeeds)
Add selftest for PyList_SetItem (demonstrating a different leak)
libcpychecker/refcounts.py | 56 ++++++++++++++------
tests/cpychecker/refcounts/missing_decref/input.c | 42 +++++++++++++++
.../cpychecker/refcounts/missing_decref/script.py | 22 ++++++++
.../cpychecker/refcounts/missing_decref/stderr.txt | 9 +++
.../cpychecker/refcounts/missing_decref/stdout.txt | 43 +++++++++++++++
5 files changed, 156 insertions(+), 16 deletions(-)
---
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index fd9e639..c24a18a 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -175,6 +175,18 @@ class MyState(State):
ConcreteValue(stmt.lhs.type, stmt.loc, value))
return newstate
+ def steal_reference(self, region):
+ log('steal_reference(%r)' % region)
+ assert isinstance(region, Region)
+ ob_refcnt = self.make_field_region(region, 'ob_refcnt')
+ value = self.value_for_region[ob_refcnt]
+ if isinstance(value, RefcountValue):
+ # We have a value known relative to all of the refs owned by the
+ # rest of the program. Given that the rest of the program is
+ # stealing a ref, that is increasing by one, hence our value must
+ # go down by one:
+ self.value_for_region[ob_refcnt] = RefcountValue(value.relvalue - 1)
+
# Specific Python API function implementations:
def impl_PyList_New(self, stmt):
# Decl:
@@ -196,26 +208,38 @@ class MyState(State):
# int PyList_SetItem(PyObject *list, Py_ssize_t index, PyObject *item)
fnname = stmt.fn.operand.name
- # Is it really a list?
- not_a_list = self.make_concrete_return_of(stmt, -1)
- # FIXME: can we check that it's a list?
+ result = []
- # Index out of range?
- out_of_range = self.make_concrete_return_of(stmt, -1)
+ arg_list, arg_index, arg_item = [self.eval_expr(arg) for arg in stmt.args]
- success = self.make_concrete_return_of(stmt, 0)
- # FIXME: update refcounts
- # Note This function "steals" a reference to item and discards a
- # reference to an item already in the list at the affected position.
- return [Transition(self,
+ # Is it really a list?
+ if 0: # FIXME: check
+ not_a_list = self.make_concrete_return_of(stmt, -1)
+ result.append(Transition(self,
not_a_list,
- '%s() fails (not a list)' % fnname),
- Transition(self,
+ '%s() fails (not a list)' % fnname))
+
+ # Index out of range?
+ if 0: # FIXME: check
+ out_of_range = self.make_concrete_return_of(stmt, -1)
+ result.append(Transition(self,
out_of_range,
- '%s() fails (index out of range)' % fnname),
- Transition(self,
- success,
- '%s() succeeds' % fnname)]
+ '%s() fails (index out of range)' % fnname))
+
+ if 1:
+ success = self.make_concrete_return_of(stmt, 0)
+ # FIXME: update refcounts
+ # "Steal" a reference to item:
+ if isinstance(arg_item, Region):
+ success.steal_reference(arg_item)
+
+ # and discards a
+ # reference to an item already in the list at the affected position.
+ result.append(Transition(self,
+ success,
+ '%s() succeeds' % fnname))
+
+ return result
def _get_transitions_for_GimpleCall(self, stmt):
log('stmt.lhs: %s %r' % (stmt.lhs, stmt.lhs), 3)
diff --git a/tests/cpychecker/refcounts/missing_decref/input.c b/tests/cpychecker/refcounts/missing_decref/input.c
new file mode 100644
index 0000000..ce3dd7f
--- /dev/null
+++ b/tests/cpychecker/refcounts/missing_decref/input.c
@@ -0,0 +1,42 @@
+/*
+ Copyright 2011 David Malcolm <dmalcolm(a)redhat.com>
+ Copyright 2011 Red Hat, Inc.
+
+ This is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+*/
+
+#include <Python.h>
+
+PyObject *
+missing_decref(PyObject *self, PyObject *args)
+{
+ PyObject *list;
+ PyObject *item;
+ list = PyList_New(1);
+ if (!list)
+ return NULL;
+ item = PyLong_FromLong(42);
+ /* This error handling is incorrect: it's missing an
+ invocation of Py_DECREF(list): */
+ if (!item)
+ return NULL;
+ /* This steals a reference to item; item is not leaked when we get here: */
+ PyList_SetItem(list, 0, item);
+ return list;
+}
+static PyMethodDef test_methods[] = {
+ {"test_method", missing_decref, METH_VARARGS, NULL},
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
diff --git a/tests/cpychecker/refcounts/missing_decref/script.py b/tests/cpychecker/refcounts/missing_decref/script.py
new file mode 100644
index 0000000..fdd5ba3
--- /dev/null
+++ b/tests/cpychecker/refcounts/missing_decref/script.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Copyright 2011 David Malcolm <dmalcolm(a)redhat.com>
+# Copyright 2011 Red Hat, Inc.
+#
+# This is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+from libcpychecker import main
+main(verify_refcounting=True,
+ dump_traces=True,
+ show_traces=False)
diff --git a/tests/cpychecker/refcounts/missing_decref/stderr.txt b/tests/cpychecker/refcounts/missing_decref/stderr.txt
new file mode 100644
index 0000000..3007a9d
--- /dev/null
+++ b/tests/cpychecker/refcounts/missing_decref/stderr.txt
@@ -0,0 +1,9 @@
+tests/cpychecker/refcounts/missing_decref/input.c: In function ‘missing_decref’:
+tests/cpychecker/refcounts/missing_decref/input.c:38:1: error: ob_refcnt of PyObject is 1 too high
+tests/cpychecker/refcounts/missing_decref/input.c:27:10: note: when PyList_New() succeeds at: list = PyList_New(1);
+tests/cpychecker/refcounts/missing_decref/input.c:28:8: note: when taking False path at: if (!list)
+tests/cpychecker/refcounts/missing_decref/input.c:30:10: note: reaching: item = PyLong_FromLong(42);
+tests/cpychecker/refcounts/missing_decref/input.c:30:10: note: when PyLong_FromLong() fails at: item = PyLong_FromLong(42);
+tests/cpychecker/refcounts/missing_decref/input.c:33:8: note: when taking True path at: if (!item)
+tests/cpychecker/refcounts/missing_decref/input.c:34:9: note: reaching: return NULL;
+tests/cpychecker/refcounts/missing_decref/input.c:38:1: note: when returning
diff --git a/tests/cpychecker/refcounts/missing_decref/stdout.txt b/tests/cpychecker/refcounts/missing_decref/stdout.txt
new file mode 100644
index 0000000..9cf024d
--- /dev/null
+++ b/tests/cpychecker/refcounts/missing_decref/stdout.txt
@@ -0,0 +1,43 @@
+Trace 0:
+ Transitions:
+ 'PyList_New() succeeds'
+ 'taking False path'
+ 'PyLong_FromLong() succeeds'
+ 'taking False path'
+ 'PyList_SetItem() succeeds'
+ 'returning'
+ Return value:
+ repr(): Region('heap_region_0')
+ str(): Region('heap_region_0')
+ r->ob_refcnt: RefcountValue(1)
+ r->ob_type: RegionForGlobal(gcc.VarDecl('PyList_Type'))
+ Region('heap_region_1'):
+ repr(): Region('heap_region_1')
+ str(): Region('heap_region_1')
+ r->ob_refcnt: RefcountValue(0)
+ r->ob_type: RegionForGlobal(gcc.VarDecl('PyLong_Type'))
+
+Trace 1:
+ Transitions:
+ 'PyList_New() succeeds'
+ 'taking False path'
+ 'PyLong_FromLong() fails'
+ 'taking True path'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=None, value=0L)
+ str(): (struct PyObject *)0L
+ Region('heap_region_0'):
+ repr(): Region('heap_region_0')
+ str(): Region('heap_region_0')
+ r->ob_refcnt: RefcountValue(1)
+ r->ob_type: RegionForGlobal(gcc.VarDecl('PyList_Type'))
+
+Trace 2:
+ Transitions:
+ 'PyList_New() fails'
+ 'taking True path'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=None, value=0L)
+ str(): (struct PyObject *)0L
12 years, 10 months
[gcc-python-plugin] Improvements to error reporting
by David Hugh Malcolm
commit f3b4fa9f7f50764a43716b6541cf64a919db8114
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Wed Jul 13 15:53:48 2011 -0400
Improvements to error reporting
Quote the source code line in error reports for each "interesting" transition,
and give the target source location of conditional jumps.
Bulletproof the error reporting against gcc.Gimple that have a "loc" of None.
libcpychecker/absinterp.py | 35 ++++++++++++++++++--
libcpychecker/refcounts.py | 14 ++++---
.../refcounts/incorrect_py_none/stderr.txt | 2 +-
tests/cpychecker/refcounts/object_leak/stderr.txt | 4 +-
.../refcounts/too_many_increfs/stderr.txt | 6 ++--
5 files changed, 46 insertions(+), 15 deletions(-)
---
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index 41e7b05..8194684 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -413,9 +413,23 @@ class State:
def has_returned(self):
return self.return_rvalue is not None
- def get_gcc_loc(self):
+ def get_gcc_loc_or_none(self):
+ # Return the gcc.Location for this state, which could be None
return self.loc.get_stmt().loc
+ def get_gcc_loc(self, fun):
+ # Return a non-None gcc.Location for this state
+ # Some statements have None for their location, but gcc.error() etc
+ # don't allow this. Use the end of the function for this case.
+ log('%s %r' % (self.loc.get_stmt(), self.loc.get_stmt()))
+ log(self.loc.get_stmt().loc)
+ # grrr... not all statements have a non-NULL location
+ gccloc = self.loc.get_stmt().loc
+ if gccloc is None:
+ gccloc = fun.end
+ return gccloc
+
+
class Transition:
def __init__(self, src, dest, desc):
assert isinstance(src, State)
@@ -629,14 +643,29 @@ class StateGraph:
def extra_text(msg, indent):
sys.stderr.write('%s%s\n' % (' ' * indent, msg))
-def describe_trace(trace):
+def describe_trace(trace, fun):
# Print more details about the path through the function that
# leads to the error:
awaiting_target = None
for t in trace.transitions:
log('transition: %s' % t)
if t.desc:
- gcc.inform(t.src.loc.get_stmt().loc, 'when %s' % t.desc)
+ srcloc = t.src.get_gcc_loc_or_none()
+ if srcloc:
+ gcc.inform(t.src.get_gcc_loc(fun),
+ ('when %s at: %s'
+ % (t.desc, get_src_for_loc(srcloc))))
+ else:
+ gcc.inform(t.src.get_gcc_loc(fun),
+ 'when %s' % t.desc)
+
+ if t.src.loc.bb != t.dest.loc.bb:
+ # Tell the user where conditionals reach:
+ destloc = t.dest.get_gcc_loc_or_none()
+ if destloc:
+ gcc.inform(destloc,
+ 'reaching: %s' % get_src_for_loc(destloc))
+ return
for j in range(len(trace.states)-1):
state = trace.states[j]
nextstate = trace.states[j+1]
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 9e84ae7..fd9e639 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -472,7 +472,7 @@ def check_refcounts(fun, dump_traces=False, show_traces=False):
log('trace.err: %s %r' % (trace.err, trace.err))
gcc.error(trace.err.loc,
str(trace.err))
- describe_trace(trace)
+ describe_trace(trace, fun)
# FIXME: in our example this ought to mention where the values came from
continue
# Otherwise, the trace proceeds normally
@@ -519,17 +519,19 @@ def check_refcounts(fun, dump_traces=False, show_traces=False):
if isinstance(ob_refcnt, RefcountValue):
if ob_refcnt.relvalue > exp_refcnt:
# too high
- gcc.error(endstate.get_gcc_loc(),
+ # FIXME: Better to give the loc where it was allocated? (for dynalloc; for others, then the return location?)
+ gcc.error(endstate.get_gcc_loc(fun),
'ob_refcnt of %s is %i too high' % (desc, ob_refcnt.relvalue - exp_refcnt))
- describe_trace(trace)
+ describe_trace(trace, fun)
elif ob_refcnt.relvalue < exp_refcnt:
# too low
- gcc.error(endstate.get_gcc_loc(),
+ # FIXME: Better to give the loc where it was allocated?
+ gcc.error(endstate.get_gcc_loc(fun),
'ob_refcnt of %s is %i too low' % (desc, exp_refcnt - ob_refcnt.relvalue))
- describe_trace(trace)
+ describe_trace(trace, fun)
if isinstance(return_value, RegionForGlobal):
if return_value.vardecl.name == '_Py_NoneStruct':
- gcc.inform(endstate.get_gcc_loc(),
+ gcc.inform(endstate.get_gcc_loc(fun),
'consider using "Py_RETURN_NONE;"')
if 0:
diff --git a/tests/cpychecker/refcounts/incorrect_py_none/stderr.txt b/tests/cpychecker/refcounts/incorrect_py_none/stderr.txt
index d484e79..77bcbad 100644
--- a/tests/cpychecker/refcounts/incorrect_py_none/stderr.txt
+++ b/tests/cpychecker/refcounts/incorrect_py_none/stderr.txt
@@ -1,4 +1,4 @@
tests/cpychecker/refcounts/incorrect_py_none/input.c: In function ‘losing_refcnt_of_none’:
tests/cpychecker/refcounts/incorrect_py_none/input.c:26:5: error: ob_refcnt of return value is 1 too low
-tests/cpychecker/refcounts/incorrect_py_none/input.c:26:5: note: when returning
+tests/cpychecker/refcounts/incorrect_py_none/input.c:26:5: note: when returning at: return Py_None;
tests/cpychecker/refcounts/incorrect_py_none/input.c:26:5: note: consider using "Py_RETURN_NONE;"
diff --git a/tests/cpychecker/refcounts/object_leak/stderr.txt b/tests/cpychecker/refcounts/object_leak/stderr.txt
index d5ecd19..352d380 100644
--- a/tests/cpychecker/refcounts/object_leak/stderr.txt
+++ b/tests/cpychecker/refcounts/object_leak/stderr.txt
@@ -1,4 +1,4 @@
tests/cpychecker/refcounts/object_leak/input.c: In function ‘object_leak’:
tests/cpychecker/refcounts/object_leak/input.c:29:5: error: ob_refcnt of PyObject is 1 too high
-tests/cpychecker/refcounts/object_leak/input.c:26:15: note: when PyLong_FromLong() succeeds
-tests/cpychecker/refcounts/object_leak/input.c:29:5: note: when returning
+tests/cpychecker/refcounts/object_leak/input.c:26:15: note: when PyLong_FromLong() succeeds at: PyObject *tmp = PyLong_FromLong(0x1000);
+tests/cpychecker/refcounts/object_leak/input.c:29:5: note: when returning at: return NULL;
diff --git a/tests/cpychecker/refcounts/too_many_increfs/stderr.txt b/tests/cpychecker/refcounts/too_many_increfs/stderr.txt
index 84f8d14..ff6b056 100644
--- a/tests/cpychecker/refcounts/too_many_increfs/stderr.txt
+++ b/tests/cpychecker/refcounts/too_many_increfs/stderr.txt
@@ -1,6 +1,6 @@
tests/cpychecker/refcounts/too_many_increfs/input.c: In function ‘too_many_increfs’:
tests/cpychecker/refcounts/too_many_increfs/input.c:31:5: error: ob_refcnt of return value is 1 too high
-tests/cpychecker/refcounts/too_many_increfs/input.c:26:9: note: when PyLong_FromLong() succeeds
-tests/cpychecker/refcounts/too_many_increfs/input.c:31:5: note: when returning
+tests/cpychecker/refcounts/too_many_increfs/input.c:26:9: note: when PyLong_FromLong() succeeds at: tmp = PyLong_FromLong(0x1000);
+tests/cpychecker/refcounts/too_many_increfs/input.c:31:5: note: when returning at: return tmp;
tests/cpychecker/refcounts/too_many_increfs/input.c:30:5: error: dereferencing NULL (tmp->ob_refcnt) at tests/cpychecker/refcounts/too_many_increfs/input.c:30
-tests/cpychecker/refcounts/too_many_increfs/input.c:26:9: note: when PyLong_FromLong() fails
+tests/cpychecker/refcounts/too_many_increfs/input.c:26:9: note: when PyLong_FromLong() fails at: tmp = PyLong_FromLong(0x1000);
12 years, 10 months
[gcc-python-plugin] Avoid hardware dependent results in the tests/plugin/parameters selftest
by David Hugh Malcolm
commit 1e05da0915689d5254362a7f74d42b9aec64cc4e
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Wed Jul 13 14:22:11 2011 -0400
Avoid hardware dependent results in the tests/plugin/parameters selftest
Out of all the parameters I could have chosen for the selftest, I managed
to pick the two parameters that vary based on the hardware on the test
machine, thus embedding an expected value that only worked with a specific
certain amount of RAM.
Specifically, 'ggc-min-expand' and 'ggc-min-heapsize' are set up by gcc's
init_ggc_heuristics() to values based on the amount of physical memory on the
test machine.
Change it to something that (hopefully) has a hardware-independent default.
tests/plugin/parameters/script.py | 6 +++---
tests/plugin/parameters/stdout.txt | 24 ++++++++++++------------
2 files changed, 15 insertions(+), 15 deletions(-)
---
diff --git a/tests/plugin/parameters/script.py b/tests/plugin/parameters/script.py
index b6ad3c1..7ba5138 100644
--- a/tests/plugin/parameters/script.py
+++ b/tests/plugin/parameters/script.py
@@ -31,11 +31,11 @@ def print_param_named(name):
'help'):
print(' param.%s: %r' % (attr, getattr(param, attr)))
-print_param_named('ggc-min-expand')
-print_param_named('ggc-min-heapsize')
+print_param_named('struct-reorg-cold-struct-ratio')
+print_param_named('predictable-branch-outcome')
# Verify that paramters can be set via "current_value":
-p = params['ggc-min-expand']
+p = params['predictable-branch-outcome']
assert p.current_value > 0
p.current_value = 0
assert p.current_value == 0
diff --git a/tests/plugin/parameters/stdout.txt b/tests/plugin/parameters/stdout.txt
index fe49af7..3bb724f 100644
--- a/tests/plugin/parameters/stdout.txt
+++ b/tests/plugin/parameters/stdout.txt
@@ -27,17 +27,17 @@ class Parameter(__builtin__.object)
| __new__ = <built-in method __new__ of type object>
| T.__new__(S, ...) -> a new object with type S, a subtype of T
-ggc-min-expand
- param.option: 'ggc-min-expand'
- param.current_value: 62
- param.default_value: 62
- param.max_value: 0
+struct-reorg-cold-struct-ratio
+ param.option: 'struct-reorg-cold-struct-ratio'
+ param.current_value: 10
+ param.default_value: 10
+ param.max_value: 100
param.min_value: 0
- param.help: 'Minimum heap expansion to trigger garbage collection, as a percentage of the total size of the heap'
-ggc-min-heapsize
- param.option: 'ggc-min-heapsize'
- param.current_value: 60567
- param.default_value: 60567
- param.max_value: 0
+ param.help: 'The threshold ratio between current and hottest structure counts'
+predictable-branch-outcome
+ param.option: 'predictable-branch-outcome'
+ param.current_value: 2
+ param.default_value: 2
+ param.max_value: 50
param.min_value: 0
- param.help: 'Minimum heap size before we start collecting garbage, in kilobytes'
+ param.help: 'Maximal estimated outcome of branch considered predictable'
12 years, 10 months
[gcc-python-plugin] Add "dump_traces" option to check_refcounts, to make it easier to wriute selftests
by David Hugh Malcolm
commit deb94095a36a61d674b19f14ddf4c51f10132f85
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Wed Jul 13 13:59:22 2011 -0400
Add "dump_traces" option to check_refcounts, to make it easier to wriute selftests
libcpychecker/__init__.py | 4 +-
libcpychecker/refcounts.py | 89 +++++++++++++++++++++++++++++++++++---------
2 files changed, 73 insertions(+), 20 deletions(-)
---
diff --git a/libcpychecker/__init__.py b/libcpychecker/__init__.py
index 9996585..e58045f 100644
--- a/libcpychecker/__init__.py
+++ b/libcpychecker/__init__.py
@@ -21,12 +21,12 @@ from PyArg_ParseTuple import check_pyargs, log
from refcounts import check_refcounts, get_traces
def on_pass_execution(optpass, fun,
+ dump_traces=False,
show_traces=False,
verify_refcounting=False,
*args, **kwargs):
# Only run in one pass
# FIXME: should we be adding our own pass for this?
- #log(optpass)
if optpass.name == '*warn_function_return':
if fun:
log(fun)
@@ -34,7 +34,7 @@ def on_pass_execution(optpass, fun,
# The refcount code is too buggy for now to be on by default:
if verify_refcounting:
- check_refcounts(fun, show_traces)
+ check_refcounts(fun, dump_traces, show_traces)
def is_a_method_callback(decl):
methods = get_all_PyMethodDef_methods()
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 4704abb..9e84ae7 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -377,11 +377,78 @@ class MyState(State):
def get_traces(fun):
return list(iter_traces(fun, MyState))
-def check_refcounts(fun, show_traces):
+def dump_traces_to_stdout(traces):
+ """
+ For use in selftests: dump the traces to stdout, in a form that (hopefully)
+ will allow usable comparisons against "gold" output ( not embedding
+ anything that changes e.g. names of temporaries, address of wrapper
+ objects, etc)
+ """
+ def dump_object(region, title):
+ print(' %s:' % title)
+ print(' repr(): %r' % region)
+ print(' str(): %s' % region)
+ if isinstance(region, Region):
+ print(' r->ob_refcnt: %r'
+ % endstate.get_value_of_field_by_region(region, 'ob_refcnt'))
+ print(' r->ob_type: %r'
+ % endstate.get_value_of_field_by_region(region, 'ob_type'))
+
+ for i, trace in enumerate(traces):
+ print('Trace %i:' % i)
+
+ # Emit the "interesting transitions" i.e. those with descriptions:
+ print(' Transitions:')
+ for trans in trace.transitions:
+ if trans.desc:
+ print(' %r' % trans.desc)
+
+ # Emit information about the end state:
+ endstate = trace.states[-1]
+
+ if trace.err:
+ print(' error: %r' % trace.err)
+ print(' error: %s' % trace.err)
+
+ if endstate.return_rvalue:
+ dump_object(endstate.return_rvalue, 'Return value')
+
+ # Other affected PyObject instances:
+ for k in endstate.region_for_var:
+ if not isinstance(endstate.region_for_var[k], Region):
+ continue
+ region = endstate.region_for_var[k]
+
+ # Consider those for which we know something about an "ob_refcnt"
+ # field:
+ if 'ob_refcnt' not in region.fields:
+ continue
+
+ if region == endstate.return_rvalue:
+ # (We did the return value above)
+ continue
+
+ dump_object(region, str(region))
+
+ if i + 1 < len(traces):
+ print
+
+def check_refcounts(fun, dump_traces=False, show_traces=False):
+ """
+ The top-level function of the refcount checker, checking the refcounting
+ behavior of a function
+
+ fun: the gcc.Function to be checked
+
+ dump_traces: bool: if True, dump information about the traces through
+ the function to stdout (for self tests)
+
+ show_traces: bool: if True, display a diagram of the state transition graph
+ """
# Abstract interpretation:
# Walk the CFG, gathering the information we're interested in
- log('check_refcounts(%r, %r)' % (fun, show_traces))
+ log('check_refcounts(%r, %r, %r)' % (fun, dump_traces, show_traces))
if show_traces:
from libcpychecker.visualizations import StateGraphPrettyPrinter
@@ -393,23 +460,9 @@ def check_refcounts(fun, show_traces):
invoke_dot(dot)
traces = iter_traces(fun, MyState)
- if show_traces:
+ if dump_traces:
traces = list(traces)
- for i, trace in enumerate(traces):
- def my_logger(item, indent=0):
- sys.stdout.write('%s%s\n' % (' ' * indent, item))
- trace.log(my_logger, 'TRACE %i' % i, 0)
-
- # Show trace #0:
- if 0: # i == 0:
- from libcpychecker.visualizations import TracePrettyPrinter
- tpp = TracePrettyPrinter(fun.cfg, trace)
- dot = tpp.to_dot()
- print dot
- f = open('test.dot', 'w')
- f.write(dot)
- f.close()
- invoke_dot(dot)
+ dump_traces_to_stdout(traces)
for i, trace in enumerate(traces):
trace.log(log, 'TRACE %i' % i, 0)
12 years, 10 months
[gcc-python-plugin] Set up ob_type on objects within the analyser
by David Hugh Malcolm
commit 231dc347abcdcce132ff392fe97489536030cb84
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Wed Jul 13 01:55:22 2011 -0400
Set up ob_type on objects within the analyser
docs/basics.rst | 6 ++++
gccutils.py | 10 ++++++++
libcpychecker/refcounts.py | 25 +++++++++++++++++--
.../cpychecker/refcounts/fold_conditional/input.c | 20 ++++++++++++---
.../refcounts/fold_conditional/script.py | 2 +
.../refcounts/fold_conditional/stdout.txt | 1 +
6 files changed, 57 insertions(+), 7 deletions(-)
---
diff --git a/docs/basics.rst b/docs/basics.rst
index e3838a0..b3ec2f7 100644
--- a/docs/basics.rst
+++ b/docs/basics.rst
@@ -462,6 +462,12 @@ Global data access
that name, returning it as a :py:class:`gcc.TypeDecl`, or None if it
wasn't found
+.. py:function:: gccutils.get_global_vardecl_by_name(name)
+
+ Given a string `name`, look for a C/C++ variable in global scope with
+ that name, returning it as a :py:class:`gcc.VarDecl`, or None if it
+ wasn't found
+
Working with source code
========================
diff --git a/gccutils.py b/gccutils.py
index 05425bb..851e6f7 100644
--- a/gccutils.py
+++ b/gccutils.py
@@ -37,6 +37,16 @@ def get_variables_as_dict():
result[var.decl.name] = var
return result
+def get_global_vardecl_by_name(name):
+ # Look up a variable in global scope by name, returning a gcc.VarDecl,
+ # or None if not found
+ result = {}
+ for u in gcc.get_translation_units():
+ for v in u.block.vars:
+ if isinstance(v, gcc.VarDecl):
+ if v.name == name:
+ return v
+
def invoke_dot(dot):
from subprocess import Popen, PIPE
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 6e1ce42..4704abb 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -138,16 +138,22 @@ class MyState(State):
assert isinstance(stmt.fn.operand, gcc.FunctionDecl)
assert isinstance(typename, str)
# the C identifier of the global PyTypeObject for the type
- # FIXME: not yet used
+
+ # Get the gcc.VarDecl for the global PyTypeObject
+ typeobjdecl = gccutils.get_global_vardecl_by_name(typename)
+ assert isinstance(typeobjdecl, gcc.VarDecl)
fnname = stmt.fn.operand.name
# Allocation and assignment:
success = self.copy()
success.loc = self.loc.next_loc()
+ typeobjregion = success.var_region(typeobjdecl)
nonnull = success.make_heap_region()
ob_refcnt = success.make_field_region(nonnull, 'ob_refcnt') # FIXME: this should be a memref and fieldref
success.value_for_region[ob_refcnt] = RefcountValue(1)
+ ob_type = success.make_field_region(nonnull, 'ob_type')
+ success.value_for_region[ob_type] = typeobjregion
success.assign(stmt.lhs, nonnull)
success = Transition(self,
success,
@@ -186,7 +192,8 @@ class MyState(State):
return [success, failure]
def impl_PyList_SetItem(self, stmt):
- # int PyList_SetItem(PyObject *list, Py_ssize_t index, PyObject *item)
+ # Decl:
+ # int PyList_SetItem(PyObject *list, Py_ssize_t index, PyObject *item)
fnname = stmt.fn.operand.name
# Is it really a list?
@@ -294,7 +301,19 @@ class MyState(State):
return False
if isinstance(lhs, ConcreteValue):
return lhs.value == rhs.value
- log('got here')
+ if isinstance(rhs, Region):
+ if isinstance(lhs, Region):
+ return lhs == rhs
+ elif stmt.exprcode == gcc.NeExpr:
+ if isinstance(rhs, ConcreteValue):
+ if isinstance(lhs, Region) and rhs.value == 0:
+ return True
+ if isinstance(lhs, ConcreteValue):
+ return lhs.value != rhs.value
+ if isinstance(rhs, Region):
+ if isinstance(lhs, Region):
+ return lhs != rhs
+ log('unable to compare %r with %r' % (lhs, rhs))
return UnknownValue(stmt.lhs.type, stmt.loc)
def eval_rhs(self, stmt):
diff --git a/tests/cpychecker/refcounts/fold_conditional/input.c b/tests/cpychecker/refcounts/fold_conditional/input.c
index 817380c..25edd1f 100644
--- a/tests/cpychecker/refcounts/fold_conditional/input.c
+++ b/tests/cpychecker/refcounts/fold_conditional/input.c
@@ -30,14 +30,26 @@ fold_conditional(PyObject *self, PyObject *args)
/*
Try a useless conditional to verify that the analyser knows it's
- true:
+ false:
*/
- if (PyList_GET_SIZE(list) == 5) {
+ if (PyList_GET_SIZE(list) != 5) {
+ /* The analyser should figure out that we can never get here: */
return list;
}
- /* The analyser should figure out that we can never get here: */
- return (PyObject*)-1;
+ /*
+ Try another useless conditional, to verify that the analyser knows
+ it's false:
+ */
+ if (Py_TYPE(list) != &PyList_Type) {
+ /*
+ Likewise, the analyser should figure out that we can never get
+ here:
+ */
+ return list;
+ }
+
+ return list;
}
static PyMethodDef test_methods[] = {
{"test_method", fold_conditional, METH_VARARGS, NULL},
diff --git a/tests/cpychecker/refcounts/fold_conditional/script.py b/tests/cpychecker/refcounts/fold_conditional/script.py
index 5c1776e..8bdc003 100644
--- a/tests/cpychecker/refcounts/fold_conditional/script.py
+++ b/tests/cpychecker/refcounts/fold_conditional/script.py
@@ -42,6 +42,8 @@ def verify_traces(optpass, fun):
print(' r->ob_refcnt: %r' % state.get_value_of_field_by_region(r, 'ob_refcnt'))
# Verify that we know "ob_size" of the returned value:
print(' r->ob_size: %r' % state.get_value_of_field_by_region(r, 'ob_size'))
+ # Verify that we know "ob_type" of the returned value:
+ print(' r->ob_type: %r' % state.get_value_of_field_by_region(r, 'ob_type'))
# Verify the "fail" trace:
state = traces[1].states[-1]
diff --git a/tests/cpychecker/refcounts/fold_conditional/stdout.txt b/tests/cpychecker/refcounts/fold_conditional/stdout.txt
index 351ab31..37ed904 100644
--- a/tests/cpychecker/refcounts/fold_conditional/stdout.txt
+++ b/tests/cpychecker/refcounts/fold_conditional/stdout.txt
@@ -2,5 +2,6 @@ Trace 0:
returned: Region('heap_region_1')
r->ob_refcnt: RefcountValue(1)
r->ob_size: ConcreteValue(gcctype='Py_ssize_t', loc=None, value=5L)
+ r->ob_type: RegionForGlobal(gcc.VarDecl('PyList_Type'))
Trace 1:
returned: (struct PyObject *)0L
12 years, 10 months
[gcc-python-plugin] Set ob_size in PyList_New
by David Hugh Malcolm
commit 27c7e166df0271783b41a50718d6729edc8b43e1
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Wed Jul 13 01:11:20 2011 -0400
Set ob_size in PyList_New
libcpychecker/refcounts.py | 17 +++++++++++++----
.../cpychecker/refcounts/fold_conditional/input.c | 12 +++++++++++-
.../refcounts/fold_conditional/script.py | 2 ++
.../refcounts/fold_conditional/stdout.txt | 1 +
4 files changed, 27 insertions(+), 5 deletions(-)
---
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 2a5e119..6e1ce42 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -130,7 +130,9 @@ class MyState(State):
def impl_object_ctor(self, stmt, typename):
"""
Given a gcc.GimpleCall to a Python API function that returns a
- PyObject*, generate a [success, failure] pair of Transitions
+ PyObject*, generate a
+ (newobj, success, failure)
+ triple, where newobj is a region, and success/failure are Transitions
"""
assert isinstance(stmt, gcc.GimpleCall)
assert isinstance(stmt.fn.operand, gcc.FunctionDecl)
@@ -153,7 +155,7 @@ class MyState(State):
failure = self.make_assignment(stmt.lhs,
ConcreteValue(stmt.lhs.type, stmt.loc, 0),
'%s() fails' % fnname)
- return [success, failure]
+ return (nonnull, success, failure)
def make_concrete_return_of(self, stmt, value):
"""
@@ -169,11 +171,18 @@ class MyState(State):
# Specific Python API function implementations:
def impl_PyList_New(self, stmt):
- success, failure = self.impl_object_ctor(stmt, 'PyList_Type')
+ # Decl:
+ # PyObject* PyList_New(Py_ssize_t len)
+ # Returns a new reference, or raises MemoryError
+ lenarg = self.eval_expr(stmt.args[0])
+ newobj, success, failure = self.impl_object_ctor(stmt, 'PyList_Type')
+ # Set ob_size:
+ ob_size = success.dest.make_field_region(newobj, 'ob_size')
+ success.dest.value_for_region[ob_size] = lenarg
return [success, failure]
def impl_PyLong_FromLong(self, stmt):
- success, failure = self.impl_object_ctor(stmt, 'PyLong_Type')
+ newobj, success, failure = self.impl_object_ctor(stmt, 'PyLong_Type')
return [success, failure]
def impl_PyList_SetItem(self, stmt):
diff --git a/tests/cpychecker/refcounts/fold_conditional/input.c b/tests/cpychecker/refcounts/fold_conditional/input.c
index 2ca7241..817380c 100644
--- a/tests/cpychecker/refcounts/fold_conditional/input.c
+++ b/tests/cpychecker/refcounts/fold_conditional/input.c
@@ -27,7 +27,17 @@ fold_conditional(PyObject *self, PyObject *args)
if (!list) {
return NULL;
}
- return list;
+
+ /*
+ Try a useless conditional to verify that the analyser knows it's
+ true:
+ */
+ if (PyList_GET_SIZE(list) == 5) {
+ return list;
+ }
+
+ /* The analyser should figure out that we can never get here: */
+ return (PyObject*)-1;
}
static PyMethodDef test_methods[] = {
{"test_method", fold_conditional, METH_VARARGS, NULL},
diff --git a/tests/cpychecker/refcounts/fold_conditional/script.py b/tests/cpychecker/refcounts/fold_conditional/script.py
index 2c62d7a..5c1776e 100644
--- a/tests/cpychecker/refcounts/fold_conditional/script.py
+++ b/tests/cpychecker/refcounts/fold_conditional/script.py
@@ -40,6 +40,8 @@ def verify_traces(optpass, fun):
r = state.return_rvalue
print(' returned: %r' %r)
print(' r->ob_refcnt: %r' % state.get_value_of_field_by_region(r, 'ob_refcnt'))
+ # Verify that we know "ob_size" of the returned value:
+ print(' r->ob_size: %r' % state.get_value_of_field_by_region(r, 'ob_size'))
# Verify the "fail" trace:
state = traces[1].states[-1]
diff --git a/tests/cpychecker/refcounts/fold_conditional/stdout.txt b/tests/cpychecker/refcounts/fold_conditional/stdout.txt
index ec5989b..351ab31 100644
--- a/tests/cpychecker/refcounts/fold_conditional/stdout.txt
+++ b/tests/cpychecker/refcounts/fold_conditional/stdout.txt
@@ -1,5 +1,6 @@
Trace 0:
returned: Region('heap_region_1')
r->ob_refcnt: RefcountValue(1)
+ r->ob_size: ConcreteValue(gcctype='Py_ssize_t', loc=None, value=5L)
Trace 1:
returned: (struct PyObject *)0L
12 years, 10 months
[gcc-python-plugin] Remove dead code
by David Hugh Malcolm
commit e4612812e856a14132a0054edebac52c4968b3b3
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Wed Jul 13 00:10:13 2011 -0400
Remove dead code
libcpychecker/refcounts.py | 41 -----------------------------------------
1 files changed, 0 insertions(+), 41 deletions(-)
---
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index e4d53eb..2a5e119 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -451,47 +451,6 @@ def check_refcounts(fun, show_traces):
gcc.inform(endstate.get_gcc_loc(),
'consider using "Py_RETURN_NONE;"')
- final_refs = trace.final_references()
- if return_value in final_refs:
- final_refs.remove(return_value)
- else:
- if isinstance(return_value, NonNullPtrValue):
- gcc.permerror(trace.get_last_stmt().loc,
- ('return of PyObject* (%s)'
- ' without Py_INCREF()'
- % return_value))
-
- # Anything remaining is a leak:
- if len(final_refs) > 0:
- for ref in final_refs:
- gcc.permerror(ref.stmt.loc,
- 'leak of PyObject* reference acquired at %s'
- % describe_stmt(ref.stmt))
- log('ref: %s' % ref, 1)
- # Print more details about the path through the function that
- # leads to the error:
- for j in range(len(trace.states)):
- state = trace.states[j]
- stmt = state.loc.get_stmt()
- if isinstance(stmt, gcc.GimpleCond):
- nextstate = trace.states[j+1]
- next_stmt = nextstate.loc.get_stmt()
- extra_text('%s: taking %s path at %s'
- % (stmt.loc,
- nextstate.prior_bool,
- get_src_for_loc(stmt.loc)), 1)
- #log(next_stmt, 3)
- # FIXME: phi nodes don't have locations:
- if hasattr(next_stmt, 'loc'):
- if next_stmt.loc:
- extra_text('%s: reaching here %s'
- % (next_stmt.loc,
- get_src_for_loc(next_stmt.loc)),
- 2)
- extra_text('%s: returning %s'
- % (ref.stmt.loc, return_value),
- 1) # FIXME: loc is wrong
-
if 0:
dot = cfg_to_dot(fun.cfg)
invoke_dot(dot)
12 years, 10 months
[gcc-python-plugin] Fix the folding of gcc.GimpleCond where we can know the value of the boolean
by David Hugh Malcolm
commit 4207b2bccc8fc526b9172ea44cc3be54dedbaf15
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Wed Jul 13 00:07:34 2011 -0400
Fix the folding of gcc.GimpleCond where we can know the value of the boolean
Eliminate NonNullPtrValue, since we're using Region instances for this
Start building out the description of the Python API
libcpychecker/absinterp.py | 13 ---
libcpychecker/c_stdio.py | 2 +-
libcpychecker/refcounts.py | 107 +++++++++++++++-----
.../cpychecker/refcounts/fold_conditional/input.c | 35 +++++++
.../refcounts/fold_conditional/script.py | 54 ++++++++++
.../refcounts/fold_conditional/stdout.txt | 5 +
6 files changed, 178 insertions(+), 38 deletions(-)
---
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index cbbc9d9..41e7b05 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -110,19 +110,6 @@ def describe_stmt(stmt):
else:
return str(stmt.loc)
-class NonNullPtrValue(PtrValue):
- def __init__(self, refdelta, stmt):
- PtrValue.__init__(self, True)
- self.refdelta = refdelta
- self.stmt = stmt
-
- def __str__(self):
- return ('non-NULL(%s refs) acquired at %s'
- % (self.refdelta, describe_stmt(self.stmt)))
-
- def __repr__(self):
- return 'NonNullPtrValue(%i)' % self.refdelta
-
class Location:
"""A location within a CFG: a gcc.BasicBlock together with an index into
the gimple list. (We don't support SSA passes)"""
diff --git a/libcpychecker/c_stdio.py b/libcpychecker/c_stdio.py
index bf4bd2a..8577d1e 100644
--- a/libcpychecker/c_stdio.py
+++ b/libcpychecker/c_stdio.py
@@ -19,7 +19,7 @@
# Only partial coverage so var
import gcc
-from absinterp import AbstractValue, NullPtrValue, InvalidlyNullParameter
+from absinterp import AbstractValue, ConcreteValue, InvalidlyNullParameter
class InternalCheckerError(Exception):
pass
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index f647c30..e4d53eb 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -127,6 +127,80 @@ class MyState(State):
else:
raise "foo"
+ def impl_object_ctor(self, stmt, typename):
+ """
+ Given a gcc.GimpleCall to a Python API function that returns a
+ PyObject*, generate a [success, failure] pair of Transitions
+ """
+ assert isinstance(stmt, gcc.GimpleCall)
+ assert isinstance(stmt.fn.operand, gcc.FunctionDecl)
+ assert isinstance(typename, str)
+ # the C identifier of the global PyTypeObject for the type
+ # FIXME: not yet used
+
+ fnname = stmt.fn.operand.name
+
+ # Allocation and assignment:
+ success = self.copy()
+ success.loc = self.loc.next_loc()
+ nonnull = success.make_heap_region()
+ ob_refcnt = success.make_field_region(nonnull, 'ob_refcnt') # FIXME: this should be a memref and fieldref
+ success.value_for_region[ob_refcnt] = RefcountValue(1)
+ success.assign(stmt.lhs, nonnull)
+ success = Transition(self,
+ success,
+ '%s() succeeds' % fnname)
+ failure = self.make_assignment(stmt.lhs,
+ ConcreteValue(stmt.lhs.type, stmt.loc, 0),
+ '%s() fails' % fnname)
+ return [success, failure]
+
+ def make_concrete_return_of(self, stmt, value):
+ """
+ Clone this state (at a function call), updating the location, and
+ setting the result of the call to the given concrete value
+ """
+ newstate = self.copy()
+ newstate.loc = self.loc.next_loc()
+ if stmt.lhs:
+ newstate.assign(stmt.lhs,
+ ConcreteValue(stmt.lhs.type, stmt.loc, value))
+ return newstate
+
+ # Specific Python API function implementations:
+ def impl_PyList_New(self, stmt):
+ success, failure = self.impl_object_ctor(stmt, 'PyList_Type')
+ return [success, failure]
+
+ def impl_PyLong_FromLong(self, stmt):
+ success, failure = self.impl_object_ctor(stmt, 'PyLong_Type')
+ return [success, failure]
+
+ def impl_PyList_SetItem(self, stmt):
+ # int PyList_SetItem(PyObject *list, Py_ssize_t index, PyObject *item)
+ fnname = stmt.fn.operand.name
+
+ # Is it really a list?
+ not_a_list = self.make_concrete_return_of(stmt, -1)
+ # FIXME: can we check that it's a list?
+
+ # Index out of range?
+ out_of_range = self.make_concrete_return_of(stmt, -1)
+
+ success = self.make_concrete_return_of(stmt, 0)
+ # FIXME: update refcounts
+ # Note This function "steals" a reference to item and discards a
+ # reference to an item already in the list at the affected position.
+ return [Transition(self,
+ not_a_list,
+ '%s() fails (not a list)' % fnname),
+ Transition(self,
+ out_of_range,
+ '%s() fails (index out of range)' % fnname),
+ Transition(self,
+ success,
+ '%s() succeeds' % fnname)]
+
def _get_transitions_for_GimpleCall(self, stmt):
log('stmt.lhs: %s %r' % (stmt.lhs, stmt.lhs), 3)
log('stmt.fn: %s %r' % (stmt.fn, stmt.fn), 3)
@@ -138,30 +212,14 @@ class MyState(State):
log('dir(stmt.fn.operand): %s' % dir(stmt.fn.operand), 4)
log('stmt.fn.operand.name: %r' % stmt.fn.operand.name, 4)
fnname = stmt.fn.operand.name
- # Function returning new references:
- if fnname in ('PyList_New', 'PyLong_FromLong'):
- #nonnull = NonNullPtrValue(1, stmt)
-
- # Allocation and assignment:
- success = self.copy()
- success.loc = self.loc.next_loc()
- nonnull = success.make_heap_region()
- ob_refcnt = success.make_field_region(nonnull, 'ob_refcnt') # FIXME: this should be a memref and fieldref
- success.value_for_region[ob_refcnt] = RefcountValue(1)
- success.assign(stmt.lhs, nonnull)
- success = Transition(self,
- success,
- '%s() succeeds' % fnname)
- failure = self.make_assignment(stmt.lhs,
- ConcreteValue(stmt.lhs.type, stmt.loc, 0),
- '%s() fails' % fnname)
- return [success, failure]
+
+ if fnname in ('PyList_New', 'PyLong_FromLong', 'PyList_SetItem'):
+ meth = getattr(self, 'impl_%s' % fnname)
+ return meth(stmt)
# Function returning borrowed references:
elif fnname in ('Py_InitModule4_64',):
return [self.make_assignment(stmt.lhs, NonNullPtrValue(1, stmt)),
self.make_assignment(stmt.lhs, NullPtrValue())]
- #elif fnname in ('PyList_SetItem'):
- # pass
else:
#from libcpychecker.c_stdio import c_stdio_functions, handle_c_stdio_function
@@ -222,10 +280,11 @@ class MyState(State):
log('eval of rhs: %r' % rhs)
log('stmt.exprcode: %r' % stmt.exprcode)
if stmt.exprcode == gcc.EqExpr:
- if isinstance(lhs, NonNullPtrValue) and rhs == 0:
- return False
- if isinstance(lhs, ConcreteValue):
- return lhs.value == rhs
+ if isinstance(rhs, ConcreteValue):
+ if isinstance(lhs, Region) and rhs.value == 0:
+ return False
+ if isinstance(lhs, ConcreteValue):
+ return lhs.value == rhs.value
log('got here')
return UnknownValue(stmt.lhs.type, stmt.loc)
diff --git a/tests/cpychecker/refcounts/fold_conditional/input.c b/tests/cpychecker/refcounts/fold_conditional/input.c
new file mode 100644
index 0000000..2ca7241
--- /dev/null
+++ b/tests/cpychecker/refcounts/fold_conditional/input.c
@@ -0,0 +1,35 @@
+/*
+ Copyright 2011 David Malcolm <dmalcolm(a)redhat.com>
+ Copyright 2011 Red Hat, Inc.
+
+ This is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+*/
+
+#include <Python.h>
+
+PyObject *
+fold_conditional(PyObject *self, PyObject *args)
+{
+ PyObject *list;
+ list = PyList_New(5);
+ if (!list) {
+ return NULL;
+ }
+ return list;
+}
+static PyMethodDef test_methods[] = {
+ {"test_method", fold_conditional, METH_VARARGS, NULL},
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
diff --git a/tests/cpychecker/refcounts/fold_conditional/script.py b/tests/cpychecker/refcounts/fold_conditional/script.py
new file mode 100644
index 0000000..2c62d7a
--- /dev/null
+++ b/tests/cpychecker/refcounts/fold_conditional/script.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+# Copyright 2011 David Malcolm <dmalcolm(a)redhat.com>
+# Copyright 2011 Red Hat, Inc.
+#
+# This is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+import gcc
+from libcpychecker import main, get_traces
+
+def verify_traces(optpass, fun):
+ # Only run in one pass
+ # FIXME: should we be adding our own pass for this?
+ if optpass.name == '*warn_function_return':
+ if fun:
+ traces = get_traces(fun)
+
+ # We should have two traces: there are two different outcomes
+ # for the function call, and the subsequent conditional can only
+ # work one way in each of those scenarios, so it shouldn't lead
+ # to additional traces
+
+ # print('traces: %r' % traces)
+ assert len(traces) == 2
+
+ # Verify the "success" trace:
+ state = traces[0].states[-1]
+ print('Trace 0:')
+ r = state.return_rvalue
+ print(' returned: %r' %r)
+ print(' r->ob_refcnt: %r' % state.get_value_of_field_by_region(r, 'ob_refcnt'))
+
+ # Verify the "fail" trace:
+ state = traces[1].states[-1]
+ print('Trace 1:')
+ print(' returned: %s' % state.return_rvalue)
+
+gcc.register_callback(gcc.PLUGIN_PASS_EXECUTION,
+ verify_traces)
+
+from libcpychecker import main
+main(verify_refcounting=True,
+ show_traces=False)
diff --git a/tests/cpychecker/refcounts/fold_conditional/stdout.txt b/tests/cpychecker/refcounts/fold_conditional/stdout.txt
new file mode 100644
index 0000000..ec5989b
--- /dev/null
+++ b/tests/cpychecker/refcounts/fold_conditional/stdout.txt
@@ -0,0 +1,5 @@
+Trace 0:
+ returned: Region('heap_region_1')
+ r->ob_refcnt: RefcountValue(1)
+Trace 1:
+ returned: (struct PyObject *)0L
12 years, 10 months
[gcc-python-plugin] Ensure that the checker can cope with calls to functions it doesn't know about
by David Hugh Malcolm
commit 692b19490688c461841339b2df51c166e7e8164c
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Jul 12 22:59:44 2011 -0400
Ensure that the checker can cope with calls to functions it doesn't know about
libcpychecker/absinterp.py | 17 +++++---
libcpychecker/refcounts.py | 10 ++--
.../refcounts/unrecognized_function/input.c | 37 +++++++++++++++++
.../refcounts/unrecognized_function/script.py | 43 ++++++++++++++++++++
.../refcounts/unrecognized_function/stdout.txt | 2 +
5 files changed, 97 insertions(+), 12 deletions(-)
---
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index cb1ed5a..cbbc9d9 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -39,6 +39,16 @@ class AbstractValue:
def __repr__(self):
return 'AbstractValue(gcctype=%r, loc=%r)' % (str(self.gcctype), self.loc)
+class UnknownValue(AbstractValue):
+ """
+ A value that we know nothing about
+ """
+ def __str__(self):
+ return 'unknown %s from %s' % (self.gcctype, self.loc)
+
+ def __repr__(self):
+ return 'UnknownValue(gcctype=%r, loc=%r)' % (self.gcctype, self.loc)
+
class ConcreteValue(AbstractValue):
"""
A known, specific value (e.g. 0)
@@ -87,13 +97,6 @@ class NullPtrDereference(PredictedError):
return ('dereferencing NULL (%s) at %s'
% (self.cr, self.state.loc.get_stmt().loc))
-class UnknownValue(AbstractValue):
- def __str__(self):
- return 'unknown %s from %s' % (self.gcctype, self.stmt)
-
- def __repr__(self):
- return 'UnknownValue(%r, %r)' % (self.gcctype, self.stmt)
-
class PtrValue(AbstractValue):
"""An abstract (PyObject*) value"""
def __init__(self, nonnull):
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index f512200..f647c30 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -163,15 +163,15 @@ class MyState(State):
#elif fnname in ('PyList_SetItem'):
# pass
else:
- from libcpychecker.c_stdio import c_stdio_functions, handle_c_stdio_function
+ #from libcpychecker.c_stdio import c_stdio_functions, handle_c_stdio_function
- if fnname in c_stdio_functions:
- return handle_c_stdio_function(self, fnname, stmt)
+ #if fnname in c_stdio_functions:
+ # return handle_c_stdio_function(self, fnname, stmt)
# Unknown function:
log('Invocation of unknown function: %r' % fnname)
return [self.make_assignment(stmt.lhs,
- UnknownValue(returntype, stmt),
+ UnknownValue(returntype, stmt.loc),
None)]
log('stmt.args: %s %r' % (stmt.args, stmt.args), 3)
@@ -227,7 +227,7 @@ class MyState(State):
if isinstance(lhs, ConcreteValue):
return lhs.value == rhs
log('got here')
- return UnknownValue(None, stmt)
+ return UnknownValue(stmt.lhs.type, stmt.loc)
def eval_rhs(self, stmt):
rhs = stmt.rhs
diff --git a/tests/cpychecker/refcounts/unrecognized_function/input.c b/tests/cpychecker/refcounts/unrecognized_function/input.c
new file mode 100644
index 0000000..a5d9020
--- /dev/null
+++ b/tests/cpychecker/refcounts/unrecognized_function/input.c
@@ -0,0 +1,37 @@
+/*
+ Copyright 2011 David Malcolm <dmalcolm(a)redhat.com>
+ Copyright 2011 Red Hat, Inc.
+
+ This is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+*/
+
+#include <Python.h>
+
+/*
+ Ensure that the checker can cope with calls to a function that it doesn't
+ recognize:
+*/
+extern PyObject *foo(int i);
+
+PyObject *
+call_to_unrecognized_function(PyObject *self, PyObject *args)
+{
+ PyObject *result = foo(42);
+ return result;
+}
+static PyMethodDef test_methods[] = {
+ {"test_method", call_to_unrecognized_function, METH_VARARGS, NULL},
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
diff --git a/tests/cpychecker/refcounts/unrecognized_function/script.py b/tests/cpychecker/refcounts/unrecognized_function/script.py
new file mode 100644
index 0000000..8c554e6
--- /dev/null
+++ b/tests/cpychecker/refcounts/unrecognized_function/script.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+# Copyright 2011 David Malcolm <dmalcolm(a)redhat.com>
+# Copyright 2011 Red Hat, Inc.
+#
+# This is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+import gcc
+from libcpychecker import main, get_traces
+
+def verify_traces(optpass, fun):
+ # Only run in one pass
+ # FIXME: should we be adding our own pass for this?
+ if optpass.name == '*warn_function_return':
+ if fun:
+ traces = get_traces(fun)
+
+ # We should have one trace
+ # print('traces: %r' % traces)
+ assert len(traces) == 1
+
+ # Verify the trace:
+ state = traces[0].states[-1]
+ print('Trace 0:')
+ print(' returned: %s' % state.return_rvalue)
+
+gcc.register_callback(gcc.PLUGIN_PASS_EXECUTION,
+ verify_traces)
+
+from libcpychecker import main
+main(verify_refcounting=True,
+ show_traces=False)
diff --git a/tests/cpychecker/refcounts/unrecognized_function/stdout.txt b/tests/cpychecker/refcounts/unrecognized_function/stdout.txt
new file mode 100644
index 0000000..8b4569f
--- /dev/null
+++ b/tests/cpychecker/refcounts/unrecognized_function/stdout.txt
@@ -0,0 +1,2 @@
+Trace 0:
+ returned: unknown struct PyObject * from tests/cpychecker/refcounts/unrecognized_function/input.c:31
12 years, 10 months