commit 0ef3912f078c7ffa2315f9e9a639646f84a84919 Author: David Malcolm dmalcolm@redhat.com Date: Fri Sep 2 14:28:53 2011 -0400
cpychecker: handle PyArg_ParseTuple with "O!" in the refcount checker
Also: support disabling the format string checker, given that it emits a false positive in the new test case for refcount handling on "O!"
libcpychecker/__init__.py | 5 +- libcpychecker/refcounts.py | 30 +++++++-- .../PyArg_ParseTuple/correct_O_bang/input.c | 65 +++++++++++++++++++ .../PyArg_ParseTuple/correct_O_bang/script.py | 28 ++++++++ .../PyArg_ParseTuple/correct_O_bang/stdout.txt | 66 ++++++++++++++++++++ 5 files changed, 187 insertions(+), 7 deletions(-) --- diff --git a/libcpychecker/__init__.py b/libcpychecker/__init__.py index 3d431b4..47e5899 100644 --- a/libcpychecker/__init__.py +++ b/libcpychecker/__init__.py @@ -27,18 +27,21 @@ class CpyChecker(gcc.GimplePass): def __init__(self, dump_traces=False, show_traces=False, + verify_pyargs=True, verify_refcounting=False, show_possible_null_derefs=False): gcc.GimplePass.__init__(self, 'cpychecker') self.dump_traces = dump_traces self.show_traces = show_traces + self.verify_pyargs = verify_pyargs self.verify_refcounting = verify_refcounting self.show_possible_null_derefs = show_possible_null_derefs
def execute(self, fun): if fun: log('%s', fun) - check_pyargs(fun) + if self.verify_pyargs: + check_pyargs(fun)
# The refcount code is too buggy for now to be on by default: if self.verify_refcounting: diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py index 00f357d..256dc17 100644 --- a/libcpychecker/refcounts.py +++ b/libcpychecker/refcounts.py @@ -27,7 +27,8 @@ 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 PyArgParseFmt, FormatStringError +from libcpychecker.PyArg_ParseTuple import PyArgParseFmt, FormatStringError, \ + TypeCheckCheckerType, TypeCheckResultType from libcpychecker.types import is_py3k, is_debug_build, get_PyObjectPtr from libcpychecker.utils import log
@@ -635,19 +636,36 @@ class MyState(State): self.make_sane_object(stmt, 'object from arg "O"', RefcountValue.borrowed_ref()))
+ if unit.code == 'O!': + if isinstance(exptype, TypeCheckCheckerType): + # This is read from, not written to: + return None + if isinstance(exptype, TypeCheckResultType): + # non-NULL sane PyObject* + # FIXME: we could perhaps set ob_type to the given type. + # However "O!" only enforces the weaker condition: + # if (PyType_IsSubtype(arg->ob_type, type)) + return PointerToRegion(get_PyObjectPtr(), + stmt.loc, + self.make_sane_object(stmt, 'object from arg "O!"', + RefcountValue.borrowed_ref())) + # Unknown value: + check_isinstance(exptype, gcc.PointerType) return UnknownValue(exptype.dereference, stmt.loc)
def _handle_successful_parse(fmt): exptypes = fmt.iter_exp_types() for v_vararg, (unit, exptype) in zip(v_varargs, exptypes): - # print('v_vararg: %r' % v_vararg) - # print(' unit: %r' % unit) - # print(' exptype: %r %s' % (exptype, exptype)) + if 0: + print('v_vararg: %r' % v_vararg) + print(' unit: %r' % unit) + print(' exptype: %r %s' % (exptype, exptype)) if isinstance(v_vararg, PointerToRegion): v_new = _get_new_value_for_vararg(unit, exptype) - check_isinstance(v_new, AbstractValue) - s_success.value_for_region[v_vararg.region] = v_new + if v_new: + check_isinstance(v_new, AbstractValue) + s_success.value_for_region[v_vararg.region] = v_new
fmt_string = _get_format_string(v_fmt) if fmt_string: diff --git a/tests/cpychecker/refcounts/PyArg_ParseTuple/correct_O_bang/input.c b/tests/cpychecker/refcounts/PyArg_ParseTuple/correct_O_bang/input.c new file mode 100644 index 0000000..604ba3e --- /dev/null +++ b/tests/cpychecker/refcounts/PyArg_ParseTuple/correct_O_bang/input.c @@ -0,0 +1,65 @@ +/* + Copyright 2011 David Malcolm dmalcolm@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 PyArg_ParseTuple + that uses the "O!" format code, with a type that we don't know about +*/ + +struct FooObject { + PyObject_HEAD + char *msg; +}; + +extern PyTypeObject foo_type; + +PyObject * +test(PyObject *self, PyObject *args) +{ + struct FooObject *foo_obj; + + if (!PyArg_ParseTuple(args, "O!:test", + &foo_type, &foo_obj)) { + return NULL; + } + + /* + We now have a borrowed non-NULL ref to the FooObject; the checker + ought not to complain. + + (It doesn't know that msg is non-NULL or not, but should not issue + a message about that) + */ + + return PyString_FromString(foo_obj->msg); +} +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/PyArg_ParseTuple/correct_O_bang/script.py b/tests/cpychecker/refcounts/PyArg_ParseTuple/correct_O_bang/script.py new file mode 100644 index 0000000..da33e59 --- /dev/null +++ b/tests/cpychecker/refcounts/PyArg_ParseTuple/correct_O_bang/script.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Copyright 2011 David Malcolm dmalcolm@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 + +# The purpose of this test is to verify O! with the refcount checker. +# +# Currently the format checker issues a false positive for the O!, so we +# turn off verify_pyargs for this test: +main(verify_pyargs=False, + verify_refcounting=True, + dump_traces=True, + show_traces=False) diff --git a/tests/cpychecker/refcounts/PyArg_ParseTuple/correct_O_bang/stdout.txt b/tests/cpychecker/refcounts/PyArg_ParseTuple/correct_O_bang/stdout.txt new file mode 100644 index 0000000..199e52a --- /dev/null +++ b/tests/cpychecker/refcounts/PyArg_ParseTuple/correct_O_bang/stdout.txt @@ -0,0 +1,66 @@ +Trace 0: + Transitions: + 'PyArg_ParseTuple() succeeds' + 'taking False path' + 'PyString_FromString() succeeds' + 'returning' + Return value: + repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyArg_ParseTuple/correct_O_bang/input.c', line=52), region=RegionOnHeap('PyStringObject', gcc.Location(file='tests/cpychecker/refcounts/PyArg_ParseTuple/correct_O_bang/input.c', line=52))) + str(): (struct PyObject *)&RegionOnHeap('PyStringObject', gcc.Location(file='tests/cpychecker/refcounts/PyArg_ParseTuple/correct_O_bang/input.c', line=52)) from tests/cpychecker/refcounts/PyArg_ParseTuple/correct_O_bang/input.c:52 + r->ob_refcnt: refs: 1 + N where N >= 0 + r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyArg_ParseTuple/correct_O_bang/input.c', line=52), region=RegionForGlobal(gcc.VarDecl('PyString_Type'))) + 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/PyArg_ParseTuple/correct_O_bang/input.c', line=35), 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/PyArg_ParseTuple/correct_O_bang/input.c', line=35), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')")) + Exception: + (struct PyObject *)0 from tests/cpychecker/refcounts/PyArg_ParseTuple/correct_O_bang/input.c:36 + +Trace 1: + Transitions: + 'PyArg_ParseTuple() succeeds' + 'taking False path' + 'PyString_FromString() fails' + 'returning' + Return value: + repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyArg_ParseTuple/correct_O_bang/input.c', line=52), value=0) + str(): (struct PyObject *)0 from tests/cpychecker/refcounts/PyArg_ParseTuple/correct_O_bang/input.c:52 + 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/PyArg_ParseTuple/correct_O_bang/input.c', line=35), 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/PyArg_ParseTuple/correct_O_bang/input.c', line=35), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')")) + Exception: + RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) + +Trace 2: + Transitions: + 'PyArg_ParseTuple() fails' + 'taking True path' + 'returning' + Return value: + repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyArg_ParseTuple/correct_O_bang/input.c', line=41), value=0) + str(): (struct PyObject *)0 from tests/cpychecker/refcounts/PyArg_ParseTuple/correct_O_bang/input.c:41 + 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/PyArg_ParseTuple/correct_O_bang/input.c', line=35), 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/PyArg_ParseTuple/correct_O_bang/input.c', line=35), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')")) + Exception: + RegionForGlobal(gcc.VarDecl('PyExc_TypeError'))
gcc-python-plugin-commits@lists.stg.fedorahosted.org