From a5647329049912b83e9581464ff153d609e88f83 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Wed, 17 May 2017 16:25:34 -0400
Subject: [PATCH 04/31] FIXME: start of JSON-RPC implemenation

---
 gcc/Makefile.in          |   1 +
 gcc/json-rpc.c           | 315 +++++++++++++++++++++++++++++++++++++++++++++++
 gcc/json-rpc.h           |  68 ++++++++++
 gcc/json.c               | 126 ++++++++++++++++++-
 gcc/json.h               |  19 ++-
 gcc/selftest-run-tests.c |   1 +
 gcc/selftest.h           |   1 +
 7 files changed, 525 insertions(+), 6 deletions(-)
 create mode 100644 gcc/json-rpc.c
 create mode 100644 gcc/json-rpc.h

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 3625c20..abc286d 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1362,6 +1362,7 @@ OBJS = \
 	ira-emit.o \
 	ira-lives.o \
 	json.o \
+	json-rpc.o \
 	jump.o \
 	langhooks.o \
 	lcm.o \
diff --git a/gcc/json-rpc.c b/gcc/json-rpc.c
new file mode 100644
index 0000000..a0b624e
--- /dev/null
+++ b/gcc/json-rpc.c
@@ -0,0 +1,315 @@
+/* JSON-RPC server implementation.
+   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 "json.h"
+#include "json-rpc.h"
+#include "selftest.h"
+
+json::object *
+jsonrpc::make_response (const json::value *id)
+{
+  json::object *response = new json::object ();
+  response->set ("jsonrpc", new json::string ("2.0"));
+  if (id)
+    response->set ("id", id->clone ());
+  return response;
+}
+
+json::object *
+jsonrpc::make_failure (int code, const char *message, const json::value *id)
+{
+  json::object *failure = make_response (id);
+  json::object *error = new json::object ();
+  failure->set ("error", error);
+
+  error->set ("code", new json::number (code));
+  error->set ("message", new json::string (message));
+  return failure;
+}
+
+json::value *
+jsonrpc::make_parse_error ()
+{
+  return make_failure (PARSE_ERROR, "parse error", NULL);
+}
+
+json::value *
+jsonrpc::make_invalid_request (const json::value *id)
+{
+  return make_failure (INVALID_REQUEST, "invalid request", id);
+}
+
+json::value *
+jsonrpc::make_method_not_found (const json::value *id, const char *method)
+{
+  const char *msg = ACONCAT (("method not found: ", method, NULL));
+  return make_failure (METHOD_NOT_FOUND, msg, id);
+}
+
+json::value *
+jsonrpc::make_invalid_params (const json::value *id, const char *msg)
+{
+  return make_failure (INVALID_PARAMS, msg, id);
+}
+
+json::value *
+jsonrpc::make_success (const json::value *id, json::value *result)
+{
+  json::object *success = make_response (id);
+  success->set ("result", result);
+  return success;
+}
+
+json::value *
+jsonrpc::server::handle_request (json::value *request)
+{
+  if (m_verbose)
+    {
+      fprintf (stderr, "request: ");
+      request->dump (stderr);
+      fprintf (stderr, "\n");
+    }
+
+  json::value *response = handle_request_1 (request);
+
+  if (m_verbose)
+    {
+      fprintf (stderr, "response: ");
+      response->dump (stderr);
+      fprintf (stderr, "\n");
+    }
+
+  return response;
+}
+
+json::value *
+jsonrpc::server::handle_request_1 (json::value *request)
+{
+  // FIXME: handle batch requests
+  if (request->get_kind () != json::JSON_OBJECT)
+    return make_parse_error ();
+
+  json::object *reqobj = static_cast <json::object *> (request);
+
+  /* Lookup id.  */
+  json::value *id = reqobj->get ("id");
+
+  /* If no id, then it's a notification.  */
+
+  /* TODO: validate non-NULL id.  */
+
+  /* Check version.  */
+  json::value *version = reqobj->get ("jsonrpc");
+  if (!version)
+    return make_invalid_request (id);
+  const json::string *version_string = version->as_string ();
+  if (!version_string)
+    return make_failure (INVALID_REQUEST, "jsonrpc was not 2.0", id);
+  if (0 != strcmp (version_string->get_string (), "2.0"))
+    return make_failure (INVALID_REQUEST, "jsonrpc was not 2.0", id);
+
+  json::value *method = reqobj->get ("method");
+  if (!method)
+    return make_failure (INVALID_REQUEST, "no method", id);
+  const json::string *method_str = method->as_string ();
+  if (!method_str)
+    return make_failure (INVALID_REQUEST, "method was not a string", id);
+
+  const json::value *params = reqobj->get ("params");
+  return dispatch (method_str->get_string (), params, id);
+}
+
+json::value *
+jsonrpc::server::handle_request_string (const char *utf8)
+{
+  json::value *request = json::parse_utf8_string (utf8);
+  json::value *response = handle_request (request);
+  delete request;
+  return response;
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Selftests.  */
+
+/* Assert that RESPONSE is a non-NULL response for ID.
+   Verify that the jsonrpc value is "2.0".
+   Return RESPONSE, cast to an object *.  */
+
+static const json::object *
+assert_is_response (const json::value *response, int id)
+{
+  ASSERT_TRUE (response != NULL);
+  const json::object *obj = response->as_object ();
+  ASSERT_TRUE (obj != NULL);
+  json::value *jsonrpc = obj->get ("jsonrpc");
+  ASSERT_TRUE (jsonrpc != NULL);
+  ASSERT_STREQ ("2.0", jsonrpc->as_string ()->get_string ());
+  ASSERT_EQ (id, obj->get ("id")->as_number ()->get ());
+  return obj;
+}
+
+/* Assert that RESPONSE is a non-NULL successful response for ID.
+   Verify that the jsonrpc value is "2.0".
+   Return the "result" value.  */
+
+static const json::value *
+assert_is_success (const json::value *response, int id)
+{
+  const json::object *obj = assert_is_response (response, id);
+  ASSERT_EQ (NULL, obj->get ("error"));
+  return obj->get ("result");
+}
+
+/* Assert that RESPONSE is a non-NULL failure response for ID.
+   Verify that the jsonrpc value is "2.0".
+   Verify that CODE and MESSAGE match the given values.  */
+
+static void
+assert_is_failure (const json::value *response, int code, const char *message,
+		   int id)
+{
+  const json::object *obj = assert_is_response (response, id);
+  ASSERT_EQ (NULL, obj->get ("result"));
+  const json::object *err = obj->get ("error")->as_object ();
+  ASSERT_EQ (code, err->get ("code")->as_number ()->get ());
+  ASSERT_STREQ (message, err->get ("message")->as_string ()->get_string ());
+}
+
+using namespace jsonrpc;
+
+class test_server : public jsonrpc::server
+{
+ public:
+  test_server (bool verbose) : server (verbose) {}
+
+  json::value *
+  dispatch (const char *method, const json::value *params,
+	    const json::value *id) FINAL OVERRIDE
+  {
+    if (0 == strcmp (method, "subtract"))
+      return do_subtract (params, id);
+    return make_method_not_found (id, method);
+  }
+
+  json::value *
+  do_subtract (const json::value *params, const json::value *id)
+  {
+    const json::array *positional = params->as_array ();
+    if (!positional)
+      return make_invalid_params (id, "params was not an array");
+    if (positional->get_length () != 2)
+      return make_invalid_params (id, "length of params was not 2");
+
+    const json::number *arg0 = positional->get (0)->as_number ();
+    if (!arg0)
+      return make_invalid_params (id, "param 0 was not a number");
+    const json::number *arg1 = positional->get (1)->as_number ();
+    if (!arg1)
+      return make_invalid_params (id, "param 1 was not a number");
+
+    double result = arg0->get () - arg1->get ();
+
+    return make_success (id, new json::number (result));
+  }
+};
+
+static void
+test_simple ()
+{
+  test_server s (false);
+  const char *request
+    = ("{\"jsonrpc\": \"2.0\", \"method\": \"subtract\","
+       " \"params\": [42, 23], \"id\": 1}");
+  json::value *response = s.handle_request_string (request);
+  const json::value *result = assert_is_success (response, 1);
+  ASSERT_EQ (19, result->as_number ()->get ());
+  delete response;
+}
+
+static void
+test_bad_params ()
+{
+  test_server s (false);
+  const char *request
+    = ("{\"jsonrpc\": \"2.0\", \"method\": \"subtract\","
+       " \"params\": [42, \"0\"], \"id\": 1}");
+  json::value *response = s.handle_request_string (request);
+  assert_is_failure (response, INVALID_PARAMS, "param 1 was not a number", 1);
+  delete response;
+}
+
+static void
+test_bad_version ()
+{
+  test_server s (false);
+  const char *request
+    = ("{\"jsonrpc\": \"1.0\", \"method\": \"subtract\","
+       " \"params\": [42, 23], \"id\": 1}");
+  json::value *response = s.handle_request_string (request);
+  assert_is_failure (response, INVALID_REQUEST, "jsonrpc was not 2.0", 1);
+  delete response;
+}
+
+static void
+test_method_not_a_string ()
+{
+  test_server s (false);
+  const char *request
+    = ("{\"jsonrpc\": \"2.0\", \"method\": 1,"
+       " \"params\": \"bar\", \"id\": 1}");
+  json::value *response = s.handle_request_string (request);
+  assert_is_failure (response, INVALID_REQUEST, "method was not a string", 1);
+  delete response;
+}
+
+static void
+test_method_not_found ()
+{
+  test_server s (false);
+  const char *request
+    = ("{\"jsonrpc\": \"2.0\", \"method\": \"does_not_exist\","
+       " \"params\": [42, 23], \"id\": 1}");
+  json::value *response = s.handle_request_string (request);
+  assert_is_failure (response, METHOD_NOT_FOUND,
+    "method not found: does_not_exist", 1);
+  delete response;
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+json_rpc_c_tests ()
+{
+  test_simple ();
+  test_bad_params ();
+  test_bad_version ();
+  test_method_not_a_string ();
+  test_method_not_found ();
+  // TODO: bogus JSON
+}
+
+} // namespace selftest
+
+#endif /* #if CHECKING_P */
diff --git a/gcc/json-rpc.h b/gcc/json-rpc.h
new file mode 100644
index 0000000..2f1b8b8
--- /dev/null
+++ b/gcc/json-rpc.h
@@ -0,0 +1,68 @@
+/* JSON-RPC.
+   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_JSON_RPC_H
+#define GCC_JSON_RPC_H
+
+namespace jsonrpc
+{
+
+class server
+{
+ public:
+  server (bool verbose) : m_verbose (verbose) {}
+  virtual ~server () {}
+  json::value *handle_request (json::value *);
+  json::value *handle_request_string (const char *utf8);
+
+  virtual json::value *dispatch (const char *method,
+				 const json::value *params,
+				 const json::value *id) = 0;
+
+ private:
+  json::value *handle_request_1 (json::value *);
+
+  bool m_verbose;
+};
+
+extern json::object *make_response (const json::value *id);
+extern json::object *make_failure (int code, const char *message,
+				   const json::value *id);
+
+const int PARSE_ERROR = -32700;
+const int INVALID_REQUEST = -32600;
+const int METHOD_NOT_FOUND = -32601;
+const int INVALID_PARAMS = -32602;
+const int INTERNAL_ERROR = -32603;
+
+extern json::value *make_parse_error ();
+
+extern json::value *make_invalid_request (const json::value *id);
+
+extern json::value *make_method_not_found (const json::value *id,
+					   const char *method);
+
+extern json::value *make_invalid_params (const json::value *id,
+					 const char *msg);
+
+extern json::value *make_success (const json::value *id, json::value *result);
+
+} // namespace jsonrpc
+
+#endif  /* GCC_JSON_RPC_H  */
diff --git a/gcc/json.c b/gcc/json.c
index 05448de..6833dc6 100644
--- a/gcc/json.c
+++ b/gcc/json.c
@@ -27,6 +27,38 @@ using namespace json;
 
 /* Implemenation.  */
 
+const object *
+value::as_object () const
+{
+  if (get_kind () != JSON_OBJECT)
+    return NULL;
+  return static_cast <const object *> (this);
+}
+
+const array *
+value::as_array () const
+{
+  if (get_kind () != JSON_ARRAY)
+    return NULL;
+  return static_cast <const array *> (this);
+}
+
+const number *
+value::as_number () const
+{
+  if (get_kind () != JSON_NUMBER)
+    return NULL;
+  return static_cast <const number *> (this);
+}
+
+const string *
+value::as_string () const
+{
+  if (get_kind () != JSON_STRING)
+    return NULL;
+  return static_cast <const string *> (this);
+}
+
 object::~object ()
 {
   for (map_t::iterator it = m_map.begin (); it != m_map.end (); ++it)
@@ -36,6 +68,29 @@ object::~object ()
     }
 }
 
