[gtk/wip/otte/json: 74/85] jsonparser: Improve error handling




commit 59fc8d428d14b12dad364cfa2da3b232db1effbc
Author: Benjamin Otte <otte redhat com>
Date:   Sat Dec 4 20:51:11 2021 +0100

    jsonparser: Improve error handling
    
     * Add a custom GError domain with our own error values
     * Add API so external code can emit errors
     * Add location information to errors
     * Allow API to query errors

 gtk/json/gtkjsonparser.c        | 502 +++++++++++++++++++++++++++++++++-------
 gtk/json/gtkjsonparserprivate.h |  27 ++-
 tools/gtk-json-format.c         |  42 +++-
 tools/gtk-json-validate.c       |  37 ++-
 4 files changed, 518 insertions(+), 90 deletions(-)
---
diff --git a/gtk/json/gtkjsonparser.c b/gtk/json/gtkjsonparser.c
index 38a90b4d67..2f1e464477 100644
--- a/gtk/json/gtkjsonparser.c
+++ b/gtk/json/gtkjsonparser.c
@@ -45,6 +45,8 @@ struct _GtkJsonParser
   const guchar *end; /* pointer after end of data we're reading */
 
   GError *error; /* if an error has happened, it's stored here. Errors aren't recoverable. */
+  const guchar *error_start; /* start of error location */
+  const guchar *error_end; /* end of error location */
 
   GtkJsonBlock *block; /* current block */
   GtkJsonBlock *blocks; /* blocks array */
