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 += ' \n' % self.fun.start.file result += ' \n' % self.fun.decl.name - result += ' \n' % report.msg + result += ' \n' % report.message.text result += '
File: %s
Function: %s
Error: %s
Error: %s
\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