+void
+object::dump (FILE *outf) const
+{
+  /* Note that the order is not guaranteed.  */
+  fprintf (outf, "{");
+  for (map_t::iterator it = m_map.begin (); it != m_map.end (); ++it)
+    {
+      if (it != m_map.begin ())
+	fprintf (outf, ", ");
+      const char *key = const_cast <char *>((*it).first);
+      value *value = (*it).second;
+      fprintf (outf, "\"%s\": ", key);
+      value->dump (outf);
+    }
+  fprintf (outf, "}");
+}
+
+value *
+object::clone () const
+{
+  gcc_unreachable (); // FIXME
+}
+
 value *
 object::get (const char *key) const
 {
@@ -48,9 +103,9 @@ object::get (const char *key) const
 /* Takes ownership of KEY and VALUE.  */
 
 void
-object::set (char *key, value *v)
+object::set (const char *key, value *v)
 {
-  m_map.put (key, v);
+  m_map.put (xstrdup (key), v);
 }
 
 array::~array ()
@@ -61,6 +116,63 @@ array::~array ()
     delete v;
 }
 
+void
+array::dump (FILE *outf) const
+{
+  fprintf (outf, "[");
+  unsigned i;
+  value *v;
+  FOR_EACH_VEC_ELT (m_elements, i, v)
+    {
+      if (i)
+	fprintf (outf, ", ");
+      v->dump (outf);
+    }
+  fprintf (outf, "]");
+}
+
+value *
+array::clone () const
+{
+  gcc_unreachable (); // FIXME
+}
+
+void
+number::dump (FILE *outf) const
+{
+  fprintf (outf, "%f", m_value);
+}
+
+value *
+number::clone () const
+{
+  return new number (m_value);
+}
+
+void
+string::dump (FILE *outf) const
+{
+  fprintf (outf, "\"%s\"", m_utf8);  // FIXME: escaping
+}
+
+value *
+string::clone () const
+{
+  gcc_unreachable (); // FIXME
+}
+
+void
+literal::dump (FILE *outf) const
+{
+  fprintf (outf, "FIXME"); // FIXME: escaping
+}
+
+value *
+literal::clone () const
+{
+  gcc_unreachable (); // FIXME
+}
+
 /* Parsing decls.  */
 
 namespace json {
@@ -179,9 +291,12 @@ lexer::consume ()
   gcc_assert (m_num_next_tokens > 0);
   gcc_assert (m_num_next_tokens <= MAX_TOKENS);
 
-  fprintf (stderr, "consuming token: ");
-  dump_token (stderr, &m_next_tokens[0]);
-  fprintf (stderr, "\n");
+  if (0)
+    {
+      fprintf (stderr, "consuming token: ");
+      dump_token (stderr, &m_next_tokens[0]);
+      fprintf (stderr, "\n");
+    }
 
   if (m_next_tokens[0].id == TOK_STRING)
     free (m_next_tokens[0].u.string);
@@ -508,6 +623,7 @@ parser::parse_object ()
 
       value *v = parse_value ();
       result->set (key, v);
+      free (key);
 
       tok = m_lexer.peek ();
       if (tok->id == TOK_COMMA)
diff --git a/gcc/json.h b/gcc/json.h
index 4f77d8f..e094446 100644
--- a/gcc/json.h
+++ b/gcc/json.h
@@ -48,6 +48,13 @@ class value
  public:
   virtual ~value () {}
   virtual enum kind get_kind () const = 0;
+  virtual void dump (FILE *outf) const = 0;
+  virtual value *clone () const = 0;
+
+  const object *as_object () const;
+  const array *as_array () const;
+  const number *as_number () const;
+  const string *as_string () const;
 };
 
 /* Subclass of value for objects: key/value pairs.  */