@@ -54,16 +56,17 @@ struct _GtkJsonParser
 
 typedef enum {
   WHITESPACE     = (1 << 4),
-  STRING_ELEMENT = (1 << 5),
-  STRING_MARKER  = (1 << 6),
+  NEWLINE        = (1 << 5),
+  STRING_ELEMENT = (1 << 6),
+  STRING_MARKER  = (1 << 7),
 } JsonCharacterType;
 
 #define JSON_CHARACTER_NODE_MASK ((1 << 4) - 1)
 
 static const guchar json_character_table[256] = {
   ['\t'] = WHITESPACE,
-  ['\r'] = WHITESPACE,
-  ['\n'] = WHITESPACE,
+  ['\r'] = WHITESPACE | NEWLINE,
+  ['\n'] = WHITESPACE | NEWLINE,
   [' ']  = WHITESPACE | STRING_ELEMENT,
   ['!']  = STRING_ELEMENT,
   ['"']  = GTK_JSON_STRING | STRING_MARKER,
@@ -177,6 +180,21 @@ json_skip_characters (const guchar      *start,
   return s;
 }
 
+static const guchar *
+json_skip_characters_until (const guchar      *start,
+                            const guchar      *end,
+                            JsonCharacterType  type)
+{
+  const guchar *s;
+
+  for (s = start; s < end; s++)
+    {
+      if (json_character_table[*s] & type)
+        break;
+    }
+  return s;
+}
+
 static const guchar *
 json_find_character (const guchar      *start,
                      JsonCharacterType  type)
@@ -191,14 +209,45 @@ json_find_character (const guchar      *start,
   return s;
 }
 
+GQuark
+gtk_json_error_quark (void)
+{
+  return g_quark_from_static_string ("gtk-json-error-quark");
+}
+
 static void
-gtk_json_parser_value_error (GtkJsonParser *self,
-                             const char  *format,
-                             ...) G_GNUC_PRINTF(2, 3);
+gtk_json_parser_take_error (GtkJsonParser *self,
+                            const guchar  *start_location,
+                            const guchar  *end_location,
+                            GError        *error)
+{
+  g_assert (start_location <= end_location);
+  g_assert (g_bytes_get_data (self->bytes, NULL) <= (gconstpointer) start_location);
+  g_assert (end_location <= self->end);
+
+  if (self->error)
+    {
+      g_error_free (error);
+      return;
+    }
+
+  self->error = error;
+  self->error_start = start_location;
+  self->error_end = end_location;
+}
+
 static void
-gtk_json_parser_value_error (GtkJsonParser *self,
-                             const char  *format,
-                             ...)
+gtk_json_parser_syntax_error_at (GtkJsonParser *self,
+                                 const guchar  *error_start,
+                                 const guchar  *error_end,
+                                 const char    *format,
+                                 ...) G_GNUC_PRINTF(4, 5);
+static void
+gtk_json_parser_syntax_error_at (GtkJsonParser *self,
+                                 const guchar  *error_start,
+                                 const guchar  *error_end,
+                                 const char    *format,
+                                 ...)
 {
   va_list args;
 
@@ -206,30 +255,137 @@ gtk_json_parser_value_error (GtkJsonParser *self,
     return;
 
   va_start (args, format);
-  self->error = g_error_new_valist (G_FILE_ERROR,
-                                    G_FILE_ERROR_FAILED,
-                                    format, args);
+  gtk_json_parser_take_error (self,
+                              error_start,
+                              error_end,
+                              g_error_new_valist (GTK_JSON_ERROR,
+                                                  GTK_JSON_ERROR_SYNTAX,
+                                                  format, args));
   va_end (args);
 }
 
 static void
 gtk_json_parser_syntax_error (GtkJsonParser *self,
-                              const char  *format,
+                              const char    *format,
                               ...) G_GNUC_PRINTF(2, 3);
 static void
 gtk_json_parser_syntax_error (GtkJsonParser *self,
-                              const char  *format,
+                              const char    *format,
+                              ...)
+{
+  va_list args;
+  const guchar *error_end;
+
+  if (self->error)
+    return;
+
+  va_start (args, format);
+  for (error_end = self->reader;
+       error_end < self->end && g_ascii_isalnum (*error_end);
+       error_end++)
+    ;
+  if (error_end == self->reader &&
+      g_utf8_get_char_validated ((const char *) error_end, self->end - error_end) < (gunichar) -2)
+    {
+      error_end = (const guchar *) g_utf8_next_char (error_end);
+    }
+
+  gtk_json_parser_take_error (self,
+                              self->reader,
+                              error_end,
+                              g_error_new_valist (GTK_JSON_ERROR,
+                                                  GTK_JSON_ERROR_SYNTAX,
+                                                  format, args));
+  va_end (args);
+}
+
+static void
+gtk_json_parser_type_error (GtkJsonParser *self,
+                            const char    *format,
+                            ...) G_GNUC_PRINTF(2, 3);
+static void
+gtk_json_parser_type_error (GtkJsonParser *self,
+                            const char    *format,
+                            ...)
+{
+  const guchar *start_location;
+  va_list args;
+
+  if (self->error)
+    return;
+
+  if (self->block->value)
+    start_location = self->block->value;
+  else if (self->block != self->blocks)
+    start_location = self->block[-1].value;
+  else
+    start_location = g_bytes_get_data (self->bytes, NULL);
+
+  va_start (args, format);
+  gtk_json_parser_take_error (self,
+                              start_location,
+                              self->reader,
+                              g_error_new_valist (GTK_JSON_ERROR,
+                                                  GTK_JSON_ERROR_TYPE,
+                                                  format, args));
+  va_end (args);
+}
+
+void
+gtk_json_parser_value_error (GtkJsonParser *self,
+                             const char    *format,
+                             ...)
+{
+  const guchar *start_location;
+  va_list args;
+
+  if (self->error)
+    return;
+
+  if (self->block->value)
+    start_location = self->block->value;
+  else if (self->block != self->blocks)
+    start_location = self->block[-1].value;
+  else
+    start_location = g_bytes_get_data (self->bytes, NULL);
+
+  va_start (args, format);
+  gtk_json_parser_take_error (self,
+                              start_location,
+                              self->reader,
+                              g_error_new_valist (GTK_JSON_ERROR,
+                                                  GTK_JSON_ERROR_VALUE,
+                                                  format, args));
+  va_end (args);
+}
+
+void
+gtk_json_parser_schema_error (GtkJsonParser *self,
+                              const char    *format,
                               ...)
 {
+  const guchar *start_location;
   va_list args;
 
   if (self->error)
     return;
 
+  if (self->block->member_name)
+    start_location = self->block->member_name;
+  if (self->block->value)
+    start_location = self->block->value;
+  else if (self->block != self->blocks)
+    start_location = self->block[-1].value;
+  else
+    start_location = g_bytes_get_data (self->bytes, NULL);
+
   va_start (args, format);
-  self->error = g_error_new_valist (G_FILE_ERROR,
-                                    G_FILE_ERROR_FAILED,
-                                    format, args);
+  gtk_json_parser_take_error (self,
+                              start_location,
+                              self->reader,
+                              g_error_new_valist (GTK_JSON_ERROR,
+                                                  GTK_JSON_ERROR_SCHEMA,
+                                                  format, args));
   va_end (args);
 }
 
@@ -459,9 +615,13 @@ gtk_json_unescape_string (const guchar *escaped)
 static gboolean
 gtk_json_parser_parse_string (GtkJsonParser *self)
 {
+  const guchar *start;
+
+  start = self->reader;
+
   if (!gtk_json_parser_try_char (self, '"'))
     {
-      gtk_json_parser_syntax_error (self, "Not a string");
+      gtk_json_parser_type_error (self, "Not a string");
       return FALSE;
     }
 
@@ -471,7 +631,12 @@ gtk_json_parser_parse_string (GtkJsonParser *self)
     {
       if (*self->reader < 0x20)
         {
-          gtk_json_parser_syntax_error (self, "Disallowed control character in string literal");
+          if (*self->reader == '\r' || *self->reader == '\n')
+            gtk_json_parser_syntax_error (self, "Newlines in strings are not allowed");
+          else if (*self->reader == '\t')
+            gtk_json_parser_syntax_error (self, "Tabs not allowed in strings");
+          else
+            gtk_json_parser_syntax_error (self, "Disallowed control character in string literal");
           return FALSE;
         }
       else if (*self->reader > 127)
@@ -492,9 +657,11 @@ gtk_json_parser_parse_string (GtkJsonParser *self)
       else if (*self->reader == '\\')
         {
           if (gtk_json_parser_remaining (self) < 2)
-            goto end;
-          self->reader++;
-          switch (*self->reader)
+            {
+              self->reader = self->end;
+              goto end;
+            }
+          switch (self->reader[1])
             {
             case '"':
             case '\\':
@@ -508,42 +675,50 @@ gtk_json_parser_parse_string (GtkJsonParser *self)
 
             case 'u':
               /* lots of work necessary to validate the unicode escapes here */
-              if (gtk_json_parser_remaining (self) < 5 ||
-                  !g_ascii_isxdigit (self->reader[1]) ||
+              if (gtk_json_parser_remaining (self) < 6 ||
                   !g_ascii_isxdigit (self->reader[2]) ||
                   !g_ascii_isxdigit (self->reader[3]) ||
-                  !g_ascii_isxdigit (self->reader[4]))
+                  !g_ascii_isxdigit (self->reader[4]) ||
+                  !g_ascii_isxdigit (self->reader[5]))
                 {
-                  gtk_json_parser_syntax_error (self, "Invalid Unicode escape sequence");
+                  const guchar *end;
+                  for (end = self->reader + 2;
+                       end < self->reader + 6 && end < self->end;
+                       end++)
+                    {
+                      if (!g_ascii_isxdigit (*end))
+                        break;
+                    }
+                  gtk_json_parser_syntax_error_at (self, self->reader, end, "Invalid Unicode escape 
sequence");
                   return FALSE;
                 }
               else
                 {
-                  gunichar unichar = (g_ascii_xdigit_value (self->reader[1]) << 12) |
-                                     (g_ascii_xdigit_value (self->reader[2]) <<  8) |
-                                     (g_ascii_xdigit_value (self->reader[3]) <<  4) |
-                                     (g_ascii_xdigit_value (self->reader[4]));
+                  gsize escape_size = 6;
+                  gunichar unichar = (g_ascii_xdigit_value (self->reader[2]) << 12) |
+                                     (g_ascii_xdigit_value (self->reader[3]) <<  8) |
+                                     (g_ascii_xdigit_value (self->reader[4]) <<  4) |
+                                     (g_ascii_xdigit_value (self->reader[5]));
 
-                  self->reader += 4;
                   /* resolve UTF-16 surrogates for Unicode characters not in the BMP,
                    * as per ECMA 404, ยง 9, "String"
                    */
                   if (g_unichar_type (unichar) == G_UNICODE_SURROGATE)
                     {
-                      if (gtk_json_parser_remaining (self) >= 7 &&
-                          self->reader[1] == '\\' &&
-                          self->reader[2] == 'u' &&
-                          g_ascii_isxdigit (self->reader[3]) &&
-                          g_ascii_isxdigit (self->reader[4]) &&
-                          g_ascii_isxdigit (self->reader[5]) &&
-                          g_ascii_isxdigit (self->reader[6]))
+                      if (gtk_json_parser_remaining (self) >= 12 &&
+                          self->reader[6] == '\\' &&
+                          self->reader[7] == 'u' &&
+                          g_ascii_isxdigit (self->reader[8]) &&
+                          g_ascii_isxdigit (self->reader[9]) &&
+                          g_ascii_isxdigit (self->reader[10]) &&
+                          g_ascii_isxdigit (self->reader[11]))
                         {
                           unichar = decode_utf16_surrogate_pair (unichar,
-                                                                 (g_ascii_xdigit_value (self->reader[3]) << 
12) |
-                                                                 (g_ascii_xdigit_value (self->reader[4]) <<  
8) |
-                                                                 (g_ascii_xdigit_value (self->reader[5]) <<  
4) |
-                                                                 (g_ascii_xdigit_value (self->reader[6])));
-                          self->reader += 6;
+                                                                 (g_ascii_xdigit_value (self->reader[8]) << 
12) |
+                                                                 (g_ascii_xdigit_value (self->reader[9]) <<  
8) |
+                                                                 (g_ascii_xdigit_value (self->reader[10]) << 
 4) |
+                                                                 (g_ascii_xdigit_value (self->reader[11])));
+                          escape_size += 6;
                         }
                       else
                         {
@@ -552,39 +727,75 @@ gtk_json_parser_parse_string (GtkJsonParser *self)
 
                       if (unichar == 0)
                         {
-                          gtk_json_parser_syntax_error (self, "Invalid UTF-16 surrogate pair");
+                          gtk_json_parser_syntax_error_at (self, self->reader, self->reader + escape_size, 
"Invalid UTF-16 surrogate pair");
                           return FALSE;
                         }
+
+                      self->reader += escape_size - 2;
                     }
                 }
               break;
             default:
-              gtk_json_parser_syntax_error (self, "Unknown escape sequence");
+              if (g_utf8_get_char_validated ((const char *) self->reader + 1, self->end - self->reader - 1) 
< (gunichar) -2)
+                gtk_json_parser_syntax_error_at (self, self->reader, (const guchar *) g_utf8_next_char 
(self->reader + 1), "Unknown escape sequence");
+              else
+                gtk_json_parser_syntax_error_at (self, self->reader, self->reader + 1, "Unknown escape 
sequence");
               return FALSE;
             }
-          self->reader++;
+          self->reader += 2;
         }
 
       self->reader = json_skip_characters (self->reader, self->end, STRING_ELEMENT);
     }
 
 end:
-  gtk_json_parser_syntax_error (self, "Unterminated string literal");
+  gtk_json_parser_syntax_error_at (self, start, self->reader, "Unterminated string literal");
   return FALSE;
 }
 
 static gboolean
 gtk_json_parser_parse_number (GtkJsonParser *self)
 {
+  const guchar *start = self->reader;
+  gboolean have_sign;
+
   /* sign */
-  gtk_json_parser_try_char (self, '-');
+  have_sign = gtk_json_parser_try_char (self, '-');
 
   /* integer part */
-  if (!gtk_json_parser_try_char (self, '0'))
+  if (gtk_json_parser_try_char (self, '0'))
+    {
+      /* Technically, "01" in the JSON grammar would be 2 numbers:
+       * "0" followed by "1".
+       * Practically, nobody understands that it's 2 numbers, so we
+       * special-purpose an error message for it, because 2 numbers
+       * can never follow each other.
+       */
+      if (!gtk_json_parser_is_eof (self) &&
+          g_ascii_isdigit (*self->reader))
+        {
+          do
+            {
+              self->reader++;
+            }
+          while (!gtk_json_parser_is_eof (self) &&
+                 g_ascii_isdigit (*self->reader));
+          
+          gtk_json_parser_syntax_error_at (self, start, self->reader, "Numbers may not start with leading 
0s");
+          return FALSE;
+        }
+    }
+  else
     {
       if (gtk_json_parser_is_eof (self) ||
           !g_ascii_isdigit (*self->reader))
-        goto out;
+        {
+          if (have_sign)
+            gtk_json_parser_syntax_error_at (self, start, self->reader, "Expected a number after '-' 
character");
+          else
+            gtk_json_parser_type_error (self, "Not a number");
+          return FALSE;
+        }
 
       self->reader++;
 
@@ -593,29 +804,41 @@ gtk_json_parser_parse_number (GtkJsonParser *self)
     }
 
   /* fractional part */
-  if (gtk_json_parser_remaining (self) >= 2 && *self->reader == '.' && g_ascii_isdigit (self->reader[1]))
+  if (gtk_json_parser_try_char (self, '.'))
     {
-      self->reader += 2;
+      if (!g_ascii_isdigit (*self->reader))
+        {
+          gtk_json_parser_syntax_error_at (self, start, self->reader, "Expected a digit after '.'");
+          return FALSE;
+        }
 
-      while (!gtk_json_parser_is_eof (self) && g_ascii_isdigit (*self->reader))
-        self->reader++;
+      do
+        {
+          self->reader++;
+        }
+      while (!gtk_json_parser_is_eof (self) && g_ascii_isdigit (*self->reader));
     }
 
   /* exponent */
-  if (gtk_json_parser_remaining (self) >= 2 && (self->reader[0] == 'e' || self->reader[0] == 'E') &&
-      (g_ascii_isdigit (self->reader[1]) ||
-       (gtk_json_parser_remaining (self) >= 3 && (self->reader[1] == '+' || self->reader[1] == '-') && 
g_ascii_isdigit (self->reader[2]))))
+  if (gtk_json_parser_try_char (self, 'e') ||
+      gtk_json_parser_try_char (self, 'E'))
     {
-      self->reader += 2;
+      if (!gtk_json_parser_try_char (self, '-'))
+        gtk_json_parser_try_char (self, '+');
 
-      while (!gtk_json_parser_is_eof (self) && g_ascii_isdigit (*self->reader))
-        self->reader++;
+      if (!g_ascii_isdigit (*self->reader))
+        {
+          gtk_json_parser_syntax_error_at (self, start, self->reader, "Expected a digit in exponent");
+          return FALSE;
+        }
+
+      do
+        {
+          self->reader++;
+        }
+      while (!gtk_json_parser_is_eof (self) && g_ascii_isdigit (*self->reader));
     }
   return TRUE;
-
-out:
-  gtk_json_parser_syntax_error (self, "Not a valid number");
-  return FALSE;
 }
 
 static gboolean
@@ -655,7 +878,17 @@ gtk_json_parser_parse_value (GtkJsonParser *self)
       break;
   }
 
-  gtk_json_parser_syntax_error (self, "Expected a value");
+  if (gtk_json_parser_remaining (self) >= 2 &&
+      (self->block->value[0] == '.' || self->block->value[0] == '+') &&
+      g_ascii_isdigit (self->block->value[1]))
+    {
+      const guchar *end = self->block->value + 3;
+      while (end < self->end && g_ascii_isalnum (*end))
+        end++;
+      gtk_json_parser_syntax_error_at (self, self->block->value, end, "Numbers may not start with '%c'", 
*self->block->value);
+    }
+  else
+    gtk_json_parser_syntax_error (self, "Expected a value");
   return FALSE;
 }
 
@@ -731,8 +964,15 @@ gtk_json_parser_new_for_bytes (GBytes *bytes)
   self->block->type = GTK_JSON_BLOCK_TOPLEVEL;
 
   gtk_json_parser_skip_whitespace (self);
-  self->block->value = self->reader;
-  gtk_json_parser_parse_value (self);
+  if (gtk_json_parser_is_eof (self))
+    {
+      gtk_json_parser_syntax_error_at (self, g_bytes_get_data (self->bytes, NULL), self->reader, "Empty 
document");
+    }
+  else
+    {
+      self->block->value = self->reader;
+      gtk_json_parser_parse_value (self);
+    }
 
   return self;
 }
@@ -806,7 +1046,7 @@ gtk_json_parser_next (GtkJsonParser *self)
         }
       else
         {
-          gtk_json_parser_syntax_error (self, "Data at end of document");
+          gtk_json_parser_syntax_error_at (self, self->reader, self->end, "Data at end of document");
         }
       return FALSE;
 
@@ -814,7 +1054,10 @@ gtk_json_parser_next (GtkJsonParser *self)
       gtk_json_parser_skip_whitespace (self);
       if (gtk_json_parser_is_eof (self))
         {
-          gtk_json_parser_syntax_error (self, "Unexpected end of document");
+          gtk_json_parser_syntax_error_at (self,
+                                           self->block[-1].value,
+                                           self->reader,
+                                           "Unterminated object");
           self->block->member_name = NULL;
           self->block->value = NULL;
         }
@@ -830,6 +1073,11 @@ gtk_json_parser_next (GtkJsonParser *self)
           return FALSE;
         }
       gtk_json_parser_skip_whitespace (self);
+      if (!gtk_json_parser_has_char (self, '"'))
+        {
+          gtk_json_parser_syntax_error (self, "Expected a string for object member name");
+          return FALSE;
+        }
       self->block->member_name = self->reader;
 
       if (!gtk_json_parser_parse_string (self))
@@ -851,7 +1099,10 @@ gtk_json_parser_next (GtkJsonParser *self)
       gtk_json_parser_skip_whitespace (self);
       if (gtk_json_parser_is_eof (self))
         {
-          gtk_json_parser_syntax_error (self, "Unexpected end of document");
+          gtk_json_parser_syntax_error_at (self,
+                                           self->block[-1].value,
+                                           self->reader,
+                                           "Unterminated array");
           self->block->member_name = NULL;
           self->block->value = NULL;
         }
@@ -899,6 +1150,89 @@ gtk_json_parser_get_error (GtkJsonParser *self)
   return self->error;
 }
 
+void
+gtk_json_parser_get_error_offset (GtkJsonParser *self,
+                                  gsize         *start,
+                                  gsize         *end)
+{
+  const guchar *data;
+
+  if (self->error == NULL)
+    {
+      if (start)
+        *start = 0;
+      if (end)
+        *end = 0;
+      return;
+    }
+
+  data = g_bytes_get_data (self->bytes, NULL);
+  if (start)
+    *start = self->error_start - data;
+  if (end)
+    *end = self->error_end - data;
+}
+
+void
+gtk_json_parser_get_error_location (GtkJsonParser *self,
+                                    gsize         *start_line,
+                                    gsize         *start_line_bytes,
+                                    gsize         *end_line,
+                                    gsize         *end_line_bytes)
+{
+  const guchar *s, *line_start;
+  gsize lines;
+
+  if (self->error == NULL)
+    {
+      if (start_line)
+        *start_line = 0;
+      if (start_line_bytes)
+        *start_line_bytes = 0;
+      if (end_line)
+        *end_line = 0;
+      if (end_line_bytes)
+        *end_line_bytes = 0;
+      return;
+    }
+
+  line_start = g_bytes_get_data (self->bytes, NULL);
+  lines = 0;
+
+  for (s = json_skip_characters_until (line_start, self->error_start, NEWLINE);
+       s < self->error_start;
+       s = json_skip_characters_until  (line_start, self->error_start, NEWLINE))
+    {
+      if (s[0] == '\r' && s + 1 < self->error_start && s[1] == '\n')
+        s++;
+      lines++;
+      line_start = s + 1;
+    }
+
+  if (start_line)
+    *start_line = lines;
+  if (start_line_bytes)
+    *start_line_bytes = s - line_start;
+
+  if (end_line == NULL && end_line_bytes == NULL)
+    return;
+
+  for (s = json_skip_characters_until (s, self->error_end, NEWLINE);
+       s < self->error_end;
+       s = json_skip_characters_until (line_start, self->error_end, NEWLINE))
+    {
+      if (s[0] == '\r' && s + 1 < self->error_start && s[1] == '\n')
+        s++;
+      lines++;
+      line_start = s + 1;
+    }
+
+  if (end_line)
+    *end_line = lines;
+  if (end_line_bytes)
+    *end_line_bytes = s - line_start;
+}
+
 static gboolean
 gtk_json_parser_has_member (GtkJsonParser *self)
 {
@@ -991,7 +1325,7 @@ gtk_json_parser_get_boolean (GtkJsonParser *self)
   else if (*self->block->value == 'f')
     return FALSE;
 
-  gtk_json_parser_value_error (self, "Expected a boolean value");
+  gtk_json_parser_type_error (self, "Expected a boolean value");
   return FALSE;
 }
 
@@ -1008,7 +1342,7 @@ gtk_json_parser_get_number (GtkJsonParser *self)
 
   if (!strchr ("-0123456789", *self->block->value))
     {
-      gtk_json_parser_value_error (self, "Expected a number");
+      gtk_json_parser_type_error (self, "Expected a number");
       return 0;
     }
 
@@ -1054,7 +1388,7 @@ gtk_json_parser_get_string (GtkJsonParser *self)
 
   if (*self->block->value != '"')
     {
-      gtk_json_parser_value_error (self, "Expected a string");
+      gtk_json_parser_type_error (self, "Expected a string");
       return g_strdup ("");
     }
 
@@ -1069,7 +1403,7 @@ gtk_json_parser_start_object (GtkJsonParser *self)
 
   if (!gtk_json_parser_try_char (self, '{'))
     {
-      gtk_json_parser_value_error (self, "Expected an object");
+      gtk_json_parser_type_error (self, "Expected an object");
       return FALSE;
     }
 
@@ -1078,11 +1412,20 @@ gtk_json_parser_start_object (GtkJsonParser *self)
   gtk_json_parser_skip_whitespace (self);
   if (gtk_json_parser_is_eof (self))
     {
-      gtk_json_parser_syntax_error (self, "Unexpected end of document");
+      gtk_json_parser_syntax_error_at (self,
+                                       self->block[-1].value,
+                                       self->reader,
+                                       "Unterminated object");
       return FALSE;
     }
   if (gtk_json_parser_has_char (self, '}'))
     return TRUE;
+
+  if (!gtk_json_parser_has_char (self, '"'))
+    {
+      gtk_json_parser_syntax_error (self, "Expected a string for object member name");
+      return FALSE;
+    }
   self->block->member_name = self->reader;
 
   if (!gtk_json_parser_parse_string (self))
@@ -1110,7 +1453,7 @@ gtk_json_parser_start_array (GtkJsonParser *self)
 
   if (!gtk_json_parser_try_char (self, '['))
     {
-      gtk_json_parser_value_error (self, "Expected an array");
+      gtk_json_parser_type_error (self, "Expected an array");
       return FALSE;
     }
 
@@ -1118,7 +1461,10 @@ gtk_json_parser_start_array (GtkJsonParser *self)
   gtk_json_parser_skip_whitespace (self);
   if (gtk_json_parser_is_eof (self))
     {
-      gtk_json_parser_syntax_error (self, "Unexpected end of document");
+      gtk_json_parser_syntax_error_at (self,
+                                       self->block[-1].value,
+                                       self->reader,
+                                       "Unterminated array");
       return FALSE;
     }
   if (gtk_json_parser_has_char (self, ']'))
diff --git a/gtk/json/gtkjsonparserprivate.h b/gtk/json/gtkjsonparserprivate.h
index f3ff43fd88..fe737fb719 100644
--- a/gtk/json/gtkjsonparserprivate.h
+++ b/gtk/json/gtkjsonparserprivate.h
@@ -35,8 +35,19 @@ typedef enum {
   GTK_JSON_ARRAY
 } GtkJsonNode;
 
+typedef enum {
+  GTK_JSON_ERROR_FAILED,
+  GTK_JSON_ERROR_SYNTAX,
+  GTK_JSON_ERROR_TYPE,
+  GTK_JSON_ERROR_VALUE,
+  GTK_JSON_ERROR_SCHEMA,
+} GtkJsonError;
+
 typedef struct _GtkJsonParser GtkJsonParser;
 
+#define GTK_JSON_ERROR (gtk_json_error_quark ())
+GQuark                  gtk_json_error_quark                    (void);
+
 GtkJsonParser *         gtk_json_parser_new_for_bytes           (GBytes                 *bytes);
 GtkJsonParser *         gtk_json_parser_new_for_string          (const char             *string,
                                                                  gssize                  size);
@@ -45,7 +56,6 @@ void                    gtk_json_parser_free                    (GtkJsonParser
 
 gboolean                gtk_json_parser_next                    (GtkJsonParser          *self);
 GtkJsonNode             gtk_json_parser_get_node                (GtkJsonParser          *self);
-const GError *          gtk_json_parser_get_error               (GtkJsonParser          *self) G_GNUC_PURE;
 char *                  gtk_json_parser_get_member_name         (GtkJsonParser          *self);
 gssize                  gtk_json_parser_select_member           (GtkJsonParser          *self,
                                                                  const char * const     *options);
@@ -60,6 +70,21 @@ gboolean                gtk_json_parser_start_object            (GtkJsonParser
 gboolean                gtk_json_parser_start_array             (GtkJsonParser          *self);
 gboolean                gtk_json_parser_end                     (GtkJsonParser          *self);
 
+const GError *          gtk_json_parser_get_error               (GtkJsonParser          *self) G_GNUC_PURE;
+void                    gtk_json_parser_get_error_offset        (GtkJsonParser          *self,
+                                                                 gsize                  *start,
+                                                                 gsize                  *end);
+void                    gtk_json_parser_get_error_location      (GtkJsonParser          *self,
+                                                                 gsize                  *start_line,
+                                                                 gsize                  *start_line_bytes,
+                                                                 gsize                  *end_line,
+                                                                 gsize                  *end_line_bytes);
+void                    gtk_json_parser_value_error             (GtkJsonParser          *self,
+                                                                 const char             *format,
+                                                                 ...) G_GNUC_PRINTF(2, 3);
+void                    gtk_json_parser_schema_error            (GtkJsonParser          *self,
+                                                                 const char             *format,
+                                                                 ...) G_GNUC_PRINTF(2, 3);
 
 G_END_DECLS
 
diff --git a/tools/gtk-json-format.c b/tools/gtk-json-format.c
index 9187b08039..2ea4f13014 100644
--- a/tools/gtk-json-format.c
+++ b/tools/gtk-json-format.c
@@ -221,24 +221,52 @@ format (GtkJsonPrinter *printer,
     }
 
   parser = gtk_json_parser_new_for_bytes (bytes);
-  g_bytes_unref (bytes);
   parse_and_print (parser, printer);
+
   if (gtk_json_parser_get_error (parser))
     {
-      char *uri = g_file_get_uri (file);
-      const GError *parser_error = gtk_json_parser_get_error (parser);
-
+      char *uri;
+      const GError *parser_error;
+      const char *data, *start, *end;
+      gsize start_line, start_bytes, start_offset, end_line, end_bytes, end_offset;
+      GString *string;
+
+      uri = g_file_get_uri (file);
+      parser_error = gtk_json_parser_get_error (parser);
+      data = g_bytes_get_data (bytes, NULL);
+      gtk_json_parser_get_error_offset (parser, &start_offset, &end_offset);
+      start = data + start_offset;
+      end = data + end_offset;
+      gtk_json_parser_get_error_location (parser,
+                                          &start_line, &start_bytes,
+                                          &end_line, &end_bytes);
+      
+      string = g_string_new (NULL);
+      g_string_append_printf (string, "%zu:%lu", 
+                              start_line + 1,
+                              g_utf8_pointer_to_offset (start - start_bytes, start) + 1);
+      if (start_line != end_line || start_bytes != end_bytes)
+        {
+          g_string_append (string, "-");
+          if (start_line != end_line)
+            g_string_append_printf (string, "%zu:", end_line + 1);
+          g_string_append_printf (string, "%lu", g_utf8_pointer_to_offset (end - end_bytes, end) + 1);
+        }
       /* Translators: the first %s is the program name, the second one
-       * is the URI of the file, the third is the error message.
+       * is the URI of the file, the third is the file location and the
+       * final one the error message.
        */
-      g_printerr (_("%s: %s: error parsing file: %s\n"),
-                  g_get_prgname (), uri, parser_error->message);
+      g_printerr (_("%s: %s: error parsing file: %s: %s\n"),
+                  g_get_prgname (), uri, string->str, parser_error->message);
+      g_string_free (string, TRUE);
       g_free (uri);
       gtk_json_parser_free (parser);
+      g_bytes_unref (bytes);
       return FALSE;
     }
 
   gtk_json_parser_free (parser);
+  g_bytes_unref (bytes);
 
   return TRUE;
 }
diff --git a/tools/gtk-json-validate.c b/tools/gtk-json-validate.c
index 73e94990e6..23c049631a 100644
--- a/tools/gtk-json-validate.c
+++ b/tools/gtk-json-validate.c
@@ -60,23 +60,52 @@ validate (GFile *file)
     }
 
   parser = gtk_json_parser_new_for_bytes (bytes);
-  g_bytes_unref (bytes);
 
   while (gtk_json_parser_next (parser));
 
   json_error = gtk_json_parser_get_error (parser);
   if (json_error)
     {
+      char *uri;
+      const char *data, *start, *end;
+      gsize start_line, start_bytes, start_offset, end_line, end_bytes, end_offset;
+      GString *string;
+
+      uri = g_file_get_uri (file);
+      data = g_bytes_get_data (bytes, NULL);
+      gtk_json_parser_get_error_offset (parser, &start_offset, &end_offset);
+      start = data + start_offset;
+      end = data + end_offset;
+      gtk_json_parser_get_error_location (parser,
+                                          &start_line, &start_bytes,
+                                          &end_line, &end_bytes);
+      
+      string = g_string_new (NULL);
+      g_string_append_printf (string, "%zu:%lu", 
+                              start_line + 1,
+                              g_utf8_pointer_to_offset (start - start_bytes, start) + 1);
+      if (start_line != end_line || start_bytes != end_bytes)
+        {
+          g_string_append (string, "-");
+          if (start_line != end_line)
+            g_string_append_printf (string, "%zu:", end_line + 1);
+          g_string_append_printf (string, "%lu", g_utf8_pointer_to_offset (end - end_bytes, end) + 1);
+        }
       /* Translators: the first %s is the program name, the second one
-       * is the URI of the file, the third is the error message.
+       * is the URI of the file, the third is the file location and the
+       * final one the error message.
        */
-      g_printerr (_("%s: %s: error parsing file: %s\n"),
-                  g_get_prgname (), g_file_get_uri (file), json_error->message);
+      g_printerr (_("%s: %s: error parsing file: %s: %s\n"),
+                  g_get_prgname (), uri, string->str, json_error->message);
+      g_string_free (string, TRUE);
+      g_free (uri);
       gtk_json_parser_free (parser);
+      g_bytes_unref (bytes);
       return FALSE;
     }
 
   gtk_json_parser_free (parser);
+  g_bytes_unref (bytes);
 
   return TRUE;
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]