[prev in list] [next in list] [prev in thread] [next in thread] 

List:       gcc-python-plugin-commits
Subject:    [gcc-python-plugin] cpychecker: add cpychecker_steals_reference_to_arg(n) attribute
From:       dmalcolm () fedoraproject ! org (dmalcolm)
Date:       2011-10-27 0:29:01
Message-ID: 20111027002902.AF68B120434 () lists ! fedorahosted ! org
[Download RAW message or body]

commit e95fb39f025e5bfdb68c45822980a373a09d2859
Author: David Malcolm <dmalcolm at redhat.com>
Date:   Wed Oct 26 20:28:26 2011 -0400

    cpychecker: add cpychecker_steals_reference_to_arg(n) attribute

 docs/cpychecker.rst                                |   66 +++++++++++++++++-
 libcpychecker/absinterp.py                         |   45 +++++++++++-
 libcpychecker/attributes.py                        |   38 +++++++++--
 libcpychecker/refcounts.py                         |   14 ++++-
 .../correct-marking/input.c                        |   60 ++++++++++++++++
 .../correct-marking/script.py                      |   22 ++++++
 .../correct-marking/stdout.txt                     |   20 ++++++
 .../steals-reference-to-arg/correct-usage/input.c  |   68 ++++++++++++++++++
 .../correct-usage/script.py                        |   22 ++++++
 .../correct-usage/stdout.txt                       |   48 +++++++++++++
 .../incorrect-marking/input.c                      |   55 +++++++++++++++
 .../incorrect-marking/script.py                    |   22 ++++++
 .../incorrect-marking/stderr.txt                   |   10 +++
 .../incorrect-marking/stdout.txt                   |   20 ++++++
 .../incorrect-usage/input.c                        |   72 ++++++++++++++++++++
 .../incorrect-usage/script.py                      |   22 ++++++
 .../incorrect-usage/stderr.txt                     |   13 ++++
 .../incorrect-usage/stdout.txt                     |   43 ++++++++++++
 18 files changed, 645 insertions(+), 15 deletions(-)
---
diff --git a/docs/cpychecker.rst b/docs/cpychecker.rst
index a520ae9..5ced382 100644
--- a/docs/cpychecker.rst
+++ b/docs/cpychecker.rst
@@ -151,7 +151,19 @@ function when the function terminates.
 It will assume this behavior for any function (or call through a function
 pointer) that returns a PyObject*.
 
-It is possible to override this behavior using custom compiler attributes:
+It is possible to override this behavior using custom compiler attributes as
+follows:
+
+Marking functions that return borrowed references
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The checker provides a custom GCC attribute:
+
+.. code-block:: c
+
+   __attribute__((cpychecker_returns_borrowed_ref))
+
+which can be used to mark function declarations:
 
 .. code-block:: c
 
@@ -168,9 +180,55 @@ It is possible to override this behavior using custom compiler \
attributes:  CPYCHECKER_RETURNS_BORROWED_REF;
 
 Given the above, the checker will assume that invocations of ``foo()`` are