@@ -58,9 +65,11 @@ class object : public value
   ~object ();
 
   enum kind get_kind () const FINAL OVERRIDE { return JSON_OBJECT; }
+  void dump (FILE *outf) const FINAL OVERRIDE;
+  value *clone () const FINAL OVERRIDE;
 
   value *get (const char *key) const;
-  void set (char *key, value *v);
+  void set (const char *key, value *v);
 
  private:
   typedef hash_map <char *, value *,
@@ -76,6 +85,8 @@ class array : public value
   ~array ();
 
   enum kind get_kind () const FINAL OVERRIDE { return JSON_ARRAY; }
+  void dump (FILE *outf) const FINAL OVERRIDE;
+  value *clone () const FINAL OVERRIDE;
 
   unsigned get_length () const { return m_elements.length (); }
   value *get (int idx) const { return m_elements[idx]; }
@@ -93,6 +104,8 @@ class number : public value
   number (double value) : m_value (value) {}
 
   enum kind get_kind () const FINAL OVERRIDE { return JSON_NUMBER; }
+  void dump (FILE *outf) const FINAL OVERRIDE;
+  value *clone () const FINAL OVERRIDE;
 
   double get () const { return m_value; }
 
@@ -109,6 +122,8 @@ class string : public value
   ~string () { free (m_utf8); }
 
   enum kind get_kind () const FINAL OVERRIDE { return JSON_STRING; }
