This is the mail archive of the
gcc-patches@gcc.gnu.org
mailing list for the GCC project.
Re: [PATCH] RFC: machine-readable diagnostic output (PR other/19165)
- From: Richard Biener <richard dot guenther at gmail dot com>
- To: David Malcolm <dmalcolm at redhat dot com>
- Cc: GCC Patches <gcc-patches at gcc dot gnu dot org>
- Date: Tue, 13 Nov 2018 15:46:18 +0100
- Subject: Re: [PATCH] RFC: machine-readable diagnostic output (PR other/19165)
- References: <1542095880-19037-1-git-send-email-dmalcolm@redhat.com>
On Tue, Nov 13, 2018 at 8:58 AM David Malcolm <dmalcolm@redhat.com> wrote:
>
> This patch implements a -fdiagnostics-format=json option which
> converts the diagnostics to be output to stderr in a JSON format;
> see the documentation in invoke.texi.
>
> Logically-related diagnostics are nested at the JSON level, using
> the auto_diagnostic_group mechanism.
LGTM if people really want it.
Richard.
> gcc/ChangeLog:
> PR other/19165
> * Makefile.in (OBJS): Move json.o to...
> (OBJS-libcommon): ...here and add diagnostic-format-json.o.
> * common.opt (fdiagnostics-format=): New option.
> (diagnostics_output_format): New enum.
> * diagnostic-format-json.cc: New file.
> * diagnostic.c (default_diagnostic_final_cb): New function, taken
> from start of diagnostic_finish.
> (diagnostic_initialize): Initialize final_cb to
> default_diagnostic_final_cb.
> (diagnostic_finish): Move "being treated as errors" messages to
> default_diagnostic_final_cb. Call any final_cb.
> * diagnostic.h (enum diagnostics_output_format): New enum.
> (struct diagnostic_context): Add "final_cb".
> (diagnostic_output_format_init): New decl.
> * doc/invoke.texi (-fdiagnostics-format): New option.
> * dwarf2out.c (gen_producer_string): Ignore
> OPT_fdiagnostics_format_.
> * gcc.c (driver_handle_option): Handle OPT_fdiagnostics_format_.
> * lto-wrapper.c (append_diag_options): Ignore it.
> * opts.c (common_handle_option): Handle it.
> ---
> gcc/Makefile.in | 2 +-
> gcc/common.opt | 17 +++
> gcc/diagnostic-format-json.cc | 265 ++++++++++++++++++++++++++++++++++++++++++
> gcc/diagnostic.c | 40 ++++---
> gcc/diagnostic.h | 16 +++
> gcc/doc/invoke.texi | 78 +++++++++++++
> gcc/dwarf2out.c | 1 +
> gcc/gcc.c | 5 +
> gcc/lto-wrapper.c | 1 +
> gcc/opts.c | 5 +
> 10 files changed, 414 insertions(+), 16 deletions(-)
> create mode 100644 gcc/diagnostic-format-json.cc
>
> diff --git a/gcc/Makefile.in b/gcc/Makefile.in
> index 16c9ed6..9534d59 100644
> --- a/gcc/Makefile.in
> +++ b/gcc/Makefile.in
> @@ -1395,7 +1395,6 @@ OBJS = \
> ira-color.o \
> ira-emit.o \
> ira-lives.o \
> - json.o \
> jump.o \
> langhooks.o \
> lcm.o \
> @@ -1619,6 +1618,7 @@ OBJS = \
> # Objects in libcommon.a, potentially used by all host binaries and with
> # no target dependencies.
> OBJS-libcommon = diagnostic.o diagnostic-color.o diagnostic-show-locus.o \
> + diagnostic-format-json.o json.o \
> edit-context.o \
> pretty-print.o intl.o \
> sbitmap.o \
> diff --git a/gcc/common.opt b/gcc/common.opt
> index 5a5d332..2f669f6 100644
> --- a/gcc/common.opt
> +++ b/gcc/common.opt
> @@ -1273,6 +1273,23 @@ Enum(diagnostic_color_rule) String(always) Value(DIAGNOSTICS_COLOR_YES)
> EnumValue
> Enum(diagnostic_color_rule) String(auto) Value(DIAGNOSTICS_COLOR_AUTO)
>
> +fdiagnostics-format=
> +Common Joined RejectNegative Enum(diagnostics_output_format)
> +-fdiagnostics-format=[text|json] Select output format
> +
> +; Required for these enum values.
> +SourceInclude
> +diagnostic.h
> +
> +Enum
> +Name(diagnostics_output_format) Type(int)
> +
> +EnumValue
> +Enum(diagnostics_output_format) String(text) Value(DIAGNOSTICS_OUTPUT_FORMAT_TEXT)
> +
> +EnumValue
> +Enum(diagnostics_output_format) String(json) Value(DIAGNOSTICS_OUTPUT_FORMAT_JSON)
> +
> fdiagnostics-parseable-fixits
> Common Var(flag_diagnostics_parseable_fixits)
> Print fix-it hints in machine-readable form.
> diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-json.cc
> new file mode 100644
> index 0000000..7860696
> --- /dev/null
> +++ b/gcc/diagnostic-format-json.cc
> @@ -0,0 +1,265 @@
> +/* JSON output for diagnostics
> + Copyright (C) 2018 Free Software Foundation, Inc.
> + Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +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 "diagnostic.h"
> +#include "json.h"
> +
> +/* The top-level JSON array of pending diagnostics. */
> +
> +static json::array *toplevel_array;
> +
> +/* The JSON object for the current diagnostic group. */
> +
> +static json::object *cur_group;
> +
> +/* The JSON array for the "children" array within the current diagnostic
> + group. */
> +
> +static json::array *cur_children_array;
> +
> +/* Generate a JSON object for LOC. */
> +
> +static json::object *
> +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));
> + result->set ("line", new json::number (exploc.line));
> + result->set ("column", new json::number (exploc.column));
> + return result;
> +}
> +
> +/* Generate a JSON object for LOC_RANGE. */
> +
> +static json::object *
> +json_from_location_range (const location_range *loc_range, unsigned range_idx)
> +{
> + location_t caret_loc = get_pure_location (loc_range->m_loc);
> +
> + if (caret_loc == UNKNOWN_LOCATION)
> + return NULL;
> +
> + location_t start_loc = get_start (loc_range->m_loc);
> + location_t finish_loc = get_finish (loc_range->m_loc);
> +
> + json::object *result = new json::object ();
> + result->set ("caret", json_from_expanded_location (caret_loc));
> + if (start_loc != caret_loc)
> + result->set ("start", json_from_expanded_location (start_loc));
> + if (finish_loc != caret_loc)
> + result->set ("finish", json_from_expanded_location (finish_loc));
> +
> + if (loc_range->m_label)
> + {
> + label_text text;
> + text = loc_range->m_label->get_text (range_idx);
> + if (text.m_buffer)
> + result->set ("label", new json::string (text.m_buffer));
> + text.maybe_free ();
> + }
> +
> + return result;
> +}
> +
> +/* Generate a JSON object for HINT. */
> +
> +static json::object *
> +json_from_fixit_hint (const fixit_hint *hint)
> +{
> + json::object *fixit_obj = new json::object ();
> +
> + location_t start_loc = hint->get_start_loc ();
> + fixit_obj->set ("start", json_from_expanded_location (start_loc));
> + location_t next_loc = hint->get_next_loc ();
> + fixit_obj->set ("next", json_from_expanded_location (next_loc));
> + fixit_obj->set ("string", new json::string (hint->get_string ()));
> +
> + return fixit_obj;
> +}
> +
> +/* No-op implementation of "begin_diagnostic" for JSON output. */
> +
> +static void
> +json_begin_diagnostic (diagnostic_context *, diagnostic_info *)
> +{
> +}
> +
> +/* Implementation of "end_diagnostic" for JSON output.
> + Generate a JSON object for DIAGNOSTIC, and store for output
> + within current diagnostic group. */
> +
> +static void
> +json_end_diagnostic (diagnostic_context *context, diagnostic_info *diagnostic)
> +{
> + json::object *diag_obj = new json::object ();
> +
> + /* Get "kind" of diagnostic. */
> + {
> + static const char *const diagnostic_kind_text[] = {
> +#define DEFINE_DIAGNOSTIC_KIND(K, T, C) (T),
> +#include "diagnostic.def"
> +#undef DEFINE_DIAGNOSTIC_KIND
> + "must-not-happen"
> + };
> + /* Lose the trailing ": ". */
> + const char *kind_text = diagnostic_kind_text[diagnostic->kind];
> + size_t len = strlen (kind_text);
> + gcc_assert (len > 2);
> + gcc_assert (kind_text[len - 2] == ':');
> + gcc_assert (kind_text[len - 1] == ' ');
> + char *rstrip = xstrdup (kind_text);
> + rstrip[len - 2] = '\0';
> + diag_obj->set ("kind", new json::string (rstrip));
> + free (rstrip);
> + }
> +
> + // FIXME: encoding of the message (json::string requires UTF-8)
> + diag_obj->set ("message",
> + new json::string (pp_formatted_text (context->printer)));
> + pp_clear_output_area (context->printer);
> +
> + // FIXME: get this from inside diagnostic_report_diagnostic
> + diagnostic_t orig_diag_kind = diagnostic->kind;
> + char *option_text;
> + option_text = context->option_name (context, diagnostic->option_index,
> + orig_diag_kind, diagnostic->kind);
> + if (option_text)
> + {
> + diag_obj->set ("option", new json::string (option_text));
> + free (option_text);
> + }
> +
> + /* If we've already emitted a diagnostic within this auto_diagnostic_group,
> + then add diag_obj to its "children" array. */
> + if (cur_group)
> + {
> + gcc_assert (cur_children_array);
> + cur_children_array->append (diag_obj);
> + }
> + else
> + {
> + /* Otherwise, make diag_obj be the top-level object within the group;
> + add a "children" array. */
> + toplevel_array->append (diag_obj);
> + cur_group = diag_obj;
> + cur_children_array = new json::array ();
> + diag_obj->set ("children", cur_children_array);
> + }
> +
> + const rich_location *richloc = diagnostic->richloc;
> +
> + json::array *loc_array = new json::array ();
> + diag_obj->set ("locations", loc_array);
> +
> + for (unsigned int i = 0; i < richloc->get_num_locations (); i++)
> + {
> + const location_range *loc_range = richloc->get_range (i);
> + json::object *loc_obj = json_from_location_range (loc_range, i);
> + if (loc_obj)
> + loc_array->append (loc_obj);
> + }
> +
> + if (richloc->get_num_fixit_hints ())
> + {
> + json::array *fixit_array = new json::array ();
> + diag_obj->set ("fixits", fixit_array);
> + for (unsigned int i = 0; i < richloc->get_num_fixit_hints (); i++)
> + {
> + const fixit_hint *hint = richloc->get_fixit_hint (i);
> + json::object *fixit_obj = json_from_fixit_hint (hint);
> + fixit_array->append (fixit_obj);
> + }
> + }
> +
> + /* TODO: tree-ish things:
> + TODO: functions
> + TODO: inlining information
> + TODO: macro expansion information. */
> +}
> +
> +/* No-op implementation of "begin_group_cb" for JSON output. */
> +
> +static void
> +json_begin_group (diagnostic_context *)
> +{
> +}
> +
> +/* Implementation of "end_group_cb" for JSON output. */
> +
> +static void
> +json_end_group (diagnostic_context *)
> +{
> + cur_group = NULL;
> + cur_children_array = NULL;
> +}
> +
> +/* Callback for final cleanup for JSON output. */
> +
> +static void
> +json_final_cb (diagnostic_context *)
> +{
> + /* Flush the top-level array. */
> + toplevel_array->dump (stderr);
> + fprintf (stderr, "\n");
> + delete toplevel_array;
> + toplevel_array = NULL;
> +}
> +
> +/* Set the output format for CONTEXT to FORMAT. */
> +
> +void
> +diagnostic_output_format_init (diagnostic_context *context,
> + enum diagnostics_output_format format)
> +{
> + switch (format)
> + {
> + default:
> + gcc_unreachable ();
> + case DIAGNOSTICS_OUTPUT_FORMAT_TEXT:
> + /* The default; do nothing. */
> + break;
> +
> + case DIAGNOSTICS_OUTPUT_FORMAT_JSON:
> + {
> + /* Set up top-level JSON array. */
> + if (toplevel_array == NULL)
> + toplevel_array = new json::array ();
> +
> + /* Override callbacks. */
> + context->begin_diagnostic = json_begin_diagnostic;
> + context->end_diagnostic = json_end_diagnostic;
> + context->begin_group_cb = json_begin_group;
> + context->end_group_cb = json_end_group;
> + context->final_cb = json_final_cb;
> +
> + /* The option is handled in JSON format, rather than as text. */
> + context->show_option_requested = false;
> +
> + /* Don't colorize the text. */
> + pp_show_color (context->printer) = false;
> + }
> + break;
> + }
> +}
> diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
> index 3d86a21..96d170d 100644
> --- a/gcc/diagnostic.c
> +++ b/gcc/diagnostic.c
> @@ -131,6 +131,28 @@ diagnostic_set_caret_max_width (diagnostic_context *context, int value)
> context->caret_max_width = value;
> }
>
> +/* Default implementation of final_cb. */
> +
> +static void
> +default_diagnostic_final_cb (diagnostic_context *context)
> +{
> + /* Some of the errors may actually have been warnings. */
> + if (diagnostic_kind_count (context, DK_WERROR))
> + {
> + /* -Werror was given. */
> + if (context->warning_as_error_requested)
> + pp_verbatim (context->printer,
> + _("%s: all warnings being treated as errors"),
> + progname);
> + /* At least one -Werror= was given. */
> + else
> + pp_verbatim (context->printer,
> + _("%s: some warnings being treated as errors"),
> + progname);
> + pp_newline_and_flush (context->printer);
> + }
> +}
> +
> /* Initialize the diagnostic message outputting machinery. */
> void
> diagnostic_initialize (diagnostic_context *context, int n_opts)
> @@ -185,6 +207,7 @@ diagnostic_initialize (diagnostic_context *context, int n_opts)
> context->diagnostic_group_emission_count = 0;
> context->begin_group_cb = NULL;
> context->end_group_cb = NULL;
> + context->final_cb = default_diagnostic_final_cb;
> }
>
> /* Maybe initialize the color support. We require clients to do this
> @@ -220,21 +243,8 @@ diagnostic_color_init (diagnostic_context *context, int value /*= -1 */)
> void
> diagnostic_finish (diagnostic_context *context)
> {
> - /* Some of the errors may actually have been warnings. */
> - if (diagnostic_kind_count (context, DK_WERROR))
> - {
> - /* -Werror was given. */
> - if (context->warning_as_error_requested)
> - pp_verbatim (context->printer,
> - _("%s: all warnings being treated as errors"),
> - progname);
> - /* At least one -Werror= was given. */
> - else
> - pp_verbatim (context->printer,
> - _("%s: some warnings being treated as errors"),
> - progname);
> - pp_newline_and_flush (context->printer);
> - }
> + if (context->final_cb)
> + context->final_cb (context);
>
> diagnostic_file_cache_fini ();
>
> diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
> index 3498a9b..5fa1902 100644
> --- a/gcc/diagnostic.h
> +++ b/gcc/diagnostic.h
> @@ -24,6 +24,17 @@ along with GCC; see the file COPYING3. If not see
> #include "pretty-print.h"
> #include "diagnostic-core.h"
>
> +/* Enum for overriding the standard output format. */
> +
> +enum diagnostics_output_format
> +{
> + /* The default: textual output. */
> + DIAGNOSTICS_OUTPUT_FORMAT_TEXT,
> +
> + /* JSON-based output. */
> + DIAGNOSTICS_OUTPUT_FORMAT_JSON
> +};
> +
> /* A diagnostic is described by the MESSAGE to send, the FILE and LINE of
> its context and its KIND (ice, error, warning, note, ...) See complete
> list in diagnostic.def. */
> @@ -243,6 +254,9 @@ struct diagnostic_context
> /* If non-NULL, this will be called when a stack of groups is
> popped if any diagnostics were emitted within that group. */
> void (*end_group_cb) (diagnostic_context * context);
> +
> + /* Callback for final cleanup. */
> + void (*final_cb) (diagnostic_context *context);
> };
>
> static inline void
> @@ -401,5 +415,7 @@ extern char *file_name_as_prefix (diagnostic_context *, const char *);
>
> extern char *build_message_string (const char *, ...) ATTRIBUTE_PRINTF_1;
>
> +extern void diagnostic_output_format_init (diagnostic_context *,
> + enum diagnostics_output_format);
>
> #endif /* ! GCC_DIAGNOSTIC_H */
> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> index f26ada0..885eff6 100644
> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -268,6 +268,7 @@ Objective-C and Objective-C++ Dialects}.
> @gccoptlist{-fmessage-length=@var{n} @gol
> -fdiagnostics-show-location=@r{[}once@r{|}every-line@r{]} @gol
> -fdiagnostics-color=@r{[}auto@r{|}never@r{|}always@r{]} @gol
> +-fdiagnostics-format=@r{[}text@r{|}json@r{]} @gol
> -fno-diagnostics-show-option -fno-diagnostics-show-caret @gol
> -fno-diagnostics-show-labels -fno-diagnostics-show-line-numbers @gol
> -fdiagnostics-minimum-margin-width=@var{width} @gol
> @@ -3948,6 +3949,83 @@ Do not print column numbers in diagnostics. This may be necessary if
> diagnostics are being scanned by a program that does not understand the
> column numbers, such as @command{dejagnu}.
>
> +@item -fdiagnostics-format=@var{FORMAT}
> +@opindex fdiagnostics-format
> +Select a different format for printing diagnostics.
> +@var{FORMAT} is @samp{text} or @samp{json}.
> +The default is @samp{text}.
> +
> +The JSON format consists of a top-level JSON array containing JSON
> +objects representing the diagnostics, where diagnostics can have
> +child diagnostics.
> +
> +For example, this error and note:
> +
> +@smallexample
> +misleading-indentation.c:15:3: warning: this 'if' clause does not
> + guard... [-Wmisleading-indentation]
> + 15 | if (flag)
> + | ^~
> +misleading-indentation.c:17:5: note: ...this statement, but the latter
> + is misleadingly indented as if it were guarded by the 'if'
> + 17 | y = 2;
> + | ^
> +@end smallexample
> +
> +@noindent
> +might be printed in JSON form like this:
> +
> +@smallexample
> +[
> + @{
> + "children": [
> + @{
> + "kind": "note",
> + "locations": [
> + @{
> + "caret": @{
> + "column": 5,
> + "file": "misleading-indentation.c",
> + "line": 17
> + @}
> + @}
> + ],
> + "message": "...this statement, but the latter is misleadingly indented as if it were guarded by the \u2018if\u2019"
> + @}
> + ],
> + "kind": "warning",
> + "locations": [
> + @{
> + "caret": @{
> + "column": 3,
> + "file": "misleading-indentation.c",
> + "line": 15
> + @},
> + "finish": @{
> + "column": 4,
> + "file": "misleading-indentation.c",
> + "line": 15
> + @}
> + @}
> + ],
> + "message": "this \u2018if\u2019 clause does not guard...",
> + "option": "-Wmisleading-indentation"
> + @},
> + @dots{}
> +]
> +@end smallexample
> +
> +@noindent
> +where the @code{note} is a child of the @code{warning}.
> +
> +A diagnostic has a @code{kind}. If this is @code{warning}, then there is
> +an @code{option} key describing the command-line option controlling the
> +warning.
> +
> +A diagnostic can contain zero or more locations. Each location has up
> +to three positions within it: a @code{caret} position, and, optional
> +@code{start} and @code{finish} positions.
> +
> @end table
>
> @node Warning Options
> diff --git a/gcc/dwarf2out.c b/gcc/dwarf2out.c
> index 8b478aa..4fb0527 100644
> --- a/gcc/dwarf2out.c
> +++ b/gcc/dwarf2out.c
> @@ -24267,6 +24267,7 @@ gen_producer_string (void)
> case OPT_fdiagnostics_show_labels:
> case OPT_fdiagnostics_show_line_numbers:
> case OPT_fdiagnostics_color_:
> + case OPT_fdiagnostics_format_:
> case OPT_fverbose_asm:
> case OPT____:
> case OPT__sysroot_:
> diff --git a/gcc/gcc.c b/gcc/gcc.c
> index c0d17eb..afc18b3 100644
> --- a/gcc/gcc.c
> +++ b/gcc/gcc.c
> @@ -4001,6 +4001,11 @@ driver_handle_option (struct gcc_options *opts,
> diagnostic_color_init (dc, value);
> break;
>
> + case OPT_fdiagnostics_format_:
> + diagnostic_output_format_init (dc,
> + (enum diagnostics_output_format)value);
> + break;
> +
> case OPT_Wa_:
> {
> int prev, j;
> diff --git a/gcc/lto-wrapper.c b/gcc/lto-wrapper.c
> index 2b9d47e..90d8c1c 100644
> --- a/gcc/lto-wrapper.c
> +++ b/gcc/lto-wrapper.c
> @@ -646,6 +646,7 @@ append_diag_options (obstack *argv_obstack, struct cl_decoded_option *opts,
> switch (option->opt_index)
> {
> case OPT_fdiagnostics_color_:
> + case OPT_fdiagnostics_format_:
> case OPT_fdiagnostics_show_caret:
> case OPT_fdiagnostics_show_labels:
> case OPT_fdiagnostics_show_line_numbers:
> diff --git a/gcc/opts.c b/gcc/opts.c
> index 34c283d..514e7f6 100644
> --- a/gcc/opts.c
> +++ b/gcc/opts.c
> @@ -2220,6 +2220,11 @@ common_handle_option (struct gcc_options *opts,
> diagnostic_color_init (dc, value);
> break;
>
> + case OPT_fdiagnostics_format_:
> + diagnostic_output_format_init (dc,
> + (enum diagnostics_output_format)value);
> + break;
> +
> case OPT_fdiagnostics_parseable_fixits:
> dc->parseable_fixits_p = value;
> break;
> --
> 1.8.5.3
>