-returning a borrowed reference (or NULL), rather than a new reference, and
-will apply the same policy when verifying the implementation of ``foo()``
-itself.
+returning a borrowed reference (or NULL), rather than a new reference.  It
+will also check that this is that case when verifying the implementation of
+``foo()`` itself.
+
+Marking functions that steal references to their arguments
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The checker provides a custom GCC attribute:
+
+.. code-block:: c
+
+     __attribute__((cpychecker_steals_reference_to_arg(n)))
+
+which can be used to mark function declarations:
+
+.. code-block:: c
+
+  /* The checker automatically defines this preprocessor name when creating
+     the custom attribute: */
+  #if defined(WITH_CPYCHECKER_STEALS_REFERENCE_TO_ARG_ATTRIBUTE)
+    #define CPYCHECKER_STEALS_REFERENCE_TO_ARG(n) \
+     __attribute__((cpychecker_steals_reference_to_arg(n)))
+  #else
+   #define CPYCHECKER_STEALS_REFERENCE_TO_ARG(n)
+  #endif
+
+  extern void foo(PyObject *obj)
+    CPYCHECKER_STEALS_REFERENCE_TO_ARG(1);
+
+Given the above, the checker will assume that invocations of ``foo()`` steal
+a reference to the first argument (``obj``).  It will also verify that this is
+the case when analyzing the implementation of ``foo()`` itself.
+
+More then one argument can be marked:
+
+.. code-block:: c
+
+  extern void bar(int i, PyObject *obj, int j, PyObject *other)
+    CPYCHECKER_STEALS_REFERENCE_TO_ARG(2)
+    CPYCHECKER_STEALS_REFERENCE_TO_ARG(4);
+
+The argument indices are 1-based (the above example is thus referring to
+``obj`` and to ``other``).
+
+All such arguments to the attribute should be ``PyObject*`` (or a pointer to a
+derived structure type).
+
+It is assumed that such references are stolen for all possible outcomes of the
+function - if a function can either succeed or fail, the reference is stolen in
+both possible worlds.
 
 Error-handling checking
 -----------------------
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index 0117bc5..248102c 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -1865,10 +1865,14 @@ class State(object):
                 if fnname in fnnames_returning_borrowed_refs:
                     # The function being called was marked as returning a
                     # borrowed ref, rather than a new ref:
-                    return \
                self.cpython.make_transitions_for_borrowed_ref_or_fail(stmt,
-                                                     'borrowed ref from %s()' % \
                fnname)
-                return self.cpython.make_transitions_for_new_ref_or_fail(stmt,
-                                                                 'new ref from \
(unknown) %s' % fnname) +                    return self.apply_fncall_side_effects(
+                        self.cpython.make_transitions_for_borrowed_ref_or_fail(stmt,
+                                                                               \
'borrowed ref from %s()' % fnname), +                        stmt)
+                return self.apply_fncall_side_effects(
+                    self.cpython.make_transitions_for_new_ref_or_fail(stmt,
+                                                                 'new ref from \
(unknown) %s' % fnname), +                    stmt)
 
             # Unknown function of other type:
             log('Invocation of unknown function: %r', fnname)
@@ -1882,6 +1886,25 @@ class State(object):
         for i, arg in enumerate(stmt.args):
             log('args[%i]: %s %r', i, arg, arg)
 
