Bulletproof -fdiagnostics-format=json against bad locations (PR c++/90462)
authorDavid Malcolm <dmalcolm@redhat.com>
Thu, 23 May 2019 00:42:03 +0000 (00:42 +0000)
committerDavid Malcolm <dmalcolm@gcc.gnu.org>
Thu, 23 May 2019 00:42:03 +0000 (00:42 +0000)
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.

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.

From-SVN: r271535

gcc/ChangeLog
gcc/diagnostic-format-json.cc
gcc/json.cc
gcc/json.h
gcc/selftest-run-tests.c
gcc/selftest.h
gcc/testsuite/ChangeLog
gcc/testsuite/g++.dg/pr90462.C [new file with mode: 0644]

index fd69b14863236c1e7e207ec8d39fefcfd0e422c3..9f0f8893022a073ad1340a5594b72a8525e10899 100644 (file)
@@ -1,3 +1,23 @@
+2019-05-22  David Malcolm  <dmalcolm@redhat.com>
+
+       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.
+
 2019-05-22  Kwok Cheung Yeung  <kcy@codesourcery.com>
             Andrew Stubbs  <amd@codesourcery.com>
 
index 035521047666e2f0b57f4e822fdfeeb27f919709..53c3b630b1cda0e6ab5b130501beb6319b34a234 100644 (file)
@@ -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 */
index 2e8e21b6791e0f3deedfb4f0cc4383eb6dfc46f9..512e2e513b91393637098e9f3ffade35bcd27d7f 100644 (file)
@@ -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 ();
index 0527a2fc01fcb7e70023472851719bd0675eef25..d8a690ede5cf8e94b51c5e842bcbf24497dd20a9 100644 (file)
@@ -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 *,
index c2e0ee284abac051591801e31b879124d88eba3c..6ed7d82beeb76080348033dad5264f695a02f360 100644 (file)
@@ -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 ();
index 3e00e7516b87c92f22ee0163c1b53725947c51ca..d278f0a2900fd172fa582cbe0cf9ee77d8a91421 100644 (file)
@@ -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 ();
index d92eb8b1575d5abafbc6f8c813bbcf298d920d83..788f3e012aca5ddb28d4733c09858e33810dbb92 100644 (file)
@@ -1,3 +1,8 @@
+2019-05-22  David Malcolm  <dmalcolm@redhat.com>
+
+       PR c++/90462
+       * g++.dg/pr90462.C: New test.
+
 2019-05-22  Marek Polacek  <polacek@redhat.com>
 
        * g++.dg/cpp1y/udlit-char-template-neg.C: Expect the error on a
diff --git a/gcc/testsuite/g++.dg/pr90462.C b/gcc/testsuite/g++.dg/pr90462.C
new file mode 100644 (file)
index 0000000..2585ba0
--- /dev/null
@@ -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 ".*" } */