[gcc-python-plugin] cpychecker: add a custom "cpychecker_returns_borrowed_ref" attribute
by David Hugh Malcolm
commit 3e1ab29204c81b6ca256c7f1d4dea1be9ab01193
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Oct 25 19:01:03 2011 -0400
cpychecker: add a custom "cpychecker_returns_borrowed_ref" attribute
Add a custom:
cpychecker_returns_borrowed_ref
attribute to the checker, along with the pre-defined preprocessor name:
WITH_CPYCHECKER_RETURNS_BORROWED_REF_ATTRIBUTE
so that Python C code can mark functions returning a PyObject* for which the
returned reference is borrowed, rather than a new reference.
Update the checker to take this into account when considering both calls to
those function, and when analyzing such functions themselves.
libcpychecker/__init__.py | 5 ++
libcpychecker/absinterp.py | 6 ++
libcpychecker/attributes.py | 38 ++++++++++++++
libcpychecker/refcounts.py | 24 ++++++++-
.../returns-borrowed-ref/correct-marking/input.c | 53 +++++++++++++++++++
.../returns-borrowed-ref/correct-marking/script.py | 22 ++++++++
.../correct-marking/stdout.txt | 15 +++++
.../returns-borrowed-ref/correct-usage/input.c | 53 +++++++++++++++++++
.../returns-borrowed-ref/correct-usage/script.py | 22 ++++++++
.../returns-borrowed-ref/correct-usage/stdout.txt | 43 +++++++++++++++
.../returns-borrowed-ref/incorrect-marking/input.c | 54 +++++++++++++++++++
.../incorrect-marking/script.py | 22 ++++++++
.../incorrect-marking/stderr.txt | 9 +++
.../incorrect-marking/stdout.txt | 41 +++++++++++++++
.../returns-borrowed-ref/incorrect-usage/input.c | 55 ++++++++++++++++++++
.../returns-borrowed-ref/incorrect-usage/script.py | 22 ++++++++
.../incorrect-usage/stderr.txt | 10 ++++
.../incorrect-usage/stdout.txt | 41 +++++++++++++++
18 files changed, 534 insertions(+), 1 deletions(-)
---
diff --git a/libcpychecker/__init__.py b/libcpychecker/__init__.py
index 3a9f0e8..46a9927 100644
--- a/libcpychecker/__init__.py
+++ b/libcpychecker/__init__.py
@@ -19,6 +19,7 @@ import gcc
from libcpychecker.formatstrings import check_pyargs
from libcpychecker.utils import log
from libcpychecker.refcounts import check_refcounts, get_traces
+from libcpychecker.attributes import register_our_attributes
class CpyChecker(gcc.GimplePass):
"""
@@ -84,6 +85,10 @@ def get_all_PyMethodDef_methods():
return result
def main(**kwargs):
+ # Register our custom attributes:
+ gcc.register_callback(gcc.PLUGIN_ATTRIBUTES,
+ register_our_attributes)
+
# Register our GCC pass:
ps = CpyChecker(**kwargs)
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index 3c2c436..0117bc5 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -1861,6 +1861,12 @@ class State(object):
# Assume that all such functions either:
# - return a new reference, or
# - return NULL and set an exception (e.g. MemoryError)
+ from libcpychecker.attributes import fnnames_returning_borrowed_refs
+ 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)
diff --git a/libcpychecker/attributes.py b/libcpychecker/attributes.py
new file mode 100644
index 0000000..5207c5b
--- /dev/null
+++ b/libcpychecker/attributes.py
@@ -0,0 +1,38 @@
+# Copyright 2011 David Malcolm <dmalcolm(a)redhat.com>
+# Copyright 2011 Red Hat, Inc.
+#
+# This is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+import gcc
+from 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)
+
+def register_our_attributes():
+ # Callback, called by the gcc.PLUGIN_ATTRIBUTES event
+ 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')
+
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 7989186..3bfa382 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -26,6 +26,7 @@ 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.diagnostics import Reporter, Annotator, Note
from libcpychecker.PyArg_ParseTuple import PyArgParseFmt, FormatStringError, \
TypeCheckCheckerType, TypeCheckResultType
@@ -500,6 +501,22 @@ class CPython(Facet):
s_failure = self.mkstate_exception(stmt)
return self.state.make_transitions_for_fncall(stmt, s_success, s_failure)
+ def make_transitions_for_borrowed_ref_or_fail(self, stmt, objname=None):
+ """
+ Generate the appropriate list of 2 transitions for a call to a
+ function that either:
+ - returns either a borrowed ref, or
+ - fails with NULL and sets an exception
+ Optionally, a name for the new object can be supplied; otherwise
+ a sane default will be used.
+ """
+ if objname is None:
+ fnname = stmt.fn.operand.name
+ objname = 'borrowed ref from call to %s' % fnname
+ s_success = self.mkstate_borrowed_ref(stmt, objname)
+ s_failure = self.mkstate_exception(stmt)
+ return self.state.make_transitions_for_fncall(stmt, s_success, s_failure)
+
def object_ptr_has_global_ob_type(self, v_object_ptr, vardecl_name):
"""
Boolean: do we know that the given PyObject* has an ob_type matching
@@ -2235,7 +2252,12 @@ def check_refcounts(fun, dump_traces=False, show_traces=False,
# 1; all other PyObject should have a net delta of 0:
if isinstance(return_value, PointerToRegion) and region == return_value.region:
desc = 'return value'
- exp_refs = ['return value']
+ if fun.decl.name in fnnames_returning_borrowed_refs:
+ # ...then this function has been marked as returning a
+ # borrowed reference, rather than a new one:
+ exp_refs = []
+ else:
+ exp_refs = ['return value']
else:
# Try to get a descriptive name for the region:
desc = trace.get_description_for_region(region)
diff --git a/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-marking/input.c b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-marking/input.c
new file mode 100644
index 0000000..a7b1dbe
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-marking/input.c
@@ -0,0 +1,53 @@
+/*
+ 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 compiling a function that's been (correctly) marked as returning
+ a borrowed reference
+*/
+
+#if defined(WITH_CPYCHECKER_RETURNS_BORROWED_REF_ATTRIBUTE)
+ #define CPYCHECKER_RETURNS_BORROWED_REF() \
+ __attribute__((cpychecker_returns_borrowed_ref))
+#else
+ #define CPYCHECKER_RETURNS_BORROWED_REF()
+ #error (This should have been defined)
+#endif
+
+extern PyObject *test(PyObject *self, PyObject *args)
+ CPYCHECKER_RETURNS_BORROWED_REF();
+
+PyObject *
+test(PyObject *self, PyObject *args)
+{
+ /*
+ This code returns a borrowed reference:
+ */
+ return self;
+}
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-marking/script.py b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-marking/script.py
new file mode 100644
index 0000000..6bb62fd
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-marking/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)
diff --git a/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-marking/stdout.txt b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-marking/stdout.txt
new file mode 100644
index 0000000..27d3840
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-marking/stdout.txt
@@ -0,0 +1,15 @@
+Trace 0:
+ Transitions:
+ 'returning'
+ Return value:
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-marking/input.c', line=39), region=Region("region-for-arg-gcc.ParmDecl('self')"))
+ str(): (struct PyObject *)&Region("region-for-arg-gcc.ParmDecl('self')") from tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-marking/input.c:39
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-marking/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/returns-borrowed-ref/correct-marking/input.c', line=39), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ Exception:
+ (struct PyObject *)0 from tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-marking/input.c:40
diff --git a/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-usage/input.c b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-usage/input.c
new file mode 100644
index 0000000..46c97b0
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-usage/input.c
@@ -0,0 +1,53 @@
+/*
+ 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 correctly calling a function that's been marked as returning a
+ borrowed reference
+*/
+
+#if defined(WITH_CPYCHECKER_RETURNS_BORROWED_REF_ATTRIBUTE)
+ #define CPYCHECKER_RETURNS_BORROWED_REF() \
+ __attribute__((cpychecker_returns_borrowed_ref))
+#else
+ #define CPYCHECKER_RETURNS_BORROWED_REF()
+ #error (This should have been defined)
+#endif
+
+extern PyObject *foo(void)
+ CPYCHECKER_RETURNS_BORROWED_REF();
+
+PyObject *
+test(PyObject *self, PyObject *args)
+{
+ PyObject *obj = foo(); /* we now have a borrowed ref, or NULL */
+ Py_XINCREF(obj); /* we now have a new ref, or NULL */
+
+ return obj;
+}
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-usage/script.py b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-usage/script.py
new file mode 100644
index 0000000..6bb62fd
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-usage/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)
diff --git a/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-usage/stdout.txt b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-usage/stdout.txt
new file mode 100644
index 0000000..4b9c41a
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-usage/stdout.txt
@@ -0,0 +1,43 @@
+Trace 0:
+ Transitions:
+ 'when foo() succeeds'
+ 'taking False path'
+ 'returning'
+ Return value:
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-usage/input.c', line=41), region=RegionOnHeap('borrowed ref from foo()', gcc.Location(file='tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-usage/input.c', line=41)))
+ str(): (struct PyObject *)&RegionOnHeap('borrowed ref from foo()', gcc.Location(file='tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-usage/input.c', line=41)) from tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-usage/input.c:41
+ r->ob_refcnt: refs: 1 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-usage/input.c', line=41), region=Region('PyTypeObject for borrowed ref from 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/attributes/returns-borrowed-ref/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/returns-borrowed-ref/correct-usage/input.c', line=39), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ Exception:
+ (struct PyObject *)0 from tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-usage/input.c:40
+
+Trace 1:
+ Transitions:
+ 'when foo() fails'
+ 'taking True path'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-usage/input.c', line=41), value=0)
+ str(): (struct PyObject *)0 from tests/cpychecker/refcounts/attributes/returns-borrowed-ref/correct-usage/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/attributes/returns-borrowed-ref/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/returns-borrowed-ref/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/returns-borrowed-ref/correct-usage/input.c:41
diff --git a/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/input.c b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/input.c
new file mode 100644
index 0000000..4ce87d2
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/input.c
@@ -0,0 +1,54 @@
+/*
+ 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 compiling a function that's been incorrectly marked as returning
+ a borrowed reference, when it in fact returns a new reference
+*/
+
+#if defined(WITH_CPYCHECKER_RETURNS_BORROWED_REF_ATTRIBUTE)
+ #define CPYCHECKER_RETURNS_BORROWED_REF() \
+ __attribute__((cpychecker_returns_borrowed_ref))
+#else
+ #define CPYCHECKER_RETURNS_BORROWED_REF()
+ #error (This should have been defined)
+#endif
+
+extern PyObject *test(PyObject *self, PyObject *args)
+ CPYCHECKER_RETURNS_BORROWED_REF();
+
+PyObject *
+test(PyObject *self, PyObject *args)
+{
+ /*
+ This code returns a new reference, but the function has been marked
+ as returning a borrowed reference:
+ */
+ return PyLong_FromLong(42);
+}
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/script.py b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/script.py
new file mode 100644
index 0000000..6bb62fd
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/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)
diff --git a/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/stderr.txt b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/stderr.txt
new file mode 100644
index 0000000..7438af8
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/stderr.txt
@@ -0,0 +1,9 @@
+tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/input.c: In function 'test':
+tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/input.c:45:5: error: ob_refcnt of return value is 1 too high
+tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/input.c:45:5: note: was expecting final ob_refcnt to be N + 0 (for some unknown N)
+tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/input.c:45:5: note: but final ob_refcnt is N + 1
+tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/input.c:45:5: note: PyLongObject allocated at: return PyLong_FromLong(42);
+tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/input.c:45:5: note: when PyLong_FromLong() succeeds at: return PyLong_FromLong(42);
+tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/input.c:45:5: note: ob_refcnt is now refs: 1 + N where N >= 0
+tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/input.c:45:5: note: returning at: return PyLong_FromLong(42);
+tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/input.c:40:1: note: graphical error report for function 'test' written out to 'tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/input.c.test-refcount-errors.html'
diff --git a/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/stdout.txt b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/stdout.txt
new file mode 100644
index 0000000..7bf1303
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/stdout.txt
@@ -0,0 +1,41 @@
+Trace 0:
+ Transitions:
+ 'when PyLong_FromLong() succeeds'
+ 'returning'
+ Return value:
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/input.c', line=45), region=RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/input.c', line=45)))
+ str(): (struct PyObject *)&RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/input.c', line=45)) from tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/input.c:45
+ r->ob_refcnt: refs: 1 + N where N >= 0
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/input.c', line=45), 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/returns-borrowed-ref/incorrect-marking/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/returns-borrowed-ref/incorrect-marking/input.c', line=39), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ Exception:
+ (struct PyObject *)0 from tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/input.c:40
+
+Trace 1:
+ Transitions:
+ 'when PyLong_FromLong() fails'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/input.c', line=45), value=0)
+ str(): (struct PyObject *)0 from tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-marking/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/returns-borrowed-ref/incorrect-marking/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/returns-borrowed-ref/incorrect-marking/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/returns-borrowed-ref/incorrect-marking/input.c:45
diff --git a/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/input.c b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/input.c
new file mode 100644
index 0000000..d7bd880
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/input.c
@@ -0,0 +1,55 @@
+/*
+ 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 calling a function that's been marked as returning a borrowed
+ reference, but incorrectly treating it as returning a new ref
+*/
+
+#if defined(WITH_CPYCHECKER_RETURNS_BORROWED_REF_ATTRIBUTE)
+ #define CPYCHECKER_RETURNS_BORROWED_REF() \
+ __attribute__((cpychecker_returns_borrowed_ref))
+#else
+ #define CPYCHECKER_RETURNS_BORROWED_REF()
+ #error (This should have been defined)
+#endif
+
+extern PyObject *foo(void)
+ CPYCHECKER_RETURNS_BORROWED_REF();
+
+PyObject *
+test(PyObject *self, PyObject *args)
+{
+ /*
+ This code is incorrect: foo() returns a borrowed ref (or NULL), so the
+ returned refcount will be one too low:
+ */
+ PyObject *obj = foo();
+ return obj;
+}
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/script.py b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/script.py
new file mode 100644
index 0000000..6bb62fd
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/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)
diff --git a/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/stderr.txt b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/stderr.txt
new file mode 100644
index 0000000..70e3a47
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/stderr.txt
@@ -0,0 +1,10 @@
+tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/input.c: In function 'test':
+tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/input.c:46:5: error: ob_refcnt of return value is 1 too low
+tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/input.c:46:5: note: was expecting final ob_refcnt to be N + 1 (for some unknown N)
+tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/input.c:46:5: note: due to object being referenced by: return value
+tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/input.c:46:5: note: but final ob_refcnt is N + 0
+tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/input.c:45:15: note: borrowed ref from foo() allocated at: PyObject *obj = foo();
+tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/input.c:45:15: note: when foo() succeeds at: PyObject *obj = foo();
+tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/input.c:45:15: note: ob_refcnt is now refs: 0 + N where N >= 1
+tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/input.c:46:5: note: returning at: return obj;
+tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/input.c:40:1: note: graphical error report for function 'test' written out to 'tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/input.c.test-refcount-errors.html'
diff --git a/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/stdout.txt b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/stdout.txt
new file mode 100644
index 0000000..edd9442
--- /dev/null
+++ b/tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/stdout.txt
@@ -0,0 +1,41 @@
+Trace 0:
+ Transitions:
+ 'when foo() succeeds'
+ 'returning'
+ Return value:
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/input.c', line=45), region=RegionOnHeap('borrowed ref from foo()', gcc.Location(file='tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/input.c', line=45)))
+ str(): (struct PyObject *)&RegionOnHeap('borrowed ref from foo()', gcc.Location(file='tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/input.c', line=45)) from tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/input.c:45
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/input.c', line=45), region=Region('PyTypeObject for borrowed ref from 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/attributes/returns-borrowed-ref/incorrect-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/returns-borrowed-ref/incorrect-usage/input.c', line=39), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ Exception:
+ (struct PyObject *)0 from tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/input.c:40
+
+Trace 1:
+ Transitions:
+ 'when foo() fails'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-usage/input.c', line=45), value=0)
+ str(): (struct PyObject *)0 from tests/cpychecker/refcounts/attributes/returns-borrowed-ref/incorrect-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/returns-borrowed-ref/incorrect-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/returns-borrowed-ref/incorrect-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/returns-borrowed-ref/incorrect-usage/input.c:45
12 years, 7 months
[gcc-python-plugin] add gcc.define_macro()
by David Hugh Malcolm
commit 2f917919393efad9ae58689ff457c5d5181a944d
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Oct 25 15:59:25 2011 -0400
add gcc.define_macro()
Support the creation of pre-defined preprocessor macros by adding a
gcc.define_macro()
function.
It is only callable from certain places (calling the underlying C code
at the wrong time can segfault cc1)
Provide an example of using this to conditionalize the use of a custom
attribute in C code
docs/attributes.rst | 26 +++++++++++
docs/misc.rst | 1 +
docs/preprocessor.rst | 25 ++++++++++
gcc-python.c | 42 +++++++++++++++++
tests/examples/attributes-with-macros/input.c | 53 ++++++++++++++++++++++
tests/examples/attributes-with-macros/script.py | 42 +++++++++++++++++
tests/examples/attributes-with-macros/stdout.txt | 5 ++
tests/plugin/macros/correct/input.c | 48 +++++++++++++++++++
tests/plugin/macros/correct/script.py | 28 +++++++++++
tests/plugin/macros/too-early/input.c | 48 +++++++++++++++++++
tests/plugin/macros/too-early/script.py | 23 +++++++++
tests/plugin/macros/too-early/stderr.txt | 4 ++
12 files changed, 345 insertions(+), 0 deletions(-)
---
diff --git a/docs/attributes.rst b/docs/attributes.rst
index 4e99aec..ac310f8 100644
--- a/docs/attributes.rst
+++ b/docs/attributes.rst
@@ -100,3 +100,29 @@ leads to this output from the script:
.. literalinclude:: ../tests/examples/attributes/stdout.txt
:language: bash
+
+Using the preprocessor to guard attribute usage
+-----------------------------------------------
+
+Unfortunately, the above C code will only work when it is compiled with the
+Python script that adds the custom attributes.
+
+You can avoid this by using :py:func:`gcc.define_macro()` to pre-define a
+preprocessor name (e.g. "WITH_ATTRIBUTE_CLAIMS_MUTEX") at the same time as when
+you define the attribute:
+
+ .. literalinclude:: ../tests/examples/attributes-with-macros/script.py
+ :lines: 18-
+ :language: python
+
+This way the user can write this C code instead, and have it work both with
+and without the Python script:
+
+ .. literalinclude:: ../tests/examples/attributes-with-macros/input.c
+ :lines: 22-45
+ :language: c
+
+giving this output from the script:
+
+ .. literalinclude:: ../tests/examples/attributes-with-macros/stdout.txt
+ :language: bash
diff --git a/docs/misc.rst b/docs/misc.rst
index bd6dcc0..75c1322 100644
--- a/docs/misc.rst
+++ b/docs/misc.rst
@@ -27,6 +27,7 @@ scripts.
callgraph.rst
options.rst
parameters.rst
+ preprocessor.rst
versions.rst
tables-of-passes.rst
rtl.rst
diff --git a/docs/preprocessor.rst b/docs/preprocessor.rst
new file mode 100644
index 0000000..7e37f03
--- /dev/null
+++ b/docs/preprocessor.rst
@@ -0,0 +1,25 @@
+Working with the preprocessor
+=============================
+
+For languages that support a preprocessor, it's possible to inject new
+"built-in" macros into the compilation from a Python script.
+
+The motivation for this is to better support the creation of custom
+attributes, by creating preprocessor names that can be tested against.
+
+.. py:function:: gcc.define_macro(name)
+
+ Defines a preprocessor macro with the given name, which may be of use
+ for code that needs to test for the presence of your script.
+
+ This function can only be called from within specific event callbacks,
+ since it manipulates the state of the preprocessor for a given source
+ file.
+
+ For now, only call it in a handler for the event `gcc.PLUGIN_ATTRIBUTES`:
+
+ .. literalinclude:: ../tests/examples/attributes-with-macros/script.py
+ :lines: 18-
+ :language: python
+
+
diff --git a/gcc-python.c b/gcc-python.c
index 544415f..7c39fee 100644
--- a/gcc-python.c
+++ b/gcc-python.c
@@ -34,6 +34,8 @@ int plugin_is_GPL_compatible;
#include "cgraph.h"
#include "opts.h"
+#include "c-family/c-pragma.h" /* for parse_in */
+
#if 0
#define LOG(msg) \
(void)fprintf(stderr, "%s:%i:%s\n", __FILE__, __LINE__, (msg))
@@ -349,6 +351,41 @@ gcc_python_register_callback(PyObject *self, PyObject *args, PyObject *kwargs)
Py_RETURN_NONE;
}
+static PyObject*
+gcc_python_define_macro(PyObject *self,
+ PyObject *args, PyObject *kwargs)
+{
+ const char *macro;
+ char *keywords[] = {"macro",
+ NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+ "s:define_preprocessor_name", keywords,
+ ¯o)) {
+ return NULL;
+ }
+
+ if (0) {
+ fprintf(stderr, "gcc.define_macro(\"%s\")\n", macro);
+ }
+
+ if (!parse_in) {
+ return PyErr_Format(PyExc_ValueError,
+ "gcc.define_macro(\"%s\") called without a compilation unit",
+ macro);
+ }
+
+ if (!gcc_python_is_within_event(NULL)) {
+ return PyErr_Format(PyExc_ValueError,
+ "gcc.define_macro(\"%s\") called from outside an event callback",
+ macro);
+ }
+
+ cpp_define (parse_in, macro);
+
+ Py_RETURN_NONE;
+}
+
/*
I initially attempted to directly wrap gcc's:
diagnostic_report_diagnostic()
@@ -794,6 +831,11 @@ static PyMethodDef GccMethods[] = {
(METH_VARARGS | METH_KEYWORDS),
"Register a callback, to be called when various GCC events occur."},
+ {"define_macro",
+ (PyCFunction)gcc_python_define_macro,
+ (METH_VARARGS | METH_KEYWORDS),
+ "Pre-define a named value in the preprocessor."},
+
/* Diagnostics: */
{"permerror", gcc_python_permerror, METH_VARARGS,
NULL},
diff --git a/tests/examples/attributes-with-macros/input.c b/tests/examples/attributes-with-macros/input.c
new file mode 100644
index 0000000..6449dbb
--- /dev/null
+++ b/tests/examples/attributes-with-macros/input.c
@@ -0,0 +1,53 @@
+/*
+ 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/>.
+*/
+
+/* Test of adding custom attributes */
+
+#if defined(WITH_ATTRIBUTE_CLAIMS_MUTEX)
+ #define CLAIMS_MUTEX(x) __attribute__((claims_mutex(x)))
+#else
+ #define CLAIMS_MUTEX(x)
+#endif
+
+#if defined(WITH_ATTRIBUTE_RELEASES_MUTEX)
+ #define RELEASES_MUTEX(x) __attribute__((releases_mutex(x)))
+#else
+ #define RELEASES_MUTEX(x)
+#endif
+
+
+/* Function declarations with custom attributes: */
+extern void some_function(void)
+ CLAIMS_MUTEX("io");
+
+extern void some_other_function(void)
+ RELEASES_MUTEX("io");
+
+extern void yet_another_function(void)
+ CLAIMS_MUTEX("db")
+ CLAIMS_MUTEX("io")
+ RELEASES_MUTEX("io");
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/examples/attributes-with-macros/script.py b/tests/examples/attributes-with-macros/script.py
new file mode 100644
index 0000000..07280e5
--- /dev/null
+++ b/tests/examples/attributes-with-macros/script.py
@@ -0,0 +1,42 @@
+# Copyright 2011 David Malcolm <dmalcolm(a)redhat.com>
+# Copyright 2011 Red Hat, Inc.
+#
+# This is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+import gcc
+
+def attribute_callback_for_claims_mutex(*args):
+ print('attribute_callback_for_claims_mutex called: args: %s' % (args, ))
+
+def attribute_callback_for_releases_mutex(*args):
+ print('attribute_callback_for_releases_mutex called: args: %s' % (args, ))
+
+def register_our_attributes():
+ gcc.register_attribute('claims_mutex',
+ 1, 1,
+ False, False, False,
+ attribute_callback_for_claims_mutex)
+ gcc.define_macro('WITH_ATTRIBUTE_CLAIMS_MUTEX')
+
+ gcc.register_attribute('releases_mutex',
+ 1, 1,
+ False, False, False,
+ attribute_callback_for_releases_mutex)
+ gcc.define_macro('WITH_ATTRIBUTE_RELEASES_MUTEX')
+
+# Wire up our callback:
+gcc.register_callback(gcc.PLUGIN_ATTRIBUTES,
+ register_our_attributes)
+
diff --git a/tests/examples/attributes-with-macros/stdout.txt b/tests/examples/attributes-with-macros/stdout.txt
new file mode 100644
index 0000000..699564d
--- /dev/null
+++ b/tests/examples/attributes-with-macros/stdout.txt
@@ -0,0 +1,5 @@
+attribute_callback_for_claims_mutex called: args: (gcc.FunctionDecl('some_function'), gcc.StringCst('io'))
+attribute_callback_for_releases_mutex called: args: (gcc.FunctionDecl('some_other_function'), gcc.StringCst('io'))
+attribute_callback_for_claims_mutex called: args: (gcc.FunctionDecl('yet_another_function'), gcc.StringCst('db'))
+attribute_callback_for_claims_mutex called: args: (gcc.FunctionDecl('yet_another_function'), gcc.StringCst('io'))
+attribute_callback_for_releases_mutex called: args: (gcc.FunctionDecl('yet_another_function'), gcc.StringCst('io'))
diff --git a/tests/plugin/macros/correct/input.c b/tests/plugin/macros/correct/input.c
new file mode 100644
index 0000000..ea76692
--- /dev/null
+++ b/tests/plugin/macros/correct/input.c
@@ -0,0 +1,48 @@
+/*
+ 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/>.
+*/
+
+/* Test of adding custom preprocessor macros */
+
+#ifndef FOO
+ /* We should not get here: */
+ #error FOO is not defined
+#endif
+
+#ifndef BAR
+ /* We should not get here: */
+ #error BAR is not defined
+#endif
+
+#if BAR != 42
+ /* We should not get here: */
+ #error BAR is not 42
+#endif
+
+int main(int argc, char *argv[])
+{
+ return BAR;
+}
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/plugin/macros/correct/script.py b/tests/plugin/macros/correct/script.py
new file mode 100644
index 0000000..3d6ba46
--- /dev/null
+++ b/tests/plugin/macros/correct/script.py
@@ -0,0 +1,28 @@
+# Copyright 2011 David Malcolm <dmalcolm(a)redhat.com>
+# Copyright 2011 Red Hat, Inc.
+#
+# This is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+import gcc
+
+# Verify that we can register custom preprocessor macros:
+
+def register_our_macros():
+ gcc.define_macro('FOO')
+ gcc.define_macro('BAR=42')
+
+# Wire up our callback:
+gcc.register_callback(gcc.PLUGIN_ATTRIBUTES,
+ register_our_macros)
diff --git a/tests/plugin/macros/correct/stdout.txt b/tests/plugin/macros/correct/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/tests/plugin/macros/too-early/input.c b/tests/plugin/macros/too-early/input.c
new file mode 100644
index 0000000..ea76692
--- /dev/null
+++ b/tests/plugin/macros/too-early/input.c
@@ -0,0 +1,48 @@
+/*
+ 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/>.
+*/
+
+/* Test of adding custom preprocessor macros */
+
+#ifndef FOO
+ /* We should not get here: */
+ #error FOO is not defined
+#endif
+
+#ifndef BAR
+ /* We should not get here: */
+ #error BAR is not defined
+#endif
+
+#if BAR != 42
+ /* We should not get here: */
+ #error BAR is not 42
+#endif
+
+int main(int argc, char *argv[])
+{
+ return BAR;
+}
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/plugin/macros/too-early/script.py b/tests/plugin/macros/too-early/script.py
new file mode 100644
index 0000000..6cca7b8
--- /dev/null
+++ b/tests/plugin/macros/too-early/script.py
@@ -0,0 +1,23 @@
+# Copyright 2011 David Malcolm <dmalcolm(a)redhat.com>
+# Copyright 2011 Red Hat, Inc.
+#
+# This is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+import gcc
+
+# Verify that we gracefully handle an attempt to register a custom
+# preprocessor macros at the wrong time:
+
+gcc.define_macro('THIS_IS_TOO_EARLY')
diff --git a/tests/plugin/macros/too-early/stderr.txt b/tests/plugin/macros/too-early/stderr.txt
new file mode 100644
index 0000000..2803ebe
--- /dev/null
+++ b/tests/plugin/macros/too-early/stderr.txt
@@ -0,0 +1,4 @@
+Traceback (most recent call last):
+ File "tests/plugin/macros/too-early/script.py", line 23, in <module>
+ gcc.define_macro('THIS_IS_TOO_EARLY')
+ValueError: gcc.define_macro("THIS_IS_TOO_EARLY") called from outside an event callback
diff --git a/tests/plugin/macros/too-early/stdout.txt b/tests/plugin/macros/too-early/stdout.txt
new file mode 100644
index 0000000..e69de29
12 years, 7 months
[gcc-python-plugin] track which event we're within when a callback fires
by David Hugh Malcolm
commit 2143f3f09a10f522a506ec6792f7bc7d0aab3e50
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Oct 25 15:49:51 2011 -0400
track which event we're within when a callback fires
This will be of use when bulletproofing certain APIs that can only
be called at certain times within the compilation.
gcc-python-closure.c | 15 ++++++++++++++-
gcc-python-closure.h | 12 +++++++++++-
gcc-python-gimple.c | 2 +-
gcc-python.c | 25 ++++++++++++++++++++++++-
gcc-python.h | 5 +++++
5 files changed, 55 insertions(+), 4 deletions(-)
---
diff --git a/gcc-python-closure.c b/gcc-python-closure.c
index c6f83dc..5c9b8ae 100644
--- a/gcc-python-closure.c
+++ b/gcc-python-closure.c
@@ -25,7 +25,7 @@
#include "function.h"
struct callback_closure *
-gcc_python_closure_new(PyObject *callback, PyObject *extraargs, PyObject *kwargs)
+gcc_python_closure_new_generic(PyObject *callback, PyObject *extraargs, PyObject *kwargs)
{
struct callback_closure *closure;
@@ -59,6 +59,19 @@ gcc_python_closure_new(PyObject *callback, PyObject *extraargs, PyObject *kwargs
Py_INCREF(kwargs);
}
+ closure->event = GCC_PYTHON_PLUGIN_BAD_EVENT;
+
+ return closure;
+}
+
+struct callback_closure *
+gcc_python_closure_new_for_plugin_event(PyObject *callback, PyObject *extraargs, PyObject *kwargs,
+ enum plugin_event event)
+{
+ struct callback_closure *closure = gcc_python_closure_new_generic(callback, extraargs, kwargs);
+ if (closure) {
+ closure->event = event;
+ }
return closure;
}
diff --git a/gcc-python-closure.h b/gcc-python-closure.h
index 99dc170..7950fbc 100644
--- a/gcc-python-closure.h
+++ b/gcc-python-closure.h
@@ -25,10 +25,20 @@ struct callback_closure
PyObject *callback;
PyObject *extraargs;
PyObject *kwargs;
+ enum plugin_event event;
+ /* or GCC_PYTHON_PLUGIN_BAD_EVENT if not an event */
};
struct callback_closure *
-gcc_python_closure_new(PyObject *callback, PyObject *extraargs, PyObject *kwargs);
+gcc_python_closure_new_generic(PyObject *callback,
+ PyObject *extraargs,
+ PyObject *kwargs);
+
+struct callback_closure *
+gcc_python_closure_new_for_plugin_event(PyObject *callback,
+ PyObject *extraargs,
+ PyObject *kwargs,
+ enum plugin_event event);
PyObject *
gcc_python_closure_make_args(struct callback_closure * closure,
diff --git a/gcc-python-gimple.c b/gcc-python-gimple.c
index 7baec2b..8e24408 100644
--- a/gcc-python-gimple.c
+++ b/gcc-python-gimple.c
@@ -119,7 +119,7 @@ gcc_Gimple_walk_tree(struct PyGccGimple * self, PyObject *args, PyObject *kwargs
callback = PyTuple_GetItem(args, 0);
extraargs = PyTuple_GetSlice(args, 1, PyTuple_Size(args));
- closure = gcc_python_closure_new(callback, extraargs, kwargs);
+ closure = gcc_python_closure_new_generic(callback, extraargs, kwargs);
if (!closure) {
return NULL;
}
diff --git a/gcc-python.c b/gcc-python.c
index 30a6a17..544415f 100644
--- a/gcc-python.c
+++ b/gcc-python.c
@@ -149,6 +149,21 @@ static void trace_callback_for_##NAME(void *gcc_data, void *user_data) \
# undef DEFEVENT
#endif /* GCC_PYTHON_TRACE_ALL_EVENTS */
+static enum plugin_event current_event = GCC_PYTHON_PLUGIN_BAD_EVENT;
+
+int gcc_python_is_within_event(enum plugin_event *out_event)
+{
+ if (current_event != GCC_PYTHON_PLUGIN_BAD_EVENT) {
+ if (out_event) {
+ *out_event = current_event;
+ }
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+
static void
gcc_python_finish_invoking_callback(PyGILState_STATE gstate,
int expect_wrapped_data, PyObject *wrapped_gcc_data,
@@ -158,6 +173,7 @@ gcc_python_finish_invoking_callback(PyGILState_STATE gstate,
PyObject *args = NULL;
PyObject *result = NULL;
location_t saved_loc = input_location;
+ enum plugin_event saved_event;
assert(closure);
/* We take ownership of wrapped_gcc_data.
@@ -176,8 +192,14 @@ gcc_python_finish_invoking_callback(PyGILState_STATE gstate,
if (!args) {
goto cleanup;
}
+
+ saved_event = current_event;
+ current_event = closure->event;
+
result = PyObject_Call(closure->callback, args, closure->kwargs);
+ current_event = saved_event;
+
if (!result) {
/* Treat an unhandled Python error as a compilation error: */
gcc_python_print_exception("Unhandled Python exception raised within callback");
@@ -277,7 +299,8 @@ gcc_python_register_callback(PyObject *self, PyObject *args, PyObject *kwargs)
//printf("%s:%i:gcc_python_register_callback\n", __FILE__, __LINE__);
- closure = gcc_python_closure_new(callback, extraargs, kwargs);
+ closure = gcc_python_closure_new_for_plugin_event(callback, extraargs, kwargs,
+ (enum plugin_event)event);
if (!closure) {
return PyErr_NoMemory();
}
diff --git a/gcc-python.h b/gcc-python.h
index f350ea7..55af6fa 100644
--- a/gcc-python.h
+++ b/gcc-python.h
@@ -25,6 +25,9 @@
#include "gimple.h"
#include "params.h"
+/* GCC doesn't seem to give us an ID for "invalid event", so invent one: */
+#define GCC_PYTHON_PLUGIN_BAD_EVENT (0xffff)
+
/*
Macro DECLARE_SIMPLE_WRAPPER():
ARG_structname:
@@ -217,6 +220,8 @@ gcc_python_insert_new_wrapper_into_cache(PyObject **cache,
/* gcc-python.c */
+int gcc_python_is_within_event(enum plugin_event *out_event);
+
char * gcc_python_strdup(const char *str);
void gcc_python_print_exception(const char *msg);
12 years, 7 months
[gcc-python-plugin] docs: fix description of syntax of __attribute__(())
by David Hugh Malcolm
commit 4c6ff87c5d32867f3d12534815f35e88cc2a1a32
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Oct 25 11:22:06 2011 -0400
docs: fix description of syntax of __attribute__(())
docs/attributes.rst | 13 ++++++++-----
1 files changed, 8 insertions(+), 5 deletions(-)
---
diff --git a/docs/attributes.rst b/docs/attributes.rst
index 74f89b3..4e99aec 100644
--- a/docs/attributes.rst
+++ b/docs/attributes.rst
@@ -18,7 +18,7 @@
Creating custom GCC attributes
==============================
-GNU C supports a non-standard `__attribute__() syntax
+GNU C supports a non-standard `__attribute__(()) syntax
<http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html>`_ for marking
declarations with additional information that may be of interest to the
optimizer, and for checking the correctness of the code.
@@ -30,13 +30,16 @@ describing the interaction of a function on mutex objects:
.. code-block:: c
- extern some_function(void) __attribute__(claims_mutex("io"));
- extern some_other_function(void) __attribute__(releases_mutex("io"));
+ extern void some_function(void)
+ __attribute__((claims_mutex("io")));
+
+ extern void some_other_function(void)
+ __attribute__((releases_mutex("io")));
and use this in a custom code-checker.
Custom attributes can take string and integer parameters. For example, the
-above custom attribute takes a single string parameter. A custom attribute can
+above custom attributes take a single string parameter. A custom attribute can
take more than one parameter, or none at all.
To create custom attributes from Python, you need to wire up a callback
@@ -56,7 +59,7 @@ attribute is encountered in C code.
callable)
Registers a new GCC attribute with the given *name* , usable in C source
- code via ``__attribute__()``.
+ code via ``__attribute__(())``.
:param name: the name of the new attribute
:type name: str
12 years, 7 months
[gcc-python-plugin] add support for creating custom attributes from Python
by David Hugh Malcolm
commit a9a4a13446a644ddd0b4616010c45ee68365c924
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Mon Oct 24 20:34:18 2011 -0400
add support for creating custom attributes from Python
Makefile | 1 +
docs/attributes.rst | 99 +++++++++++++
docs/index.rst | 1 +
gcc-python-attribute.c | 261 ++++++++++++++++++++++++++++++++++
gcc-python-wrappers.h | 4 +
gcc-python.c | 36 +++++
tests/examples/attributes/input.c | 36 +++++
tests/examples/attributes/script.py | 41 ++++++
tests/examples/attributes/stdout.txt | 5 +
tests/plugin/attributes/input.c | 51 +++++++
tests/plugin/attributes/script.py | 48 ++++++
tests/plugin/attributes/stdout.txt | 12 ++
12 files changed, 595 insertions(+), 0 deletions(-)
---
diff --git a/Makefile b/Makefile
index 3e01942..8c62ef9 100644
--- a/Makefile
+++ b/Makefile
@@ -19,6 +19,7 @@ GCC=gcc
PLUGIN_SOURCE_FILES= \
gcc-python.c \
+ gcc-python-attribute.c \
gcc-python-callgraph.c \
gcc-python-cfg.c \
gcc-python-closure.c \
diff --git a/docs/attributes.rst b/docs/attributes.rst
new file mode 100644
index 0000000..74f89b3
--- /dev/null
+++ b/docs/attributes.rst
@@ -0,0 +1,99 @@
+.. 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/>.
+
+Creating custom GCC attributes
+==============================
+
+GNU C supports a non-standard `__attribute__() syntax
+<http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html>`_ for marking
+declarations with additional information that may be of interest to the
+optimizer, and for checking the correctness of the code.
+
+The GCC Python plugin allows you to create custom attributes, which may
+be of use to your scripts: you can use this to annotate C code with additional
+information. For example, you could create a custom attribute for functions
+describing the interaction of a function on mutex objects:
+
+.. code-block:: c
+
+ extern some_function(void) __attribute__(claims_mutex("io"));
+ extern some_other_function(void) __attribute__(releases_mutex("io"));
+
+and use this in a custom code-checker.
+
+Custom attributes can take string and integer parameters. For example, the
+above custom attribute takes a single string parameter. A custom attribute can
+take more than one parameter, or none at all.
+
+To create custom attributes from Python, you need to wire up a callback
+response to the `gcc.PLUGIN_ATTRIBUTES` event:
+
+ .. literalinclude:: ../tests/examples/attributes/script.py
+ :lines: 39-40
+ :language: python
+
+This callback should then call :py:func:`gcc.register_attribute` to associate
+the name of the attribute with a Python callback to be called when the
+attribute is encountered in C code.
+
+.. py:function:: gcc.register_attribute(name, min_length, max_length, \
+ decl_required, type_required, \
+ function_type_required, \
+ callable)
+
+ Registers a new GCC attribute with the given *name* , usable in C source
+ code via ``__attribute__()``.
+
+ :param name: the name of the new attribute
+ :type name: str
+ :param min_length: the minimum number of arguments expected when the attribute is used
+ :type min_length: int
+ :param max_length: the maximum number of arguments expected when the
+ attribute is used (-1 for no maximum)
+ :type max_length: int
+ :param decl_required:
+ :type decl_required:
+ :param type_required:
+ :type type_required:
+ :param function_type_required:
+ :type function_type_required:
+ :param callable: the callback to be invoked when the attribute is seen
+ :type callable: a callable object, such as a function
+
+In this example, we can simply print when the attribute is seen, to verify that
+the callback mechanism is working:
+
+ .. literalinclude:: ../tests/examples/attributes/script.py
+ :lines: 22-36
+ :language: python
+
+Putting it all together, here is an example Python script for the plugin:
+
+ .. literalinclude:: ../tests/examples/attributes/script.py
+ :lines: 18-
+ :language: python
+
+Compiling this test C source file:
+
+ .. literalinclude:: ../tests/examples/attributes/input.c
+ :lines: 22-29
+ :language: c
+
+leads to this output from the script:
+
+ .. literalinclude:: ../tests/examples/attributes/stdout.txt
+ :language: bash
diff --git a/docs/index.rst b/docs/index.rst
index 6048a05..685e490 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -36,6 +36,7 @@ Contents:
tree.rst
gimple.rst
passes.rst
+ attributes.rst
cpychecker.rst
success.rst
misc.rst
diff --git a/gcc-python-attribute.c b/gcc-python-attribute.c
new file mode 100644
index 0000000..59bdc0f
--- /dev/null
+++ b/gcc-python-attribute.c
@@ -0,0 +1,261 @@
+/*
+ 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>
+#include "gcc-python.h"
+#include "gcc-python-wrappers.h"
+
+#include "tree.h"
+#include "diagnostic.h"
+#include "plugin.h"
+
+/*
+ Attribute handling
+*/
+/* Dictionary mapping string attribute names to callables */
+static PyObject *attribute_dict;
+
+/*
+ Helper function when a custom attribute is encountered, for generating
+ the arguments to the Python callback.
+
+ The args to the function call will be the node, plus the args of the
+ attribute:
+ (node, arg0, arg1, ...)
+*/
+PyObject *
+make_args_for_attribute_callback(tree node, tree args)
+{
+ PyObject *list_args = NULL;
+ PyObject *py_args = NULL;
+ PyObject *py_node = NULL;
+ Py_ssize_t i;
+
+ /* Walk "args" (a tree_list), converting to a python list of wrappers */
+ list_args = gcc_python_tree_make_list_from_tree_list_chain(args);
+ if (!list_args) {
+ goto error;
+ }
+
+ py_args = PyTuple_New(1 + PyList_Size(list_args));
+ if (!py_args) {
+ goto error;
+ }
+
+ py_node = gcc_python_make_wrapper_tree(node);
+ if (!py_node) {
+ goto error;
+ }
+ PyTuple_SET_ITEM(py_args, 0, py_node);
+
+ for (i = 0; i < PyList_Size(list_args); i++) {
+ PyObject *arg = PyList_GetItem(list_args, i);
+ Py_INCREF(arg);
+ PyTuple_SET_ITEM(py_args, i + 1, arg);
+ }
+ Py_DECREF(list_args);
+
+ return py_args;
+
+ error:
+ Py_XDECREF(list_args);
+ Py_XDECREF(py_args);
+ Py_XDECREF(py_node);
+ return NULL;
+}
+
+static tree
+handle_python_attribute(tree *node, tree name, tree args,
+ int flags, bool *no_add_attrs)
+{
+ PyObject *callable;
+
+ /* Debug code: */
+ if (0) {
+ printf("handle_python_attribute called\n");
+ fprintf(stderr, "node: ");
+ debug_tree(*node); /* the site of the attribute e.g. a var_decl */
+
+ fprintf(stderr, "name: ");
+ debug_tree(name); /* an identifier_node e.g. "custom_attribute_without_args" */
+
+ fprintf(stderr, "args: ");
+ debug_tree(args); /* if present, a tree_list, e.g. of constants */
+ fprintf(stderr, "flags: %i\n", flags);
+ fprintf(stderr, "and here!\n");
+ }
+
+ /*
+ How do we get to the attribute?
+
+ This code:
+ const struct attribute_spec *spec = lookup_attribute_spec (name);
+ suggests that attributes must have unique names, so keep a dict mapping
+ strings to callables
+ */
+ assert(IDENTIFIER_NODE == TREE_CODE(name));
+ callable = PyDict_GetItemString(attribute_dict, IDENTIFIER_POINTER(name));
+ assert(callable);
+
+ {
+ PyGILState_STATE gstate;
+ PyObject *py_args = NULL;
+ PyObject *result = NULL;
+
+ gstate = PyGILState_Ensure();
+
+ /*
+ The args to the function call will be the node, plus the args of the
+ attribute:
+ */
+ py_args = make_args_for_attribute_callback(*node, args);
+ if (!py_args) {
+ goto cleanup;
+ }
+ result = PyObject_Call(callable, py_args, NULL);
+ if (!result) {
+ /* Treat an unhandled Python error as a compilation error: */
+ error("Unhandled Python exception raised within %s attribute handler",
+ IDENTIFIER_POINTER(name));
+ PyErr_PrintEx(1);
+ }
+
+ /* (the result is ignored) */
+
+ cleanup:
+ Py_XDECREF(py_args);
+ Py_XDECREF(result);
+
+ PyGILState_Release(gstate);
+ }
+
+ return NULL; // FIXME
+}
+
+PyObject*
+gcc_python_register_attribute(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char *name;
+ int min_length;
+ int max_length;
+ int decl_required;
+ int type_required;
+ int function_type_required;
+ PyObject *callable;
+ struct attribute_spec *attr;
+
+ char *keywords[] = {"name",
+ "min_length",
+ "max_length",
+ "decl_required",
+ "type_required",
+ "function_type_required",
+ "callable",
+ NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+ "siiiiiO:register_attribute", keywords,
+ &name,
+ &min_length,
+ &max_length,
+ &decl_required,
+ &type_required,
+ &function_type_required,
+ &callable)) {
+ return NULL;
+ }
+
+ /*
+ "struct attribute_spec" is declared in gcc/tree.h
+
+ register_attribute() is called by GCC for various attrs stored in
+ tables of global data e.g.:
+ const struct attribute_spec lto_attribute_table[]
+
+ Hence we must malloc the data, so that it persists for the rest of the
+ lifetime of the process
+
+ We get a "handler" callback, it gets passed the name of the attribute,
+ so maybe we can map names to callables.
+ */
+ attr = PyMem_New(struct attribute_spec, 1);
+ if (!attr) {
+ return PyErr_NoMemory();
+ }
+
+ /* Clear it first, for safety: */
+ memset(attr, 0, sizeof(struct attribute_spec));
+
+ /*
+ Populate "attr"
+
+ Annoyingly, all of the fields are marked as "const" within
+ struct attribute_spec, so we have to cast away the constness, leading
+ to the following deeply ugly code:
+ */
+ *(char**)&attr->name = gcc_python_strdup(name);
+ if (!attr->name) {
+ PyMem_Free(attr);
+ return PyErr_NoMemory();
+ }
+ *(int*)&attr->min_length = min_length;
+ *(int*)&attr->max_length = max_length;
+ *(bool*)&attr->decl_required = decl_required;
+ *(bool*)&attr->type_required = type_required;
+ *(bool*)&attr->function_type_required = function_type_required;
+ *(tree (**) (tree *node, tree name, tree args, int flags, bool *no_add_attrs))&attr->handler = handle_python_attribute;
+
+ /*
+ Associate the user-supplied callable with the given name, so that
+ handle_python_attribute knows which one to call:
+ */
+ if (!attribute_dict) {
+ attribute_dict = PyDict_New();
+ if (!attribute_dict) {
+ PyMem_Free((char*)attr->name);
+ PyMem_Free(attr);
+ return NULL;
+ }
+ }
+ assert(attribute_dict);
+
+ if (-1 == PyDict_SetItemString(attribute_dict, name, callable)) {
+ PyMem_Free((char*)attr->name);
+ PyMem_Free(attr);
+ return NULL;
+ }
+
+ /*
+ OK, call into GCC to register the attribute.
+
+ (register_attribute doesn't have a return value; failures appear
+ to be fatal)
+ */
+ register_attribute (attr);
+
+ Py_RETURN_NONE;
+}
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/gcc-python-wrappers.h b/gcc-python-wrappers.h
index b67320a..fc3bf8e 100644
--- a/gcc-python-wrappers.h
+++ b/gcc-python-wrappers.h
@@ -27,6 +27,10 @@
PyMODINIT_FUNC initoptpass(void);
+/* gcc-python-attribute.c: */
+PyObject*
+gcc_python_register_attribute(PyObject *self, PyObject *args, PyObject *kwargs);
+
/* gcc-python-callgraph.c: */
PyObject *
gcc_CallgraphEdge_repr(struct PyGccCallgraphEdge * self);
diff --git a/gcc-python.c b/gcc-python.c
index 69b5b49..30a6a17 100644
--- a/gcc-python.c
+++ b/gcc-python.c
@@ -194,6 +194,14 @@ cleanup:
input_location = saved_loc;
}
+/*
+ C-level callbacks for each event ID follow, thunking into the registered
+ Python callable.
+
+ There's some repetition here, but it can be easier to debug if you have
+ separate breakpoint locations for each event ID.
+ */
+
static void
gcc_python_callback_for_tree(void *gcc_data, void *user_data)
{
@@ -207,6 +215,22 @@ gcc_python_callback_for_tree(void *gcc_data, void *user_data)
user_data);
}
+
+static void
+gcc_python_callback_for_PLUGIN_ATTRIBUTES(void *gcc_data, void *user_data)
+{
+ PyGILState_STATE gstate;
+
+ //printf("%s:%i:(%p, %p)\n", __FILE__, __LINE__, gcc_data, user_data);
+ assert(pass);
+
+ gstate = PyGILState_Ensure();
+
+ gcc_python_finish_invoking_callback(gstate,
+ 0, NULL,
+ user_data);
+}
+
static void
gcc_python_callback_for_PLUGIN_PASS_EXECUTION(void *gcc_data, void *user_data)
{
@@ -259,6 +283,13 @@ gcc_python_register_callback(PyObject *self, PyObject *args, PyObject *kwargs)
}
switch ((enum plugin_event)event) {
+ case PLUGIN_ATTRIBUTES:
+ register_callback("python", // FIXME
+ (enum plugin_event)event,
+ gcc_python_callback_for_PLUGIN_ATTRIBUTES,
+ closure);
+ break;
+
case PLUGIN_PRE_GENERICIZE:
register_callback("python", // FIXME
(enum plugin_event)event,
@@ -730,6 +761,11 @@ gcc_python_get_dump_base_name(PyObject *self, PyObject *noargs)
}
static PyMethodDef GccMethods[] = {
+ {"register_attribute",
+ (PyCFunction)gcc_python_register_attribute,
+ (METH_VARARGS | METH_KEYWORDS),
+ "Register an attribute."},
+
{"register_callback",
(PyCFunction)gcc_python_register_callback,
(METH_VARARGS | METH_KEYWORDS),
diff --git a/tests/examples/attributes/input.c b/tests/examples/attributes/input.c
new file mode 100644
index 0000000..0aa3c41
--- /dev/null
+++ b/tests/examples/attributes/input.c
@@ -0,0 +1,36 @@
+/*
+ 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/>.
+*/
+
+/* Test of adding custom attributes */
+
+/* Function declarations with custom attributes: */
+extern some_function(void) __attribute__((claims_mutex("io")));
+
+extern some_other_function(void) __attribute__((releases_mutex("io")));
+
+extern yet_another_function(void) __attribute__((claims_mutex("db"),
+ claims_mutex("io"),
+ releases_mutex("io")));
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/examples/attributes/script.py b/tests/examples/attributes/script.py
new file mode 100644
index 0000000..40a9953
--- /dev/null
+++ b/tests/examples/attributes/script.py
@@ -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/>.
+
+import gcc
+
+# Verify that we can register custom attributes:
+
+def attribute_callback_for_claims_mutex(*args):
+ print('attribute_callback_for_claims_mutex called: args: %s' % (args, ))
+
+def attribute_callback_for_releases_mutex(*args):
+ print('attribute_callback_for_releases_mutex called: args: %s' % (args, ))
+
+def register_our_attributes():
+ gcc.register_attribute('claims_mutex',
+ 1, 1,
+ False, False, False,
+ attribute_callback_for_claims_mutex)
+ gcc.register_attribute('releases_mutex',
+ 1, 1,
+ False, False, False,
+ attribute_callback_for_releases_mutex)
+
+# Wire up our callback:
+gcc.register_callback(gcc.PLUGIN_ATTRIBUTES,
+ register_our_attributes)
+
diff --git a/tests/examples/attributes/stdout.txt b/tests/examples/attributes/stdout.txt
new file mode 100644
index 0000000..699564d
--- /dev/null
+++ b/tests/examples/attributes/stdout.txt
@@ -0,0 +1,5 @@
+attribute_callback_for_claims_mutex called: args: (gcc.FunctionDecl('some_function'), gcc.StringCst('io'))
+attribute_callback_for_releases_mutex called: args: (gcc.FunctionDecl('some_other_function'), gcc.StringCst('io'))
+attribute_callback_for_claims_mutex called: args: (gcc.FunctionDecl('yet_another_function'), gcc.StringCst('db'))
+attribute_callback_for_claims_mutex called: args: (gcc.FunctionDecl('yet_another_function'), gcc.StringCst('io'))
+attribute_callback_for_releases_mutex called: args: (gcc.FunctionDecl('yet_another_function'), gcc.StringCst('io'))
diff --git a/tests/plugin/attributes/input.c b/tests/plugin/attributes/input.c
new file mode 100644
index 0000000..3b8ed8e
--- /dev/null
+++ b/tests/plugin/attributes/input.c
@@ -0,0 +1,51 @@
+/*
+ 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/>.
+*/
+
+/* Test of adding custom attributes */
+
+/* Function declaration with custom attribs: */
+int main(int argc, char *argv[])
+ __attribute__((custom_attribute_without_args,
+ custom_attribute_with_one_arg(1066),
+ custom_attribute_with_one_or_two_args("secret agent"),
+ custom_attribute_with_one_or_two_args("elephant", "giraffe") ));
+
+/* Global var declaration with custom attribs: */
+int test_global __attribute__((custom_attribute_without_args,
+ custom_attribute_with_one_arg(1492),
+ custom_attribute_with_one_or_two_args("private investigator"),
+ custom_attribute_with_one_or_two_args("cow", "hedgehog") ));
+
+int main(int argc, char *argv[])
+{
+ /* Local var declaration with custom attribs: */
+ int i __attribute__((custom_attribute_without_args,
+ custom_attribute_with_one_arg(1776),
+ custom_attribute_with_one_or_two_args("haberdasher"),
+ custom_attribute_with_one_or_two_args("turtle", "bear") ));
+ return 0;
+}
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/plugin/attributes/script.py b/tests/plugin/attributes/script.py
new file mode 100644
index 0000000..e6d9719
--- /dev/null
+++ b/tests/plugin/attributes/script.py
@@ -0,0 +1,48 @@
+# Copyright 2011 David Malcolm <dmalcolm(a)redhat.com>
+# Copyright 2011 Red Hat, Inc.
+#
+# This is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+import gcc
+
+# Verify that we can register custom attributes:
+
+def my_attribute_callback_A(*args):
+ print('my_attribute_callback_A called: args: %s' % (args, ))
+
+def my_attribute_callback_B(*args):
+ print('my_attribute_callback_B called: args: %s' % (args, ))
+
+def my_attribute_callback_C(*args):
+ print('my_attribute_callback_C called: args: %s' % (args, ))
+
+def register_our_attributes():
+ gcc.register_attribute('custom_attribute_without_args',
+ 0, 0,
+ False, False, False,
+ my_attribute_callback_A)
+ gcc.register_attribute('custom_attribute_with_one_arg',
+ 1, 1,
+ False, False, False,
+ my_attribute_callback_B)
+ gcc.register_attribute('custom_attribute_with_one_or_two_args',
+ 1, 2,
+ False, False, False,
+ my_attribute_callback_C)
+
+# Wire up our callback:
+gcc.register_callback(gcc.PLUGIN_ATTRIBUTES,
+ register_our_attributes)
+
diff --git a/tests/plugin/attributes/stdout.txt b/tests/plugin/attributes/stdout.txt
new file mode 100644
index 0000000..46bcf3a
--- /dev/null
+++ b/tests/plugin/attributes/stdout.txt
@@ -0,0 +1,12 @@
+my_attribute_callback_A called: args: (gcc.FunctionDecl('main'),)
+my_attribute_callback_B called: args: (gcc.FunctionDecl('main'), gcc.IntegerCst(1066))
+my_attribute_callback_C called: args: (gcc.FunctionDecl('main'), gcc.StringCst('secret agent'))
+my_attribute_callback_C called: args: (gcc.FunctionDecl('main'), gcc.StringCst('elephant'), gcc.StringCst('giraffe'))
+my_attribute_callback_A called: args: (gcc.VarDecl('test_global'),)
+my_attribute_callback_B called: args: (gcc.VarDecl('test_global'), gcc.IntegerCst(1492))
+my_attribute_callback_C called: args: (gcc.VarDecl('test_global'), gcc.StringCst('private investigator'))
+my_attribute_callback_C called: args: (gcc.VarDecl('test_global'), gcc.StringCst('cow'), gcc.StringCst('hedgehog'))
+my_attribute_callback_A called: args: (gcc.VarDecl('i'),)
+my_attribute_callback_B called: args: (gcc.VarDecl('i'), gcc.IntegerCst(1776))
+my_attribute_callback_C called: args: (gcc.VarDecl('i'), gcc.StringCst('haberdasher'))
+my_attribute_callback_C called: args: (gcc.VarDecl('i'), gcc.StringCst('turtle'), gcc.StringCst('bear'))
12 years, 7 months
[gcc-python-plugin] cpychecker: allow the checker to run on an SSA representation
by David Hugh Malcolm
commit ec88419c0e37e9b9db860551c218e2dde499f17b
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Fri Oct 21 19:35:43 2011 -0400
cpychecker: allow the checker to run on an SSA representation
This patch adds minimal support to libcpychecker to allow it to run on the
SSA representation of a function.
However, it doesn't change when the "cpychecker" pass runs (actually changing
this causes numerous changes to the output of the self tests, alas).
Note that we don't really do much with the SSA form: we ignore Phi nodes, and
all gcc.SsaName() references are handled by referring back to the underlying
variable, so that our region-to-value and pointer-to-region mappings should
continue to work.
However, in theory this may allow us e.g. to use facts about the code deduced
by SSA optimization passes.
libcpychecker/__init__.py | 8 +++++++-
libcpychecker/absinterp.py | 12 +++++++++++-
2 files changed, 18 insertions(+), 2 deletions(-)
---
diff --git a/libcpychecker/__init__.py b/libcpychecker/__init__.py
index e54de04..3a9f0e8 100644
--- a/libcpychecker/__init__.py
+++ b/libcpychecker/__init__.py
@@ -86,4 +86,10 @@ def get_all_PyMethodDef_methods():
def main(**kwargs):
# Register our GCC pass:
ps = CpyChecker(**kwargs)
- ps.register_before('*warn_function_return')
+
+ if 1:
+ # non-SSA version:
+ ps.register_before('*warn_function_return')
+ else:
+ # SSA version:
+ ps.register_after('ssa')
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index 5dc4074..3c2c436 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -1182,6 +1182,10 @@ class State(object):
log('eval_lvalue: %r %s', expr, expr)
if loc:
check_isinstance(loc, gcc.Location)
+ if isinstance(expr, gcc.SsaName):
+ region = self.var_region(expr.var)
+ check_isinstance(region, Region)
+ return region
if isinstance(expr, (gcc.VarDecl, gcc.ParmDecl, gcc.ResultDecl, gcc.FunctionDecl)):
region = self.var_region(expr)
check_isinstance(region, Region)
@@ -1226,6 +1230,12 @@ class State(object):
return expr
if isinstance(expr, gcc.IntegerCst):
return ConcreteValue(expr.type, loc, expr.constant)
+ if isinstance(expr, gcc.SsaName):
+ region = self.var_region(expr.var)
+ check_isinstance(region, Region)
+ value = self.get_store(region, expr.type, loc)
+ check_isinstance(value, AbstractValue)
+ return value
if isinstance(expr, (gcc.VarDecl, gcc.ParmDecl, gcc.ResultDecl)):
region = self.var_region(expr)
check_isinstance(region, Region)
@@ -1794,7 +1804,7 @@ class State(object):
newstate,
'not returning from %s' % stmt.fn)]
- if isinstance(stmt.fn, (gcc.VarDecl, gcc.ParmDecl)):
+ if isinstance(stmt.fn, (gcc.VarDecl, gcc.ParmDecl, gcc.SsaName)):
# Calling through a function pointer:
val = self.eval_rvalue(stmt.fn, stmt.loc)
log('val: %s', val)
12 years, 7 months
[gcc-python-plugin] cpychecker: make gcc-with-cpychecker run the refcount-checking code
by David Hugh Malcolm
commit 7cd150fe609b606f259af18fc98d34b4fbde20a7
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Fri Oct 21 19:13:06 2011 -0400
cpychecker: make gcc-with-cpychecker run the refcount-checking code
gcc-with-cpychecker | 8 +++++++-
1 files changed, 7 insertions(+), 1 deletions(-)
---
diff --git a/gcc-with-cpychecker b/gcc-with-cpychecker
index 8089ede..e7701e8 100755
--- a/gcc-with-cpychecker
+++ b/gcc-with-cpychecker
@@ -52,7 +52,13 @@ if 0:
print(ns)
print(other_args)
-cmd = 'from libcpychecker import main; main()'
+# Enable the refcount-checker when running via this script
+#
+# We would use the regular keyword argument syntax:
+# verify_refcounting=True
+# but unfortunately gcc's option parser seems to not be able to cope with '='
+# within an option's value. So we do it using dictionary syntax instead:
+cmd = 'from libcpychecker import main; main(**{"verify_refcounting":True})'
args = ['gcc',
('-fplugin=%s' % PLUGIN),
12 years, 7 months
[gcc-python-plugin] cpychecker: fix typechecking of Py_BuildValue with code "s"
by David Hugh Malcolm
commit 5c6cee670a98211882e37401ba746d01335309fb
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Fri Oct 21 18:42:51 2011 -0400
cpychecker: fix typechecking of Py_BuildValue with code "s"
libcpychecker/Py_BuildValue.py | 4 +-
libcpychecker/formatstrings.py | 7 ++--
.../refcounts/Py_BuildValue/correct-code-s/input.c | 39 ++++++++++++++++++++
.../Py_BuildValue/correct-code-s/script.py | 22 +++++++++++
.../Py_BuildValue/correct-code-s/stdout.txt | 21 +++++++++++
5 files changed, 88 insertions(+), 5 deletions(-)
---
diff --git a/libcpychecker/Py_BuildValue.py b/libcpychecker/Py_BuildValue.py
index a56939e..508ee56 100644
--- a/libcpychecker/Py_BuildValue.py
+++ b/libcpychecker/Py_BuildValue.py
@@ -205,12 +205,12 @@ class PyBuildValueFmt(ParsedFormatString):
if c in 'sz':
if next == '#':
result.add_argument(c + '#',
- [get_const_char_ptr_ptr(),
+ [get_const_char_ptr(),
get_hash_size_type(with_size_t).pointer])
i += 1
else:
result.add_argument(c,
- [get_const_char_ptr_ptr()])
+ [get_const_char_ptr()])
continue
if c in 'NSO':
diff --git a/libcpychecker/formatstrings.py b/libcpychecker/formatstrings.py
index c94c60c..11dfb1c 100644
--- a/libcpychecker/formatstrings.py
+++ b/libcpychecker/formatstrings.py
@@ -290,9 +290,10 @@ def compatible_type(exp_type, actual_type, actualarg=None):
# Support character arrays vs const char*:
if str(exp_type) == 'const char *':
- if isinstance(actual_type, gcc.ArrayType):
- if actual_type.dereference == gcc.Type.char():
- return True
+ if isinstance(actual_type, gcc.PointerType):
+ if isinstance(actual_type.dereference, gcc.ArrayType):
+ if actual_type.dereference.dereference == gcc.Type.char():
+ return True
return False
diff --git a/tests/cpychecker/refcounts/Py_BuildValue/correct-code-s/input.c b/tests/cpychecker/refcounts/Py_BuildValue/correct-code-s/input.c
new file mode 100644
index 0000000..2c2c2c6
--- /dev/null
+++ b/tests/cpychecker/refcounts/Py_BuildValue/correct-code-s/input.c
@@ -0,0 +1,39 @@
+/*
+ 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 type-checking in a correct call to Py_BuildValue with code "s"
+*/
+
+PyObject *
+test(const char *foo, const char *bar)
+{
+ return Py_BuildValue("(s, s)",
+ foo, bar);
+}
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/refcounts/Py_BuildValue/correct-code-s/script.py b/tests/cpychecker/refcounts/Py_BuildValue/correct-code-s/script.py
new file mode 100644
index 0000000..fdd5ba3
--- /dev/null
+++ b/tests/cpychecker/refcounts/Py_BuildValue/correct-code-s/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/Py_BuildValue/correct-code-s/stdout.txt b/tests/cpychecker/refcounts/Py_BuildValue/correct-code-s/stdout.txt
new file mode 100644
index 0000000..d7856cc
--- /dev/null
+++ b/tests/cpychecker/refcounts/Py_BuildValue/correct-code-s/stdout.txt
@@ -0,0 +1,21 @@
+Trace 0:
+ Transitions:
+ 'when Py_BuildValue() succeeds'
+ 'returning'
+ Return value:
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/Py_BuildValue/correct-code-s/input.c', line=29), region=RegionOnHeap('new ref from call to Py_BuildValue', gcc.Location(file='tests/cpychecker/refcounts/Py_BuildValue/correct-code-s/input.c', line=29)))
+ str(): (struct PyObject *)&RegionOnHeap('new ref from call to Py_BuildValue', gcc.Location(file='tests/cpychecker/refcounts/Py_BuildValue/correct-code-s/input.c', line=29)) from tests/cpychecker/refcounts/Py_BuildValue/correct-code-s/input.c:29
+ r->ob_refcnt: refs: 1 + N where N >= 0
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/Py_BuildValue/correct-code-s/input.c', line=29), region=Region('PyTypeObject for new ref from call to Py_BuildValue'))
+ Exception:
+ (struct PyObject *)0 from tests/cpychecker/refcounts/Py_BuildValue/correct-code-s/input.c:28
+
+Trace 1:
+ Transitions:
+ 'when Py_BuildValue() fails'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/Py_BuildValue/correct-code-s/input.c', line=29), value=0)
+ str(): (struct PyObject *)0 from tests/cpychecker/refcounts/Py_BuildValue/correct-code-s/input.c:29
+ Exception:
+ (struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) from tests/cpychecker/refcounts/Py_BuildValue/correct-code-s/input.c:29
12 years, 7 months
[gcc-python-plugin] cpychecker: fix PyGILState_Ensure
by David Hugh Malcolm
commit b0ae958d990e81b644b31625094b864dce012e5c
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Fri Oct 21 18:05:44 2011 -0400
cpychecker: fix PyGILState_Ensure
libcpychecker/refcounts.py | 7 ++-
.../refcounts/PyGILState_Ensure/correct/input.c | 42 ++++++++++++++++++++
.../refcounts/PyGILState_Ensure/correct/script.py | 22 ++++++++++
.../refcounts/PyGILState_Ensure/correct/stdout.txt | 7 +++
4 files changed, 76 insertions(+), 2 deletions(-)
---
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index b2677ed..7989186 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -1056,8 +1056,11 @@ class CPython(Facet):
########################################################################
def impl_PyGILState_Ensure(self, stmt):
# http://docs.python.org/c-api/init.html#PyGILState_Ensure
- # For now, treat it as a no-op:
- return [self.state.mktrans_nop(stmt, 'PyGILState_Ensure')]
+ # Return some opaque handle:
+ returntype = stmt.fn.type.dereference.type
+ return [self.state.mktrans_assignment(stmt.lhs,
+ UnknownValue.make(returntype, stmt.loc),
+ 'PyGILState_Ensure')]
def impl_PyGILState_Release(self, stmt, v_state):
# http://docs.python.org/c-api/init.html#PyGILState_Release
diff --git a/tests/cpychecker/refcounts/PyGILState_Ensure/correct/input.c b/tests/cpychecker/refcounts/PyGILState_Ensure/correct/input.c
new file mode 100644
index 0000000..178c02b
--- /dev/null
+++ b/tests/cpychecker/refcounts/PyGILState_Ensure/correct/input.c
@@ -0,0 +1,42 @@
+/*
+ Copyright 2011 David Malcolm <dmalcolm(a)redhat.com>
+ Copyright 2011 Red Hat, Inc.
+
+ This is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+*/
+
+#include <Python.h>
+
+/*
+ Test of correct usage of PyGILState_{Ensure,Release}
+*/
+
+void
+test(void)
+{
+ PyGILState_STATE state = PyGILState_Ensure();
+
+ /* do things */
+
+ PyGILState_Release(state);
+}
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/refcounts/PyGILState_Ensure/correct/script.py b/tests/cpychecker/refcounts/PyGILState_Ensure/correct/script.py
new file mode 100644
index 0000000..fdd5ba3
--- /dev/null
+++ b/tests/cpychecker/refcounts/PyGILState_Ensure/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/PyGILState_Ensure/correct/stdout.txt b/tests/cpychecker/refcounts/PyGILState_Ensure/correct/stdout.txt
new file mode 100644
index 0000000..c251aa4
--- /dev/null
+++ b/tests/cpychecker/refcounts/PyGILState_Ensure/correct/stdout.txt
@@ -0,0 +1,7 @@
+Trace 0:
+ Transitions:
+ 'PyGILState_Ensure'
+ 'calling PyGILState_Release()'
+ 'returning'
+ Exception:
+ (struct PyObject *)0 from tests/cpychecker/refcounts/PyGILState_Ensure/correct/input.c:28
12 years, 7 months
[gcc-python-plugin] cpychecker: add __slots__ to all important classes
by David Hugh Malcolm
commit af5d44e66f74b512132e3c3c07deec1e8013c6a9
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Fri Oct 21 14:48:09 2011 -0400
cpychecker: add __slots__ to all important classes
Convert all important classes to be new-style, and add __slots__ definitions
to them.
This didn't seem to have much effect on speed and memory in my crude
benchmarking, but gains us some additional checking against e.g. typos in
attribute names, and better documents the attributes we expect.
libcpychecker/absinterp.py | 49 +++++++++++++++++++++++++++++++++++++------
libcpychecker/refcounts.py | 4 +++
2 files changed, 46 insertions(+), 7 deletions(-)
---
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index 9cbda7d..5dc4074 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -42,11 +42,13 @@ from libcpychecker.types import *
# Various kinds of r-value:
############################################################################
-class AbstractValue:
+class AbstractValue(object):
"""
Base class, representing some subset of possible values out of the full
set of values that this r-value could hold.
"""
+ __slots__ = ('gcctype', 'loc', 'fromsplit')
+
def __init__(self, gcctype, loc):
if gcctype:
check_isinstance(gcctype, gcc.Type)
@@ -307,6 +309,8 @@ class ConcreteValue(AbstractValue):
"""
A known, specific value (e.g. 0)
"""
+ __slots__ = ('value', )
+
def __init__(self, gcctype, loc, value):
check_isinstance(gcctype, gcc.Type)
if loc:
@@ -441,6 +445,8 @@ class WithinRange(AbstractValue):
"""
A value known to be within a given range e.g. -3 <= val <= +4
"""
+ __slots__ = ('minvalue', 'maxvalue', )
+
def __init__(self, gcctype, loc, *values):
"""
The constructor can take one or more values; the resulting set
@@ -647,6 +653,8 @@ class WithinRange(AbstractValue):
class PointerToRegion(AbstractValue):
"""A non-NULL pointer value, pointing at a specific Region"""
+ __slots__ = ('region', )
+
def __init__(self, gcctype, loc, region):
AbstractValue.__init__(self, gcctype, loc)
check_isinstance(region, Region)
@@ -833,9 +841,11 @@ def describe_stmt(stmt):
else:
return str(stmt.loc)
-class Location:
+class Location(object):
"""A location within a CFG: a gcc.BasicBlock together with an index into
the gimple list. (We don't support SSA passes)"""
+ __slots__ = ('bb', 'idx', )
+
def __init__(self, bb, idx):
check_isinstance(bb, gcc.BasicBlock)
check_isinstance(idx, int)
@@ -887,7 +897,9 @@ class Location:
else:
return None
-class Region:
+class Region(object):
+ __slots__ = ('name', 'parent', 'children', 'fields', )
+
def __init__(self, name, parent):
self.name = name
self.parent = parent
@@ -911,6 +923,8 @@ class RegionForGlobal(Region):
Represents the area of memory (e.g. in .data or .bss section)
used to store a particular globa
"""
+ __slots__ = ('vardecl', )
+
def __init__(self, vardecl):
check_isinstance(vardecl, (gcc.VarDecl, gcc.FunctionDecl))
Region.__init__(self, vardecl.name, None)
@@ -927,6 +941,8 @@ class RegionOnStack(Region):
return '%s on stack' % self.name
class RegionForLocal(RegionOnStack):
+ __slots__ = ('vardecl', )
+
def __init__(self, vardecl, stack):
RegionOnStack.__init__(self, 'region for %r' % vardecl, stack)
self.vardecl = vardecl
@@ -935,6 +951,8 @@ class RegionOnHeap(Region):
"""
Represents an area of memory allocated on the heap
"""
+ __slots__ = ('alloc_stmt', )
+
def __init__(self, name, alloc_stmt):
check_isinstance(alloc_stmt, gcc.Gimple)
Region.__init__(self, name, None)
@@ -952,11 +970,15 @@ class RegionForStringConstant(Region):
Represents an area of memory used for string constants
typically allocated in the .data segment
"""
+ __slots__ = ('text', )
+
def __init__(self, text):
Region.__init__(self, text, None)
self.text = text
class ArrayElementRegion(Region):
+ __slots__ = ('index', )
+
def __init__(self, name, parent, index):
Region.__init__(self, name, parent)
self.index = index
@@ -1010,7 +1032,7 @@ class SplitValue(Exception):
return result
-class Facet:
+class Facet(object):
"""
A facet of state, relating to a particular API (e.g. libc, cpython, etc)
@@ -1022,6 +1044,8 @@ class Facet:
current state to new states (e.g. success, failure, etc), creating
appropriate new States with appropriate new Facet subclass instances.
"""
+ __slots__ = ('state', )
+
def __init__(self, state):
check_isinstance(state, State)
self.state = state
@@ -1030,7 +1054,7 @@ class Facet:
# Concrete subclasses should implement this.
raise NotImplementedError
-class State:
+class State(object):
"""
A Location with memory state, and zero or more additional "facets" of
state, one per API that we care about.
@@ -1054,6 +1078,10 @@ class State:
Hopefully this will allow checking of additional APIs to be slotted into
the checker, whilst keeping each API's special-case rules isolated.
"""
+
+ # We can't use the __slots__ optimization here, as we're adding additional
+ # per-facet attributes
+
def __init__(self, fun, loc, facets, region_for_var=None, value_for_region=None,
return_rvalue=None, has_returned=False, not_returning=False):
check_isinstance(fun, gcc.Function)
@@ -2153,7 +2181,12 @@ class State:
region_id = 0
-class Transition:
+class Transition(object):
+ __slots__ = ('src', # State
+ 'dest', # State
+ 'desc', # str
+ )
+
def __init__(self, src, dest, desc):
check_isinstance(src, State)
check_isinstance(dest, State)
@@ -2171,7 +2204,9 @@ class Transition:
logger('dest:')
self.dest.log(logger)
-class Trace:
+class Trace(object):
+ __slots__ = ('states', 'transitions', 'err', )
+
"""A sequence of States and Transitions"""
def __init__(self):
self.states = []
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 9cb3867..b2677ed 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -117,6 +117,8 @@ class RefcountValue(AbstractValue):
- a newly constructed object gets (1, 0): we own a reference on it,
and we don't know if there are any external refs on it.
"""
+ __slots__ = ('relvalue', 'min_external')
+
def __init__(self, relvalue, min_external):
self.relvalue = relvalue
self.min_external = min_external
@@ -209,6 +211,8 @@ class GenericTpDealloc(AbstractValue):
return [Transition(state, s_new, desc)]
class CPython(Facet):
+ __slots__ = ('exception_rvalue', )
+
def __init__(self, state, exception_rvalue=None, fun=None):
Facet.__init__(self, state)
if exception_rvalue:
12 years, 7 months