"Firehose" is a serialization format for results from code analysis tools:
http://firehose.readthedocs.io/en/latest/index.html (along with a Python module for working with the format). This patch implements a set of C++ classes modeling the format, with support for populating them from a JSON dump, so that we can lossly serialize diagnostics and other static analysis results. gcc/ChangeLog: * Makefile.in (OBJS): Add firehose.o. * firehose.cc: New file. * firehose.h: New file. * selftest-run-tests.c (selftest::run_tests): Call selftest::firehose_cc_tests. * selftest.h (selftest::firehose_cc_tests): New decl. gcc/testsuite/ChangeLog: * selftests/checker-output/test-clang-analyzer.json: New file. * selftests/checker-output/test-cppcheck.json: New file. * selftests/checker-output/test-failure.json: New file. --- gcc/Makefile.in | 1 + gcc/firehose.cc | 709 +++++++++++++++++++++ gcc/firehose.h | 199 ++++++ gcc/selftest-run-tests.c | 1 + gcc/selftest.h | 1 + .../checker-output/test-clang-analyzer.json | 122 ++++ .../selftests/checker-output/test-cppcheck.json | 50 ++ .../selftests/checker-output/test-failure.json | 38 ++ 8 files changed, 1121 insertions(+) create mode 100644 gcc/firehose.cc create mode 100644 gcc/firehose.h create mode 100644 gcc/testsuite/selftests/checker-output/test-clang-analyzer.json create mode 100644 gcc/testsuite/selftests/checker-output/test-cppcheck.json create mode 100644 gcc/testsuite/selftests/checker-output/test-failure.json diff --git a/gcc/Makefile.in b/gcc/Makefile.in index 4f7fd0c..488f699 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1280,6 +1280,7 @@ OBJS = \ expr.o \ fibonacci_heap.o \ final.o \ + firehose.o \ fixed-value.o \ fold-const.o \ fold-const-call.o \ diff --git a/gcc/firehose.cc b/gcc/firehose.cc new file mode 100644 index 0000000..b2aa167 --- /dev/null +++ b/gcc/firehose.cc @@ -0,0 +1,709 @@ +/* Serialization format for checker results. + Copyright (C) 2017 Free Software Foundation, Inc. + +This file is part of GCC. + +GCC 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, or (at your option) any later +version. + +GCC 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 GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "firehose.h" +#include "selftest.h" +#include "selftest-input.h" + +namespace firehose { + +/* Attempt to parse JV as a json object containing "line" and "column" + attributes (a serialization of a firehose.model.Point python object). + + If successful, write a location_t to OUT_VALUE, using GIVENPATH as the + filename, and return true. + Otherwise, write an error message to OUT_ERR (which must be freed by + the caller) and return false. */ + +static bool +get_location_from_point (const char *givenpath, const json::value *jv, + location_t &out_value, char *&out_err) +{ + int line; + if (!jv->get_int_by_key ("line", line, out_err)) + return false; + + int column; + if (!jv->get_int_by_key ("column", column, out_err)) + return false; + + out_value + = linemap_position_for_file_line_and_column (line_table, + givenpath, line, column); + return true; +} + +/* As above, but expect JV to be a json object containing a "start" + and "end" (a serialization of a firehose.model.Range python object). */ + +static bool +get_location_from_range (const char *givenpath, const json::value *jv, + location_t &out_value, char *&out_err) +{ + const json::value *jv_start; + if (!jv->get_value_by_key ("start", jv_start, out_err)) + return false; + + location_t start; + if (!get_location_from_point (givenpath, jv_start, + start, out_err)) + return false; + + const json::value *jv_end; + if (!jv->get_value_by_key ("end", jv_end, out_err)) + return false; + location_t end; + if (!get_location_from_point (givenpath, jv_end, + end, out_err)) + return false; + + out_value = make_location (start, start, end); + return true; +} + +/* Attempt to extract an attribute "location" from JV, where the value + ought to be a serialization of a firehose.model.Location python object. + + If successful, write a location_t to OUT_VALUE and return true. + Otherwise, write an error message to OUT_ERR (which must be freed by + the caller) and return false. */ + +static bool +get_location (const json::value *jv, location_t &out_value, char *&out_err) +{ + const json::value *location; + if (!jv->get_value_by_key ("location", location, out_err)) + return false; + + const json::value *file; + if (!location->get_value_by_key ("file", file, out_err)) + return false; + const char *givenpath; + if (!file->get_string_by_key ("givenpath", givenpath, out_err)) + return false; + + const json::value *point = location->as_object ()->get_if_nonnull ("point"); + if (point) + { + if (!get_location_from_point (givenpath, point, out_value, out_err)) + return false; + } + else + { + const json::value *range + = location->as_object ()->get_if_nonnull ("range_"); + + if (range) + { + if (!get_location_from_range (givenpath, range, out_value, + out_err)) + return false; + } + } + + // ignore "function" for now + return true; +} + +/* firehose::state's ctor. */ + +state::state () : m_location (UNKNOWN_LOCATION), m_notes (NULL) +{ +} + +/* firehose::state's dtor. */ + +state::~state () +{ + free (m_notes); +} + +/* Attempt to allocate a new firehose::state based on JV, which ought to be a + serialization of a firehose.model.State python object. + + Return the new state if successful. + Otherwise return NULL and write an error message to OUT_ERR + (which must be freed by the caller). */ + +state * +state::from_json (const json::value *jv, char *&out_err) +{ + state *s = new state (); + + /* Extract the state's location to m_location. */ + if (!get_location (jv, s->m_location, out_err)) + { + delete s; + return NULL; + } + + /* Get any notes. */ + json::value *notes = jv->as_object ()->get_if_nonnull ("notes"); + if (notes) + { + const char *text; + if (!notes->get_string_by_key ("text", text, out_err)) + { + delete s; + return NULL; + } + s->m_notes = xstrdup (text); + } + + return s; +} + +/* firehose::trace's dtor. */ + +trace::~trace () +{ + int i; + state *state; + FOR_EACH_VEC_ELT (m_states, i, state) + delete state; +} + +/* Attempt to allocate a new firehose::trace based on JV, which ought to be a + serialization of a firehose.model.State python object. + + Return the new state if successful. + Otherwise return NULL and write an error message to OUT_ERR + (which must be freed by the caller). */ + +trace * +trace::from_json (const json::value *jv, char *&out_err) +{ + const json::array *states; + if (!jv->get_array_by_key ("states", states, out_err)) + return NULL; + + trace *t = new trace (); + for (unsigned idx = 0; idx < states->get_length (); idx++) + { + const json::value *item = states->get (idx); + if (0) + { + fprintf (stderr, "got state %i: ", idx); + item->dump (stderr); + fprintf (stderr, "\n"); + } + firehose::state *state = state::from_json (item, out_err); + if (!state) + { + delete t; + return NULL; + } + t->m_states.safe_push (state); + } + + return t; +} + +/* Filter out the states to just those with notes. */ + +void +trace::filter () +{ + unsigned idx = 0; + while (idx < m_states.length ()) + { + if (m_states[idx]->m_notes == NULL) + { + delete m_states[idx]; + m_states.ordered_remove (idx); + } + else + idx++; + } +} + +/* Determine if THIS trace is merely a single state that duplicates + the information within ISSUE. */ + +bool +trace::is_redundant_p (const issue& issue) const +{ + if (m_states.length () > 1) + return false; + if (m_states.length () < 1) + return true; + + state *s0 = m_states[0]; + + if (s0->m_location != issue.m_location) + return false; + if (s0->m_notes) + if (0 != strcmp (s0->m_notes, issue.m_message)) + return false; + + /* Single state, with same location, and same message as ISSUE. */ + return true; +} + +/* firehose::result's ctor. */ + +result::result () +: m_message (NULL), m_location (UNKNOWN_LOCATION) +{ +} + +/* firehose::result's dtor. */ + +result::~result () +{ + free (m_message); +} + +/* Attempt to allocate a new firehose::result based on JV, which ought to be a + serialization of a firehose.model.Result python object. + + Return the new state if successful. + Otherwise return NULL and write an error message to OUT_ERR + (which must be freed by the caller). */ + +result * +result::from_json (const json::value *jv, char *&out_err) +{ + const char *type; + if (!jv->get_string_by_key ("type", type, out_err)) + return NULL; + result *result = NULL; + if (0 == strcmp (type, "Issue")) + { + result = issue::from_json (jv, out_err); + } + if (0 == strcmp (type, "Info")) + { + result = info::from_json (jv, out_err); + } + if (0 == strcmp (type, "Failure")) + { + result = failure::from_json (jv, out_err); + } + if (!result) + { + out_err = xstrdup ("unrecognized type of result"); + delete result; + return NULL; + } + + /* Extract the results's message's text to m_message. */ + const json::value *message; + if (!jv->get_value_by_key ("message", message, out_err)) + { + delete result; + return NULL; + } + const char *message_text; + if (!message->get_string_by_key ("text", message_text, out_err)) + { + delete result; + return NULL; + } + result->m_message = xstrdup (message_text); + + /* Extract the result's location to m_location. */ + if (!get_location (jv, result->m_location, out_err)) + { + delete result; + return NULL; + } + + return result; +} + +/* firehose::issue's ctor. */ + +issue::issue () : result (), m_testid (NULL), m_trace (NULL) +{ +} + +/* firehose::issue's dtor. */ + +issue::~issue () +{ + free (m_testid); + delete m_trace; +} + +/* Attempt to allocate a new firehose::issue based on JV, which ought to be a + serialization of a firehose.model.Issue python object. + + Return the new state if successful. + Otherwise return NULL and write an error message to OUT_ERR + (which must be freed by the caller). */ + +issue * +issue::from_json (const json::value *jv, char *&out_err) +{ + issue *r = new issue (); + + /* FIXME: get any testid. */ + const char *testid_text = NULL; + if (!jv->get_optional_string_by_key ("testid", testid_text, out_err)) + { + delete r; + return NULL; + } + if (testid_text) + r->m_testid = xstrdup (testid_text); + + /* Get any trace as m_trace. */ + const json::value *trace = jv->as_object ()->get_if_nonnull ("trace"); + if (trace) + { + r->m_trace = trace::from_json (trace, out_err); + if (!r->m_trace) + { + delete r; + return NULL; + } + } + + return r; +} + +/* firehose::info's ctor. */ + +info::info () : result (), m_infoid (NULL) +{ +} + +/* firehose::info's dtor. */ + +info::~info () +{ + free (m_infoid); +} + +/* Attempt to allocate a new firehose::info based on JV, which ought to be a + serialization of a firehose.model.Info python object. + + Return the new state if successful. + Otherwise return NULL and write an error message to OUT_ERR + (which must be freed by the caller). */ + +info * +info::from_json (const json::value *jv, char *&out_err) +{ + info *r = new info (); + + /* FIXME: get any infoid. */ + const char *infoid_text = NULL; + if (!jv->get_optional_string_by_key ("infoid", infoid_text, out_err)) + { + delete r; + return NULL; + } + if (infoid_text) + r->m_infoid = xstrdup (infoid_text); + + return r; +} + +/* firehose::failure's ctor. */ + +failure::failure () : result (), m_failureid (NULL) +{ +} + +/* firehose::failure's dtor. */ + +failure::~failure () +{ + free (m_failureid); +} + +/* Attempt to allocate a new firehose::failure based on JV, which ought to be a + serialization of a firehose.model.Failure python object. + + Return the new state if successful. + Otherwise return NULL and write an error message to OUT_ERR + (which must be freed by the caller). */ + +failure * +failure::from_json (const json::value *jv, char *&out_err) +{ + failure *r = new failure (); + + /* FIXME: get any failureid. */ + const char *failureid_text = NULL; + if (!jv->get_optional_string_by_key ("failureid", failureid_text, out_err)) + { + delete r; + return NULL; + } + if (failureid_text) + r->m_failureid = xstrdup (failureid_text); + + return r; +} + +/* firehose::generator's ctor. */ + +generator::generator () +: m_name (NULL), m_version (NULL) +{ +} + +/* firehose::generator's dtor. */ + +generator::~generator () +{ + free (m_name); + free (m_version); +} + +/* Attempt to populate this firehose::generator based on JV, which ought to be a + serialization of a firehose.model.Generator python object. + + Return true if successful. + Otherwise return false and write an error message to OUT_ERR + (which must be freed by the caller). */ + +bool +generator::from_json (const json::value *jv, char *&out_err) +{ + const char *name; + if (!jv->get_string_by_key ("name", name, out_err)) + return false; + m_name = xstrdup (name); + + const char *version = NULL; + if (!jv->get_optional_string_by_key ("version", version, out_err)) + return false; + if (version) + m_version = xstrdup (version); + + return true; +} + +/* Attempt to populate this firehose::metadata based on JV, which ought to be a + serialization of a firehose.model.Metadata python object. + + Return true if successful. + Otherwise return false and write an error message to OUT_ERR + (which must be freed by the caller). */ + +bool +metadata::from_json (const json::value *jv, char *&out_err) +{ + const json::value *jv_generator = NULL; + if (!jv->get_value_by_key ("generator", jv_generator, out_err)) + return false; + if (!m_generator.from_json (jv_generator, out_err)) + return false; + + return true; +} + +/* firehose::analysis's dtor. */ + +analysis::~analysis () +{ + int i; + result *result; + FOR_EACH_VEC_ELT (m_results, i, result) + delete result; +} + +/* Attempt to populate this firehose::analysis based on JV, which ought to be a + serialization of a firehose.model.Analysis python object. + + Return true if successful. + Otherwise return false and write an error message to OUT_ERR + (which must be freed by the caller). */ + +bool +analysis::from_json (const json::value *jv, char *&out_err) +{ + const json::value *jv_metadata = NULL; + if (!jv->get_value_by_key ("metadata", jv_metadata, out_err)) + return false; + if (!m_metadata.from_json (jv_metadata, out_err)) + return false; + + const json::array *results; + if (!jv->get_array_by_key ("results", results, out_err)) + return false; + + for (unsigned i = 0; i < results->get_length (); i++) + { + json::value *item = results->get (i); + //error ("%s", item->to_str ()); + result *r = result::from_json (item, out_err); + if (!r) + return false; + m_results.safe_push (r); + } + + // FIXME: custom fields + // FIXME: selftests for all of this + + return true; +} + +} // namespace firehose + + +#if CHECKING_P + +namespace selftest { + +/* Selftests. */ + +/* Given JSONFILE, a path relative to SRCDIR/gcc/testsuite/selftests, + load the json Firehose file there, populating OUT. + Fail if any errors occur. */ + +static void +get_analysis (firehose::analysis &out, const char *jsonfile) +{ + char *filename = locate_file (jsonfile); + char *buffer = selftest::read_file (SELFTEST_LOCATION, filename); + ASSERT_TRUE (buffer != NULL); + free (filename); + + char *err = NULL; + json::value *jv = json::parse_utf8_string (buffer, &err); + free (buffer); + ASSERT_TRUE (err == NULL); + ASSERT_TRUE (jv != NULL); + + //jv->dump(stderr); + out.from_json (jv, err); + ASSERT_TRUE (err == NULL); + delete jv; +} + +/* Parse a sample JSON output generated via the firehose parser for the + clang analyzer's plist output, and verify various properties + about it. */ + +static void +test_parsing_clang_analyzer () +{ + firehose::analysis analysis; + get_analysis (analysis, "checker-output/test-clang-analyzer.json"); + + ASSERT_STREQ ("clang-analyzer", analysis.m_metadata.m_generator.m_name); + ASSERT_EQ (NULL, analysis.m_metadata.m_generator.m_version); + + ASSERT_EQ (1, analysis.m_results.length ()); + firehose::result *r = analysis.m_results[0]; + ASSERT_EQ (r->get_kind (), firehose::result::FIREHOSE_ISSUE); + + firehose::issue *issue = (firehose::issue *)r; + ASSERT_STREQ ("Address of stack memory associated with" + " local variable 'tmp' returned to caller", + issue->m_message); + ASSERT_EQ (NULL, issue->m_testid); + + ASSERT_LOCEQ ("../../src/bogus.c", 5, 3, issue->m_location); + + ASSERT_TRUE (issue->m_trace != NULL); + ASSERT_EQ (3, issue->m_trace->m_states.length ()); + firehose::state *state0 = issue->m_trace->m_states[0]; + ASSERT_LOCEQ ("../../src/bogus.c", 3, 3, state0->m_location); + ASSERT_EQ (NULL, state0->m_notes); + + firehose::state *state1 = issue->m_trace->m_states[1]; + ASSERT_LOCEQ ("../../src/bogus.c", 5, 3, state1->m_location); + ASSERT_EQ (NULL, state1->m_notes); + + firehose::state *state2 = issue->m_trace->m_states[2]; + ASSERT_LOCEQ ("../../src/bogus.c", 5, 3, state2->m_location); + ASSERT_STREQ ("Address of stack memory associated with" + " local variable 'tmp' returned to caller", + state2->m_notes); + ASSERT_FALSE (issue->m_trace->is_redundant_p (*issue)); + + /* Verify filtering out non-textual states from the trace. */ + issue->m_trace->filter (); + ASSERT_EQ (1, issue->m_trace->m_states.length ()); + + /* Verify that the filtered trace is redundant. */ + ASSERT_TRUE (issue->m_trace->is_redundant_p (*issue)); +} + +/* Parse a sample JSON output generated via the firehose parser for + cppchecks's output, and verify various properties about it. */ + +static void +test_parsing_cppcheck () +{ + firehose::analysis analysis; + get_analysis (analysis, "checker-output/test-cppcheck.json"); + + ASSERT_STREQ ("cppcheck", analysis.m_metadata.m_generator.m_name); + ASSERT_STREQ ("1.63", analysis.m_metadata.m_generator.m_version); + + ASSERT_EQ (1, analysis.m_results.length ()); + firehose::result *r = analysis.m_results[0]; + ASSERT_EQ (r->get_kind (), firehose::result::FIREHOSE_ISSUE); + + firehose::issue *issue = (firehose::issue *)r; + ASSERT_STREQ ("Memory leak: ptr_1", issue->m_message); + ASSERT_STREQ ("memleak", issue->m_testid); + + ASSERT_LOCEQ ("../../src/test-sources/conditional-leak.c", 11, 0, + issue->m_location); + + ASSERT_TRUE (issue->m_trace == NULL); +} + +/* Parse a JSON file describing a failure to run a checker, and verify + various properties about it. */ + +static void +test_parsing_failure () +{ + firehose::analysis analysis; + get_analysis (analysis, "checker-output/test-failure.json"); + + ASSERT_STREQ ("always-fails", analysis.m_metadata.m_generator.m_name); + + ASSERT_EQ (1, analysis.m_results.length ()); + firehose::result *r = analysis.m_results[0]; + ASSERT_EQ (r->get_kind (), firehose::result::FIREHOSE_FAILURE); + + firehose::failure *failure = (firehose::failure *)r; + ASSERT_STREQ ("Exception running always-fails: [Errno 2]" + " No such file or directory:" + " '/this/executable/does/not/exist'", failure->m_message); + ASSERT_STREQ ("exception", failure->m_failureid); +} + +/* Run all of the selftests within this file. */ + +void +firehose_cc_tests () +{ + test_parsing_clang_analyzer (); + test_parsing_cppcheck (); + //test_parsing_info (); + test_parsing_failure (); +} + +} // namespace selftest + +#endif /* #if CHECKING_P */ diff --git a/gcc/firehose.h b/gcc/firehose.h new file mode 100644 index 0000000..6c67b45 --- /dev/null +++ b/gcc/firehose.h @@ -0,0 +1,199 @@ +/* Serialization format for checker results. + Copyright (C) 2017 Free Software Foundation, Inc. + +This file is part of GCC. + +GCC 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, or (at your option) any later +version. + +GCC 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 GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#ifndef GCC_FIREHOSE_H +#define GCC_FIREHOSE_H + +/* "Firehose" is a serialization format for results from code + analysis tools: + + http://firehose.readthedocs.io/en/latest/index.html + + (along with a Python module for working with the format). + + This file implements a set of C++ classes modeling the format, + with support for populating them from a JSON dump, so that we + can lossly serialize diagnostics and other static analysis results. */ + +#include "json.h" + +namespace firehose { + +/* Forward decls. */ + +struct issue; + +/* A state within a firehose::trace. */ + +struct state +{ + state (); + ~state (); + + static state *from_json (const json::value *jv, char *&out_err); + + location_t m_location; + char *m_notes; +}; + +/* An optional list of events within an issue that describe the circumstances + leading up to a problem. */ + +struct trace +{ + ~trace (); + + static trace *from_json (const json::value *jv, char *&out_err); + + void filter (); + + /* If we're just left with a single state that duplicates what we + already printed for the issue, don't bother printing it. */ + bool is_redundant_p (const issue& issue) const; + + auto_vec <state *> m_states; +}; + +/* firehose::result is a base class. + + There are three subclasses: + + - a firehose::issue represents a report from an analyzer about a possible + problem with the software under test. + - a firehose::info represents additional kinds of information generated by + an analyzer that isn't a problem per-se e.g. code metrics, licensing info, + etc. + - a firehose::failure represents a report about a failure of the analyzer + itself (e.g. if the analyzer crashed). */ + +struct result +{ + enum kind + { + FIREHOSE_ISSUE, + FIREHOSE_INFO, + FIREHOSE_FAILURE + }; + + result (); + virtual ~result (); + + static result *from_json (const json::value *jv, char *&out_err); + + virtual enum kind get_kind () const = 0; + + char *m_message; + location_t m_location; +}; + +/* An issue represents a report from an analyzer about a possible problem + with the software under test. */ + +struct issue : public result +{ + issue (); + ~issue (); + + static issue *from_json (const json::value *jv, char *&out_err); + enum kind get_kind () const FINAL OVERRIDE { return FIREHOSE_ISSUE; } + + char *m_testid; + trace *m_trace; +}; + +/* An info represents additional kinds of information generated by an analyzer + that isn't a problem per-se e.g. code metrics, licensing info, + cross-referencing information, etc. */ + +struct info : public result +{ + info (); + ~info (); + + static info *from_json (const json::value *jv, char *&out_err); + enum kind get_kind () const FINAL OVERRIDE { return FIREHOSE_INFO; } + + char *m_infoid; +}; + +/* A failure represents a report about a failure of the analyzer itself + (e.g. if the analyzer crashed). + + If any of these are present then we don't have full coverage. + + For some analyzers this is an all-or-nothing affair: we either get + issues reported, or a failure happens (e.g. a segfault of the + analysis tool). + + Other analyzers may be more fine-grained: able to report some + issues, but choke on some subset of the code under analysis. + For example cpychecker runs once per function, and any unhandled + Python exceptions only affect one function. */ + +struct failure : public result +{ + failure (); + ~failure (); + + static failure *from_json (const json::value *jv, char *&out_err); + enum kind get_kind () const FINAL OVERRIDE { return FIREHOSE_FAILURE; } + + char *m_failureid; +}; + +/* A class describing a static analyzer, for use within firehose::metadata. */ + +struct generator +{ + generator (); + ~generator (); + + bool from_json (const json::value *jv, char *&out_err); + + char *m_name; + char *m_version; +}; + +/* The firehose::metadata class contains metadata about a static analyzer + invocation. */ + +struct metadata +{ + bool from_json (const json::value *jv, char *&out_err); + + generator m_generator; +}; + +/* The firehose::analysis class represents one invocation of a code analysis + tool. */ + +struct analysis +{ + ~analysis (); + + bool from_json (const json::value *jv, char *&out_err); + + metadata m_metadata; + auto_vec<result *> m_results; + //custom_fields *m_custom_fields; +}; + +} // namespace firehose + +#endif /* GCC_FIREHOSE_H */ diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c index 025e574..8afcd43 100644 --- a/gcc/selftest-run-tests.c +++ b/gcc/selftest-run-tests.c @@ -74,6 +74,7 @@ selftest::run_tests () gimple_c_tests (); rtl_tests_c_tests (); read_rtl_function_c_tests (); + firehose_cc_tests (); /* Higher-level tests, or for components that other selftests don't rely on. */ diff --git a/gcc/selftest.h b/gcc/selftest.h index 4e8891c..e86ce38 100644 --- a/gcc/selftest.h +++ b/gcc/selftest.h @@ -177,6 +177,7 @@ extern void edit_context_c_tests (); extern void et_forest_c_tests (); extern void fold_const_c_tests (); extern void fibonacci_heap_c_tests (); +extern void firehose_cc_tests (); extern void function_tests_c_tests (); extern void gimple_c_tests (); extern void ggc_tests_c_tests (); diff --git a/gcc/testsuite/selftests/checker-output/test-clang-analyzer.json b/gcc/testsuite/selftests/checker-output/test-clang-analyzer.json new file mode 100644 index 0000000..eda9abc --- /dev/null +++ b/gcc/testsuite/selftests/checker-output/test-clang-analyzer.json @@ -0,0 +1,122 @@ +{ + "customfields": { + "scan-build-invocation": "scan-build -v -plist --use-analyzer /usr/bin/clang -o /tmp/tmp8ytuRj gcc -B. -c ../../src/bogus.c", + "returncode": 0, + "stdout": "scan-build: Using '/usr/bin/clang' for static analysis\nscan-build: Emitting reports for this run to '/tmp/tmp8ytuRj/2017-05-24-001755-39710-1'.\nscan-build: Analysis run complete.\nscan-build: Analysis results (plist files) deposited in '/tmp/tmp8ytuRj/2017-05-24-001755-39710-1'\n", + "stderr": "../../src/bogus.c: In function \u2018test\u2019:\n../../src/bogus.c:5:10: warning: function returns address of local variable [-Wreturn-local-addr]\n return tmp;\n ^~~\n../../src/bogus.c:5:3: warning: Address of stack memory associated with local variable 'tmp' returned to caller\n return tmp;\n ^~~~~~~~~~\n1 warning generated.\n", + "plistpath": "/tmp/tmp8ytuRj/2017-05-24-001755-39710-1/report-DEoPmt.plist" + }, + "results": [ + { + "severity": null, + "trace": { + "states": [ + { + "notes": null, + "location": { + "function": { + "name": "" + }, + "range_": { + "start": { + "column": 3, + "line": 3 + }, + "end": { + "column": 6, + "line": 3 + } + }, + "file": { + "abspath": null, + "givenpath": "../../src/bogus.c", + "hash_": null + }, + "point": null + } + }, + { + "notes": null, + "location": { + "function": { + "name": "" + }, + "range_": { + "start": { + "column": 3, + "line": 5 + }, + "end": { + "column": 8, + "line": 5 + } + }, + "file": { + "abspath": null, + "givenpath": "../../src/bogus.c", + "hash_": null + }, + "point": null + } + }, + { + "notes": { + "text": "Address of stack memory associated with local variable 'tmp' returned to caller" + }, + "location": { + "function": { + "name": "" + }, + "range_": null, + "file": { + "abspath": null, + "givenpath": "../../src/bogus.c", + "hash_": null + }, + "point": { + "column": 3, + "line": 5 + } + } + } + ] + }, + "type": "Issue", + "notes": null, + "testid": null, + "message": { + "text": "Address of stack memory associated with local variable 'tmp' returned to caller" + }, + "cwe": null, + "customfields": null, + "location": { + "function": null, + "range_": null, + "file": { + "abspath": null, + "givenpath": "../../src/bogus.c", + "hash_": null + }, + "point": { + "column": 3, + "line": 5 + } + } + } + ], + "metadata": { + "stats": { + "wallclocktime": 0.22788214683532715 + }, + "sut": null, + "file_": { + "abspath": "/home/david/coding-3/gcc-git-static-analysis/build/gcc/../../src/bogus.c", + "givenpath": "../../src/bogus.c", + "hash_": null + }, + "generator": { + "version": null, + "name": "clang-analyzer" + } + } +} \ No newline at end of file diff --git a/gcc/testsuite/selftests/checker-output/test-cppcheck.json b/gcc/testsuite/selftests/checker-output/test-cppcheck.json new file mode 100644 index 0000000..c9651ee --- /dev/null +++ b/gcc/testsuite/selftests/checker-output/test-cppcheck.json @@ -0,0 +1,50 @@ +{ + "customfields": { + "cppcheck-invocation": "cppcheck --xml --xml-version=2 ../../src/test-sources/conditional-leak.c", + "returncode": 0, + "stdout": "Checking ../../src/test-sources/conditional-leak.c...\n", + "stderr": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<results version=\"2\">\n <cppcheck version=\"1.63\"/>\n <errors>\n <error id=\"memleak\" severity=\"error\" msg=\"Memory leak: ptr_1\" verbose=\"Memory leak: ptr_1\">\n <location file=\"../../src/test-sources/conditional-leak.c\" line=\"11\"/>\n </error>\n </errors>\n</results>\n" + }, + "results": [ + { + "severity": "error", + "trace": null, + "type": "Issue", + "notes": null, + "testid": "memleak", + "message": { + "text": "Memory leak: ptr_1" + }, + "cwe": null, + "customfields": null, + "location": { + "function": null, + "range_": null, + "file": { + "abspath": null, + "givenpath": "../../src/test-sources/conditional-leak.c", + "hash_": null + }, + "point": { + "column": 0, + "line": 11 + } + } + } + ], + "metadata": { + "stats": { + "wallclocktime": 0.006749868392944336 + }, + "sut": null, + "file_": { + "abspath": "/home/david/coding-3/gcc-git-static-analysis/build/gcc/../../src/test-sources/conditional-leak.c", + "givenpath": "../../src/test-sources/conditional-leak.c", + "hash_": null + }, + "generator": { + "version": "1.63", + "name": "cppcheck" + } + } +} \ No newline at end of file diff --git a/gcc/testsuite/selftests/checker-output/test-failure.json b/gcc/testsuite/selftests/checker-output/test-failure.json new file mode 100644 index 0000000..fd07cab --- /dev/null +++ b/gcc/testsuite/selftests/checker-output/test-failure.json @@ -0,0 +1,38 @@ +{ + "customfields": { + "traceback": "Traceback (most recent call last):\n File \"/home/david/coding-3/gcc-git-static-analysis/src/checkers/checker.py\", line 142, in checked_invoke\n analysis = self.raw_invoke(gccinv, sourcefile)\n File \"./checkers/always_fails.py\", line 40, in raw_invoke\n return self._run_subprocess(sourcefile, args)\n File \"/home/david/coding-3/gcc-git-static-analysis/src/checkers/checker.py\", line 213, in _run_subprocess\n stdout=PIPE, stderr=PIPE, env=env)\n File \"/usr/lib64/python2.7/site-packages/subprocess32.py\", line 812, in __init__\n restore_signals, start_new_session)\n File \"/usr/lib64/python2.7/site-packages/subprocess32.py\", line 1557, in _execute_child\n raise child_exception_type(errno_num, err_msg)\nOSError: [Errno 2] No such file or directory: '/this/executable/does/not/exist'\n" + }, + "results": [ + { + "type": "Failure", + "message": { + "text": "Exception running always-fails: [Errno 2] No such file or directory: '/this/executable/does/not/exist'" + }, + "failureid": "exception", + "location": { + "function": null, + "range_": null, + "file": { + "abspath": null, + "givenpath": "checkers/test-sources/harmless.c", + "hash_": null + }, + "point": null + }, + "customfields": null + } + ], + "metadata": { + "stats": null, + "sut": null, + "file_": { + "abspath": "/home/david/coding-3/gcc-git-static-analysis/src/checkers/test-sources/harmless.c", + "givenpath": "checkers/test-sources/harmless.c", + "hash_": null + }, + "generator": { + "version": null, + "name": "always-fails" + } + } +} \ No newline at end of file -- 1.8.5.3