Skip to content

Commit ef48966

Browse files
committed
Fix source-code locations of return statements
In a function with multiple return statements, they are all combined into a single gcc.GimpleReturn, with a None for its .loc, which led to the source-location at a return being reported as fun.end, which is confusing, see: https://fedorahosted.org/gcc-python-plugin/ticket/58 Add a "lastgccloc" attribute to absinterp.State, updating this whenever building a new state with a new stmt that has a non-None location. States with stmt.loc == None inherit the lastgccloc of the previous state, thus propagating sane locations, and avoiding the need to use fun.end (I hope): the return statements are reported as the location of the assignment to the retval. Manual inspection of the existing testcase results shows that the reported locations are now sane. Added a testcase to specifically test for this behavior: tests/cpychecker/refcounts/multiple-returns Fixes https://fedorahosted.org/gcc-python-plugin/ticket/58
1 parent 43d8275 commit ef48966

File tree

25 files changed

+290
-91
lines changed

25 files changed

+290
-91
lines changed

libcpychecker/absinterp.py

+25-23
Original file line numberDiff line numberDiff line change
@@ -1122,15 +1122,6 @@ def describe_stmt(stmt):
11221122
else:
11231123
return str(stmt.loc)
11241124

1125-
def next_stmt_node(stmtnode):
1126-
if len(stmtnode.succs) != 1:
1127-
raise ValueError('len(stmtnode.succs) == %i at %s'
1128-
% (len(stmtnode.succs), stmtnode))
1129-
edge = list(stmtnode.succs)[0]
1130-
assert edge.srcnode == stmtnode
1131-
assert edge.dstnode != stmtnode
1132-
return edge.dstnode
1133-
11341125
class Region(object):
11351126
__slots__ = ('name', 'parent', 'children', 'fields', )
11361127

@@ -1336,14 +1327,16 @@ class State(object):
13361327
# We can't use the __slots__ optimization here, as we're adding additional
13371328
# per-facet attributes
13381329

