diff --git a/docs/basics.rst b/docs/basics.rst
index c5d47301..b707f37f 100644
--- a/docs/basics.rst
+++ b/docs/basics.rst
@@ -38,6 +38,12 @@ The plugin has the following requirements:
http://pygments.org/
+ * "firehose": The libcpychecker code uses the "firehose" Python
+ static analysis results library, for its internal data model for
+ results:
+
+ https://pypi.python.org/pypi/firehose/
+
* graphviz: many of the interesting examples use "dot" to draw diagrams
(e.g. control-flow graphs), so it's worth having graphviz installed.
@@ -76,13 +82,13 @@ On a Fedora box you can install them by running the following as root:
.. code-block:: bash
- yum install gcc-plugin-devel python-devel python-six python-pygments graphviz
+ yum install gcc-plugin-devel python-devel python-six python-pygments graphviz python-firehose
for building against Python 2, or:
.. code-block:: bash
- yum install gcc-plugin-devel python3-devel python3-six python3-pygments graphviz
+ yum install gcc-plugin-devel python3-devel python3-six python3-pygments graphviz python3-firehose
when building for Python 3.
@@ -258,6 +264,11 @@ You can override this using gcc.set_location:
each time after returning from Python back to the plugin, after printing
any traceback.
+.. py:function:: gcc.get_location(loc)
+
+ Obtain the error-reporting location that would be used if an exception
+ occurs, as a `gcc.Location`
+
Accessing parameters
--------------------
diff --git a/gcc-c-api/gcc-cfg.c b/gcc-c-api/gcc-cfg.c
index 7af96abc..de020d1f 100644
--- a/gcc-c-api/gcc-cfg.c
+++ b/gcc-c-api/gcc-cfg.c
@@ -321,7 +321,7 @@ gcc_cfg_edge_is_complex(gcc_cfg_edge edge)
return (edge.inner->flags & EDGE_COMPLEX);
}
-GCC_PUBLIC_API(bool)
+GCC_IMPLEMENT_PUBLIC_API(bool)
gcc_cfg_edge_is_eh(gcc_cfg_edge edge)
{
return (edge.inner->flags & EDGE_EH) == EDGE_EH;
diff --git a/gcc-python.c b/gcc-python.c
index f057e2cc..b92c2d1c 100644
--- a/gcc-python.c
+++ b/gcc-python.c
@@ -145,6 +145,12 @@ PyGcc_set_location(PyObject *self, PyObject *args)
Py_RETURN_NONE;
}
+static PyObject *
+PyGcc_get_location(PyObject *self, PyObject *ignored)
+{
+ return PyGccLocation_New(gcc_get_input_location());
+}
+
static bool add_option_to_list(gcc_option opt, void *user_data)
{
PyObject *result = (PyObject*)user_data;
@@ -421,7 +427,11 @@ static PyMethodDef GccMethods[] = {
{"set_location",
(PyCFunction)PyGcc_set_location,
METH_VARARGS,
- ("Temporarily set the default location for error reports\n")},
+ ("Temporarily set the default gcc.Location for error reports\n")},
+ {"get_location",
+ (PyCFunction)PyGcc_get_location,
+ METH_NOARGS,
+ ("Get the default gcc.Location for error reports\n")},
/* Options: */
{"get_option_list",
diff --git a/gccutils/__init__.py b/gccutils/__init__.py
index 2d3064a5..1a1636d9 100644
--- a/gccutils/__init__.py
+++ b/gccutils/__init__.py
@@ -429,6 +429,8 @@ def edge_to_dot(self, e):
attrliststr = '[label = loop_exit]'
elif e.can_fallthru:
attrliststr = '[label = fallthru]'
+ elif e.eh:
+ attrliststr = '[label = eh]'
else:
attrliststr = ''
return (' %s -> %s %s;\n'
diff --git a/libcpychecker/__init__.py b/libcpychecker/__init__.py
index e46d35f6..38b8a689 100644
--- a/libcpychecker/__init__.py
+++ b/libcpychecker/__init__.py
@@ -15,7 +15,16 @@
# along with this program. If not, see
# .
+import atexit
+import sys
+import traceback
+
import gcc
+
+from firehose.model import Analysis, Metadata, Generator, Failure, \
+ CustomFields
+
+from libcpychecker.diagnostics import WrappedGccLocation
from libcpychecker.formatstrings import check_pyargs
from libcpychecker.utils import log
from libcpychecker.refcounts import check_refcounts, get_traces
@@ -25,44 +34,85 @@
if hasattr(gcc, 'PLUGIN_FINISH_DECL'):
from libcpychecker.compat import on_finish_decl
-class CpyCheckerGimplePass(gcc.GimplePass):
- """
- The custom pass that implements the per-function part of
- our extra compile-time checks
- """
+class Options:
+ '''
+ dump_traces: bool: if True, dump information about the traces through
+ the function to stdout (for self tests)
+
+ show_traces: bool: if True, display a diagram of the state transition graph
+
+ show_timings: bool: if True, add timing information to stderr
+ '''
def __init__(self,
dump_traces=False,
show_traces=False,
+ show_timings=False,
verify_pyargs=True,
verify_refcounting=False,
show_possible_null_derefs=False,
only_on_python_code=True,
maxtrans=256,
- dump_json=False):
- gcc.GimplePass.__init__(self, 'cpychecker-gimple')
+ outputxmlpath=None,
+ selftest=None,
+ reportstats=False):
self.dump_traces = dump_traces
self.show_traces = show_traces
+ self.show_timings = show_timings
self.verify_pyargs = verify_pyargs
self.verify_refcounting = verify_refcounting
self.show_possible_null_derefs = show_possible_null_derefs
self.only_on_python_code = only_on_python_code
self.maxtrans = maxtrans
- self.dump_json = dump_json
+ self.outputxmlpath = outputxmlpath
+ self.selftest = selftest
+ self.reportstats = reportstats
+
+class Context:
+ def __init__(self, options):
+ generator = Generator(name='cpychecker',
+ version=None)
+ metadata=Metadata(generator=generator,
+ sut=None,
+ file_=None,
+ stats=None)
+ self.options = options
+ self.analysis = Analysis(metadata, [])
+ self.was_flushed = False
+
+ def flush(self):
+ if 0:
+ self.analysis.to_xml().write(sys.stderr)
+
+ if self.options.outputxmlpath:
+ with open(self.options.outputxmlpath, 'w') as f:
+ self.analysis.to_xml().write(f)
+
+ self.was_flushed = True
+
+class CpyCheckerGimplePass(gcc.GimplePass):
+ """
+ The custom pass that implements the per-function part of
+ our extra compile-time checks
+ """
+ def __init__(self, ctxt, options):
+ gcc.GimplePass.__init__(self, 'cpychecker-gimple')
+ self.ctxt = ctxt
+ self.options = options
def execute(self, fun):
if fun:
log('%s', fun)
- if self.verify_pyargs:
- check_pyargs(fun)
+ if self.options.verify_pyargs:
+ check_pyargs(fun, self.ctxt)
- if self.only_on_python_code:
+ if self.options.only_on_python_code:
# Only run the refcount checker on code that
# includes :
if not get_PyObject():
return
# The refcount code is too buggy for now to be on by default:
- if self.verify_refcounting:
+ if self.options.verify_refcounting:
if 0:
# Profiled version:
import cProfile
@@ -78,25 +128,47 @@ def execute(self, fun):
# Normal mode (without profiler):
self._check_refcounts(fun)
- def _check_refcounts(self, fun):
- check_refcounts(fun, self.dump_traces, self.show_traces,
- self.show_possible_null_derefs,
- maxtrans=self.maxtrans,
- dump_json=self.dump_json)
+ if self.options.selftest:
+ self.options.selftest(self.ctxt, fun)
+ def _check_refcounts(self, fun):
+ try:
+ check_refcounts(self.ctxt,
+ fun,
+ self.options)
+ except:
+ if self.options.outputxmlpath:
+ # If we're capturing Firehose XML output, simply capture
+ # unhandled exception there:
+ loc = WrappedGccLocation(gcc.get_location(),
+ fun.decl.name)
+ tb = traceback.format_exc()
+ failure = Failure(location=loc,
+ failureid='python-exception',
+ message=None,
+ customfields=CustomFields(traceback=tb))
+ self.ctxt.analysis.results.append(failure)
+ else:
+ raise
class CpyCheckerIpaPass(gcc.SimpleIpaPass):
"""
The custom pass that implements the whole-program part of
our extra compile-time checks
"""
- def __init__(self):
+ def __init__(self, ctxt):
gcc.SimpleIpaPass.__init__(self, 'cpychecker-ipa')
+ self.ctxt = ctxt
def execute(self):
- check_initializers()
+ check_initializers(self.ctxt)
+
+def main(options=None, **kwargs):
+ if options is None:
+ options = Options(**kwargs)
+
+ ctxt = Context(options)
-def main(**kwargs):
# Register our custom attributes:
gcc.register_callback(gcc.PLUGIN_ATTRIBUTES,
register_our_attributes)
@@ -107,7 +179,7 @@ def main(**kwargs):
on_finish_decl)
# Register our GCC passes:
- gimple_ps = CpyCheckerGimplePass(**kwargs)
+ gimple_ps = CpyCheckerGimplePass(ctxt, options)
if 1:
# non-SSA version:
gimple_ps.register_before('*warn_function_return')
@@ -115,5 +187,10 @@ def main(**kwargs):
# SSA version:
gimple_ps.register_after('ssa')
- ipa_ps = CpyCheckerIpaPass()
+ ipa_ps = CpyCheckerIpaPass(ctxt)
ipa_ps.register_before('*free_lang_data')
+
+ # Ensure that the ctxt is flushed after everything is run:
+ atexit.register(ctxt.flush)
+
+ return ctxt
diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py
index a7fd3281..299bf4df 100644
--- a/libcpychecker/absinterp.py
+++ b/libcpychecker/absinterp.py
@@ -1,5 +1,5 @@
-# Copyright 2011, 2012 David Malcolm
-# Copyright 2011, 2012 Red Hat, Inc.
+# Copyright 2011, 2012, 2013 David Malcolm
+# Copyright 2011, 2012, 2013 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
@@ -24,7 +24,6 @@
from collections import OrderedDict
from libcpychecker.utils import log, logging_enabled
from libcpychecker.types import *
-from libcpychecker.diagnostics import location_as_json, type_as_json
debug_comparisons = 0
@@ -456,6 +455,8 @@ def get_transitions_for_function_call(self, state, stmt):
check_isinstance(stmt, gcc.GimpleCall)
class CallOfNullFunctionPtr(PredictedError):
+ testid = 'call-of-null-function-ptr'
+
def __init__(self, stmt, value):
check_isinstance(stmt, gcc.Gimple)
check_isinstance(value, AbstractValue)
@@ -966,6 +967,8 @@ def get_transitions_for_function_call(self, state, stmt):
check_isinstance(stmt, gcc.GimpleCall)
class CallOfUninitializedFunctionPtr(PredictedError):
+ testid = 'call-of-uninitialized-function-ptr'
+
def __init__(self, stmt, value):
check_isinstance(stmt, gcc.Gimple)
check_isinstance(value, AbstractValue)
@@ -992,6 +995,8 @@ class PredictedError(Exception):
pass
class InvalidlyNullParameter(PredictedError):
+ testid = 'invalidly-null-parameter'
+
# Use this when we can predict that a function is called with NULL as an
# argument for an argument that must not be NULL
def __init__(self, fnname, paramidx, nullvalue):
@@ -1015,6 +1020,8 @@ def __init__(self, state, expr, value, isdefinite):
self.isdefinite = isdefinite
class PredictedArithmeticError(PredictedError):
+ testid = 'arithmetic-error'
+
def __init__(self, err, rhsvalue, isdefinite):
check_isinstance(err, (ArithmeticError, ValueError))
self.err = err
@@ -1028,6 +1035,8 @@ def __str__(self):
return 'possible %s with right-hand-side %s' % (self.err, self.rhsvalue)
class UsageOfUninitializedData(PredictedValueError):
+ testid = 'usage-of-uninitialized-data'
+
def __init__(self, state, expr, value, desc):
check_isinstance(state, State)
check_isinstance(expr, gcc.Tree)
@@ -1041,6 +1050,8 @@ def __str__(self):
% (self.desc, self.state.loc.get_stmt().loc))
class NullPtrDereference(PredictedValueError):
+ testid = 'null-ptr-dereference'
+
def __init__(self, state, expr, ptr, isdefinite):
check_isinstance(state, State)
check_isinstance(expr, gcc.Tree)
@@ -1056,6 +1067,8 @@ def __str__(self):
% (self.expr, self.state.loc.get_stmt().loc))
class NullPtrArgument(PredictedValueError):
+ testid = 'null-ptr-argument'
+
def __init__(self, state, stmt, idx, ptr, isdefinite, why):
check_isinstance(state, State)
check_isinstance(stmt, gcc.Gimple)
@@ -1087,6 +1100,8 @@ def __str__(self):
class ReadFromDeallocatedMemory(PredictedError):
+ testid = 'read-from-deallocated-memory'
+
def __init__(self, stmt, value):
check_isinstance(stmt, gcc.Gimple)
check_isinstance(value, DeallocatedMemory)
@@ -1098,6 +1113,8 @@ def __str__(self):
% (self.stmt.loc, self.value))
class PassingPointerToDeallocatedMemory(PredictedError):
+ testid = 'passing-pointer-to-deallocated-memory'
+
def __init__(self, argidx, fnname, stmt, value):
check_isinstance(stmt, gcc.Gimple)
check_isinstance(value, DeallocatedMemory)
@@ -1387,12 +1404,14 @@ class State(object):
# We can't use the __slots__ optimization here, as we're adding additional
# per-facet attributes
- def __init__(self, fun, loc, facets, region_for_var=None, value_for_region=None,
+ def __init__(self, fun, ctxt, loc, facets, region_for_var=None,
+ value_for_region=None,
return_rvalue=None, has_returned=False, not_returning=False):
check_isinstance(fun, gcc.Function)
check_isinstance(loc, Location)
check_isinstance(facets, dict)
self.fun = fun
+ self.ctxt = ctxt
self.loc = loc
self.facets = facets
@@ -1467,6 +1486,7 @@ def log(self, logger):
def copy(self):
s_new = State(self.fun,
+ self.ctxt,
self.loc,
self.facets,
self.region_for_var.copy(),
@@ -2983,7 +3003,7 @@ def on_transition(self, transition, result):
if self.trans_seen > self.maxtrans:
raise TooComplicated(result)
-def iter_traces(fun, facets, prefix=None, limits=None, depth=0):
+def iter_traces(fun, ctxt, facets, prefix=None, limits=None, depth=0):
"""
Traverse the tree of traces of program state, returning a list
of Trace instances.
@@ -3000,6 +3020,7 @@ def iter_traces(fun, facets, prefix=None, limits=None, depth=0):
if prefix is None:
prefix = Trace()
curstate = State(fun,
+ ctxt,
Location.get_block_start(fun.cfg.entry),
facets,
None, None, None)
@@ -3076,7 +3097,7 @@ def iter_traces(fun, facets, prefix=None, limits=None, depth=0):
# Recurse
# This gives us a depth-first traversal of the state tree
try:
- for trace in iter_traces(fun, facets, newprefix, limits,
+ for trace in iter_traces(fun, ctxt, facets, newprefix, limits,
depth + 1):
result.append(trace)
except TooComplicated:
diff --git a/libcpychecker/diagnostics.py b/libcpychecker/diagnostics.py
index 0b420726..59db7dec 100644
--- a/libcpychecker/diagnostics.py
+++ b/libcpychecker/diagnostics.py
@@ -1,5 +1,5 @@
-# Copyright 2011, 2012 David Malcolm
-# Copyright 2011, 2012 Red Hat, Inc.
+# Copyright 2011, 2012, 2013 David Malcolm
+# Copyright 2011, 2012, 2013 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
@@ -23,11 +23,102 @@
flushed, allowing us to de-duplicate error reports.
"""
+import sys
+
+from firehose.model import Issue, Location, File, Function, \
+ Point, Message, Notes, Trace, State
+
import gcc
from gccutils import get_src_for_loc, check_isinstance
from libcpychecker.visualizations import HtmlRenderer
from libcpychecker.utils import log
+
+# Firehose support
+class CpycheckerIssue(Issue):
+ """
+ Subclass of firehose.model.Issue, which adds the concept
+ of adding notes at the end of the trace in the gcc output,
+ mostly for byte-for-byte compatibility with old stderr in the
+ selftests
+ """
+ def __init__(self,
+ cwe,
+ testid,
+ location,
+ message,
+ notes,
+ trace):
+ # We don't want any of our testids to be None:
+ assert isinstance(testid, str)
+
+ Issue.__init__(self, cwe, testid, location, message, notes, trace)
+
+ self.initial_notes = []
+ self.final_notes = []
+
+class WrappedGccLocation(Location):
+ """
+ A firehose.model.Location
+ wrapping a gcc.Location
+ """
+ def __init__(self, gccloc, funcname):
+ self.gccloc = gccloc
+
+ if funcname:
+ function = Function(funcname)
+ else:
+ function = None
+ if gccloc:
+ file_ = File(givenpath=gccloc.file,
+ abspath=None)
+ point = Point(line=gccloc.line,
+ column=gccloc.column)
+ else:
+ file_ = File(givenpath='FIXME',
+ abspath=None)
+ point = None
+ Location.__init__(self,
+ file=file_,
+ function=function,
+ point=point)
+
+class WrappedAbsinterpLocation(WrappedGccLocation):
+ """
+ A firehose.model.Location that wraps a libcpychecker.absinterp.Location
+ """
+ def __init__(self, loc, funcname):
+ self.loc = loc
+ gccloc = loc.get_gcc_loc()
+ WrappedGccLocation.__init__(self, gccloc, funcname)
+
+class CustomState(State):
+ '''
+ A firehose.model.State, but with hooks for byte-for-byte compat with
+ old output
+ '''
+ def __init__(self, *args, **kwargs):
+ State.__init__(self, *args, **kwargs)
+ self.extra_notes = []
+
+ def add_note(self, text):
+ assert isinstance(text, str)
+ if self.notes is None:
+ self.notes = Notes(text)
+ else:
+ self.extra_notes.append(text)
+
+def make_issue(funcname, gccloc, msg, testid, cwe):
+ r = CpycheckerIssue(cwe=cwe,
+ testid=testid,
+ location=WrappedGccLocation(gccloc, funcname),
+ message=Message(text=msg),
+ notes=None,
+ trace=None)
+ return r
+
+
+
class Annotator:
"""
A collection of hooks for use when describing a trace (either as text,
@@ -38,11 +129,41 @@ class Annotator:
"""
def get_notes(self, transition):
"""
- Return a list of Note instances giving extra information about the
+ Return a list of str instances giving extra information about the
transition
"""
raise NotImplementedError
+def make_firehose_trace(funcname, trace, annotator):
+ """
+ input is a libcpychecker.absinterp.Trace
+ output is a firehose.model.Trace (aka a Trace within this module)
+ """
+ result = Trace([])
+ for t in trace.transitions:
+ log('transition: %s', t)
+ def add_state(s_in, is_src):
+ srcloc = s_in.get_gcc_loc_or_none()
+ if srcloc:
+ location=WrappedAbsinterpLocation(s_in.loc,
+ funcname=funcname)
+ s = CustomState(location,
+ notes=None)
+ paras = []
+ if t.desc and is_src:
+ s.add_note(t.desc)
+
+ if annotator:
+ notes = annotator.get_notes(t)
+ for note in notes:
+ s.add_note(note)
+
+ result.add_state(s)
+ add_state(t.src, True)
+ add_state(t.dest, False)
+
+ return result
+
class TestAnnotator(Annotator):
"""
A sample annotator that adds information to the trace on movement between
@@ -53,42 +174,26 @@ def get_notes(self, transition):
srcloc = transition.src.get_gcc_loc_or_none()
if srcloc:
if transition.src.loc != transition.dest.loc:
- result.append(Note(srcloc,
- ('transition from "%s" to "%s"'
- % (transition.src.loc.get_stmt(),
- transition.dest.loc.get_stmt()))))
+ result.append('transition from "%s" to "%s"'
+ % (transition.src.loc.get_stmt(),
+ transition.dest.loc.get_stmt()))
return result
-def describe_trace(trace, report, annotator):
- """
- Buffer up more details about the path through the function that
- leads to the error, using report.add_inform()
- """
- awaiting_target = None
- for t in trace.transitions:
- log('transition: %s', t)
- srcloc = t.src.get_gcc_loc_or_none()
- if t.desc:
- if srcloc:
- report.add_inform(t.src.get_gcc_loc(report.fun),
- ('%s at: %s'
- % (t.desc, get_src_for_loc(srcloc))))
- else:
- report.add_inform(t.src.get_gcc_loc(report.fun),
- '%s' % t.desc)
+def is_duplicate_of(r1, r2):
+ check_isinstance(r1, Issue)
+ check_isinstance(r2, Issue)
- 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:
- report.add_inform(destloc,
- 'reaching: %s' % get_src_for_loc(destloc))
+ # Simplistic equivalence classes for now:
+ # the same function, source location, and message; everything
+ # else can be different
+ if r1.location.function != r2.location.function:
+ return False
+ if r1.location.point != r2.location.point:
+ return False
+ if r1.message != r2.message:
+ return False
- if annotator:
- notes = annotator.get_notes(t)
- for note in notes:
- if note.loc and note.loc == srcloc:
- report.add_inform(note.loc, note.msg)
+ return True
class Reporter:
"""
@@ -99,21 +204,20 @@ class Reporter:
instances, and only fully flushing one of them within each equivalence
class
"""
- def __init__(self):
+ def __init__(self, ctxt):
+ self.ctxt = ctxt
self.reports = []
self._got_warnings = False
- def make_warning(self, fun, loc, msg):
+ def make_warning(self, fun, loc, msg, testid, cwe):
assert isinstance(fun, gcc.Function)
assert isinstance(loc, gcc.Location)
self._got_warnings = True
- w = Report(fun, loc, msg)
+ w = make_issue(fun.decl.name, loc, msg, testid, cwe)
self.reports.append(w)
- w.add_warning(loc, msg)
-
return w
def make_debug_dump(self, fun, loc, msg):
@@ -126,25 +230,6 @@ def make_debug_dump(self, fun, loc, msg):
def got_warnings(self):
return self._got_warnings
- def to_json(self, fun):
- result = dict(filename=fun.start.file,
- function=dict(name=fun.decl.name,
- # line number range:
- lines=(fun.decl.location.line - 1,
- fun.end.line + 1)),
- reports=[])
- for report in self.reports:
- result['reports'].append(report.to_json(fun))
- return result
-
- def dump_json(self, fun, filename):
- js = self.to_json(fun)
- from json import dump, dumps
- with open(filename, 'w') as f:
- dump(js, f, sort_keys=True, indent=4)
- if 0:
- print(dumps(js, sort_keys=True, indent=4))
-
def to_html(self, fun):
# (FIXME: eliminate self.fun from HtmlRenderer and the above arg)
r = HtmlRenderer(fun)
@@ -164,155 +249,120 @@ def remove_duplicates(self):
Try to organize Report instances into equivalence classes, and only
keep the first Report within each class
"""
+
+ # Set of all Report instances that are a duplicate of another:
+ duplicates = set()
+
+ # dict from "primary" Report to the set of its duplicates:
+ duplicates_of = {}
+
for report in self.reports[:]:
# The report might have been removed during the iteration:
- if report.is_duplicate:
+ if report in duplicates:
continue
for candidate in self.reports[:]:
- if report != candidate and not report.is_duplicate:
- if candidate.is_duplicate_of(report):
- report.add_duplicate(candidate)
+ if report != candidate and report not in duplicates:
+ if is_duplicate_of(candidate, report):
+ duplicates.add(candidate)
self.reports.remove(candidate)
+ if report not in duplicates_of:
+ duplicates_of[report] = set([candidate])
+ else:
+ duplicates_of[report].add(candidate)
# Add a note to each report that survived about any duplicates:
- for report in self.reports:
- if report.duplicates:
- report.add_note(report.loc,
- ('found %i similar trace(s) to this'
- % len(report.duplicates)))
+ for report in duplicates_of:
+ num_duplicates = len(duplicates_of[report])
+ report.final_notes.append((report.location.gccloc,
+ 'found %i similar trace(s) to this'
+ % num_duplicates))
def flush(self):
for r in self.reports:
- r.flush()
-
-class SavedDiagnostic:
- """
- A saved GCC diagnostic, which we can choose to emit or suppress at a later
- date
- """
- def __init__(self, loc, msg):
- assert isinstance(loc, gcc.Location)
- assert isinstance(msg, str)
- self.loc = loc
- self.msg = msg
-
-class SavedWarning(SavedDiagnostic):
- def flush(self):
- gcc.warning(self.loc, self.msg)
-
-class SavedInform(SavedDiagnostic):
- def flush(self):
- gcc.inform(self.loc, self.msg)
-
-class Report:
+ emit_report(self.ctxt, r)
+
+def emit_warning(ctxt, loc, msg, funcname, testid, cwe, notes,
+ customfields=None):
+ #gcc.warning(loc, msg)
+
+ if notes is not None:
+ text = notes
+ notes = Notes(text)
+ r = make_issue(funcname, loc, msg, testid, cwe)
+ r.notes = notes
+ r.customfields = customfields
+
+ emit_report(ctxt, r)
+
+def emit_report(ctxt, r):
+ emit_report_as_warning(r)
+ ctxt.analysis.results.append(r)
+
+def emit_report_as_warning(r):
+ # Emit gcc output to stderr, using the Report instance:
+ gcc.warning(r.location.gccloc, r.message.text)
+
+ for gccloc, msg in r.initial_notes:
+ gcc.inform(gccloc, msg)
+
+ if r.notes:
+ sys.stderr.write(r.notes.text)
+ if not r.notes.text.endswith('\n'):
+ sys.stderr.write('\n')
+
+ if r.trace:
+ last_state_with_notes = None
+ for state in r.trace.states:
+ gccloc = state.location.gccloc
+ if last_state_with_notes:
+ if last_state_with_notes.location.loc.bb != state.location.loc.bb:
+ # Tell the user where conditionals reach:
+ gcc.inform(gccloc,
+ 'reaching: %s' % get_src_for_loc(gccloc))
+ last_state_with_notes = None
+ #gcc.inform(gccloc,
+ # 'reaching: %s' % get_src_for_loc(gccloc))
+ if state.notes:
+ text = state.notes.text
+ if gccloc is not None:
+ text += ' at: %s' % get_src_for_loc(gccloc)
+ last_state_with_notes = state
+ gcc.inform(gccloc, text)
+ for text in state.extra_notes:
+ gcc.inform(gccloc, text)
+
+ for gccloc, msg in r.final_notes:
+ gcc.inform(gccloc, msg)
+
+'''
+def describe_trace(trace, report, annotator):
"""
- Data about a particular bug found by the checker
+ Buffer up more details about the path through the function that
+ leads to the error, using report.add_inform()
"""
- def __init__(self, fun, loc, msg):
- self.fun = fun
- self.loc = loc
- self.msg = msg
- self.trace = None
- self._annotators = {}
- self.notes = []
- self._saved_diagnostics = [] # list of SavedDiagnostic
-
- # De-duplication handling:
- self.is_duplicate = False
- self.duplicates = [] # list of Report
-
- def add_warning(self, loc, msg):
- # Add a gcc.warning() to the buffer of GCC diagnostics
- self._saved_diagnostics.append(SavedWarning(loc, msg))
-
- def add_inform(self, loc, msg):
- # Add a gcc.inform() to the buffer of GCC diagnostics
- self._saved_diagnostics.append(SavedInform(loc, msg))
-
- def flush(self):
- # Flush the buffer of GCC diagnostics
- for d in self._saved_diagnostics:
- d.flush()
-
- def add_trace(self, trace, annotator=None):
- self.trace = trace
- self._annotators[trace] = annotator
- describe_trace(trace, self, annotator)
+ for t in trace.transitions:
+ log('transition: %s', t)
+ srcloc = t.src.get_gcc_loc_or_none()
+ if t.desc:
+ if srcloc:
+ report.add_inform(t.src.get_gcc_loc(report.fun),
+ ('%s at: %s'
+ % (t.desc, get_src_for_loc(srcloc))))
+ else:
+ report.add_inform(t.src.get_gcc_loc(report.fun),
+ '%s' % t.desc)
- def add_note(self, loc, msg):
- """
- Add a note at the given location. This is added both to
- the buffer of GCC diagnostics, and also to a saved list that's
- available to the HTML renderer.
- """
- self.add_inform(loc, msg)
- note = Note(loc, msg)
- self.notes.append(note)
- return note
-
- def get_annotator_for_trace(self, trace):
- return self._annotators.get(trace)
-
- def is_duplicate_of(self, other):
- check_isinstance(other, Report)
-
- # Simplistic equivalence classes for now:
- # the same function, source location, and message; everything
- # else can be different
- if self.fun != other.fun:
- return False
- if self.loc != other.loc:
- return False
- if self.msg != other.msg:
- return False
-
- return True
-
- def add_duplicate(self, other):
- assert not self.is_duplicate
- self.duplicates.append(other)
- other.is_duplicate = True
-
- def to_json(self, fun):
- assert self.trace
- result = dict(message=self.msg,
- severity='warning', # FIXME
- states=[])
- # Generate a list of (state, desc) pairs, putting the desc from the
- # transition into source state; the final state will have an empty
- # string
- pairs = []
- for t_iter in self.trace.transitions:
- pairs.append( (t_iter.src, t_iter.desc) )
- pairs.append( (self.trace.transitions[-1].dest, None) )
- for i, (s_iter, desc) in enumerate(pairs):
- result['states'].append(s_iter.as_json(desc))
- result['notes'] = [dict(location=location_as_json(note.loc),
- message=note.msg)
- for note in self.notes]
- return result
+ 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:
+ report.add_inform(destloc,
+ 'reaching: %s' % get_src_for_loc(destloc))
+ if annotator:
+ notes = annotator.get_notes(t)
+ for note in notes:
+ if note.loc and note.loc == srcloc:
+ report.add_inform(note.loc, note.msg)
+'''
-class Note:
- """
- A note within a self
- """
- def __init__(self, loc, msg):
- self.loc = loc
- self.msg = msg
-
-
-def location_as_json(loc):
- if loc:
- return (dict(line=loc.line,
- column=loc.column),
- dict(line=loc.line,
- column=loc.column))
- else:
- return None
-
-def type_as_json(t):
- if t:
- return str(t)
- else:
- return None
diff --git a/libcpychecker/formatstrings.py b/libcpychecker/formatstrings.py
index a49fce86..b83ce914 100644
--- a/libcpychecker/formatstrings.py
+++ b/libcpychecker/formatstrings.py
@@ -1,5 +1,5 @@
-# Copyright 2011 David Malcolm
-# Copyright 2011 Red Hat, Inc.
+# Copyright 2011, 2012, 2013 David Malcolm
+# Copyright 2011, 2012, 2013 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
@@ -17,8 +17,11 @@
import sys
+from firehose.model import CustomFields
+
from gccutils import get_src_for_loc, get_global_typedef
+from libcpychecker.diagnostics import emit_warning
from libcpychecker.types import *
from libcpychecker.utils import log
@@ -59,6 +62,9 @@ def __init__(self, fmt_string):
self.fmt_string = fmt_string
class UnknownFormatChar(FormatStringWarning):
+
+ testid = 'unknown-format-char'
+
def __init__(self, fmt_string, ch):
FormatStringWarning.__init__(self, fmt_string)
self.ch = ch
@@ -67,11 +73,17 @@ def __str__(self):
return "unknown format char in \"%s\": '%s'" % (self.fmt_string, self.ch)
class UnhandledCode(UnknownFormatChar):
+
+ testid = 'unhandled-format-code'
+
def __str__(self):
return "unhandled format code in \"%s\": '%s' (FIXME)" % (self.fmt_string, self.ch)
class MismatchedParentheses(FormatStringWarning):
+
+ testid = 'mismatched-parentheses-in-format-string'
+
def __str__(self):
return "mismatched parentheses in format string \"%s\"" % (self.fmt_string, )
@@ -132,6 +144,9 @@ def __repr__(self):
% (self.__class__.__name__, self.fmt_string, self.args))
class WrongNumberOfVars(ParsedFormatStringWarning):
+
+ testid = 'wrong-number-of-vars-in-format-string'
+
def __init__(self, funcname, fmt, varargs):
ParsedFormatStringWarning.__init__(self, funcname, fmt)
self.varargs = varargs
@@ -187,6 +202,17 @@ def _describe_vararg(va):
% (self.arg_num, self.vararg, describe_type(self.vararg.type),
describe_type(self.exp_type), self.arg_fmt_string))
+ def make_custom_fields(self):
+ cf = CustomFields()
+ cf['function'] = self.funcname
+ cf['format-code'] = self.arg_fmt_string
+ cf['full-format-string'] = self.fmt.fmt_string
+ cf['expected-type'] = describe_type(self.exp_type)
+ cf['actual-type'] = describe_type(self.vararg.type)
+ cf['expression'] = str(self.vararg)
+ cf['argument-num'] = self.arg_num
+ return cf
+
def __str__(self):
return ('Mismatching type in call to %s with format code "%s"'
% (self.funcname, self.fmt.fmt_string))
@@ -310,7 +336,7 @@ def compare_int_types():
return False
-def check_pyargs(fun):
+def check_pyargs(fun, ctxt):
from libcpychecker.PyArg_ParseTuple import PyArgParseFmt
from libcpychecker.Py_BuildValue import PyBuildValueFmt
@@ -348,11 +374,21 @@ def check_keyword_array(stmt, idx):
elif isinstance(contents, gcc.IntegerCst):
elements[elt_idx] = contents.constant
if elements[-1] != 0:
- gcc.warning(stmt.loc, 'keywords to PyArg_ParseTupleAndKeywords are not NULL-terminated')
+ emit_warning(ctxt,
+ stmt.loc, 'keywords to PyArg_ParseTupleAndKeywords are not NULL-terminated',
+ fun.decl.name,
+ testid='missing-null-termination',
+ cwe=None,
+ notes=None)
i = 0
for elt in elements[0:-1]:
if not elt:
- gcc.warning(stmt.loc, 'keyword argument %d missing in PyArg_ParseTupleAndKeywords call' % i)
+ emit_warning(ctxt,
+ stmt.loc, 'keyword argument %d missing in PyArg_ParseTupleAndKeywords call' % i,
+ fun.decl.name,
+ testid='missing-keyword-argument',
+ cwe=None,
+ notes=None)
i = i + 1
def check_callsite(stmt, parser, funcname, format_idx, varargs_idx, with_size_t):
@@ -381,7 +417,12 @@ def check_callsite(stmt, parser, funcname, format_idx, varargs_idx, with_size_t)
fmt = parser.from_string(fmt_string, with_size_t)
except FormatStringWarning:
exc = sys.exc_info()[1]
- gcc.warning(stmt.loc, str(exc))
+ emit_warning(ctxt,
+ stmt.loc, str(exc),
+ fun.decl.name,
+ testid=exc.testid, # FIXME
+ cwe=None,
+ notes=None)
return
log('fmt: %r', fmt.args)
@@ -392,11 +433,21 @@ def check_callsite(stmt, parser, funcname, format_idx, varargs_idx, with_size_t)
varargs = stmt.args[varargs_idx:]
# log('varargs: %r', varargs)
if len(varargs) < len(exp_types):
- gcc.warning(loc, str(NotEnoughVars(funcname, fmt, varargs)))
+ emit_warning(ctxt,
+ loc, str(NotEnoughVars(funcname, fmt, varargs)),
+ fun.decl.name,
+ testid='not-enough-vars-in-format-string',
+ cwe=None,
+ notes=None)
return
if len(varargs) > len(exp_types):
- gcc.warning(loc, str(TooManyVars(funcname, fmt, varargs)))
+ emit_warning(ctxt,
+ loc, str(TooManyVars(funcname, fmt, varargs)),
+ fun.decl.name,
+ testid='too-many-vars-in-format-string',
+ cwe=None,
+ notes=None)
return
for index, ((exp_arg, exp_type), vararg) in enumerate(zip(exp_types, varargs)):
@@ -408,9 +459,14 @@ def check_callsite(stmt, parser, funcname, format_idx, varargs_idx, with_size_t)
loc = vararg.location
else:
loc = stmt.loc
- gcc.warning(loc,
- str(err))
- sys.stderr.write(err.extra_info())
+ emit_warning(ctxt,
+ loc,
+ str(err),
+ fun.decl.name,
+ testid='mismatching-type-in-format-string',
+ cwe=None,
+ notes=err.extra_info(),
+ customfields=err.make_custom_fields())
def maybe_check_callsite(stmt):
if stmt.fndecl:
diff --git a/libcpychecker/initializers.py b/libcpychecker/initializers.py
index dff6fc38..bf1a91d2 100644
--- a/libcpychecker/initializers.py
+++ b/libcpychecker/initializers.py
@@ -21,11 +21,12 @@
from gccutils import check_isinstance
+from libcpychecker.diagnostics import emit_warning
from libcpychecker.utils import log
-def check_initializers():
+def check_initializers(ctxt):
# Invoked by the "cpychecker-ipa" pass, once per compilation unit
- verify_any_PyMethodDef_flags()
+ verify_any_PyMethodDef_flags(ctxt)
from collections import OrderedDict
class StructInitializer(object):
@@ -103,14 +104,14 @@ def get_location(self):
METH_STATIC = 0x0020
METH_COEXIST = 0x0040
-def verify_any_PyMethodDef_flags():
+def verify_any_PyMethodDef_flags(ctxt):
"""
Check all initializers for PyMethodDef arrays.
Verify that the flags used match the real signature of the callback
function (albeit usually cast to a PyCFunction):
http://docs.python.org/c-api/structures.html#PyMethodDef
"""
- methods = get_all_PyMethodDef_initializers()
+ methods = get_all_PyMethodDef_initializers(ctxt)
#from pprint import pprint
#pprint(methods)
@@ -134,10 +135,15 @@ def verify_any_PyMethodDef_flags():
exptypemsg = 'expected ml_meth callback of type "PyObject (fn)(someobject *, PyObject *)"'
actualargs = len(ml_meth.type.argument_types)
if expargs != actualargs:
- gcc.warning(si.get_location(),
- 'flags do not match callback signature for %r'
- ' within PyMethodDef table'
- % ml_meth.name)
+ emit_warning(ctxt,
+ si.get_location(),
+ msg=('flags do not match callback signature for %r'
+ ' within PyMethodDef table'
+ % ml_meth.name),
+ funcname=None,
+ testid='flags-within-PyMethodDef',
+ cwe=None,
+ notes=None) # FIXME: add the notes from below
gcc.inform(si.get_location(),
exptypemsg + ' (%s arguments)' % expargs)
gcc.inform(si.get_location(),
@@ -146,7 +152,7 @@ def verify_any_PyMethodDef_flags():
gcc.inform(si.get_location(),
'see http://docs.python.org/c-api/structures.html#PyMethodDef')
-def get_all_PyMethodDef_initializers():
+def get_all_PyMethodDef_initializers(ctxt):
"""
Locate all initializers for PyMethodDef, returning a list
of StructInitializer instances
@@ -171,8 +177,13 @@ def get_all_PyMethodDef_initializers():
if 0:
print('final ml_name: %r' % ml_name)
if ml_name is not None:
- gcc.warning(table[-1].get_location(),
- 'missing NULL sentinel value at end of PyMethodDef table')
+ emit_warning(ctxt,
+ table[-1].get_location(),
+ 'missing NULL sentinel value at end of PyMethodDef table',
+ funcname=None,
+ testid='missing-NULL-terminator-in-PyMethodDef-table',
+ cwe=None,
+ notes=None)
result += table
return result
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index abe034ac..36161642 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -1,5 +1,5 @@
-# Copyright 2011, 2012 David Malcolm
-# Copyright 2011, 2012 Red Hat, Inc.
+# Copyright 2011, 2012, 2013 David Malcolm
+# Copyright 2011, 2012, 2013 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
@@ -21,6 +21,9 @@
# for a description of how such code is meant to be written
import sys
+
+from firehose.model import Notes, Failure, Message, CustomFields, Info
+
import gcc
from gccutils import cfg_to_dot, invoke_dot, get_src_for_loc, check_isinstance
@@ -29,7 +32,8 @@
from libcpychecker.attributes import fnnames_returning_borrowed_refs, \
stolen_refs_by_fnname, fnnames_setting_exception, \
fnnames_setting_exception_on_negative_result
-from libcpychecker.diagnostics import Reporter, Annotator, Note
+from libcpychecker.diagnostics import Reporter, Annotator, emit_warning, \
+ make_firehose_trace, WrappedGccLocation
from libcpychecker.PyArg_ParseTuple import PyArgParseFmt, FormatStringWarning,\
TypeCheckCheckerType, TypeCheckResultType, \
ConverterCallbackType, ConverterResultType
@@ -1202,6 +1206,7 @@ def impl_PyArg_UnpackTuple(self, stmt, v_args, v_name, v_min, v_max,
# Detect wrong number of arguments:
if len(v_varargs) != v_max.value:
class WrongNumberOfVarargs(PredictedError):
+ testid = 'wrong-number-of-varargs'
def __init__(self, v_max, v_varargs):
self.v_max = v_max
self.v_varargs = v_varargs
@@ -2718,15 +2723,25 @@ def _check_objargs(self, stmt, fnmeta, args, base_idx):
loc = v_arg.loc
if not loc:
loc = stmt.loc
- gcc.warning(loc,
- ('argument %i had type %s but was expecting a PyObject* (or subclass)'
- % (i + base_idx + 1, v_arg.gcctype)))
+ emit_warning(self.state.ctxt,
+ loc,
+ ('argument %i had type %s but was expecting a PyObject* (or subclass)'
+ % (i + base_idx + 1, v_arg.gcctype)),
+ self.state.fun.decl.name,
+ testid='arg-was-not-PyObject-ptr', # FIXME
+ cwe=None,
+ notes=None)
# check NULL-termination:
if not args or not args[-1].is_null_ptr():
- gcc.warning(stmt.loc,
+ emit_warning(self.state.ctxt,
+ stmt.loc,
('arguments to %s were not NULL-terminated'
- % fnmeta.name))
+ % fnmeta.name),
+ self.state.fun.decl.name,
+ testid='arguments-not-NULL-terminated',
+ cwe=None,
+ notes=None)
def impl_PyObject_CallFunctionObjArgs(self, stmt, v_callable, *args):
fnmeta = FnMeta(name='PyObject_CallFunctionObjArgs',
@@ -3556,6 +3571,7 @@ def impl_PyTuple_Pack(self, stmt, v_n, *v_args):
if isinstance(v_n, ConcreteValue):
if v_n.value != len(v_args):
class WrongArgCount(PredictedError):
+ testid = 'wrong-argument-count'
def __str__(self):
return 'mismatching argument count in call to %s' % fnmeta.name
raise WrongArgCount()
@@ -3800,8 +3816,8 @@ def impl_SWIG_Python_SetErrorMsg(self, stmt, v_errtype, v_msg):
return result
-def get_traces(fun):
- return list(iter_traces(fun,
+def get_traces(fun, ctxt):
+ return list(iter_traces(fun, ctxt,
{'cpython':CPython},
limits=Limits(maxtrans=1024)))
@@ -3907,13 +3923,11 @@ def get_notes(self, transition):
if region in transition.src.value_for_region:
src_value = transition.src.value_for_region[region]
if dest_value != src_value:
- result.append(Note(loc,
- ('%s now has value: %s'
- % (region, dest_value))))
+ result.append('%s now has value: %s'
+ % (region, dest_value))
else:
- result.append(Note(loc,
- ('%s has initial value: %s'
- % (region, dest_value))))
+ result.append('%s has initial value: %s'
+ % (region, dest_value))
# Show exception information:
esa = ExceptionStateAnnotator()
@@ -3953,19 +3967,17 @@ def get_notes(self, transition):
if src_refcnt != dest_refcnt:
log('src_refcnt: %r', src_refcnt)
log('dest_refcnt: %r', dest_refcnt)
- result.append(Note(loc,
- ('ob_refcnt is now %s' % dest_refcnt)))
+ result.append('ob_refcnt is now %s' % dest_refcnt)
# Add a note when there's a change to the set of persistent storage
# locations referencing this object:
src_refs = transition.src.get_persistent_refs_for_region(self.region)
dest_refs = transition.dest.get_persistent_refs_for_region(self.region)
if src_refs != dest_refs:
- result.append(Note(loc,
- ('%s is now referenced by %i non-stack value(s): %s'
- % (self.desc,
- len(dest_refs),
- ', '.join([ref.name for ref in dest_refs])))))
+ result.append('%s is now referenced by %i non-stack value(s): %s'
+ % (self.desc,
+ len(dest_refs),
+ ', '.join([ref.name for ref in dest_refs])))
if 0:
# For debugging: show the history of all references to the given
@@ -3973,8 +3985,7 @@ def get_notes(self, transition):
src_refs = transition.src.get_all_refs_for_region(self.region)
dest_refs = transition.dest.get_all_refs_for_region(self.region)
if src_refs != dest_refs:
- result.append(Note(loc,
- ('all refs: %s' % dest_refs)))
+ result.append('all refs: %s' % dest_refs)
return result
class ExceptionStateAnnotator(Annotator):
@@ -3996,9 +4007,8 @@ def get_notes(self, transition):
if hasattr(transition.dest, 'cpython'):
if transition.dest.cpython.exception_rvalue != transition.src.cpython.exception_rvalue:
- result.append(Note(loc,
- ('thread-local exception state now has value: %s'
- % transition.dest.cpython.exception_rvalue)))
+ result.append('thread-local exception state now has value: %s'
+ % transition.dest.cpython.exception_rvalue)
return result
@@ -4017,27 +4027,34 @@ def function_is_tp_iternext_callback(fun):
# Helper function for when ob_refcnt is wrong:
def emit_refcount_warning(msg,
exp_refcnt, exp_refs, v_ob_refcnt, r_obj, desc,
- trace, endstate, fun, rep):
- w = rep.make_warning(fun, endstate.get_gcc_loc(fun), msg)
- w.add_note(endstate.get_gcc_loc(fun),
- ('was expecting final ob_refcnt to be N + %i (for some unknown N)'
- % exp_refcnt))
+ trace, endstate, fun, rep,
+ testid,
+ cwe):
+ w = rep.make_warning(fun, endstate.get_gcc_loc(fun), msg, testid, cwe)
+ gccloc = w.location.gccloc
+ w.initial_notes.append((gccloc,
+ 'was expecting final ob_refcnt to be N + %i (for some unknown N)'
+ % exp_refcnt))
if exp_refcnt > 0:
- w.add_note(endstate.get_gcc_loc(fun),
- ('due to object being referenced by: %s'
- % ', '.join(exp_refs)))
- w.add_note(endstate.get_gcc_loc(fun),
- ('but final ob_refcnt is N + %i'
- % v_ob_refcnt.relvalue))
+ w.initial_notes.append((gccloc,
+ 'due to object being referenced by: %s'
+ % ', '.join(exp_refs)))
+ w.initial_notes.append((gccloc,
+ 'but final ob_refcnt is N + %i'
+ % v_ob_refcnt.relvalue))
+ if 0:
+ # Handy for debugging:
+ notetext += 'this was trace %i' % i
+
# For dynamically-allocated objects, indicate where they
# were allocated:
if isinstance(r_obj, RegionOnHeap):
alloc_loc = r_obj.alloc_stmt.loc
if alloc_loc:
- w.add_note(r_obj.alloc_stmt.loc,
- ('%s allocated at: %s'
- % (r_obj.name,
- get_src_for_loc(alloc_loc))))
+ w.initial_notes.append((r_obj.alloc_stmt.loc,
+ ('%s allocated at: %s'
+ % (r_obj.name,
+ get_src_for_loc(alloc_loc)))))
# Summarize the control flow we followed through the function:
if 1:
@@ -4046,12 +4063,8 @@ def emit_refcount_warning(msg,
# Debug help:
from libcpychecker.diagnostics import TestAnnotator
annotator = TestAnnotator()
- w.add_trace(trace, annotator)
+ w.trace = make_firehose_trace(fun.decl.name, trace, annotator)
- if 0:
- # Handy for debugging:
- w.add_note(endstate.get_gcc_loc(fun),
- 'this was trace %i' % i)
return w
@@ -4106,20 +4119,24 @@ def check_refcount_for_one_object(r_obj, v_ob_refcnt, v_return,
% (desc,
v_ob_refcnt.relvalue - exp_refcnt),
exp_refcnt, exp_refs, v_ob_refcnt, r_obj, desc,
- trace, endstate, fun, rep)
+ trace, endstate, fun, rep,
+ testid='refcount-too-high',
+ cwe=None)
elif v_ob_refcnt.relvalue < exp_refcnt:
# Refcount is too low:
w = emit_refcount_warning('ob_refcnt of %s is %i too low'
% (desc,
exp_refcnt - v_ob_refcnt.relvalue),
exp_refcnt, exp_refs, v_ob_refcnt, r_obj, desc,
- trace, endstate, fun, rep)
+ trace, endstate, fun, rep,
+ testid='refcount-too-low',
+ cwe=None)
# Special-case hint for when None has too low a refcount:
if is_return_value:
if isinstance(v_return.region, RegionForGlobal):
if v_return.region.vardecl.name == '_Py_NoneStruct':
- w.add_note(endstate.get_gcc_loc(fun),
- 'consider using "Py_RETURN_NONE;"')
+ w.final_notes.append((endstate.get_gcc_loc(fun),
+ 'consider using "Py_RETURN_NONE;"'))
# Detect failure to set exceptions when returning NULL,
# and verify usage of
@@ -4144,8 +4161,12 @@ def warn_about_NULL_without_exception(v_return,
w = rep.make_warning(fun,
endstate.get_gcc_loc(fun),
- 'returning (PyObject*)NULL without setting an exception')
- w.add_trace(trace, ExceptionStateAnnotator())
+ 'returning (PyObject*)NULL without setting an exception',
+ testid='returns-NULL-without-setting-exception',
+ cwe=None)
+ w.trace = make_firehose_trace(fun.decl.name,
+ trace,
+ ExceptionStateAnnotator())
# If this is function was marked with our custom
# __attribute__((cpychecker_sets_exception))
@@ -4158,8 +4179,12 @@ def warn_about_NULL_without_exception(v_return,
endstate.get_gcc_loc(fun),
('function is marked with'
' __attribute__((cpychecker_sets_exception))'
- ' but can return without setting an exception'))
- w.add_trace(trace, ExceptionStateAnnotator())
+ ' but can return without setting an exception'),
+ testid='marked-as-setting-exception-but-does-not',
+ cwe=None)
+ w.trace = make_firehose_trace(fun.decl.name,
+ trace,
+ ExceptionStateAnnotator())
# If this is function was marked with our custom
# __attribute__((cpychecker_negative_result_sets_exception))
@@ -4175,13 +4200,40 @@ def warn_about_NULL_without_exception(v_return,
('function is marked with __attribute__(('
'cpychecker_negative_result_sets_exception))'
' but can return %s without setting an exception'
- % v_return.value))
- w.add_trace(trace, ExceptionStateAnnotator())
-
+ % v_return.value),
+ testid='marked-as-setting-exception-but-does-not',
+ cwe=None)
+ w.trace = make_firehose_trace(fun.decl.name,
+ trace,
+ ExceptionStateAnnotator())
+
+
+# Gather stats on function:
+def gather_stats_on_function(fun, dstdict):
+ dstdict['num_basic_blocks'] = 0
+ dstdict['num_edges'] = 0
+ dstdict['num_gimple_statements'] = 0
+ dstdict['num_Py_api_calls'] = 0
+ for bb in fun.cfg.basic_blocks:
+ dstdict['num_basic_blocks'] += 1
+ for edge in bb.succs:
+ dstdict['num_edges'] += 1
+ if bb.gimple:
+ for stmt in bb.gimple:
+ dstdict['num_gimple_statements'] += 1
+ if isinstance(stmt, gcc.GimpleCall):
+ if stmt.fndecl:
+ if stmt.fndecl.name.startswith('Py') \
+ or stmt.fndecl.name.startswith('_Py'):
+ dstdict['num_Py_api_calls'] += 1
+ # Gather stats on all function calls:
+ key = 'calls_to_%s' % stmt.fndecl.name
+ if key in dstdict:
+ dstdict[key] += 1
+ else:
+ dstdict[key] = 1
-def impl_check_refcounts(fun, dump_traces=False,
- show_possible_null_derefs=False,
- maxtrans=256):
+def impl_check_refcounts(ctxt, fun, options):
"""
Inner implementation of the refcount checker, checking the refcounting
behavior of a function, returning a Reporter instance.
@@ -4190,15 +4242,24 @@ def impl_check_refcounts(fun, dump_traces=False,
that want to get at the Reporter directly (e.g. for JSON output)
fun: the gcc.Function to be checked
-
- dump_traces: bool: if True, dump information about the traces through
- the function to stdout (for self tests)
"""
# Abstract interpretation:
# Walk the CFG, gathering the information we're interested in
check_isinstance(fun, gcc.Function)
+ if options.reportstats:
+ # Optionally, dump information about each function to the firehose
+ # results:
+ customfields = CustomFields()
+ gather_stats_on_function(fun, customfields)
+
+ info = Info(infoid='stats',
+ location=WrappedGccLocation(fun.start, fun.decl.name),
+ message=None,
+ customfields=customfields)
+ ctxt.analysis.results.append(info)
+
# Generate a mapping from facet names to facet classes, so that we know
# what additional attributes each State instance will have.
#
@@ -4208,19 +4269,30 @@ def impl_check_refcounts(fun, dump_traces=False,
if get_PyObject():
facets['cpython'] = CPython
- limits=Limits(maxtrans=maxtrans)
+ limits=Limits(maxtrans=options.maxtrans)
try:
traces = iter_traces(fun,
+ ctxt,
facets,
limits=limits)
except TooComplicated:
err = sys.exc_info()[1]
- gcc.inform(fun.start,
- 'this function is too complicated for the reference-count checker to fully analyze: not all paths were analyzed')
+ MESSAGE = ('this function is too complicated for the reference-count'
+ ' checker to fully analyze: not all paths were analyzed')
+ gcc.inform(fun.start, MESSAGE)
+ customfields = CustomFields(maxtrans=options.maxtrans)
+ gather_stats_on_function(fun, customfields)
+
+ failure = Failure(failureid='too-complicated',
+ location=WrappedGccLocation(fun.start, fun.decl.name),
+ message=Message(MESSAGE),
+ customfields=customfields)
+ ctxt.analysis.results.append(failure)
+
traces = err.complete_traces
- if dump_traces:
+ if options.dump_traces:
traces = list(traces)
dump_traces_to_stdout(traces)
@@ -4228,7 +4300,7 @@ def impl_check_refcounts(fun, dump_traces=False,
if 0:
filename = ('%s.%s-refcount-traces.html'
% (gcc.get_dump_base_name(), fun.decl.name))
- rep = Reporter()
+ rep = Reporter(ctxt)
for i, trace in enumerate(traces):
endstate = trace.states[-1]
r = rep.make_debug_dump(fun,
@@ -4241,7 +4313,7 @@ def impl_check_refcounts(fun, dump_traces=False,
('graphical debug report for function %r written out to %r'
% (fun.decl.name, filename)))
- rep = Reporter()
+ rep = Reporter(ctxt)
# Iterate through all traces, adding reports to the Reporter:
for i, trace in enumerate(traces):
@@ -4260,15 +4332,17 @@ def impl_check_refcounts(fun, dump_traces=False,
if isinstance(trace.err, (NullPtrDereference, NullPtrArgument,
PredictedArithmeticError)):
if not trace.err.isdefinite:
- if not show_possible_null_derefs:
+ if not options.show_possible_null_derefs:
continue
- w = rep.make_warning(fun, trace.err.loc, str(trace.err))
- w.add_trace(trace)
+ w = rep.make_warning(fun, trace.err.loc, str(trace.err),
+ trace.err.testid,
+ cwe=None)
+ w.trace = make_firehose_trace(fun.decl.name, trace, None)
if hasattr(trace.err, 'why'):
if trace.err.why:
- w.add_note(trace.err.loc,
- trace.err.why)
+ w.final_notes.append((trace.err.loc,
+ trace.err.why))
# FIXME: in our example this ought to mention where the values came from
continue
# Otherwise, the trace proceeds normally
@@ -4305,10 +4379,12 @@ def impl_check_refcounts(fun, dump_traces=False,
if isinstance(rvalue, DeallocatedMemory):
w = rep.make_warning(fun,
endstate.get_gcc_loc(fun),
- 'returning pointer to deallocated memory')
- w.add_trace(trace)
- w.add_note(rvalue.loc,
- 'memory deallocated here')
+ 'returning pointer to deallocated memory',
+ testid='returns-pointer-to-deallocated-memory',
+ cwe=None)
+ w.trace = make_firehose_trace(fun.decl.name, trace, None)
+ w.final_notes.append((rvalue.loc,
+ 'memory deallocated here'))
warn_about_NULL_without_exception(v_return,
trace, endstate, fun, rep)
@@ -4318,35 +4394,24 @@ def impl_check_refcounts(fun, dump_traces=False,
return rep
-def check_refcounts(fun, dump_traces=False, show_traces=False,
- show_possible_null_derefs=False,
- show_timings=False,
- maxtrans=256,
- dump_json=False):
+def check_refcounts(ctxt, fun, options):
"""
The top-level function of the refcount checker, checking the refcounting
behavior of a function
fun: the gcc.Function to be checked
-
- dump_traces: bool: if True, dump information about the traces through
- the function to stdout (for self tests)
-
- show_traces: bool: if True, display a diagram of the state transition graph
-
- show_timings: bool: if True, add timing information to stderr
"""
- log('check_refcounts(%r, %r, %r)', fun, dump_traces, show_traces)
+ log('check_refcounts(%r)', fun)
# show_timings = 1
- if show_timings:
+ if options.show_timings:
import time
start_cpusecs = time.clock()
gcc.inform(fun.start, 'Analyzing reference-counting within %s' % fun.decl.name)
- if show_traces:
+ if options.show_traces:
from libcpychecker.visualizations import StateGraphPrettyPrinter
sg = StateGraph(fun, log, MyState)
sgpp = StateGraphPrettyPrinter(sg)
@@ -4355,10 +4420,7 @@ def check_refcounts(fun, dump_traces=False, show_traces=False,
# print(dot)
invoke_dot(dot)
- rep = impl_check_refcounts(fun,
- dump_traces,
- show_possible_null_derefs,
- maxtrans)
+ rep = impl_check_refcounts(ctxt, fun, options)
# Organize the Report instances into equivalence classes, simplifying
# the list of reports:
@@ -4370,12 +4432,6 @@ def check_refcounts(fun, dump_traces=False, show_traces=False,
rep.flush()
if rep.got_warnings():
- if dump_json:
- # JSON output:
- filename = ('%s.%s.json'
- % (gcc.get_dump_base_name(), fun.decl.name))
- rep.dump_json(fun, filename)
-
filename = ('%s.%s-refcount-errors.html'
% (gcc.get_dump_base_name(), fun.decl.name))
rep.dump_html(fun, filename)
@@ -4383,7 +4439,7 @@ def check_refcounts(fun, dump_traces=False, show_traces=False,
('graphical error report for function %r written out to %r'
% (fun.decl.name, filename)))
- if show_timings:
+ if options.show_timings:
end_cpusecs = time.clock()
gcc.inform(fun.start, 'Finished analyzing reference-counting within %s' % fun.decl.name)
gcc.inform(fun.start,
diff --git a/libcpychecker/visualizations.py b/libcpychecker/visualizations.py
index bf7ee99a..04e806c2 100644
--- a/libcpychecker/visualizations.py
+++ b/libcpychecker/visualizations.py
@@ -1,5 +1,5 @@
-# Copyright 2011 David Malcolm
-# Copyright 2011 Red Hat, Inc.
+# Copyright 2011, 2012, 2013 David Malcolm
+# Copyright 2011, 2012, 2013 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
@@ -244,6 +244,17 @@ def make_header(self):
font-size: 90%;
}
+.visindex {
+ border: 0.2em solid #ccc;
+ -moz-box-shadow: 2px 2px 2px #ccc;
+ -webkit-box-shadow: 2px 2px 2px #ccc;
+ box-shadow: 2px 2px 2px #ccc;
+ #margin-left: 5em;
+ font-family: proportional;
+ #font-style: bold;
+ #font-size: 100%;
+}
+
.error {
#border: 0.1em dotted #ddffdd;
#padding: 1em;
@@ -295,7 +306,7 @@ def make_report(self, report):
result += '\n'
result += ' | File: | %s |
\n' % self.fun.start.file
result += ' | Function: | %s |
\n' % self.fun.decl.name
- result += ' | Error: | %s |
\n' % report.msg
+ result += ' | Error: | %s |
\n' % report.message.text
result += '
\n'
# Render any trace that we have:
@@ -314,9 +325,7 @@ def make_html_for_trace(self, report, trace):
# (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)
+ reached_lines.add(state.location.line)
# Render source code:
srcfile = self.fun.start.file
@@ -343,13 +352,6 @@ def make_html_for_trace(self, report, trace):
lines = html.splitlines()
result = lines[0]
- # Generate any notes from the report's annotator (if any):
- notes = []
- annotator = report.get_annotator_for_trace(trace)
- if annotator:
- for trans in trace.transitions:
- notes += annotator.get_notes(trans)
-
# The rest contains the actual source lines:
lines = lines[1:]
for linenum, line in zip(range(start_line, end_line), lines):
@@ -370,22 +372,18 @@ def make_html_for_trace(self, report, trace):
result += '\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 += '%s\n' % trans.desc
+ visindex = 0
+ for state in trace.states:
+ if state.notes:
+ visindex += 1
+ if state.location.line == linenum:
+ notes = '%s\n' % state.notes.text # FIXME: escape
+ for text in state.extra_notes:
+ notes += '%s\n' % text
+ result += '%i %s
\n' % (visindex, notes.strip()) # FIXME: escape
# Report the top-level message, if it happens here:
- if report.loc.line == linenum:
- result += '%s\n' % report.msg
- # Add notes attached to the report:
- for note in report.notes:
- if note.loc and note.loc.line == linenum:
- result += '%s\n' % note.msg
- # Add any notes from the annotator:
- for note in notes:
- if note.loc and note.loc.line == linenum:
- result += '%s\n' % note.msg
+ if report.location.line == linenum:
+ result += '%s\n' % report.message.text # FIXME: escape
result += '\n'
result += '\n'
@@ -400,18 +398,20 @@ def make_html_for_trace(self, report, trace):
#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:
+ for i in range(len(trace.states)-1):
+ src = trace.states[i]
+ dst = trace.states[i + 1]
+ if src.notes:
+ src_loc = src.location
+ dst_loc = dst.location
+ if src_loc != dst_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,
+ self.trace_idx, dst_loc.line,
''))#trans.desc))
result += ' })\n'
result += '\n'
diff --git a/tests/cpychecker/PyArg_ParseTuple/incorrect_codes_S_and_U/script.py b/tests/cpychecker/PyArg_ParseTuple/incorrect_codes_S_and_U/script.py
index e120c53f..440641b3 100644
--- a/tests/cpychecker/PyArg_ParseTuple/incorrect_codes_S_and_U/script.py
+++ b/tests/cpychecker/PyArg_ParseTuple/incorrect_codes_S_and_U/script.py
@@ -1,5 +1,5 @@
-# Copyright 2011 David Malcolm
-# Copyright 2011 Red Hat, Inc.
+# Copyright 2011, 2013 David Malcolm
+# Copyright 2011, 2013 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
@@ -15,5 +15,23 @@
# along with this program. If not, see
# .
+from gccutils.selftests import assertEqual
from libcpychecker import main
-main()
+
+def selftest(ctxt, fun):
+ assertEqual(len(ctxt.analysis.results), 2)
+
+ issue0 = ctxt.analysis.results[0]
+ assertEqual(issue0.testid, 'mismatching-type-in-format-string')
+
+ # Verify that the issue contains meaningful custom fields:
+ assertEqual(issue0.customfields['function'], 'PyArg_ParseTuple')
+ assertEqual(issue0.customfields['format-code'], 'S')
+ assertEqual(issue0.customfields['full-format-string'], 'SU')
+ assertEqual(issue0.customfields['expected-type'],
+ 'one of "struct PyStringObject * *" or "struct PyObject * *"')
+ assertEqual(issue0.customfields['actual-type'], '"int *" (pointing to 32 bits)')
+ assertEqual(issue0.customfields['expression'], '&val1')
+ assertEqual(issue0.customfields['argument-num'], 3)
+
+main(selftest=selftest)
diff --git a/tests/cpychecker/PyMethodDef/correct/script.py b/tests/cpychecker/PyMethodDef/correct/script.py
index 0d5d05f3..32d1d066 100644
--- a/tests/cpychecker/PyMethodDef/correct/script.py
+++ b/tests/cpychecker/PyMethodDef/correct/script.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright 2011 David Malcolm
-# Copyright 2011 Red Hat, Inc.
+# Copyright 2011, 2013 David Malcolm
+# Copyright 2011, 2013 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
@@ -16,6 +16,6 @@
# along with this program. If not, see
# .
-from libcpychecker import main
+from libcpychecker import main, Options
-main(verify_refcounting=False)
+main(Options(verify_refcounting=False))
diff --git a/tests/cpychecker/PyMethodDef/incorrect-types/script.py b/tests/cpychecker/PyMethodDef/incorrect-types/script.py
index 0d5d05f3..32d1d066 100644
--- a/tests/cpychecker/PyMethodDef/incorrect-types/script.py
+++ b/tests/cpychecker/PyMethodDef/incorrect-types/script.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright 2011 David Malcolm
-# Copyright 2011 Red Hat, Inc.
+# Copyright 2011, 2013 David Malcolm
+# Copyright 2011, 2013 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
@@ -16,6 +16,6 @@
# along with this program. If not, see
# .
-from libcpychecker import main
+from libcpychecker import main, Options
-main(verify_refcounting=False)
+main(Options(verify_refcounting=False))
diff --git a/tests/cpychecker/PyMethodDef/missing-sentinel/script.py b/tests/cpychecker/PyMethodDef/missing-sentinel/script.py
index 0d5d05f3..32d1d066 100644
--- a/tests/cpychecker/PyMethodDef/missing-sentinel/script.py
+++ b/tests/cpychecker/PyMethodDef/missing-sentinel/script.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright 2011 David Malcolm
-# Copyright 2011 Red Hat, Inc.
+# Copyright 2011, 2013 David Malcolm
+# Copyright 2011, 2013 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
@@ -16,6 +16,6 @@
# along with this program. If not, see
# .
-from libcpychecker import main
+from libcpychecker import main, Options
-main(verify_refcounting=False)
+main(Options(verify_refcounting=False))
diff --git a/tests/cpychecker/fileptr-missing-error-check/script.py b/tests/cpychecker/fileptr-missing-error-check/script.py
index fef1ee19..32d1d066 100644
--- a/tests/cpychecker/fileptr-missing-error-check/script.py
+++ b/tests/cpychecker/fileptr-missing-error-check/script.py
@@ -1,5 +1,6 @@
-# Copyright 2011 David Malcolm
-# Copyright 2011 Red Hat, Inc.
+# -*- coding: utf-8 -*-
+# Copyright 2011, 2013 David Malcolm
+# Copyright 2011, 2013 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
@@ -15,5 +16,6 @@
# along with this program. If not, see
# .
-from libcpychecker import main
-main(verify_refcounting=False)
+from libcpychecker import main, Options
+
+main(Options(verify_refcounting=False))
diff --git a/tests/cpychecker/refcounts/combinatorial-explosion/script.py b/tests/cpychecker/refcounts/combinatorial-explosion/script.py
index fdd5ba3e..1797ecb2 100644
--- a/tests/cpychecker/refcounts/combinatorial-explosion/script.py
+++ b/tests/cpychecker/refcounts/combinatorial-explosion/script.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright 2011 David Malcolm
-# Copyright 2011 Red Hat, Inc.
+# Copyright 2011, 2013 David Malcolm
+# Copyright 2011, 2013 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
@@ -16,7 +16,75 @@
# along with this program. If not, see
# .
-from libcpychecker import main
-main(verify_refcounting=True,
- dump_traces=True,
- show_traces=False)
+import atexit
+import os
+import sys
+
+from firehose.model import Analysis, Failure
+
+from gccutils.selftests import assertEqual
+from libcpychecker import main, Options
+
+XML_OUTPUT_PATH = 'tests/cpychecker/refcounts/combinatorial-explosion/output.xml'
+
+def assertGreater(lhs, rhs):
+ if not (lhs > rhs):
+ raise ValueError('%r <= %r' % (lhs, rhs))
+
+def verify_analysis(analysis):
+ # Verify that a Failure instance made it into the firehose output:
+ assertEqual(len(analysis.results), 1)
+ f = analysis.results[0]
+
+ assert isinstance(f, Failure)
+
+ assertEqual(f.failureid, 'too-complicated')
+ assertEqual(f.location.file.givenpath,
+ 'tests/cpychecker/refcounts/combinatorial-explosion/input.c')
+ assertEqual(f.location.function.name, 'test_adding_module_objects')
+ assertEqual(f.location.line, 31)
+ assertEqual(f.location.column, 1)
+ assertEqual(f.message.text,
+ ('this function is too complicated for the reference-count'
+ ' checker to fully analyze: not all paths were analyzed'))
+ assert isinstance(f.customfields['maxtrans'], int)
+ assertEqual(f.customfields['maxtrans'], options.maxtrans)
+ assertGreater(f.customfields['num_basic_blocks'], 50)
+ # 72 with gcc 4.7.2 and python 2.7.3
+
+ assertGreater(f.customfields['num_gimple_statements'], 100)
+ # 166 with gcc 4.7.2 and python 2.7.3
+
+ assertEqual(f.customfields['num_Py_api_calls'], 33)
+ # 33 with gcc 4.7.2 and python 2.7.3
+ # (1 call to PyLong_FromLong, 32 calls to PyModule_AddObject):
+ assertEqual(f.customfields['calls_to_PyLong_FromLong'], 1)
+ assertEqual(f.customfields['calls_to_PyModule_AddObject'], 32)
+
+def verify_firehose():
+ global ctxt
+ sys.stderr.write('verify_firehose()\n')
+ assert ctxt.was_flushed
+
+ verify_analysis(ctxt.analysis)
+
+ # Verify that the XML was actually written:
+ with open(XML_OUTPUT_PATH) as f:
+ analysis_from_disk = Analysis.from_xml(f)
+ # Verify that everything made it to disk:
+ verify_analysis(analysis_from_disk)
+
+ os.unlink(XML_OUTPUT_PATH)
+
+ # Ensure that this testing code actually got run (stdout is
+ # checked):
+ sys.stderr.write('GOT HERE\n')
+
+options = Options(verify_refcounting=True,
+ dump_traces=False,
+ show_traces=False,
+ outputxmlpath=XML_OUTPUT_PATH)
+atexit.register(verify_firehose)
+ctxt = main(options)
+if 0:
+ sys.stderr.write('%s\n' % atexit._exithandlers)
diff --git a/tests/cpychecker/refcounts/combinatorial-explosion/stderr.txt b/tests/cpychecker/refcounts/combinatorial-explosion/stderr.txt
index e10681bf..626b2519 100644
--- a/tests/cpychecker/refcounts/combinatorial-explosion/stderr.txt
+++ b/tests/cpychecker/refcounts/combinatorial-explosion/stderr.txt
@@ -1,2 +1,4 @@
tests/cpychecker/refcounts/combinatorial-explosion/input.c: In function 'test_adding_module_objects':
-tests/cpychecker/refcounts/combinatorial-explosion/input.c:31:1: note: this function is too complicated for the reference-count checker to fully analyze: not all paths were analyzed
+tests/cpychecker/refcounts/combinatorial-explosion/input.c:31:nn: note: this function is too complicated for the reference-count checker to fully analyze: not all paths were analyzed
+verify_firehose()
+GOT HERE
diff --git a/tests/cpychecker/refcounts/combinatorial-explosion/stdout.txt b/tests/cpychecker/refcounts/combinatorial-explosion/stdout.txt
index bf9ca0fb..e69de29b 100644
--- a/tests/cpychecker/refcounts/combinatorial-explosion/stdout.txt
+++ b/tests/cpychecker/refcounts/combinatorial-explosion/stdout.txt
@@ -1,983 +0,0 @@
-Trace 0:
- Transitions:
- 'when PyLong_FromLong() succeeds'
- 'taking False path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() fails'
- 'taking False path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'returning'
- Return value:
- repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)))
- str(): (struct PyObject *)&RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:32
- r->ob_refcnt: refs: 1 + N where N >= 31
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionForGlobal(gcc.VarDecl('PyLong_Type')))
- region for gcc.ParmDecl('m') on stack:
- repr(): RegionOnStack("region for gcc.ParmDecl('m')")
- str(): region for gcc.ParmDecl('m') on stack
- r->ob_refcnt: refs: 0 + N where N >= 1
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=30), region=Region("region-for-type-of-arg-gcc.ParmDecl('m')"))
- Exception:
- (struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:160
-
-Trace 1:
- Transitions:
- 'when PyLong_FromLong() succeeds'
- 'taking False path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() fails'
- 'taking False path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() fails'
- 'taking False path'
- 'returning'
- Return value:
- repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)))
- str(): (struct PyObject *)&RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:32
- r->ob_refcnt: refs: 1 + N where N >= 30
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionForGlobal(gcc.VarDecl('PyLong_Type')))
- region for gcc.ParmDecl('m') on stack:
- repr(): RegionOnStack("region for gcc.ParmDecl('m')")
- str(): region for gcc.ParmDecl('m') on stack
- r->ob_refcnt: refs: 0 + N where N >= 1
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=30), region=Region("region-for-type-of-arg-gcc.ParmDecl('m')"))
- Exception:
- (struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:172
-
-Trace 2:
- Transitions:
- 'when PyLong_FromLong() succeeds'
- 'taking False path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() fails'
- 'taking False path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() fails'
- 'taking False path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'returning'
- Return value:
- repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)))
- str(): (struct PyObject *)&RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:32
- r->ob_refcnt: refs: 1 + N where N >= 30
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionForGlobal(gcc.VarDecl('PyLong_Type')))
- region for gcc.ParmDecl('m') on stack:
- repr(): RegionOnStack("region for gcc.ParmDecl('m')")
- str(): region for gcc.ParmDecl('m') on stack
- r->ob_refcnt: refs: 0 + N where N >= 1
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=30), region=Region("region-for-type-of-arg-gcc.ParmDecl('m')"))
- Exception:
- (struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:168
-
-Trace 3:
- Transitions:
- 'when PyLong_FromLong() succeeds'
- 'taking False path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() fails'
- 'taking False path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() fails'
- 'taking False path'
- 'when PyModule_AddObject() fails'
- 'taking False path'
- 'returning'
- Return value:
- repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)))
- str(): (struct PyObject *)&RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:32
- r->ob_refcnt: refs: 1 + N where N >= 29
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionForGlobal(gcc.VarDecl('PyLong_Type')))
- region for gcc.ParmDecl('m') on stack:
- repr(): RegionOnStack("region for gcc.ParmDecl('m')")
- str(): region for gcc.ParmDecl('m') on stack
- r->ob_refcnt: refs: 0 + N where N >= 1
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=30), region=Region("region-for-type-of-arg-gcc.ParmDecl('m')"))
- Exception:
- (struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:172
-
-Trace 4:
- Transitions:
- 'when PyLong_FromLong() succeeds'
- 'taking False path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'returning'
- Return value:
- repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)))
- str(): (struct PyObject *)&RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:32
- r->ob_refcnt: refs: 1 + N where N >= 32
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionForGlobal(gcc.VarDecl('PyLong_Type')))
- region for gcc.ParmDecl('m') on stack:
- repr(): RegionOnStack("region for gcc.ParmDecl('m')")
- str(): region for gcc.ParmDecl('m') on stack
- r->ob_refcnt: refs: 0 + N where N >= 1
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=30), region=Region("region-for-type-of-arg-gcc.ParmDecl('m')"))
- Exception:
- (struct PyObject *)0 from tests/cpychecker/refcounts/combinatorial-explosion/input.c:31
-
-Trace 5:
- Transitions:
- 'when PyLong_FromLong() succeeds'
- 'taking False path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() fails'
- 'taking False path'
- 'returning'
- Return value:
- repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)))
- str(): (struct PyObject *)&RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:32
- r->ob_refcnt: refs: 1 + N where N >= 31
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionForGlobal(gcc.VarDecl('PyLong_Type')))
- region for gcc.ParmDecl('m') on stack:
- repr(): RegionOnStack("region for gcc.ParmDecl('m')")
- str(): region for gcc.ParmDecl('m') on stack
- r->ob_refcnt: refs: 0 + N where N >= 1
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=30), region=Region("region-for-type-of-arg-gcc.ParmDecl('m')"))
- Exception:
- (struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:172
-
-Trace 6:
- Transitions:
- 'when PyLong_FromLong() succeeds'
- 'taking False path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() fails'
- 'taking False path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'returning'
- Return value:
- repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)))
- str(): (struct PyObject *)&RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:32
- r->ob_refcnt: refs: 1 + N where N >= 31
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionForGlobal(gcc.VarDecl('PyLong_Type')))
- region for gcc.ParmDecl('m') on stack:
- repr(): RegionOnStack("region for gcc.ParmDecl('m')")
- str(): region for gcc.ParmDecl('m') on stack
- r->ob_refcnt: refs: 0 + N where N >= 1
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=30), region=Region("region-for-type-of-arg-gcc.ParmDecl('m')"))
- Exception:
- (struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:168
-
-Trace 7:
- Transitions:
- 'when PyLong_FromLong() succeeds'
- 'taking False path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() fails'
- 'taking False path'
- 'when PyModule_AddObject() fails'
- 'taking False path'
- 'returning'
- Return value:
- repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)))
- str(): (struct PyObject *)&RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:32
- r->ob_refcnt: refs: 1 + N where N >= 30
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionForGlobal(gcc.VarDecl('PyLong_Type')))
- region for gcc.ParmDecl('m') on stack:
- repr(): RegionOnStack("region for gcc.ParmDecl('m')")
- str(): region for gcc.ParmDecl('m') on stack
- r->ob_refcnt: refs: 0 + N where N >= 1
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=30), region=Region("region-for-type-of-arg-gcc.ParmDecl('m')"))
- Exception:
- (struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:172
-
-Trace 8:
- Transitions:
- 'when PyLong_FromLong() succeeds'
- 'taking False path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() fails'
- 'taking False path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'returning'
- Return value:
- repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)))
- str(): (struct PyObject *)&RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:32
- r->ob_refcnt: refs: 1 + N where N >= 31
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionForGlobal(gcc.VarDecl('PyLong_Type')))
- region for gcc.ParmDecl('m') on stack:
- repr(): RegionOnStack("region for gcc.ParmDecl('m')")
- str(): region for gcc.ParmDecl('m') on stack
- r->ob_refcnt: refs: 0 + N where N >= 1
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=30), region=Region("region-for-type-of-arg-gcc.ParmDecl('m')"))
- Exception:
- (struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:164
-
-Trace 9:
- Transitions:
- 'when PyLong_FromLong() succeeds'
- 'taking False path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() fails'
- 'taking False path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() fails'
- 'taking False path'
- 'returning'
- Return value:
- repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)))
- str(): (struct PyObject *)&RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:32
- r->ob_refcnt: refs: 1 + N where N >= 30
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionForGlobal(gcc.VarDecl('PyLong_Type')))
- region for gcc.ParmDecl('m') on stack:
- repr(): RegionOnStack("region for gcc.ParmDecl('m')")
- str(): region for gcc.ParmDecl('m') on stack
- r->ob_refcnt: refs: 0 + N where N >= 1
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=30), region=Region("region-for-type-of-arg-gcc.ParmDecl('m')"))
- Exception:
- (struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:172
-
-Trace 10:
- Transitions:
- 'when PyLong_FromLong() succeeds'
- 'taking False path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() fails'
- 'taking False path'
- 'when PyModule_AddObject() fails'
- 'taking False path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'returning'
- Return value:
- repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)))
- str(): (struct PyObject *)&RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:32
- r->ob_refcnt: refs: 1 + N where N >= 30
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionForGlobal(gcc.VarDecl('PyLong_Type')))
- region for gcc.ParmDecl('m') on stack:
- repr(): RegionOnStack("region for gcc.ParmDecl('m')")
- str(): region for gcc.ParmDecl('m') on stack
- r->ob_refcnt: refs: 0 + N where N >= 1
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=30), region=Region("region-for-type-of-arg-gcc.ParmDecl('m')"))
- Exception:
- (struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:168
-
-Trace 11:
- Transitions:
- 'when PyLong_FromLong() succeeds'
- 'taking False path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() succeeds'
- 'taking True path'
- 'when PyModule_AddObject() fails'
- 'taking False path'
- 'when PyModule_AddObject() fails'
- 'taking False path'
- 'when PyModule_AddObject() fails'
- 'taking False path'
- 'returning'
- Return value:
- repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)))
- str(): (struct PyObject *)&RegionOnHeap('PyLongObject', gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32)) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:32
- r->ob_refcnt: refs: 1 + N where N >= 29
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=32), region=RegionForGlobal(gcc.VarDecl('PyLong_Type')))
- region for gcc.ParmDecl('m') on stack:
- repr(): RegionOnStack("region for gcc.ParmDecl('m')")
- str(): region for gcc.ParmDecl('m') on stack
- r->ob_refcnt: refs: 0 + N where N >= 1
- r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/combinatorial-explosion/input.c', line=30), region=Region("region-for-type-of-arg-gcc.ParmDecl('m')"))
- Exception:
- (struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) from tests/cpychecker/refcounts/combinatorial-explosion/input.c:172
diff --git a/tests/cpychecker/refcounts/exceptions/getopts.py b/tests/cpychecker/refcounts/exceptions/getopts.py
new file mode 100644
index 00000000..68031806
--- /dev/null
+++ b/tests/cpychecker/refcounts/exceptions/getopts.py
@@ -0,0 +1 @@
+print('-fexceptions')
diff --git a/tests/cpychecker/refcounts/exceptions/input.c b/tests/cpychecker/refcounts/exceptions/input.c
new file mode 100644
index 00000000..98803425
--- /dev/null
+++ b/tests/cpychecker/refcounts/exceptions/input.c
@@ -0,0 +1,58 @@
+/*
+ Copyright 2011 David Malcolm
+ 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
+ .
+*/
+
+#include
+
+/*
+ Test of correct reference-handling in a call to PyArg_ParseTupleAndKeywords
+ that uses the "O" format code
+*/
+
+PyObject *
+test(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ PyObject *obj;
+ char *keywords[] = {"object", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+ "O:test", keywords,
+ &obj)) {
+ return NULL;
+ }
+
+ /*
+ We now have a borrowed non-NULL ref to "obj".
+
+ To correctly use it as the return value, we need to INCREF it:
+ */
+ Py_INCREF(obj);
+ return obj;
+}
+static PyMethodDef test_methods[] = {
+ {"test_method", (PyCFunction)test, (METH_VARARGS | METH_KEYWORDS), 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/exceptions/script.py b/tests/cpychecker/refcounts/exceptions/script.py
new file mode 100644
index 00000000..fdd5ba3e
--- /dev/null
+++ b/tests/cpychecker/refcounts/exceptions/script.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Copyright 2011 David Malcolm
+# 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
+# .
+
+from libcpychecker import main
+main(verify_refcounting=True,
+ dump_traces=True,
+ show_traces=False)
diff --git a/tests/cpychecker/refcounts/exceptions/stdout.txt b/tests/cpychecker/refcounts/exceptions/stdout.txt
new file mode 100644
index 00000000..b463ec1d
--- /dev/null
+++ b/tests/cpychecker/refcounts/exceptions/stdout.txt
@@ -0,0 +1,53 @@
+Trace 0:
+ Transitions:
+ 'when PyArg_ParseTupleAndKeywords() succeeds'
+ 'taking False path'
+ 'returning'
+ Return value:
+ repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/exceptions/input.c', line=33), region=RegionOnHeap('object from arg "O"', gcc.Location(file='tests/cpychecker/refcounts/exceptions/input.c', line=33)))
+ str(): (struct PyObject *)&RegionOnHeap('object from arg "O"', gcc.Location(file='tests/cpychecker/refcounts/exceptions/input.c', line=33)) from tests/cpychecker/refcounts/exceptions/input.c:33
+ r->ob_refcnt: (long int)val [-0x7fffffffffffffff <= val <= 0x7fffffffffffffff] from tests/cpychecker/refcounts/exceptions/input.c:44
+ r->ob_type: None
+ region for gcc.ParmDecl('self') on stack:
+ repr(): RegionOnStack("region for gcc.ParmDecl('self')")
+ str(): region for gcc.ParmDecl('self') on stack
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/exceptions/input.c', line=28), region=Region("region-for-type-of-arg-gcc.ParmDecl('self')"))
+ region for gcc.ParmDecl('args') on stack:
+ repr(): RegionOnStack("region for gcc.ParmDecl('args')")
+ str(): region for gcc.ParmDecl('args') on stack
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/exceptions/input.c', line=28), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ region for gcc.ParmDecl('kwargs') on stack:
+ repr(): RegionOnStack("region for gcc.ParmDecl('kwargs')")
+ str(): region for gcc.ParmDecl('kwargs') on stack
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/exceptions/input.c', line=28), region=Region("region-for-type-of-arg-gcc.ParmDecl('kwargs')"))
+ Exception:
+ (struct PyObject *)0 from tests/cpychecker/refcounts/exceptions/input.c:29
+
+Trace 1:
+ Transitions:
+ 'when PyArg_ParseTupleAndKeywords() fails'
+ 'taking True path'
+ 'returning'
+ Return value:
+ repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/exceptions/input.c', line=36), value=0)
+ str(): (struct PyObject *)0 from tests/cpychecker/refcounts/exceptions/input.c:36
+ region for gcc.ParmDecl('self') on stack:
+ repr(): RegionOnStack("region for gcc.ParmDecl('self')")
+ str(): region for gcc.ParmDecl('self') on stack
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/exceptions/input.c', line=28), region=Region("region-for-type-of-arg-gcc.ParmDecl('self')"))
+ region for gcc.ParmDecl('args') on stack:
+ repr(): RegionOnStack("region for gcc.ParmDecl('args')")
+ str(): region for gcc.ParmDecl('args') on stack
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/exceptions/input.c', line=28), region=Region("region-for-type-of-arg-gcc.ParmDecl('args')"))
+ region for gcc.ParmDecl('kwargs') on stack:
+ repr(): RegionOnStack("region for gcc.ParmDecl('kwargs')")
+ str(): region for gcc.ParmDecl('kwargs') on stack
+ r->ob_refcnt: refs: 0 + N where N >= 1
+ r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/exceptions/input.c', line=28), region=Region("region-for-type-of-arg-gcc.ParmDecl('kwargs')"))
+ Exception:
+ (struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_TypeError')) from tests/cpychecker/refcounts/exceptions/input.c:33
diff --git a/tests/cpychecker/refcounts/incorrect_py_none/script.py b/tests/cpychecker/refcounts/incorrect_py_none/script.py
index 7fb78e80..a2f14304 100644
--- a/tests/cpychecker/refcounts/incorrect_py_none/script.py
+++ b/tests/cpychecker/refcounts/incorrect_py_none/script.py
@@ -1,5 +1,5 @@
-# Copyright 2011 David Malcolm
-# Copyright 2011 Red Hat, Inc.
+# Copyright 2011, 2013 David Malcolm
+# Copyright 2011, 2013 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
@@ -16,14 +16,15 @@
# .
import gcc
-from libcpychecker import main, get_traces
+from libcpychecker import main, get_traces, Context, Options
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)
+ ctxt = Context(Options())
+ traces = get_traces(fun, ctxt)
# We should have a single trace
#print('traces: %r' % traces)
diff --git a/tests/cpychecker/refcounts/json/basic/script.py b/tests/cpychecker/refcounts/json/basic/script.py
deleted file mode 100644
index fcf8e268..00000000
--- a/tests/cpychecker/refcounts/json/basic/script.py
+++ /dev/null
@@ -1,103 +0,0 @@
-# Copyright 2012 David Malcolm
-# Copyright 2012 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
-# .
-
-# Verify that the JSON serialization of error reports is working
-
-import gcc
-from libcpychecker.refcounts import impl_check_refcounts
-
-from gccutils.selftests import assertEqual
-
-def verify_json(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:
- rep = impl_check_refcounts(fun)
- js = rep.to_json(fun)
- if 0:
- from json import dumps
- print(dumps(js, sort_keys=True, indent=4))
-
- # Verify the top-level JSON that's emitted:
- assertEqual(js['filename'], 'tests/cpychecker/refcounts/json/basic/input.c')
- assertEqual(js['function']['name'], 'losing_refcnt_of_none')
- assertEqual(js['function']['lines'][0], 22)
- assertEqual(js['function']['lines'][1], 28)
-
- # Verify the JSON for the single error report:
- assertEqual(len(js['reports']), 1)
- r = js['reports'][0]
- assertEqual(r['severity'], "warning")
- assertEqual(r['message'], "ob_refcnt of return value is 1 too low")
- assertEqual(len(r['notes']), 4)
- assertEqual(r['notes'][0]['message'],
- "was expecting final ob_refcnt to be N + 1 (for some unknown N)")
- assertEqual(r['notes'][1]['message'],
- "due to object being referenced by: return value")
- assertEqual(r['notes'][2]['message'],
- "but final ob_refcnt is N + 0")
- assertEqual(r['notes'][3]['message'],
- "consider using \"Py_RETURN_NONE;\"")
-
- # Verify the JSON serialization of the endstate within the report:
- statejs = r['states'][-1]
-
- if 0:
- from json import dumps
- print(dumps(statejs, sort_keys=True, indent=4))
-
- # Verify that we have a location:
- for i in range(2):
- assert statejs['location'][i]['column'] > 0
- assertEqual(statejs['location'][i]['line'], 26)
-
- assertEqual(statejs['message'], None)
-
- vars = statejs['variables']
-
- # Verify that the bug in the handling of Py_None's ob_refcnt
- # is described within the JSON:
- none_refcnt = vars['_Py_NoneStruct.ob_refcnt']
- assertEqual(none_refcnt['gcctype'], "Py_ssize_t")
- assertEqual(none_refcnt['kind'], "RefcountValue")
- assertEqual(none_refcnt['actual_ob_refcnt']['refs_we_own'], 0)
- assertEqual(none_refcnt['expected_ob_refcnt']['pointers_to_this'],
- ['return value'])
-
- # Verify "self":
- selfjs = vars['self']
- assertEqual(selfjs['gcctype'], 'struct PyObject *')
- assertEqual(selfjs['kind'], 'PointerToRegion')
- assertEqual(selfjs['value_comes_from'][0]['line'], 23)
-
- ob_refcnt = vars['self->ob_refcnt']
- assertEqual(ob_refcnt['gcctype'], 'Py_ssize_t')
- assertEqual(ob_refcnt['kind'], 'RefcountValue')
- assertEqual(ob_refcnt['value_comes_from'][0]['line'], 23)
-
- ob_type = vars['self->ob_type']
- assertEqual(ob_type['gcctype'], 'struct PyTypeObject *')
- assertEqual(ob_type['kind'], 'PointerToRegion')
- assertEqual(ob_type['value_comes_from'][0]['line'], 23)
-
- # Ensure that this testing code actually got run (stdout is
- # checked):
- print('GOT HERE')
-
-gcc.register_callback(gcc.PLUGIN_PASS_EXECUTION,
- verify_json)
diff --git a/tests/cpychecker/refcounts/object_leak/script.py b/tests/cpychecker/refcounts/object_leak/script.py
index c70a11e2..14870822 100644
--- a/tests/cpychecker/refcounts/object_leak/script.py
+++ b/tests/cpychecker/refcounts/object_leak/script.py
@@ -17,14 +17,14 @@
# .
import gcc
-from libcpychecker import main, get_traces
+from libcpychecker import main, get_traces, Context, Options
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)
+ traces = get_traces(fun, Context(Options()))
# We should have two traces
# print('traces: %r' % traces)
diff --git a/tests/cpychecker/refcounts/json/basic/input.c b/tests/cpychecker/refcounts/xml/basic/input.c
similarity index 100%
rename from tests/cpychecker/refcounts/json/basic/input.c
rename to tests/cpychecker/refcounts/xml/basic/input.c
diff --git a/tests/cpychecker/refcounts/xml/basic/metadata.ini b/tests/cpychecker/refcounts/xml/basic/metadata.ini
new file mode 100644
index 00000000..0d5711cb
--- /dev/null
+++ b/tests/cpychecker/refcounts/xml/basic/metadata.ini
@@ -0,0 +1,3 @@
+[ExpectedBehavior]
+# We expect only compilation *warnings*, so we expect a 0 exit code
+exitcode = 0
diff --git a/tests/cpychecker/refcounts/xml/basic/script.py b/tests/cpychecker/refcounts/xml/basic/script.py
new file mode 100644
index 00000000..97b883fb
--- /dev/null
+++ b/tests/cpychecker/refcounts/xml/basic/script.py
@@ -0,0 +1,121 @@
+# Copyright 2012, 2013 David Malcolm
+# Copyright 2012, 2013 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
+# .
+
+# Verify that the JSON serialization of error reports is working
+
+import os
+
+from firehose.model import Analysis
+
+import gcc
+
+from gccutils.selftests import assertEqual
+from libcpychecker import Context, Options
+from libcpychecker.refcounts import impl_check_refcounts
+
+XML_OUTPUT_PATH = 'tests/cpychecker/refcounts/xml/basic/output.xml'
+
+def verify_analysis(analysis):
+ assertEqual(len(analysis.results), 1)
+ w = analysis.results[0]
+
+ assertEqual(w.cwe, None)
+ assertEqual(w.testid, 'refcount-too-low')
+ assertEqual(w.location.file.givenpath,
+ 'tests/cpychecker/refcounts/xml/basic/input.c')
+ assertEqual(w.location.function.name, 'losing_refcnt_of_none')
+ assertEqual(w.location.line, 26)
+ assertEqual(w.location.column, 5)
+ assertEqual(w.message.text, 'ob_refcnt of return value is 1 too low')
+ assertEqual(w.notes, None) # FIXME
+ # FIXME: we ought to get this:
+ # "was expecting final ob_refcnt to be N + 1 (for some unknown N)")
+ # "due to object being referenced by: return value")
+ # "but final ob_refcnt is N + 0")
+ # "consider using \"Py_RETURN_NONE;\"")
+
+ # Verify what we captured for the endstate within the report:
+ endstate = w.trace.states[-1]
+
+ # Verify that we have a location:
+ assertEqual(endstate.location.line, 26)
+ assert endstate.location.column > 0
+
+ assertEqual(endstate.notes, None)
+
+ '''
+ vars = statejs['variables']
+
+ # Verify that the bug in the handling of Py_None's ob_refcnt
+ # is described within the JSON:
+ none_refcnt = vars['_Py_NoneStruct.ob_refcnt']
+ assertEqual(none_refcnt['gcctype'], "Py_ssize_t")
+ assertEqual(none_refcnt['kind'], "RefcountValue")
+ assertEqual(none_refcnt['actual_ob_refcnt']['refs_we_own'], 0)
+ assertEqual(none_refcnt['expected_ob_refcnt']['pointers_to_this'],
+ ['return value'])
+
+ # Verify "self":
+ selfjs = vars['self']
+ assertEqual(selfjs['gcctype'], 'struct PyObject *')
+ assertEqual(selfjs['kind'], 'PointerToRegion')
+ assertEqual(selfjs['value_comes_from'][0]['line'], 23)
+
+ ob_refcnt = vars['self->ob_refcnt']
+ assertEqual(ob_refcnt['gcctype'], 'Py_ssize_t')
+ assertEqual(ob_refcnt['kind'], 'RefcountValue')
+ assertEqual(ob_refcnt['value_comes_from'][0]['line'], 23)
+
+ ob_type = vars['self->ob_type']
+ assertEqual(ob_type['gcctype'], 'struct PyTypeObject *')
+ assertEqual(ob_type['kind'], 'PointerToRegion')
+ assertEqual(ob_type['value_comes_from'][0]['line'], 23)
+ '''
+
+def verify_firehose(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:
+ options = Options(outputxmlpath=XML_OUTPUT_PATH)
+ ctxt = Context(options)
+ rep = impl_check_refcounts(ctxt, fun, options)
+ rep.flush()
+ ctxt.flush()
+
+ if 0:
+ print(ctxt.analysis.to_xml_str())
+
+ assertEqual(len(ctxt.analysis.results), 1)
+ w = ctxt.analysis.results[0]
+
+ verify_analysis(ctxt.analysis)
+
+ # Verify that the XML was written:
+ with open(XML_OUTPUT_PATH) as f:
+ analysis_from_disk = Analysis.from_xml(f)
+ # Verify that everything made it to disk:
+ verify_analysis(analysis_from_disk)
+
+ os.unlink(XML_OUTPUT_PATH)
+
+ # Ensure that this testing code actually got run (stdout is
+ # checked):
+ print('GOT HERE')
+
+gcc.register_callback(gcc.PLUGIN_PASS_EXECUTION,
+ verify_firehose)
diff --git a/tests/cpychecker/refcounts/xml/basic/stderr.txt b/tests/cpychecker/refcounts/xml/basic/stderr.txt
new file mode 100644
index 00000000..015d570e
--- /dev/null
+++ b/tests/cpychecker/refcounts/xml/basic/stderr.txt
@@ -0,0 +1,7 @@
+tests/cpychecker/refcounts/xml/basic/input.c: In function 'losing_refcnt_of_none':
+tests/cpychecker/refcounts/xml/basic/input.c:26:5: warning: ob_refcnt of return value is 1 too low [enabled by default]
+tests/cpychecker/refcounts/xml/basic/input.c:26:5: note: was expecting final ob_refcnt to be N + 1 (for some unknown N)
+tests/cpychecker/refcounts/xml/basic/input.c:26:5: note: due to object being referenced by: return value
+tests/cpychecker/refcounts/xml/basic/input.c:26:5: note: but final ob_refcnt is N + 0
+tests/cpychecker/refcounts/xml/basic/input.c:26:5: note: returning at: return Py_None;
+tests/cpychecker/refcounts/xml/basic/input.c:26:5: note: consider using "Py_RETURN_NONE;"
diff --git a/tests/cpychecker/refcounts/json/basic/stdout.txt b/tests/cpychecker/refcounts/xml/basic/stdout.txt
similarity index 100%
rename from tests/cpychecker/refcounts/json/basic/stdout.txt
rename to tests/cpychecker/refcounts/xml/basic/stdout.txt
diff --git a/tests/cpychecker/refcounts/xml/exceptions/input.c b/tests/cpychecker/refcounts/xml/exceptions/input.c
new file mode 100644
index 00000000..b7296b35
--- /dev/null
+++ b/tests/cpychecker/refcounts/xml/exceptions/input.c
@@ -0,0 +1,40 @@
+/*
+ Copyright 2013 David Malcolm
+ Copyright 2013 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
+ .
+*/
+
+#include
+
+/*
+ Verify that the XML output captures any "unhandled" python exceptions that
+ occur in the analyzer
+*/
+extern PyObject *generate_exception(PyObject *);
+
+PyObject *
+test(PyObject *obj)
+{
+ return generate_exception(obj);
+}
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/refcounts/xml/exceptions/metadata.ini b/tests/cpychecker/refcounts/xml/exceptions/metadata.ini
new file mode 100644
index 00000000..2fe60000
--- /dev/null
+++ b/tests/cpychecker/refcounts/xml/exceptions/metadata.ini
@@ -0,0 +1,4 @@
+[ExpectedBehavior]
+# This test case should succeed, whilst emitting a note on stderr;
+# don't treat the stderr output as leading to an expected failure:
+exitcode = 0
diff --git a/tests/cpychecker/refcounts/xml/exceptions/script.py b/tests/cpychecker/refcounts/xml/exceptions/script.py
new file mode 100644
index 00000000..db2cd09e
--- /dev/null
+++ b/tests/cpychecker/refcounts/xml/exceptions/script.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+# Copyright 2011, 2013 David Malcolm
+# Copyright 2011, 2013 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
+# .
+
+import atexit
+import os
+import sys
+
+from firehose.model import Analysis, Failure
+
+from gccutils.selftests import assertEqual
+from libcpychecker import main, Options
+from libcpychecker.refcounts import CPython
+
+XML_OUTPUT_PATH = 'tests/cpychecker/refcounts/xml/exceptions/output.xml'
+
+def assertEqual(lhs, rhs):
+ if lhs != rhs:
+ raise ValueError('non-equal values: %r != %r' % (lhs, rhs))
+
+def verify_analysis(analysis):
+ # Verify that a Failure instance made it into the firehose output:
+ assertEqual(len(analysis.results), 1)
+ f = analysis.results[0]
+
+ assert isinstance(f, Failure)
+
+ assertEqual(f.failureid, 'python-exception')
+ assertEqual(f.message, None)
+ assertEqual(f.location.file.givenpath,
+ 'tests/cpychecker/refcounts/xml/exceptions/input.c')
+ assertEqual(f.location.function.name, 'test')
+ assertEqual(f.location.line, 31)
+ assertEqual(f.location.column, 5)
+ tb = f.customfields['traceback']
+ assert tb.startswith('Traceback (most recent call last):\n')
+ assert tb.endswith('ValueError: this exception exists to test XML output\n')
+
+def verify_firehose():
+ global ctxt
+ sys.stderr.write('verify_firehose()\n')
+ assert ctxt.was_flushed
+
+ verify_analysis(ctxt.analysis)
+
+ # Verify that the XML was actually written:
+ with open(XML_OUTPUT_PATH) as f:
+ analysis_from_disk = Analysis.from_xml(f)
+ # Verify that everything made it to disk:
+ verify_analysis(analysis_from_disk)
+
+ os.unlink(XML_OUTPUT_PATH)
+
+ # Ensure that this testing code actually got run (stderr is
+ # checked):
+ sys.stderr.write('GOT HERE\n')
+
+atexit.register(verify_firehose)
+ctxt = main(Options(verify_refcounting=True,
+ dump_traces=False,
+ show_traces=False,
+ outputxmlpath=XML_OUTPUT_PATH))
+
+# Monkeypatch refcounts.py to inject an exception by supplying
+# a hook for analyzing function in the C code named "generate_exception"
+def impl_generate_exception(self, stmt, v_obj):
+ raise ValueError('this exception exists to test XML output')
+CPython.impl_generate_exception = impl_generate_exception
+
+if 0:
+ sys.stderr.write('%s\n' % atexit._exithandlers)
diff --git a/tests/cpychecker/refcounts/xml/exceptions/stderr.txt b/tests/cpychecker/refcounts/xml/exceptions/stderr.txt
new file mode 100644
index 00000000..588ef36a
--- /dev/null
+++ b/tests/cpychecker/refcounts/xml/exceptions/stderr.txt
@@ -0,0 +1,2 @@
+verify_firehose()
+GOT HERE
diff --git a/tests/cpychecker/refcounts/xml/exceptions/stdout.txt b/tests/cpychecker/refcounts/xml/exceptions/stdout.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/cpychecker/refcounts/xml/flushing/getopts.py b/tests/cpychecker/refcounts/xml/flushing/getopts.py
new file mode 100644
index 00000000..1b9f027b
--- /dev/null
+++ b/tests/cpychecker/refcounts/xml/flushing/getopts.py
@@ -0,0 +1,3 @@
+# d365d9bb68a40953532ad12ec77935d3ad75f0c0 was failing to emit xml
+# with this:
+print('-pipe')
diff --git a/tests/cpychecker/refcounts/xml/flushing/input.c b/tests/cpychecker/refcounts/xml/flushing/input.c
new file mode 100644
index 00000000..e5333802
--- /dev/null
+++ b/tests/cpychecker/refcounts/xml/flushing/input.c
@@ -0,0 +1,27 @@
+/*
+ Copyright 2012 David Malcolm
+ Copyright 2012 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
+ .
+*/
+
+#include
+
+PyObject *
+losing_refcnt_of_none(PyObject *self, PyObject *args)
+{
+ /* Bug: this code is missing a Py_INCREF on Py_None */
+ return Py_None;
+}
diff --git a/tests/cpychecker/refcounts/xml/flushing/metadata.ini b/tests/cpychecker/refcounts/xml/flushing/metadata.ini
new file mode 100644
index 00000000..0d5711cb
--- /dev/null
+++ b/tests/cpychecker/refcounts/xml/flushing/metadata.ini
@@ -0,0 +1,3 @@
+[ExpectedBehavior]
+# We expect only compilation *warnings*, so we expect a 0 exit code
+exitcode = 0
diff --git a/tests/cpychecker/refcounts/xml/flushing/script.py b/tests/cpychecker/refcounts/xml/flushing/script.py
new file mode 100644
index 00000000..7c50b08f
--- /dev/null
+++ b/tests/cpychecker/refcounts/xml/flushing/script.py
@@ -0,0 +1,73 @@
+# Copyright 2012, 2013 David Malcolm
+# Copyright 2012, 2013 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
+# .
+
+# Verify that the final flushing of XML error reports is working
+
+import atexit
+import os
+import sys
+
+from firehose.model import Analysis
+
+from libcpychecker import main, Options
+
+XML_OUTPUT_PATH = 'tests/cpychecker/refcounts/xml/flushing/output.xml'
+
+def assertEqual(lhs, rhs):
+ if lhs != rhs:
+ raise ValueError('non-equal values: %r != %r' % (lhs, rhs))
+
+def verify_analysis(analysis):
+ assertEqual(len(analysis.results), 1)
+ w = analysis.results[0]
+
+ assertEqual(w.cwe, None)
+ assertEqual(w.testid, 'refcount-too-low')
+ assertEqual(w.location.file.givenpath,
+ 'tests/cpychecker/refcounts/xml/flushing/input.c')
+ assertEqual(w.location.function.name, 'losing_refcnt_of_none')
+ assertEqual(w.location.line, 26)
+ assertEqual(w.location.column, 5)
+ assertEqual(w.message.text, 'ob_refcnt of return value is 1 too low')
+
+def verify_firehose():
+ global ctxt
+ sys.stderr.write('verify_firehose()\n')
+ assert ctxt.was_flushed
+
+ assertEqual(len(ctxt.analysis.results), 1)
+ w = ctxt.analysis.results[0]
+
+ verify_analysis(ctxt.analysis)
+
+ # Verify that the XML was actually written:
+ with open(XML_OUTPUT_PATH) as f:
+ analysis_from_disk = Analysis.from_xml(f)
+ # Verify that everything made it to disk:
+ verify_analysis(analysis_from_disk)
+
+ os.unlink(XML_OUTPUT_PATH)
+
+ # Ensure that this testing code actually got run (stdout is
+ # checked):
+ sys.stderr.write('GOT HERE\n')
+
+atexit.register(verify_firehose)
+ctxt = main(Options(verify_refcounting=True,
+ outputxmlpath=XML_OUTPUT_PATH))
+if 0:
+ sys.stderr.write('%s\n' % atexit._exithandlers)
diff --git a/tests/cpychecker/refcounts/xml/flushing/stderr.txt b/tests/cpychecker/refcounts/xml/flushing/stderr.txt
new file mode 100644
index 00000000..f3d29e20
--- /dev/null
+++ b/tests/cpychecker/refcounts/xml/flushing/stderr.txt
@@ -0,0 +1,10 @@
+tests/cpychecker/refcounts/xml/flushing/input.c: In function 'losing_refcnt_of_none':
+tests/cpychecker/refcounts/xml/flushing/input.c:26:nn: warning: ob_refcnt of return value is 1 too low [enabled by default]
+tests/cpychecker/refcounts/xml/flushing/input.c:26:nn: note: was expecting final ob_refcnt to be N + 1 (for some unknown N)
+tests/cpychecker/refcounts/xml/flushing/input.c:26:nn: note: due to object being referenced by: return value
+tests/cpychecker/refcounts/xml/flushing/input.c:26:nn: note: but final ob_refcnt is N + 0
+tests/cpychecker/refcounts/xml/flushing/input.c:26:nn: note: returning at: return Py_None;
+tests/cpychecker/refcounts/xml/flushing/input.c:26:nn: note: consider using "Py_RETURN_NONE;"
+tests/cpychecker/refcounts/xml/flushing/input.c:24:nn: note: graphical error report for function 'losing_refcnt_of_none' written out to 'tests/cpychecker/refcounts/xml/flushing/input.c.losing_refcnt_of_none-refcount-errors.html'
+verify_firehose()
+GOT HERE
diff --git a/tests/cpychecker/refcounts/xml/flushing/stdout.txt b/tests/cpychecker/refcounts/xml/flushing/stdout.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/cpychecker/refcounts/xml/stats/input.c b/tests/cpychecker/refcounts/xml/stats/input.c
new file mode 100644
index 00000000..d158a6cc
--- /dev/null
+++ b/tests/cpychecker/refcounts/xml/stats/input.c
@@ -0,0 +1,61 @@
+/*
+ Copyright 2013 David Malcolm
+ Copyright 2013 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
+ .
+*/
+
+#include
+
+PyObject *
+test(PyObject *obj)
+{
+ PyObject *list = NULL;
+ PyObject *item = NULL;
+
+ list = PyList_New(3);
+ if (!list) {
+ goto error;
+ }
+
+ item = PyInt_FromLong(42);
+ if (!item) {
+ goto error;
+ }
+
+ Py_INCREF(item);
+ PyList_SetItem(list, 0, item);
+ Py_INCREF(item);
+ PyList_SetItem(list, 1, item);
+ Py_INCREF(item);
+ PyList_SetItem(list, 2, item);
+
+ Py_DECREF(item);
+
+ return list;
+
+ error:
+ Py_XDECREF(item);
+ Py_XDECREF(list);
+ return NULL;
+}
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/cpychecker/refcounts/xml/stats/metadata.ini b/tests/cpychecker/refcounts/xml/stats/metadata.ini
new file mode 100644
index 00000000..2fe60000
--- /dev/null
+++ b/tests/cpychecker/refcounts/xml/stats/metadata.ini
@@ -0,0 +1,4 @@
+[ExpectedBehavior]
+# This test case should succeed, whilst emitting a note on stderr;
+# don't treat the stderr output as leading to an expected failure:
+exitcode = 0
diff --git a/tests/cpychecker/refcounts/xml/stats/script.py b/tests/cpychecker/refcounts/xml/stats/script.py
new file mode 100644
index 00000000..903d5589
--- /dev/null
+++ b/tests/cpychecker/refcounts/xml/stats/script.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+# Copyright 2013 David Malcolm
+# Copyright 2013 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
+# .
+
+# Selftest of Options(reportstats=True)
+
+import sys
+
+from firehose.model import Analysis, Failure
+
+from libcpychecker import main, Options
+from libcpychecker.refcounts import CPython
+
+def assertEqual(lhs, rhs):
+ if lhs != rhs:
+ raise ValueError('non-equal values: %r != %r' % (lhs, rhs))
+
+def selftest(ctxt, fun):
+ # Verify that info about the function made it into the report
+ assertEqual(len(ctxt.analysis.results), 1)
+ info0 = ctxt.analysis.results[0]
+ assertEqual(info0.infoid, 'stats')
+ assertEqual(info0.location.function.name, 'test')
+ assertEqual(info0.customfields['num_Py_api_calls'], 5)
+ assertEqual(info0.customfields['calls_to_PyList_New'], 1)
+ assertEqual(info0.customfields['calls_to_PyInt_FromLong'], 1)
+ assertEqual(info0.customfields['calls_to_PyList_SetItem'], 3)
+
+ assert info0.customfields['num_basic_blocks'] > 10
+ assert info0.customfields['num_edges'] > 10
+ assert info0.customfields['num_gimple_statements'] > 10
+
+main(Options(verify_refcounting=True,
+ reportstats=True,
+ selftest=selftest))
diff --git a/tests/cpychecker/refcounts/xml/stats/stderr.txt b/tests/cpychecker/refcounts/xml/stats/stderr.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/cpychecker/refcounts/xml/stats/stdout.txt b/tests/cpychecker/refcounts/xml/stats/stdout.txt
new file mode 100644
index 00000000..e69de29b