[gcc-python-plugin] add "static" boolean property to gcc.VarDecl
by David Hugh Malcolm
commit fcc1a57b50dae09868451e229242ebe4d81198a5
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Wed Oct 12 18:30:23 2011 -0400
add "static" boolean property to gcc.VarDecl
Add a "static" boolean attribute to gcc.VarDecl, and use it in the cpychecker
to avoid falsely-reporting such locals being uninitialized data as reported in
https://fedorahosted.org/pipermail/gcc-python-plugin/2011-October/000087....
It doesn't yet attempt to track the state of such static variables across
multiple function calls
docs/tree.rst | 15 +++++++
generate-tree-c.py | 3 +
libcpychecker/absinterp.py | 12 +++++-
tests/cpychecker/absinterp/static-data/input.c | 46 +++++++++++++++++++++
tests/cpychecker/absinterp/static-data/script.py | 22 ++++++++++
tests/cpychecker/absinterp/static-data/stdout.txt | 15 +++++++
6 files changed, 112 insertions(+), 1 deletions(-)
---
diff --git a/docs/tree.rst b/docs/tree.rst
index a33339f..5be10b9 100644
--- a/docs/tree.rst
+++ b/docs/tree.rst
@@ -222,6 +222,21 @@ Declarations
The :py:class:`gcc.CallgraphNode` for this function declaration, or
`None`
+.. py:class:: gcc.VarDecl
+
+ A subclass of :py:class:`gcc.Declaration` indicating the declaration of a
+ variable (e.g. a global or a local).
+
+ .. py:attribute:: initial
+
+ The initial value for this variable as a :py:class:`gcc.Constructor`,
+ or None
+
+ .. py:attribute:: static
+
+ (boolean) Is this variable to be allocated with static storage?
+
+
.. Declaration
.. ClassMethodDecl
.. ConstDecl
diff --git a/generate-tree-c.py b/generate-tree-c.py
index 4cdaa3a..88b1d81 100644
--- a/generate-tree-c.py
+++ b/generate-tree-c.py
@@ -418,6 +418,9 @@ def generate_tree_code_classes():
add_simple_getter('initial',
'gcc_python_make_wrapper_tree(DECL_INITIAL(self->t))',
"The initial value for this variable as a gcc.Constructor, or None")
+ add_simple_getter('static',
+ 'PyBool_FromLong(TREE_STATIC(self->t))',
+ "Boolean: is this variable to be allocated with static storage")
if tree_type.SYM == 'CONSTRUCTOR':
add_complex_getter('elements',
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index 85490f5..cedd098 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -1042,7 +1042,17 @@ class State:
for local in fun.local_decls:
region = RegionForLocal(local, stack)
self.region_for_var[local] = region
- self.value_for_region[region] = UninitializedData(local.type, fun.start)
+ if local.static:
+ # Statically-allocated locals are zero-initialized before the
+ # function is called for the first time, and then preserve
+ # state between function calls
+ # For now, don't try to track all possible values a static var
+ # can take; simply treat it as an UnknownValue
+ v_local = UnknownValue(local.type, fun.start)
+ else:
+ v_local = UninitializedData(local.type, fun.start)
+ self.value_for_region[region] = v_local
+
# Region for the gcc.ResultDecl, if any:
if fun.decl.result:
result = fun.decl.result
diff --git a/tests/cpychecker/absinterp/static-data/input.c b/tests/cpychecker/absinterp/static-data/input.c
new file mode 100644
index 0000000..dd66536
--- /dev/null
+++ b/tests/cpychecker/absinterp/static-data/input.c
@@ -0,0 +1,46 @@
+/*
+ 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 handling functions with static data:
+*/
+int test(void)
+{
+ static int i;
+
+ /*
+ i is implicitly zero-initialized before the first time the function
+ is entered, and its state persists between calls to the function.
+
+ The analyzer isn't yet smart enough to determine that i is never
+ written to, and so believes that both paths are possible:
+ */
+ if (i) {
+ return 42;
+ } else {
+ return 43;
+ }
+}
+
+/*
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/absinterp/static-data/script.py b/tests/cpychecker/absinterp/static-data/script.py
new file mode 100644
index 0000000..6bb62fd
--- /dev/null
+++ b/tests/cpychecker/absinterp/static-data/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/absinterp/static-data/stdout.txt b/tests/cpychecker/absinterp/static-data/stdout.txt
new file mode 100644
index 0000000..0bb62c6
--- /dev/null
+++ b/tests/cpychecker/absinterp/static-data/stdout.txt
@@ -0,0 +1,15 @@
+Trace 0:
+ Transitions:
+ 'when taking True path'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='int', loc=gcc.Location(file='tests/cpychecker/absinterp/static-data/input.c', line=35), value=42)
+ str(): (int)42 from tests/cpychecker/absinterp/static-data/input.c:35
+
+Trace 1:
+ Transitions:
+ 'when taking False path'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='int', loc=gcc.Location(file='tests/cpychecker/absinterp/static-data/input.c', line=37), value=43)
+ str(): (int)43 from tests/cpychecker/absinterp/static-data/input.c:37
12 years, 7 months
[gcc-python-plugin] cpychecker: fixup debug code to support non-python sources
by David Hugh Malcolm
commit 34e96f8f0ade82615d53b8a2185baeca24b8acd7
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Wed Oct 12 16:49:12 2011 -0400
cpychecker: fixup debug code to support non-python sources
Fix the debug code (DebugAnnotator) to cope with non-python sources, where
State instances don't have a cpython facet
libcpychecker/refcounts.py | 9 +++++----
1 files changed, 5 insertions(+), 4 deletions(-)
---
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 7014424..fafd86d 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -1976,10 +1976,11 @@ class ExceptionStateAnnotator(Annotator):
result = []
- if transition.dest.cpython.exception_rvalue != transition.src.cpython.exception_rvalue:
- result.append(Note(loc,
- ('thread-local exception state now has value: %s'
- % transition.dest.cpython.exception_rvalue)))
+ if hasattr(transition.dest, 'cpython'):
+ if transition.dest.cpython.exception_rvalue != transition.src.cpython.exception_rvalue:
+ result.append(Note(loc,
+ ('thread-local exception state now has value: %s'
+ % transition.dest.cpython.exception_rvalue)))
return result
12 years, 7 months
[gcc-python-plugin] cpychecker: handle function pointer variables
by David Hugh Malcolm
commit b6496b518378d67067f42c15057664643943b9b1
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Wed Oct 12 16:29:26 2011 -0400
cpychecker: handle function pointer variables
Support assigning to an l-value that's a function pointer variable with an
r-value that's the name of a function.
Detect calls through uninitialized and NULL function-pointer r-values.
libcpychecker/absinterp.py | 43 ++++++++++++++-
.../cpychecker/absinterp/function-pointers/input.c | 54 ++++++++++++++++++++
.../absinterp/function-pointers/script.py | 22 ++++++++
.../absinterp/function-pointers/stderr.txt | 12 ++++
.../absinterp/function-pointers/stdout.txt | 22 ++++++++
5 files changed, 150 insertions(+), 3 deletions(-)
---
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index 429eded..85490f5 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -185,6 +185,26 @@ class ConcreteValue(AbstractValue):
if isinstance(self.gcctype, gcc.PointerType):
return self.value == 0
+ def get_transitions_for_function_call(self, state, stmt):
+ check_isinstance(state, State)
+ check_isinstance(stmt, gcc.GimpleCall)
+
+ class CallOfNullFunctionPtr(PredictedError):
+ def __init__(self, stmt, value):
+ check_isinstance(stmt, gcc.Gimple)
+ check_isinstance(value, AbstractValue)
+ self.stmt = stmt
+ self.value = value
+
+ def __str__(self):
+ return ('call of NULL function pointer at %s: %s'
+ % (self.stmt.loc, self.value))
+
+ if self.is_null_ptr():
+ raise CallOfNullFunctionPtr(stmt, self)
+
+ return AbstractValue.get_transitions_for_function_call(self, state, stmt)
+
def eval_binop(self, exprcode, rhs, gcctype, loc):
if isinstance(rhs, ConcreteValue):
if exprcode == gcc.PlusExpr:
@@ -283,6 +303,23 @@ class UninitializedData(AbstractValue):
def impl_is_ge(self, rhs):
return None
+ def get_transitions_for_function_call(self, state, stmt):
+ check_isinstance(state, State)
+ check_isinstance(stmt, gcc.GimpleCall)
+
+ class CallOfUninitializedFunctionPtr(PredictedError):
+ def __init__(self, stmt, value):
+ check_isinstance(stmt, gcc.Gimple)
+ check_isinstance(value, AbstractValue)
+ self.stmt = stmt
+ self.value = value
+
+ def __str__(self):
+ return ('call of uninitialized function pointer at %s: %s'
+ % (self.stmt.loc, self.value))
+
+ raise CallOfUninitializedFunctionPtr(stmt, self)
+
############################################################################
# Various kinds of predicted error:
############################################################################
@@ -461,7 +498,7 @@ class RegionForGlobal(Region):
used to store a particular globa
"""
def __init__(self, vardecl):
- check_isinstance(vardecl, gcc.VarDecl)
+ check_isinstance(vardecl, (gcc.VarDecl, gcc.FunctionDecl))
Region.__init__(self, vardecl.name, None)
self.vardecl = vardecl
@@ -694,7 +731,7 @@ class State:
log('eval_lvalue: %r %s', expr, expr)
if loc:
check_isinstance(loc, gcc.Location)
- if isinstance(expr, (gcc.VarDecl, gcc.ParmDecl, gcc.ResultDecl)):
+ if isinstance(expr, (gcc.VarDecl, gcc.ParmDecl, gcc.ResultDecl, gcc.FunctionDecl)):
region = self.var_region(expr)
check_isinstance(region, Region)
return region
@@ -801,7 +838,7 @@ class State:
self.value_for_region[dest_region] = value
def var_region(self, var):
- check_isinstance(var, (gcc.VarDecl, gcc.ParmDecl, gcc.ResultDecl))
+ check_isinstance(var, (gcc.VarDecl, gcc.ParmDecl, gcc.ResultDecl, gcc.FunctionDecl))
if var not in self.region_for_var:
# Presumably a reference to a global variable:
log('adding region for global var: %r', var)
diff --git a/tests/cpychecker/absinterp/function-pointers/input.c b/tests/cpychecker/absinterp/function-pointers/input.c
new file mode 100644
index 0000000..6819983
--- /dev/null
+++ b/tests/cpychecker/absinterp/function-pointers/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/>.
+*/
+
+/*
+ Test of handling function pointers
+*/
+#include <stdlib.h>
+
+int foo(int);
+
+int test(int i, int j)
+{
+ int (*fn_ptr)(int);
+
+ if (i) {
+ fn_ptr = foo;
+ } else {
+ if (j) {
+ /*
+ Check that we can detect a call through a NULL function
+ pointer:
+ */
+ fn_ptr = NULL;
+ }
+ }
+
+ /* otherwise fn_ptr is still uninitialized (which should be detected) */
+
+ return fn_ptr(i);
+}
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/absinterp/function-pointers/script.py b/tests/cpychecker/absinterp/function-pointers/script.py
new file mode 100644
index 0000000..6bb62fd
--- /dev/null
+++ b/tests/cpychecker/absinterp/function-pointers/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/absinterp/function-pointers/stderr.txt b/tests/cpychecker/absinterp/function-pointers/stderr.txt
new file mode 100644
index 0000000..ce5426e
--- /dev/null
+++ b/tests/cpychecker/absinterp/function-pointers/stderr.txt
@@ -0,0 +1,12 @@
+tests/cpychecker/absinterp/function-pointers/input.c: In function 'test':
+tests/cpychecker/absinterp/function-pointers/input.c:45:5: error: call of NULL function pointer at tests/cpychecker/absinterp/function-pointers/input.c:45: (int (*<T513>) (int))0 from tests/cpychecker/absinterp/function-pointers/input.c:39
+tests/cpychecker/absinterp/function-pointers/input.c:31:8: note: when taking False path at: if (i) {
+tests/cpychecker/absinterp/function-pointers/input.c:34:12: note: reaching: if (j) {
+tests/cpychecker/absinterp/function-pointers/input.c:34:12: note: when taking True path at: if (j) {
+tests/cpychecker/absinterp/function-pointers/input.c:39:20: note: reaching: fn_ptr = NULL;
+tests/cpychecker/absinterp/function-pointers/input.c:45:5: error: call of uninitialized function pointer at tests/cpychecker/absinterp/function-pointers/input.c:45: uninitialized data at tests/cpychecker/absinterp/function-pointers/input.c:28
+tests/cpychecker/absinterp/function-pointers/input.c:31:8: note: when taking False path at: if (i) {
+tests/cpychecker/absinterp/function-pointers/input.c:34:12: note: reaching: if (j) {
+tests/cpychecker/absinterp/function-pointers/input.c:34:12: note: when taking False path at: if (j) {
+tests/cpychecker/absinterp/function-pointers/input.c:45:5: note: reaching: return fn_ptr(i);
+tests/cpychecker/absinterp/function-pointers/input.c:28:1: note: graphical error report for function 'test' written out to 'tests/cpychecker/absinterp/function-pointers/input.c.test-refcount-errors.html'
diff --git a/tests/cpychecker/absinterp/function-pointers/stdout.txt b/tests/cpychecker/absinterp/function-pointers/stdout.txt
new file mode 100644
index 0000000..48595a7
--- /dev/null
+++ b/tests/cpychecker/absinterp/function-pointers/stdout.txt
@@ -0,0 +1,22 @@
+Trace 0:
+ Transitions:
+ 'when taking True path'
+ "calling (int (*<T513>) (int))&RegionForGlobal(gcc.FunctionDecl('foo')) from tests/cpychecker/absinterp/function-pointers/input.c:32"
+ 'returning'
+ Return value:
+ repr(): UnknownValue(gcctype=<gcc.IntegerType object at 0xdeadbeef>, loc=gcc.Location(file='tests/cpychecker/absinterp/function-pointers/input.c', line=45))
+ str(): unknown int from tests/cpychecker/absinterp/function-pointers/input.c:45
+
+Trace 1:
+ Transitions:
+ 'when taking False path'
+ 'when taking True path'
+ error: CallOfNullFunctionPtr()
+ error: call of NULL function pointer at tests/cpychecker/absinterp/function-pointers/input.c:45: (int (*<T513>) (int))0 from tests/cpychecker/absinterp/function-pointers/input.c:39
+
+Trace 2:
+ Transitions:
+ 'when taking False path'
+ 'when taking False path'
+ error: CallOfUninitializedFunctionPtr()
+ error: call of uninitialized function pointer at tests/cpychecker/absinterp/function-pointers/input.c:45: uninitialized data at tests/cpychecker/absinterp/function-pointers/input.c:28
12 years, 7 months
[gcc-python-plugin] cpychecker: support code that doesn't include Python's headers
by David Hugh Malcolm
commit 269f8f3f5237c892554b3e07398edc73470d433e
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Wed Oct 12 15:17:24 2011 -0400
cpychecker: support code that doesn't include Python's headers
Continue generalizing the State handling, so that we only use the
CPython facet to states for code that has #include-d Python's
headers
libcpychecker/refcounts.py | 16 ++++++-
.../refcounts/without-python-headers/input.c | 40 ++++++++++++++++++++
.../refcounts/without-python-headers/script.py | 22 +++++++++++
.../refcounts/without-python-headers/stdout.txt | 15 +++++++
4 files changed, 90 insertions(+), 3 deletions(-)
---
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index eb7d7bc..7014424 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -1854,8 +1854,9 @@ def dump_traces_to_stdout(traces):
dump_region(region, str(region))
# Exception state:
- print(' Exception:')
- print(' %s' % endstate.cpython.exception_rvalue)
+ if hasattr(endstate, 'cpython'):
+ print(' Exception:')
+ print(' %s' % endstate.cpython.exception_rvalue)
if i + 1 < len(traces):
sys.stdout.write('\n')
@@ -2005,6 +2006,15 @@ def check_refcounts(fun, dump_traces=False, show_traces=False,
check_isinstance(fun, gcc.Function)
+ # Generate a mapping from facet names to facet classes, so that we know
+ # what additional attributes each State instance will have.
+ #
+ # For now, we just have an optional CPython instance per state, but only
+ # for code that's included Python's headers:
+ facets = {}
+ if get_PyObject():
+ facets['cpython'] = CPython
+
if show_traces:
from libcpychecker.visualizations import StateGraphPrettyPrinter
sg = StateGraph(fun, log, MyState)
@@ -2016,7 +2026,7 @@ def check_refcounts(fun, dump_traces=False, show_traces=False,
try:
traces = iter_traces(fun,
- {'cpython':CPython},
+ facets,
limits=Limits(maxtrans=1024))
except TooComplicated:
gcc.inform(fun.start,
diff --git a/tests/cpychecker/refcounts/without-python-headers/input.c b/tests/cpychecker/refcounts/without-python-headers/input.c
new file mode 100644
index 0000000..871f58b
--- /dev/null
+++ b/tests/cpychecker/refcounts/without-python-headers/input.c
@@ -0,0 +1,40 @@
+/*
+ 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/>.
+*/
+
+/*
+ Verify that the cpychecker code can successfully run on code that doesn't
+ include Python's headers
+*/
+
+int
+test(int i, int j)
+{
+ if (i < j) {
+ return 42;
+ } else {
+ return 43;
+ }
+}
+
+/*
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/refcounts/without-python-headers/script.py b/tests/cpychecker/refcounts/without-python-headers/script.py
new file mode 100644
index 0000000..6bb62fd
--- /dev/null
+++ b/tests/cpychecker/refcounts/without-python-headers/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/without-python-headers/stdout.txt b/tests/cpychecker/refcounts/without-python-headers/stdout.txt
new file mode 100644
index 0000000..5a51faf
--- /dev/null
+++ b/tests/cpychecker/refcounts/without-python-headers/stdout.txt
@@ -0,0 +1,15 @@
+Trace 0:
+ Transitions:
+ 'when taking True path'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='int', loc=gcc.Location(file='tests/cpychecker/refcounts/without-python-headers/input.c', line=29), value=42)
+ str(): (int)42 from tests/cpychecker/refcounts/without-python-headers/input.c:29
+
+Trace 1:
+ Transitions:
+ 'when taking False path'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='int', loc=gcc.Location(file='tests/cpychecker/refcounts/without-python-headers/input.c', line=31), value=43)
+ str(): (int)43 from tests/cpychecker/refcounts/without-python-headers/input.c:31
12 years, 7 months
[gcc-python-plugin] cpychecker: initial implementation of PyObject_CallMethod
by David Hugh Malcolm
commit e434adbc1f653de41a4941580d0ccfec740a175c
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Oct 11 18:45:31 2011 -0400
cpychecker: initial implementation of PyObject_CallMethod
libcpychecker/refcounts.py | 87 +++++++++++++++++++-
.../PyObject_CallMethod/correct-code-N/input.c | 45 ++++++++++
.../PyObject_CallMethod/correct-code-N/script.py | 22 +++++
.../PyObject_CallMethod/correct-code-N/stdout.txt | 74 +++++++++++++++++
.../refcounts/PyObject_CallMethod/correct/input.c | 44 ++++++++++
.../PyObject_CallMethod/correct/script.py | 22 +++++
.../PyObject_CallMethod/correct/stdout.txt | 41 +++++++++
7 files changed, 334 insertions(+), 1 deletions(-)
---
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 81d04f7..eb7d7bc 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -30,7 +30,7 @@ from libcpychecker.diagnostics import Reporter, Annotator, Note
from libcpychecker.PyArg_ParseTuple import PyArgParseFmt, FormatStringError, \
TypeCheckCheckerType, TypeCheckResultType
from libcpychecker.Py_BuildValue import PyBuildValueFmt, ObjectFormatUnit, \
- CodeSO
+ CodeSO, CodeN
from libcpychecker.types import is_py3k, is_debug_build, get_PyObjectPtr
from libcpychecker.utils import log
@@ -278,6 +278,7 @@ class CPython(Facet):
newvalue = fn(oldvalue)
log('newvalue: %r', newvalue)
self.state.value_for_region[ob_refcnt] = newvalue
+ return newvalue
def add_ref(self, pyobjectptr, loc):
"""
@@ -303,6 +304,20 @@ class CPython(Facet):
loc,
_incref_external)
+ def dec_ref(self, pyobjectptr, loc):
+ """
+ Remove a "visible" reference to pyobjectptr's ob_refcnt i.e. a
+ reference being held by a PyObject* that we are directly tracking.
+ """
+ def _decref_internal(oldvalue):
+ return RefcountValue(oldvalue.relvalue - 1,
+ oldvalue.min_external)
+ check_isinstance(pyobjectptr, PointerToRegion)
+ v_ob_refcnt = self.change_refcount(pyobjectptr,
+ loc,
+ _decref_internal)
+ # FIXME: potentially this destroys the object
+
def set_exception(self, exc_name, loc):
"""
Given the name of a (PyObject*) global for an exception class, such as
@@ -1302,6 +1317,76 @@ class CPython(Facet):
########################################################################
# PyObject_*
########################################################################
+ def _handle_PyObject_CallMethod(self, stmt, v_o, v_name, v_fmt, v_varargs, with_size_t):
+ check_isinstance(v_o, AbstractValue)
+ check_isinstance(v_name, AbstractValue)
+ check_isinstance(v_fmt, AbstractValue)
+ check_isinstance(v_varargs, tuple) # of AbstractValue
+ check_isinstance(with_size_t, bool)
+
+ # The function can succeed or fail
+ # If any of the PyObject* inputs are NULL, it is doomed to failure
+ def _handle_successful_parse(fmt):
+ """
+ Returns a boolean: is success of the function possible?
+ """
+ exptypes = fmt.iter_exp_types()
+ for v_vararg, (unit, exptype) in zip(v_varargs, exptypes):
+ if 0:
+ print('v_vararg: %r' % v_vararg)
+ print(' unit: %r' % unit)
+ print(' exptype: %r %s' % (exptype, exptype))
+ if isinstance(unit, ObjectFormatUnit):
+ # NULL inputs ptrs guarantee failure:
+ if isinstance(v_vararg, ConcreteValue):
+ if v_vararg.is_null_ptr():
+ # The call will fail:
+ return False
+
+ # non-NULL input ptrs receive "external" references on
+ # success for codes "S" and "O", but code "N" steals a
+ # reference for the args. The args are then decref-ed
+ # by the call. Hence args with code "N" lose a ref:
+ if isinstance(v_vararg, PointerToRegion):
+ if isinstance(unit, CodeN):
+ t_success.dest.cpython.dec_ref(v_vararg, stmt.loc)
+ t_failure.dest.cpython.dec_ref(v_vararg, stmt.loc)
+ return True
+
+ t_success, t_failure = self.make_transitions_for_new_ref_or_fail(stmt)
+
+ fmt_string = _get_format_string(v_fmt)
+ if fmt_string:
+ try:
+ fmt = PyBuildValueFmt.from_string(fmt_string, with_size_t)
+ if not _handle_successful_parse(fmt):
+ return [t_failure]
+ except FormatStringError:
+ pass
+
+ return [t_success, t_failure]
+
+ def impl_PyObject_CallMethod(self, stmt, v_o, v_name, v_format, *args):
+ # http://docs.python.org/c-api/object.html#PyObject_CallMethod
+ #
+ # Implemented in Objects/abstract.c:
+ # PyObject *
+ # PyObject_CallMethod(PyObject *o, char *name, char *format, ...)
+ return self._handle_PyObject_CallMethod(stmt, v_o, v_name, v_format,
+ args, with_size_t=False)
+
+ def _PyObject_CallMethod_SizeT(self, stmt, v_o, v_name, v_format, *args):
+ # abstract.h has:
+ # #ifdef PY_SSIZE_T_CLEAN
+ # #define PyObject_CallMethod _PyObject_CallMethod_SizeT
+ # #endif
+ #
+ # Implemented in Objects/abstract.c:
+ # PyObject *
+ # _PyObject_CallMethod_SizeT(PyObject *o, char *name, char *format, ...)
+ return self._handle_PyObject_CallMethod(stmt, v_o, v_name, v_format,
+ args, with_size_t=True)
+
def impl_PyObject_HasAttrString(self, stmt, v_o, v_attr_name):
# http://docs.python.org/c-api/object.html#PyObject_HasAttrString
diff --git a/tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c b/tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c
new file mode 100644
index 0000000..8ca63c5
--- /dev/null
+++ b/tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c
@@ -0,0 +1,45 @@
+/*
+ Copyright 2011 David Malcolm <dmalcolm(a)redhat.com>
+ Copyright 2011 Red Hat, Inc.
+
+ This is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+*/
+
+#include <Python.h>
+
+/*
+ Test of correct reference-handling in a call to PyObject_CallMethod
+ with the "N" format unit
+*/
+
+PyObject *
+test(PyObject *self, PyObject *args)
+{
+ return PyObject_CallMethod(self, "method_name",
+ "(s, s, s, N)",
+ "fee", "fi", "fo", PyLong_FromLong(0x1000));
+}
+static PyMethodDef test_methods[] = {
+ {"test_method", test, METH_VARARGS, NULL},
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/script.py b/tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/script.py
new file mode 100644
index 0000000..fdd5ba3
--- /dev/null
+++ b/tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/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/PyObject_CallMethod/correct-code-N/stdout.txt b/tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/stdout.txt
new file mode 100644
index 0000000..0ee09e9
--- /dev/null
+++ b/tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/stdout.txt
@@ -0,0 +1,74 @@
+Trace 0:
+ Transitions:
+ 'when PyLong_FromLong() succeeds'
+ 'when PyObject_CallMethod() succeeds'
+ 'returning'
+ Return value:
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c', line=30), region=RegionOnHeap('new ref from call to PyObject_CallMethod', gcc.Location(file='tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c', line=30)))
+ str(): (struct PyObject *)&RegionOnHeap('new ref from call to PyObject_CallMethod', gcc.Location(file='tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c', line=30)) from tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c:30
+ r->ob_refcnt: refs: 1 + N where N >= 0
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c', line=30), region=Region('PyTypeObject for new ref from call to PyObject_CallMethod'))
+ 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/PyObject_CallMethod/correct-code-N/input.c', line=28), 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/PyObject_CallMethod/correct-code-N/input.c', line=28), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ PyLongObject allocated at tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c:30:
+ repr(): RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c', line=30))
+ str(): PyLongObject allocated at tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c:30
+ r->ob_refcnt: refs: 0 + N where N >= 0
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c', line=30), region=RegionForGlobal(gcc.VarDecl('PyLong_Type')))
+ Exception:
+ (struct PyObject *)0 from tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c:29
+
+Trace 1:
+ Transitions:
+ 'when PyLong_FromLong() succeeds'
+ 'when PyObject_CallMethod() fails'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c', line=30), value=0)
+ str(): (struct PyObject *)0 from tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c:30
+ 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/PyObject_CallMethod/correct-code-N/input.c', line=28), 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/PyObject_CallMethod/correct-code-N/input.c', line=28), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ PyLongObject allocated at tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c:30:
+ repr(): RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c', line=30))
+ str(): PyLongObject allocated at tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c:30
+ r->ob_refcnt: refs: 0 + N where N >= 0
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c', line=30), region=RegionForGlobal(gcc.VarDecl('PyLong_Type')))
+ Exception:
+ (struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) from tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c:30
+
+Trace 2:
+ Transitions:
+ 'when PyLong_FromLong() fails'
+ 'when PyObject_CallMethod() fails'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c', line=30), value=0)
+ str(): (struct PyObject *)0 from tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c:30
+ 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/PyObject_CallMethod/correct-code-N/input.c', line=28), 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/PyObject_CallMethod/correct-code-N/input.c', line=28), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ Exception:
+ (struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) from tests/cpychecker/refcounts/PyObject_CallMethod/correct-code-N/input.c:30
diff --git a/tests/cpychecker/refcounts/PyObject_CallMethod/correct/input.c b/tests/cpychecker/refcounts/PyObject_CallMethod/correct/input.c
new file mode 100644
index 0000000..4168135
--- /dev/null
+++ b/tests/cpychecker/refcounts/PyObject_CallMethod/correct/input.c
@@ -0,0 +1,44 @@
+/*
+ Copyright 2011 David Malcolm <dmalcolm(a)redhat.com>
+ Copyright 2011 Red Hat, Inc.
+
+ This is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+*/
+
+#include <Python.h>
+
+/*
+ Test of correct reference-handling in a call to PyObject_CallMethod
+*/
+
+PyObject *
+test(PyObject *self, PyObject *args)
+{
+ return PyObject_CallMethod(self, "method_name",
+ "(s, s, s, s)",
+ "fee", "fi", "fo", "fum");
+}
+static PyMethodDef test_methods[] = {
+ {"test_method", test, METH_VARARGS, NULL},
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/refcounts/PyObject_CallMethod/correct/script.py b/tests/cpychecker/refcounts/PyObject_CallMethod/correct/script.py
new file mode 100644
index 0000000..fdd5ba3
--- /dev/null
+++ b/tests/cpychecker/refcounts/PyObject_CallMethod/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/PyObject_CallMethod/correct/stdout.txt b/tests/cpychecker/refcounts/PyObject_CallMethod/correct/stdout.txt
new file mode 100644
index 0000000..3bb3119
--- /dev/null
+++ b/tests/cpychecker/refcounts/PyObject_CallMethod/correct/stdout.txt
@@ -0,0 +1,41 @@
+Trace 0:
+ Transitions:
+ 'when PyObject_CallMethod() succeeds'
+ 'returning'
+ Return value:
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyObject_CallMethod/correct/input.c', line=29), region=RegionOnHeap('new ref from call to PyObject_CallMethod', gcc.Location(file='tests/cpychecker/refcounts/PyObject_CallMethod/correct/input.c', line=29)))
+ str(): (struct PyObject *)&RegionOnHeap('new ref from call to PyObject_CallMethod', gcc.Location(file='tests/cpychecker/refcounts/PyObject_CallMethod/correct/input.c', line=29)) from tests/cpychecker/refcounts/PyObject_CallMethod/correct/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/PyObject_CallMethod/correct/input.c', line=29), region=Region('PyTypeObject for new ref from call to PyObject_CallMethod'))
+ 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/PyObject_CallMethod/correct/input.c', line=27), region=Region("region-for-type-of-arg-gcc.ParmDecl('self')"))
+ Region("region-for-arg-gcc.ParmDecl('args')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('args')")
+ str(): Region("region-for-arg-gcc.ParmDecl('args')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyObject_CallMethod/correct/input.c', line=27), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ Exception:
+ (struct PyObject *)0 from tests/cpychecker/refcounts/PyObject_CallMethod/correct/input.c:28
+
+Trace 1:
+ Transitions:
+ 'when PyObject_CallMethod() fails'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyObject_CallMethod/correct/input.c', line=29), value=0)
+ str(): (struct PyObject *)0 from tests/cpychecker/refcounts/PyObject_CallMethod/correct/input.c:29
+ 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/PyObject_CallMethod/correct/input.c', line=27), region=Region("region-for-type-of-arg-gcc.ParmDecl('self')"))
+ Region("region-for-arg-gcc.ParmDecl('args')"):
+ repr(): Region("region-for-arg-gcc.ParmDecl('args')")
+ str(): Region("region-for-arg-gcc.ParmDecl('args')")
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/PyObject_CallMethod/correct/input.c', line=27), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ Exception:
+ (struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) from tests/cpychecker/refcounts/PyObject_CallMethod/correct/input.c:29
12 years, 7 months
[gcc-python-plugin] assume that gcc's plugin headers don't flatten the c-family directory
by David Hugh Malcolm
commit 215730cbec40a6fe482fabb7f1ecc3d747f1b5d2
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Oct 11 16:20:53 2011 -0400
assume that gcc's plugin headers don't flatten the c-family directory
Older builds of GCC flattened the "c-family" directory in the installed
plugin headers.
This was fixed in this GCC commit:
http://gcc.gnu.org/viewcvs?view=revision&revision=176741
This commit updates gcc-python-plugin usage of those headers to reflect the
changes in that gcc commit.
(see also https://bugzilla.redhat.com/show_bug.cgi?id=728011 )
gcc-python-option.c | 2 +-
generate-tree-c.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
---
diff --git a/gcc-python-option.c b/gcc-python-option.c
index 58b158c..31a58d0 100644
--- a/gcc-python-option.c
+++ b/gcc-python-option.c
@@ -20,7 +20,7 @@
#include <Python.h>
#include "gcc-python.h"
#include "gcc-python-wrappers.h"
-#include "c-common.h" /* for warn_format */
+#include "c-family/c-common.h" /* for warn_format */
#include "diagnostic.h"
diff --git a/generate-tree-c.py b/generate-tree-c.py
index 34287c6..4cdaa3a 100644
--- a/generate-tree-c.py
+++ b/generate-tree-c.py
@@ -30,7 +30,7 @@ cu.add_include('gcc-plugin.h')
cu.add_include("tree.h")
cu.add_include("function.h")
cu.add_include("basic-block.h")
-cu.add_include("c-common.h")
+cu.add_include("c-family/c-common.h")
modinit_preinit = ''
modinit_postinit = ''
12 years, 7 months
[gcc-python-plugin] improvements to gcc.GimpleCond
by David Hugh Malcolm
commit f9de31e4cf18aa1a9ee827fb61127d3710aac512
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Mon Oct 10 15:32:04 2011 -0400
improvements to gcc.GimpleCond
Fix documentation of gcc.GimpleCond, and add attributes (true_label and false_label)
docs/gimple.rst | 41 ++++++++++++++---
generate-gimple-c.py | 8 +++
.../plugin/gimple-cond/explicit-comparison/input.c | 41 +++++++++++++++++
.../gimple-cond/explicit-comparison/script.py | 48 ++++++++++++++++++++
.../gimple-cond/explicit-comparison/stdout.txt | 26 +++++++++++
.../plugin/gimple-cond/implicit-comparison/input.c | 41 +++++++++++++++++
.../gimple-cond/implicit-comparison/script.py | 48 ++++++++++++++++++++
.../gimple-cond/implicit-comparison/stdout.txt | 26 +++++++++++
8 files changed, 272 insertions(+), 7 deletions(-)
---
diff --git a/docs/gimple.rst b/docs/gimple.rst
index e0c8ae5..354e373 100644
--- a/docs/gimple.rst
+++ b/docs/gimple.rst
@@ -142,21 +142,48 @@ TODO
.. py:class:: gcc.GimpleCond
- Subclass of gcc.Gimple: an "if" statement, signifying the end of a `gcc.BasicBlock`
+ Subclass of gcc.Gimple: a conditional jump, of the form::
+
+ if (LHS EXPRCODE RHS) goto TRUE_LABEL else goto FALSE_LABEL
.. py:attribute:: lhs
- Left-hand-side of the assignment, as a gcc.Tree
+ Left-hand-side of the comparison, as a gcc.Tree
+
+ .. py:attribute:: exprcode
+
+ The comparison predicate, as a :py:class:`gcc.Comparison` subclass (the
+ type itself, not an instance). For example, the gcc.GimpleCond statement
+ for this fragment of C code::
+
+ if (a == b)
+
+ would have stmt.exprcode == gcc.EqExpr
.. py:attribute:: rhs
- The operands on the right-hand-side of the expression, as a list of
- gcc.Tree instances
+ The right-hand-side of the comparison, as a gcc.Tree
- .. py:attribute:: exprcode
+ .. py:attribute:: true_label
- The kind of the expression, as an gcc.Tree subclass (the type itself, not
- an instance)
+ The :py:class:`gcc.LabelDecl` node used as the jump target for when the
+ comparison is true
+
+ .. py:attribute:: false_label
+
+ The :py:class:`gcc.LabelDecl` node used as the jump target for when the
+ comparison is false
+
+ Note that a C conditional of the form::
+
+ if (some_int) {suiteA} else {suiteB}
+
+ is implicitly expanded to::
+
+ if (some_int != 0) {suiteA} else {suiteB}
+
+ and this becomes a gcc.GimpleCond with `lhs` as the integer, `exprcode` as
+ `<type 'gcc.NeExpr'>`, and `rhs` as `gcc.IntegerCst(0)`.
.. py:class:: gcc.GimplePhi
diff --git a/generate-gimple-c.py b/generate-gimple-c.py
index 960915b..3a82e67 100644
--- a/generate-gimple-c.py
+++ b/generate-gimple-c.py
@@ -325,6 +325,14 @@ def generate_gimple_subclasses():
'rhs',
'gcc_python_make_wrapper_tree(gimple_cond_rhs(self->stmt))',
None)
+ getsettable.add_simple_getter(cu,
+ 'true_label',
+ 'gcc_python_make_wrapper_tree(gimple_cond_true_label(self->stmt))',
+ None)
+ getsettable.add_simple_getter(cu,
+ 'false_label',
+ 'gcc_python_make_wrapper_tree(gimple_cond_false_label(self->stmt))',
+ None)
return getsettable
def make_getset_Phi():
diff --git a/tests/plugin/gimple-cond/explicit-comparison/input.c b/tests/plugin/gimple-cond/explicit-comparison/input.c
new file mode 100644
index 0000000..512401c
--- /dev/null
+++ b/tests/plugin/gimple-cond/explicit-comparison/input.c
@@ -0,0 +1,41 @@
+/*
+ Copyright 2011 David Malcolm <dmalcolm(a)redhat.com>
+ Copyright 2011 Red Hat, Inc.
+
+ This is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ Trivial example code to be compiled, for testing purposes
+ */
+
+int
+helper_function(int a, int b)
+{
+ /* This should lead to a GimpleCond statement: */
+ if (a < b) {
+ return 42;
+ } else {
+ return a - b;
+ }
+}
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/plugin/gimple-cond/explicit-comparison/script.py b/tests/plugin/gimple-cond/explicit-comparison/script.py
new file mode 100644
index 0000000..90bab40
--- /dev/null
+++ b/tests/plugin/gimple-cond/explicit-comparison/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/>.
+
+# Verify examining details of functions
+
+import gcc
+from gccutils import pprint
+
+def on_pass_execution(p, fn):
+ if p.name in '*warn_function_return':
+ assert isinstance(fn, gcc.Function)
+ print('fn: %r' % fn)
+
+ assert isinstance(fn.decl, gcc.FunctionDecl)
+ print('fn.decl.name: %r' % fn.decl.name)
+
+ if fn.cfg: # None for some early passes
+ print('CFG:')
+ for bb in fn.cfg.basic_blocks:
+ print(' BLOCK %i' % bb.index)
+ if bb.gimple:
+ for i,stmt in enumerate(bb.gimple):
+ print(' gimple[%i]:' % i)
+ print(' str(stmt): %r' % str(stmt))
+ print(' repr(stmt): %r' % repr(stmt))
+ if isinstance(stmt, gcc.GimpleCond):
+ print(' stmt.lhs: %r' % stmt.lhs)
+ print(' stmt.exprcode: %r' % stmt.exprcode)
+ print(' stmt.rhs: %r' % stmt.rhs)
+ print(' stmt.true_label: %r' % stmt.true_label)
+ print(' stmt.false_label: %r' % stmt.false_label)
+
+gcc.register_callback(gcc.PLUGIN_PASS_EXECUTION,
+ on_pass_execution)
diff --git a/tests/plugin/gimple-cond/explicit-comparison/stdout.txt b/tests/plugin/gimple-cond/explicit-comparison/stdout.txt
new file mode 100644
index 0000000..f00151a
--- /dev/null
+++ b/tests/plugin/gimple-cond/explicit-comparison/stdout.txt
@@ -0,0 +1,26 @@
+fn: gcc.Function('helper_function')
+fn.decl.name: 'helper_function'
+CFG:
+ BLOCK 0
+ BLOCK 1
+ BLOCK 2
+ gimple[0]:
+ str(stmt): 'if (a < b)'
+ repr(stmt): 'gcc.GimpleCond()'
+ stmt.lhs: gcc.ParmDecl('a')
+ stmt.exprcode: <type 'gcc.LtExpr'>
+ stmt.rhs: gcc.ParmDecl('b')
+ stmt.true_label: None
+ stmt.false_label: None
+ BLOCK 3
+ gimple[0]:
+ str(stmt): 'D.nnnnn = 42;'
+ repr(stmt): 'gcc.GimpleAssign()'
+ BLOCK 4
+ gimple[0]:
+ str(stmt): 'D.nnnnn = a - b;'
+ repr(stmt): 'gcc.GimpleAssign()'
+ BLOCK 5
+ gimple[0]:
+ str(stmt): 'return D.nnnnn;'
+ repr(stmt): 'gcc.GimpleReturn()'
diff --git a/tests/plugin/gimple-cond/implicit-comparison/input.c b/tests/plugin/gimple-cond/implicit-comparison/input.c
new file mode 100644
index 0000000..7361f37
--- /dev/null
+++ b/tests/plugin/gimple-cond/implicit-comparison/input.c
@@ -0,0 +1,41 @@
+/*
+ Copyright 2011 David Malcolm <dmalcolm(a)redhat.com>
+ Copyright 2011 Red Hat, Inc.
+
+ This is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ Trivial example code to be compiled, for testing purposes
+ */
+
+int
+helper_function(int a, int b)
+{
+ /* This should lead to a GimpleCond statement: */
+ if (a) {
+ return 42;
+ } else {
+ return b;
+ }
+}
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/plugin/gimple-cond/implicit-comparison/script.py b/tests/plugin/gimple-cond/implicit-comparison/script.py
new file mode 100644
index 0000000..90bab40
--- /dev/null
+++ b/tests/plugin/gimple-cond/implicit-comparison/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/>.
+
+# Verify examining details of functions
+
+import gcc
+from gccutils import pprint
+
+def on_pass_execution(p, fn):
+ if p.name in '*warn_function_return':
+ assert isinstance(fn, gcc.Function)
+ print('fn: %r' % fn)
+
+ assert isinstance(fn.decl, gcc.FunctionDecl)
+ print('fn.decl.name: %r' % fn.decl.name)
+
+ if fn.cfg: # None for some early passes
+ print('CFG:')
+ for bb in fn.cfg.basic_blocks:
+ print(' BLOCK %i' % bb.index)
+ if bb.gimple:
+ for i,stmt in enumerate(bb.gimple):
+ print(' gimple[%i]:' % i)
+ print(' str(stmt): %r' % str(stmt))
+ print(' repr(stmt): %r' % repr(stmt))
+ if isinstance(stmt, gcc.GimpleCond):
+ print(' stmt.lhs: %r' % stmt.lhs)
+ print(' stmt.exprcode: %r' % stmt.exprcode)
+ print(' stmt.rhs: %r' % stmt.rhs)
+ print(' stmt.true_label: %r' % stmt.true_label)
+ print(' stmt.false_label: %r' % stmt.false_label)
+
+gcc.register_callback(gcc.PLUGIN_PASS_EXECUTION,
+ on_pass_execution)
diff --git a/tests/plugin/gimple-cond/implicit-comparison/stdout.txt b/tests/plugin/gimple-cond/implicit-comparison/stdout.txt
new file mode 100644
index 0000000..548f158
--- /dev/null
+++ b/tests/plugin/gimple-cond/implicit-comparison/stdout.txt
@@ -0,0 +1,26 @@
+fn: gcc.Function('helper_function')
+fn.decl.name: 'helper_function'
+CFG:
+ BLOCK 0
+ BLOCK 1
+ BLOCK 2
+ gimple[0]:
+ str(stmt): 'if (a != 0)'
+ repr(stmt): 'gcc.GimpleCond()'
+ stmt.lhs: gcc.ParmDecl('a')
+ stmt.exprcode: <type 'gcc.NeExpr'>
+ stmt.rhs: gcc.IntegerCst(0)
+ stmt.true_label: None
+ stmt.false_label: None
+ BLOCK 3
+ gimple[0]:
+ str(stmt): 'D.nnnnn = 42;'
+ repr(stmt): 'gcc.GimpleAssign()'
+ BLOCK 4
+ gimple[0]:
+ str(stmt): 'D.nnnnn = b;'
+ repr(stmt): 'gcc.GimpleAssign()'
+ BLOCK 5
+ gimple[0]:
+ str(stmt): 'return D.nnnnn;'
+ repr(stmt): 'gcc.GimpleReturn()'
12 years, 7 months
[gcc-python-plugin] cpychecker: remove C code fragment
by David Hugh Malcolm
commit bbf974cfda0de891993790c7c5ba71293df738f7
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Fri Oct 7 18:59:35 2011 -0400
cpychecker: remove C code fragment
libcpychecker/Py_BuildValue.py | 33 ---------------------------------
1 files changed, 0 insertions(+), 33 deletions(-)
---
diff --git a/libcpychecker/Py_BuildValue.py b/libcpychecker/Py_BuildValue.py
index 4a1ceda..a56939e 100644
--- a/libcpychecker/Py_BuildValue.py
+++ b/libcpychecker/Py_BuildValue.py
@@ -76,39 +76,6 @@ def _type_of_simple_arg(arg):
elif arg == 'K':
return get_PY_LONG_LONG().unsigned_equivalent
- """
- case 'u':
- {
- PyObject *v;
- Py_UNICODE *u = va_arg(*p_va, Py_UNICODE *);
- Py_ssize_t n;
- if (**p_format == '#') {
- ++*p_format;
- if (flags & FLAG_SIZE_T)
- n = va_arg(*p_va, Py_ssize_t);
- else
- n = va_arg(*p_va, int);
- }
- else
- n = -1;
- if (u == NULL) {
- v = Py_None;
- Py_INCREF(v);
- }
- else {
- if (n < 0)
- n = _ustrlen(u);
- v = PyUnicode_FromUnicode(u, n);
- }
- return v;
- }
- """
-
- #case 'f':
- #case 'd':
- #case 'D':
- #case 'c':
-
class ObjectFormatUnit(FormatUnit):
"""
Base class for Py_BuildValue format codes that expect a PyObject*
12 years, 8 months
[gcc-python-plugin] cpychecker: implement more of Py_BuildValue
by David Hugh Malcolm
commit d7da6aadaecdeae394fc82ff02de40f64434367c
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Fri Oct 7 18:52:47 2011 -0400
cpychecker: implement more of Py_BuildValue
libcpychecker/Py_BuildValue.py | 46 +++++++++++++++++++------------
libcpychecker/formatstrings.py | 2 +-
testcpychecker.py | 57 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 86 insertions(+), 19 deletions(-)
---
diff --git a/libcpychecker/Py_BuildValue.py b/libcpychecker/Py_BuildValue.py
index c04ebe0..4a1ceda 100644
--- a/libcpychecker/Py_BuildValue.py
+++ b/libcpychecker/Py_BuildValue.py
@@ -18,7 +18,7 @@
# Domain-specific warning:
# Detecting errors in usage of the Py_BuildValue API
#
-# See http://docs.python.org/c-api/arg.html
+# See http://docs.python.org/c-api/arg.html#Py_BuildValue
#
# FIXME:
# Note that all of the "#" codes are affected by the presence of the
@@ -39,6 +39,7 @@
# http://hg.python.org/cpython/rev/5d4a5655575f/
import gcc
+import sys
from libcpychecker.formatstrings import *
from libcpychecker.types import *
@@ -50,28 +51,31 @@ def _type_of_simple_arg(arg):
# Analogous to Python/modsupport.c:do_mkvalue, this is the same order as
# that function's "switch" statement:
simple = {
- # FIXME: all of these just use "int":
- 'b': gcc.Type.unsigned_char,
+ # all of these actually just use "int":
+ 'b': gcc.Type.char,
'B': gcc.Type.unsigned_char,
'h': gcc.Type.short,
'i': gcc.Type.int,
-
- # FIXME: code just uses an unsigned int:
- # 'H': gcc.Type.unsigned_short_int,
-
- # FIXME:
- # 'I': gcc.Type.unsigned_int,
- # 'n'
- # 'l'
- # 'k'
- # 'L'
- # 'K'
+ 'H': gcc.Type.unsigned_short,
+ 'I': gcc.Type.unsigned_int,
+ # 'n' covered below
+ 'l': gcc.Type.long,
+ 'k': gcc.Type.unsigned_long,
+ # 'L' covered below
+ # 'K' covered below
}
if arg in simple:
# FIXME: ideally this shouldn't need calling; it should just be an
# attribute:
return simple[arg]()
+ if arg == 'n':
+ return get_Py_ssize_t()
+ elif arg == 'L':
+ return get_PY_LONG_LONG()
+ elif arg == 'K':
+ return get_PY_LONG_LONG().unsigned_equivalent
+
"""
case 'u':
{
@@ -151,6 +155,11 @@ class PyBuildValueFmt(ParsedFormatString):
ParsedFormatString.__init__(self, fmt_string)
self.arg_stack = [self.args]
+ def num_expected(self):
+ from pprint import pformat
+ #sys.stderr.write('%s\n' % pformat(list(self.iter_exp_types())))
+ return len(list(self.iter_exp_types()))
+
def iter_exp_types(self):
"""
Yield a sequence of (FormatUnit, gcc.Type) pairs, representing
@@ -203,14 +212,15 @@ class PyBuildValueFmt(ParsedFormatString):
next = fmt_string[i]
else:
next = None
- # print '(%r, %r)' % (c, None)
+ #sys.stderr.write('(%r, %r)\n' % (c, None))
+ # Analogous to Python/modsupport.c:do_mkvalue, this is in the same
+ # order as that function's "switch" statement:
simple_type = _type_of_simple_arg(c)
if simple_type:
- result.add_argument(c, [simple_type.pointer])
+ result.add_argument(c, [simple_type])
+ continue
- # Analogous to Python/modsupport.c:do_mkvalue, this is in the same
- # order as that function's "switch" statement:
if c == '(':
result._do_open_compound_fmt('(', ')')
continue
diff --git a/libcpychecker/formatstrings.py b/libcpychecker/formatstrings.py
index e5844b4..c94c60c 100644
--- a/libcpychecker/formatstrings.py
+++ b/libcpychecker/formatstrings.py
@@ -355,7 +355,7 @@ def check_pyargs(fun):
# args[1]: char * format
# args[2...]: output pointers
- if len(stmt.args) > 1:
+ if len(stmt.args) >= format_idx:
fmt_string = get_format_string(stmt, format_idx)
if fmt_string:
log('fmt_string: %r', fmt_string)
diff --git a/testcpychecker.py b/testcpychecker.py
index 33c4c7e..0dfa1da 100644
--- a/testcpychecker.py
+++ b/testcpychecker.py
@@ -474,5 +474,62 @@ class PyArg_ParseTupleAndKeywordsTests(PyArg_ParseTupleTests):
'}\n') % locals()
return src, function_name
+class Py_BuildValueTests(AnalyzerTests):
+ def test_bogus_format_string(self):
+ src = ('PyObject *\n'
+ 'bogus_format_string(PyObject *self, PyObject *args)\n'
+ '{\n'
+ ' return Py_BuildValue("this is not a valid format string");\n'
+ '}\n')
+ experr = ("$(SRCFILE): In function 'bogus_format_string':\n"
+ '$(SRCFILE):12:5: error: unknown format char in "this is not a valid format string": \'t\' [-fpermissive]\n')
+ self.assertFindsError(src, experr)
+
+ def test_not_enough_varargs(self):
+ src = """
+PyObject *
+not_enough_varargs(PyObject *self, PyObject *args)
+{
+ return Py_BuildValue("i");
+}
+"""
+ self.assertFindsError(src,
+ "$(SRCFILE): In function 'not_enough_varargs':\n"
+ '$(SRCFILE):13:5: error: Not enough arguments in call to Py_BuildValue with format string "i"\n'
+ ' expected 1 extra arguments:\n'
+ ' "int"\n'
+ ' but got none\n'
+ ' [-fpermissive]\n')
+
+ def test_too_many_varargs(self):
+ src = """
+PyObject *
+too_many_varargs(PyObject *self, PyObject *args)
+{
+ return Py_BuildValue("i", 0, 1);
+}
+"""
+ self.assertFindsError(src,
+ "$(SRCFILE): In function 'too_many_varargs':\n"
+ '$(SRCFILE):13:5: error: Too many arguments in call to Py_BuildValue with format string "i"\n'
+ ' expected 1 extra arguments:\n'
+ ' "int"\n'
+ ' but got 2:\n'
+ ' "int"\n'
+ ' "int"\n'
+ ' [-fpermissive]\n')
+
+ def test_correct_usage(self):
+ src = """
+PyObject *
+correct_usage(PyObject *self, PyObject *args)
+{
+ return Py_BuildValue("ii", 0, 1);
+}
+"""
+ self.assertNoErrors(src)
+
+
+
if __name__ == '__main__':
unittest.main()
12 years, 8 months
[gcc-python-plugin] cpychecker: add missing file
by David Hugh Malcolm
commit f3dbae3a7a42da8faa34d81b96accf100fd80187
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Fri Oct 7 15:43:36 2011 -0400
cpychecker: add missing file
libcpychecker/Py_BuildValue.py | 263 ++++++++++++++++++++++++++++++++++++++++
1 files changed, 263 insertions(+), 0 deletions(-)
---
diff --git a/libcpychecker/Py_BuildValue.py b/libcpychecker/Py_BuildValue.py
new file mode 100644
index 0000000..c04ebe0
--- /dev/null
+++ b/libcpychecker/Py_BuildValue.py
@@ -0,0 +1,263 @@
+# 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/>.
+
+# Domain-specific warning:
+# Detecting errors in usage of the Py_BuildValue API
+#
+# See http://docs.python.org/c-api/arg.html
+#
+# FIXME:
+# Note that all of the "#" codes are affected by the presence of the
+# macro PY_SSIZE_T_CLEAN. If the macro was defined before including Python.h,
+# the various lengths for these format codes are of C type "Py_ssize_t" rather
+# than "int".
+#
+# This behavior was clarified in the Python 3 version of the C API
+# documentation[1], though the Python 2 version of the API docs leave which
+# codes are affected somewhat ambiguoues.
+#
+# Nevertheless, the API _does_ work this way in Python 2: all format codes
+# with a "#" do work this way.
+#
+# You can see the implementation of the API in CPython's Python/getargs.c
+#
+# [1] The relevant commit to the CPython docs was:
+# http://hg.python.org/cpython/rev/5d4a5655575f/
+
+import gcc
+
+from libcpychecker.formatstrings import *
+from libcpychecker.types import *
+from libcpychecker.utils import log
+
+def _type_of_simple_arg(arg):
+ # Convert 1-character argument code to a gcc.Type, covering the easy cases
+ #
+ # Analogous to Python/modsupport.c:do_mkvalue, this is the same order as
+ # that function's "switch" statement:
+ simple = {
+ # FIXME: all of these just use "int":
+ 'b': gcc.Type.unsigned_char,
+ 'B': gcc.Type.unsigned_char,
+ 'h': gcc.Type.short,
+ 'i': gcc.Type.int,
+
+ # FIXME: code just uses an unsigned int:
+ # 'H': gcc.Type.unsigned_short_int,
+
+ # FIXME:
+ # 'I': gcc.Type.unsigned_int,
+ # 'n'
+ # 'l'
+ # 'k'
+ # 'L'
+ # 'K'
+ }
+ if arg in simple:
+ # FIXME: ideally this shouldn't need calling; it should just be an
+ # attribute:
+ return simple[arg]()
+
+ """
+ case 'u':
+ {
+ PyObject *v;
+ Py_UNICODE *u = va_arg(*p_va, Py_UNICODE *);
+ Py_ssize_t n;
+ if (**p_format == '#') {
+ ++*p_format;
+ if (flags & FLAG_SIZE_T)
+ n = va_arg(*p_va, Py_ssize_t);
+ else
+ n = va_arg(*p_va, int);
+ }
+ else
+ n = -1;
+ if (u == NULL) {
+ v = Py_None;
+ Py_INCREF(v);
+ }
+ else {
+ if (n < 0)
+ n = _ustrlen(u);
+ v = PyUnicode_FromUnicode(u, n);
+ }
+ return v;
+ }
+ """
+
+ #case 'f':
+ #case 'd':
+ #case 'D':
+ #case 'c':
+
+class ObjectFormatUnit(FormatUnit):
+ """
+ Base class for Py_BuildValue format codes that expect a PyObject*
+ """
+ def get_expected_types(self):
+ return [get_PyObject().pointer]
+
+class CodeSO(ObjectFormatUnit):
+ """
+ Corresponds to Py_BuildValue format codes "S" and "O"
+ """
+ pass
+
+class CodeN(ObjectFormatUnit):
+ """
+ Corresponds to Py_BuildValue format code "N"
+ """
+ pass
+
+
+class CompoundFmt:
+ def __init__(self, opench, closech):
+ self.opench = opench
+ self.closech = closech
+ self.args = []
+
+ def __repr__(self):
+ return 'CompoundFmt(%r, %r, %r)' % (self.opench, self.args, self.closech)
+
+ def append(self, item):
+ self.args.append(item)
+
+ def iter_exp_types(self):
+ for arg in self.args:
+ if isinstance(arg, CompoundFmt):
+ for inner in arg.iter_exp_types():
+ yield inner
+ else:
+ for exp_type in arg.get_expected_types():
+ yield (arg, exp_type)
+
+class PyBuildValueFmt(ParsedFormatString):
+ def __init__(self, fmt_string):
+ ParsedFormatString.__init__(self, fmt_string)
+ self.arg_stack = [self.args]
+
+ def iter_exp_types(self):
+ """
+ Yield a sequence of (FormatUnit, gcc.Type) pairs, representing
+ the expected types of the varargs
+ """
+ for arg in self.args:
+ if isinstance(arg, CompoundFmt):
+ for inner in arg.iter_exp_types():
+ yield inner
+ else:
+ for exp_type in arg.get_expected_types():
+ yield (arg, exp_type)
+
+ def add_argument(self, code, expected_types):
+ self.arg_stack[-1].append(ConcreteUnit(code, expected_types))
+
+ def add_complex_argument(self, arg):
+ self.arg_stack[-1].append(arg)
+
+ def _do_open_compound_fmt(self, opench, closech):
+ """
+ Analogous to Python/modsupport.c:do_mktuple
+ """
+ # Store tuples using lists, so that they are mutable during
+ # construction:
+ new = CompoundFmt(opench, closech)
+ self.arg_stack[-1].append(new)
+ self.arg_stack.append(new)
+
+ def _do_close_compound_fmt(self, closech):
+ if len(self.arg_stack) < 1:
+ raise MismatchedParentheses(self.fmt_string)
+ cf = self.arg_stack.pop()
+ if cf.closech != closech:
+ raise MismatchedParentheses(self.fmt_string)
+
+ @classmethod
+ def from_string(cls, fmt_string, with_size_t):
+ """
+ Parse fmt_string, generating a PyBuildValue instance
+ Compare to Python/modsupport.c:va_build_value
+ FIXME: only implements a subset of the various cases
+ """
+ result = PyBuildValueFmt(fmt_string)
+ i = 0
+ while i < len(fmt_string):
+ c = fmt_string[i]
+ i += 1
+ if i < len(fmt_string):
+ next = fmt_string[i]
+ else:
+ next = None
+ # print '(%r, %r)' % (c, None)
+
+ simple_type = _type_of_simple_arg(c)
+ if simple_type:
+ result.add_argument(c, [simple_type.pointer])
+
+ # Analogous to Python/modsupport.c:do_mkvalue, this is in the same
+ # order as that function's "switch" statement:
+ if c == '(':
+ result._do_open_compound_fmt('(', ')')
+ continue
+ if c == '[':
+ result._do_open_compound_fmt('[', ']')
+ continue
+ if c == '{':
+ result._do_open_compound_fmt('{', '}')
+ continue
+
+ if c in ')]}':
+ result._do_close_compound_fmt(c)
+ continue
+
+ if c in 'sz':
+ if next == '#':
+ result.add_argument(c + '#',
+ [get_const_char_ptr_ptr(),
+ get_hash_size_type(with_size_t).pointer])
+ i += 1
+ else:
+ result.add_argument(c,
+ [get_const_char_ptr_ptr()])
+ continue
+
+ if c in 'NSO':
+ if next == '&':
+ result.add_complex_argument()
+ i += 1
+ else:
+ if c == 'N':
+ result.add_complex_argument(CodeN(c))
+ else:
+ result.add_complex_argument(CodeSO(c))
+ continue
+
+ if c in ":, \t":
+ continue
+
+ raise UnknownFormatChar(fmt_string, c)
+
+ if len(result.arg_stack) > 1:
+ # Not all parentheses were closed:
+ raise MismatchedParentheses(self.fmt_string)
+
+ #print result
+ #from pprint import pprint
+ #pprint(result.args)
+
+ return result
12 years, 8 months