1339-
def __init__(self, stmtgraph, stmtnode, facets, region_for_var=None, value_for_region=None,
1330+
def __init__(self, stmtgraph, stmtnode, lastgccloc,
1331+
facets, region_for_var=None, value_for_region=None,
13401332
return_rvalue=None, has_returned=False, not_returning=False):
13411333
check_isinstance(stmtgraph, StmtGraph)
13421334
check_isinstance(stmtnode, StmtNode)
13431335
check_isinstance(facets, dict)
13441336
self.stmtgraph = stmtgraph
13451337
self.fun = stmtgraph.fun
13461338
self.stmtnode = stmtnode
1339+
self.lastgccloc = lastgccloc
13471340
self.facets = facets
13481341

13491342
# Mapping from VarDecl.name to Region:
@@ -1418,6 +1411,7 @@ def log(self, logger):
14181411
def copy(self):
14191412
s_new = State(self.stmtgraph,
14201413
self.stmtnode,
1414+
self.lastgccloc,
14211415
self.facets,
14221416
self.region_for_var.copy(),
14231417
self.value_for_region.copy(),
@@ -1928,18 +1922,29 @@ def mktrans_assignment(self, lhs, rhs, desc):
19281922
log('mktrans_assignment(%r, %r, %r)', lhs, rhs, desc)
19291923
if desc:
19301924
check_isinstance(desc, str)
1931-
new = self.copy()
1932-
new.stmtnode = next_stmt_node(self.stmtnode)
1925+
new = self.use_next_stmt_node()
19331926
if lhs:
19341927
new.assign(lhs, rhs, self.stmtnode.get_gcc_loc())
19351928
return Transition(self, new, desc)
19361929

19371930
def update_stmt_node(self, new_stmt_node):
19381931
new = self.copy()
19391932
new.stmtnode = new_stmt_node
1933+
if new.stmtnode.stmt and new.stmtnode.stmt.loc:
1934+
new.lastgccloc = new.stmtnode.stmt.loc
1935+
else:
1936+
new.lastgccloc = self.lastgccloc
19401937
return new
19411938

19421939
def use_next_stmt_node(self):
1940+
def next_stmt_node(stmtnode):
1941+
if len(stmtnode.succs) != 1:
1942+
raise ValueError('len(stmtnode.succs) == %i at %s'
1943+
% (len(stmtnode.succs), stmtnode))
1944+
edge = list(stmtnode.succs)[0]
1945+
assert edge.srcnode == stmtnode
1946+
assert edge.dstnode != stmtnode
1947+
return edge.dstnode
19431948
new_stmt_node = next_stmt_node(self.stmtnode)
19441949
return self.update_stmt_node(new_stmt_node)
19451950

@@ -1959,7 +1964,8 @@ def get_gcc_loc(self, fun):
19591964
# grrr... not all statements have a non-NULL location
19601965
gccloc = self.stmtnode.get_stmt().loc
19611966
if gccloc is None:
1962-
gccloc = fun.end
1967+
assert self.lastgccloc
1968+
return self.lastgccloc
19631969
return gccloc
19641970
else:
19651971
return fun.end
@@ -2081,8 +2087,7 @@ def mkstate_nop(self, stmt):
20812087
Clone this state (at a function call), updating the location, for
20822088
functions with "void" return type
20832089
"""
2084-
newstate = self.copy()
2085-
newstate.stmtnode = next_stmt_node(self.stmtnode)
2090+
newstate = self.use_next_stmt_node()
20862091
return newstate
20872092

20882093
def mkstate_return_of(self, stmt, v_return):
@@ -2091,8 +2096,7 @@ def mkstate_return_of(self, stmt, v_return):
20912096
setting the result of the call to the given AbstractValue
20922097
"""
20932098
check_isinstance(v_return, AbstractValue)
2094-
newstate = self.copy()
2095-
newstate.stmtnode = next_stmt_node(self.stmtnode)
2099+
newstate = self.use_next_stmt_node()
20962100
if stmt.lhs:
20972101
newstate.assign(stmt.lhs,
20982102
v_return,
@@ -2105,8 +2109,7 @@ def mkstate_concrete_return_of(self, stmt, value):
21052109
setting the result of the call to the given concrete value
21062110
"""
21072111
check_isinstance(value, numeric_types)
2108-
newstate = self.copy()
2109-
newstate.stmtnode = next_stmt_node(self.stmtnode)
2112+
newstate = self.use_next_stmt_node()
21102113
if stmt.lhs:
21112114
newstate.assign(stmt.lhs,
21122115
ConcreteValue(stmt.lhs.type, stmt.loc, value),
@@ -2119,8 +2122,7 @@ def mktrans_nop(self, stmt, fnname):
21192122
effect within our simulation (beyond advancing to the next location).
21202123
[We might subsequently modify the destination state, though]
21212124
"""
2122-
newstate = self.copy()
2123-
newstate.stmtnode = next_stmt_node(self.stmtnode)
2125+
newstate = self.use_next_stmt_node()
21242126
return Transition(self, newstate, 'calling %s()' % fnname)
21252127

21262128

@@ -2641,8 +2643,7 @@ def _get_transitions_for_GimpleAsm(self, stmt):
26412643

26422644
if stmt.string == '':
26432645
# Empty fragment of inline assembler:
2644-
s_next = self.copy()
2645-
s_next.stmtnode = next_stmt_node(self.stmtnode)
2646+
s_next = self.use_next_stmt_node()
26462647
return [Transition(self, s_next, None)]
26472648

26482649
raise NotImplementedError('Unable to handle inline assembler: %s'
@@ -2952,6 +2953,7 @@ def iter_traces(stmtgraph, facets, prefix=None, limits=None, depth=0):
29522953
prefix = Trace()
29532954
curstate = State(stmtgraph,
29542955
stmtgraph.get_entry_nodes()[0],
2956+
None,
29552957
facets,
29562958
None, None, None)
29572959
#Resources())

libcpychecker/refcounts.py

+5-10
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,7 @@ def never_returns(self):
149149
def add_outcome(self, desc, s_new=None):
150150
assert not self._never_returns
151151
if s_new is None:
152-
s_new = self.s_src.copy()
153-
s_new.stmtnode = next_stmt_node(self.s_src.stmtnode)
152+
s_new = self.s_src.use_next_stmt_node()
154153
oc_new = Outcome(self, desc, s_new)
155154
self.outcomes.append(oc_new)
156155
return oc_new
@@ -448,8 +447,7 @@ def get_transitions_for_function_call(self, state, stmt):
448447
result = state.mktrans_assignment(stmt.lhs,
449448
UnknownValue.make(returntype, stmt.loc),
450449
desc)
451-
s_new = state.copy()
452-
s_new.stmtnode = next_stmt_node(state.stmtnode)
450+
s_new = state.use_next_stmt_node()
453451

454452
if region is not None:
455453
# Mark the region as deallocated:
@@ -824,8 +822,7 @@ def mkstate_new_ref(self, stmt, name, typeobjregion=None):
824822
825823
Returns a pair: (newstate, RegionOnHeap for the new object)
826824
"""
827-
newstate = self.state.copy()
828-
newstate.stmtnode = next_stmt_node(self.state.stmtnode)
825+
newstate = self.state.use_next_stmt_node()
829826

830827
r_nonnull = newstate.cpython.make_sane_object(stmt, name,
831828
RefcountValue.new_ref(stmt.loc, None),
@@ -842,8 +839,7 @@ def mkstate_new_ref(self, stmt, name, typeobjregion=None):
842839
def mkstate_borrowed_ref(self, stmt, fnmeta, r_typeobj=None):
843840
"""Make a new State, giving a borrowed ref to some object"""
844841
check_isinstance(fnmeta, FnMeta)
845-
newstate = self.state.copy()
846-
newstate.stmtnode = next_stmt_node(self.state.stmtnode)
842+
newstate = self.state.use_next_stmt_node()
847843

848844
r_nonnull = newstate.cpython.make_sane_object(stmt,
849845
'borrowed reference returned by %s()' % fnmeta.name,
@@ -2484,8 +2480,7 @@ def impl_PyMem_Free(self, stmt, v_ptr):
24842480

24852481
# FIXME: it's unsafe to call repeatedly, or on the wrong memory region
24862482

2487-
s_new = self.state.copy()
2488-
s_new.stmtnode = next_stmt_node(self.state.stmtnode)
2483+
s_new = self.state.use_next_stmt_node()
24892484
desc = None
24902485

24912486
# It's safe to call on NULL

tests/cpychecker/refcounts/PyDict_SetItemString/incorrect/stderr.txt

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
In function 'test':
2-
tests/cpychecker/refcounts/PyDict_SetItemString/incorrect/input.c:58:nn: warning: memory leak: ob_refcnt of '*value' is 1 too high [enabled by default]
2+
tests/cpychecker/refcounts/PyDict_SetItemString/incorrect/input.c:52:nn: warning: memory leak: ob_refcnt of '*value' is 1 too high [enabled by default]
33
tests/cpychecker/refcounts/PyDict_SetItemString/incorrect/input.c:37:nn: note: '*value' was allocated at: value = PyLong_FromLong(1000);
4-
tests/cpychecker/refcounts/PyDict_SetItemString/incorrect/input.c:58:nn: note: was expecting final owned ob_refcnt of '*value' to be 0 since nothing references it but final ob_refcnt is refs: 1 owned, 1 borrowed
4+
tests/cpychecker/refcounts/PyDict_SetItemString/incorrect/input.c:52:nn: note: was expecting final owned ob_refcnt of '*value' to be 0 since nothing references it but final ob_refcnt is refs: 1 owned, 1 borrowed
55
tests/cpychecker/refcounts/PyDict_SetItemString/incorrect/input.c:32:nn: note: when PyDict_New() succeeds at: dict = PyDict_New();
66
tests/cpychecker/refcounts/PyDict_SetItemString/incorrect/input.c:33:nn: note: taking False path at: if (!dict) {
77
tests/cpychecker/refcounts/PyDict_SetItemString/incorrect/input.c:37:nn: note: reaching: value = PyLong_FromLong(1000);
@@ -13,5 +13,5 @@ tests/cpychecker/refcounts/PyDict_SetItemString/incorrect/input.c:42:nn: note: w
1313
tests/cpychecker/refcounts/PyDict_SetItemString/incorrect/input.c:42:nn: note: ob_refcnt is now refs: 1 owned, 1 borrowed
1414
tests/cpychecker/refcounts/PyDict_SetItemString/incorrect/input.c:42:nn: note: taking False path at: if (-1 == PyDict_SetItemString(dict, "key", value)) {
1515
tests/cpychecker/refcounts/PyDict_SetItemString/incorrect/input.c:52:nn: note: reaching: return dict;
16-
tests/cpychecker/refcounts/PyDict_SetItemString/incorrect/input.c:58:nn: note: returning
16+
tests/cpychecker/refcounts/PyDict_SetItemString/incorrect/input.c:52:nn: note: returning
1717
tests/cpychecker/refcounts/PyDict_SetItemString/incorrect/input.c:28:nn: note: graphical error report for function 'test' written out to 'tests/cpychecker/refcounts/PyDict_SetItemString/incorrect/input.c.test-refcount-errors.html'

tests/cpychecker/refcounts/PyList_Append/incorrect-loop/stderr.txt

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
In function 'test':
2-
tests/cpychecker/refcounts/PyList_Append/incorrect-loop/input.c:60:nn: warning: memory leak: ob_refcnt of '*item' is 1 too high [enabled by default]
2+
tests/cpychecker/refcounts/PyList_Append/incorrect-loop/input.c:59:nn: warning: memory leak: ob_refcnt of '*item' is 1 too high [enabled by default]
33
tests/cpychecker/refcounts/PyList_Append/incorrect-loop/input.c:41:nn: note: '*item' was allocated at: item = item_ctor();
4-
tests/cpychecker/refcounts/PyList_Append/incorrect-loop/input.c:60:nn: note: was expecting final owned ob_refcnt of '*item' to be 1 due to object being referenced by: PyListObject.ob_item[0] but final ob_refcnt is refs: 2 owned
4+
tests/cpychecker/refcounts/PyList_Append/incorrect-loop/input.c:59:nn: note: was expecting final owned ob_refcnt of '*item' to be 1 due to object being referenced by: PyListObject.ob_item[0] but final ob_refcnt is refs: 2 owned
55
tests/cpychecker/refcounts/PyList_Append/incorrect-loop/input.c:34:nn: note: when PyList_New() succeeds at: list = PyList_New(0);
66
tests/cpychecker/refcounts/PyList_Append/incorrect-loop/input.c:35:nn: note: taking False path at: if (!list) {
77
tests/cpychecker/refcounts/PyList_Append/incorrect-loop/input.c:38:nn: note: reaching: for (i = 0; i < n; i++) {
@@ -20,5 +20,5 @@ tests/cpychecker/refcounts/PyList_Append/incorrect-loop/input.c:38:nn: note: rea
2020
tests/cpychecker/refcounts/PyList_Append/incorrect-loop/input.c:38:nn: note: when considering n == (int)1 from tests/cpychecker/refcounts/PyList_Append/incorrect-loop/input.c:29 at: for (i = 0; i < n; i++) {
2121
tests/cpychecker/refcounts/PyList_Append/incorrect-loop/input.c:38:nn: note: taking False path at: for (i = 0; i < n; i++) {
2222
tests/cpychecker/refcounts/PyList_Append/incorrect-loop/input.c:59:nn: note: reaching: return list;
23-
tests/cpychecker/refcounts/PyList_Append/incorrect-loop/input.c:60:nn: note: returning
23+
tests/cpychecker/refcounts/PyList_Append/incorrect-loop/input.c:59:nn: note: returning
2424
tests/cpychecker/refcounts/PyList_Append/incorrect-loop/input.c:30:nn: note: graphical error report for function 'test' written out to 'tests/cpychecker/refcounts/PyList_Append/incorrect-loop/input.c.test-refcount-errors.html'

tests/cpychecker/refcounts/PyList_Append/incorrect/stderr.txt

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
In function 'test':
2-
tests/cpychecker/refcounts/PyList_Append/incorrect/input.c:54:nn: warning: memory leak: ob_refcnt of '*item' is 1 too high [enabled by default]
2+
tests/cpychecker/refcounts/PyList_Append/incorrect/input.c:53:nn: warning: memory leak: ob_refcnt of '*item' is 1 too high [enabled by default]
33
tests/cpychecker/refcounts/PyList_Append/incorrect/input.c:36:nn: note: '*item' was allocated at: item = PyLong_FromLong(42);
4-
tests/cpychecker/refcounts/PyList_Append/incorrect/input.c:54:nn: note: was expecting final owned ob_refcnt of '*item' to be 1 due to object being referenced by: PyListObject.ob_item[0] but final ob_refcnt is refs: 2 owned
4+
tests/cpychecker/refcounts/PyList_Append/incorrect/input.c:53:nn: note: was expecting final owned ob_refcnt of '*item' to be 1 due to object being referenced by: PyListObject.ob_item[0] but final ob_refcnt is refs: 2 owned
55
tests/cpychecker/refcounts/PyList_Append/incorrect/input.c:32:nn: note: when PyList_New() succeeds at: list = PyList_New(0);
66
tests/cpychecker/refcounts/PyList_Append/incorrect/input.c:33:nn: note: taking False path at: if (!list) {
77
tests/cpychecker/refcounts/PyList_Append/incorrect/input.c:36:nn: note: reaching: item = PyLong_FromLong(42);
@@ -14,5 +14,5 @@ tests/cpychecker/refcounts/PyList_Append/incorrect/input.c:42:nn: note: ob_refcn
1414
tests/cpychecker/refcounts/PyList_Append/incorrect/input.c:42:nn: note: '*item' is now referenced by 1 non-stack value(s): PyListObject.ob_item[0]
1515
tests/cpychecker/refcounts/PyList_Append/incorrect/input.c:42:nn: note: taking False path at: if (-1 == PyList_Append(list, item)) {
1616
tests/cpychecker/refcounts/PyList_Append/incorrect/input.c:53:nn: note: reaching: return list;
17-
tests/cpychecker/refcounts/PyList_Append/incorrect/input.c:54:nn: note: returning
17+
tests/cpychecker/refcounts/PyList_Append/incorrect/input.c:53:nn: note: returning
1818
tests/cpychecker/refcounts/PyList_Append/incorrect/input.c:28:nn: note: graphical error report for function 'test' written out to 'tests/cpychecker/refcounts/PyList_Append/incorrect/input.c.test-refcount-errors.html'

tests/cpychecker/refcounts/PyList_SET_ITEM_macro/incorrect_multiple/stderr.txt

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
In function 'handle_SET_ITEM_macro':
2-
tests/cpychecker/refcounts/PyList_SET_ITEM_macro/incorrect_multiple/input.c:76:nn: warning: memory leak: ob_refcnt of PyLongObject is 1 too high [enabled by default]
2+
cc1: warning: memory leak: ob_refcnt of PyLongObject is 1 too high [enabled by default]
33
tests/cpychecker/refcounts/PyList_SET_ITEM_macro/incorrect_multiple/input.c:50:nn: note: PyLongObject was allocated at: items[2] = PyLong_FromLong(3000);
4-
tests/cpychecker/refcounts/PyList_SET_ITEM_macro/incorrect_multiple/input.c:76:nn: note: was expecting final owned ob_refcnt of PyLongObject to be 1 due to object being referenced by: ob_item array for PyListObject[2] but final ob_refcnt is refs: 2 owned
4+
cc1: note: was expecting final owned ob_refcnt of PyLongObject to be 1 due to object being referenced by: ob_item array for PyListObject[2] but final ob_refcnt is refs: 2 owned
55
tests/cpychecker/refcounts/PyList_SET_ITEM_macro/incorrect_multiple/input.c:33:nn: note: when PyList_New() succeeds at: list = PyList_New(3);
66
tests/cpychecker/refcounts/PyList_SET_ITEM_macro/incorrect_multiple/input.c:34:nn: note: taking False path at: if (!list) {
77
tests/cpychecker/refcounts/PyList_SET_ITEM_macro/incorrect_multiple/input.c:39:nn: note: reaching: items[0] = PyLong_FromLong(1000);
@@ -17,5 +17,5 @@ tests/cpychecker/refcounts/PyList_SET_ITEM_macro/incorrect_multiple/input.c:51:n
1717
tests/cpychecker/refcounts/PyList_SET_ITEM_macro/incorrect_multiple/input.c:64:nn: note: reaching: PyList_SET_ITEM(list, 0, items[0]);
1818
tests/cpychecker/refcounts/PyList_SET_ITEM_macro/incorrect_multiple/input.c:66:nn: note: PyLongObject is now referenced by 1 non-stack value(s): ob_item array for PyListObject[2]
1919
tests/cpychecker/refcounts/PyList_SET_ITEM_macro/incorrect_multiple/input.c:71:nn: note: ob_refcnt is now refs: 2 owned
20-
tests/cpychecker/refcounts/PyList_SET_ITEM_macro/incorrect_multiple/input.c:76:nn: note: returning
20+
cc1: note: returning
2121
tests/cpychecker/refcounts/PyList_SET_ITEM_macro/incorrect_multiple/input.c:24:nn: note: graphical error report for function 'handle_SET_ITEM_macro' written out to 'tests/cpychecker/refcounts/PyList_SET_ITEM_macro/incorrect_multiple/input.c.handle_SET_ITEM_macro-refcount-errors.html'
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
In function 'handle_SetItem':
2-
tests/cpychecker/refcounts/PySequence_SetItem/incorrect/input.c:51:nn: warning: memory leak: ob_refcnt of '*item' is 1 too high [enabled by default]
2+
tests/cpychecker/refcounts/PySequence_SetItem/incorrect/input.c:50:nn: warning: memory leak: ob_refcnt of '*item' is 1 too high [enabled by default]
33
tests/cpychecker/refcounts/PySequence_SetItem/incorrect/input.c:34:nn: note: '*item' was allocated at: item = PyLong_FromLong(42);
4-
tests/cpychecker/refcounts/PySequence_SetItem/incorrect/input.c:51:nn: note: was expecting final owned ob_refcnt of '*item' to be 0 since nothing references it but final ob_refcnt is refs: 1 owned
4+
tests/cpychecker/refcounts/PySequence_SetItem/incorrect/input.c:50:nn: note: was expecting final owned ob_refcnt of '*item' to be 0 since nothing references it but final ob_refcnt is refs: 1 owned
55
tests/cpychecker/refcounts/PySequence_SetItem/incorrect/input.c:29:nn: note: when PyList_New() succeeds at: list = PyList_New(1);
66
tests/cpychecker/refcounts/PySequence_SetItem/incorrect/input.c:30:nn: note: taking False path at: if (!list) {
77
tests/cpychecker/refcounts/PySequence_SetItem/incorrect/input.c:34:nn: note: reaching: item = PyLong_FromLong(42);
@@ -12,5 +12,5 @@ tests/cpychecker/refcounts/PySequence_SetItem/incorrect/input.c:41:nn: note: rea
1212
tests/cpychecker/refcounts/PySequence_SetItem/incorrect/input.c:41:nn: note: PySequence_SetItem() succeeds at: rv = PySequence_SetItem(list, 0, item);
1313
tests/cpychecker/refcounts/PySequence_SetItem/incorrect/input.c:42:nn: note: taking False path at: if (rv != 0) {
1414
tests/cpychecker/refcounts/PySequence_SetItem/incorrect/input.c:50:nn: note: reaching: return list;
15-
tests/cpychecker/refcounts/PySequence_SetItem/incorrect/input.c:51:nn: note: returning
15+
tests/cpychecker/refcounts/PySequence_SetItem/incorrect/input.c:50:nn: note: returning
1616
tests/cpychecker/refcounts/PySequence_SetItem/incorrect/input.c:24:nn: note: graphical error report for function 'handle_SetItem' written out to 'tests/cpychecker/refcounts/PySequence_SetItem/incorrect/input.c.handle_SetItem-refcount-errors.html'

0 commit comments

Comments
 (0)