+  void dump (FILE *outf) const FINAL OVERRIDE;
+  value *clone () const FINAL OVERRIDE;
 
   const char *get_string () const { return m_utf8; }
 
@@ -124,6 +139,8 @@ class literal : public value
   literal (enum kind kind) : m_kind (kind) {}
 
   enum kind get_kind () const FINAL OVERRIDE { return m_kind; }
+  void dump (FILE *outf) const FINAL OVERRIDE;
+  value *clone () const FINAL OVERRIDE;
 
  private:
   enum kind m_kind;
diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c
index b8fe4a1..49aa263 100644
--- a/gcc/selftest-run-tests.c
+++ b/gcc/selftest-run-tests.c
@@ -67,6 +67,7 @@ selftest::run_tests ()
   fibonacci_heap_c_tests ();
   typed_splay_tree_c_tests ();
   json_c_tests ();
+  json_rpc_c_tests ();
 
   /* Mid-level data structures.  */
   input_c_tests ();
diff --git a/gcc/selftest.h b/gcc/selftest.h
index 6b03e39..3a5402b 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -184,6 +184,7 @@ extern void hash_map_tests_c_tests ();
 extern void hash_set_tests_c_tests ();
 extern void input_c_tests ();
 extern void json_c_tests ();
+extern void json_rpc_c_tests ();
 extern void pretty_print_c_tests ();
 extern void read_rtl_function_c_tests ();
 extern void rtl_tests_c_tests ();
-- 
1.8.5.3

