[gcc-python-plugin] cpychecker: implement PyDict_GetItemString
by David Hugh Malcolm
commit de4b96b06916479f8fb6486e019918a33ef3a96e
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Aug 30 14:41:14 2011 -0400
cpychecker: implement PyDict_GetItemString
libcpychecker/refcounts.py | 36 ++++++++++++-
.../refcounts/PyDict_GetItemString/correct/input.c | 56 ++++++++++++++++++++
.../PyDict_GetItemString/correct/script.py | 22 ++++++++
.../PyDict_GetItemString/correct/stdout.txt | 44 +++++++++++++++
.../PyDict_GetItemString/incorrect/input.c | 49 +++++++++++++++++
.../PyDict_GetItemString/incorrect/script.py | 22 ++++++++
.../PyDict_GetItemString/incorrect/stderr.txt | 13 +++++
.../PyDict_GetItemString/incorrect/stdout.txt | 41 ++++++++++++++
8 files changed, 282 insertions(+), 1 deletions(-)
---
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 7595141..9ad71ea 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -28,7 +28,7 @@ from gccutils import cfg_to_dot, invoke_dot, get_src_for_loc, check_isinstance
from libcpychecker.absinterp import *
from libcpychecker.diagnostics import Reporter, Annotator, Note
from libcpychecker.PyArg_ParseTuple import log
-from libcpychecker.types import is_py3k, is_debug_build
+from libcpychecker.types import is_py3k, is_debug_build, get_PyObjectPtr
# I found myself regularly getting State and Transition instances confused. To
# ameliorate that, here are some naming conventions and abbreviations:
@@ -102,6 +102,9 @@ def stmt_is_return_of_objptr(stmt):
if type_is_pyobjptr(stmt.retval.type):
return True
+def make_null_pyobject_ptr(stmt):
+ return ConcreteValue(get_PyObjectPtr(), stmt.loc, 0)
+
class RefcountValue(AbstractValue):
"""
Value for an ob_refcnt field.
@@ -460,6 +463,18 @@ class MyState(State):
t_failure.dest.set_exception('PyExc_MemoryError')
return t_failure.dest
+
+ def mktrans_from_fncall_state(self, stmt, state, partialdesc):
+ """
+ Given a function call here, convert a State instance into a Transition
+ instance, marking it.
+ """
+ check_isinstance(stmt, gcc.GimpleCall)
+ check_isinstance(state, State)
+ check_isinstance(partialdesc, str)
+ fnname = stmt.fn.operand.name
+ return Transition(self, state, '%s() %s' % (fnname, partialdesc))
+
def make_transitions_for_fncall(self, stmt, s_success, s_failure):
"""
Given a function call, convert a pair of State instances into a pair
@@ -531,6 +546,25 @@ class MyState(State):
desc)]
# Specific Python API function implementations:
+ def impl_PyDict_GetItemString(self, stmt):
+ # Declared in dictobject.h:
+ # PyAPI_FUNC(PyObject *) PyDict_GetItemString(PyObject *dp, const char *key);
+ # Defined in dictobject.c
+ #
+ # Returns a borrowed ref, or NULL if not found (can also return NULL
+ # and set MemoryError)
+ s_success = self.mkstate_borrowed_ref(stmt, 'PyDict_GetItemString')
+ t_notfound = self.mktrans_assignment(stmt.lhs,
+ make_null_pyobject_ptr(stmt),
+ 'PyDict_GetItemString does not find string')
+ if 0:
+ t_memoryexc = self.mktrans_assignment(stmt.lhs,
+ make_null_pyobject_ptr(stmt),
+ 'OOM allocating string') # FIXME: set exception
+ return [self.mktrans_from_fncall_state(stmt, s_success, 'succeeds'),
+ t_notfound]
+ #t_memoryexc]
+
def impl_PyErr_SetString(self, stmt):
# Declared in pyerrors.h:
# PyAPI_FUNC(void) PyErr_SetString(PyObject *, const char *);
diff --git a/tests/cpychecker/refcounts/PyDict_GetItemString/correct/input.c b/tests/cpychecker/refcounts/PyDict_GetItemString/correct/input.c
new file mode 100644
index 0000000..aed4f36
--- /dev/null
+++ b/tests/cpychecker/refcounts/PyDict_GetItemString/correct/input.c
@@ -0,0 +1,56 @@
+/*
+ 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>
+
+/*
+ Test of correct reference-handling in a call to PyDict_GetItemString
+*/
+
+extern PyObject *some_dict;
+
+PyObject *
+test(PyObject *self, PyObject *args)
+{
+ PyObject *item;
+ item = PyDict_GetItemString(some_dict, "item");
+ if (item) {
+ /*
+ If it succeeds, it only returns a borrowed ref, so we must incref
+ it:
+ */
+ Py_INCREF(item);
+ return item;
+ }
+ PyErr_SetString(PyExc_KeyError, "item not found");
+ return NULL;
+}
+
+static PyMethodDef test_methods[] = {
+ {"test_method", test, METH_VARARGS, NULL},
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/refcounts/PyDict_GetItemString/correct/script.py b/tests/cpychecker/refcounts/PyDict_GetItemString/correct/script.py
new file mode 100644
index 0000000..fdd5ba3
--- /dev/null
+++ b/tests/cpychecker/refcounts/PyDict_GetItemString/correct/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/PyDict_GetItemString/correct/stdout.txt b/tests/cpychecker/refcounts/PyDict_GetItemString/correct/stdout.txt
new file mode 100644
index 0000000..98ace2f
--- /dev/null
+++ b/tests/cpychecker/refcounts/PyDict_GetItemString/correct/stdout.txt
@@ -0,0 +1,44 @@
+Trace 0:
+ Transitions:
+ 'PyDict_GetItemString() succeeds'
+ 'taking True path'
+ 'returning'
+ Return value:
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyDict_GetItemString/correct/input.c', line=32), region=RegionOnHeap('PyDict_GetItemString', gcc.Location(file='tests/cpychecker/refcounts/PyDict_GetItemString/correct/input.c', line=32)))
+ str(): (struct PyObject *)&RegionOnHeap('PyDict_GetItemString', gcc.Location(file='tests/cpychecker/refcounts/PyDict_GetItemString/correct/input.c', line=32)) from tests/cpychecker/refcounts/PyDict_GetItemString/correct/input.c:32
+ r->ob_refcnt: refs: 1 + N where N >= 1
+ r->ob_type: None
+ Region("region-for-arg-gcc.ParmDecl('self')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('self')")
+ str(): Region("region-for-arg-gcc.ParmDecl('self')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyDict_GetItemString/correct/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('self')"))
+ Region("region-for-arg-gcc.ParmDecl('args')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('args')")
+ str(): Region("region-for-arg-gcc.ParmDecl('args')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyDict_GetItemString/correct/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ Exception:
+ (struct PyObject *)0 from tests/cpychecker/refcounts/PyDict_GetItemString/correct/input.c:30
+
+Trace 1:
+ Transitions:
+ 'PyDict_GetItemString does not find string'
+ 'taking False path'
+ 'calling PyErr_SetString()'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyDict_GetItemString/correct/input.c', line=42), value=0)
+ str(): (struct PyObject *)0 from tests/cpychecker/refcounts/PyDict_GetItemString/correct/input.c:42
+ Region("region-for-arg-gcc.ParmDecl('self')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('self')")
+ str(): Region("region-for-arg-gcc.ParmDecl('self')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyDict_GetItemString/correct/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('self')"))
+ Region("region-for-arg-gcc.ParmDecl('args')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('args')")
+ str(): Region("region-for-arg-gcc.ParmDecl('args')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyDict_GetItemString/correct/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ Exception:
+ unknown struct PyObject * from /usr/include/python2.7/pyerrors.h:135
diff --git a/tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c b/tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c
new file mode 100644
index 0000000..be57ea7
--- /dev/null
+++ b/tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c
@@ -0,0 +1,49 @@
+/*
+ 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>
+
+/*
+ Test of incorrect reference-handling in a call to PyDict_GetItemString
+*/
+
+extern PyObject *some_dict;
+
+PyObject *
+test(PyObject *self, PyObject *args)
+{
+ /*
+ This is incorrect: if the call succeeds, it only returns a borrowed ref,
+ so this will leave it with too low a reference count:
+ */
+ return PyDict_GetItemString(some_dict, "item");
+}
+
+static PyMethodDef test_methods[] = {
+ {"test_method", test, METH_VARARGS, NULL},
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/script.py b/tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/script.py
new file mode 100644
index 0000000..fdd5ba3
--- /dev/null
+++ b/tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/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/PyDict_GetItemString/incorrect/stderr.txt b/tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/stderr.txt
new file mode 100644
index 0000000..5e6463b
--- /dev/null
+++ b/tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/stderr.txt
@@ -0,0 +1,13 @@
+tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c: In function 'test':
+tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c:35:5: error: ob_refcnt of return value is 1 too low
+tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c:35:5: note: was expecting final ob_refcnt to be N + 1 (for some unknown N)
+tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c:35:5: note: due to object being referenced by: return value
+tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c:35:5: note: but final ob_refcnt is N + 0
+tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c:35:5: note: PyDict_GetItemString allocated at: return PyDict_GetItemString(some_dict, "item");
+tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c:35:5: note: when PyDict_GetItemString() succeeds at: return PyDict_GetItemString(some_dict, "item");
+tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c:35:5: note: ob_refcnt is now refs: 0 + N where N >= 1
+tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c:35:5: note: when returning at: return PyDict_GetItemString(some_dict, "item");
+tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c:35:5: error: returning (PyObject*)NULL without setting an exception
+tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c:35:5: note: when PyDict_GetItemString does not find string at: return PyDict_GetItemString(some_dict, "item");
+tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c:35:5: note: when returning at: return PyDict_GetItemString(some_dict, "item");
+tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c:30:1: note: graphical error report for function 'test' written out to 'tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c.test-refcount-errors.html'
diff --git a/tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/stdout.txt b/tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/stdout.txt
new file mode 100644
index 0000000..f741ffb
--- /dev/null
+++ b/tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/stdout.txt
@@ -0,0 +1,41 @@
+Trace 0:
+ Transitions:
+ 'PyDict_GetItemString() succeeds'
+ 'returning'
+ Return value:
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c', line=35), region=RegionOnHeap('PyDict_GetItemString', gcc.Location(file='tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c', line=35)))
+ str(): (struct PyObject *)&RegionOnHeap('PyDict_GetItemString', gcc.Location(file='tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c', line=35)) from tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c:35
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: None
+ Region("region-for-arg-gcc.ParmDecl('self')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('self')")
+ str(): Region("region-for-arg-gcc.ParmDecl('self')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('self')"))
+ Region("region-for-arg-gcc.ParmDecl('args')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('args')")
+ str(): Region("region-for-arg-gcc.ParmDecl('args')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ Exception:
+ (struct PyObject *)0 from tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c:30
+
+Trace 1:
+ Transitions:
+ 'PyDict_GetItemString does not find string'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c', line=35), value=0)
+ str(): (struct PyObject *)0 from tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c:35
+ Region("region-for-arg-gcc.ParmDecl('self')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('self')")
+ str(): Region("region-for-arg-gcc.ParmDecl('self')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('self')"))
+ Region("region-for-arg-gcc.ParmDecl('args')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('args')")
+ str(): Region("region-for-arg-gcc.ParmDecl('args')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ Exception:
+ (struct PyObject *)0 from tests/cpychecker/refcounts/PyDict_GetItemString/incorrect/input.c:30
12 years, 9 months
[gcc-python-plugin] cpychecker: implement PyErr_SetString
by David Hugh Malcolm
commit 209ee357a6279eea16e845b3d4989ee8903825ad
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Aug 30 14:16:00 2011 -0400
cpychecker: implement PyErr_SetString
libcpychecker/refcounts.py | 20 +++++++++
.../refcounts/PyErr_SetString/correct/input.c | 44 ++++++++++++++++++++
.../refcounts/PyErr_SetString/correct/script.py | 22 ++++++++++
.../refcounts/PyErr_SetString/correct/stdout.txt | 19 ++++++++
4 files changed, 105 insertions(+), 0 deletions(-)
---
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 2b4cb05..7595141 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -379,6 +379,16 @@ class MyState(State):
self.value_for_region[ob_refcnt] = RefcountValue(value.relvalue - 1,
value.min_external + 1)
+ def mktrans_nop(self, stmt, fnname):
+ """
+ Make a Transition for handling a function call that has no "visible"
+ effect within our simulation (beyond advancing to the next location).
+ [We might subsequently modify the destination state, though]
+ """
+ newstate = self.copy()
+ newstate.loc = self.loc.next_loc()
+ return Transition(self, newstate, 'calling %s()' % fnname)
+
def mkstate_new_ref(self, stmt, name, typeobjregion=None):
"""
Make a new State, in which a new ref to some object has been
@@ -521,6 +531,16 @@ class MyState(State):
desc)]
# Specific Python API function implementations:
+ def impl_PyErr_SetString(self, stmt):
+ # Declared in pyerrors.h:
+ # PyAPI_FUNC(void) PyErr_SetString(PyObject *, const char *);
+ # Defined in Python/errors.c
+ #
+ v_exc, v_string = self.eval_stmt_args(stmt)
+ t_next = self.mktrans_nop(stmt, 'PyErr_SetString')
+ t_next.dest.exception_rvalue = v_exc
+ return [t_next]
+
def impl_PyList_New(self, stmt):
# Decl:
# PyObject* PyList_New(Py_ssize_t len)
diff --git a/tests/cpychecker/refcounts/PyErr_SetString/correct/input.c b/tests/cpychecker/refcounts/PyErr_SetString/correct/input.c
new file mode 100644
index 0000000..99660d6
--- /dev/null
+++ b/tests/cpychecker/refcounts/PyErr_SetString/correct/input.c
@@ -0,0 +1,44 @@
+/*
+ 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>
+
+/*
+ Test of correct usage of PyErr_SetString
+*/
+
+PyObject *
+test(PyObject *self, PyObject *args)
+{
+ PyErr_SetString(PyExc_RuntimeError,
+ "This test method always fails");
+ return NULL;
+}
+static PyMethodDef test_methods[] = {
+ {"test_method", test, METH_VARARGS, NULL},
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/refcounts/PyErr_SetString/correct/script.py b/tests/cpychecker/refcounts/PyErr_SetString/correct/script.py
new file mode 100644
index 0000000..fdd5ba3
--- /dev/null
+++ b/tests/cpychecker/refcounts/PyErr_SetString/correct/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/PyErr_SetString/correct/stdout.txt b/tests/cpychecker/refcounts/PyErr_SetString/correct/stdout.txt
new file mode 100644
index 0000000..138bb65
--- /dev/null
+++ b/tests/cpychecker/refcounts/PyErr_SetString/correct/stdout.txt
@@ -0,0 +1,19 @@
+Trace 0:
+ Transitions:
+ 'calling PyErr_SetString()'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyErr_SetString/correct/input.c', line=31), value=0)
+ str(): (struct PyObject *)0 from tests/cpychecker/refcounts/PyErr_SetString/correct/input.c:31
+ Region("region-for-arg-gcc.ParmDecl('self')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('self')")
+ str(): Region("region-for-arg-gcc.ParmDecl('self')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyErr_SetString/correct/input.c', line=27), region=Region("region-for-type-of-arg-gcc.ParmDecl('self')"))
+ Region("region-for-arg-gcc.ParmDecl('args')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('args')")
+ str(): Region("region-for-arg-gcc.ParmDecl('args')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyErr_SetString/correct/input.c', line=27), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ Exception:
+ unknown struct PyObject * from /usr/include/python2.7/pyerrors.h:140
12 years, 9 months
[gcc-python-plugin] cpychecker: add (disabled) hook that complains about Python API entrypoints that weren't explicitly
by David Hugh Malcolm
commit 27aa698452edccfd4e4de5e9651a92b49d4e852b
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Aug 30 14:12:52 2011 -0400
cpychecker: add (disabled) hook that complains about Python API entrypoints that weren't explicitly handled
libcpychecker/refcounts.py | 7 +++++++
1 files changed, 7 insertions(+), 0 deletions(-)
---
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 550f393..2b4cb05 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -728,6 +728,13 @@ class MyState(State):
#if fnname in c_stdio_functions:
# return handle_c_stdio_function(self, fnname, stmt)
+ if 0:
+ # For extending coverage of the Python API:
+ # Detect and complain about Python API entrypoints that
+ # weren't explicitly handled
+ if fnname.startswith('_Py') or fnname.startswith('Py'):
+ raise NotImplementedError('not yet implemented: %s' % fnname)
+
# Unknown function returning (PyObject*):
if str(stmt.fn.operand.type.type) == 'struct PyObject *':
log('Invocation of unknown function returning PyObject *: %r' % fnname)
12 years, 9 months
[gcc-python-plugin] cpychecker: introduce a naming convention to try to stop confusion between State and Transition
by David Hugh Malcolm
commit 5feb3cac7c4d28e9dfdbfa6feadc99c9a1a5deda
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Aug 30 14:02:17 2011 -0400
cpychecker: introduce a naming convention to try to stop confusion between State and Transition
libcpychecker/absinterp.py | 10 ++-
libcpychecker/refcounts.py | 152 ++++++++++++++++++++++++--------------------
2 files changed, 91 insertions(+), 71 deletions(-)
---
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index 70f5320..8bfdc07 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -54,7 +54,7 @@ class AbstractValue:
check_isinstance(state, State)
check_isinstance(stmt, gcc.GimpleCall)
returntype = stmt.fn.type.dereference.type
- return [state.make_assignment(stmt.lhs,
+ return [state.mktrans_assignment(stmt.lhs,
UnknownValue(returntype, stmt.loc),
'calling %s' % self)]
@@ -768,8 +768,12 @@ class State:
self.value_for_region[region] = UninitializedData(local.type, fun.start)
self.verify()
- def make_assignment(self, lhs, rhs, desc):
- log('make_assignment(%r, %r, %r)', lhs, rhs, desc)
+ def mktrans_assignment(self, lhs, rhs, desc):
+ """
+ Return a Transition to a state at the next location, with the RHS
+ assigned to the LHS, if LHS is not None
+ """
+ log('mktrans_assignment(%r, %r, %r)', lhs, rhs, desc)
if desc:
check_isinstance(desc, str)
new = self.copy()
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 5590f7b..550f393 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -30,6 +30,18 @@ from libcpychecker.diagnostics import Reporter, Annotator, Note
from libcpychecker.PyArg_ParseTuple import log
from libcpychecker.types import is_py3k, is_debug_build
+# I found myself regularly getting State and Transition instances confused. To
+# ameliorate that, here are some naming conventions and abbreviations:
+#
+# Within method names:
+# "mktrans_" means "make a Transition"
+# "mkstate_" means "make a State"
+#
+# Within variable names
+# the prefix "t_" means a Transition
+# the prefix "s_" means a State
+# the prefix "v_" means an AbstractValue
+
def stmt_is_assignment_to_count(stmt):
if hasattr(stmt, 'lhs'):
if stmt.lhs:
@@ -152,7 +164,7 @@ class GenericTpDealloc(AbstractValue):
# Get the description of the region before trashing it:
desc = 'calling tp_dealloc on %s' % region
- result = state.make_assignment(stmt.lhs,
+ result = state.mktrans_assignment(stmt.lhs,
UnknownValue(returntype, stmt.loc),
'calling tp_dealloc on %s' % region)
new = state.copy()
@@ -230,10 +242,10 @@ class MyState(State):
typeobjregion)
self.verify()
- def make_assignment(self, key, value, desc, additional_ptr=None):
+ def mktrans_assignment(self, key, value, desc, additional_ptr=None):
if desc:
check_isinstance(desc, str)
- transition = State.make_assignment(self, key, value, desc)
+ transition = State.mktrans_assignment(self, key, value, desc)
if additional_ptr:
transition.dest.owned_refs.append(additional_ptr)
return transition
@@ -308,7 +320,7 @@ class MyState(State):
"""
Given a gcc.GimpleCall to a Python API function that returns a
PyObject*, generate a
- (newobj, success, failure)
+ (newobj, t_success, t_failure)
triple, where newobj is a region, and success/failure are Transitions
"""
check_isinstance(stmt, gcc.GimpleCall)
@@ -330,18 +342,18 @@ class MyState(State):
typeobjregion = self.var_region(typeobjdecl)
# The "success" case:
- success, nonnull = self.make_new_ref(stmt, typename, typeobjregion)
- success = Transition(self,
- success,
- '%s() succeeds' % fnname)
+ s_success, nonnull = self.mkstate_new_ref(stmt, typename, typeobjregion)
+ t_success = Transition(self,
+ s_success,
+ '%s() succeeds' % fnname)
# The "failure" case:
- failure = self.make_assignment(stmt.lhs,
+ t_failure = self.mktrans_assignment(stmt.lhs,
ConcreteValue(stmt.lhs.type, stmt.loc, 0),
'%s() fails' % fnname)
- failure.dest.set_exception('PyExc_MemoryError')
- return (nonnull, success, failure)
+ t_failure.dest.set_exception('PyExc_MemoryError')
+ return (nonnull, t_success, t_failure)
- def make_concrete_return_of(self, stmt, value):
+ def mkstate_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
@@ -367,7 +379,7 @@ class MyState(State):
self.value_for_region[ob_refcnt] = RefcountValue(value.relvalue - 1,
value.min_external + 1)
- def make_new_ref(self, stmt, name, typeobjregion=None):
+ def mkstate_new_ref(self, stmt, name, typeobjregion=None):
"""
Make a new State, in which a new ref to some object has been
assigned to the statement's LHS.
@@ -406,8 +418,8 @@ class MyState(State):
stmt.loc)
return newstate, nonnull
- def make_borrowed_ref(self, stmt, name):
- """Make a new state, giving a borrowed ref to some object"""
+ def mkstate_borrowed_ref(self, stmt, name):
+ """Make a new State, giving a borrowed ref to some object"""
newstate = self.copy()
newstate.loc = self.loc.next_loc()
@@ -426,28 +438,32 @@ class MyState(State):
stmt.loc)
return newstate
- def make_exception(self, stmt, fnname):
- """Make a new state, giving NULL and some exception"""
+ def mkstate_exception(self, stmt, fnname):
+ """Make a new State, giving NULL and some exception"""
if stmt.lhs:
value = ConcreteValue(stmt.lhs.type, stmt.loc, 0)
else:
value = None
- failure = self.make_assignment(stmt.lhs,
- value,
- None)
- failure.dest.set_exception('PyExc_MemoryError')
- return failure.dest
-
+ t_failure = self.mktrans_assignment(stmt.lhs,
+ value,
+ None)
+ t_failure.dest.set_exception('PyExc_MemoryError')
+ return t_failure.dest
- def make_transitions_for_fncall(self, stmt, success, failure):
+ def make_transitions_for_fncall(self, stmt, s_success, s_failure):
+ """
+ Given a function call, convert a pair of State instances into a pair
+ of Transition instances, marking one as a successful call, the other
+ as a failed call.
+ """
check_isinstance(stmt, gcc.GimpleCall)
- check_isinstance(success, State)
- check_isinstance(failure, State)
+ check_isinstance(s_success, State)
+ check_isinstance(s_failure, State)
fnname = stmt.fn.operand.name
- return [Transition(self, success, '%s() succeeds' % fnname),
- Transition(self, failure, '%s() fails' % fnname)]
+ return [Transition(self, s_success, '%s() succeeds' % fnname),
+ Transition(self, s_failure, '%s() fails' % fnname)]
def eval_stmt_args(self, stmt):
assert isinstance(stmt, gcc.GimpleCall)
@@ -470,7 +486,7 @@ class MyState(State):
if isinstance(args[0], PointerToRegion):
if isinstance(args[0].region, RegionForStringConstant):
desc = args[0].region.text
- return [self.make_assignment(stmt.lhs,
+ return [self.mktrans_assignment(stmt.lhs,
UnknownValue(returntype, stmt.loc),
desc)]
@@ -481,7 +497,7 @@ class MyState(State):
# traces)
args = self.eval_stmt_args(stmt)
desc = '__dump(%s)' % (','.join([str(arg) for arg in args]))
- return [self.make_assignment(stmt.lhs,
+ return [self.mktrans_assignment(stmt.lhs,
UnknownValue(returntype, stmt.loc),
desc)]
@@ -500,7 +516,7 @@ class MyState(State):
if args[0] != args[1]:
raise AssertionError('%s != %s' % (args[0], args[1]))
desc = '__cpychecker_assert_equal(%s)' % (','.join([str(arg) for arg in args]))
- return [self.make_assignment(stmt.lhs,
+ return [self.mktrans_assignment(stmt.lhs,
UnknownValue(returntype, stmt.loc),
desc)]
@@ -551,22 +567,22 @@ class MyState(State):
op, newitem = self.eval_stmt_args(stmt)
# On success, adds a ref on input:
- success = self.make_concrete_return_of(stmt, 0)
- success.add_ref(newitem, stmt.loc)
+ s_success = self.mkstate_concrete_return_of(stmt, 0)
+ s_success.add_ref(newitem, stmt.loc)
#...and set the pointer value within ob_item array, so that we can
# discount that refcount:
ob_item_region = self.make_field_region(op.region, 'ob_item')
ob_size_region = self.make_field_region(op.region, 'ob_size')
- check_isinstance(success.value_for_region[ob_size_region], ConcreteValue) # for now
- index = success.value_for_region[ob_size_region].value
- array_region = success._array_region(ob_item_region, index)
- success.value_for_region[array_region] = newitem
+ check_isinstance(s_success.value_for_region[ob_size_region], ConcreteValue) # for now
+ index = s_success.value_for_region[ob_size_region].value
+ array_region = s_success._array_region(ob_item_region, index)
+ s_success.value_for_region[array_region] = newitem
# Can fail with memory error, overflow error:
- failure = self.make_concrete_return_of(stmt, -1)
- failure.set_exception('PyExc_MemoryError')
+ s_failure = self.mkstate_concrete_return_of(stmt, -1)
+ s_failure.set_exception('PyExc_MemoryError')
- return self.make_transitions_for_fncall(stmt, success, failure)
+ return self.make_transitions_for_fncall(stmt, s_success, s_failure)
def impl_PyList_SetItem(self, stmt):
# Decl:
@@ -580,30 +596,30 @@ class MyState(State):
# Is it really a list?
if 0: # FIXME: check
- not_a_list = self.make_concrete_return_of(stmt, -1)
+ not_a_list = self.mkstate_concrete_return_of(stmt, -1)
result.append(Transition(self,
not_a_list,
'%s() fails (not a list)' % fnname))
# Index out of range?
if 0: # FIXME: check
- out_of_range = self.make_concrete_return_of(stmt, -1)
+ out_of_range = self.mkstate_concrete_return_of(stmt, -1)
result.append(Transition(self,
out_of_range,
'%s() fails (index out of range)' % fnname))
if 1:
- success = self.make_concrete_return_of(stmt, 0)
+ s_success = self.mkstate_concrete_return_of(stmt, 0)
# FIXME: update refcounts
# "Steal" a reference to item:
if isinstance(arg_item, PointerToRegion):
check_isinstance(arg_item.region, Region)
- success.steal_reference(arg_item.region)
+ s_success.steal_reference(arg_item.region)
# and discards a
# reference to an item already in the list at the affected position.
result.append(Transition(self,
- success,
+ s_success,
'%s() succeeds' % fnname))
return result
@@ -614,14 +630,14 @@ class MyState(State):
# Also:
# #define PyArg_ParseTuple _PyArg_ParseTuple_SizeT
- success = self.make_concrete_return_of(stmt, 1)
+ s_success = self.mkstate_concrete_return_of(stmt, 1)
- failure = self.make_concrete_return_of(stmt, 0)
+ s_failure = self.mkstate_concrete_return_of(stmt, 0)
# Various errors are possible, but a TypeError is always possible
# e.g. for the case of the wrong number of arguments:
- failure.set_exception('PyExc_TypeError')
+ s_failure.set_exception('PyExc_TypeError')
- return self.make_transitions_for_fncall(stmt, success, failure)
+ return self.make_transitions_for_fncall(stmt, s_success, s_failure)
def impl_Py_InitModule4_64(self, stmt):
# Decl:
@@ -636,9 +652,9 @@ class MyState(State):
# with tracerefs:
# #define Py_InitModule4 Py_InitModule4TraceRefs_64
# #define Py_InitModule4 Py_InitModule4TraceRefs
- success = self.make_borrowed_ref(stmt, 'output from Py_InitModule4')
- failure = self.make_exception(stmt, 'Py_InitModule4')
- return self.make_transitions_for_fncall(stmt, success, failure)
+ s_success = self.mkstate_borrowed_ref(stmt, 'output from Py_InitModule4')
+ s_failure = self.mkstate_exception(stmt, 'Py_InitModule4')
+ return self.make_transitions_for_fncall(stmt, s_success, s_failure)
def impl__PyObject_New(self, stmt):
# Declaration in objimpl.h:
@@ -657,19 +673,19 @@ class MyState(State):
tp_rvalue = self.eval_rvalue(stmt.args[0], stmt.loc)
# Success case: allocation and assignment:
- success, nonnull = self.make_new_ref(stmt, '_PyObject_New')
+ s_success, nonnull = self.mkstate_new_ref(stmt, '_PyObject_New')
# ...and set up ob_type on the result object:
- ob_type = success.make_field_region(nonnull, 'ob_type')
- success.value_for_region[ob_type] = tp_rvalue
- success = Transition(self,
- success,
+ ob_type = s_success.make_field_region(nonnull, 'ob_type')
+ s_success.value_for_region[ob_type] = tp_rvalue
+ t_success = Transition(self,
+ s_success,
'_PyObject_New() succeeds')
# Failure case:
- failure = self.make_assignment(stmt.lhs,
+ t_failure = self.mktrans_assignment(stmt.lhs,
ConcreteValue(stmt.lhs.type, stmt.loc, 0),
'_PyObject_New() fails')
- failure.dest.set_exception('PyExc_MemoryError')
- return [success, failure]
+ t_failure.dest.set_exception('PyExc_MemoryError')
+ return [t_success, t_failure]
def _get_transitions_for_GimpleCall(self, stmt):
log('stmt.lhs: %s %r', stmt.lhs, stmt.lhs)
@@ -718,14 +734,14 @@ class MyState(State):
# Assume that all such functions either:
# - return a new reference, or
# - return NULL and set an exception (e.g. MemoryError)
- success, nonnull = self.make_new_ref(stmt,
- 'new ref from (unknown) %s' % fnname)
- failure = self.make_exception(stmt, fnname)
- return self.make_transitions_for_fncall(stmt, success, failure)
+ s_success, nonnull = self.mkstate_new_ref(stmt,
+ 'new ref from (unknown) %s' % fnname)
+ s_failure = self.mkstate_exception(stmt, fnname)
+ return self.make_transitions_for_fncall(stmt, s_success, s_failure)
# Unknown function of other type:
log('Invocation of unknown function: %r', fnname)
- return [self.make_assignment(stmt.lhs,
+ return [self.mktrans_assignment(stmt.lhs,
UnknownValue(returntype, stmt.loc),
None)]
@@ -969,9 +985,9 @@ class MyState(State):
log('removing ownership of %s', value)
nextstate.owned_refs.remove(value)
"""
- return [self.make_assignment(stmt.lhs,
- value,
- None)]
+ return [self.mktrans_assignment(stmt.lhs,
+ value,
+ None)]
def _get_transitions_for_GimpleReturn(self, stmt):
#log('stmt.lhs: %r %s', stmt.lhs, stmt.lhs)
12 years, 9 months
[gcc-python-plugin] cpychecker: show exception status in debug dumps
by David Hugh Malcolm
commit 69faa3035ff014d9405b5495fa5160569c3e3e74
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Aug 30 13:42:31 2011 -0400
cpychecker: show exception status in debug dumps
libcpychecker/refcounts.py | 6 ++++++
1 files changed, 6 insertions(+), 0 deletions(-)
---
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 8e1f428..5590f7b 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -1153,6 +1153,12 @@ class DebugAnnotator(Annotator):
('%s has initial value: %s'
% (region, dest_value))))
+ # Show exception information:
+ if transition.dest.exception_rvalue != transition.src.exception_rvalue:
+ result.append(Note(loc,
+ ('thread-local exception state now has value: %s'
+ % transition.dest.exception_rvalue)))
+
return result
12 years, 9 months
[gcc-python-plugin] cpychecker: fix an exception that occurred when C code discarded a (PyObject*) value
by David Hugh Malcolm
commit 0552cb99b377bdd43f3e96ad1d5bbe9d6cdd7dd1
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Aug 30 11:29:48 2011 -0400
cpychecker: fix an exception that occurred when C code discarded a (PyObject*) value
libcpychecker/refcounts.py | 6 ++-
.../refcounts/unrecognized_function3/input.c | 41 ++++++++++++++++++++
.../refcounts/unrecognized_function3/script.py | 22 ++++++++++
.../refcounts/unrecognized_function3/stderr.txt | 9 ++++
.../refcounts/unrecognized_function3/stdout.txt | 38 ++++++++++++++++++
5 files changed, 115 insertions(+), 1 deletions(-)
---
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index b16e7a9..8e1f428 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -428,8 +428,12 @@ class MyState(State):
def make_exception(self, stmt, fnname):
"""Make a new state, giving NULL and some exception"""
+ if stmt.lhs:
+ value = ConcreteValue(stmt.lhs.type, stmt.loc, 0)
+ else:
+ value = None
failure = self.make_assignment(stmt.lhs,
- ConcreteValue(stmt.lhs.type, stmt.loc, 0),
+ value,
None)
failure.dest.set_exception('PyExc_MemoryError')
return failure.dest
diff --git a/tests/cpychecker/refcounts/unrecognized_function3/input.c b/tests/cpychecker/refcounts/unrecognized_function3/input.c
new file mode 100644
index 0000000..00462e0
--- /dev/null
+++ b/tests/cpychecker/refcounts/unrecognized_function3/input.c
@@ -0,0 +1,41 @@
+/*
+ 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, in which the return value is discarded:
+*/
+extern PyObject *foo(int i);
+
+void
+call_to_unrecognized_function(PyObject *self, PyObject *args)
+{
+ /* Discard the result (leaking it if the call succeeds): */
+ foo(42);
+}
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/refcounts/unrecognized_function3/script.py b/tests/cpychecker/refcounts/unrecognized_function3/script.py
new file mode 100644
index 0000000..fdd5ba3
--- /dev/null
+++ b/tests/cpychecker/refcounts/unrecognized_function3/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/unrecognized_function3/stderr.txt b/tests/cpychecker/refcounts/unrecognized_function3/stderr.txt
new file mode 100644
index 0000000..67c4659
--- /dev/null
+++ b/tests/cpychecker/refcounts/unrecognized_function3/stderr.txt
@@ -0,0 +1,9 @@
+tests/cpychecker/refcounts/unrecognized_function3/input.c: In function 'call_to_unrecognized_function':
+tests/cpychecker/refcounts/unrecognized_function3/input.c:33:1: error: ob_refcnt of new ref from (unknown) foo is 1 too high
+tests/cpychecker/refcounts/unrecognized_function3/input.c:33:1: note: was expecting final ob_refcnt to be N + 0 (for some unknown N)
+tests/cpychecker/refcounts/unrecognized_function3/input.c:33:1: note: but final ob_refcnt is N + 1
+tests/cpychecker/refcounts/unrecognized_function3/input.c:32:8: note: new ref from (unknown) foo allocated at: foo(42);
+tests/cpychecker/refcounts/unrecognized_function3/input.c:32:8: note: when foo() succeeds at: foo(42);
+tests/cpychecker/refcounts/unrecognized_function3/input.c:32:8: note: ob_refcnt is now refs: 1 + N where N >= 0
+tests/cpychecker/refcounts/unrecognized_function3/input.c:33:1: note: when returning at: }
+tests/cpychecker/refcounts/unrecognized_function3/input.c:30:1: note: graphical error report for function 'call_to_unrecognized_function' written out to 'tests/cpychecker/refcounts/unrecognized_function3/input.c.call_to_unrecognized_function-refcount-errors.html'
diff --git a/tests/cpychecker/refcounts/unrecognized_function3/stdout.txt b/tests/cpychecker/refcounts/unrecognized_function3/stdout.txt
new file mode 100644
index 0000000..ccec1a7
--- /dev/null
+++ b/tests/cpychecker/refcounts/unrecognized_function3/stdout.txt
@@ -0,0 +1,38 @@
+Trace 0:
+ Transitions:
+ 'foo() succeeds'
+ 'returning'
+ Region("region-for-arg-gcc.ParmDecl('self')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('self')")
+ str(): Region("region-for-arg-gcc.ParmDecl('self')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function3/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('self')"))
+ Region("region-for-arg-gcc.ParmDecl('args')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('args')")
+ str(): Region("region-for-arg-gcc.ParmDecl('args')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function3/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ new ref from (unknown) foo allocated at tests/cpychecker/refcounts/unrecognized_function3/input.c:32:
+ repr(): RegionOnHeap('new ref from (unknown) foo', gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function3/input.c', line=32))
+ str(): new ref from (unknown) foo allocated at tests/cpychecker/refcounts/unrecognized_function3/input.c:32
+ r->ob_refcnt: refs: 1 + N where N >= 0
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function3/input.c', line=32), region=Region('PyTypeObject for new ref from (unknown) foo'))
+ Exception:
+ (struct PyObject *)0 from tests/cpychecker/refcounts/unrecognized_function3/input.c:30
+
+Trace 1:
+ Transitions:
+ 'foo() fails'
+ 'returning'
+ Region("region-for-arg-gcc.ParmDecl('self')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('self')")
+ str(): Region("region-for-arg-gcc.ParmDecl('self')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function3/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('self')"))
+ Region("region-for-arg-gcc.ParmDecl('args')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('args')")
+ str(): Region("region-for-arg-gcc.ParmDecl('args')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function3/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ Exception:
+ RegionForGlobal(gcc.VarDecl('PyExc_MemoryError'))
12 years, 9 months
[gcc-python-plugin] cpychecker: when assuming a PyObject* is sane, also assume it has a sane ob_type
by David Hugh Malcolm
commit f69adc0fdd4ee98f30bd92f01dc20b32e80f0bb1
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Mon Aug 29 16:09:02 2011 -0400
cpychecker: when assuming a PyObject* is sane, also assume it has a sane ob_type
Move the code that sets up a sane ob_type and ob_type->tp_dealloc from just
being within MyState.impl_object_ctor to within MyState.make_new_ref, so that
it also affects _PyObject_New and unknown functions returning a PyObject*
This is needed to suppress lots of false-positive warnings about dereferencing
some_tmp->tp_dealloc whenever user code does a Py_DECREF() on such a PyObject*,
owing to the ob_type formerly being an UnknownValue
libcpychecker/refcounts.py | 40 ++++--
tests/cpychecker/refcounts/getter/stdout.txt | 2 +-
.../refcounts/subclass/handling/stdout.txt | 2 +-
.../refcounts/unrecognized_function/input.c | 8 +
.../refcounts/unrecognized_function/stdout.txt | 2 +-
.../refcounts/unrecognized_function2/input.c | 56 ++++++++
.../refcounts/unrecognized_function2/script.py | 22 +++
.../refcounts/unrecognized_function2/stdout.txt | 138 ++++++++++++++++++++
8 files changed, 252 insertions(+), 18 deletions(-)
---
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 7bd7366..b16e7a9 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -325,24 +325,16 @@ class MyState(State):
fnname = stmt.fn.operand.name
- # The "success" case: allocation and assignment:
- success, nonnull = self.make_new_ref(stmt, typename)
+ # (the region hierarchy is shared by all states, so we can get the
+ # var region from "self", rather than "success")
+ typeobjregion = self.var_region(typeobjdecl)
- # ...and set up type object:
- typeobjregion = success.var_region(typeobjdecl)
- tp_dealloc = success.make_field_region(typeobjregion, 'tp_dealloc')
- type_of_tp_dealloc = gccutils.get_field_by_name(get_PyTypeObject().type,
- 'tp_dealloc').type
- success.value_for_region[tp_dealloc] = GenericTpDealloc(type_of_tp_dealloc,
- stmt.loc)
-
- ob_type = success.make_field_region(nonnull, 'ob_type')
- success.value_for_region[ob_type] = PointerToRegion(get_PyTypeObject().pointer,
- stmt.loc,
- typeobjregion)
+ # The "success" case:
+ success, nonnull = self.make_new_ref(stmt, typename, typeobjregion)
success = Transition(self,
success,
'%s() succeeds' % fnname)
+ # The "failure" case:
failure = self.make_assignment(stmt.lhs,
ConcreteValue(stmt.lhs.type, stmt.loc, 0),
'%s() fails' % fnname)
@@ -375,7 +367,7 @@ class MyState(State):
self.value_for_region[ob_refcnt] = RefcountValue(value.relvalue - 1,
value.min_external + 1)
- def make_new_ref(self, stmt, name):
+ def make_new_ref(self, stmt, name, typeobjregion=None):
"""
Make a new State, in which a new ref to some object has been
assigned to the statement's LHS.
@@ -394,6 +386,24 @@ class MyState(State):
stmt.loc,
nonnull),
stmt.loc)
+ # Ensure that the new object has a sane ob_type:
+ if typeobjregion is None:
+ # If no specific type object provided by caller, supply one:
+ typeobjregion = Region('PyTypeObject for %s' % name, None)
+ # it is its own region:
+ self.region_for_var[typeobjregion] = typeobjregion
+
+ # Set up obj->ob_type:
+ ob_type = newstate.make_field_region(nonnull, 'ob_type')
+ newstate.value_for_region[ob_type] = PointerToRegion(get_PyTypeObject().pointer,
+ stmt.loc,
+ typeobjregion)
+ # Set up obj->ob_type->tp_dealloc:
+ tp_dealloc = newstate.make_field_region(typeobjregion, 'tp_dealloc')
+ type_of_tp_dealloc = gccutils.get_field_by_name(get_PyTypeObject().type,
+ 'tp_dealloc').type
+ newstate.value_for_region[tp_dealloc] = GenericTpDealloc(type_of_tp_dealloc,
+ stmt.loc)
return newstate, nonnull
def make_borrowed_ref(self, stmt, name):
diff --git a/tests/cpychecker/refcounts/getter/stdout.txt b/tests/cpychecker/refcounts/getter/stdout.txt
index 2a1260a..723f9d8 100644
--- a/tests/cpychecker/refcounts/getter/stdout.txt
+++ b/tests/cpychecker/refcounts/getter/stdout.txt
@@ -6,7 +6,7 @@ Trace 0:
repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/getter/input.c', line=42), region=RegionOnHeap('new ref from (unknown) make_wrapper', gcc.Location(file='tests/cpychecker/refcounts/getter/input.c', line=42)))
str(): (struct PyObject *)&RegionOnHeap('new ref from (unknown) make_wrapper', gcc.Location(file='tests/cpychecker/refcounts/getter/input.c', line=42)) from tests/cpychecker/refcounts/getter/input.c:42
r->ob_refcnt: refs: 1 + N where N >= 0
- r->ob_type: None
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/getter/input.c', line=42), region=Region('PyTypeObject for new ref from (unknown) make_wrapper'))
Region("region-for-arg-gcc.ParmDecl('self')"):
repr(): Region("region-for-arg-gcc.ParmDecl('self')")
str(): Region("region-for-arg-gcc.ParmDecl('self')")
diff --git a/tests/cpychecker/refcounts/subclass/handling/stdout.txt b/tests/cpychecker/refcounts/subclass/handling/stdout.txt
index eb0c90f..8fb97fb 100644
--- a/tests/cpychecker/refcounts/subclass/handling/stdout.txt
+++ b/tests/cpychecker/refcounts/subclass/handling/stdout.txt
@@ -6,7 +6,7 @@ Trace 0:
repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/subclass/handling/input.c', line=39), region=RegionOnHeap('new ref from (unknown) PyString_FromFormat', gcc.Location(file='tests/cpychecker/refcounts/subclass/handling/input.c', line=39)))
str(): (struct PyObject *)&RegionOnHeap('new ref from (unknown) PyString_FromFormat', gcc.Location(file='tests/cpychecker/refcounts/subclass/handling/input.c', line=39)) from tests/cpychecker/refcounts/subclass/handling/input.c:39
r->ob_refcnt: refs: 1 + N where N >= 0
- r->ob_type: None
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/subclass/handling/input.c', line=39), region=Region('PyTypeObject for new ref from (unknown) PyString_FromFormat'))
Region("region-for-arg-gcc.ParmDecl('self')"):
repr(): Region("region-for-arg-gcc.ParmDecl('self')")
str(): Region("region-for-arg-gcc.ParmDecl('self')")
diff --git a/tests/cpychecker/refcounts/unrecognized_function/input.c b/tests/cpychecker/refcounts/unrecognized_function/input.c
index a5d9020..8e7b909 100644
--- a/tests/cpychecker/refcounts/unrecognized_function/input.c
+++ b/tests/cpychecker/refcounts/unrecognized_function/input.c
@@ -35,3 +35,11 @@ static PyMethodDef test_methods[] = {
{"test_method", call_to_unrecognized_function, METH_VARARGS, NULL},
{NULL, NULL, 0, NULL} /* Sentinel */
};
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/refcounts/unrecognized_function/stdout.txt b/tests/cpychecker/refcounts/unrecognized_function/stdout.txt
index cddc6e6..b28a287 100644
--- a/tests/cpychecker/refcounts/unrecognized_function/stdout.txt
+++ b/tests/cpychecker/refcounts/unrecognized_function/stdout.txt
@@ -6,7 +6,7 @@ Trace 0:
repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function/input.c', line=31), region=RegionOnHeap('new ref from (unknown) foo', gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function/input.c', line=31)))
str(): (struct PyObject *)&RegionOnHeap('new ref from (unknown) foo', gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function/input.c', line=31)) from tests/cpychecker/refcounts/unrecognized_function/input.c:31
r->ob_refcnt: refs: 1 + N where N >= 0
- r->ob_type: None
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function/input.c', line=31), region=Region('PyTypeObject for new ref from (unknown) foo'))
Region("region-for-arg-gcc.ParmDecl('self')"):
repr(): Region("region-for-arg-gcc.ParmDecl('self')")
str(): Region("region-for-arg-gcc.ParmDecl('self')")
diff --git a/tests/cpychecker/refcounts/unrecognized_function2/input.c b/tests/cpychecker/refcounts/unrecognized_function2/input.c
new file mode 100644
index 0000000..bf78462
--- /dev/null
+++ b/tests/cpychecker/refcounts/unrecognized_function2/input.c
@@ -0,0 +1,56 @@
+/*
+ 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 *tmp;
+
+ /* Call an unrecognized function: */
+ tmp = foo(42);
+ if (!tmp) {
+ return NULL;
+ }
+
+ /* Verify that tp_dealloc is sane: */
+ Py_DECREF(tmp);
+
+ /* Now do it again, to get a sane return value: */
+ return foo(42);
+}
+static PyMethodDef test_methods[] = {
+ {"test_method", call_to_unrecognized_function, METH_VARARGS, NULL},
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/refcounts/unrecognized_function2/script.py b/tests/cpychecker/refcounts/unrecognized_function2/script.py
new file mode 100644
index 0000000..fdd5ba3
--- /dev/null
+++ b/tests/cpychecker/refcounts/unrecognized_function2/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/unrecognized_function2/stdout.txt b/tests/cpychecker/refcounts/unrecognized_function2/stdout.txt
new file mode 100644
index 0000000..e3e660c
--- /dev/null
+++ b/tests/cpychecker/refcounts/unrecognized_function2/stdout.txt
@@ -0,0 +1,138 @@
+Trace 0:
+ Transitions:
+ 'foo() succeeds'
+ 'taking False path'
+ 'taking True path'
+ 'foo() succeeds'
+ 'returning'
+ Return value:
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=43), region=RegionOnHeap('new ref from (unknown) foo', gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=43)))
+ str(): (struct PyObject *)&RegionOnHeap('new ref from (unknown) foo', gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=43)) from tests/cpychecker/refcounts/unrecognized_function2/input.c:43
+ r->ob_refcnt: refs: 1 + N where N >= 0
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=43), region=Region('PyTypeObject for new ref from (unknown) foo'))
+ Region("region-for-arg-gcc.ParmDecl('self')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('self')")
+ str(): Region("region-for-arg-gcc.ParmDecl('self')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('self')"))
+ Region("region-for-arg-gcc.ParmDecl('args')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('args')")
+ str(): Region("region-for-arg-gcc.ParmDecl('args')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ new ref from (unknown) foo allocated at tests/cpychecker/refcounts/unrecognized_function2/input.c:34:
+ repr(): RegionOnHeap('new ref from (unknown) foo', gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=34))
+ str(): new ref from (unknown) foo allocated at tests/cpychecker/refcounts/unrecognized_function2/input.c:34
+ r->ob_refcnt: refs: 0 + N where N >= 0
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=34), region=Region('PyTypeObject for new ref from (unknown) foo'))
+ Exception:
+ (struct PyObject *)0 from tests/cpychecker/refcounts/unrecognized_function2/input.c:30
+
+Trace 1:
+ Transitions:
+ 'foo() succeeds'
+ 'taking False path'
+ 'taking True path'
+ 'foo() fails'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=43), value=0)
+ str(): (struct PyObject *)0 from tests/cpychecker/refcounts/unrecognized_function2/input.c:43
+ Region("region-for-arg-gcc.ParmDecl('self')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('self')")
+ str(): Region("region-for-arg-gcc.ParmDecl('self')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('self')"))
+ Region("region-for-arg-gcc.ParmDecl('args')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('args')")
+ str(): Region("region-for-arg-gcc.ParmDecl('args')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ new ref from (unknown) foo allocated at tests/cpychecker/refcounts/unrecognized_function2/input.c:34:
+ repr(): RegionOnHeap('new ref from (unknown) foo', gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=34))
+ str(): new ref from (unknown) foo allocated at tests/cpychecker/refcounts/unrecognized_function2/input.c:34
+ r->ob_refcnt: refs: 0 + N where N >= 0
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=34), region=Region('PyTypeObject for new ref from (unknown) foo'))
+ Exception:
+ RegionForGlobal(gcc.VarDecl('PyExc_MemoryError'))
+
+Trace 2:
+ Transitions:
+ 'foo() succeeds'
+ 'taking False path'
+ 'taking False path'
+ 'calling tp_dealloc on new ref from (unknown) foo allocated at tests/cpychecker/refcounts/unrecognized_function2/input.c:34'
+ 'foo() succeeds'
+ 'returning'
+ Return value:
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=43), region=RegionOnHeap('new ref from (unknown) foo', gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=43)))
+ str(): (struct PyObject *)&RegionOnHeap('new ref from (unknown) foo', gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=43)) from tests/cpychecker/refcounts/unrecognized_function2/input.c:43
+ r->ob_refcnt: refs: 1 + N where N >= 0
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=43), region=Region('PyTypeObject for new ref from (unknown) foo'))
+ Region("region-for-arg-gcc.ParmDecl('self')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('self')")
+ str(): Region("region-for-arg-gcc.ParmDecl('self')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('self')"))
+ Region("region-for-arg-gcc.ParmDecl('args')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('args')")
+ str(): Region("region-for-arg-gcc.ParmDecl('args')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ new ref from (unknown) foo allocated at tests/cpychecker/refcounts/unrecognized_function2/input.c:34:
+ repr(): RegionOnHeap('new ref from (unknown) foo', gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=34))
+ str(): new ref from (unknown) foo allocated at tests/cpychecker/refcounts/unrecognized_function2/input.c:34
+ r->ob_refcnt: None
+ r->ob_type: None
+ Exception:
+ (struct PyObject *)0 from tests/cpychecker/refcounts/unrecognized_function2/input.c:30
+
+Trace 3:
+ Transitions:
+ 'foo() succeeds'
+ 'taking False path'
+ 'taking False path'
+ 'calling tp_dealloc on new ref from (unknown) foo allocated at tests/cpychecker/refcounts/unrecognized_function2/input.c:34'
+ 'foo() fails'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=43), value=0)
+ str(): (struct PyObject *)0 from tests/cpychecker/refcounts/unrecognized_function2/input.c:43
+ Region("region-for-arg-gcc.ParmDecl('self')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('self')")
+ str(): Region("region-for-arg-gcc.ParmDecl('self')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('self')"))
+ Region("region-for-arg-gcc.ParmDecl('args')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('args')")
+ str(): Region("region-for-arg-gcc.ParmDecl('args')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ new ref from (unknown) foo allocated at tests/cpychecker/refcounts/unrecognized_function2/input.c:34:
+ repr(): RegionOnHeap('new ref from (unknown) foo', gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=34))
+ str(): new ref from (unknown) foo allocated at tests/cpychecker/refcounts/unrecognized_function2/input.c:34
+ r->ob_refcnt: None
+ r->ob_type: None
+ Exception:
+ RegionForGlobal(gcc.VarDecl('PyExc_MemoryError'))
+
+Trace 4:
+ Transitions:
+ 'foo() fails'
+ 'taking True path'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=36), value=0)
+ str(): (struct PyObject *)0 from tests/cpychecker/refcounts/unrecognized_function2/input.c:36
+ Region("region-for-arg-gcc.ParmDecl('self')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('self')")
+ str(): Region("region-for-arg-gcc.ParmDecl('self')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('self')"))
+ Region("region-for-arg-gcc.ParmDecl('args')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('args')")
+ str(): Region("region-for-arg-gcc.ParmDecl('args')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function2/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ Exception:
+ RegionForGlobal(gcc.VarDecl('PyExc_MemoryError'))
12 years, 9 months
[gcc-python-plugin] cpychecker: simplify MyState.impl_object_ctor in terms of MyState.make_new_ref
by David Hugh Malcolm
commit 02feb6393966cc5f86f4e247f97972789efec140
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Mon Aug 29 15:33:51 2011 -0400
cpychecker: simplify MyState.impl_object_ctor in terms of MyState.make_new_ref
libcpychecker/refcounts.py | 15 +++------------
1 files changed, 3 insertions(+), 12 deletions(-)
---
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 594da3d..7bd7366 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -325,11 +325,10 @@ class MyState(State):
fnname = stmt.fn.operand.name
- # Allocation and assignment:
- success = self.copy()
- success.loc = self.loc.next_loc()
+ # The "success" case: allocation and assignment:
+ success, nonnull = self.make_new_ref(stmt, typename)
- # Set up type object:
+ # ...and set up type object:
typeobjregion = success.var_region(typeobjdecl)
tp_dealloc = success.make_field_region(typeobjregion, 'tp_dealloc')
type_of_tp_dealloc = gccutils.get_field_by_name(get_PyTypeObject().type,
@@ -337,18 +336,10 @@ class MyState(State):
success.value_for_region[tp_dealloc] = GenericTpDealloc(type_of_tp_dealloc,
stmt.loc)
- 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.new_ref()
ob_type = success.make_field_region(nonnull, 'ob_type')
success.value_for_region[ob_type] = PointerToRegion(get_PyTypeObject().pointer,
stmt.loc,
typeobjregion)
- success.assign(stmt.lhs,
- PointerToRegion(stmt.lhs.type,
- stmt.loc,
- nonnull),
- stmt.loc)
success = Transition(self,
success,
'%s() succeeds' % fnname)
12 years, 9 months
[gcc-python-plugin] cpychecker: bulletproof against function calls where the return value is not captured
by David Hugh Malcolm
commit 401a7be1de02ad407918e05a6216c8a6dc65f1fe
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Mon Aug 29 15:17:17 2011 -0400
cpychecker: bulletproof against function calls where the return value is not captured
libcpychecker/refcounts.py | 22 ++++++++++++----------
1 files changed, 12 insertions(+), 10 deletions(-)
---
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 5b40416..594da3d 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -397,11 +397,12 @@ class MyState(State):
nonnull = newstate.make_heap_region(name, stmt)
ob_refcnt = newstate.make_field_region(nonnull, 'ob_refcnt') # FIXME: this should be a memref and fieldref
newstate.value_for_region[ob_refcnt] = RefcountValue.new_ref()
- newstate.assign(stmt.lhs,
- PointerToRegion(stmt.lhs.type,
- stmt.loc,
- nonnull),
- stmt.loc)
+ if stmt.lhs:
+ newstate.assign(stmt.lhs,
+ PointerToRegion(stmt.lhs.type,
+ stmt.loc,
+ nonnull),
+ stmt.loc)
return newstate, nonnull
def make_borrowed_ref(self, stmt, name):
@@ -416,11 +417,12 @@ class MyState(State):
#newstate.value_for_region[ob_type] = PointerToRegion(get_PyTypeObject().pointer,
# stmt.loc,
# typeobjregion)
- newstate.assign(stmt.lhs,
- PointerToRegion(stmt.lhs.type,
- stmt.loc,
- nonnull),
- stmt.loc)
+ if stmt.lhs:
+ newstate.assign(stmt.lhs,
+ PointerToRegion(stmt.lhs.type,
+ stmt.loc,
+ nonnull),
+ stmt.loc)
return newstate
def make_exception(self, stmt, fnname):
12 years, 9 months
[gcc-python-plugin] cpychecker: assume unknown functions returning (PyObject*) either return a new ref, or NULL+exceptio
by David Hugh Malcolm
commit 90d237c02bb8fe6bd7b40aa6c2420652c87ce906
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Mon Aug 29 15:01:46 2011 -0400
cpychecker: assume unknown functions returning (PyObject*) either return a new ref, or NULL+exception
libcpychecker/refcounts.py | 13 ++++++-
tests/cpychecker/refcounts/getter/stdout.txt | 22 ++++++++++-
.../refcounts/subclass/handling/stdout.txt | 27 ++++++++++++-
.../refcounts/unrecognized_function/script.py | 23 +----------
.../refcounts/unrecognized_function/stdout.txt | 41 +++++++++++++++++++-
5 files changed, 98 insertions(+), 28 deletions(-)
---
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index eaebbde..5b40416 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -705,7 +705,18 @@ class MyState(State):
#if fnname in c_stdio_functions:
# return handle_c_stdio_function(self, fnname, stmt)
- # Unknown function:
+ # Unknown function returning (PyObject*):
+ if str(stmt.fn.operand.type.type) == 'struct PyObject *':
+ log('Invocation of unknown function returning PyObject *: %r' % fnname)
+ # Assume that all such functions either:
+ # - return a new reference, or
+ # - return NULL and set an exception (e.g. MemoryError)
+ success, nonnull = self.make_new_ref(stmt,
+ 'new ref from (unknown) %s' % fnname)
+ failure = self.make_exception(stmt, fnname)
+ return self.make_transitions_for_fncall(stmt, success, failure)
+
+ # Unknown function of other type:
log('Invocation of unknown function: %r', fnname)
return [self.make_assignment(stmt.lhs,
UnknownValue(returntype, stmt.loc),
diff --git a/tests/cpychecker/refcounts/getter/stdout.txt b/tests/cpychecker/refcounts/getter/stdout.txt
index 161415c..2a1260a 100644
--- a/tests/cpychecker/refcounts/getter/stdout.txt
+++ b/tests/cpychecker/refcounts/getter/stdout.txt
@@ -1,9 +1,12 @@
Trace 0:
Transitions:
+ 'make_wrapper() succeeds'
'returning'
Return value:
- repr(): UnknownValue(gcctype=<gcc.PointerType object at 0xdeadbeef>, loc=gcc.Location(file='tests/cpychecker/refcounts/getter/input.c', line=42))
- str(): unknown struct PyObject * from tests/cpychecker/refcounts/getter/input.c:42
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/getter/input.c', line=42), region=RegionOnHeap('new ref from (unknown) make_wrapper', gcc.Location(file='tests/cpychecker/refcounts/getter/input.c', line=42)))
+ str(): (struct PyObject *)&RegionOnHeap('new ref from (unknown) make_wrapper', gcc.Location(file='tests/cpychecker/refcounts/getter/input.c', line=42)) from tests/cpychecker/refcounts/getter/input.c:42
+ r->ob_refcnt: refs: 1 + N where N >= 0
+ r->ob_type: None
Region("region-for-arg-gcc.ParmDecl('self')"):
repr(): Region("region-for-arg-gcc.ParmDecl('self')")
str(): Region("region-for-arg-gcc.ParmDecl('self')")
@@ -11,3 +14,18 @@ Trace 0:
r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/getter/input.c', line=35), region=Region("region-for-type-of-arg-gcc.ParmDecl('self')"))
Exception:
(struct PyObject *)0 from tests/cpychecker/refcounts/getter/input.c:36
+
+Trace 1:
+ Transitions:
+ 'make_wrapper() fails'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/getter/input.c', line=42), value=0)
+ str(): (struct PyObject *)0 from tests/cpychecker/refcounts/getter/input.c:42
+ Region("region-for-arg-gcc.ParmDecl('self')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('self')")
+ str(): Region("region-for-arg-gcc.ParmDecl('self')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/getter/input.c', line=35), region=Region("region-for-type-of-arg-gcc.ParmDecl('self')"))
+ Exception:
+ RegionForGlobal(gcc.VarDecl('PyExc_MemoryError'))
diff --git a/tests/cpychecker/refcounts/subclass/handling/stdout.txt b/tests/cpychecker/refcounts/subclass/handling/stdout.txt
index c4d8cbf..eb0c90f 100644
--- a/tests/cpychecker/refcounts/subclass/handling/stdout.txt
+++ b/tests/cpychecker/refcounts/subclass/handling/stdout.txt
@@ -1,9 +1,12 @@
Trace 0:
Transitions:
+ 'PyString_FromFormat() succeeds'
'returning'
Return value:
- repr(): UnknownValue(gcctype=<gcc.PointerType object at 0xdeadbeef>, loc=gcc.Location(file='tests/cpychecker/refcounts/subclass/handling/input.c', line=39))
- str(): unknown struct PyObject * from tests/cpychecker/refcounts/subclass/handling/input.c:39
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/subclass/handling/input.c', line=39), region=RegionOnHeap('new ref from (unknown) PyString_FromFormat', gcc.Location(file='tests/cpychecker/refcounts/subclass/handling/input.c', line=39)))
+ str(): (struct PyObject *)&RegionOnHeap('new ref from (unknown) PyString_FromFormat', gcc.Location(file='tests/cpychecker/refcounts/subclass/handling/input.c', line=39)) from tests/cpychecker/refcounts/subclass/handling/input.c:39
+ r->ob_refcnt: refs: 1 + N where N >= 0
+ r->ob_type: None
Region("region-for-arg-gcc.ParmDecl('self')"):
repr(): Region("region-for-arg-gcc.ParmDecl('self')")
str(): Region("region-for-arg-gcc.ParmDecl('self')")
@@ -16,3 +19,23 @@ Trace 0:
r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/subclass/handling/input.c', line=37), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
Exception:
(struct PyObject *)0 from tests/cpychecker/refcounts/subclass/handling/input.c:38
+
+Trace 1:
+ Transitions:
+ 'PyString_FromFormat() fails'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/subclass/handling/input.c', line=39), value=0)
+ str(): (struct PyObject *)0 from tests/cpychecker/refcounts/subclass/handling/input.c:39
+ Region("region-for-arg-gcc.ParmDecl('self')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('self')")
+ str(): Region("region-for-arg-gcc.ParmDecl('self')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/subclass/handling/input.c', line=37), region=Region("region-for-type-of-arg-gcc.ParmDecl('self')"))
+ Region("region-for-arg-gcc.ParmDecl('args')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('args')")
+ str(): Region("region-for-arg-gcc.ParmDecl('args')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/subclass/handling/input.c', line=37), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ Exception:
+ RegionForGlobal(gcc.VarDecl('PyExc_MemoryError'))
diff --git a/tests/cpychecker/refcounts/unrecognized_function/script.py b/tests/cpychecker/refcounts/unrecognized_function/script.py
index 8c554e6..fdd5ba3 100644
--- a/tests/cpychecker/refcounts/unrecognized_function/script.py
+++ b/tests/cpychecker/refcounts/unrecognized_function/script.py
@@ -16,28 +16,7 @@
# 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,
+ dump_traces=True,
show_traces=False)
diff --git a/tests/cpychecker/refcounts/unrecognized_function/stdout.txt b/tests/cpychecker/refcounts/unrecognized_function/stdout.txt
index 8b4569f..cddc6e6 100644
--- a/tests/cpychecker/refcounts/unrecognized_function/stdout.txt
+++ b/tests/cpychecker/refcounts/unrecognized_function/stdout.txt
@@ -1,2 +1,41 @@
Trace 0:
- returned: unknown struct PyObject * from tests/cpychecker/refcounts/unrecognized_function/input.c:31
+ Transitions:
+ 'foo() succeeds'
+ 'returning'
+ Return value:
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function/input.c', line=31), region=RegionOnHeap('new ref from (unknown) foo', gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function/input.c', line=31)))
+ str(): (struct PyObject *)&RegionOnHeap('new ref from (unknown) foo', gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function/input.c', line=31)) from tests/cpychecker/refcounts/unrecognized_function/input.c:31
+ r->ob_refcnt: refs: 1 + N where N >= 0
+ r->ob_type: None
+ Region("region-for-arg-gcc.ParmDecl('self')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('self')")
+ str(): Region("region-for-arg-gcc.ParmDecl('self')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('self')"))
+ Region("region-for-arg-gcc.ParmDecl('args')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('args')")
+ str(): Region("region-for-arg-gcc.ParmDecl('args')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ Exception:
+ (struct PyObject *)0 from tests/cpychecker/refcounts/unrecognized_function/input.c:30
+
+Trace 1:
+ Transitions:
+ 'foo() fails'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function/input.c', line=31), value=0)
+ str(): (struct PyObject *)0 from tests/cpychecker/refcounts/unrecognized_function/input.c:31
+ Region("region-for-arg-gcc.ParmDecl('self')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('self')")
+ str(): Region("region-for-arg-gcc.ParmDecl('self')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('self')"))
+ Region("region-for-arg-gcc.ParmDecl('args')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('args')")
+ str(): Region("region-for-arg-gcc.ParmDecl('args')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/unrecognized_function/input.c', line=29), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ Exception:
+ RegionForGlobal(gcc.VarDecl('PyExc_MemoryError'))
12 years, 9 months