[gcc-python-plugin] Detect reference leaks for values that aren't the return value
by David Hugh Malcolm
commit dc9ee927b831e7b53f3b416d15c627a80b65e33a
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Jul 12 19:45:28 2011 -0400
Detect reference leaks for values that aren't the return value
Introduce a ConcreteValue subclass of AbstractValue, and eliminate the
NullPtrValue class, so that we can treat "NULL" in C code in the same way
that we deal with simulated function-call failures
Track the location of AbstractValue, rather than the stmt, in the hope that
this will be available in more places.
Rewrite the ob_refcnt tracker so that it investigates all ob_refcnt fields
we know something about at the end of each trace, rather than just that of the
return value.
Add a new test case:
tests/cpychecker/refcounts/object_leak
to verify detection of leaking an object reference within a function.
libcpychecker/absinterp.py | 53 ++++++++++++-------
libcpychecker/refcounts.py | 48 +++++++++++++-----
.../refcounts/correct_object_ctor/stdout.txt | 2 +-
tests/cpychecker/refcounts/object_leak/input.c | 34 +++++++++++++
tests/cpychecker/refcounts/object_leak/script.py | 47 +++++++++++++++++
tests/cpychecker/refcounts/object_leak/stderr.txt | 4 ++
tests/cpychecker/refcounts/object_leak/stdout.txt | 4 ++
7 files changed, 157 insertions(+), 35 deletions(-)
---
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index ec7c018..cb1ed5a 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -23,15 +23,42 @@ from collections import OrderedDict
from PyArg_ParseTuple import log
class AbstractValue:
- def __init__(self, gcctype, stmt):
+ def __init__(self, gcctype, loc):
+ assert isinstance(gcctype, gcc.Type)
+ if loc:
+ assert isinstance(loc, gcc.Location)
self.gcctype = gcctype
- self.stmt = stmt
+ self.loc = loc
+
+ def __str__(self):
+ if self.loc:
+ return '%s from %s' % (self.gcctype, self.loc)
+ else:
+ return '%' % self.gcctype
+
+ def __repr__(self):
+ return 'AbstractValue(gcctype=%r, loc=%r)' % (str(self.gcctype), self.loc)
+
+class ConcreteValue(AbstractValue):
+ """
+ A known, specific value (e.g. 0)
+ """
+ def __init__(self, gcctype, loc, value):
+ assert isinstance(gcctype, gcc.Type)
+ if loc:
+ assert isinstance(loc, gcc.Location)
+ self.gcctype = gcctype
+ self.loc = loc
+ self.value = value
def __str__(self):
- return '%s from %s' % (self.gcctype, self.stmt)
+ if self.loc:
+ return '(%s)%r from %s' % (self.gcctype, self.value, self.loc)
+ else:
+ return '(%s)%r' % (self.gcctype, self.value)
def __repr__(self):
- return 'AbstractValue(%r, %r)' % (self.gcctype, self.stmt)
+ return 'ConcreteValue(gcctype=%r, loc=%r, value=%r)' % (str(self.gcctype), self.loc, self.value)
class PredictedError(Exception):
pass
@@ -72,20 +99,6 @@ class PtrValue(AbstractValue):
def __init__(self, nonnull):
self.nonnull = nonnull
-class NullPtrValue(PtrValue):
- def __init__(self, stmt=None):
- PtrValue.__init__(self, False)
- self.stmt = stmt
-
- def __str__(self):
- if self.stmt:
- return 'NULL value from %s' % get_src_for_loc(self.stmt.loc)
- else:
- return 'NULL'
-
- def __repr__(self):
- return 'NullPtrValue()'
-
def describe_stmt(stmt):
if isinstance(stmt, gcc.GimpleCall):
if isinstance(stmt.fn.operand, gcc.FunctionDecl):
@@ -252,7 +265,7 @@ class State:
if isinstance(expr, Region):
return expr
if isinstance(expr, gcc.IntegerCst):
- return expr.constant
+ return ConcreteValue(expr.type, None, expr.constant)
if isinstance(expr, gcc.VarDecl):
region = self.var_region(expr)
value = self.get_store(region)
@@ -316,7 +329,7 @@ class State:
log('foo')
if isinstance(cr.target, gcc.MemRef):
ptr = self.eval_expr(cr.target.operand)
- if isinstance(ptr, NullPtrValue):
+ if isinstance(ptr, ConcreteValue) and ptr.value == 0:
raise NullPtrDereference(self, cr)
return self.make_field_region(ptr, cr.field.name)
elif isinstance(cr.target, gcc.VarDecl):
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index ee6e80a..f512200 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -153,7 +153,7 @@ class MyState(State):
success,
'%s() succeeds' % fnname)
failure = self.make_assignment(stmt.lhs,
- NullPtrValue(),
+ ConcreteValue(stmt.lhs.type, stmt.loc, 0),
'%s() fails' % fnname)
return [success, failure]
# Function returning borrowed references:
@@ -224,8 +224,8 @@ class MyState(State):
if stmt.exprcode == gcc.EqExpr:
if isinstance(lhs, NonNullPtrValue) and rhs == 0:
return False
- if isinstance(lhs, NullPtrValue) and rhs == 0:
- return True
+ if isinstance(lhs, ConcreteValue):
+ return lhs.value == rhs
log('got here')
return UnknownValue(None, stmt)
@@ -236,8 +236,8 @@ class MyState(State):
b = self.eval_expr(rhs[1])
log('a: %r' % a)
log('b: %r' % b)
- if isinstance(a, RefcountValue) and isinstance(b, long):
- return RefcountValue(a.relvalue + b)
+ if isinstance(a, RefcountValue) and isinstance(b, ConcreteValue):
+ return RefcountValue(a.relvalue + b.value)
raise 'bar'
elif stmt.exprcode == gcc.ComponentRef:
return self.eval_expr(rhs[0])
@@ -349,29 +349,49 @@ def check_refcounts(fun, show_traces):
log('return_value: %r' % return_value, 0)
log('endstate.region_for_var: %r' % endstate.region_for_var, 0)
log('endstate.value_for_region: %r' % endstate.value_for_region, 0)
- if 1: # return_value is not NULL
- ob_refcnt = endstate.get_value_of_field_by_region(return_value,
+
+ # Consider all regions of memory we know about:
+ for k in endstate.region_for_var:
+ if not isinstance(endstate.region_for_var[k], Region):
+ continue
+ region = endstate.region_for_var[k]
+
+ log('considering ob_refcnt of %r' % region)
+
+ # Consider those for which we know something about an "ob_refcnt"
+ # field:
+ if 'ob_refcnt' not in region.fields:
+ continue
+
+ ob_refcnt = endstate.get_value_of_field_by_region(region,
'ob_refcnt')
log('ob_refcnt: %r' % ob_refcnt, 0)
+
+ # If it's the return value, it should have a net refcnt delta of
+ # 1; all other PyObject should have a net delta of 0:
+ if region == return_value:
+ desc = 'return value'
+ exp_refcnt = 1
+ else:
+ desc = 'PyObject'
+ exp_refcnt = 0
+ log('exp_refcnt: %r' % exp_refcnt, 0)
if isinstance(ob_refcnt, RefcountValue):
- if ob_refcnt.relvalue > 1:
+ if ob_refcnt.relvalue > exp_refcnt:
# too high
gcc.error(endstate.get_gcc_loc(),
- 'ob_refcnt of return value is %i too high' % (ob_refcnt.relvalue - 1))
+ 'ob_refcnt of %s is %i too high' % (desc, ob_refcnt.relvalue - exp_refcnt))
describe_trace(trace)
- elif ob_refcnt.relvalue < 1:
+ elif ob_refcnt.relvalue < exp_refcnt:
# too low
gcc.error(endstate.get_gcc_loc(),
- 'ob_refcnt of return value is %i too low' % (1 - ob_refcnt.relvalue))
+ 'ob_refcnt of %s is %i too low' % (desc, exp_refcnt - ob_refcnt.relvalue))
describe_trace(trace)
if isinstance(return_value, RegionForGlobal):
if return_value.vardecl.name == '_Py_NoneStruct':
gcc.inform(endstate.get_gcc_loc(),
'consider using "Py_RETURN_NONE;"')
- #for k in endstate.region_for_var:
- # log(k)
-
final_refs = trace.final_references()
if return_value in final_refs:
final_refs.remove(return_value)
diff --git a/tests/cpychecker/refcounts/correct_object_ctor/stdout.txt b/tests/cpychecker/refcounts/correct_object_ctor/stdout.txt
index 73e1d1b..36697c0 100644
--- a/tests/cpychecker/refcounts/correct_object_ctor/stdout.txt
+++ b/tests/cpychecker/refcounts/correct_object_ctor/stdout.txt
@@ -2,4 +2,4 @@ Trace 0:
returned: Region('heap_region_0')
r->ob_refcnt: RefcountValue(1)
Trace 1:
- returned: NullPtrValue()
+ returned: ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/correct_object_ctor/input.c', line=30), value=0)
diff --git a/tests/cpychecker/refcounts/object_leak/input.c b/tests/cpychecker/refcounts/object_leak/input.c
new file mode 100644
index 0000000..fad289b
--- /dev/null
+++ b/tests/cpychecker/refcounts/object_leak/input.c
@@ -0,0 +1,34 @@
+/*
+ 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>
+
+PyObject *
+object_leak(PyObject *self, PyObject *args)
+{
+ /* Buggy code: create an object: */
+ PyObject *tmp = PyLong_FromLong(0x1000);
+
+ /* Then throw it away, leaking the object (assuming the call succeeds) */
+ return NULL;
+}
+static PyMethodDef test_methods[] = {
+ {"test_method", object_leak, METH_VARARGS, NULL},
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
diff --git a/tests/cpychecker/refcounts/object_leak/script.py b/tests/cpychecker/refcounts/object_leak/script.py
new file mode 100644
index 0000000..c70a11e
--- /dev/null
+++ b/tests/cpychecker/refcounts/object_leak/script.py
@@ -0,0 +1,47 @@
+# -*- 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/>.
+
+import gcc
+from libcpychecker import main, get_traces
+
+def verify_traces(optpass, fun):
+ # Only run in one pass
+ # FIXME: should we be adding our own pass for this?
+ if optpass.name == '*warn_function_return':
+ if fun:
+ traces = get_traces(fun)
+
+ # We should have two traces
+ # print('traces: %r' % traces)
+ assert len(traces) == 2
+
+ # Verify the "success" trace:
+ state = traces[0].states[-1]
+ print('Trace 0:')
+ print(' returned: %r' % state.return_rvalue)
+
+ # Verify the "fail" trace:
+ state = traces[1].states[-1]
+ print('Trace 1:')
+ print(' returned: %r' % state.return_rvalue)
+
+gcc.register_callback(gcc.PLUGIN_PASS_EXECUTION,
+ verify_traces)
+
+main(verify_refcounting=True,
+ show_traces=False)
diff --git a/tests/cpychecker/refcounts/object_leak/stderr.txt b/tests/cpychecker/refcounts/object_leak/stderr.txt
new file mode 100644
index 0000000..d5ecd19
--- /dev/null
+++ b/tests/cpychecker/refcounts/object_leak/stderr.txt
@@ -0,0 +1,4 @@
+tests/cpychecker/refcounts/object_leak/input.c: In function ‘object_leak’:
+tests/cpychecker/refcounts/object_leak/input.c:29:5: error: ob_refcnt of PyObject is 1 too high
+tests/cpychecker/refcounts/object_leak/input.c:26:15: note: when PyLong_FromLong() succeeds
+tests/cpychecker/refcounts/object_leak/input.c:29:5: note: when returning
diff --git a/tests/cpychecker/refcounts/object_leak/stdout.txt b/tests/cpychecker/refcounts/object_leak/stdout.txt
new file mode 100644
index 0000000..b8dd984
--- /dev/null
+++ b/tests/cpychecker/refcounts/object_leak/stdout.txt
@@ -0,0 +1,4 @@
+Trace 0:
+ returned: ConcreteValue(gcctype='struct PyObject *', loc=None, value=0L)
+Trace 1:
+ returned: ConcreteValue(gcctype='struct PyObject *', loc=None, value=0L)
12 years, 10 months
[gcc-python-plugin] Suggest the use of Py_RETURN_NONE when detecting _Py_NoneStruct.ob_refcnt is too low
by David Hugh Malcolm
commit 357a860786849fb55bbfe646b67a48541beb101f
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Jul 12 18:14:43 2011 -0400
Suggest the use of Py_RETURN_NONE when detecting _Py_NoneStruct.ob_refcnt is too low
Introduce RegionForGlobal subclass of Region to better track manipulation of
global variables
libcpychecker/absinterp.py | 15 ++++++++++++++-
libcpychecker/refcounts.py | 7 ++++---
.../refcounts/correct_py_none/stdout.txt | 2 +-
.../refcounts/incorrect_py_none/stderr.txt | 1 +
.../refcounts/incorrect_py_none/stdout.txt | 2 +-
5 files changed, 21 insertions(+), 6 deletions(-)
---
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index 46bc21e..ec7c018 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -161,6 +161,19 @@ class Region:
def __repr__(self):
return 'Region(%r)' % self.name
+class RegionForGlobal(Region):
+ """
+ Represents the area of memory (e.g. in .data or .bss section)
+ used to store a particular globa
+ """
+ def __init__(self, vardecl):
+ assert isinstance(vardecl, gcc.VarDecl)
+ Region.__init__(self, vardecl.name, None)
+ self.vardecl = vardecl
+
+ def __repr__(self):
+ return 'RegionForGlobal(%r)' % self.vardecl
+
# Used for making unique IDs:
num_heap_regions = 0
@@ -280,7 +293,7 @@ class State:
if var not in self.region_for_var:
# Presumably a reference to a global variable:
log('adding region for global var: %r' % var)
- region = Region(var.name, None)
+ region = RegionForGlobal(var)
# it is its own region:
self.region_for_var[var] = region
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 9d728a6..ee6e80a 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -364,6 +364,10 @@ def check_refcounts(fun, show_traces):
gcc.error(endstate.get_gcc_loc(),
'ob_refcnt of return value is %i too low' % (1 - ob_refcnt.relvalue))
describe_trace(trace)
+ if isinstance(return_value, RegionForGlobal):
+ if return_value.vardecl.name == '_Py_NoneStruct':
+ gcc.inform(endstate.get_gcc_loc(),
+ 'consider using "Py_RETURN_NONE;"')
#for k in endstate.region_for_var:
# log(k)
@@ -377,9 +381,6 @@ def check_refcounts(fun, show_traces):
('return of PyObject* (%s)'
' without Py_INCREF()'
% return_value))
- if str(return_value) == '&_Py_NoneStruct':
- extra_text('%s: suggest use of "Py_RETURN_NONE;"'
- % trace.get_last_stmt().loc, 1)
# Anything remaining is a leak:
if len(final_refs) > 0:
diff --git a/tests/cpychecker/refcounts/correct_py_none/stdout.txt b/tests/cpychecker/refcounts/correct_py_none/stdout.txt
index 9761507..eb2f3ef 100644
--- a/tests/cpychecker/refcounts/correct_py_none/stdout.txt
+++ b/tests/cpychecker/refcounts/correct_py_none/stdout.txt
@@ -1,2 +1,2 @@
_Py_NoneStruct.ob_refcnt: RefcountValue(1)
-state.return_rvalue: Region('_Py_NoneStruct')
+state.return_rvalue: RegionForGlobal(gcc.VarDecl('_Py_NoneStruct'))
diff --git a/tests/cpychecker/refcounts/incorrect_py_none/stderr.txt b/tests/cpychecker/refcounts/incorrect_py_none/stderr.txt
index 2ca03e6..d484e79 100644
--- a/tests/cpychecker/refcounts/incorrect_py_none/stderr.txt
+++ b/tests/cpychecker/refcounts/incorrect_py_none/stderr.txt
@@ -1,3 +1,4 @@
tests/cpychecker/refcounts/incorrect_py_none/input.c: In function ‘losing_refcnt_of_none’:
tests/cpychecker/refcounts/incorrect_py_none/input.c:26:5: error: ob_refcnt of return value is 1 too low
tests/cpychecker/refcounts/incorrect_py_none/input.c:26:5: note: when returning
+tests/cpychecker/refcounts/incorrect_py_none/input.c:26:5: note: consider using "Py_RETURN_NONE;"
diff --git a/tests/cpychecker/refcounts/incorrect_py_none/stdout.txt b/tests/cpychecker/refcounts/incorrect_py_none/stdout.txt
index cbdf4a4..2dc6bcc 100644
--- a/tests/cpychecker/refcounts/incorrect_py_none/stdout.txt
+++ b/tests/cpychecker/refcounts/incorrect_py_none/stdout.txt
@@ -1,2 +1,2 @@
_Py_NoneStruct.ob_refcnt: RefcountValue(0)
-state.return_rvalue: Region('_Py_NoneStruct')
+state.return_rvalue: RegionForGlobal(gcc.VarDecl('_Py_NoneStruct'))
12 years, 10 months
[gcc-python-plugin] Fix lookup of ob_refcnt on PyObject* for instances that were never modified
by David Hugh Malcolm
commit ce23d9e9b14606f47388390bd6e984b6cc488537
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Jul 12 17:42:59 2011 -0400
Fix lookup of ob_refcnt on PyObject* for instances that were never modified
Move test_incorrect_py_none from testcpychecker into a new:
tests/cpychecker/refcounts/incorrect_py_none
adding more detailed verification of the final state
libcpychecker/absinterp.py | 14 ++++----
libcpychecker/refcounts.py | 13 +++----
testcpychecker.py | 9 -----
.../cpychecker/refcounts/incorrect_py_none/input.c | 31 ++++++++++++++++
.../refcounts/incorrect_py_none/script.py | 39 ++++++++++++++++++++
.../refcounts/incorrect_py_none/stderr.txt | 3 ++
.../refcounts/incorrect_py_none/stdout.txt | 2 +
.../refcounts/too_many_increfs/stdout.txt | 2 +-
8 files changed, 89 insertions(+), 24 deletions(-)
---
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index 8975cc0..46bc21e 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -279,7 +279,7 @@ class State:
assert isinstance(var, gcc.VarDecl)
if var not in self.region_for_var:
# Presumably a reference to a global variable:
- log('adding region for global var %r' % var.name)
+ log('adding region for global var: %r' % var)
region = Region(var.name, None)
# it is its own region:
self.region_for_var[var] = region
@@ -345,6 +345,7 @@ class State:
def get_value_of_field_by_varname(self, varname, field):
# Lookup varname.field
# For use in writing selftests
+ log('get_value_of_field_by_varname(%r, %r)' % (varname, field), 0)
assert isinstance(varname, str)
assert isinstance(field, str)
for k in self.region_for_var:
@@ -358,14 +359,13 @@ class State:
def get_value_of_field_by_region(self, rvalue, field):
# Lookup rvalue->field
# For use in writing selftests
+ log('get_value_of_field_by_region(%r, %r)' % (rvalue, field), 0)
assert isinstance(rvalue, Region)
assert isinstance(field, str)
- for k in self.region_for_var:
- if isinstance(k, Region):
- if k == rvalue:
- region = self.make_field_region(rvalue, field)
- value = self.value_for_region.get(region, None)
- return value
+ if field in rvalue.fields:
+ field_region = rvalue.fields[field]
+ return self.value_for_region.get(field_region, None)
+ return None
def init_for_function(self, fun):
log('init_for_function(%r)' % fun)
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 9e55985..9d728a6 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -296,14 +296,13 @@ def check_refcounts(fun, show_traces):
log('check_refcounts(%r, %r)' % (fun, show_traces))
- sg = StateGraph(fun, log, MyState)
-
if show_traces:
from libcpychecker.visualizations import StateGraphPrettyPrinter
+ sg = StateGraph(fun, log, MyState)
sgpp = StateGraphPrettyPrinter(sg)
dot = sgpp.to_dot()
#dot = sgpp.extra_items()
- print(dot)
+ # print(dot)
invoke_dot(dot)
traces = iter_traces(fun, MyState)
@@ -347,13 +346,13 @@ def check_refcounts(fun, show_traces):
# Locate all PyObject that we touched
endstate = trace.states[-1]
endstate.log(log, 0)
- # print('return_value: %r' % return_value)
- # print('endstate.region_for_var: %r' % endstate.region_for_var)
- # print('endstate.value_for_region: %r' % endstate.value_for_region)
+ log('return_value: %r' % return_value, 0)
+ log('endstate.region_for_var: %r' % endstate.region_for_var, 0)
+ log('endstate.value_for_region: %r' % endstate.value_for_region, 0)
if 1: # return_value is not NULL
ob_refcnt = endstate.get_value_of_field_by_region(return_value,
'ob_refcnt')
- # print('ob_refcnt: %r' % ob_refcnt)
+ log('ob_refcnt: %r' % ob_refcnt, 0)
if isinstance(ob_refcnt, RefcountValue):
if ob_refcnt.relvalue > 1:
# too high
diff --git a/testcpychecker.py b/testcpychecker.py
index 4fcd8d1..fc6693d 100644
--- a/testcpychecker.py
+++ b/testcpychecker.py
@@ -512,15 +512,6 @@ class RefcountErrorTests(AnalyzerTests):
' $(SRCFILE):21: returning 0\n')
self.assertFindsError(sm, experr)
- def test_incorrect_py_none(self):
- sm = self.make_mod_method('losing_refcnt_of_none',
- ' /* Bug: this code is missing a Py_INCREF on Py_None */\n'
- ' return Py_None;\n')
- experr = ('$(SRCFILE): In function ‘losing_refcnt_of_none’:\n'
- '$(SRCFILE):20:5: error: return of PyObject* (&_Py_NoneStruct) without Py_INCREF() [-fpermissive]\n'
- ' $(SRCFILE):20: suggest use of "Py_RETURN_NONE;"\n')
- self.assertFindsError(sm, experr)
-
# Test disabled for now: we can't easily import this under gcc anymore:
class TestArgParsing: # (unittest.TestCase):
def assert_args(self, arg_str, exp_result):
diff --git a/tests/cpychecker/refcounts/incorrect_py_none/input.c b/tests/cpychecker/refcounts/incorrect_py_none/input.c
new file mode 100644
index 0000000..cbe8811
--- /dev/null
+++ b/tests/cpychecker/refcounts/incorrect_py_none/input.c
@@ -0,0 +1,31 @@
+/*
+ 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>
+
+PyObject *
+losing_refcnt_of_none(PyObject *self, PyObject *args)
+{
+ /* Bug: this code is missing a Py_INCREF on Py_None */
+ return Py_None;
+}
+static PyMethodDef test_methods[] = {
+ {"test_method", losing_refcnt_of_none, METH_VARARGS, NULL},
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
diff --git a/tests/cpychecker/refcounts/incorrect_py_none/script.py b/tests/cpychecker/refcounts/incorrect_py_none/script.py
new file mode 100644
index 0000000..c31bb6e
--- /dev/null
+++ b/tests/cpychecker/refcounts/incorrect_py_none/script.py
@@ -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/>.
+
+import gcc
+from libcpychecker import main, get_traces
+
+def verify_traces(optpass, fun):
+ # Only run in one pass
+ # FIXME: should we be adding our own pass for this?
+ if optpass.name == '*warn_function_return':
+ if fun:
+ traces = get_traces(fun)
+
+ # We should have a single trace
+ #print('traces: %r' % traces)
+ assert len(traces) == 1
+ state = traces[0].states[-1]
+ print('_Py_NoneStruct.ob_refcnt: %r'
+ % state.get_value_of_field_by_varname('_Py_NoneStruct', 'ob_refcnt'))
+ print('state.return_rvalue: %r' % state.return_rvalue)
+
+gcc.register_callback(gcc.PLUGIN_PASS_EXECUTION,
+ verify_traces)
+
+main(verify_refcounting=True)
diff --git a/tests/cpychecker/refcounts/incorrect_py_none/stderr.txt b/tests/cpychecker/refcounts/incorrect_py_none/stderr.txt
new file mode 100644
index 0000000..2ca03e6
--- /dev/null
+++ b/tests/cpychecker/refcounts/incorrect_py_none/stderr.txt
@@ -0,0 +1,3 @@
+tests/cpychecker/refcounts/incorrect_py_none/input.c: In function ‘losing_refcnt_of_none’:
+tests/cpychecker/refcounts/incorrect_py_none/input.c:26:5: error: ob_refcnt of return value is 1 too low
+tests/cpychecker/refcounts/incorrect_py_none/input.c:26:5: note: when returning
diff --git a/tests/cpychecker/refcounts/incorrect_py_none/stdout.txt b/tests/cpychecker/refcounts/incorrect_py_none/stdout.txt
new file mode 100644
index 0000000..cbdf4a4
--- /dev/null
+++ b/tests/cpychecker/refcounts/incorrect_py_none/stdout.txt
@@ -0,0 +1,2 @@
+_Py_NoneStruct.ob_refcnt: RefcountValue(0)
+state.return_rvalue: Region('_Py_NoneStruct')
diff --git a/tests/cpychecker/refcounts/too_many_increfs/stdout.txt b/tests/cpychecker/refcounts/too_many_increfs/stdout.txt
index 4490c69..75d8063 100644
--- a/tests/cpychecker/refcounts/too_many_increfs/stdout.txt
+++ b/tests/cpychecker/refcounts/too_many_increfs/stdout.txt
@@ -1,5 +1,5 @@
Trace 0:
- returned: Region('heap_region_2')
+ returned: Region('heap_region_1')
r->ob_refcnt: RefcountValue(2)
Trace 1:
error: NullPtrDereference()
12 years, 10 months
[gcc-python-plugin] Highlight new and changing values in State Graph visualizations
by David Hugh Malcolm
commit 0a332a45338016d66ee7ad1078215c7cbbd8f433
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Jul 12 16:16:42 2011 -0400
Highlight new and changing values in State Graph visualizations
libcpychecker/absinterp.py | 8 ++++++++
libcpychecker/visualizations.py | 39 +++++++++++++++++++++++++++++++--------
2 files changed, 39 insertions(+), 8 deletions(-)
---
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index fba8fa5..8975cc0 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -602,6 +602,14 @@ class StateGraph:
# We're at a terminating state:
logger('FINISHED TRACE')
+ def get_prev_state(self, state):
+ assert state in self.states
+ for t in self.transitions:
+ if t.dest == state:
+ return t.src
+ # Not found:
+ return None
+
def extra_text(msg, indent):
sys.stderr.write('%s%s\n' % (' ' * indent, msg))
diff --git a/libcpychecker/visualizations.py b/libcpychecker/visualizations.py
index 10f5685..f44f6f4 100644
--- a/libcpychecker/visualizations.py
+++ b/libcpychecker/visualizations.py
@@ -70,7 +70,7 @@ class StateGraphPrettyPrinter(StatePrettyPrinter):
def state_id(self, state):
return 'state%i' % id(state)
- def state_to_dot_label(self, state):
+ def state_to_dot_label(self, state, prevstate):
result = '<table cellborder="0" border="0" cellspacing="0">\n'
# Show data:
@@ -82,16 +82,39 @@ class StateGraphPrettyPrinter(StatePrettyPrinter):
for key in state.region_for_var:
region = state.region_for_var[key]
value = state.value_for_region.get(region, None)
+
+ # Highlight new and changing values:
+ is_new_key = True
+ is_different_value = False
+ if prevstate:
+ if key in prevstate.region_for_var:
+ is_new_key = False
+ prevregion = prevstate.region_for_var[key]
+ prevvalue = prevstate.value_for_region.get(prevregion, None)
+ if value != prevvalue:
+ is_different_value = True
+
+ if is_new_key:
+ bgcolor = 'green'
+ value_bgcolor = 'green'
+ else:
+ bgcolor = None
+ if is_different_value:
+ value_bgcolor = 'green'
+ else:
+ value_bgcolor = None
+
result += ('<tr> %s %s %s</tr>\n'
- % (self._dot_td(key),
- self._dot_td(region),
- self._dot_td(value)))
+ % (self._dot_td(key, bgcolor=bgcolor),
+ self._dot_td(region, bgcolor=bgcolor),
+ self._dot_td(value, bgcolor=value_bgcolor)))
+
# Show any return value:
if state.return_rvalue:
result += ('<tr> %s %s %s</tr>\n'
% (self._dot_td(''),
- self._dot_td('Return Value'),
- self._dot_td(state.return_rvalue)))
+ self._dot_td('Return Value', bgcolor='green'),
+ self._dot_td(state.return_rvalue, bgcolor='green')))
result += '</table></td></tr>'
# Show location:
@@ -116,10 +139,10 @@ class StateGraphPrettyPrinter(StatePrettyPrinter):
result += ' label="State Transitions";\n'
result += ' node [shape=box];\n'
for state in self.sg.states:
-
+ prevstate = self.sg.get_prev_state(state)
result += (' %s [label=<%s>];\n'
% (self.state_id(state),
- self.state_to_dot_label(state)))
+ self.state_to_dot_label(state, prevstate)))
#result += (' %s -> %s:stmt%i;\n'
# % (self.state_id(state),
12 years, 10 months
[gcc-python-plugin] Report erroneous refcounts returned from functions, and errors
by David Hugh Malcolm
commit 713c2fac72a3ac703c06b6d44c4272a3f076c0b2
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Jul 12 00:07:48 2011 -0400
Report erroneous refcounts returned from functions, and errors
Convert "test_too_many_increfs" from testcpychecker into a more detailed
test case as "tests/cpychecker/refcounts/too_many_increfs"
libcpychecker/absinterp.py | 45 ++++++++++++------
libcpychecker/refcounts.py | 35 ++++++++++++--
testcpychecker.py | 12 -----
.../cpychecker/refcounts/correct_py_none/script.py | 1 +
.../refcounts/correct_py_none/stdout.txt | 1 +
.../cpychecker/refcounts/too_many_increfs/input.c | 37 ++++++++++++++
.../refcounts/too_many_increfs/script.py | 51 ++++++++++++++++++++
.../refcounts/too_many_increfs/stderr.txt | 6 ++
.../refcounts/too_many_increfs/stdout.txt | 7 +++
9 files changed, 164 insertions(+), 31 deletions(-)
---
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index 9be99e5..fba8fa5 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -50,7 +50,15 @@ class InvalidlyNullParameter(PredictedError):
class NullPtrDereference(PredictedError):
- pass
+ def __init__(self, state, cr):
+ assert isinstance(state, State)
+ assert isinstance(cr, gcc.ComponentRef)
+ self.state = state
+ self.cr = cr
+
+ def __str__(self):
+ return ('dereferencing NULL (%s) at %s'
+ % (self.cr, self.state.loc.get_stmt().loc))
class UnknownValue(AbstractValue):
def __str__(self):
@@ -242,14 +250,11 @@ class State:
region = self.get_field_region(expr)#.target, expr.field.name)
value = self.get_store(region)
return value
- if 0: # isinstance(expr, gcc.AddrExpr):
- #log(dir(expr))
- #log(expr.operand)
- # Handle Py_None, which is a #define of (&_Py_NoneStruct)
+ if isinstance(expr, gcc.AddrExpr):
+ log(expr.operand)
if isinstance(expr.operand, gcc.VarDecl):
- if expr.operand.name == '_Py_NoneStruct':
- # FIXME: do we need a stmt?
- return PtrToGlobal(1, None, expr.operand.name)
+ region = self.var_region(expr.operand)
+ return region
if expr is None:
return None
return UnknownValue(expr.type, None) # FIXME
@@ -274,7 +279,7 @@ class State:
assert isinstance(var, gcc.VarDecl)
if var not in self.region_for_var:
# Presumably a reference to a global variable:
- log('adding region for global var')
+ log('adding region for global var %r' % var.name)
region = Region(var.name, None)
# it is its own region:
self.region_for_var[var] = region
@@ -299,7 +304,7 @@ class State:
if isinstance(cr.target, gcc.MemRef):
ptr = self.eval_expr(cr.target.operand)
if isinstance(ptr, NullPtrValue):
- raise NullPtrDereference()
+ raise NullPtrDereference(self, cr)
return self.make_field_region(ptr, cr.field.name)
elif isinstance(cr.target, gcc.VarDecl):
log('bar')
@@ -392,6 +397,9 @@ class State:
def has_returned(self):
return self.return_rvalue is not None
+ def get_gcc_loc(self):
+ return self.loc.get_stmt().loc
+
class Transition:
def __init__(self, src, dest, desc):
assert isinstance(src, State)
@@ -409,14 +417,16 @@ class Transition:
self.dest.log(logger, indent + 1)
class Trace:
- """A sequence of State"""
+ """A sequence of States and Transitions"""
def __init__(self):
self.states = []
+ self.transitions = []
self.err = None
- def add(self, state):
- assert isinstance(state, State)
- self.states.append(state)
+ def add(self, transition):
+ assert isinstance(transition, Transition)
+ self.states.append(transition.dest)
+ self.transitions.append(transition)
return self
def add_error(self, err):
@@ -425,6 +435,7 @@ class Trace:
def copy(self):
t = Trace()
t.states = self.states[:]
+ t.transitions = self.transitions[:]
t.err = self.err # FIXME: should this be a copy?
return t
@@ -522,7 +533,7 @@ def iter_traces(fun, stateclass, prefix=None):
result = []
for transition in transitions:
# Recurse:
- for trace in iter_traces(fun, stateclass, prefix.copy().add(transition.dest)):
+ for trace in iter_traces(fun, stateclass, prefix.copy().add(transition)):
result.append(trace)
return result
else:
@@ -598,6 +609,10 @@ def describe_trace(trace):
# Print more details about the path through the function that
# leads to the error:
awaiting_target = None
+ for t in trace.transitions:
+ log('transition: %s' % t)
+ if t.desc:
+ gcc.inform(t.src.loc.get_stmt().loc, 'when %s' % t.desc)
for j in range(len(trace.states)-1):
state = trace.states[j]
nextstate = trace.states[j+1]
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 63a3047..9e55985 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -151,10 +151,10 @@ class MyState(State):
success.assign(stmt.lhs, nonnull)
success = Transition(self,
success,
- '%s() succeeded' % fnname)
+ '%s() succeeds' % fnname)
failure = self.make_assignment(stmt.lhs,
NullPtrValue(),
- '%s() failed' % fnname)
+ '%s() fails' % fnname)
return [success, failure]
# Function returning borrowed references:
elif fnname in ('Py_InitModule4_64',):
@@ -331,17 +331,44 @@ def check_refcounts(fun, show_traces):
# This trace bails early with a fatal error; it probably doesn't
# have a return value
log('trace.err: %s %r' % (trace.err, trace.err))
- gcc.permerror(trace.err.loc,
- str(trace.err))
+ gcc.error(trace.err.loc,
+ str(trace.err))
describe_trace(trace)
# FIXME: in our example this ought to mention where the values came from
continue
# Otherwise, the trace proceeds normally
return_value = trace.return_value()
log('trace.return_value(): %s' % trace.return_value())
+
# Ideally, we should "own" exactly one reference, and it should be
# the return value. Anything else is an error (and there are other
# kinds of error...)
+
+ # Locate all PyObject that we touched
+ endstate = trace.states[-1]
+ endstate.log(log, 0)
+ # print('return_value: %r' % return_value)
+ # print('endstate.region_for_var: %r' % endstate.region_for_var)
+ # print('endstate.value_for_region: %r' % endstate.value_for_region)
+ if 1: # return_value is not NULL
+ ob_refcnt = endstate.get_value_of_field_by_region(return_value,
+ 'ob_refcnt')
+ # print('ob_refcnt: %r' % ob_refcnt)
+ if isinstance(ob_refcnt, RefcountValue):
+ if ob_refcnt.relvalue > 1:
+ # too high
+ gcc.error(endstate.get_gcc_loc(),
+ 'ob_refcnt of return value is %i too high' % (ob_refcnt.relvalue - 1))
+ describe_trace(trace)
+ elif ob_refcnt.relvalue < 1:
+ # too low
+ gcc.error(endstate.get_gcc_loc(),
+ 'ob_refcnt of return value is %i too low' % (1 - ob_refcnt.relvalue))
+ describe_trace(trace)
+
+ #for k in endstate.region_for_var:
+ # log(k)
+
final_refs = trace.final_references()
if return_value in final_refs:
final_refs.remove(return_value)
diff --git a/testcpychecker.py b/testcpychecker.py
index 92a8507..4fcd8d1 100644
--- a/testcpychecker.py
+++ b/testcpychecker.py
@@ -491,18 +491,6 @@ class RefcountErrorTests(AnalyzerTests):
self.add_module(sm, methods)
return sm
- def test_too_many_increfs(self):
- sm = self.make_mod_method('too_many_increfs',
- ' PyObject *tmp;\n'
- ' tmp = PyLong_FromLong(0x1000);\n'
- ' /* This INCREF is redundant, and introduces a leak: */\n'
- ' Py_INCREF(tmp);\n'
- ' return tmp;\n')
- experr = ('$(SRCFILE): In function ‘too_many_increfs’:\n'
- '$(SRCFILE):19:5: error: additional Py_INCREF() [-fpermissive]\n'
- )
- self.assertFindsError(sm, experr)
-
def test_missing_decref(self):
sm = self.make_mod_method('missing_decref',
' PyObject *list;\n'
diff --git a/tests/cpychecker/refcounts/correct_py_none/script.py b/tests/cpychecker/refcounts/correct_py_none/script.py
index 7625fde..06097dc 100644
--- a/tests/cpychecker/refcounts/correct_py_none/script.py
+++ b/tests/cpychecker/refcounts/correct_py_none/script.py
@@ -32,6 +32,7 @@ def verify_traces(optpass, fun):
state = traces[0].states[-1]
print('_Py_NoneStruct.ob_refcnt: %r'
% state.get_value_of_field_by_varname('_Py_NoneStruct', 'ob_refcnt'))
+ print('state.return_rvalue: %r' % state.return_rvalue)
gcc.register_callback(gcc.PLUGIN_PASS_EXECUTION,
verify_traces)
diff --git a/tests/cpychecker/refcounts/correct_py_none/stdout.txt b/tests/cpychecker/refcounts/correct_py_none/stdout.txt
index b9f3e10..9761507 100644
--- a/tests/cpychecker/refcounts/correct_py_none/stdout.txt
+++ b/tests/cpychecker/refcounts/correct_py_none/stdout.txt
@@ -1 +1,2 @@
_Py_NoneStruct.ob_refcnt: RefcountValue(1)
+state.return_rvalue: Region('_Py_NoneStruct')
diff --git a/tests/cpychecker/refcounts/too_many_increfs/input.c b/tests/cpychecker/refcounts/too_many_increfs/input.c
new file mode 100644
index 0000000..a74901e
--- /dev/null
+++ b/tests/cpychecker/refcounts/too_many_increfs/input.c
@@ -0,0 +1,37 @@
+/*
+ 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>
+
+PyObject *
+too_many_increfs(PyObject *self, PyObject *args)
+{
+ PyObject *tmp;
+ tmp = PyLong_FromLong(0x1000);
+
+ /* This INCREF is redundant, and introduces a leak (or a read through
+ NULL): */
+ Py_INCREF(tmp);
+ return tmp;
+}
+
+static PyMethodDef test_methods[] = {
+ {"test_method", too_many_increfs, METH_VARARGS, NULL},
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
diff --git a/tests/cpychecker/refcounts/too_many_increfs/script.py b/tests/cpychecker/refcounts/too_many_increfs/script.py
new file mode 100644
index 0000000..d3e41ea
--- /dev/null
+++ b/tests/cpychecker/refcounts/too_many_increfs/script.py
@@ -0,0 +1,51 @@
+# -*- 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/>.
+
+import gcc
+from libcpychecker import main, get_traces
+
+def verify_traces(optpass, fun):
+ # Only run in one pass
+ # FIXME: should we be adding our own pass for this?
+ if optpass.name == '*warn_function_return':
+ if fun:
+ traces = get_traces(fun)
+
+ # We should have two traces
+ # print('traces: %r' % traces)
+ assert len(traces) == 2
+
+ # Verify the "success" trace:
+ state = traces[0].states[-1]
+ print('Trace 0:')
+ r = state.return_rvalue
+ print(' returned: %r' %r)
+ print(' r->ob_refcnt: %r' % state.get_value_of_field_by_region(r, 'ob_refcnt'))
+
+ # Verify the "fail" trace:
+ state = traces[1].states[-1]
+ print('Trace 1:')
+ print(' error: %r' % traces[1].err)
+ print(' error: %s' % traces[1].err)
+ print(' returned: %r' % state.return_rvalue)
+
+gcc.register_callback(gcc.PLUGIN_PASS_EXECUTION,
+ verify_traces)
+
+from libcpychecker import main
+main(verify_refcounting=True)
diff --git a/tests/cpychecker/refcounts/too_many_increfs/stderr.txt b/tests/cpychecker/refcounts/too_many_increfs/stderr.txt
new file mode 100644
index 0000000..84f8d14
--- /dev/null
+++ b/tests/cpychecker/refcounts/too_many_increfs/stderr.txt
@@ -0,0 +1,6 @@
+tests/cpychecker/refcounts/too_many_increfs/input.c: In function ‘too_many_increfs’:
+tests/cpychecker/refcounts/too_many_increfs/input.c:31:5: error: ob_refcnt of return value is 1 too high
+tests/cpychecker/refcounts/too_many_increfs/input.c:26:9: note: when PyLong_FromLong() succeeds
+tests/cpychecker/refcounts/too_many_increfs/input.c:31:5: note: when returning
+tests/cpychecker/refcounts/too_many_increfs/input.c:30:5: error: dereferencing NULL (tmp->ob_refcnt) at tests/cpychecker/refcounts/too_many_increfs/input.c:30
+tests/cpychecker/refcounts/too_many_increfs/input.c:26:9: note: when PyLong_FromLong() fails
diff --git a/tests/cpychecker/refcounts/too_many_increfs/stdout.txt b/tests/cpychecker/refcounts/too_many_increfs/stdout.txt
new file mode 100644
index 0000000..4490c69
--- /dev/null
+++ b/tests/cpychecker/refcounts/too_many_increfs/stdout.txt
@@ -0,0 +1,7 @@
+Trace 0:
+ returned: Region('heap_region_2')
+ r->ob_refcnt: RefcountValue(2)
+Trace 1:
+ error: NullPtrDereference()
+ error: dereferencing NULL (tmp->ob_refcnt) at tests/cpychecker/refcounts/too_many_increfs/input.c:30
+ returned: None
12 years, 10 months
[gcc-python-plugin] Eliminate the DataState class, merging it all into State
by David Hugh Malcolm
commit 94a2e7472dbc417a4b9589be546868cca83184c6
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Mon Jul 11 22:43:27 2011 -0400
Eliminate the DataState class, merging it all into State
libcpychecker/absinterp.py | 89 +++++++++----------
libcpychecker/refcounts.py | 40 +++++----
libcpychecker/visualizations.py | 29 ++++---
.../refcounts/correct_object_ctor/script.py | 6 +-
.../cpychecker/refcounts/correct_py_none/script.py | 2 +-
5 files changed, 83 insertions(+), 83 deletions(-)
---
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index d779e1a..9be99e5 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -156,35 +156,48 @@ class Region:
# Used for making unique IDs:
num_heap_regions = 0
-class DataState:
- """The parts of a State that aren't the location"""
+class State:
+ """A Location with memory state"""
+ def __init__(self, loc, region_for_var=None, value_for_region=None, return_rvalue=None):
+ assert isinstance(loc, Location)
+ self.loc = loc
- def __init__(self, region_for_var=None, value_for_region=None):
# Mapping from VarDecl.name to Region:
if region_for_var:
+ assert isinstance(region_for_var, OrderedDict)
self.region_for_var = region_for_var
else:
self.region_for_var = OrderedDict()
# Mapping from Region to AbstractValue:
if value_for_region:
+ assert isinstance(value_for_region, OrderedDict)
self.value_for_region = value_for_region
else:
self.value_for_region = OrderedDict()
- self.return_rvalue = None
+ self.return_rvalue = return_rvalue
+
+ def __str__(self):
+ return ('loc: %s region_for_var:%s value_for_region:%s%s'
+ % (self.loc,
+ self.region_for_var,
+ self.value_for_region,
+ self._extra()))
def __repr__(self):
- return ('region_for_var:%r value_for_region:%r'
- % (self.region_for_var, self.value_for_region))
+ return ('loc: %r region_for_var:%r value_for_region:%r%r'
+ % (self.loc,
+ self.region_for_var,
+ self.value_for_region,
+ self._extra()))
def log(self, logger, indent):
logger(str(self.region_for_var), indent + 1)
logger(str(self.value_for_region), indent + 1)
+ # Display data in tabular form:
from gccutils import Table
t = Table(['Expression', 'Region', 'Value'])
- #logger(' Expression | Region | Value ', indent + 1)
- #logger('------------+--------+-------', indent + 1)
for k in self.region_for_var:
region = self.region_for_var[k]
value = self.value_for_region.get(region, None)
@@ -195,9 +208,19 @@ class DataState:
for line in s.getvalue().splitlines():
logger(line, indent + 1)
+ logger('extra: %s' % (self._extra(), ), indent)
+
+ # FIXME: derived class/extra:
+ self.resources.log(logger, indent)
+
+ logger('loc: %s' % self.loc, indent)
+ if self.loc.get_stmt():
+ logger('%s' % self.loc.get_stmt().loc, indent + 1)
+
def copy(self):
- c = DataState(self.region_for_var.copy(),
- self.value_for_region.copy())
+ c = self.__class__(loc,
+ self.region_for_var.copy(),
+ self.value_for_region.copy())
c.return_rvalue = self.return_rvalue
return c
@@ -339,42 +362,14 @@ class DataState:
value = self.value_for_region.get(region, None)
return value
-class State:
- """A Location with a DataState"""
- def __init__(self, loc, data):
- assert isinstance(data, DataState)
- self.loc = loc
- self.data = data
-
def init_for_function(self, fun):
log('init_for_function(%r)' % fun)
root_region = Region('root', None)
stack = Region('stack for %s' % fun.decl.name, root_region)
for local in fun.local_decls:
region = Region('region for %r' % local, stack)
- self.data.region_for_var[local] = region
- self.data.value_for_region[region] = None # uninitialized
-
- def copy(self):
- return self.__class__(loc, self.data.copy())
-
- def __str__(self):
- return '%s: %s%s' % (self.data, self.loc, self._extra())
-
- def __repr__(self):
- return '%s: %s%s' % (self.data, self.loc, self._extra())
-
- def log(self, logger, indent):
- self.data.log(logger, indent + 1)
- #logger('data: %s' % (self.data, ), indent)
- logger('extra: %s' % (self._extra(), ), indent)
-
- # FIXME: derived class/extra:
- self.resources.log(logger, indent)
-
- logger('loc: %s' % self.loc, indent)
- if self.loc.get_stmt():
- logger('%s' % self.loc.get_stmt().loc, indent + 1)
+ self.region_for_var[local] = region
+ self.value_for_region[region] = None # uninitialized
def make_assignment(self, lhs, rhs, desc):
log('make_assignment(%r, %r, %r)' % (lhs, rhs, desc))
@@ -382,7 +377,7 @@ class State:
assert isinstance(desc, str)
new = self.copy()
new.loc = self.loc.next_loc()
- new.data.assign(lhs, rhs)
+ new.assign(lhs, rhs)
return Transition(self, new, desc)
def update_loc(self, newloc):
@@ -395,7 +390,7 @@ class State:
return self.update_loc(newloc)
def has_returned(self):
- return self.data.return_rvalue is not None
+ return self.return_rvalue is not None
class Transition:
def __init__(self, src, dest, desc):
@@ -441,11 +436,11 @@ class Trace:
if self.err:
logger(' Trace ended with error: %s' % self.err, indent + 1)
- #def get_last_stmt(self):
- # return self.states[-1].loc.get_stmt()
+ def get_last_stmt(self):
+ return self.states[-1].loc.get_stmt()
def return_value(self):
- return self.states[-1].data.return_rvalue
+ return self.states[-1].return_rvalue
def final_references(self):
return self.states[-1].owned_refs
@@ -493,7 +488,7 @@ def iter_traces(fun, stateclass, prefix=None):
if prefix is None:
prefix = Trace()
curstate = stateclass(Location.get_block_start(fun.cfg.entry),
- DataState(),
+ None, None, None,
[],
Resources())
else:
@@ -554,7 +549,7 @@ class StateGraph:
# Recursively gather states:
initial = stateclass(Location.get_block_start(fun.cfg.entry),
- DataState(),
+ None, None, None,
[],
Resources())
initial.init_for_function(fun)
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 51f46e7..63a3047 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -67,13 +67,17 @@ class RefcountValue(AbstractValue):
return 'RefcountValue(%i)' % self.relvalue
class MyState(State):
- def __init__(self, loc, data, owned_refs, resources):
- State.__init__(self, loc, data)
+ def __init__(self, loc, region_for_var, value_for_region, return_rvalue, owned_refs, resources):
+ State.__init__(self, loc, region_for_var, value_for_region, return_rvalue)
self.owned_refs = owned_refs
self.resources = resources
def copy(self):
- return self.__class__(self.loc, self.data.copy(), self.owned_refs[:],
+ return self.__class__(self.loc,
+ self.region_for_var.copy(),
+ self.value_for_region.copy(),
+ self.return_rvalue,
+ self.owned_refs[:],
self.resources.copy())
def _extra(self):
@@ -141,10 +145,10 @@ class MyState(State):
# Allocation and assignment:
success = self.copy()
success.loc = self.loc.next_loc()
- nonnull = success.data.make_heap_region()
- ob_refcnt = success.data.make_field_region(nonnull, 'ob_refcnt') # FIXME: this should be a memref and fieldref
- success.data.value_for_region[ob_refcnt] = RefcountValue(1)
- success.data.assign(stmt.lhs, nonnull)
+ nonnull = success.make_heap_region()
+ ob_refcnt = success.make_field_region(nonnull, 'ob_refcnt') # FIXME: this should be a memref and fieldref
+ success.value_for_region[ob_refcnt] = RefcountValue(1)
+ success.assign(stmt.lhs, nonnull)
success = Transition(self,
success,
'%s() succeeded' % fnname)
@@ -212,8 +216,8 @@ class MyState(State):
def eval_condition(self, stmt):
log('eval_condition: %s' % stmt)
- lhs = self.data.eval_expr(stmt.lhs)
- rhs = self.data.eval_expr(stmt.rhs)
+ lhs = self.eval_expr(stmt.lhs)
+ rhs = self.eval_expr(stmt.rhs)
log('eval of lhs: %r' % lhs)
log('eval of rhs: %r' % rhs)
log('stmt.exprcode: %r' % stmt.exprcode)
@@ -228,21 +232,21 @@ class MyState(State):
def eval_rhs(self, stmt):
rhs = stmt.rhs
if stmt.exprcode == gcc.PlusExpr:
- a = self.data.eval_expr(rhs[0])
- b = self.data.eval_expr(rhs[1])
+ a = self.eval_expr(rhs[0])
+ b = self.eval_expr(rhs[1])
log('a: %r' % a)
log('b: %r' % b)
if isinstance(a, RefcountValue) and isinstance(b, long):
return RefcountValue(a.relvalue + b)
raise 'bar'
elif stmt.exprcode == gcc.ComponentRef:
- return self.data.eval_expr(rhs[0])
+ return self.eval_expr(rhs[0])
elif stmt.exprcode == gcc.VarDecl:
- return self.data.eval_expr(rhs[0])
+ return self.eval_expr(rhs[0])
elif stmt.exprcode == gcc.IntegerCst:
- return self.data.eval_expr(rhs[0])
+ return self.eval_expr(rhs[0])
elif stmt.exprcode == gcc.AddrExpr:
- return self.data.eval_expr(rhs[0])
+ return self.eval_expr(rhs[0])
else:
raise 'foo'
@@ -275,12 +279,12 @@ class MyState(State):
log('stmt: %r %s' % (stmt, stmt))
log('stmt.retval: %r' % stmt.retval)
- rvalue = self.data.eval_expr(stmt.retval)
+ rvalue = self.eval_expr(stmt.retval)
log('rvalue from eval_expr: %r' % rvalue)
nextstate = self.copy()
- nextstate.data.return_rvalue = rvalue
- assert nextstate.data.return_rvalue is not None # ensure termination
+ nextstate.return_rvalue = rvalue
+ assert nextstate.return_rvalue is not None # ensure termination
return [Transition(self, nextstate, 'returning')]
def get_traces(fun):
diff --git a/libcpychecker/visualizations.py b/libcpychecker/visualizations.py
index 9241997..10f5685 100644
--- a/libcpychecker/visualizations.py
+++ b/libcpychecker/visualizations.py
@@ -70,27 +70,28 @@ class StateGraphPrettyPrinter(StatePrettyPrinter):
def state_id(self, state):
return 'state%i' % id(state)
- def data_state_to_dot_label(self, ds):
- result = ('<tr> %s %s %s</tr>\n'
+ def state_to_dot_label(self, state):
+ result = '<table cellborder="0" border="0" cellspacing="0">\n'
+
+ # Show data:
+ result += '<tr><td colspan="2"><table border="0" cellborder="1">'
+ result += ('<tr> %s %s %s</tr>\n'
% (self._dot_td('Expression'),
self._dot_td('lvalue'),
self._dot_td('rvalue')))
- for key in ds.region_for_var:
- region = ds.region_for_var[key]
- value = ds.value_for_region.get(region, None)
+ for key in state.region_for_var:
+ region = state.region_for_var[key]
+ value = state.value_for_region.get(region, None)
result += ('<tr> %s %s %s</tr>\n'
% (self._dot_td(key),
self._dot_td(region),
self._dot_td(value)))
- return result
-
- def state_to_dot_label(self, state):
- result = '<table cellborder="0" border="0" cellspacing="0">\n'
-
- # Show data:
- print 'state.data: %r' % state.data
- result += '<tr><td colspan="2"><table border="0" cellborder="1">'
- result += self.data_state_to_dot_label(state.data)
+ # Show any return value:
+ if state.return_rvalue:
+ result += ('<tr> %s %s %s</tr>\n'
+ % (self._dot_td(''),
+ self._dot_td('Return Value'),
+ self._dot_td(state.return_rvalue)))
result += '</table></td></tr>'
# Show location:
diff --git a/tests/cpychecker/refcounts/correct_object_ctor/script.py b/tests/cpychecker/refcounts/correct_object_ctor/script.py
index d2ee6a2..9999add 100644
--- a/tests/cpychecker/refcounts/correct_object_ctor/script.py
+++ b/tests/cpychecker/refcounts/correct_object_ctor/script.py
@@ -33,14 +33,14 @@ def verify_traces(optpass, fun):
# Verify the "success" trace:
state = traces[0].states[-1]
print('Trace 0:')
- r = state.data.return_rvalue
+ r = state.return_rvalue
print(' returned: %r' %r)
- print(' r->ob_refcnt: %r' % state.data.get_value_of_field_by_region(r, 'ob_refcnt'))
+ print(' r->ob_refcnt: %r' % state.get_value_of_field_by_region(r, 'ob_refcnt'))
# Verify the "fail" trace:
state = traces[1].states[-1]
print('Trace 1:')
- print(' returned: %r' % state.data.return_rvalue)
+ print(' returned: %r' % state.return_rvalue)
gcc.register_callback(gcc.PLUGIN_PASS_EXECUTION,
verify_traces)
diff --git a/tests/cpychecker/refcounts/correct_py_none/script.py b/tests/cpychecker/refcounts/correct_py_none/script.py
index 96af54b..7625fde 100644
--- a/tests/cpychecker/refcounts/correct_py_none/script.py
+++ b/tests/cpychecker/refcounts/correct_py_none/script.py
@@ -31,7 +31,7 @@ def verify_traces(optpass, fun):
assert len(traces) == 1
state = traces[0].states[-1]
print('_Py_NoneStruct.ob_refcnt: %r'
- % state.data.get_value_of_field_by_varname('_Py_NoneStruct', 'ob_refcnt'))
+ % state.get_value_of_field_by_varname('_Py_NoneStruct', 'ob_refcnt'))
gcc.register_callback(gcc.PLUGIN_PASS_EXECUTION,
verify_traces)
12 years, 10 months
[gcc-python-plugin] Rewrite of state tracking within libcpychecker (work-in-progress)
by David Hugh Malcolm
commit 3758a32c4b88ef46d0c7f8a9c0096661b7a1df51
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Mon Jul 11 19:14:26 2011 -0400
Rewrite of state tracking within libcpychecker (work-in-progress)
Introduce DataState class to track the non-location parts of State (and the
returned value, if any).
Introduce memory regions for lvalues, to handle allocations and pointers
Show the above in visualizations of the state tree
Split out the "test_correct_py_none" and "test_correct_object_ctor" from the
unittest-based testcpychecker.py suite, and into their own directories, so
that we can assert in more detail about the traces
libcpychecker/__init__.py | 2 +-
libcpychecker/absinterp.py | 299 +++++++++++++++++---
libcpychecker/refcounts.py | 133 ++++++---
libcpychecker/visualizations.py | 24 ++-
testcpychecker.py | 17 --
.../refcounts/correct_object_ctor/input.c | 37 +++
.../refcounts/correct_object_ctor/script.py | 51 ++++
.../refcounts/correct_object_ctor/stdout.txt | 5 +
tests/cpychecker/refcounts/correct_py_none/input.c | 31 ++
.../cpychecker/refcounts/correct_py_none/script.py | 39 +++
.../refcounts/correct_py_none/stdout.txt | 1 +
11 files changed, 523 insertions(+), 116 deletions(-)
---
diff --git a/libcpychecker/__init__.py b/libcpychecker/__init__.py
index 85dee13..9996585 100644
--- a/libcpychecker/__init__.py
+++ b/libcpychecker/__init__.py
@@ -18,7 +18,7 @@
import gcc
from PyArg_ParseTuple import check_pyargs, log
-from refcounts import check_refcounts
+from refcounts import check_refcounts, get_traces
def on_pass_execution(optpass, fun,
show_traces=False,
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index 19e34ff..d779e1a 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -16,6 +16,7 @@
# <http://www.gnu.org/licenses/>.
import gcc
+import gccutils
import sys
from gccutils import get_src_for_loc
from collections import OrderedDict
@@ -48,6 +49,9 @@ class InvalidlyNullParameter(PredictedError):
% (self.fnname, self.paramidx, self.nullvalue))
+class NullPtrDereference(PredictedError):
+ pass
+
class UnknownValue(AbstractValue):
def __str__(self):
return 'unknown %s from %s' % (self.gcctype, self.stmt)
@@ -55,7 +59,7 @@ class UnknownValue(AbstractValue):
def __repr__(self):
return 'UnknownValue(%r, %r)' % (self.gcctype, self.stmt)
-class PtrValue:
+class PtrValue(AbstractValue):
"""An abstract (PyObject*) value"""
def __init__(self, nonnull):
self.nonnull = nonnull
@@ -95,18 +99,6 @@ class NonNullPtrValue(PtrValue):
def __repr__(self):
return 'NonNullPtrValue(%i)' % self.refdelta
-class PtrToGlobal(NonNullPtrValue):
- def __init__(self, refdelta, stmt, name):
- NonNullPtrValue.__init__(self, refdelta, stmt)
- self.name = name
-
- def __str__(self):
- if self.stmt:
- return ('&%s acquired at %s'
- % (self.name, describe_stmt(self.stmt)))
- else:
- return ('&%s' % self.name)
-
class Location:
"""A location within a CFG: a gcc.BasicBlock together with an index into
the gimple list. (We don't support SSA passes)"""
@@ -149,12 +141,220 @@ class Location:
else:
return None
+class Region:
+ def __init__(self, name, parent):
+ self.name = name
+ self.parent = parent
+ self.children = []
+ self.fields = {}
+ if parent:
+ parent.children.append(self)
+
+ def __repr__(self):
+ return 'Region(%r)' % self.name
+
+# Used for making unique IDs:
+num_heap_regions = 0
+
+class DataState:
+ """The parts of a State that aren't the location"""
+
+ def __init__(self, region_for_var=None, value_for_region=None):
+ # Mapping from VarDecl.name to Region:
+ if region_for_var:
+ self.region_for_var = region_for_var
+ else:
+ self.region_for_var = OrderedDict()
+
+ # Mapping from Region to AbstractValue:
+ if value_for_region:
+ self.value_for_region = value_for_region
+ else:
+ self.value_for_region = OrderedDict()
+
+ self.return_rvalue = None
+
+ def __repr__(self):
+ return ('region_for_var:%r value_for_region:%r'
+ % (self.region_for_var, self.value_for_region))
+
+ def log(self, logger, indent):
+ logger(str(self.region_for_var), indent + 1)
+ logger(str(self.value_for_region), indent + 1)
+ from gccutils import Table
+ t = Table(['Expression', 'Region', 'Value'])
+ #logger(' Expression | Region | Value ', indent + 1)
+ #logger('------------+--------+-------', indent + 1)
+ for k in self.region_for_var:
+ region = self.region_for_var[k]
+ value = self.value_for_region.get(region, None)
+ t.add_row((k, region, value),)
+ from StringIO import StringIO
+ s = StringIO()
+ t.write(s)
+ for line in s.getvalue().splitlines():
+ logger(line, indent + 1)
+
+ def copy(self):
+ c = DataState(self.region_for_var.copy(),
+ self.value_for_region.copy())
+ c.return_rvalue = self.return_rvalue
+ return c
+
+ def eval_expr(self, expr):
+ log('eval_expr: %r' % expr)
+ if isinstance(expr, AbstractValue):
+ return expr
+ if isinstance(expr, Region):
+ return expr
+ if isinstance(expr, gcc.IntegerCst):
+ return expr.constant
+ if isinstance(expr, gcc.VarDecl):
+ region = self.var_region(expr)
+ value = self.get_store(region)
+ return value
+ #return UnknownValue(expr.type, str(expr))
+ if isinstance(expr, gcc.ComponentRef):
+ #assert isinstance(expr.field, gcc.FieldDecl)
+ region = self.get_field_region(expr)#.target, expr.field.name)
+ value = self.get_store(region)
+ return value
+ if 0: # isinstance(expr, gcc.AddrExpr):
+ #log(dir(expr))
+ #log(expr.operand)
+ # Handle Py_None, which is a #define of (&_Py_NoneStruct)
+ if isinstance(expr.operand, gcc.VarDecl):
+ if expr.operand.name == '_Py_NoneStruct':
+ # FIXME: do we need a stmt?
+ return PtrToGlobal(1, None, expr.operand.name)
+ if expr is None:
+ return None
+ return UnknownValue(expr.type, None) # FIXME
+
+ def assign(self, lhs, rhs):
+ log('assign(%r, %r)' % (lhs, rhs))
+ log('assign(%s, %s)' % (lhs, rhs))
+ if isinstance(lhs, gcc.VarDecl):
+ dest_region = self.var_region(lhs)
+ elif isinstance(lhs, gcc.ComponentRef):
+ assert isinstance(lhs.field, gcc.FieldDecl)
+ dest_region = self.get_field_region(lhs)
+ else:
+ assert False # FIXME!
+ value = self.eval_expr(rhs)
+ log('value: %s %r' % (value, value))
+ self.value_for_region[dest_region] = value
+ #assert isinstance(rhs, gcc.VarDecl) # for now
+ #self.value_for_region(
+
+ def var_region(self, var):
+ assert isinstance(var, gcc.VarDecl)
+ if var not in self.region_for_var:
+ # Presumably a reference to a global variable:
+ log('adding region for global var')
+ region = Region(var.name, None)
+ # it is its own region:
+ self.region_for_var[var] = region
+
+ # Initialize the refcount of global PyObject instances
+ # e.g. _Py_NoneStruct to 0 i.e. we don't own any references to them
+ if str(var.type) == 'struct PyObject':
+ from refcounts import RefcountValue
+ ob_refcnt = self.make_field_region(region, 'ob_refcnt') # FIXME: this should be a memref and fieldref
+ self.value_for_region[ob_refcnt] = RefcountValue(0)
+ return self.region_for_var[var]
+
+ def get_field_region(self, cr): #target, field):
+ assert isinstance(cr, gcc.ComponentRef)
+ #cr.debug()
+ log('target: %r' % cr.target)
+ log('field: %r' % cr.field)
+ #fr = FIXME #self.make_field_region(target, field)
+ if 1: # fr not in self.region_for_var:
+ if 1: # cr.target not in self.region_for_var:
+ log('foo')
+ if isinstance(cr.target, gcc.MemRef):
+ ptr = self.eval_expr(cr.target.operand)
+ if isinstance(ptr, NullPtrValue):
+ raise NullPtrDereference()
+ return self.make_field_region(ptr, cr.field.name)
+ elif isinstance(cr.target, gcc.VarDecl):
+ log('bar')
+ vr = self.var_region(cr.target)
+ log(vr)
+ return self.make_field_region(vr, cr.field.name)
+ log('cr: %r %s' % (cr, cr))
+ return self.region_for_var[cr]
+
+ def get_store(self, region):
+ assert isinstance(region, Region)
+ # self.log(log, 0)
+ return self.value_for_region[region]
+
+ def make_heap_region(self):
+ global num_heap_regions
+ region = Region('heap_region_%i' % num_heap_regions, None)
+ num_heap_regions += 1
+ # it is its own region:
+ self.region_for_var[region] = region
+ return region
+
+ def make_field_region(self, target, field):
+ assert isinstance(target, Region)
+ assert isinstance(field, str)
+ log('make_field_region(%r, %r)' % (target, field))
+ if field in target.fields:
+ log('reusing')
+ return target.fields[field]
+ log('not reusing')
+ region = Region('%s.%s' % (target.name, field), target)
+ target.fields[field] = region
+ # it is its own region:
+ self.region_for_var[region] = region
+ return region
+
+
+ def get_value_of_field_by_varname(self, varname, field):
+ # Lookup varname.field
+ # For use in writing selftests
+ assert isinstance(varname, str)
+ assert isinstance(field, str)
+ for k in self.region_for_var:
+ if isinstance(k, gcc.VarDecl):
+ if k.name == varname:
+ region = self.region_for_var[k]
+ region = self.make_field_region(region, field)
+ value = self.value_for_region.get(region, None)
+ return value
+
+ def get_value_of_field_by_region(self, rvalue, field):
+ # Lookup rvalue->field
+ # For use in writing selftests
+ assert isinstance(rvalue, Region)
+ assert isinstance(field, str)
+ for k in self.region_for_var:
+ if isinstance(k, Region):
+ if k == rvalue:
+ region = self.make_field_region(rvalue, field)
+ value = self.value_for_region.get(region, None)
+ return value
+
class State:
- """A Location with a dict of vars and values"""
+ """A Location with a DataState"""
def __init__(self, loc, data):
+ assert isinstance(data, DataState)
self.loc = loc
self.data = data
+ def init_for_function(self, fun):
+ log('init_for_function(%r)' % fun)
+ root_region = Region('root', None)
+ stack = Region('stack for %s' % fun.decl.name, root_region)
+ for local in fun.local_decls:
+ region = Region('region for %r' % local, stack)
+ self.data.region_for_var[local] = region
+ self.data.value_for_region[region] = None # uninitialized
+
def copy(self):
return self.__class__(loc, self.data.copy())
@@ -165,7 +365,8 @@ class State:
return '%s: %s%s' % (self.data, self.loc, self._extra())
def log(self, logger, indent):
- logger('data: %s' % (self.data, ), indent)
+ self.data.log(logger, indent + 1)
+ #logger('data: %s' % (self.data, ), indent)
logger('extra: %s' % (self._extra(), ), indent)
# FIXME: derived class/extra:
@@ -175,18 +376,13 @@ class State:
if self.loc.get_stmt():
logger('%s' % self.loc.get_stmt().loc, indent + 1)
- def get_key_for_lvalue(self, lvalue):
- return str(lvalue) # FIXME
-
- def make_assignment(self, lvalue, value, desc):
+ def make_assignment(self, lhs, rhs, desc):
+ log('make_assignment(%r, %r, %r)' % (lhs, rhs, desc))
if desc:
assert isinstance(desc, str)
new = self.copy()
new.loc = self.loc.next_loc()
- if lvalue:
- assert isinstance(lvalue, gcc.VarDecl) # for now
- key = self.get_key_for_lvalue(lvalue)
- new.data[key] = value
+ new.data.assign(lhs, rhs)
return Transition(self, new, desc)
def update_loc(self, newloc):
@@ -198,6 +394,9 @@ class State:
newloc = self.loc.next_loc()
return self.update_loc(newloc)
+ def has_returned(self):
+ return self.data.return_rvalue is not None
+
class Transition:
def __init__(self, src, dest, desc):
assert isinstance(src, State)
@@ -209,6 +408,11 @@ class Transition:
def __repr__(self):
return 'Transition(%r, %r)' % (self.dest, self.desc)
+ def log(self, logger, indent):
+ logger('desc: %r' % self.desc, indent)
+ logger('dest:', indent)
+ self.dest.log(logger, indent + 1)
+
class Trace:
"""A sequence of State"""
def __init__(self):
@@ -237,15 +441,11 @@ class Trace:
if self.err:
logger(' Trace ended with error: %s' % self.err, indent + 1)
- def get_last_stmt(self):
- return self.states[-1].loc.get_stmt()
+ #def get_last_stmt(self):
+ # return self.states[-1].loc.get_stmt()
def return_value(self):
- last_stmt = self.get_last_stmt()
- if isinstance(last_stmt, gcc.GimpleReturn):
- return self.states[-1].eval_expr(last_stmt.retval)
- else:
- return None
+ return self.states[-1].data.return_rvalue
def final_references(self):
return self.states[-1].owned_refs
@@ -293,11 +493,14 @@ def iter_traces(fun, stateclass, prefix=None):
if prefix is None:
prefix = Trace()
curstate = stateclass(Location.get_block_start(fun.cfg.entry),
- OrderedDict(),
+ DataState(),
[],
Resources())
else:
curstate = prefix.states[-1]
+ if curstate.has_returned():
+ # This state has returned a value (and hence terminated):
+ return [prefix]
# We need the prevstate to handle Phi nodes
if len(prefix.states) > 1:
@@ -308,7 +511,7 @@ def iter_traces(fun, stateclass, prefix=None):
prefix.log(log, 'PREFIX', 1)
log(' %s:%s' % (fun.decl.name, curstate.loc))
try:
- transitions = curstate.get_transitions(prevstate)
+ transitions = curstate.get_transitions()
assert isinstance(transitions, list)
except PredictedError, err:
# We're at a terminating state:
@@ -351,28 +554,30 @@ class StateGraph:
# Recursively gather states:
initial = stateclass(Location.get_block_start(fun.cfg.entry),
- OrderedDict(),
+ DataState(),
[],
Resources())
+ initial.init_for_function(fun)
self.states.append(initial)
- self._gather_states(initial, None, logger)
+ self._gather_states(initial, logger)
- def _gather_states(self, curstate, prevstate, logger):
+ def _gather_states(self, curstate, logger):
logger(' %s:%s' % (self.fun.decl.name, curstate.loc))
try:
- transitions = curstate.get_transitions(prevstate)
- print transitions
+ transitions = curstate.get_transitions()
+ #print transitions
assert isinstance(transitions, list)
except PredictedError, err:
# We're at a terminating state:
- raise "foo" # FIXME
- err.loc = prefix.get_last_stmt().loc
- trace_with_err = prefix.copy()
- trace_with_err.add_error(err)
- trace_with_err.log(log, 'FINISHED TRACE WITH ERROR: %s' % err, 1)
- return [trace_with_err]
+ errstate = curstate.copy()
+ transition = Transition(curstate, errstate, str(err))
+ self.states.append(transition.dest)
+ self.transitions.append(transition)
+ return
- logger('transitions: %s' % transitions, 2)
+ logger('transitions:', 2)
+ for t in transitions:
+ t.log(logger, 3)
if len(transitions) > 0:
for transition in transitions:
@@ -381,8 +586,12 @@ class StateGraph:
self.states.append(transition.dest)
self.transitions.append(transition)
+ if transition.dest.has_returned():
+ # This state has returned a value (and hence terminated)
+ continue
+
# Recurse:
- self._gather_states(transition.dest, curstate, logger)
+ self._gather_states(transition.dest, logger)
else:
# We're at a terminating state:
logger('FINISHED TRACE')
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 9219714..51f46e7 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -56,6 +56,16 @@ def stmt_is_return_of_objptr(stmt):
if type_is_pyobjptr(stmt.retval.type):
return True
+class RefcountValue(AbstractValue):
+ def __init__(self, relvalue):
+ self.relvalue = relvalue
+
+ def __str__(self):
+ return 'refs: %i' % self.relvalue
+
+ def __repr__(self):
+ return 'RefcountValue(%i)' % self.relvalue
+
class MyState(State):
def __init__(self, loc, data, owned_refs, resources):
State.__init__(self, loc, data)
@@ -83,11 +93,11 @@ class MyState(State):
transition.dest.owned_refs.append(additional_ptr)
return transition
- def get_transitions(self, oldstate):
+ def get_transitions(self):
# Return a list of Transition instances, based on input State
stmt = self.loc.get_stmt()
if stmt:
- return self._get_transitions_for_stmt(stmt, oldstate)
+ return self._get_transitions_for_stmt(stmt)
else:
result = []
for loc in self.loc.next_locs():
@@ -97,7 +107,7 @@ class MyState(State):
log('result: %s' % result)
return result
- def _get_transitions_for_stmt(self, stmt, oldstate):
+ def _get_transitions_for_stmt(self, stmt):
log('_get_transitions_for_stmt: %r %s' % (stmt, stmt), 2)
log('dir(stmt): %s' % dir(stmt), 3)
if isinstance(stmt, gcc.GimpleCall):
@@ -106,35 +116,13 @@ class MyState(State):
return [self.use_next_loc()]
elif isinstance(stmt, gcc.GimpleCond):
return self._get_transitions_for_GimpleCond(stmt)
- elif isinstance(stmt, gcc.GimplePhi):
- return self._get_transitions_for_GimplePhi(stmt, oldstate)
elif isinstance(stmt, gcc.GimpleReturn):
- return []
+ return self._get_transitions_for_GimpleReturn(stmt)
elif isinstance(stmt, gcc.GimpleAssign):
return self._get_transitions_for_GimpleAssign(stmt)
else:
raise "foo"
- def eval_expr(self, expr):
- if isinstance(expr, gcc.IntegerCst):
- return expr.constant
- if isinstance(expr, gcc.VarDecl):
- if expr.name in self.data:
- return self.data[expr.name]
- else:
- return UnknownValue(expr.type, None)
- if isinstance(expr, gcc.AddrExpr):
- #log(dir(expr))
- #log(expr.operand)
- # Handle Py_None, which is a #define of (&_Py_NoneStruct)
- if isinstance(expr.operand, gcc.VarDecl):
- if expr.operand.name == '_Py_NoneStruct':
- # FIXME: do we need a stmt?
- return PtrToGlobal(1, None, expr.operand.name)
- if expr is None:
- return None
- return UnknownValue(expr.type, None) # FIXME
-
def _get_transitions_for_GimpleCall(self, stmt):
log('stmt.lhs: %s %r' % (stmt.lhs, stmt.lhs), 3)
log('stmt.fn: %s %r' % (stmt.fn, stmt.fn), 3)
@@ -148,9 +136,22 @@ class MyState(State):
fnname = stmt.fn.operand.name
# Function returning new references:
if fnname in ('PyList_New', 'PyLong_FromLong'):
- nonnull = NonNullPtrValue(1, stmt)
- return [self.make_assignment(stmt.lhs, nonnull, '%s() succeeded' % fnname, nonnull),
- self.make_assignment(stmt.lhs, NullPtrValue(), '%s() failed' % fnname)]
+ #nonnull = NonNullPtrValue(1, stmt)
+
+ # Allocation and assignment:
+ success = self.copy()
+ success.loc = self.loc.next_loc()
+ nonnull = success.data.make_heap_region()
+ ob_refcnt = success.data.make_field_region(nonnull, 'ob_refcnt') # FIXME: this should be a memref and fieldref
+ success.data.value_for_region[ob_refcnt] = RefcountValue(1)
+ success.data.assign(stmt.lhs, nonnull)
+ success = Transition(self,
+ success,
+ '%s() succeeded' % fnname)
+ failure = self.make_assignment(stmt.lhs,
+ NullPtrValue(),
+ '%s() failed' % fnname)
+ return [success, failure]
# Function returning borrowed references:
elif fnname in ('Py_InitModule4_64',):
return [self.make_assignment(stmt.lhs, NonNullPtrValue(1, stmt)),
@@ -209,23 +210,10 @@ class MyState(State):
return [make_transition_for_true(stmt),
make_transition_for_false(stmt)]
- def _get_transitions_for_GimplePhi(self, stmt, oldstate):
- log('stmt: %s' % stmt)
- log('stmt.lhs: %s' % stmt.lhs)
- log('stmt.args: %s' % stmt.args)
- # Choose the correct new value, based on the edge we came in on
- for expr, edge in stmt.args:
- if edge.src == oldstate.loc.bb:
- # Update the LHS appropriately:
- next = self.use_next_loc()
- next.data[str(stmt.lhs)] = self.eval_expr(expr)
- return [next]
- raise AnalysisError('incoming edge not found')
-
def eval_condition(self, stmt):
log('eval_condition: %s' % stmt)
- lhs = self.eval_expr(stmt.lhs)
- rhs = self.eval_expr(stmt.rhs)
+ lhs = self.data.eval_expr(stmt.lhs)
+ rhs = self.data.eval_expr(stmt.rhs)
log('eval of lhs: %r' % lhs)
log('eval of rhs: %r' % rhs)
log('stmt.exprcode: %r' % stmt.exprcode)
@@ -237,28 +225,77 @@ class MyState(State):
log('got here')
return UnknownValue(None, stmt)
+ def eval_rhs(self, stmt):
+ rhs = stmt.rhs
+ if stmt.exprcode == gcc.PlusExpr:
+ a = self.data.eval_expr(rhs[0])
+ b = self.data.eval_expr(rhs[1])
+ log('a: %r' % a)
+ log('b: %r' % b)
+ if isinstance(a, RefcountValue) and isinstance(b, long):
+ return RefcountValue(a.relvalue + b)
+ raise 'bar'
+ elif stmt.exprcode == gcc.ComponentRef:
+ return self.data.eval_expr(rhs[0])
+ elif stmt.exprcode == gcc.VarDecl:
+ return self.data.eval_expr(rhs[0])
+ elif stmt.exprcode == gcc.IntegerCst:
+ return self.data.eval_expr(rhs[0])
+ elif stmt.exprcode == gcc.AddrExpr:
+ return self.data.eval_expr(rhs[0])
+ else:
+ raise 'foo'
+
def _get_transitions_for_GimpleAssign(self, stmt):
log('stmt.lhs: %r %s' % (stmt.lhs, stmt.lhs))
log('stmt.rhs: %r %s' % (stmt.rhs, stmt.rhs))
+ log('stmt: %r %s' % (stmt, stmt))
+ log('stmt.exprcode: %r' % stmt.exprcode)
+
+ value = self.eval_rhs(stmt)
+ log('value from eval_rhs: %r' % value)
nextstate = self.use_next_loc()
+ """
if isinstance(stmt.lhs, gcc.MemRef):
- value = self.eval_expr(stmt.rhs[0])
log('value: %s %r' % (value, value))
# We're writing a value to memory; if it's a PyObject*
# then we're surrending a reference on it:
if value in nextstate.owned_refs:
log('removing ownership of %s' % value)
nextstate.owned_refs.remove(value)
- return [Transition(self, nextstate, None)] # for now
+ """
+ return [self.make_assignment(stmt.lhs,
+ value,
+ None)]
+
+ def _get_transitions_for_GimpleReturn(self, stmt):
+ #log('stmt.lhs: %r %s' % (stmt.lhs, stmt.lhs))
+ #log('stmt.rhs: %r %s' % (stmt.rhs, stmt.rhs))
+ log('stmt: %r %s' % (stmt, stmt))
+ log('stmt.retval: %r' % stmt.retval)
+
+ rvalue = self.data.eval_expr(stmt.retval)
+ log('rvalue from eval_expr: %r' % rvalue)
+
+ nextstate = self.copy()
+ nextstate.data.return_rvalue = rvalue
+ assert nextstate.data.return_rvalue is not None # ensure termination
+ return [Transition(self, nextstate, 'returning')]
+
+def get_traces(fun):
+ return list(iter_traces(fun, MyState))
def check_refcounts(fun, show_traces):
# Abstract interpretation:
# Walk the CFG, gathering the information we're interested in
+ log('check_refcounts(%r, %r)' % (fun, show_traces))
+
+ sg = StateGraph(fun, log, MyState)
+
if show_traces:
from libcpychecker.visualizations import StateGraphPrettyPrinter
- sg = StateGraph(fun, log, MyState)
sgpp = StateGraphPrettyPrinter(sg)
dot = sgpp.to_dot()
#dot = sgpp.extra_items()
@@ -274,7 +311,7 @@ def check_refcounts(fun, show_traces):
trace.log(my_logger, 'TRACE %i' % i, 0)
# Show trace #0:
- if i == 0:
+ if 0: # i == 0:
from libcpychecker.visualizations import TracePrettyPrinter
tpp = TracePrettyPrinter(fun.cfg, trace)
dot = tpp.to_dot()
diff --git a/libcpychecker/visualizations.py b/libcpychecker/visualizations.py
index 64df793..9241997 100644
--- a/libcpychecker/visualizations.py
+++ b/libcpychecker/visualizations.py
@@ -70,15 +70,29 @@ class StateGraphPrettyPrinter(StatePrettyPrinter):
def state_id(self, state):
return 'state%i' % id(state)
+ def data_state_to_dot_label(self, ds):
+ result = ('<tr> %s %s %s</tr>\n'
+ % (self._dot_td('Expression'),
+ self._dot_td('lvalue'),
+ self._dot_td('rvalue')))
+ for key in ds.region_for_var:
+ region = ds.region_for_var[key]
+ value = ds.value_for_region.get(region, None)
+ result += ('<tr> %s %s %s</tr>\n'
+ % (self._dot_td(key),
+ self._dot_td(region),
+ self._dot_td(value)))
+ return result
+
def state_to_dot_label(self, state):
result = '<table cellborder="0" border="0" cellspacing="0">\n'
# Show data:
- for key in state.data:
- value = state.data[key]
- result += ('<tr> %s %s </tr>\n'
- % (self._dot_td(key + ' : '),
- self._dot_td(value)))
+ print 'state.data: %r' % state.data
+ result += '<tr><td colspan="2"><table border="0" cellborder="1">'
+ result += self.data_state_to_dot_label(state.data)
+ result += '</table></td></tr>'
+
# Show location:
stmt = state.loc.get_stmt()
if stmt:
diff --git a/testcpychecker.py b/testcpychecker.py
index e641985..92a8507 100644
--- a/testcpychecker.py
+++ b/testcpychecker.py
@@ -491,23 +491,6 @@ class RefcountErrorTests(AnalyzerTests):
self.add_module(sm, methods)
return sm
- def test_correct_py_none(self):
- sm = self.make_mod_method('correct_none',
- ' Py_RETURN_NONE;\n')
- self.assertNoErrors(sm)
-
- def test_correct_object_ctor(self):
- sm = self.make_mod_method('correct_object_ctor',
- ' /* This code is correct: the API call returns a \n'
- ' new reference. Use some temporaries to make things more\n'
- ' difficult for the checker.: */\n'
- ' PyObject *tmpA;\n'
- ' PyObject *tmpB;\n'
- ' tmpA = PyLong_FromLong(0x1000);\n'
- ' tmpB = tmpA;\n'
- ' return tmpB;\n')
- self.assertNoErrors(sm)
-
def test_too_many_increfs(self):
sm = self.make_mod_method('too_many_increfs',
' PyObject *tmp;\n'
diff --git a/tests/cpychecker/refcounts/correct_object_ctor/input.c b/tests/cpychecker/refcounts/correct_object_ctor/input.c
new file mode 100644
index 0000000..278ad97
--- /dev/null
+++ b/tests/cpychecker/refcounts/correct_object_ctor/input.c
@@ -0,0 +1,37 @@
+/*
+ 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>
+
+PyObject *
+correct_object_ctor(PyObject *self, PyObject *args)
+{
+ /* This code is correct: the API call returns a
+ new reference (or a NULL for MemoryError). Use some temporaries
+ to make things more difficult for the checker: */
+ PyObject *tmpA;
+ PyObject *tmpB;
+ tmpA = PyLong_FromLong(0x1000);
+ tmpB = tmpA;
+ return tmpB;
+}
+static PyMethodDef test_methods[] = {
+ {"test_method", correct_object_ctor, METH_VARARGS, NULL},
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
diff --git a/tests/cpychecker/refcounts/correct_object_ctor/script.py b/tests/cpychecker/refcounts/correct_object_ctor/script.py
new file mode 100644
index 0000000..d2ee6a2
--- /dev/null
+++ b/tests/cpychecker/refcounts/correct_object_ctor/script.py
@@ -0,0 +1,51 @@
+# -*- 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/>.
+
+import gcc
+from libcpychecker import main, get_traces
+
+def verify_traces(optpass, fun):
+ # Only run in one pass
+ # FIXME: should we be adding our own pass for this?
+ if optpass.name == '*warn_function_return':
+ if fun:
+ traces = get_traces(fun)
+
+ # We should have two traces
+ # print('traces: %r' % traces)
+ assert len(traces) == 2
+
+ # Verify the "success" trace:
+ state = traces[0].states[-1]
+ print('Trace 0:')
+ r = state.data.return_rvalue
+ print(' returned: %r' %r)
+ print(' r->ob_refcnt: %r' % state.data.get_value_of_field_by_region(r, 'ob_refcnt'))
+
+ # Verify the "fail" trace:
+ state = traces[1].states[-1]
+ print('Trace 1:')
+ print(' returned: %r' % state.data.return_rvalue)
+
+gcc.register_callback(gcc.PLUGIN_PASS_EXECUTION,
+ verify_traces)
+
+#main(verify_refcounting=True)
+
+#main(verify_refcounting=True,
+# show_traces=False)
diff --git a/tests/cpychecker/refcounts/correct_object_ctor/stdout.txt b/tests/cpychecker/refcounts/correct_object_ctor/stdout.txt
new file mode 100644
index 0000000..73e1d1b
--- /dev/null
+++ b/tests/cpychecker/refcounts/correct_object_ctor/stdout.txt
@@ -0,0 +1,5 @@
+Trace 0:
+ returned: Region('heap_region_0')
+ r->ob_refcnt: RefcountValue(1)
+Trace 1:
+ returned: NullPtrValue()
diff --git a/tests/cpychecker/refcounts/correct_py_none/input.c b/tests/cpychecker/refcounts/correct_py_none/input.c
new file mode 100644
index 0000000..cd67af4
--- /dev/null
+++ b/tests/cpychecker/refcounts/correct_py_none/input.c
@@ -0,0 +1,31 @@
+/*
+ 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>
+
+PyObject *
+correct_none(PyObject *self, PyObject *args)
+{
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef test_methods[] = {
+ {"test_method", correct_none, METH_VARARGS, NULL},
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
diff --git a/tests/cpychecker/refcounts/correct_py_none/script.py b/tests/cpychecker/refcounts/correct_py_none/script.py
new file mode 100644
index 0000000..96af54b
--- /dev/null
+++ b/tests/cpychecker/refcounts/correct_py_none/script.py
@@ -0,0 +1,39 @@
+# -*- 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/>.
+
+import gcc
+from libcpychecker import main, get_traces
+
+def verify_traces(optpass, fun):
+ # Only run in one pass
+ # FIXME: should we be adding our own pass for this?
+ if optpass.name == '*warn_function_return':
+ if fun:
+ traces = get_traces(fun)
+
+ # We should have a single trace
+ #print('traces: %r' % traces)
+ assert len(traces) == 1
+ state = traces[0].states[-1]
+ print('_Py_NoneStruct.ob_refcnt: %r'
+ % state.data.get_value_of_field_by_varname('_Py_NoneStruct', 'ob_refcnt'))
+
+gcc.register_callback(gcc.PLUGIN_PASS_EXECUTION,
+ verify_traces)
+
+main(verify_refcounting=True)
diff --git a/tests/cpychecker/refcounts/correct_py_none/stdout.txt b/tests/cpychecker/refcounts/correct_py_none/stdout.txt
new file mode 100644
index 0000000..b9f3e10
--- /dev/null
+++ b/tests/cpychecker/refcounts/correct_py_none/stdout.txt
@@ -0,0 +1 @@
+_Py_NoneStruct.ob_refcnt: RefcountValue(1)
12 years, 10 months
[gcc-python-plugin] Syntax-highlight Gimple within CFG dumps
by David Hugh Malcolm
commit de08cdc57a06ace67c8a899951edc43ba0781fa0
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Mon Jul 11 16:23:37 2011 -0400
Syntax-highlight Gimple within CFG dumps
gccutils.py | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
---
diff --git a/gccutils.py b/gccutils.py
index 7c9654d..05425bb 100644
--- a/gccutils.py
+++ b/gccutils.py
@@ -327,7 +327,7 @@ class CfgPrettyPrinter(DotPrettyPrinter):
def stmt_to_html(self, stmt, stmtidx):
text = str(stmt).strip()
- text = self.to_html(text)
+ text = self.code_to_html(text)
bgcolor = None
# Work towards visualization of CPython refcounting rules.
12 years, 10 months
[gcc-python-plugin] Try to scale image
by David Hugh Malcolm
commit 656272690818f954fa54fa6aa94119d9d837ccb3
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Mon Jul 11 13:41:36 2011 -0400
Try to scale image
docs/tables-of-passes.rst | 2 ++
generate-tables-of-passes-rst.py | 2 ++
2 files changed, 4 insertions(+), 0 deletions(-)
---
diff --git a/docs/tables-of-passes.rst b/docs/tables-of-passes.rst
index 83fb0e8..fbb9beb 100644
--- a/docs/tables-of-passes.rst
+++ b/docs/tables-of-passes.rst
@@ -14,6 +14,8 @@ the pass that initially creates the data (if any), the pass that destroys it
PROP_* flags).
.. image:: passes.svg
+ :width: 550px
+ :height: 3302px
:scale: 50%
diff --git a/generate-tables-of-passes-rst.py b/generate-tables-of-passes-rst.py
index d94b2db..154276e 100644
--- a/generate-tables-of-passes-rst.py
+++ b/generate-tables-of-passes-rst.py
@@ -61,6 +61,8 @@ the pass that initially creates the data (if any), the pass that destroys it
PROP_* flags).
.. image:: passes.svg
+ :width: 550px
+ :height: 3302px
:scale: 50%
''')
print("""
12 years, 10 months
[gcc-python-plugin] Try to scale image
by David Hugh Malcolm
commit f950f84025c4b5237b2e7030d77b542662de3475
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Mon Jul 11 13:40:35 2011 -0400
Try to scale image
docs/tables-of-passes.rst | 3 +--
generate-tables-of-passes-rst.py | 3 +--
2 files changed, 2 insertions(+), 4 deletions(-)
---
diff --git a/docs/tables-of-passes.rst b/docs/tables-of-passes.rst
index c916da8..83fb0e8 100644
--- a/docs/tables-of-passes.rst
+++ b/docs/tables-of-passes.rst
@@ -14,8 +14,7 @@ the pass that initially creates the data (if any), the pass that destroys it
PROP_* flags).
.. image:: passes.svg
- :width: 550px
- :height: 3302px
+ :scale: 50%
These tables contain the same information. The diagram and tables were
diff --git a/generate-tables-of-passes-rst.py b/generate-tables-of-passes-rst.py
index 635bf17..d94b2db 100644
--- a/generate-tables-of-passes-rst.py
+++ b/generate-tables-of-passes-rst.py
@@ -61,8 +61,7 @@ the pass that initially creates the data (if any), the pass that destroys it
PROP_* flags).
.. image:: passes.svg
- :width: 550px
- :height: 3302px
+ :scale: 50%
''')
print("""
These tables contain the same information. The diagram and tables were
12 years, 10 months