+    def get_function_name(self, stmt):
+        """
+        Try to get the function name for a gcc.GimpleCall statement as a
+        string, or None if we're unable to determine it.
+
+        For a simple function invocation this is easy, but if we're
+        calling through a function pointer we may or may not know.
+        """
+        check_isinstance(stmt, gcc.GimpleCall)
+
+        v_fn = self.eval_rvalue(stmt.fn, stmt.loc)
+        if isinstance(v_fn, PointerToRegion):
+            if isinstance(v_fn.region, RegionForGlobal):
+                if isinstance(v_fn.region.vardecl, gcc.FunctionDecl):
+                    return v_fn.region.vardecl.name
+
+        # Unable to determine it:
+        return None
+
     def apply_fncall_side_effects(self, transitions, stmt):
         """
         Given a list of Transition instances for a call to a function with
@@ -1895,6 +1918,20 @@ class State(object):
         check_isinstance(stmt, gcc.GimpleCall)
 
         args = self.eval_stmt_args(stmt)
+
+        fnname = self.get_function_name(stmt)
+
+        # cpython: handle functions marked as stealing references to their
+        # arguments:
+        from libcpychecker.attributes import stolen_refs_by_fnname
+        if fnname in stolen_refs_by_fnname:
+            for t_iter in transitions:
+                check_isinstance(t_iter, Transition)
+                for argindex in stolen_refs_by_fnname[stmt.fn.operand.name]:
+                    v_arg = args[argindex-1]
+                    if isinstance(v_arg, PointerToRegion):
+                        t_iter.dest.cpython.steal_reference(v_arg.region)
+
         for t_iter in transitions:
             check_isinstance(t_iter, Transition)
             for v_arg in args:
diff --git a/libcpychecker/attributes.py b/libcpychecker/attributes.py
index 5207c5b..b87cc8d 100644
--- a/libcpychecker/attributes.py
+++ b/libcpychecker/attributes.py
@@ -21,18 +21,44 @@ from gccutils import check_isinstance
 # Recorded attribute data:
 fnnames_returning_borrowed_refs = set()
 
-def attribute_callback_for_returns_borrowed_ref(*args):
-    if 0:
-        print('attribute_callback_for_returns_borrowed_ref(%r)' % args)
-    check_isinstance(args[0], gcc.FunctionDecl)
-    fnname = args[0].name
-    fnnames_returning_borrowed_refs.add(fnname)
+# A dictionary mapping from fnname to set of argument indices:
+stolen_refs_by_fnname = {}
 
 def register_our_attributes():
     # Callback, called by the gcc.PLUGIN_ATTRIBUTES event
+
+    # Handler for __attribute__((cpychecker_returns_borrowed_ref))
+    # and #ifdef WITH_CPYCHECKER_RETURNS_BORROWED_REF_ATTRIBUTE
+    def attribute_callback_for_returns_borrowed_ref(*args):
+        if 0:
+            print('attribute_callback_for_returns_borrowed_ref(%r)' % args)
+        check_isinstance(args[0], gcc.FunctionDecl)
+        fnname = args[0].name
+        fnnames_returning_borrowed_refs.add(fnname)
+
     gcc.register_attribute('cpychecker_returns_borrowed_ref',
                            0, 0,
                            False, False, False,
                            attribute_callback_for_returns_borrowed_ref)
     gcc.define_macro('WITH_CPYCHECKER_RETURNS_BORROWED_REF_ATTRIBUTE')
 
+    # Handler for __attribute__((cpychecker_steals_reference_to_arg(n)))
+    # and #ifdef WITH_CPYCHECKER_STEALS_REFERENCE_TO_ARG_ATTRIBUTE
+    def attribute_callback_for_steals_reference_to_arg(*args):
+        if 0:
+            print('attribute_callback_for_steals_reference_to_arg(%r)' % (args, ))
+        check_isinstance(args[0], gcc.FunctionDecl)
+        check_isinstance(args[1], gcc.IntegerCst)
+        fnname = args[0].name
+        argindex = int(args[1].constant)
+
+        if fnname in stolen_refs_by_fnname:
+            stolen_refs_by_fnname[fnname].add(argindex)
+        else:
+            stolen_refs_by_fnname[fnname] = set([argindex])
+
+    gcc.register_attribute('cpychecker_steals_reference_to_arg',
+                           1, 1,
+                           False, False, False,
+                           attribute_callback_for_steals_reference_to_arg)
+    gcc.define_macro('WITH_CPYCHECKER_STEALS_REFERENCE_TO_ARG_ATTRIBUTE')
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 3bfa382..57ecd31 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -26,7 +26,8 @@ import gcc
 from gccutils import cfg_to_dot, invoke_dot, get_src_for_loc, check_isinstance
 
 from libcpychecker.absinterp import *
-from libcpychecker.attributes import fnnames_returning_borrowed_refs
+from libcpychecker.attributes import fnnames_returning_borrowed_refs, \
+    stolen_refs_by_fnname
 from libcpychecker.diagnostics import Reporter, Annotator, Note
 from libcpychecker.PyArg_ParseTuple import PyArgParseFmt, FormatStringError, \
     TypeCheckCheckerType, TypeCheckResultType
@@ -2271,6 +2272,17 @@ def check_refcounts(fun, dump_traces=False, show_traces=False,
             exp_refcnt = len(exp_refs)
             log('exp_refs: %r', exp_refs)
 
+            if fun.decl.name in stolen_refs_by_fnname:
+                # Then this function is marked as stealing references to one or
+                # more of its arguments:
+                for argindex in stolen_refs_by_fnname[fun.decl.name]:
+                    # Get argument's value (in initial state of trace):
+                    parm = fun.decl.arguments[argindex - 1]
+                    v_parm = trace.states[0].eval_rvalue(parm, None)
+                    if isinstance(v_parm, PointerToRegion):
+                        if region == v_parm.region:
+                            exp_refcnt -= 1
+
             # Helper function for when ob_refcnt is wrong:
             def emit_refcount_error(msg):
                 err = rep.make_error(fun, endstate.get_gcc_loc(fun), msg)
diff --git a/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-marking/input.c \
b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-marking/input.c
 new file mode 100644
index 0000000..202d11d
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-marking/input.c
 @@ -0,0 +1,60 @@
+/*
+   Copyright 2011 David Malcolm <dmalcolm at 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 compiling a function that's been (correctly) marked as stealing
+  a reference
+*/
+
+#if defined(WITH_CPYCHECKER_STEALS_REFERENCE_TO_ARG_ATTRIBUTE)
+  #define CPYCHECKER_STEALS_REFERENCE_TO_ARG(n) \
+    __attribute__((cpychecker_steals_reference_to_arg(n)))
+#else
+  #define CPYCHECKER_STEALS_REFERENCE_TO_ARG(n)
+  #error (This should have been defined)
+#endif
+
+extern PyObject *test(PyObject *foo, PyObject *bar)
+  CPYCHECKER_STEALS_REFERENCE_TO_ARG(1)
+  CPYCHECKER_STEALS_REFERENCE_TO_ARG(2);
+
+PyObject *saved_objA;
+PyObject *saved_objB;
+
+PyObject *
+test(PyObject *foo, PyObject *bar)
+{
+    /*
+      This code steals references to both of its arguments
+    */
+    saved_objA = foo;
+    saved_objB = bar;
+
+    Py_RETURN_NONE;
+}
+
+/*
+  PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-marking/script.py \
b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-marking/script.py
 new file mode 100644
index 0000000..6bb62fd
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-marking/script.py
 @@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2011 David Malcolm <dmalcolm at 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)
diff --git a/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-marking/stdout.txt \
b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-marking/stdout.txt
 new file mode 100644
index 0000000..15cad05
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-marking/stdout.txt
 @@ -0,0 +1,20 @@
+Trace 0:
+  Transitions:
+    'returning'
+  Return value:
+    repr(): PointerToRegion(gcctype='struct PyObject *', \
loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-marking/input.c', \
line=51), region=RegionForGlobal(gcc.VarDecl('_Py_NoneStruct'))) +    str(): (struct \
PyObject *)&RegionForGlobal(gcc.VarDecl('_Py_NoneStruct')) from \
tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-marking/input.c:51
 +    r->ob_refcnt: refs: 1 + N where N >= 1
+    r->ob_type: None
+  Region("region-for-arg-gcc.ParmDecl('foo')"):
+    repr(): Region("region-for-arg-gcc.ParmDecl('foo')")
+    str(): Region("region-for-arg-gcc.ParmDecl('foo')")
+    r->ob_refcnt: refs: 0 + N where N >= 1
+    r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', \
loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-marking/input.c', \
line=43), region=Region("region-for-type-of-arg-gcc.ParmDecl('foo')")) +  \
Region("region-for-arg-gcc.ParmDecl('bar')"): +    repr(): \
Region("region-for-arg-gcc.ParmDecl('bar')") +    str(): \
Region("region-for-arg-gcc.ParmDecl('bar')") +    r->ob_refcnt: refs: 0 + N where N \
>= 1 +    r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', \
> loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-marking/input.c', \
> line=43), region=Region("region-for-type-of-arg-gcc.ParmDecl('bar')"))
+  Exception:
+    (struct PyObject *)0 from \
tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-marking/input.c:44
                
diff --git a/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-usage/input.c \
b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-usage/input.c \
new file mode 100644 index 0000000..4670c44
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-usage/input.c
 @@ -0,0 +1,68 @@
+/*
+   Copyright 2011 David Malcolm <dmalcolm at 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 correctly calling a function that's been marked as stealing a
+  reference to its arguments
+*/
+
+#if defined(WITH_CPYCHECKER_STEALS_REFERENCE_TO_ARG_ATTRIBUTE)
+  #define CPYCHECKER_STEALS_REFERENCE_TO_ARG(n) \
+    __attribute__((cpychecker_steals_reference_to_arg(n)))
+#else
+  #define CPYCHECKER_STEALS_REFERENCE_TO_ARG(n)
+  #error (This should have been defined)
+#endif
+
+extern void foo(PyObject *obj)
+  CPYCHECKER_STEALS_REFERENCE_TO_ARG(1);
+
+PyObject *
+test(PyObject *self, PyObject *args)
+{
+    PyObject *obj;
+
+    obj = PyLong_FromLong(42);
+    if (!obj) {
+        return NULL;
+    }
+
+    /*
+      We now own a reference to "obj"
+
+      Pass it to foo(), which steals it.
+
+      Given that foo has been marked with
+        __attribute__((cpychecker_steals_reference_to_arg(1)))
+      the checker should not complain.
+     */
+    foo(obj);
+    
+    Py_RETURN_NONE;
+}
+
+/*
+  PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-usage/script.py \
b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-usage/script.py
 new file mode 100644
index 0000000..6bb62fd
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-usage/script.py
 @@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2011 David Malcolm <dmalcolm at 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)
diff --git a/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-usage/stdout.txt \
b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-usage/stdout.txt
 new file mode 100644
index 0000000..4a08c84
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-usage/stdout.txt
 @@ -0,0 +1,48 @@
+Trace 0:
+  Transitions:
+    'when PyLong_FromLong() succeeds'
+    'taking False path'
+    'returning'
+  Return value:
+    repr(): PointerToRegion(gcctype='struct PyObject *', \
loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-usage/input.c', \
line=59), region=RegionForGlobal(gcc.VarDecl('_Py_NoneStruct'))) +    str(): (struct \
PyObject *)&RegionForGlobal(gcc.VarDecl('_Py_NoneStruct')) from \
tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-usage/input.c:59
 +    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/attributes/steals-reference-to-arg/correct-usage/input.c', \
line=39), 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/attributes/steals-reference-to-arg/correct-usage/input.c', \
> line=39), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+  PyLongObject allocated at \
tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-usage/input.c:43:
 +    repr(): RegionOnHeap('PyLongObject', \
gcc.Location(file='tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-usage/input.c', \
line=43)) +    str(): PyLongObject allocated at \
tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-usage/input.c:43
 +    r->ob_refcnt: refs: 0 + N where N >= 1
+    r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', \
loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-usage/input.c', \
line=43), region=RegionForGlobal(gcc.VarDecl('PyLong_Type'))) +  Exception:
+    (struct PyObject *)0 from \
tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-usage/input.c:40
 +
+Trace 1:
+  Transitions:
+    'when PyLong_FromLong() fails'
+    'taking True path'
+    'returning'
+  Return value:
+    repr(): ConcreteValue(gcctype='struct PyObject *', \
loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-usage/input.c', \
line=45), value=0) +    str(): (struct PyObject *)0 from \
tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-usage/input.c:45
 +  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/attributes/steals-reference-to-arg/correct-usage/input.c', \
line=39), 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/attributes/steals-reference-to-arg/correct-usage/input.c', \
> line=39), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+  Exception:
+    (struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) from \
tests/cpychecker/refcounts/attributes/steals-reference-to-arg/correct-usage/input.c:43
                
diff --git a/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/input.c \
b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/input.c
 new file mode 100644
index 0000000..976bcc0
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/input.c
 @@ -0,0 +1,55 @@
+/*
+   Copyright 2011 David Malcolm <dmalcolm at 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 compiling a function that's been incorrectly marked as stealing
+  a reference, when it does not.
+*/
+
+#if defined(WITH_CPYCHECKER_STEALS_REFERENCE_TO_ARG_ATTRIBUTE)
+  #define CPYCHECKER_STEALS_REFERENCE_TO_ARG(n) \
+    __attribute__((cpychecker_steals_reference_to_arg(n)))
+#else
+  #define CPYCHECKER_STEALS_REFERENCE_TO_ARG(n)
+  #error (This should have been defined)
+#endif
+
+extern PyObject *test(PyObject *foo, PyObject *bar)
+  CPYCHECKER_STEALS_REFERENCE_TO_ARG(1)
+  CPYCHECKER_STEALS_REFERENCE_TO_ARG(2);
+
+PyObject *
+test(PyObject *foo, PyObject *bar)
+{
+    /*
+      This code doesn't steals references to its arguments; the marking is
+      incorrect, and this should be flagged by the checker.
+    */
+    Py_RETURN_NONE;
+}
+
+/*
+  PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/script.py \
b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/script.py
 new file mode 100644
index 0000000..6bb62fd
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/script.py
 @@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2011 David Malcolm <dmalcolm at 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)
diff --git a/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/stderr.txt \
b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/stderr.txt
 new file mode 100644
index 0000000..18ea731
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/stderr.txt
 @@ -0,0 +1,10 @@
+tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/input.c: \
In function 'test': +tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/input.c:46:5: \
error: ob_refcnt of '*foo' is 1 too high \
+tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/input.c:46:5: \
note: was expecting final ob_refcnt to be N + -1 (for some unknown N) \
+tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/input.c:46:5: \
note: but final ob_refcnt is N + 0 \
+tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/input.c:46:5: \
note: returning at:     Py_RETURN_NONE; \
+tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/input.c:46:5: \
error: ob_refcnt of '*bar' is 1 too high \
+tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/input.c:46:5: \
note: was expecting final ob_refcnt to be N + -1 (for some unknown N) \
+tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/input.c:46:5: \
note: but final ob_refcnt is N + 0 \
+tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/input.c:46:5: \
note: returning at:     Py_RETURN_NONE; \
+tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/input.c:41:1: \
note: graphical error report for function 'test' written out to \
'tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/input.c.test-refcount-errors.html'
                
diff --git a/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/stdout.txt \
b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/stdout.txt
 new file mode 100644
index 0000000..a4ca8ec
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/stdout.txt
 @@ -0,0 +1,20 @@
+Trace 0:
+  Transitions:
+    'returning'
+  Return value:
+    repr(): PointerToRegion(gcctype='struct PyObject *', \
loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/input.c', \
line=46), region=RegionForGlobal(gcc.VarDecl('_Py_NoneStruct'))) +    str(): (struct \
PyObject *)&RegionForGlobal(gcc.VarDecl('_Py_NoneStruct')) from \
tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/input.c:46
 +    r->ob_refcnt: refs: 1 + N where N >= 1
+    r->ob_type: None
+  Region("region-for-arg-gcc.ParmDecl('foo')"):
+    repr(): Region("region-for-arg-gcc.ParmDecl('foo')")
+    str(): Region("region-for-arg-gcc.ParmDecl('foo')")
+    r->ob_refcnt: refs: 0 + N where N >= 1
+    r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', \
loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/input.c', \
line=40), region=Region("region-for-type-of-arg-gcc.ParmDecl('foo')")) +  \
Region("region-for-arg-gcc.ParmDecl('bar')"): +    repr(): \
Region("region-for-arg-gcc.ParmDecl('bar')") +    str(): \
Region("region-for-arg-gcc.ParmDecl('bar')") +    r->ob_refcnt: refs: 0 + N where N \
>= 1 +    r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', \
> loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/input.c', \
> line=40), region=Region("region-for-type-of-arg-gcc.ParmDecl('bar')"))
+  Exception:
+    (struct PyObject *)0 from \
tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-marking/input.c:41
                
diff --git a/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c \
b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c
 new file mode 100644
index 0000000..4ce5d0a
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c
 @@ -0,0 +1,72 @@
+/*
+   Copyright 2011 David Malcolm <dmalcolm at 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 calling a function that's been marked as stealing a
+  reference to its arguments, but incorrectly treating the argument as
+  still being owned by us.
+*/
+
+#if defined(WITH_CPYCHECKER_STEALS_REFERENCE_TO_ARG_ATTRIBUTE)
+  #define CPYCHECKER_STEALS_REFERENCE_TO_ARG(n) \
+    __attribute__((cpychecker_steals_reference_to_arg(n)))
+#else
+  #define CPYCHECKER_STEALS_REFERENCE_TO_ARG(n)
+  #error (This should have been defined)
+#endif
+
+extern void foo(PyObject *obj)
+  CPYCHECKER_STEALS_REFERENCE_TO_ARG(1);
+
+PyObject *
+test(PyObject *self, PyObject *args)
+{
+    PyObject *obj;
+
+    obj = PyLong_FromLong(42);
+    if (!obj) {
+        return NULL;
+    }
+
+    /*
+      We now own a reference to "obj"
+
+      Pass it to foo(), which steals it.
+
+     */
+    foo(obj);
+
+    /*
+      Given that foo has been marked with
+        __attribute__((cpychecker_steals_reference_to_arg(1)))
+      this is an error: we no longer own the reference to "obj"
+      that's expected of us when returning a non-NULL PyObject*:
+    */
+    return obj;
+}
+
+/*
+  PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/script.py \
b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/script.py
 new file mode 100644
index 0000000..6bb62fd
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/script.py
 @@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2011 David Malcolm <dmalcolm at 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)
diff --git a/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/stderr.txt \
b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/stderr.txt
 new file mode 100644
index 0000000..4add37a
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/stderr.txt
 @@ -0,0 +1,13 @@
+tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c: \
In function 'test': +tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c:64:1: \
error: ob_refcnt of return value is 1 too low \
+tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c:64:1: \
note: was expecting final ob_refcnt to be N + 1 (for some unknown N) \
+tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c:64:1: \
note: due to object being referenced by: return value \
+tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c:64:1: \
note: but final ob_refcnt is N + 0 \
+tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c:44:9: \
note: PyLongObject allocated at:     obj = PyLong_FromLong(42); \
+tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c:44:9: \
note: when PyLong_FromLong() succeeds at:     obj = PyLong_FromLong(42); \
+tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c:44:9: \
note: ob_refcnt is now refs: 1 + N where N >= 0 \
+tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c:45:8: \
note: taking False path at:     if (!obj) { \
+tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c:55:8: \
note: reaching:     foo(obj); \
+tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c:55:8: \
note: ob_refcnt is now refs: 0 + N where N >= 1 \
+tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c:64:1: \
note: returning +tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c:41:1: \
note: graphical error report for function 'test' written out to \
'tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c.test-refcount-errors.html'
                
diff --git a/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/stdout.txt \
b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/stdout.txt
 new file mode 100644
index 0000000..829f7d1
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/stdout.txt
 @@ -0,0 +1,43 @@
+Trace 0:
+  Transitions:
+    'when PyLong_FromLong() succeeds'
+    'taking False path'
+    'returning'
+  Return value:
+    repr(): PointerToRegion(gcctype='struct PyObject *', \
loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c', \
line=44), region=RegionOnHeap('PyLongObject', \
gcc.Location(file='tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c', \
line=44))) +    str(): (struct PyObject *)&RegionOnHeap('PyLongObject', \
gcc.Location(file='tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c', \
line=44)) from tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c:44
 +    r->ob_refcnt: refs: 0 + N where N >= 1
+    r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', \
loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c', \
line=44), region=RegionForGlobal(gcc.VarDecl('PyLong_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/attributes/steals-reference-to-arg/incorrect-usage/input.c', \
> line=40), 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/attributes/steals-reference-to-arg/incorrect-usage/input.c', \
line=40), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')")) +  Exception:
+    (struct PyObject *)0 from \
tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c:41
 +
+Trace 1:
+  Transitions:
+    'when PyLong_FromLong() fails'
+    'taking True path'
+    'returning'
+  Return value:
+    repr(): ConcreteValue(gcctype='struct PyObject *', \
loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c', \
line=46), value=0) +    str(): (struct PyObject *)0 from \
tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c:46
 +  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/attributes/steals-reference-to-arg/incorrect-usage/input.c', \
line=40), 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/attributes/steals-reference-to-arg/incorrect-usage/input.c', \
> line=40), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+  Exception:
+    (struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) from \
tests/cpychecker/refcounts/attributes/steals-reference-to-arg/incorrect-usage/input.c:44



[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic