PR c++/90462 reports an ICE with -fdiagnostics-format=json when attempting to serialize a malformed location to JSON.
The compound location_t in question has meaningful "caret" and "start" locations, but has UNKNOWN_LOCATION for its "finish" location, leading to a NULL pointer dereference when attempting to build a JSON string for the filename. This patch bulletproofs the JSON output so that attempts to write a JSON object for a location with a NULL file will lead to an object with no "file" key, and attempts to write a compound location with UNKNOWN_LOCATION for its start or finish will lead to the corresponding JSON child object being omitted. This patch also adds a json::object::get member function, for self-testing the above. Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu. Committed to trunk as r271535. gcc/ChangeLog: PR c++/90462 * diagnostic-format-json.cc: Include "selftest.h". (json_from_expanded_location): Only add "file" key for non-NULL file strings. (json_from_location_range): Don't add "start" and "finish" children if they are UNKNOWN_LOCATION. (selftest::test_unknown_location): New selftest. (selftest::test_bad_endpoints): New selftest. (selftest::diagnostic_format_json_cc_tests): New function. * json.cc (json::object::get): New function. (selftest::test_object_get): New selftest. (selftest::json_cc_tests): Call it. * json.h (json::object::get): New decl. * selftest-run-tests.c (selftest::run_tests): Call selftest::diagnostic_format_json_cc_tests. * selftest.h (selftest::diagnostic_format_json_cc_tests): New decl. gcc/testsuite/ChangeLog: PR c++/90462 * g++.dg/pr90462.C: New test. --- gcc/diagnostic-format-json.cc | 60 +++++++++++++++++++++++++++++++++++++++--- gcc/json.cc | 29 ++++++++++++++++++++ gcc/json.h | 1 + gcc/selftest-run-tests.c | 1 + gcc/selftest.h | 1 + gcc/testsuite/g++.dg/pr90462.C | 49 ++++++++++++++++++++++++++++++++++ 6 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 gcc/testsuite/g++.dg/pr90462.C diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-json.cc index 0355210..53c3b63 100644 --- a/gcc/diagnostic-format-json.cc +++ b/gcc/diagnostic-format-json.cc @@ -24,6 +24,7 @@ along with GCC; see the file COPYING3. If not see #include "coretypes.h" #include "diagnostic.h" #include "json.h" +#include "selftest.h" /* The top-level JSON array of pending diagnostics. */ @@ -45,7 +46,8 @@ json_from_expanded_location (location_t loc) { expanded_location exploc = expand_location (loc); json::object *result = new json::object (); - result->set ("file", new json::string (exploc.file)); + if (exploc.file) + result->set ("file", new json::string (exploc.file)); result->set ("line", new json::number (exploc.line)); result->set ("column", new json::number (exploc.column)); return result; @@ -66,9 +68,11 @@ json_from_location_range (const location_range *loc_range, unsigned range_idx) json::object *result = new json::object (); result->set ("caret", json_from_expanded_location (caret_loc)); - if (start_loc != caret_loc) + if (start_loc != caret_loc + && start_loc != UNKNOWN_LOCATION) result->set ("start", json_from_expanded_location (start_loc)); - if (finish_loc != caret_loc) + if (finish_loc != caret_loc + && finish_loc != UNKNOWN_LOCATION) result->set ("finish", json_from_expanded_location (finish_loc)); if (loc_range->m_label) @@ -262,3 +266,53 @@ diagnostic_output_format_init (diagnostic_context *context, break; } } + +#if CHECKING_P + +namespace selftest { + +/* We shouldn't call json_from_expanded_location on UNKNOWN_LOCATION, + but verify that we handle this gracefully. */ + +static void +test_unknown_location () +{ + delete json_from_expanded_location (UNKNOWN_LOCATION); +} + +/* Verify that we gracefully handle attempts to serialize bad + compound locations. */ + +static void +test_bad_endpoints () +{ + location_t bad_endpoints + = make_location (BUILTINS_LOCATION, + UNKNOWN_LOCATION, UNKNOWN_LOCATION); + + location_range loc_range; + loc_range.m_loc = bad_endpoints; + loc_range.m_range_display_kind = SHOW_RANGE_WITH_CARET; + loc_range.m_label = NULL; + + json::object *obj = json_from_location_range (&loc_range, 0); + /* We should have a "caret" value, but no "start" or "finish" values. */ + ASSERT_TRUE (obj != NULL); + ASSERT_TRUE (obj->get ("caret") != NULL); + ASSERT_TRUE (obj->get ("start") == NULL); + ASSERT_TRUE (obj->get ("finish") == NULL); + delete obj; +} + +/* Run all of the selftests within this file. */ + +void +diagnostic_format_json_cc_tests () +{ + test_unknown_location (); + test_bad_endpoints (); +} + +} // namespace selftest + +#endif /* #if CHECKING_P */ diff --git a/gcc/json.cc b/gcc/json.cc index 2e8e21b..512e2e5 100644 --- a/gcc/json.cc +++ b/gcc/json.cc @@ -99,6 +99,22 @@ object::set (const char *key, value *v) m_map.put (xstrdup (key), v); } +/* Get the json::value * for KEY. + + The object retains ownership of the value. */ + +value * +object::get (const char *key) const +{ + gcc_assert (key); + + value **ptr = const_cast <map_t &> (m_map).get (key); + if (ptr) + return *ptr; + else + return NULL; +} + /* class json::array, a subclass of json::value, representing an ordered collection of values. */ @@ -240,6 +256,18 @@ assert_print_eq (const json::value &jv, const char *expected_json) ASSERT_STREQ (expected_json, pp_formatted_text (&pp)); } +/* Verify that object::get works as expected. */ + +static void +test_object_get () +{ + object obj; + value *val = new json::string ("value"); + obj.set ("foo", val); + ASSERT_EQ (obj.get ("foo"), val); + ASSERT_EQ (obj.get ("not-present"), NULL); +} + /* Verify that JSON objects are written correctly. We can't test more than one key/value pair, as we don't impose a guaranteed ordering. */ @@ -306,6 +334,7 @@ test_writing_literals () void json_cc_tests () { + test_object_get (); test_writing_objects (); test_writing_arrays (); test_writing_numbers (); diff --git a/gcc/json.h b/gcc/json.h index 0527a2fc..d8a690e 100644 --- a/gcc/json.h +++ b/gcc/json.h @@ -90,6 +90,7 @@ class object : public value void print (pretty_printer *pp) const FINAL OVERRIDE; void set (const char *key, value *v); + value *get (const char *key) const; private: typedef hash_map <char *, value *, diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c index c2e0ee2..6ed7d82 100644 --- a/gcc/selftest-run-tests.c +++ b/gcc/selftest-run-tests.c @@ -90,6 +90,7 @@ selftest::run_tests () rely on. */ diagnostic_show_locus_c_tests (); diagnostic_c_tests (); + diagnostic_format_json_cc_tests (); edit_context_c_tests (); fold_const_c_tests (); spellcheck_c_tests (); diff --git a/gcc/selftest.h b/gcc/selftest.h index 3e00e75..d278f0a 100644 --- a/gcc/selftest.h +++ b/gcc/selftest.h @@ -218,6 +218,7 @@ extern void bitmap_c_tests (); extern void cgraph_c_tests (); extern void convert_c_tests (); extern void diagnostic_c_tests (); +extern void diagnostic_format_json_cc_tests (); extern void diagnostic_show_locus_c_tests (); extern void dumpfile_c_tests (); extern void edit_context_c_tests (); diff --git a/gcc/testsuite/g++.dg/pr90462.C b/gcc/testsuite/g++.dg/pr90462.C new file mode 100644 index 0000000..2585ba0 --- /dev/null +++ b/gcc/testsuite/g++.dg/pr90462.C @@ -0,0 +1,49 @@ +/* { dg-options "-Wdeprecated-copy -fdiagnostics-format=json" } */ + +template <class> class b; +struct B { + typedef b<char> *c; +}; +class d { +public: + B::c operator->(); +}; +template <class> struct e; +class f { + typedef int g; +}; +template <class, class> class h; +template <class i> class b { +public: + i j; + i k; + int l; + void assign() { + int m; + h<i, int> n(&m); + n.o(&j, &k, l); + } +}; +template <class i, class> class s : f { s &p(const i *, const i *, g); }; +template <class i, class t> s<i, t> &s<i, t>::p(const i *, const i *, g) { + d q; + q->assign(); +} +struct G { + G(); + G(int); + G(G &); +}; +template <class i, class> class h { +public: + h(int *); + void o(const i *, const i *, unsigned); + i r(); +}; +template <class i, class t> void h<i, t>::o(const i *, const i *, unsigned) { + G a; + a = r(); +} +template s<char, e<char>> &s<char, e<char>>::p(const char *, const char *, g); + +/* { dg-regexp ".*" } */ -- 1.8.5.3