[gcc-python-plugin] Rigidly distinguish between l-values and r-values in cpychecker
by David Hugh Malcolm
commit 42328f99a46ee8f03defb165129bf766a41afbf3
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Wed Jul 20 12:34:51 2011 -0400
Rigidly distinguish between l-values and r-values in cpychecker
Before this commit, r-values could be instances of:
- AbstractValue
- Region (signifying a non-NULL pointer to that region)
- None (for uninitialized data)
In this commit, introduce two new subclasses of AbstractValue:
- PointerToRegion for non-NULL pointers to Region instances
- UninitializedData
so that all r-values are now AbstractValue instances (and all l-values are
Region instances).
Eliminate unused PtrValue class
Add MissingValue exception, to better track value lookup
Replace State.eval_expr() with State.eval_rvalue(), and add State.eval_lvalue()
Rework selftests as necessary (using the dump_refs=True option to generate
debug information on stdout)
libcpychecker/absinterp.py | 120 ++++++++++++++++----
libcpychecker/refcounts.py | 110 +++++++++++-------
.../cpychecker/refcounts/correct_decref/stdout.txt | 6 +-
.../refcounts/correct_object_ctor/script.py | 2 +-
.../refcounts/correct_object_ctor/stdout.txt | 2 +-
.../cpychecker/refcounts/correct_py_none/script.py | 24 +----
.../refcounts/correct_py_none/stdout.txt | 12 ++-
.../refcounts/fold_conditional/script.py | 40 +------
.../refcounts/fold_conditional/stdout.txt | 28 ++++-
.../refcounts/incorrect_py_none/stdout.txt | 2 +-
tests/cpychecker/refcounts/loop_n_times/stdout.txt | 16 ++--
.../cpychecker/refcounts/missing_decref/stdout.txt | 10 +-
.../refcounts/returning_dead_object/stdout.txt | 10 +-
.../refcounts/too_many_increfs/script.py | 34 +-----
.../refcounts/too_many_increfs/stdout.txt | 18 +++-
.../refcounts/use_after_dealloc/stdout.txt | 6 +-
16 files changed, 249 insertions(+), 191 deletions(-)
---
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index 655d965..1855d01 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -95,6 +95,23 @@ class ConcreteValue(AbstractValue):
if isinstance(self.gcctype, gcc.PointerType):
return self.value == 0
+
+class PointerToRegion(AbstractValue):
+ """A non-NULL pointer value, pointing at a specific Region"""
+ def __init__(self, gcctype, loc, region):
+ AbstractValue.__init__(self, gcctype, loc)
+ assert isinstance(region, Region)
+ self.region = region
+
+ def __str__(self):
+ if self.loc:
+ return '(%s)&%r from %s' % (self.gcctype, self.region, self.loc)
+ else:
+ return '(%s)&%r' % (self.gcctype, self.region)
+
+ def __repr__(self):
+ return 'PointerToRegion(gcctype=%r, loc=%r, region=%r)' % (str(self.gcctype), self.loc, self.region)
+
class DeallocatedMemory(AbstractValue):
def __str__(self):
if self.loc:
@@ -102,6 +119,13 @@ class DeallocatedMemory(AbstractValue):
else:
return 'deallocated memory'
+class UninitializedData(AbstractValue):
+ def __str__(self):
+ if self.loc:
+ return 'uninitialized data at %s' % self.loc
+ else:
+ return 'uninitialized data'
+
class PredictedError(Exception):
pass
@@ -140,11 +164,6 @@ class ReadFromDeallocatedMemory(PredictedError):
return ('reading from deallocated memory at %s: %s'
% (self.stmt.loc, self.value))
-class PtrValue(AbstractValue):
- """An abstract (PyObject*) value"""
- def __init__(self, nonnull):
- self.nonnull = nonnull
-
def describe_stmt(stmt):
if isinstance(stmt, gcc.GimpleCall):
if isinstance(stmt.fn.operand, gcc.FunctionDecl):
@@ -238,6 +257,18 @@ class RegionOnHeap(Region):
def __str__(self):
return '%s allocated at %s' % (self.name, self.alloc_stmt.loc)
+
+class MissingValue(Exception):
+ """
+ The value tracking system couldn't figure out any information about the
+ given region
+ """
+ def __init__(self, region):
+ self.region = region
+
+ def __str__(self):
+ return 'Missing value for %s' % self.region
+
class State:
"""A Location with memory state"""
def __init__(self, loc, region_for_var=None, value_for_region=None, return_rvalue=None):
@@ -306,8 +337,33 @@ class State:
c.return_rvalue = self.return_rvalue
return c
- def eval_expr(self, expr):
- log('eval_expr: %r' % expr)
+ def verify(self):
+ """
+ Perform self-tests to ensure sanity of this State
+ """
+ for k in self.value_for_region:
+ assert isinstance(k, Region)
+ if not isinstance(self.value_for_region[k], AbstractValue):
+ raise TypeError('value for region %r is not an AbstractValue: %r'
+ % (k, self.value_for_region[k]))
+
+ def eval_lvalue(self, expr):
+ """
+ Return the Region for the given expression
+ """
+ log('eval_lvalue: %r %s' % (expr, expr))
+ if isinstance(expr, gcc.VarDecl):
+ region = self.var_region(expr)
+ assert isinstance(region, Region)
+ return region
+ raise NotImplementedError('eval_lvalue: %r %s' % (expr, expr))
+
+ def eval_rvalue(self, expr):
+ """
+ Return the value for the given expression, as an AbstractValue
+ FIXME: also as a Region?
+ """
+ log('eval_rvalue: %r %s' % (expr, expr))
if isinstance(expr, AbstractValue):
return expr
if isinstance(expr, Region):
@@ -316,19 +372,26 @@ class State:
return ConcreteValue(expr.type, None, expr.constant)
if isinstance(expr, gcc.VarDecl):
region = self.var_region(expr)
+ assert isinstance(region, Region)
value = self.get_store(region)
+ assert isinstance(value, AbstractValue)
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)
+ assert isinstance(region, Region)
+ log('got field region for %s: %r' % (expr, region))
value = self.get_store(region)
+ log('got value: %r' % value)
+ assert isinstance(value, AbstractValue)
return value
if isinstance(expr, gcc.AddrExpr):
log(expr.operand)
if isinstance(expr.operand, gcc.VarDecl):
region = self.var_region(expr.operand)
- return region
+ assert isinstance(region, Region)
+ return PointerToRegion(expr.type, None, region)
if expr is None:
return None
return UnknownValue(expr.type, None) # FIXME
@@ -341,14 +404,19 @@ class State:
elif isinstance(lhs, gcc.ComponentRef):
assert isinstance(lhs.field, gcc.FieldDecl)
dest_region = self.get_field_region(lhs)
+ #elif isinstance(lhs, gcc.ArrayRef):
+ # assert isinstance(lhs.field, gcc.FieldDecl)
+ # dest_region = self.get_array_region(lhs)
+ elif isinstance(lhs, gcc.MemRef):
+ # Write through a pointer:
+ dest_region = self.get_mem_region(lhs.operand)
else:
raise NotImplementedError("Don't know how to cope with assignment to %r (%s)"
% (lhs, lhs))
- value = self.eval_expr(rhs)
+ value = self.eval_rvalue(rhs)
log('value: %s %r' % (value, value))
+ assert isinstance(value, AbstractValue)
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)
@@ -377,10 +445,11 @@ class State:
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)
+ ptr = self.eval_rvalue(cr.target.operand) # FIXME
if isinstance(ptr, ConcreteValue) and ptr.value == 0:
raise NullPtrDereference(self, cr)
- return self.make_field_region(ptr, cr.field.name)
+ assert isinstance(ptr, PointerToRegion)
+ return self.make_field_region(ptr.region, cr.field.name)
elif isinstance(cr.target, gcc.VarDecl):
log('bar')
vr = self.var_region(cr.target)
@@ -397,7 +466,12 @@ class State:
# Not found; try default value from parent region:
if region.parent:
- return self.get_store(region.parent)
+ try:
+ return self.get_store(region.parent)
+ except MissingValue:
+ raise MissingValue(region)
+
+ raise MissingValue(region)
def make_heap_region(self, name, stmt):
region = RegionOnHeap(name, stmt)
@@ -434,14 +508,14 @@ class State:
value = self.value_for_region.get(region, None)
return value
- def get_value_of_field_by_region(self, rvalue, field):
- # Lookup rvalue->field
+ def get_value_of_field_by_region(self, region, field):
+ # Lookup region->field
# For use in writing selftests
- log('get_value_of_field_by_region(%r, %r)' % (rvalue, field), 0)
- assert isinstance(rvalue, Region)
+ log('get_value_of_field_by_region(%r, %r)' % (region, field), 0)
+ assert isinstance(region, Region)
assert isinstance(field, str)
- if field in rvalue.fields:
- field_region = rvalue.fields[field]
+ if field in region.fields:
+ field_region = region.fields[field]
return self.value_for_region.get(field_region, None)
return None
@@ -452,7 +526,8 @@ class State:
for local in fun.local_decls:
region = Region('region for %r' % local, stack)
self.region_for_var[local] = region
- self.value_for_region[region] = None # uninitialized
+ self.value_for_region[region] = UninitializedData(local.type, fun.start)
+ self.verify()
def make_assignment(self, lhs, rhs, desc):
log('make_assignment(%r, %r, %r)' % (lhs, rhs, desc))
@@ -610,6 +685,7 @@ def iter_traces(fun, stateclass, prefix=None):
[],
Resources(),
ConcreteValue(get_PyObjectPtr(), fun.start, 0))
+ curstate.init_for_function(fun)
else:
assert isinstance(prefix, Trace)
curstate = prefix.states[-1]
@@ -647,6 +723,8 @@ def iter_traces(fun, stateclass, prefix=None):
if len(transitions) > 0:
result = []
for transition in transitions:
+ transition.dest.verify()
+
# Recurse:
for trace in iter_traces(fun, stateclass, prefix.copy().add(transition)):
result.append(trace)
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 2d2afb9..872ad43 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -85,7 +85,9 @@ class GenericTpDealloc(AbstractValue):
returntype = stmt.fn.type.dereference.type
# Mark the arg as being deallocated:
- region = state.eval_expr(stmt.args[0])
+ value = state.eval_rvalue(stmt.args[0])
+ assert isinstance(value, PointerToRegion)
+ region = value.region
assert isinstance(region, Region)
log('generic tp_dealloc called for %s' % region)
@@ -227,8 +229,13 @@ class MyState(State):
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)
ob_type = success.make_field_region(nonnull, 'ob_type')
- success.value_for_region[ob_type] = typeobjregion
- success.assign(stmt.lhs, nonnull)
+ success.value_for_region[ob_type] = PointerToRegion(get_PyTypeObject().pointer,
+ stmt.loc,
+ typeobjregion)
+ success.assign(stmt.lhs,
+ PointerToRegion(stmt.lhs.type,
+ stmt.loc,
+ nonnull))
success = Transition(self,
success,
'%s() succeeds' % fnname)
@@ -277,7 +284,8 @@ class MyState(State):
# Decl:
# PyObject* PyList_New(Py_ssize_t len)
# Returns a new reference, or raises MemoryError
- lenarg = self.eval_expr(stmt.args[0])
+ lenarg = self.eval_rvalue(stmt.args[0])
+ assert isinstance(lenarg, AbstractValue)
newobj, success, failure = self.impl_object_ctor(stmt,
'PyListObject', 'PyList_Type')
# Set ob_size:
@@ -297,7 +305,7 @@ class MyState(State):
result = []
- arg_list, arg_index, arg_item = [self.eval_expr(arg) for arg in stmt.args]
+ arg_list, arg_index, arg_item = [self.eval_rvalue(arg) for arg in stmt.args]
# Is it really a list?
if 0: # FIXME: check
@@ -317,8 +325,9 @@ class MyState(State):
success = self.make_concrete_return_of(stmt, 0)
# FIXME: update refcounts
# "Steal" a reference to item:
- if isinstance(arg_item, Region):
- success.steal_reference(arg_item)
+ if isinstance(arg_item, PointerToRegion):
+ assert isinstance(arg_item.region, Region)
+ success.steal_reference(arg_item.region)
# and discards a
# reference to an item already in the list at the affected position.
@@ -354,7 +363,7 @@ class MyState(State):
if isinstance(stmt.fn, gcc.VarDecl):
# Calling through a function pointer:
- val = self.eval_expr(stmt.fn)
+ val = self.eval_rvalue(stmt.fn)
log('val: %s' % val)
assert isinstance(val, AbstractValue)
return val.get_transitions_for_function_call(self, stmt)
@@ -428,8 +437,10 @@ class MyState(State):
def eval_condition(self, stmt):
def is_equal(lhs, rhs):
+ assert isinstance(lhs, AbstractValue)
+ assert isinstance(rhs, AbstractValue)
if isinstance(rhs, ConcreteValue):
- if isinstance(lhs, Region) and rhs.value == 0:
+ if isinstance(lhs, PointerToRegion) and rhs.value == 0:
log('ptr to region vs 0: %s is definitely not equal to %s' % (lhs, rhs))
return False
if isinstance(lhs, ConcreteValue):
@@ -441,16 +452,16 @@ class MyState(State):
if lhs.relvalue > rhs.value:
# (Equality is thus not possible for this case)
return False
- if isinstance(rhs, Region):
- if isinstance(lhs, Region):
+ if isinstance(rhs, PointerToRegion):
+ if isinstance(lhs, PointerToRegion):
log('comparing regions: %s %s' % (lhs, rhs))
- return lhs == rhs
+ return lhs.region == rhs.region
# We don't know:
return None
log('eval_condition: %s' % stmt)
- lhs = self.eval_expr(stmt.lhs)
- rhs = self.eval_expr(stmt.rhs)
+ lhs = self.eval_rvalue(stmt.lhs)
+ rhs = self.eval_rvalue(stmt.rhs)
log('eval of lhs: %r' % lhs)
log('eval of rhs: %r' % rhs)
log('stmt.exprcode: %r' % stmt.exprcode)
@@ -467,10 +478,11 @@ class MyState(State):
return UnknownValue(stmt.lhs.type, stmt.loc)
def eval_rhs(self, stmt):
+ log('eval_rhs(%s): %s' % (stmt, stmt.rhs))
rhs = stmt.rhs
if stmt.exprcode == gcc.PlusExpr:
- a = self.eval_expr(rhs[0])
- b = self.eval_expr(rhs[1])
+ a = self.eval_rvalue(rhs[0])
+ b = self.eval_rvalue(rhs[1])
log('a: %r' % a)
log('b: %r' % b)
if isinstance(a, ConcreteValue) and isinstance(b, ConcreteValue):
@@ -478,27 +490,27 @@ class MyState(State):
if isinstance(a, RefcountValue) and isinstance(b, ConcreteValue):
return RefcountValue(a.relvalue + b.value)
- raise NotImplementedError("Don't know how to cope with addition of %r (%s) and %r (%s) at %s"
- % (a, a, b, b, stmt.loc))
+ raise NotImplementedError("Don't know how to cope with addition of\n %r\nand\n %r\nat %s"
+ % (a, b, stmt.loc))
elif stmt.exprcode == gcc.MinusExpr:
- a = self.eval_expr(rhs[0])
- b = self.eval_expr(rhs[1])
+ a = self.eval_rvalue(rhs[0])
+ b = self.eval_rvalue(rhs[1])
log('a: %r' % a)
log('b: %r' % b)
if isinstance(a, RefcountValue) and isinstance(b, ConcreteValue):
return RefcountValue(a.relvalue - b.value)
- raise NotImplementedError("Don't know how to cope with subtraction of %r (%s) and %r (%s) at %s"
- % (a, a, b, b, stmt.loc))
+ raise NotImplementedError("Don't know how to cope with subtraction of\n %r\nand\n %rat %s"
+ % (a, b, stmt.loc))
elif stmt.exprcode == gcc.ComponentRef:
- return self.eval_expr(rhs[0])
+ return self.eval_rvalue(rhs[0])
elif stmt.exprcode == gcc.VarDecl:
- return self.eval_expr(rhs[0])
+ return self.eval_rvalue(rhs[0])
elif stmt.exprcode == gcc.IntegerCst:
- return self.eval_expr(rhs[0])
+ return self.eval_rvalue(rhs[0])
elif stmt.exprcode == gcc.AddrExpr:
- return self.eval_expr(rhs[0])
+ return self.eval_rvalue(rhs[0])
elif stmt.exprcode == gcc.NopExpr:
- return self.eval_expr(rhs[0])
+ return self.eval_rvalue(rhs[0])
else:
raise NotImplementedError("Don't know how to cope with exprcode: %r (%s) at %s"
% (stmt.exprcode, stmt.exprcode, stmt.loc))
@@ -511,6 +523,7 @@ class MyState(State):
value = self.eval_rhs(stmt)
log('value from eval_rhs: %r' % value)
+ assert isinstance(value, AbstractValue)
if isinstance(value, DeallocatedMemory):
raise ReadFromDeallocatedMemory(stmt, value)
@@ -535,8 +548,8 @@ class MyState(State):
log('stmt: %r %s' % (stmt, stmt))
log('stmt.retval: %r' % stmt.retval)
- rvalue = self.eval_expr(stmt.retval)
- log('rvalue from eval_expr: %r' % rvalue)
+ rvalue = self.eval_rvalue(stmt.retval)
+ log('rvalue from eval_rvalue: %r' % rvalue)
nextstate = self.copy()
nextstate.return_rvalue = rvalue
@@ -553,15 +566,26 @@ def dump_traces_to_stdout(traces):
anything that changes e.g. names of temporaries, address of wrapper
objects, etc)
"""
- def dump_object(region, title):
+ def dump_object(rvalue, title):
+ assert isinstance(rvalue, AbstractValue)
print(' %s:' % title)
- print(' repr(): %r' % region)
- print(' str(): %s' % region)
- if isinstance(region, Region):
+ print(' repr(): %r' % rvalue)
+ print(' str(): %s' % rvalue)
+ if isinstance(rvalue, PointerToRegion):
print(' r->ob_refcnt: %r'
- % endstate.get_value_of_field_by_region(region, 'ob_refcnt'))
+ % endstate.get_value_of_field_by_region(rvalue.region, 'ob_refcnt'))
print(' r->ob_type: %r'
- % endstate.get_value_of_field_by_region(region, 'ob_type'))
+ % endstate.get_value_of_field_by_region(rvalue.region, 'ob_type'))
+
+ def dump_region(region, title):
+ assert isinstance(region, Region)
+ print(' %s:' % title)
+ print(' repr(): %r' % region)
+ print(' str(): %s' % region)
+ print(' r->ob_refcnt: %r'
+ % endstate.get_value_of_field_by_region(region, 'ob_refcnt'))
+ print(' r->ob_type: %r'
+ % endstate.get_value_of_field_by_region(region, 'ob_type'))
for i, trace in enumerate(traces):
print('Trace %i:' % i)
@@ -593,11 +617,12 @@ def dump_traces_to_stdout(traces):
if 'ob_refcnt' not in region.fields:
continue
- if region == endstate.return_rvalue:
+ if (isinstance(endstate.return_rvalue, PointerToRegion)
+ and region == endstate.return_rvalue.region):
# (We did the return value above)
continue
- dump_object(region, str(region))
+ dump_region(region, str(region))
# Exception state:
print(' Exception:')
@@ -673,6 +698,7 @@ def check_refcounts(fun, dump_traces=False, show_traces=False):
region = endstate.region_for_var[k]
log('considering ob_refcnt of %r' % region)
+ assert isinstance(region, Region)
# Consider those for which we know something about an "ob_refcnt"
# field:
@@ -685,7 +711,7 @@ def check_refcounts(fun, dump_traces=False, show_traces=False):
# 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:
+ if isinstance(return_value, PointerToRegion) and region == return_value.region:
desc = 'return value'
exp_refcnt = 1
else:
@@ -726,15 +752,15 @@ def check_refcounts(fun, dump_traces=False, show_traces=False):
err = emit_refcount_error('ob_refcnt of %s is %i too low'
% (desc, exp_refcnt - ob_refcnt.relvalue))
# Special-case hint for when None has too low a refcount:
- if isinstance(return_value, RegionForGlobal):
- if return_value.vardecl.name == '_Py_NoneStruct':
+ if isinstance(return_value.region, RegionForGlobal):
+ if return_value.region.vardecl.name == '_Py_NoneStruct':
err.add_note(endstate.get_gcc_loc(fun),
'consider using "Py_RETURN_NONE;"')
# Detect returning a deallocated object:
if return_value:
- if isinstance(return_value, Region):
- rvalue = endstate.value_for_region.get(return_value, None)
+ if isinstance(return_value, PointerToRegion):
+ rvalue = endstate.value_for_region.get(return_value.region, None)
if isinstance(rvalue, DeallocatedMemory):
err = rep.make_error(fun,
endstate.get_gcc_loc(fun),
diff --git a/tests/cpychecker/refcounts/correct_decref/stdout.txt b/tests/cpychecker/refcounts/correct_decref/stdout.txt
index 67637af..bab09d9 100644
--- a/tests/cpychecker/refcounts/correct_decref/stdout.txt
+++ b/tests/cpychecker/refcounts/correct_decref/stdout.txt
@@ -5,10 +5,10 @@ Trace 0:
'taking True path'
'returning'
Return value:
- repr(): RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/correct_decref/input.c', line=30))
- str(): PyLongObject allocated at tests/cpychecker/refcounts/correct_decref/input.c:30
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/correct_decref/input.c', line=30), region=RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/correct_decref/input.c', line=30)))
+ str(): (struct PyObject *)&RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/correct_decref/input.c', line=30)) from tests/cpychecker/refcounts/correct_decref/input.c:30
r->ob_refcnt: RefcountValue(1)
- r->ob_type: RegionForGlobal(gcc.VarDecl('PyLong_Type'))
+ r->ob_type: PointerToRegion(gcctype='PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/correct_decref/input.c', line=30), region=RegionForGlobal(gcc.VarDecl('PyLong_Type')))
Exception:
(PyObject *)0 from tests/cpychecker/refcounts/correct_decref/input.c:28
diff --git a/tests/cpychecker/refcounts/correct_object_ctor/script.py b/tests/cpychecker/refcounts/correct_object_ctor/script.py
index 9999add..695473a 100644
--- a/tests/cpychecker/refcounts/correct_object_ctor/script.py
+++ b/tests/cpychecker/refcounts/correct_object_ctor/script.py
@@ -35,7 +35,7 @@ def verify_traces(optpass, fun):
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'))
+ print(' r->ob_refcnt: %r' % state.get_value_of_field_by_region(r.region, 'ob_refcnt'))
# Verify the "fail" trace:
state = traces[1].states[-1]
diff --git a/tests/cpychecker/refcounts/correct_object_ctor/stdout.txt b/tests/cpychecker/refcounts/correct_object_ctor/stdout.txt
index e315a93..78a5e4f 100644
--- a/tests/cpychecker/refcounts/correct_object_ctor/stdout.txt
+++ b/tests/cpychecker/refcounts/correct_object_ctor/stdout.txt
@@ -1,5 +1,5 @@
Trace 0:
- returned: RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/correct_object_ctor/input.c', line=30))
+ returned: PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/correct_object_ctor/input.c', line=30), region=RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/correct_object_ctor/input.c', line=30)))
r->ob_refcnt: RefcountValue(1)
Trace 1:
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/correct_py_none/script.py b/tests/cpychecker/refcounts/correct_py_none/script.py
index 06097dc..6bb62fd 100644
--- a/tests/cpychecker/refcounts/correct_py_none/script.py
+++ b/tests/cpychecker/refcounts/correct_py_none/script.py
@@ -16,25 +16,7 @@
# along with this program. If not, see
# <http://www.gnu.org/licenses/>.
-import gcc
-from libcpychecker import main, get_traces
+from libcpychecker import main
-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)
+main(verify_refcounting=True,
+ dump_traces=True)
diff --git a/tests/cpychecker/refcounts/correct_py_none/stdout.txt b/tests/cpychecker/refcounts/correct_py_none/stdout.txt
index eb2f3ef..d932dc8 100644
--- a/tests/cpychecker/refcounts/correct_py_none/stdout.txt
+++ b/tests/cpychecker/refcounts/correct_py_none/stdout.txt
@@ -1,2 +1,10 @@
-_Py_NoneStruct.ob_refcnt: RefcountValue(1)
-state.return_rvalue: RegionForGlobal(gcc.VarDecl('_Py_NoneStruct'))
+Trace 0:
+ Transitions:
+ 'returning'
+ Return value:
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=None, region=RegionForGlobal(gcc.VarDecl('_Py_NoneStruct')))
+ str(): (struct PyObject *)&RegionForGlobal(gcc.VarDecl('_Py_NoneStruct'))
+ r->ob_refcnt: RefcountValue(1)
+ r->ob_type: None
+ Exception:
+ (PyObject *)0 from tests/cpychecker/refcounts/correct_py_none/input.c:24
diff --git a/tests/cpychecker/refcounts/fold_conditional/script.py b/tests/cpychecker/refcounts/fold_conditional/script.py
index 8bdc003..058c45f 100644
--- a/tests/cpychecker/refcounts/fold_conditional/script.py
+++ b/tests/cpychecker/refcounts/fold_conditional/script.py
@@ -16,43 +16,7 @@
# 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: there are two different outcomes
- # for the function call, and the subsequent conditional can only
- # work one way in each of those scenarios, so it shouldn't lead
- # to additional 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 that we know "ob_size" of the returned value:
- print(' r->ob_size: %r' % state.get_value_of_field_by_region(r, 'ob_size'))
- # Verify that we know "ob_type" of the returned value:
- print(' r->ob_type: %r' % state.get_value_of_field_by_region(r, 'ob_type'))
-
- # Verify the "fail" trace:
- state = traces[1].states[-1]
- print('Trace 1:')
- print(' returned: %s' % state.return_rvalue)
-
-gcc.register_callback(gcc.PLUGIN_PASS_EXECUTION,
- verify_traces)
-
from libcpychecker import main
main(verify_refcounting=True,
- show_traces=False)
+ show_traces=False,
+ dump_traces=True)
diff --git a/tests/cpychecker/refcounts/fold_conditional/stdout.txt b/tests/cpychecker/refcounts/fold_conditional/stdout.txt
index 70c0719..c49a070 100644
--- a/tests/cpychecker/refcounts/fold_conditional/stdout.txt
+++ b/tests/cpychecker/refcounts/fold_conditional/stdout.txt
@@ -1,7 +1,25 @@
Trace 0:
- returned: RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/fold_conditional/input.c', line=26))
- r->ob_refcnt: RefcountValue(1)
- r->ob_size: ConcreteValue(gcctype='Py_ssize_t', loc=None, value=5L)
- r->ob_type: RegionForGlobal(gcc.VarDecl('PyList_Type'))
+ Transitions:
+ 'PyList_New() succeeds'
+ 'taking False path'
+ 'taking False path'
+ 'taking True path'
+ 'returning'
+ Return value:
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/fold_conditional/input.c', line=26), region=RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/fold_conditional/input.c', line=26)))
+ str(): (struct PyObject *)&RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/fold_conditional/input.c', line=26)) from tests/cpychecker/refcounts/fold_conditional/input.c:26
+ r->ob_refcnt: RefcountValue(1)
+ r->ob_type: PointerToRegion(gcctype='PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/fold_conditional/input.c', line=26), region=RegionForGlobal(gcc.VarDecl('PyList_Type')))
+ Exception:
+ (PyObject *)0 from tests/cpychecker/refcounts/fold_conditional/input.c:24
+
Trace 1:
- returned: (struct PyObject *)0L
+ Transitions:
+ 'PyList_New() fails'
+ 'taking True path'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=None, value=0L)
+ str(): (struct PyObject *)0L
+ Exception:
+ RegionForGlobal(gcc.VarDecl('PyExc_MemoryError'))
diff --git a/tests/cpychecker/refcounts/incorrect_py_none/stdout.txt b/tests/cpychecker/refcounts/incorrect_py_none/stdout.txt
index 2dc6bcc..092b88e 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: RegionForGlobal(gcc.VarDecl('_Py_NoneStruct'))
+state.return_rvalue: PointerToRegion(gcctype='struct PyObject *', loc=None, region=RegionForGlobal(gcc.VarDecl('_Py_NoneStruct')))
diff --git a/tests/cpychecker/refcounts/loop_n_times/stdout.txt b/tests/cpychecker/refcounts/loop_n_times/stdout.txt
index e35ed88..e700715 100644
--- a/tests/cpychecker/refcounts/loop_n_times/stdout.txt
+++ b/tests/cpychecker/refcounts/loop_n_times/stdout.txt
@@ -10,15 +10,15 @@ Trace 0:
'taking True path'
'returning'
Return value:
- repr(): RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/loop_n_times/input.c', line=38))
- str(): PyListObject allocated at tests/cpychecker/refcounts/loop_n_times/input.c:38
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/loop_n_times/input.c', line=38), region=RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/loop_n_times/input.c', line=38)))
+ str(): (struct PyObject *)&RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/loop_n_times/input.c', line=38)) from tests/cpychecker/refcounts/loop_n_times/input.c:38
r->ob_refcnt: RefcountValue(1)
- r->ob_type: RegionForGlobal(gcc.VarDecl('PyList_Type'))
+ r->ob_type: PointerToRegion(gcctype='PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/loop_n_times/input.c', line=38), region=RegionForGlobal(gcc.VarDecl('PyList_Type')))
PyLongObject allocated at tests/cpychecker/refcounts/loop_n_times/input.c:42:
repr(): RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/loop_n_times/input.c', line=42))
str(): PyLongObject allocated at tests/cpychecker/refcounts/loop_n_times/input.c:42
r->ob_refcnt: RefcountValue(0)
- r->ob_type: RegionForGlobal(gcc.VarDecl('PyLong_Type'))
+ r->ob_type: PointerToRegion(gcctype='PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/loop_n_times/input.c', line=42), region=RegionForGlobal(gcc.VarDecl('PyLong_Type')))
Exception:
(PyObject *)0 from tests/cpychecker/refcounts/loop_n_times/input.c:29
@@ -35,10 +35,10 @@ Trace 1:
'calling tp_dealloc on PyLongObject allocated at tests/cpychecker/refcounts/loop_n_times/input.c:42'
'returning'
Return value:
- repr(): RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/loop_n_times/input.c', line=38))
- str(): PyListObject allocated at tests/cpychecker/refcounts/loop_n_times/input.c:38
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/loop_n_times/input.c', line=38), region=RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/loop_n_times/input.c', line=38)))
+ str(): (struct PyObject *)&RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/loop_n_times/input.c', line=38)) from tests/cpychecker/refcounts/loop_n_times/input.c:38
r->ob_refcnt: RefcountValue(1)
- r->ob_type: RegionForGlobal(gcc.VarDecl('PyList_Type'))
+ r->ob_type: PointerToRegion(gcctype='PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/loop_n_times/input.c', line=38), region=RegionForGlobal(gcc.VarDecl('PyList_Type')))
PyLongObject allocated at tests/cpychecker/refcounts/loop_n_times/input.c:42:
repr(): RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/loop_n_times/input.c', line=42))
str(): PyLongObject allocated at tests/cpychecker/refcounts/loop_n_times/input.c:42
@@ -64,7 +64,7 @@ Trace 2:
repr(): RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/loop_n_times/input.c', line=38))
str(): PyListObject allocated at tests/cpychecker/refcounts/loop_n_times/input.c:38
r->ob_refcnt: RefcountValue(0)
- r->ob_type: RegionForGlobal(gcc.VarDecl('PyList_Type'))
+ r->ob_type: PointerToRegion(gcctype='PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/loop_n_times/input.c', line=38), region=RegionForGlobal(gcc.VarDecl('PyList_Type')))
Exception:
RegionForGlobal(gcc.VarDecl('PyExc_MemoryError'))
diff --git a/tests/cpychecker/refcounts/missing_decref/stdout.txt b/tests/cpychecker/refcounts/missing_decref/stdout.txt
index 0d756d4..d92b682 100644
--- a/tests/cpychecker/refcounts/missing_decref/stdout.txt
+++ b/tests/cpychecker/refcounts/missing_decref/stdout.txt
@@ -7,15 +7,15 @@ Trace 0:
'PyList_SetItem() succeeds'
'returning'
Return value:
- repr(): RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/missing_decref/input.c', line=27))
- str(): PyListObject allocated at tests/cpychecker/refcounts/missing_decref/input.c:27
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/missing_decref/input.c', line=27), region=RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/missing_decref/input.c', line=27)))
+ str(): (struct PyObject *)&RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/missing_decref/input.c', line=27)) from tests/cpychecker/refcounts/missing_decref/input.c:27
r->ob_refcnt: RefcountValue(1)
- r->ob_type: RegionForGlobal(gcc.VarDecl('PyList_Type'))
+ r->ob_type: PointerToRegion(gcctype='PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/missing_decref/input.c', line=27), region=RegionForGlobal(gcc.VarDecl('PyList_Type')))
PyLongObject allocated at tests/cpychecker/refcounts/missing_decref/input.c:30:
repr(): RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/missing_decref/input.c', line=30))
str(): PyLongObject allocated at tests/cpychecker/refcounts/missing_decref/input.c:30
r->ob_refcnt: RefcountValue(0)
- r->ob_type: RegionForGlobal(gcc.VarDecl('PyLong_Type'))
+ r->ob_type: PointerToRegion(gcctype='PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/missing_decref/input.c', line=30), region=RegionForGlobal(gcc.VarDecl('PyLong_Type')))
Exception:
(PyObject *)0 from tests/cpychecker/refcounts/missing_decref/input.c:24
@@ -33,7 +33,7 @@ Trace 1:
repr(): RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/missing_decref/input.c', line=27))
str(): PyListObject allocated at tests/cpychecker/refcounts/missing_decref/input.c:27
r->ob_refcnt: RefcountValue(1)
- r->ob_type: RegionForGlobal(gcc.VarDecl('PyList_Type'))
+ r->ob_type: PointerToRegion(gcctype='PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/missing_decref/input.c', line=27), region=RegionForGlobal(gcc.VarDecl('PyList_Type')))
Exception:
RegionForGlobal(gcc.VarDecl('PyExc_MemoryError'))
diff --git a/tests/cpychecker/refcounts/returning_dead_object/stdout.txt b/tests/cpychecker/refcounts/returning_dead_object/stdout.txt
index 3b37db4..7929819 100644
--- a/tests/cpychecker/refcounts/returning_dead_object/stdout.txt
+++ b/tests/cpychecker/refcounts/returning_dead_object/stdout.txt
@@ -5,10 +5,10 @@ Trace 0:
'taking True path'
'returning'
Return value:
- repr(): RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/returning_dead_object/input.c', line=29))
- str(): PyLongObject allocated at tests/cpychecker/refcounts/returning_dead_object/input.c:29
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/returning_dead_object/input.c', line=29), region=RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/returning_dead_object/input.c', line=29)))
+ str(): (struct PyObject *)&RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/returning_dead_object/input.c', line=29)) from tests/cpychecker/refcounts/returning_dead_object/input.c:29
r->ob_refcnt: RefcountValue(0)
- r->ob_type: RegionForGlobal(gcc.VarDecl('PyLong_Type'))
+ r->ob_type: PointerToRegion(gcctype='PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/returning_dead_object/input.c', line=29), region=RegionForGlobal(gcc.VarDecl('PyLong_Type')))
Exception:
(PyObject *)0 from tests/cpychecker/refcounts/returning_dead_object/input.c:27
@@ -20,8 +20,8 @@ Trace 1:
'calling tp_dealloc on PyLongObject allocated at tests/cpychecker/refcounts/returning_dead_object/input.c:29'
'returning'
Return value:
- repr(): RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/returning_dead_object/input.c', line=29))
- str(): PyLongObject allocated at tests/cpychecker/refcounts/returning_dead_object/input.c:29
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/returning_dead_object/input.c', line=29), region=RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/returning_dead_object/input.c', line=29)))
+ str(): (struct PyObject *)&RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/returning_dead_object/input.c', line=29)) from tests/cpychecker/refcounts/returning_dead_object/input.c:29
r->ob_refcnt: None
r->ob_type: None
Exception:
diff --git a/tests/cpychecker/refcounts/too_many_increfs/script.py b/tests/cpychecker/refcounts/too_many_increfs/script.py
index d3e41ea..c41fe62 100644
--- a/tests/cpychecker/refcounts/too_many_increfs/script.py
+++ b/tests/cpychecker/refcounts/too_many_increfs/script.py
@@ -16,36 +16,6 @@
# 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)
+main(verify_refcounting=True,
+ dump_traces=True)
diff --git a/tests/cpychecker/refcounts/too_many_increfs/stdout.txt b/tests/cpychecker/refcounts/too_many_increfs/stdout.txt
index a7654dc..6fe5907 100644
--- a/tests/cpychecker/refcounts/too_many_increfs/stdout.txt
+++ b/tests/cpychecker/refcounts/too_many_increfs/stdout.txt
@@ -1,7 +1,19 @@
Trace 0:
- returned: RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/too_many_increfs/input.c', line=26))
- r->ob_refcnt: RefcountValue(2)
+ Transitions:
+ 'PyLong_FromLong() succeeds'
+ 'returning'
+ Return value:
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/too_many_increfs/input.c', line=26), region=RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/too_many_increfs/input.c', line=26)))
+ str(): (struct PyObject *)&RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/too_many_increfs/input.c', line=26)) from tests/cpychecker/refcounts/too_many_increfs/input.c:26
+ r->ob_refcnt: RefcountValue(2)
+ r->ob_type: PointerToRegion(gcctype='PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/too_many_increfs/input.c', line=26), region=RegionForGlobal(gcc.VarDecl('PyLong_Type')))
+ Exception:
+ (PyObject *)0 from tests/cpychecker/refcounts/too_many_increfs/input.c:24
+
Trace 1:
+ Transitions:
+ 'PyLong_FromLong() fails'
error: NullPtrDereference()
error: dereferencing NULL (tmp->ob_refcnt) at tests/cpychecker/refcounts/too_many_increfs/input.c:30
- returned: None
+ Exception:
+ RegionForGlobal(gcc.VarDecl('PyExc_MemoryError'))
diff --git a/tests/cpychecker/refcounts/use_after_dealloc/stdout.txt b/tests/cpychecker/refcounts/use_after_dealloc/stdout.txt
index 2f99e92..02a6fbf 100644
--- a/tests/cpychecker/refcounts/use_after_dealloc/stdout.txt
+++ b/tests/cpychecker/refcounts/use_after_dealloc/stdout.txt
@@ -5,10 +5,10 @@ Trace 0:
'taking True path'
'returning'
Return value:
- repr(): RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/use_after_dealloc/input.c', line=30))
- str(): PyLongObject allocated at tests/cpychecker/refcounts/use_after_dealloc/input.c:30
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/use_after_dealloc/input.c', line=30), region=RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/use_after_dealloc/input.c', line=30)))
+ str(): (struct PyObject *)&RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/use_after_dealloc/input.c', line=30)) from tests/cpychecker/refcounts/use_after_dealloc/input.c:30
r->ob_refcnt: RefcountValue(1)
- r->ob_type: RegionForGlobal(gcc.VarDecl('PyLong_Type'))
+ r->ob_type: PointerToRegion(gcctype='PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/use_after_dealloc/input.c', line=30), region=RegionForGlobal(gcc.VarDecl('PyLong_Type')))
Exception:
(PyObject *)0 from tests/cpychecker/refcounts/use_after_dealloc/input.c:28
12 years, 10 months
[gcc-python-plugin] Improve error handling within the checker
by David Hugh Malcolm
commit 72d9d21b3742dd656bf69ec92bb34be70e6d5688
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Jul 19 16:39:29 2011 -0400
Improve error handling within the checker
libcpychecker/absinterp.py | 3 ++-
libcpychecker/refcounts.py | 14 +++++++++-----
2 files changed, 11 insertions(+), 6 deletions(-)
---
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index 86b1d23..655d965 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -342,7 +342,8 @@ class State:
assert isinstance(lhs.field, gcc.FieldDecl)
dest_region = self.get_field_region(lhs)
else:
- assert False # FIXME!
+ raise NotImplementedError("Don't know how to cope with assignment to %r (%s)"
+ % (lhs, lhs))
value = self.eval_expr(rhs)
log('value: %s %r' % (value, value))
self.value_for_region[dest_region] = value
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 8823c14..2d2afb9 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -172,7 +172,8 @@ class MyState(State):
elif isinstance(stmt, gcc.GimpleAssign):
return self._get_transitions_for_GimpleAssign(stmt)
else:
- raise "foo"
+ raise NotImplementedError("Don't know how to cope with %r (%s) at %s"
+ % (stmt, stmt, stmt.loc))
def set_exception(self, exc_name):
"""
@@ -476,7 +477,9 @@ class MyState(State):
return ConcreteValue(stmt.lhs.type, stmt.loc, a.value + b.value)
if isinstance(a, RefcountValue) and isinstance(b, ConcreteValue):
return RefcountValue(a.relvalue + b.value)
- raise 'bar'
+
+ raise NotImplementedError("Don't know how to cope with addition of %r (%s) and %r (%s) at %s"
+ % (a, a, b, b, stmt.loc))
elif stmt.exprcode == gcc.MinusExpr:
a = self.eval_expr(rhs[0])
b = self.eval_expr(rhs[1])
@@ -484,7 +487,8 @@ class MyState(State):
log('b: %r' % b)
if isinstance(a, RefcountValue) and isinstance(b, ConcreteValue):
return RefcountValue(a.relvalue - b.value)
- raise 'bar'
+ raise NotImplementedError("Don't know how to cope with subtraction of %r (%s) and %r (%s) at %s"
+ % (a, a, b, b, stmt.loc))
elif stmt.exprcode == gcc.ComponentRef:
return self.eval_expr(rhs[0])
elif stmt.exprcode == gcc.VarDecl:
@@ -496,8 +500,8 @@ class MyState(State):
elif stmt.exprcode == gcc.NopExpr:
return self.eval_expr(rhs[0])
else:
- print stmt.exprcode
- raise 'foo'
+ raise NotImplementedError("Don't know how to cope with exprcode: %r (%s) at %s"
+ % (stmt.exprcode, stmt.exprcode, stmt.loc))
def _get_transitions_for_GimpleAssign(self, stmt):
log('stmt.lhs: %r %s' % (stmt.lhs, stmt.lhs))
12 years, 10 months
[gcc-python-plugin] Don't analyse traces with loops, as a primitive means of ensuring that the analysis terminates
by David Hugh Malcolm
commit aa80d2da40ee1cd19c6e4b7f0614abb72c761b24
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Jul 19 16:18:37 2011 -0400
Don't analyse traces with loops, as a primitive means of ensuring that the analysis terminates
Also, add impl_PyArg_ParseTuple() to ensure that exception state is set for
the failure case
libcpychecker/absinterp.py | 28 +++++-
libcpychecker/refcounts.py | 35 ++++++-
tests/cpychecker/refcounts/loop_n_times/input.c | 68 ++++++++++++
tests/cpychecker/refcounts/loop_n_times/script.py | 22 ++++
tests/cpychecker/refcounts/loop_n_times/stdout.txt | 115 ++++++++++++++++++++
5 files changed, 265 insertions(+), 3 deletions(-)
---
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index 9c13a06..86b1d23 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -184,6 +184,9 @@ class Location:
assert len(self.bb.succs) == 1
return Location.get_block_start(self.bb.succs[0].dest)
+ def __eq__(self, other):
+ return self.bb == other.bb and self.idx == other.idx
+
@classmethod
def get_block_start(cls, bb):
# Don't bother iterating through phi_nodes if there aren't any:
@@ -545,6 +548,15 @@ class Trace:
def final_references(self):
return self.states[-1].owned_refs
+ def has_looped(self):
+ """
+ Is the tail state of the Trace at a location where it's been before?
+ """
+ endstateloc = self.states[-1].loc
+ for state in self.states[0:-1]:
+ if state.loc == endstateloc:
+ return True
+
def true_edge(bb):
for e in bb.succs:
if e.true_value:
@@ -582,8 +594,13 @@ class Resources:
logger('releases: %s' % self._releases, indent + 1)
def iter_traces(fun, stateclass, prefix=None):
- # Traverse the tree of traces of program state
- # FIXME: this code can't cope with loops yet
+ """
+ Traverse the tree of traces of program state, returning a list
+ of Trace instances.
+
+ For now, don't include any traces that contain loops, as a primitive
+ way of ensuring termination of the analysis
+ """
log('iter_traces(%r, %r, %r)' % (fun, stateclass, prefix))
if prefix is None:
prefix = Trace()
@@ -593,11 +610,18 @@ def iter_traces(fun, stateclass, prefix=None):
Resources(),
ConcreteValue(get_PyObjectPtr(), fun.start, 0))
else:
+ assert isinstance(prefix, Trace)
curstate = prefix.states[-1]
if curstate.has_returned():
# This state has returned a value (and hence terminated):
return [prefix]
+ # Stop interpreting when you see a loop, to ensure termination:
+ if prefix.has_looped():
+ log('loop detected; stopping iteration')
+ # Don't return the prefix so far: it is not a complete trace
+ return []
+
# We need the prevstate to handle Phi nodes
if len(prefix.states) > 1:
prevstate = prefix.states[-2]
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 7d048cd..8823c14 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -261,6 +261,16 @@ class MyState(State):
# go down by one:
self.value_for_region[ob_refcnt] = RefcountValue(value.relvalue - 1)
+ def make_transitions_for_fncall(self, stmt, success, failure):
+ assert isinstance(stmt, gcc.GimpleCall)
+ assert isinstance(success, State)
+ assert isinstance(failure, State)
+
+ fnname = stmt.fn.operand.name
+
+ return [Transition(self, success, '%s() succeeds' % fnname),
+ Transition(self, failure, '%s() fails' % fnname)]
+
# Specific Python API function implementations:
def impl_PyList_New(self, stmt):
# Decl:
@@ -317,6 +327,21 @@ class MyState(State):
return result
+ def impl_PyArg_ParseTuple(self, stmt):
+ # Decl:
+ # PyAPI_FUNC(int) PyArg_ParseTuple(PyObject *, const char *, ...) Py_FORMAT_PARSETUPLE(PyArg_ParseTuple, 2, 3);
+ # Also:
+ # #define PyArg_ParseTuple _PyArg_ParseTuple_SizeT
+
+ success = self.make_concrete_return_of(stmt, 1)
+
+ failure = self.make_concrete_return_of(stmt, 0)
+ # Various errors are possible, but a TypeError is always possible
+ # e.g. for the case of the wrong number of arguments:
+ failure.set_exception('PyExc_TypeError')
+
+ return self.make_transitions_for_fncall(stmt, success, failure)
+
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)
@@ -338,9 +363,12 @@ class MyState(State):
log('stmt.fn.operand.name: %r' % stmt.fn.operand.name, 4)
fnname = stmt.fn.operand.name
- if fnname in ('PyList_New', 'PyLong_FromLong', 'PyList_SetItem'):
+ # Hand off to impl_* methods, where these exist:
+ methname = 'impl_%s' % fnname
+ if hasattr(self, methname):
meth = getattr(self, 'impl_%s' % fnname)
return meth(stmt)
+
# Function returning borrowed references:
elif fnname in ('Py_InitModule4_64',):
return [self.make_assignment(stmt.lhs, NonNullPtrValue(1, stmt)),
@@ -444,6 +472,8 @@ class MyState(State):
b = self.eval_expr(rhs[1])
log('a: %r' % a)
log('b: %r' % b)
+ if isinstance(a, ConcreteValue) and isinstance(b, ConcreteValue):
+ return ConcreteValue(stmt.lhs.type, stmt.loc, a.value + b.value)
if isinstance(a, RefcountValue) and isinstance(b, ConcreteValue):
return RefcountValue(a.relvalue + b.value)
raise 'bar'
@@ -463,7 +493,10 @@ class MyState(State):
return self.eval_expr(rhs[0])
elif stmt.exprcode == gcc.AddrExpr:
return self.eval_expr(rhs[0])
+ elif stmt.exprcode == gcc.NopExpr:
+ return self.eval_expr(rhs[0])
else:
+ print stmt.exprcode
raise 'foo'
def _get_transitions_for_GimpleAssign(self, stmt):
diff --git a/tests/cpychecker/refcounts/loop_n_times/input.c b/tests/cpychecker/refcounts/loop_n_times/input.c
new file mode 100644
index 0000000..465831d
--- /dev/null
+++ b/tests/cpychecker/refcounts/loop_n_times/input.c
@@ -0,0 +1,68 @@
+/*
+ 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>
+
+/*
+ Verify that the checker can cope with a loop where we can't know
+ the limit at analysis time
+*/
+
+PyObject *
+loop_n_times(PyObject *self, PyObject *args)
+{
+ int i, count;
+ PyObject *list;
+ PyObject *item;
+
+ if (!PyArg_ParseTuple(args, "i", &count)) {
+ return NULL;
+ }
+
+ list = PyList_New(count);
+ if (!list) {
+ return NULL;
+ }
+ item = PyLong_FromLong(42);
+ if (!item) {
+ Py_DECREF(list);
+ return NULL;
+ }
+
+ /* We can't know the value of "count" at this point: */
+ for (i = 0; i < count; i++) {
+ /* This steals a reference to item, so we need to INCREF it: */
+ Py_INCREF(item);
+ PyList_SetItem(list, 0, item);
+ }
+ Py_DECREF(item);
+ return list;
+}
+static PyMethodDef test_methods[] = {
+ {"test_method", loop_n_times, 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/loop_n_times/script.py b/tests/cpychecker/refcounts/loop_n_times/script.py
new file mode 100644
index 0000000..fdd5ba3
--- /dev/null
+++ b/tests/cpychecker/refcounts/loop_n_times/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/loop_n_times/stdout.txt b/tests/cpychecker/refcounts/loop_n_times/stdout.txt
new file mode 100644
index 0000000..e35ed88
--- /dev/null
+++ b/tests/cpychecker/refcounts/loop_n_times/stdout.txt
@@ -0,0 +1,115 @@
+Trace 0:
+ Transitions:
+ 'PyArg_ParseTuple() succeeds'
+ 'taking False path'
+ 'PyList_New() succeeds'
+ 'taking False path'
+ 'PyLong_FromLong() succeeds'
+ 'taking False path'
+ 'taking False path'
+ 'taking True path'
+ 'returning'
+ Return value:
+ repr(): RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/loop_n_times/input.c', line=38))
+ str(): PyListObject allocated at tests/cpychecker/refcounts/loop_n_times/input.c:38
+ r->ob_refcnt: RefcountValue(1)
+ r->ob_type: RegionForGlobal(gcc.VarDecl('PyList_Type'))
+ PyLongObject allocated at tests/cpychecker/refcounts/loop_n_times/input.c:42:
+ repr(): RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/loop_n_times/input.c', line=42))
+ str(): PyLongObject allocated at tests/cpychecker/refcounts/loop_n_times/input.c:42
+ r->ob_refcnt: RefcountValue(0)
+ r->ob_type: RegionForGlobal(gcc.VarDecl('PyLong_Type'))
+ Exception:
+ (PyObject *)0 from tests/cpychecker/refcounts/loop_n_times/input.c:29
+
+Trace 1:
+ Transitions:
+ 'PyArg_ParseTuple() succeeds'
+ 'taking False path'
+ 'PyList_New() succeeds'
+ 'taking False path'
+ 'PyLong_FromLong() succeeds'
+ 'taking False path'
+ 'taking False path'
+ 'taking False path'
+ 'calling tp_dealloc on PyLongObject allocated at tests/cpychecker/refcounts/loop_n_times/input.c:42'
+ 'returning'
+ Return value:
+ repr(): RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/loop_n_times/input.c', line=38))
+ str(): PyListObject allocated at tests/cpychecker/refcounts/loop_n_times/input.c:38
+ r->ob_refcnt: RefcountValue(1)
+ r->ob_type: RegionForGlobal(gcc.VarDecl('PyList_Type'))
+ PyLongObject allocated at tests/cpychecker/refcounts/loop_n_times/input.c:42:
+ repr(): RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/loop_n_times/input.c', line=42))
+ str(): PyLongObject allocated at tests/cpychecker/refcounts/loop_n_times/input.c:42
+ r->ob_refcnt: None
+ r->ob_type: None
+ Exception:
+ (PyObject *)0 from tests/cpychecker/refcounts/loop_n_times/input.c:29
+
+Trace 2:
+ Transitions:
+ 'PyArg_ParseTuple() succeeds'
+ 'taking False path'
+ 'PyList_New() succeeds'
+ 'taking False path'
+ 'PyLong_FromLong() fails'
+ 'taking True path'
+ 'taking True path'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=None, value=0L)
+ str(): (struct PyObject *)0L
+ PyListObject allocated at tests/cpychecker/refcounts/loop_n_times/input.c:38:
+ repr(): RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/loop_n_times/input.c', line=38))
+ str(): PyListObject allocated at tests/cpychecker/refcounts/loop_n_times/input.c:38
+ r->ob_refcnt: RefcountValue(0)
+ r->ob_type: RegionForGlobal(gcc.VarDecl('PyList_Type'))
+ Exception:
+ RegionForGlobal(gcc.VarDecl('PyExc_MemoryError'))
+
+Trace 3:
+ Transitions:
+ 'PyArg_ParseTuple() succeeds'
+ 'taking False path'
+ 'PyList_New() succeeds'
+ 'taking False path'
+ 'PyLong_FromLong() fails'
+ 'taking True path'
+ 'taking False path'
+ 'calling tp_dealloc on PyListObject allocated at tests/cpychecker/refcounts/loop_n_times/input.c:38'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=None, value=0L)
+ str(): (struct PyObject *)0L
+ PyListObject allocated at tests/cpychecker/refcounts/loop_n_times/input.c:38:
+ repr(): RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/loop_n_times/input.c', line=38))
+ str(): PyListObject allocated at tests/cpychecker/refcounts/loop_n_times/input.c:38
+ r->ob_refcnt: None
+ r->ob_type: None
+ Exception:
+ RegionForGlobal(gcc.VarDecl('PyExc_MemoryError'))
+
+Trace 4:
+ Transitions:
+ 'PyArg_ParseTuple() succeeds'
+ 'taking False path'
+ 'PyList_New() fails'
+ 'taking True path'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=None, value=0L)
+ str(): (struct PyObject *)0L
+ Exception:
+ RegionForGlobal(gcc.VarDecl('PyExc_MemoryError'))
+
+Trace 5:
+ Transitions:
+ 'PyArg_ParseTuple() fails'
+ 'taking True path'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=None, value=0L)
+ str(): (struct PyObject *)0L
+ Exception:
+ RegionForGlobal(gcc.VarDecl('PyExc_TypeError'))
12 years, 10 months
[gcc-python-plugin] Warn about functions that return pointers to deallocated memory
by David Hugh Malcolm
commit 7d73947367d422f0d4fef320a5d01c8309eb407c
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Jul 19 14:48:29 2011 -0400
Warn about functions that return pointers to deallocated memory
libcpychecker/refcounts.py | 12 ++++
.../refcounts/returning_dead_object/input.c | 55 ++++++++++++++++++++
.../refcounts/returning_dead_object/script.py | 24 +++++++++
.../refcounts/returning_dead_object/stderr.txt | 19 +++++++
.../refcounts/returning_dead_object/stdout.txt | 39 ++++++++++++++
5 files changed, 149 insertions(+), 0 deletions(-)
---
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index a64db40..7d048cd 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -694,6 +694,18 @@ def check_refcounts(fun, dump_traces=False, show_traces=False):
err.add_note(endstate.get_gcc_loc(fun),
'consider using "Py_RETURN_NONE;"')
+ # Detect returning a deallocated object:
+ if return_value:
+ if isinstance(return_value, Region):
+ rvalue = endstate.value_for_region.get(return_value, None)
+ if isinstance(rvalue, DeallocatedMemory):
+ err = rep.make_error(fun,
+ endstate.get_gcc_loc(fun),
+ 'returning pointer to deallocated memory')
+ err.add_trace(trace)
+ err.add_note(rvalue.loc,
+ 'memory deallocated here')
+
# Detect failure to set exceptions when returning NULL:
if not trace.err:
if (isinstance(return_value, ConcreteValue)
diff --git a/tests/cpychecker/refcounts/returning_dead_object/input.c b/tests/cpychecker/refcounts/returning_dead_object/input.c
new file mode 100644
index 0000000..37962b7
--- /dev/null
+++ b/tests/cpychecker/refcounts/returning_dead_object/input.c
@@ -0,0 +1,55 @@
+/*
+ Copyright 2011 David Malcolm <dmalcolm(a)redhat.com>
+ Copyright 2011 Red Hat, Inc.
+
+ This is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+*/
+
+#include <Python.h>
+
+/*
+ Verify that the checker warns about functions that return deallocated objects
+*/
+PyObject *
+returning_dead_object(PyObject *self, PyObject *args)
+{
+ /* Create an object: */
+ PyObject *tmp = PyLong_FromLong(0x1000);
+
+ if (!tmp) {
+ return NULL;
+ }
+
+ /*
+ Now decref the object. Depending on what other references are owned
+ on the object, it can reach a refcount of zero, and thus be deallocated.
+ */
+ Py_DECREF(tmp);
+
+ /* This is an error: the object being returned may have been deallocated */
+ return tmp;
+}
+static PyMethodDef test_methods[] = {
+ {"test_method", returning_dead_object, 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/returning_dead_object/script.py b/tests/cpychecker/refcounts/returning_dead_object/script.py
new file mode 100644
index 0000000..e469f23
--- /dev/null
+++ b/tests/cpychecker/refcounts/returning_dead_object/script.py
@@ -0,0 +1,24 @@
+# -*- 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
+
+main(verify_refcounting=True,
+ show_traces=False,
+ dump_traces=True)
diff --git a/tests/cpychecker/refcounts/returning_dead_object/stderr.txt b/tests/cpychecker/refcounts/returning_dead_object/stderr.txt
new file mode 100644
index 0000000..5ebbf01
--- /dev/null
+++ b/tests/cpychecker/refcounts/returning_dead_object/stderr.txt
@@ -0,0 +1,19 @@
+tests/cpychecker/refcounts/returning_dead_object/input.c: In function ‘returning_dead_object’:
+tests/cpychecker/refcounts/returning_dead_object/input.c:43:1: error: ob_refcnt of return value is 1 too low
+tests/cpychecker/refcounts/returning_dead_object/input.c:29:15: note: PyLongObject allocated at: PyObject *tmp = PyLong_FromLong(0x1000);
+tests/cpychecker/refcounts/returning_dead_object/input.c:29:15: note: when PyLong_FromLong() succeeds at: PyObject *tmp = PyLong_FromLong(0x1000);
+tests/cpychecker/refcounts/returning_dead_object/input.c:31:8: note: when taking False path at: if (!tmp) {
+tests/cpychecker/refcounts/returning_dead_object/input.c:39:5: note: reaching: Py_DECREF(tmp);
+tests/cpychecker/refcounts/returning_dead_object/input.c:39:5: note: when taking True path at: Py_DECREF(tmp);
+tests/cpychecker/refcounts/returning_dead_object/input.c:42:5: note: reaching: return tmp;
+tests/cpychecker/refcounts/returning_dead_object/input.c:43:1: note: when returning
+tests/cpychecker/refcounts/returning_dead_object/input.c:43:1: error: returning pointer to deallocated memory
+tests/cpychecker/refcounts/returning_dead_object/input.c:29:15: note: when PyLong_FromLong() succeeds at: PyObject *tmp = PyLong_FromLong(0x1000);
+tests/cpychecker/refcounts/returning_dead_object/input.c:31:8: note: when taking False path at: if (!tmp) {
+tests/cpychecker/refcounts/returning_dead_object/input.c:39:5: note: reaching: Py_DECREF(tmp);
+tests/cpychecker/refcounts/returning_dead_object/input.c:39:5: note: when taking False path at: Py_DECREF(tmp);
+tests/cpychecker/refcounts/returning_dead_object/input.c:39:5: note: reaching: Py_DECREF(tmp);
+tests/cpychecker/refcounts/returning_dead_object/input.c:39:5: note: when calling tp_dealloc on PyLongObject allocated at tests/cpychecker/refcounts/returning_dead_object/input.c:29 at: Py_DECREF(tmp);
+tests/cpychecker/refcounts/returning_dead_object/input.c:42:5: note: reaching: return tmp;
+tests/cpychecker/refcounts/returning_dead_object/input.c:43:1: note: when returning
+tests/cpychecker/refcounts/returning_dead_object/input.c:39:5: note: memory deallocated here
diff --git a/tests/cpychecker/refcounts/returning_dead_object/stdout.txt b/tests/cpychecker/refcounts/returning_dead_object/stdout.txt
new file mode 100644
index 0000000..3b37db4
--- /dev/null
+++ b/tests/cpychecker/refcounts/returning_dead_object/stdout.txt
@@ -0,0 +1,39 @@
+Trace 0:
+ Transitions:
+ 'PyLong_FromLong() succeeds'
+ 'taking False path'
+ 'taking True path'
+ 'returning'
+ Return value:
+ repr(): RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/returning_dead_object/input.c', line=29))
+ str(): PyLongObject allocated at tests/cpychecker/refcounts/returning_dead_object/input.c:29
+ r->ob_refcnt: RefcountValue(0)
+ r->ob_type: RegionForGlobal(gcc.VarDecl('PyLong_Type'))
+ Exception:
+ (PyObject *)0 from tests/cpychecker/refcounts/returning_dead_object/input.c:27
+
+Trace 1:
+ Transitions:
+ 'PyLong_FromLong() succeeds'
+ 'taking False path'
+ 'taking False path'
+ 'calling tp_dealloc on PyLongObject allocated at tests/cpychecker/refcounts/returning_dead_object/input.c:29'
+ 'returning'
+ Return value:
+ repr(): RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/returning_dead_object/input.c', line=29))
+ str(): PyLongObject allocated at tests/cpychecker/refcounts/returning_dead_object/input.c:29
+ r->ob_refcnt: None
+ r->ob_type: None
+ Exception:
+ (PyObject *)0 from tests/cpychecker/refcounts/returning_dead_object/input.c:27
+
+Trace 2:
+ Transitions:
+ 'PyLong_FromLong() fails'
+ 'taking True path'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=None, value=0L)
+ str(): (struct PyObject *)0L
+ Exception:
+ RegionForGlobal(gcc.VarDecl('PyExc_MemoryError'))
12 years, 10 months
[gcc-python-plugin] Warn about code that accesses deallocated memory
by David Hugh Malcolm
commit 1dd505bf06c3a4b78bcce68edfa87073421815f8
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Tue Jul 19 13:55:30 2011 -0400
Warn about code that accesses deallocated memory
Add utility function gccutils.get_field_by_name()
Allow an AbstractValue to have a None gcctype, so that we can have abstract
values set within a region and inherited by child regions (and thus all share
a single "deallocated memory" value); implement this in get_store()
Support calling code through a function pointer, by adding
get_transitions_for_function_call() to AbstractValue
Add GenericTpDealloc abstract value for function pointers for the tp_dealloc
field of PyTypeObject, which implements the above by marking the region as
deallocated (using a new DeallocatedMemory abstract value)
Add a ReadFromDeallocatedMemory exception
Add a test case for the above
docs/basics.rst | 7 ++
gccutils.py | 7 ++
libcpychecker/absinterp.py | 64 +++++++++++++++++---
libcpychecker/refcounts.py | 59 ++++++++++++++++++-
.../cpychecker/refcounts/use_after_dealloc/input.c | 58 ++++++++++++++++++
.../refcounts/use_after_dealloc/script.py | 23 +++++++
.../refcounts/use_after_dealloc/stderr.txt | 9 +++
.../refcounts/use_after_dealloc/stdout.txt | 40 ++++++++++++
8 files changed, 258 insertions(+), 9 deletions(-)
---
diff --git a/docs/basics.rst b/docs/basics.rst
index 3ef48cb..0f7d61f 100644
--- a/docs/basics.rst
+++ b/docs/basics.rst
@@ -483,6 +483,13 @@ Global data access
This function is only available when the plugin is built against
gcc 4.6 and later, not in 4.5
+.. py:function:: gccutils.get_field_by_name(decl, name)
+
+ Given one of a :py:class:`gcc.RecordType`, :py:class:`gcc.UnionType`, or
+ :py:class:`gcc.QualUnionType`, along with a string `name`, look for a
+ field with that name within the given struct or union, returning it as a
+ :py:class:`gcc.FieldDecl`, or None if it wasn't found
+
Working with source code
========================
diff --git a/gccutils.py b/gccutils.py
index c1f89e6..0069430 100644
--- a/gccutils.py
+++ b/gccutils.py
@@ -22,6 +22,13 @@ def get_src_for_loc(loc):
import linecache
return linecache.getline(loc.file, loc.line).rstrip()
+def get_field_by_name(typeobj, name):
+ assert isinstance(typeobj,
+ (gcc.RecordType, gcc.UnionType, gcc.QualUnionType))
+ for field in typeobj.fields:
+ if field.name == name:
+ return field
+
if hasattr(gcc, 'get_translation_units'):
# GCC 4.6 and later:
def get_global_typedef(name):
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index d4bb3eb..9c13a06 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -25,27 +25,47 @@ from libcpychecker.types import *
class AbstractValue:
def __init__(self, gcctype, loc):
- assert isinstance(gcctype, gcc.Type)
+ if gcctype:
+ assert isinstance(gcctype, gcc.Type)
if loc:
assert isinstance(loc, gcc.Location)
self.gcctype = gcctype
self.loc = loc
def __str__(self):
- if self.loc:
- return '%s from %s' % (self.gcctype, self.loc)
+ if self.gcctype:
+ result = '%s' % self.gcctype
else:
- return '%' % self.gcctype
+ result = 'unknown type'
+ if self.loc:
+ result += ' from %s' % self.loc
+ return result
def __repr__(self):
- return 'AbstractValue(gcctype=%r, loc=%r)' % (str(self.gcctype), self.loc)
+ return ('%s(gcctype=%r, loc=%r)'
+ % (self.__class__.__name__, str(self.gcctype), self.loc))
+
+ def get_transitions_for_function_call(self, state, stmt):
+ """
+ For use for handling function pointers. Return a list of Transition
+ instances giving the outcome of calling this function ptr value
+ """
+ assert isinstance(state, State)
+ assert isinstance(stmt, gcc.GimpleCall)
+ returntype = stmt.fn.type.dereference.type
+ return [state.make_assignment(stmt.lhs,
+ UnknownValue(returntype, stmt.loc),
+ 'calling %s' % self)]
class UnknownValue(AbstractValue):
"""
A value that we know nothing about
"""
def __str__(self):
- return 'unknown %s from %s' % (self.gcctype, self.loc)
+ if self.gcctype:
+ return 'unknown %s from %s' % (self.gcctype, self.loc)
+ else:
+ return 'unknown value from %s' % (self.gcctype, self.loc)
def __repr__(self):
return 'UnknownValue(gcctype=%r, loc=%r)' % (self.gcctype, self.loc)
@@ -71,6 +91,17 @@ class ConcreteValue(AbstractValue):
def __repr__(self):
return 'ConcreteValue(gcctype=%r, loc=%r, value=%r)' % (str(self.gcctype), self.loc, self.value)
+ def is_null_ptr(self):
+ if isinstance(self.gcctype, gcc.PointerType):
+ return self.value == 0
+
+class DeallocatedMemory(AbstractValue):
+ def __str__(self):
+ if self.loc:
+ return 'memory deallocated at %s' % self.loc
+ else:
+ return 'deallocated memory'
+
class PredictedError(Exception):
pass
@@ -98,6 +129,17 @@ class NullPtrDereference(PredictedError):
return ('dereferencing NULL (%s) at %s'
% (self.cr, self.state.loc.get_stmt().loc))
+class ReadFromDeallocatedMemory(PredictedError):
+ def __init__(self, stmt, value):
+ assert isinstance(stmt, gcc.Gimple)
+ assert isinstance(value, DeallocatedMemory)
+ self.stmt = stmt
+ self.value = value
+
+ def __str__(self):
+ return ('reading from deallocated memory at %s: %s'
+ % (self.stmt.loc, self.value))
+
class PtrValue(AbstractValue):
"""An abstract (PyObject*) value"""
def __init__(self, nonnull):
@@ -346,7 +388,12 @@ class State:
def get_store(self, region):
assert isinstance(region, Region)
# self.log(log, 0)
- return self.value_for_region[region]
+ if region in self.value_for_region:
+ return self.value_for_region[region]
+
+ # Not found; try default value from parent region:
+ if region.parent:
+ return self.get_store(region.parent)
def make_heap_region(self, name, stmt):
region = RegionOnHeap(name, stmt)
@@ -409,7 +456,8 @@ class State:
assert isinstance(desc, str)
new = self.copy()
new.loc = self.loc.next_loc()
- new.assign(lhs, rhs)
+ if lhs:
+ new.assign(lhs, rhs)
return Transition(self, new, desc)
def update_loc(self, newloc):
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 8a0c12e..a64db40 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -74,6 +74,43 @@ class RefcountValue(AbstractValue):
def __repr__(self):
return 'RefcountValue(%i)' % self.relvalue
+class GenericTpDealloc(AbstractValue):
+ """
+ A function pointer that points to a "typical" tp_dealloc callback
+ i.e. one that frees up the underlying memory
+ """
+ def get_transitions_for_function_call(self, state, stmt):
+ assert isinstance(state, State)
+ assert isinstance(stmt, gcc.GimpleCall)
+ returntype = stmt.fn.type.dereference.type
+
+ # Mark the arg as being deallocated:
+ region = state.eval_expr(stmt.args[0])
+ assert isinstance(region, Region)
+ log('generic tp_dealloc called for %s' % region)
+
+ # Get the description of the region before trashing it:
+ desc = 'calling tp_dealloc on %s' % region
+ result = state.make_assignment(stmt.lhs,
+ UnknownValue(returntype, stmt.loc),
+ 'calling tp_dealloc on %s' % region)
+ new = state.copy()
+ new.loc = state.loc.next_loc()
+
+ # Mark the region as deallocated
+ # Since regions are shared with other states, we have to set this up
+ # for this state by assigning it with a special "DeallocatedMemory"
+ # value
+ # Clear the value for any fields within the region:
+ for k, v in region.fields.iteritems():
+ if v in new.value_for_region:
+ del new.value_for_region[v]
+ # Set the default value for the whole region to be "DeallocatedMemory"
+ new.region_for_var[region] = region
+ new.value_for_region[region] = DeallocatedMemory(None, stmt.loc)
+
+ return [Transition(state, new, desc)]
+
class MyState(State):
def __init__(self, loc, region_for_var, value_for_region, return_rvalue, owned_refs, resources, exception_rvalue):
State.__init__(self, loc, region_for_var, value_for_region, return_rvalue)
@@ -176,7 +213,15 @@ class MyState(State):
# Allocation and assignment:
success = self.copy()
success.loc = self.loc.next_loc()
+
+ # Set up type object:
typeobjregion = success.var_region(typeobjdecl)
+ tp_dealloc = success.make_field_region(typeobjregion, 'tp_dealloc')
+ type_of_tp_dealloc = gccutils.get_field_by_name(get_PyTypeObject().type,
+ 'tp_dealloc').type
+ success.value_for_region[tp_dealloc] = GenericTpDealloc(type_of_tp_dealloc,
+ stmt.loc)
+
nonnull = success.make_heap_region(typename, stmt)
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)
@@ -276,9 +321,18 @@ class MyState(State):
log('stmt.lhs: %s %r' % (stmt.lhs, stmt.lhs), 3)
log('stmt.fn: %s %r' % (stmt.fn, stmt.fn), 3)
log('dir(stmt.fn): %s' % dir(stmt.fn), 4)
- log('stmt.fn.operand: %s' % stmt.fn.operand, 4)
+ if hasattr(stmt.fn, 'operand'):
+ log('stmt.fn.operand: %s' % stmt.fn.operand, 4)
returntype = stmt.fn.type.dereference.type
log('returntype: %s' % returntype)
+
+ if isinstance(stmt.fn, gcc.VarDecl):
+ # Calling through a function pointer:
+ val = self.eval_expr(stmt.fn)
+ log('val: %s' % val)
+ assert isinstance(val, AbstractValue)
+ return val.get_transitions_for_function_call(self, stmt)
+
if isinstance(stmt.fn.operand, gcc.FunctionDecl):
log('dir(stmt.fn.operand): %s' % dir(stmt.fn.operand), 4)
log('stmt.fn.operand.name: %r' % stmt.fn.operand.name, 4)
@@ -421,6 +475,9 @@ class MyState(State):
value = self.eval_rhs(stmt)
log('value from eval_rhs: %r' % value)
+ if isinstance(value, DeallocatedMemory):
+ raise ReadFromDeallocatedMemory(stmt, value)
+
nextstate = self.use_next_loc()
"""
if isinstance(stmt.lhs, gcc.MemRef):
diff --git a/tests/cpychecker/refcounts/use_after_dealloc/input.c b/tests/cpychecker/refcounts/use_after_dealloc/input.c
new file mode 100644
index 0000000..216f12f
--- /dev/null
+++ b/tests/cpychecker/refcounts/use_after_dealloc/input.c
@@ -0,0 +1,58 @@
+/*
+ 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>
+
+/*
+ Verify that the checker warns about attempts to use an object
+ after it might have been deallocated
+*/
+PyObject *
+use_after_dealloc(PyObject *self, PyObject *args)
+{
+ /* Create an object: */
+ PyObject *tmp = PyLong_FromLong(0x1000);
+
+ if (!tmp) {
+ return NULL;
+ }
+
+ /*
+ Now decref then incref the object. The object reaches a refcount
+ of zero, and thus is deallocated; and the subsequent INCREF is
+ accessing that deallocated memory:
+ */
+ Py_DECREF(tmp);
+ Py_INCREF(tmp);
+
+ /* This is an error: the object being returned has been deallocated */
+ return tmp;
+}
+static PyMethodDef test_methods[] = {
+ {"test_method", use_after_dealloc, 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/use_after_dealloc/script.py b/tests/cpychecker/refcounts/use_after_dealloc/script.py
new file mode 100644
index 0000000..371a751
--- /dev/null
+++ b/tests/cpychecker/refcounts/use_after_dealloc/script.py
@@ -0,0 +1,23 @@
+# -*- 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,
+ show_traces=False,
+ dump_traces=True)
diff --git a/tests/cpychecker/refcounts/use_after_dealloc/stderr.txt b/tests/cpychecker/refcounts/use_after_dealloc/stderr.txt
new file mode 100644
index 0000000..ded7de1
--- /dev/null
+++ b/tests/cpychecker/refcounts/use_after_dealloc/stderr.txt
@@ -0,0 +1,9 @@
+tests/cpychecker/refcounts/use_after_dealloc/input.c: In function ‘use_after_dealloc’:
+tests/cpychecker/refcounts/use_after_dealloc/input.c:42:5: error: reading from deallocated memory at tests/cpychecker/refcounts/use_after_dealloc/input.c:42: memory deallocated at tests/cpychecker/refcounts/use_after_dealloc/input.c:41
+tests/cpychecker/refcounts/use_after_dealloc/input.c:30:15: note: when PyLong_FromLong() succeeds at: PyObject *tmp = PyLong_FromLong(0x1000);
+tests/cpychecker/refcounts/use_after_dealloc/input.c:32:8: note: when taking False path at: if (!tmp) {
+tests/cpychecker/refcounts/use_after_dealloc/input.c:41:5: note: reaching: Py_DECREF(tmp);
+tests/cpychecker/refcounts/use_after_dealloc/input.c:41:5: note: when taking False path at: Py_DECREF(tmp);
+tests/cpychecker/refcounts/use_after_dealloc/input.c:41:5: note: reaching: Py_DECREF(tmp);
+tests/cpychecker/refcounts/use_after_dealloc/input.c:41:5: note: when calling tp_dealloc on PyLongObject allocated at tests/cpychecker/refcounts/use_after_dealloc/input.c:30 at: Py_DECREF(tmp);
+tests/cpychecker/refcounts/use_after_dealloc/input.c:42:5: note: reaching: Py_INCREF(tmp);
diff --git a/tests/cpychecker/refcounts/use_after_dealloc/stdout.txt b/tests/cpychecker/refcounts/use_after_dealloc/stdout.txt
new file mode 100644
index 0000000..2f99e92
--- /dev/null
+++ b/tests/cpychecker/refcounts/use_after_dealloc/stdout.txt
@@ -0,0 +1,40 @@
+Trace 0:
+ Transitions:
+ 'PyLong_FromLong() succeeds'
+ 'taking False path'
+ 'taking True path'
+ 'returning'
+ Return value:
+ repr(): RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/use_after_dealloc/input.c', line=30))
+ str(): PyLongObject allocated at tests/cpychecker/refcounts/use_after_dealloc/input.c:30
+ r->ob_refcnt: RefcountValue(1)
+ r->ob_type: RegionForGlobal(gcc.VarDecl('PyLong_Type'))
+ Exception:
+ (PyObject *)0 from tests/cpychecker/refcounts/use_after_dealloc/input.c:28
+
+Trace 1:
+ Transitions:
+ 'PyLong_FromLong() succeeds'
+ 'taking False path'
+ 'taking False path'
+ 'calling tp_dealloc on PyLongObject allocated at tests/cpychecker/refcounts/use_after_dealloc/input.c:30'
+ error: ReadFromDeallocatedMemory()
+ error: reading from deallocated memory at tests/cpychecker/refcounts/use_after_dealloc/input.c:42: memory deallocated at tests/cpychecker/refcounts/use_after_dealloc/input.c:41
+ PyLongObject allocated at tests/cpychecker/refcounts/use_after_dealloc/input.c:30:
+ repr(): RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/use_after_dealloc/input.c', line=30))
+ str(): PyLongObject allocated at tests/cpychecker/refcounts/use_after_dealloc/input.c:30
+ r->ob_refcnt: None
+ r->ob_type: None
+ Exception:
+ (PyObject *)0 from tests/cpychecker/refcounts/use_after_dealloc/input.c:28
+
+Trace 2:
+ Transitions:
+ 'PyLong_FromLong() fails'
+ 'taking True path'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=None, value=0L)
+ str(): (struct PyObject *)0L
+ Exception:
+ RegionForGlobal(gcc.VarDecl('PyExc_MemoryError'))
12 years, 10 months
[gcc-python-plugin] Beginning implementing Py_DECREF
by David Hugh Malcolm
commit c6334cde0bb1be28615e3c9ea4cd156f76493ba8
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Mon Jul 18 18:46:33 2011 -0400
Beginning implementing Py_DECREF
Implement subtraction (gcc.MinusExpr) for RefcountValue and ConcreteValue
Tweak comparisons so that comparing an ob_refcnt value and 0 can
prove non-equality for the case where the function still holds references
on an object, thus eliminating impossible calls to tp_dealloc
Add test case that exercises the above:
tests/cpychecker/refcounts/correct_decref
libcpychecker/refcounts.py | 57 ++++++++++++++-----
tests/cpychecker/refcounts/correct_decref/input.c | 58 ++++++++++++++++++++
.../cpychecker/refcounts/correct_decref/script.py | 24 ++++++++
.../cpychecker/refcounts/correct_decref/stdout.txt | 24 ++++++++
4 files changed, 148 insertions(+), 15 deletions(-)
---
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index b0bae23..8a0c12e 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -58,6 +58,13 @@ def stmt_is_return_of_objptr(stmt):
return True
class RefcountValue(AbstractValue):
+ """
+ Value for an ob_refcnt field.
+
+ 'relvalue' is all of the references owned within this function.
+
+ The actual value of ob_refcnt >= relvalue
+ """
def __init__(self, relvalue):
self.relvalue = relvalue
@@ -337,30 +344,42 @@ class MyState(State):
make_transition_for_false(stmt)]
def eval_condition(self, stmt):
- log('eval_condition: %s' % stmt)
- 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)
- if stmt.exprcode == gcc.EqExpr:
+ def is_equal(lhs, rhs):
if isinstance(rhs, ConcreteValue):
if isinstance(lhs, Region) and rhs.value == 0:
+ log('ptr to region vs 0: %s is definitely not equal to %s' % (lhs, rhs))
return False
if isinstance(lhs, ConcreteValue):
+ log('comparing concrete values: %s %s' % (lhs, rhs))
return lhs.value == rhs.value
+ if isinstance(lhs, RefcountValue):
+ log('comparing refcount value %s with concrete value: %s' % (lhs, rhs))
+ # The actual value of ob_refcnt >= lhs.relvalue
+ if lhs.relvalue > rhs.value:
+ # (Equality is thus not possible for this case)
+ return False
if isinstance(rhs, Region):
if isinstance(lhs, Region):
+ log('comparing regions: %s %s' % (lhs, rhs))
return lhs == rhs
+ # We don't know:
+ return None
+
+ log('eval_condition: %s' % stmt)
+ 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)
+ if stmt.exprcode == gcc.EqExpr:
+ result = is_equal(lhs, rhs)
+ if result is not None:
+ return result
elif stmt.exprcode == gcc.NeExpr:
- if isinstance(rhs, ConcreteValue):
- if isinstance(lhs, Region) and rhs.value == 0:
- return True
- if isinstance(lhs, ConcreteValue):
- return lhs.value != rhs.value
- if isinstance(rhs, Region):
- if isinstance(lhs, Region):
- return lhs != rhs
+ result = is_equal(lhs, rhs)
+ if result is not None:
+ return not result
+
log('unable to compare %r with %r' % (lhs, rhs))
return UnknownValue(stmt.lhs.type, stmt.loc)
@@ -374,6 +393,14 @@ class MyState(State):
if isinstance(a, RefcountValue) and isinstance(b, ConcreteValue):
return RefcountValue(a.relvalue + b.value)
raise 'bar'
+ elif stmt.exprcode == gcc.MinusExpr:
+ 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, ConcreteValue):
+ return RefcountValue(a.relvalue - b.value)
+ raise 'bar'
elif stmt.exprcode == gcc.ComponentRef:
return self.eval_expr(rhs[0])
elif stmt.exprcode == gcc.VarDecl:
diff --git a/tests/cpychecker/refcounts/correct_decref/input.c b/tests/cpychecker/refcounts/correct_decref/input.c
new file mode 100644
index 0000000..e8ae2fe
--- /dev/null
+++ b/tests/cpychecker/refcounts/correct_decref/input.c
@@ -0,0 +1,58 @@
+/*
+ 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>
+
+/*
+ Verify that the checker can cope with a Py_DECREF that is provably not
+ acting on the final reference on the object.
+*/
+PyObject *
+correct_decref(PyObject *self, PyObject *args)
+{
+ /* Create an object: */
+ PyObject *tmp = PyLong_FromLong(0x1000);
+
+ if (!tmp) {
+ return NULL;
+ }
+
+ /* Incref it so that we can safely decref it: */
+ Py_INCREF(tmp);
+
+ /*
+ Now decref it: the verifier ought to figure out that it can't
+ tp_dealloc the object, as the refcount can't reach 0:
+ */
+ Py_DECREF(tmp);
+
+ return tmp;
+}
+static PyMethodDef test_methods[] = {
+ {"test_method", correct_decref, 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/correct_decref/script.py b/tests/cpychecker/refcounts/correct_decref/script.py
new file mode 100644
index 0000000..e469f23
--- /dev/null
+++ b/tests/cpychecker/refcounts/correct_decref/script.py
@@ -0,0 +1,24 @@
+# -*- 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
+
+main(verify_refcounting=True,
+ show_traces=False,
+ dump_traces=True)
diff --git a/tests/cpychecker/refcounts/correct_decref/stdout.txt b/tests/cpychecker/refcounts/correct_decref/stdout.txt
new file mode 100644
index 0000000..67637af
--- /dev/null
+++ b/tests/cpychecker/refcounts/correct_decref/stdout.txt
@@ -0,0 +1,24 @@
+Trace 0:
+ Transitions:
+ 'PyLong_FromLong() succeeds'
+ 'taking False path'
+ 'taking True path'
+ 'returning'
+ Return value:
+ repr(): RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/correct_decref/input.c', line=30))
+ str(): PyLongObject allocated at tests/cpychecker/refcounts/correct_decref/input.c:30
+ r->ob_refcnt: RefcountValue(1)
+ r->ob_type: RegionForGlobal(gcc.VarDecl('PyLong_Type'))
+ Exception:
+ (PyObject *)0 from tests/cpychecker/refcounts/correct_decref/input.c:28
+
+Trace 1:
+ Transitions:
+ 'PyLong_FromLong() fails'
+ 'taking True path'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=None, value=0L)
+ str(): (struct PyObject *)0L
+ Exception:
+ RegionForGlobal(gcc.VarDecl('PyExc_MemoryError'))
12 years, 10 months
[gcc-python-plugin] Warn when a function returns NULL without setting the per-thread exception state
by David Hugh Malcolm
commit c1761b5727c622c2e2cec883499037209e86e831
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Mon Jul 18 15:16:44 2011 -0400
Warn when a function returns NULL without setting the per-thread exception state
libcpychecker/absinterp.py | 6 ++-
libcpychecker/refcounts.py | 42 +++++++++++++++++-
.../cpychecker/refcounts/missing_decref/stdout.txt | 6 +++
.../refcounts/not_setting_exception/input.c | 45 ++++++++++++++++++++
.../refcounts/not_setting_exception/script.py | 22 ++++++++++
.../refcounts/not_setting_exception/stderr.txt | 3 +
.../refcounts/not_setting_exception/stdout.txt | 8 ++++
tests/cpychecker/refcounts/object_leak/stderr.txt | 3 +
8 files changed, 131 insertions(+), 4 deletions(-)
---
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index e59e0ec..d4bb3eb 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -542,7 +542,8 @@ def iter_traces(fun, stateclass, prefix=None):
curstate = stateclass(Location.get_block_start(fun.cfg.entry),
None, None, None,
[],
- Resources())
+ Resources(),
+ ConcreteValue(get_PyObjectPtr(), fun.start, 0))
else:
curstate = prefix.states[-1]
if curstate.has_returned():
@@ -603,7 +604,8 @@ class StateGraph:
initial = stateclass(Location.get_block_start(fun.cfg.entry),
None, None, None,
[],
- Resources())
+ Resources(),
+ ConcreteValue(get_PyObjectPtr(), fun.start, 0))
initial.init_for_function(fun)
self.states.append(initial)
self._gather_states(initial, logger)
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 4e39ce8..b0bae23 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -68,10 +68,11 @@ class RefcountValue(AbstractValue):
return 'RefcountValue(%i)' % self.relvalue
class MyState(State):
- def __init__(self, loc, region_for_var, value_for_region, return_rvalue, owned_refs, resources):
+ def __init__(self, loc, region_for_var, value_for_region, return_rvalue, owned_refs, resources, exception_rvalue):
State.__init__(self, loc, region_for_var, value_for_region, return_rvalue)
self.owned_refs = owned_refs
self.resources = resources
+ self.exception_rvalue = exception_rvalue
def copy(self):
return self.__class__(self.loc,
@@ -79,7 +80,8 @@ class MyState(State):
self.value_for_region.copy(),
self.return_rvalue,
self.owned_refs[:],
- self.resources.copy())
+ self.resources.copy(),
+ self.exception_rvalue)
def _extra(self):
return ' %s' % self.owned_refs
@@ -128,6 +130,21 @@ class MyState(State):
else:
raise "foo"
+ def set_exception(self, exc_name):
+ """
+ Given the name of a (PyObject*) global for an exception class, such as
+ the string "PyExc_MemoryError", set the exception state to the
+ (PyObject*) for said exception class.
+
+ The list of standard exception classes can be seen at:
+ http://docs.python.org/c-api/exceptions.html#standard-exceptions
+ """
+ assert isinstance(exc_name, str)
+ exc_decl = gccutils.get_global_vardecl_by_name(exc_name)
+ assert isinstance(exc_decl, gcc.VarDecl)
+ exc_region = self.var_region(exc_decl)
+ self.exception_rvalue = exc_region
+
def impl_object_ctor(self, stmt, typename, typeobjname):
"""
Given a gcc.GimpleCall to a Python API function that returns a
@@ -165,6 +182,7 @@ class MyState(State):
failure = self.make_assignment(stmt.lhs,
ConcreteValue(stmt.lhs.type, stmt.loc, 0),
'%s() fails' % fnname)
+ failure.dest.set_exception('PyExc_MemoryError')
return (nonnull, success, failure)
def make_concrete_return_of(self, stmt, value):
@@ -460,6 +478,10 @@ def dump_traces_to_stdout(traces):
dump_object(region, str(region))
+ # Exception state:
+ print(' Exception:')
+ print(' %s' % endstate.exception_rvalue)
+
if i + 1 < len(traces):
print
@@ -588,6 +610,22 @@ def check_refcounts(fun, dump_traces=False, show_traces=False):
err.add_note(endstate.get_gcc_loc(fun),
'consider using "Py_RETURN_NONE;"')
+ # Detect failure to set exceptions when returning NULL:
+ if not trace.err:
+ if (isinstance(return_value, ConcreteValue)
+ and return_value.value == 0
+ and str(return_value.gcctype)=='struct PyObject *'):
+
+ if (isinstance(endstate.exception_rvalue,
+ ConcreteValue)
+ and endstate.exception_rvalue.value == 0):
+ err = rep.make_error(fun,
+ endstate.get_gcc_loc(fun),
+ 'returning (PyObject*)NULL without setting an exception')
+ err.add_trace(trace)
+
+ # (all traces analysed)
+
if rep.got_errors():
rep.dump_html(fun, '%s-refcount-errors.html' % fun.decl.name)
diff --git a/tests/cpychecker/refcounts/missing_decref/stdout.txt b/tests/cpychecker/refcounts/missing_decref/stdout.txt
index 7dc45b4..0d756d4 100644
--- a/tests/cpychecker/refcounts/missing_decref/stdout.txt
+++ b/tests/cpychecker/refcounts/missing_decref/stdout.txt
@@ -16,6 +16,8 @@ Trace 0:
str(): PyLongObject allocated at tests/cpychecker/refcounts/missing_decref/input.c:30
r->ob_refcnt: RefcountValue(0)
r->ob_type: RegionForGlobal(gcc.VarDecl('PyLong_Type'))
+ Exception:
+ (PyObject *)0 from tests/cpychecker/refcounts/missing_decref/input.c:24
Trace 1:
Transitions:
@@ -32,6 +34,8 @@ Trace 1:
str(): PyListObject allocated at tests/cpychecker/refcounts/missing_decref/input.c:27
r->ob_refcnt: RefcountValue(1)
r->ob_type: RegionForGlobal(gcc.VarDecl('PyList_Type'))
+ Exception:
+ RegionForGlobal(gcc.VarDecl('PyExc_MemoryError'))
Trace 2:
Transitions:
@@ -41,3 +45,5 @@ Trace 2:
Return value:
repr(): ConcreteValue(gcctype='struct PyObject *', loc=None, value=0L)
str(): (struct PyObject *)0L
+ Exception:
+ RegionForGlobal(gcc.VarDecl('PyExc_MemoryError'))
diff --git a/tests/cpychecker/refcounts/not_setting_exception/input.c b/tests/cpychecker/refcounts/not_setting_exception/input.c
new file mode 100644
index 0000000..916191b
--- /dev/null
+++ b/tests/cpychecker/refcounts/not_setting_exception/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>
+
+/*
+ Verify that the checker detects returning NULL without setting an exception
+*/
+PyObject *
+not_setting_exception(PyObject *self, PyObject *args)
+{
+ /*
+ This is an error: we're returning NULL without the thread-local
+ exception state being set:
+ */
+ return NULL;
+}
+static PyMethodDef test_methods[] = {
+ {"test_method", not_setting_exception, 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/not_setting_exception/script.py b/tests/cpychecker/refcounts/not_setting_exception/script.py
new file mode 100644
index 0000000..fdd5ba3
--- /dev/null
+++ b/tests/cpychecker/refcounts/not_setting_exception/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/not_setting_exception/stderr.txt b/tests/cpychecker/refcounts/not_setting_exception/stderr.txt
new file mode 100644
index 0000000..a3351db
--- /dev/null
+++ b/tests/cpychecker/refcounts/not_setting_exception/stderr.txt
@@ -0,0 +1,3 @@
+tests/cpychecker/refcounts/not_setting_exception/input.c: In function ‘not_setting_exception’:
+tests/cpychecker/refcounts/not_setting_exception/input.c:32:5: error: returning (PyObject*)NULL without setting an exception
+tests/cpychecker/refcounts/not_setting_exception/input.c:32:5: note: when returning at: return NULL;
diff --git a/tests/cpychecker/refcounts/not_setting_exception/stdout.txt b/tests/cpychecker/refcounts/not_setting_exception/stdout.txt
new file mode 100644
index 0000000..965b011
--- /dev/null
+++ b/tests/cpychecker/refcounts/not_setting_exception/stdout.txt
@@ -0,0 +1,8 @@
+Trace 0:
+ Transitions:
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=None, value=0L)
+ str(): (struct PyObject *)0L
+ Exception:
+ (PyObject *)0 from tests/cpychecker/refcounts/not_setting_exception/input.c:27
diff --git a/tests/cpychecker/refcounts/object_leak/stderr.txt b/tests/cpychecker/refcounts/object_leak/stderr.txt
index a3076bb..7afc99a 100644
--- a/tests/cpychecker/refcounts/object_leak/stderr.txt
+++ b/tests/cpychecker/refcounts/object_leak/stderr.txt
@@ -3,3 +3,6 @@ tests/cpychecker/refcounts/object_leak/input.c:29:5: error: ob_refcnt of PyLongO
tests/cpychecker/refcounts/object_leak/input.c:26:15: note: PyLongObject allocated at: PyObject *tmp = PyLong_FromLong(0x1000);
tests/cpychecker/refcounts/object_leak/input.c:26:15: note: when PyLong_FromLong() succeeds at: PyObject *tmp = PyLong_FromLong(0x1000);
tests/cpychecker/refcounts/object_leak/input.c:29:5: note: when returning at: return NULL;
+tests/cpychecker/refcounts/object_leak/input.c:29:5: error: returning (PyObject*)NULL without setting an exception
+tests/cpychecker/refcounts/object_leak/input.c:26:15: note: when PyLong_FromLong() succeeds at: PyObject *tmp = PyLong_FromLong(0x1000);
+tests/cpychecker/refcounts/object_leak/input.c:29:5: note: when returning at: return NULL;
12 years, 10 months
[gcc-python-plugin] libcpychecker cleanups
by David Hugh Malcolm
commit e452e8c6cfa3861bbf214cbb9143573d2f656673
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Mon Jul 18 13:55:17 2011 -0400
libcpychecker cleanups
Introduce new files:
libcpychecker/types.py : management of Python extension API types
libcpychecker/utils.py : logging
libcpychecker/PyArg_ParseTuple.py | 39 ++-----------------------
libcpychecker/__init__.py | 6 ++--
libcpychecker/absinterp.py | 3 +-
libcpychecker/types.py | 56 +++++++++++++++++++++++++++++++++++++
libcpychecker/utils.py | 28 ++++++++++++++++++
5 files changed, 92 insertions(+), 40 deletions(-)
---
diff --git a/libcpychecker/PyArg_ParseTuple.py b/libcpychecker/PyArg_ParseTuple.py
index dab815b..c45e2ce 100644
--- a/libcpychecker/PyArg_ParseTuple.py
+++ b/libcpychecker/PyArg_ParseTuple.py
@@ -43,12 +43,11 @@ import sys
const_correctness = True
-def log(msg, indent=0):
- if 0:
- sys.stderr.write('%s%s\n' % (' ' * indent, msg))
-
from gccutils import get_src_for_loc, get_global_typedef
+from libcpychecker.utils import log
+from libcpychecker.types import *
+
def get_const_char_ptr():
return gcc.Type.char().const_equivalent.pointer
@@ -60,9 +59,6 @@ def get_const_char_ptr_ptr():
return (gcc.Type.char().const_equivalent.pointer.pointer,
gcc.Type.char().pointer.pointer)
-def get_Py_ssize_t():
- return get_global_typedef('Py_ssize_t')
-
def get_hash_size_type(with_size_t):
# Was PY_SSIZE_T_CLEAN defined?
if with_size_t:
@@ -75,35 +71,6 @@ class NullPointer:
def describe(self):
return 'NULL'
-# Helper functions for looking up various CPython implementation types.
-def get_Py_buffer():
- return get_global_typedef('Py_buffer')
-
-def Py_UNICODE():
- return get_global_typedef('Py_UNICODE')
-
-def get_PY_LONG_LONG():
- # pyport.h can supply PY_LONG_LONG as a #define, as a typedef, or not at all
- # FIXME
- # If we have "long long", pyport.h uses that.
- # Assume so for now:
- return gcc.Type.long_long()
-
-def get_PyObject():
- return get_global_typedef('PyObject')
-
-def get_PyTypeObject():
- return get_global_typedef('PyTypeObject')
-
-def get_PyStringObject():
- return get_global_typedef('PyStringObject')
-
-def get_PyUnicodeObject():
- return get_global_typedef('PyUnicodeObject')
-
-def get_Py_complex():
- return get_global_typedef('Py_complex')
-
class CExtensionError(Exception):
# Base class for errors discovered by static analysis in C extension code
pass
diff --git a/libcpychecker/__init__.py b/libcpychecker/__init__.py
index e58045f..0035263 100644
--- a/libcpychecker/__init__.py
+++ b/libcpychecker/__init__.py
@@ -16,9 +16,9 @@
# <http://www.gnu.org/licenses/>.
import gcc
-
-from PyArg_ParseTuple import check_pyargs, log
-from refcounts import check_refcounts, get_traces
+from PyArg_ParseTuple import check_pyargs
+from libcpychecker.utils import log
+from libcpychecker.refcounts import check_refcounts, get_traces
def on_pass_execution(optpass, fun,
dump_traces=False,
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index e1f24d2..e59e0ec 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -20,7 +20,8 @@ import gccutils
import sys
from gccutils import get_src_for_loc
from collections import OrderedDict
-from PyArg_ParseTuple import log
+from libcpychecker.utils import log
+from libcpychecker.types import *
class AbstractValue:
def __init__(self, gcctype, loc):
diff --git a/libcpychecker/types.py b/libcpychecker/types.py
new file mode 100644
index 0000000..cfc42dc
--- /dev/null
+++ b/libcpychecker/types.py
@@ -0,0 +1,56 @@
+# 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/>.
+
+"""
+Helper functions for looking up various CPython implementation types.
+"""
+import gcc
+from gccutils import get_global_typedef
+
+def get_Py_ssize_t():
+ return get_global_typedef('Py_ssize_t')
+
+def get_Py_buffer():
+ return get_global_typedef('Py_buffer')
+
+def Py_UNICODE():
+ return get_global_typedef('Py_UNICODE')
+
+def get_PY_LONG_LONG():
+ # pyport.h can supply PY_LONG_LONG as a #define, as a typedef, or not at all
+ # FIXME
+ # If we have "long long", pyport.h uses that.
+ # Assume so for now:
+ return gcc.Type.long_long()
+
+def get_PyObject():
+ return get_global_typedef('PyObject')
+
+def get_PyObjectPtr():
+ return get_global_typedef('PyObject').pointer
+
+def get_PyTypeObject():
+ return get_global_typedef('PyTypeObject')
+
+def get_PyStringObject():
+ return get_global_typedef('PyStringObject')
+
+def get_PyUnicodeObject():
+ return get_global_typedef('PyUnicodeObject')
+
+def get_Py_complex():
+ return get_global_typedef('Py_complex')
diff --git a/libcpychecker/utils.py b/libcpychecker/utils.py
new file mode 100644
index 0000000..dae57c9
--- /dev/null
+++ b/libcpychecker/utils.py
@@ -0,0 +1,28 @@
+# Copyright 2011 David Malcolm <dmalcolm(a)redhat.com>
+# Copyright 2011 Red Hat, Inc.
+#
+# This is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+# Logging
+logfile = None
+
+def log(msg, indent=0):
+ if 0:
+ global logfile
+ if not logfile:
+ logfile = open('log.txt', 'w')
+ logfile.write('%s%s\n' % (' ' * indent, msg))
+ if 0:
+ sys.stderr.write('%s%s\n' % (' ' * indent, msg))
12 years, 10 months
[gcc-python-plugin] Try to make the plugin compile against gcc 4.5
by David Hugh Malcolm
commit 7bb567a2fb8fad5eaeadaeba6cf0b9a2a35fa1b3
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Mon Jul 18 13:42:37 2011 -0400
Try to make the plugin compile against gcc 4.5
docs/basics.rst | 15 +++++++++++++++
gcc-python.c | 10 ++++++++++
gccutils.py | 38 ++++++++++++++++++++------------------
3 files changed, 45 insertions(+), 18 deletions(-)
---
diff --git a/docs/basics.rst b/docs/basics.rst
index b3ec2f7..3ef48cb 100644
--- a/docs/basics.rst
+++ b/docs/basics.rst
@@ -442,6 +442,11 @@ Global data access
units within this invocation of GCC (that's "source code files" for the
layperson).
+ .. note::
+
+ This function is only available when the plugin is built against
+ gcc 4.6 and later, not in 4.5
+
.. py:class:: gcc.TranslationUnitDecl
Subclass of :py:class:`gcc.Tree` representing a compilation unit
@@ -462,12 +467,22 @@ Global data access
that name, returning it as a :py:class:`gcc.TypeDecl`, or None if it
wasn't found
+ .. note::
+
+ This function is only available when the plugin is built against
+ gcc 4.6 and later, not in 4.5
+
.. py:function:: gccutils.get_global_vardecl_by_name(name)
Given a string `name`, look for a C/C++ variable in global scope with
that name, returning it as a :py:class:`gcc.VarDecl`, or None if it
wasn't found
+ .. note::
+
+ This function is only available when the plugin is built against
+ gcc 4.6 and later, not in 4.5
+
Working with source code
========================
diff --git a/gcc-python.c b/gcc-python.c
index 0b148da..363e200 100644
--- a/gcc-python.c
+++ b/gcc-python.c
@@ -530,11 +530,19 @@ gcc_python_maybe_get_identifier(PyObject *self, PyObject *args)
return gcc_python_make_wrapper_tree(t);
}
+
+/*
+ get_translation_units was made globally visible in gcc revision 164331:
+ http://gcc.gnu.org/ml/gcc-cvs/2010-09/msg00625.html
+ http://gcc.gnu.org/viewcvs?view=revision&revision=164331
+*/
+#if BUILDING_GCC_VERSION >= 4006
static PyObject *
gcc_python_get_translation_units(PyObject *self, PyObject *args)
{
return VEC_tree_as_PyList(all_translation_units);
}
+#endif /* BUILDING_GCC_VERSION >= 4006 */
/* Version handling: */
@@ -647,8 +655,10 @@ static PyMethodDef GccMethods[] = {
{"maybe_get_identifier", gcc_python_maybe_get_identifier, METH_VARARGS,
"Get the gcc.IdentifierNode with this name, if it exists, otherwise None"},
+#if BUILDING_GCC_VERSION >= 4006
{"get_translation_units", gcc_python_get_translation_units, METH_VARARGS,
"Get a list of all gcc.TranslationUnitDecl"},
+#endif
/* Version handling: */
{"get_plugin_gcc_version", gcc_python_get_plugin_gcc_version, METH_VARARGS,
diff --git a/gccutils.py b/gccutils.py
index 851e6f7..c1f89e6 100644
--- a/gccutils.py
+++ b/gccutils.py
@@ -22,14 +22,26 @@ def get_src_for_loc(loc):
import linecache
return linecache.getline(loc.file, loc.line).rstrip()
-def get_global_typedef(name):
- # Look up a typedef in global scope by name, returning a gcc.TypeDecl,
- # or None if not found
- for u in gcc.get_translation_units():
- for v in u.block.vars:
- if isinstance(v, gcc.TypeDecl):
- if v.name == name:
- return v
+if hasattr(gcc, 'get_translation_units'):
+ # GCC 4.6 and later:
+ def get_global_typedef(name):
+ # Look up a typedef in global scope by name, returning a gcc.TypeDecl,
+ # or None if not found
+ for u in gcc.get_translation_units():
+ for v in u.block.vars:
+ if isinstance(v, gcc.TypeDecl):
+ if v.name == name:
+ return v
+
+ def get_global_vardecl_by_name(name):
+ # Look up a variable in global scope by name, returning a gcc.VarDecl,
+ # or None if not found
+ result = {}
+ for u in gcc.get_translation_units():
+ for v in u.block.vars:
+ if isinstance(v, gcc.VarDecl):
+ if v.name == name:
+ return v
def get_variables_as_dict():
result = {}
@@ -37,16 +49,6 @@ def get_variables_as_dict():
result[var.decl.name] = var
return result
-def get_global_vardecl_by_name(name):
- # Look up a variable in global scope by name, returning a gcc.VarDecl,
- # or None if not found
- result = {}
- for u in gcc.get_translation_units():
- for v in u.block.vars:
- if isinstance(v, gcc.VarDecl):
- if v.name == name:
- return v
-
def invoke_dot(dot):
from subprocess import Popen, PIPE
12 years, 10 months
[gcc-python-plugin] Generate HTML reports on errors (in addition to text on stderr)
by David Hugh Malcolm
commit 315e7f142da5e15c51ba4aaf25fcdcf56158f707
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Fri Jul 15 15:48:14 2011 -0400
Generate HTML reports on errors (in addition to text on stderr)
Add new file:
libcpychecker/diagnostics.py
containing a higher-level interface on top of gcc.error() et al for capturing
information about an error.
Add HtmlRenderer class, which can dump the captured information in HTML/CSS/JS
form, using pygments to syntax highlight the code, and the jsplumb JavaScript
library to add control-flow arcs (via the HTML <canvas> element) to the
result.
For now, any refcount errors in each function are all gathered and dumped into
a single file (per function), named:
name-of-function-refcount-errors.html
The relevant JavaScript libraries are referenced by URL within the generated
HTML, to keep things selfcontained, and to avoid dealing with packaging them
with the plugin.
The resulting reports work for me with Chromium 12 and Firefox 4
This is all in addition to the regular textual messages on stderr
libcpychecker/absinterp.py | 41 -------
libcpychecker/diagnostics.py | 116 ++++++++++++++++++++
libcpychecker/refcounts.py | 38 ++++---
libcpychecker/visualizations.py | 226 +++++++++++++++++++++++++++++++++++++++
4 files changed, 366 insertions(+), 55 deletions(-)
---
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index 07bece6..e1f24d2 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -653,44 +653,3 @@ class StateGraph:
def extra_text(msg, indent):
sys.stderr.write('%s%s\n' % (' ' * indent, msg))
-def describe_trace(trace, fun):
- # 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:
- srcloc = t.src.get_gcc_loc_or_none()
- if srcloc:
- gcc.inform(t.src.get_gcc_loc(fun),
- ('when %s at: %s'
- % (t.desc, get_src_for_loc(srcloc))))
- else:
- gcc.inform(t.src.get_gcc_loc(fun),
- 'when %s' % t.desc)
-
- if t.src.loc.bb != t.dest.loc.bb:
- # Tell the user where conditionals reach:
- destloc = t.dest.get_gcc_loc_or_none()
- if destloc:
- gcc.inform(destloc,
- 'reaching: %s' % get_src_for_loc(destloc))
- return
- for j in range(len(trace.states)-1):
- state = trace.states[j]
- nextstate = trace.states[j+1]
- stmt = state.loc.get_stmt()
- next_stmt = nextstate.loc.get_stmt()
- if state.loc.bb != nextstate.loc.bb:
- if stmt:
- extra_text('%s: taking %s path at %s'
- % (stmt.loc,
- nextstate.prior_bool,
- get_src_for_loc(stmt.loc)), 1)
- first_loc_in_next_bb = get_first_loc_in_block(nextstate.loc.bb)
- if first_loc_in_next_bb:
- extra_text('%s: reaching here %s'
- % (first_loc_in_next_bb,
- get_src_for_loc(first_loc_in_next_bb)),
- 2)
-
diff --git a/libcpychecker/diagnostics.py b/libcpychecker/diagnostics.py
new file mode 100644
index 0000000..f54858d
--- /dev/null
+++ b/libcpychecker/diagnostics.py
@@ -0,0 +1,116 @@
+# 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/>.
+
+"""
+Error reporting interface, supporting regular GCC messages plus higher-level
+HTML visualizations
+"""
+
+import gcc
+from gccutils import get_src_for_loc
+from libcpychecker.visualizations import HtmlRenderer
+from PyArg_ParseTuple import log
+
+def describe_trace(trace, fun):
+ """
+ Print more details about the path through the function that
+ leads to the error, using gcc.inform()
+ """
+ awaiting_target = None
+ for t in trace.transitions:
+ log('transition: %s' % t)
+ if t.desc:
+ srcloc = t.src.get_gcc_loc_or_none()
+ if srcloc:
+ gcc.inform(t.src.get_gcc_loc(fun),
+ ('when %s at: %s'
+ % (t.desc, get_src_for_loc(srcloc))))
+ else:
+ gcc.inform(t.src.get_gcc_loc(fun),
+ 'when %s' % t.desc)
+
+ if t.src.loc.bb != t.dest.loc.bb:
+ # Tell the user where conditionals reach:
+ destloc = t.dest.get_gcc_loc_or_none()
+ if destloc:
+ gcc.inform(destloc,
+ 'reaching: %s' % get_src_for_loc(destloc))
+
+class Reporter:
+ """
+ Error-reporting interface. Gathers information, sending it to GCC's
+ regular diagnostic interface, but also storing it for e.g. HTML dumps
+ """
+ def __init__(self):
+ self.reports = []
+
+ def make_error(self, fun, loc, msg):
+ assert isinstance(fun, gcc.Function)
+ assert isinstance(loc, gcc.Location)
+ gcc.error(loc, msg)
+
+ err = Report(fun, loc, msg)
+ self.reports.append(err)
+ return err
+
+ def got_errors(self):
+ return self.reports != []
+
+ def to_html(self, fun):
+ # (FIXME: eliminate self.fun from HtmlRenderer and the above arg)
+ r = HtmlRenderer(fun)
+ html = r.make_header()
+ for report in self.reports:
+ html += r.make_report(report)
+ html += r.make_footer()
+ return html
+
+ def dump_html(self, fun, filename):
+ html = self.to_html(fun)
+ with open(filename, 'w') as f:
+ f.write(html)
+
+
+class Report:
+ """
+ Data about a particular bug found by the checker
+ """
+ def __init__(self, fun, loc, msg):
+ self.fun = fun
+ self.loc = loc
+ self.msg = msg
+ self.trace = None
+ self.notes = []
+
+ def add_trace(self, trace):
+ self.trace = trace
+ describe_trace(trace, self.fun)
+
+ def add_note(self, loc, msg):
+ gcc.inform(loc, msg)
+ note = Note(loc, msg)
+ self.notes.append(note)
+ return note
+
+
+class Note:
+ """
+ A note within a report
+ """
+ def __init__(self, loc, msg):
+ self.loc = loc
+ self.msg = msg
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index e8d6b08..4e39ce8 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -26,6 +26,7 @@ import gcc
from gccutils import cfg_to_dot, invoke_dot, get_src_for_loc
from absinterp import *
+from diagnostics import Reporter
from PyArg_ParseTuple import log
def stmt_is_assignment_to_count(stmt):
@@ -479,6 +480,8 @@ def check_refcounts(fun, dump_traces=False, show_traces=False):
log('check_refcounts(%r, %r, %r)' % (fun, dump_traces, show_traces))
+ assert isinstance(fun, gcc.Function)
+
if show_traces:
from libcpychecker.visualizations import StateGraphPrettyPrinter
sg = StateGraph(fun, log, MyState)
@@ -493,15 +496,16 @@ def check_refcounts(fun, dump_traces=False, show_traces=False):
traces = list(traces)
dump_traces_to_stdout(traces)
+ rep = Reporter()
+
for i, trace in enumerate(traces):
trace.log(log, 'TRACE %i' % i, 0)
if trace.err:
# 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.error(trace.err.loc,
- str(trace.err))
- describe_trace(trace, fun)
+ err = rep.make_error(fun, trace.err.loc, str(trace.err))
+ err.add_trace(trace)
# FIXME: in our example this ought to mention where the values came from
continue
# Otherwise, the trace proceeds normally
@@ -551,35 +555,41 @@ def check_refcounts(fun, dump_traces=False, show_traces=False):
# Helper function for when ob_refcnt is wrong:
def emit_refcount_error(msg):
- gcc.error(endstate.get_gcc_loc(fun), msg)
+ err = rep.make_error(fun, endstate.get_gcc_loc(fun), msg)
# For dynamically-allocated objects, indicate where they
# were allocated:
if isinstance(region, RegionOnHeap):
alloc_loc = region.alloc_stmt.loc
if alloc_loc:
- gcc.inform(region.alloc_stmt.loc,
- ('%s allocated at: %s'
- % (region.name, get_src_for_loc(alloc_loc))))
+ err.add_note(region.alloc_stmt.loc,
+ ('%s allocated at: %s'
+ % (region.name,
+ get_src_for_loc(alloc_loc))))
# Summarize the control flow we followed through the function:
- describe_trace(trace, fun)
+ err.add_trace(trace)
+
+ return err
# Here's where we verify the refcount:
if isinstance(ob_refcnt, RefcountValue):
if ob_refcnt.relvalue > exp_refcnt:
# Refcount is too high:
- emit_refcount_error('ob_refcnt of %s is %i too high'
- % (desc, ob_refcnt.relvalue - exp_refcnt))
+ err = emit_refcount_error('ob_refcnt of %s is %i too high'
+ % (desc, ob_refcnt.relvalue - exp_refcnt))
elif ob_refcnt.relvalue < exp_refcnt:
# Refcount is too low:
- emit_refcount_error('ob_refcnt of %s is %i too low'
- % (desc, exp_refcnt - ob_refcnt.relvalue))
+ err = emit_refcount_error('ob_refcnt of %s is %i too low'
+ % (desc, exp_refcnt - ob_refcnt.relvalue))
# Special-case hint for when None has too low a refcount:
if isinstance(return_value, RegionForGlobal):
if return_value.vardecl.name == '_Py_NoneStruct':
- gcc.inform(endstate.get_gcc_loc(fun),
- 'consider using "Py_RETURN_NONE;"')
+ err.add_note(endstate.get_gcc_loc(fun),
+ 'consider using "Py_RETURN_NONE;"')
+
+ if rep.got_errors():
+ rep.dump_html(fun, '%s-refcount-errors.html' % fun.decl.name)
if 0:
dot = cfg_to_dot(fun.cfg)
diff --git a/libcpychecker/visualizations.py b/libcpychecker/visualizations.py
index f44f6f4..b646c46 100644
--- a/libcpychecker/visualizations.py
+++ b/libcpychecker/visualizations.py
@@ -15,6 +15,7 @@
# along with this program. If not, see
# <http://www.gnu.org/licenses/>.
+import gcc
from gccutils import CfgPrettyPrinter, get_src_for_loc
class StatePrettyPrinter(CfgPrettyPrinter):
@@ -167,3 +168,228 @@ class StateGraphPrettyPrinter(StatePrettyPrinter):
# result += self.extra_items()
# result += '}\n';
# return result
+
+class HtmlRenderer:
+ """
+ Render a function as HTML, possibly with annotations
+
+ Uses pygments to syntax-highlight the code.
+
+ The resulting HTML uses jsplumb to add lines indicating control flow:
+ http://code.google.com/p/jsplumb/
+ which requires JavaScript and the HTML <canvas> element
+ """
+ def __init__(self, fun):
+ assert isinstance(fun, gcc.Function)
+ self.fun = fun
+
+ from pygments.styles import get_style_by_name
+ from pygments.formatters import HtmlFormatter
+
+ # Get ready to use Pygments:
+ style = get_style_by_name('default')
+ self.formatter = HtmlFormatter(classprefix='source_')
+
+ self.trace_idx = 0
+
+ def make_header(self):
+ result = '<html>\n'
+ result += ' <head>\n'
+ result += ' <title>%s</title>\n' % self.fun.decl.name
+
+ # CSS defs, as part of the file:
+ result += ''' <style type="text/css">
+.unreached-line {
+ #margin:1em;
+ #border:0.1em dotted #00aa00;
+
+ opacity:1.0;
+ filter:alpha(opacity=0);
+ #color:black;
+}
+
+.reached-line {
+ #margin:1em;
+ #border:0.1em dotted #00aa00;
+ border: 0.1em solid blue;
+ font-weight: bold;
+
+ opacity:1.0;
+ filter:alpha(opacity=0);
+ #color:black;
+}
+
+.reached-lineno {
+ border: 0.1em solid blue;
+ font-weight: bold;
+}
+
+pre {
+ #line-height:200%
+}
+
+._jsPlumb_connector { z-index: -2; }
+._jsPlumb_endpoint { z-index: -1; }
+
+.transition {
+ #border: 0.1em dotted #ddffdd;
+ #padding: 1em;
+ margin-left: 5em;
+ font-family: proportional;
+ font-style: italic;
+ font-size: 90%;
+}
+
+.error {
+ #border: 0.1em dotted #ddffdd;
+ #padding: 1em;
+ margin-left: 5em;
+ color: red;
+ font-family: proportional;
+ font-weight: bold;
+ font-style: italic;
+ font-size: 90%;
+}
+
+.note {
+ #border: 0.1em dotted #ddffdd;
+ #padding: 1em;
+ margin-left: 5em;
+ font-family: proportional;
+ font-weight: bold;
+ font-style: italic;
+ font-size: 90%;
+}
+
+'''
+ result += self.formatter.get_style_defs()
+ result += ' </style>\n'
+
+ result += ' </head>\n'
+
+ result += ' <body>\n'
+
+ # Add scripts for jsplumb:
+ result += '<script type="text/javascript" src="http://explorercanvas.googlecode.com/svn/trunk/excanvas.js"></script>\n'
+ result += '<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.0/jquery.min.js"></script>\n'
+ result += '<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"></script>\n'
+ result += '<script type="text/javascript" src="http://jsplumb.googlecode.com/files/jquery.jsPlumb-1.2.6-all-min.js"></script>\n'
+
+ return result
+
+ def make_report(self, report):
+
+ # Heading:
+ result = '<table>\n'
+ result += ' <tr><td>File:</td> <td><b>%s</b></td></tr>\n' % self.fun.start.file
+ result += ' <tr><td>Function:</td> <td><b>%s</b></td></tr>\n' % self.fun.decl.name
+ result += ' <tr><td>Error:</td> <td><b>%s</b></td></tr>\n' % report.msg
+ result += '</table>\n'
+
+ # Render any trace that we have:
+ if report.trace:
+ result += self.make_html_for_trace(report, report.trace)
+ result += '<hr/>'
+ return result
+
+ def make_html_for_trace(self, report, trace):
+ start_line = self.fun.decl.location.line - 1
+ end_line = self.fun.end.line + 1
+
+ # Figure out which lines get executed.
+ # (Is this the finest granularity we can measure?)
+ reached_lines = set()
+ for state in trace.states:
+ loc = state.get_gcc_loc_or_none()
+ if loc:
+ reached_lines.add(loc.line)
+
+ # Render source code:
+ srcfile = self.fun.start.file
+
+ # Extract the source code for the function:
+ import linecache
+ code = ''
+ for linenum in range(start_line, end_line):
+ code += linecache.getline(srcfile, linenum)
+
+ # Use pygments to convert it all to HTML:
+ from pygments import highlight
+ from pygments.lexers import CLexer
+ html = highlight(code,
+ CLexer(), # FIXME
+ self.formatter)
+
+ # Carve it up by line, adding our own line numbering:
+ # It contains some initial content, leading up to a <pre> element
+ # Break on it, starting "result" with the initial material:
+ html = html.replace('<pre>', '<pre>\n')
+ lines = html.splitlines()
+ result = lines[0]
+
+ # The rest contains the actual source lines:
+ lines = lines[1:]
+ for linenum, line in zip(range(start_line, end_line), lines):
+ # Line number:
+ if linenum in reached_lines:
+ cls = 'reached-lineno'
+ else:
+ cls = 'lineno'
+ result += '<span class="%s">%i</span> ' % (cls, linenum)
+
+ # The body of the line:
+ if linenum in reached_lines:
+ cls = 'reached-line'
+ else:
+ cls = 'unreached-line'
+ result += '<span id="trace%i-line%i" class="%s">' % (self.trace_idx, linenum, cls)
+ result += line
+ result += '</span>\n'
+
+ # Add any comments for this line:
+ for trans in trace.transitions:
+ if trans.desc:
+ src_loc = trans.src.get_gcc_loc_or_none()
+ if src_loc and src_loc.line == linenum:
+ result += '<span class="transition">%s</span>\n' % trans.desc
+ if report.loc.line == linenum:
+ result += '<span class="error">%s</span>\n' % report.msg
+ for note in report.notes:
+ if note.loc.line == linenum:
+ result += '<span class="note">%s</span>\n' % note.msg
+
+ result += '\n'
+ result += '<script type="text/javascript">\n'
+ result += ' jsPlumb.bind("ready", function() {\n'
+ result += ' /* Set up defaults: */\n'
+ result += ' jsPlumb.Defaults.Connector = [ "Bezier", 1 ];\n'
+ result += ' jsPlumb.Defaults.Connector = [ "Straight" ];\n'
+ result += ' jsPlumb.Defaults.PaintStyle = { strokeStyle:"blue", lineWidth:1 };\n'
+ result += ' jsPlumb.Defaults.EndpointStyle = { radius:2, fillStyle:"red" };\n'
+ result += ' jsPlumb.Defaults.Anchors = [ "BottomCenter", "TopCenter" ];\n'
+ #result += ' jsPlumb.Defaults.Anchors = [ "Center", "Center" ];\n'
+ result += '\n'
+ result += ' /* Add lines: */\n'
+ for i, trans in enumerate(trace.transitions):
+ if trans.desc:
+ src_loc = trans.src.get_gcc_loc_or_none()
+ dest_loc = trans.dest.get_gcc_loc_or_none()
+ if src_loc and dest_loc and src_loc != dest_loc:
+ result += (" jsPlumb.connect({source:'trace%i-line%i',\n"
+ " target:'trace%i-line%i',\n"
+ " label:%r,\n"
+ " overlays:[['PlainArrow', { width:10, length:10, location:1.0 }]]\n"
+ " });\n"
+ % (self.trace_idx, src_loc.line,
+ self.trace_idx, dest_loc.line,
+ ''))#trans.desc))
+ result += ' })\n'
+ result += '</script>\n'
+
+ self.trace_idx += 1
+
+ return result
+
+ def make_footer(self):
+ result = ' </body>\n'
+ return result
12 years, 10 months