1 /* JSON output for diagnostics
2 Copyright (C) 2018-2023 Free Software Foundation, Inc.
3 Contributed by David Malcolm <dmalcolm@redhat.com>.
5 This file is part of GCC.
7 GCC is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 3, or (at your option) any later
12 GCC is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 You should have received a copy of the GNU General Public License
18 along with GCC; see the file COPYING3. If not see
19 <http://www.gnu.org/licenses/>. */
24 #include "coretypes.h"
25 #include "diagnostic.h"
26 #include "selftest-diagnostic.h"
27 #include "diagnostic-metadata.h"
31 /* The top-level JSON array of pending diagnostics. */
33 static json::array
*toplevel_array
;
35 /* The JSON object for the current diagnostic group. */
37 static json::object
*cur_group
;
39 /* The JSON array for the "children" array within the current diagnostic
42 static json::array
*cur_children_array
;
44 /* Generate a JSON object for LOC. */
47 json_from_expanded_location (diagnostic_context
*context
, location_t loc
)
49 expanded_location exploc
= expand_location (loc
);
50 json::object
*result
= new json::object ();
52 result
->set ("file", new json::string (exploc
.file
));
53 result
->set ("line", new json::integer_number (exploc
.line
));
55 const enum diagnostics_column_unit orig_unit
= context
->column_unit
;
59 enum diagnostics_column_unit unit
;
61 {"display-column", DIAGNOSTICS_COLUMN_UNIT_DISPLAY
},
62 {"byte-column", DIAGNOSTICS_COLUMN_UNIT_BYTE
}
64 int the_column
= INT_MIN
;
65 for (int i
= 0; i
!= ARRAY_SIZE (column_fields
); ++i
)
67 context
->column_unit
= column_fields
[i
].unit
;
68 const int col
= diagnostic_converted_column (context
, exploc
);
69 result
->set (column_fields
[i
].name
, new json::integer_number (col
));
70 if (column_fields
[i
].unit
== orig_unit
)
73 gcc_assert (the_column
!= INT_MIN
);
74 result
->set ("column", new json::integer_number (the_column
));
75 context
->column_unit
= orig_unit
;
79 /* Generate a JSON object for LOC_RANGE. */
82 json_from_location_range (diagnostic_context
*context
,
83 const location_range
*loc_range
, unsigned range_idx
)
85 location_t caret_loc
= get_pure_location (loc_range
->m_loc
);
87 if (caret_loc
== UNKNOWN_LOCATION
)
90 location_t start_loc
= get_start (loc_range
->m_loc
);
91 location_t finish_loc
= get_finish (loc_range
->m_loc
);
93 json::object
*result
= new json::object ();
94 result
->set ("caret", json_from_expanded_location (context
, caret_loc
));
95 if (start_loc
!= caret_loc
96 && start_loc
!= UNKNOWN_LOCATION
)
97 result
->set ("start", json_from_expanded_location (context
, start_loc
));
98 if (finish_loc
!= caret_loc
99 && finish_loc
!= UNKNOWN_LOCATION
)
100 result
->set ("finish", json_from_expanded_location (context
, finish_loc
));
102 if (loc_range
->m_label
)
104 label_text
text (loc_range
->m_label
->get_text (range_idx
));
106 result
->set ("label", new json::string (text
.get ()));
112 /* Generate a JSON object for HINT. */
114 static json::object
*
115 json_from_fixit_hint (diagnostic_context
*context
, const fixit_hint
*hint
)
117 json::object
*fixit_obj
= new json::object ();
119 location_t start_loc
= hint
->get_start_loc ();
120 fixit_obj
->set ("start", json_from_expanded_location (context
, start_loc
));
121 location_t next_loc
= hint
->get_next_loc ();
122 fixit_obj
->set ("next", json_from_expanded_location (context
, next_loc
));
123 fixit_obj
->set ("string", new json::string (hint
->get_string ()));
128 /* Generate a JSON object for METADATA. */
130 static json::object
*
131 json_from_metadata (const diagnostic_metadata
*metadata
)
133 json::object
*metadata_obj
= new json::object ();
135 if (metadata
->get_cwe ())
136 metadata_obj
->set ("cwe",
137 new json::integer_number (metadata
->get_cwe ()));
142 /* No-op implementation of "begin_diagnostic" for JSON output. */
145 json_begin_diagnostic (diagnostic_context
*, diagnostic_info
*)
149 /* Implementation of "end_diagnostic" for JSON output.
150 Generate a JSON object for DIAGNOSTIC, and store for output
151 within current diagnostic group. */
154 json_end_diagnostic (diagnostic_context
*context
, diagnostic_info
*diagnostic
,
155 diagnostic_t orig_diag_kind
)
157 json::object
*diag_obj
= new json::object ();
159 /* Get "kind" of diagnostic. */
161 static const char *const diagnostic_kind_text
[] = {
162 #define DEFINE_DIAGNOSTIC_KIND(K, T, C) (T),
163 #include "diagnostic.def"
164 #undef DEFINE_DIAGNOSTIC_KIND
167 /* Lose the trailing ": ". */
168 const char *kind_text
= diagnostic_kind_text
[diagnostic
->kind
];
169 size_t len
= strlen (kind_text
);
170 gcc_assert (len
> 2);
171 gcc_assert (kind_text
[len
- 2] == ':');
172 gcc_assert (kind_text
[len
- 1] == ' ');
173 char *rstrip
= xstrdup (kind_text
);
174 rstrip
[len
- 2] = '\0';
175 diag_obj
->set ("kind", new json::string (rstrip
));
179 // FIXME: encoding of the message (json::string requires UTF-8)
180 diag_obj
->set ("message",
181 new json::string (pp_formatted_text (context
->printer
)));
182 pp_clear_output_area (context
->printer
);
185 option_text
= context
->option_name (context
, diagnostic
->option_index
,
186 orig_diag_kind
, diagnostic
->kind
);
189 diag_obj
->set ("option", new json::string (option_text
));
193 if (context
->get_option_url
)
195 char *option_url
= context
->get_option_url (context
,
196 diagnostic
->option_index
);
199 diag_obj
->set ("option_url", new json::string (option_url
));
204 /* If we've already emitted a diagnostic within this auto_diagnostic_group,
205 then add diag_obj to its "children" array. */
208 gcc_assert (cur_children_array
);
209 cur_children_array
->append (diag_obj
);
213 /* Otherwise, make diag_obj be the top-level object within the group;
214 add a "children" array and record the column origin. */
215 toplevel_array
->append (diag_obj
);
216 cur_group
= diag_obj
;
217 cur_children_array
= new json::array ();
218 diag_obj
->set ("children", cur_children_array
);
219 diag_obj
->set ("column-origin",
220 new json::integer_number (context
->column_origin
));
223 const rich_location
*richloc
= diagnostic
->richloc
;
225 json::array
*loc_array
= new json::array ();
226 diag_obj
->set ("locations", loc_array
);
228 for (unsigned int i
= 0; i
< richloc
->get_num_locations (); i
++)
230 const location_range
*loc_range
= richloc
->get_range (i
);
231 json::object
*loc_obj
= json_from_location_range (context
, loc_range
, i
);
233 loc_array
->append (loc_obj
);
236 if (richloc
->get_num_fixit_hints ())
238 json::array
*fixit_array
= new json::array ();
239 diag_obj
->set ("fixits", fixit_array
);
240 for (unsigned int i
= 0; i
< richloc
->get_num_fixit_hints (); i
++)
242 const fixit_hint
*hint
= richloc
->get_fixit_hint (i
);
243 json::object
*fixit_obj
= json_from_fixit_hint (context
, hint
);
244 fixit_array
->append (fixit_obj
);
248 /* TODO: tree-ish things:
250 TODO: inlining information
251 TODO: macro expansion information. */
253 if (diagnostic
->metadata
)
255 json::object
*metadata_obj
= json_from_metadata (diagnostic
->metadata
);
256 diag_obj
->set ("metadata", metadata_obj
);
259 const diagnostic_path
*path
= richloc
->get_path ();
260 if (path
&& context
->make_json_for_path
)
262 json::value
*path_value
= context
->make_json_for_path (context
, path
);
263 diag_obj
->set ("path", path_value
);
266 diag_obj
->set ("escape-source",
267 new json::literal (richloc
->escape_on_output_p ()));
270 /* No-op implementation of "begin_group_cb" for JSON output. */
273 json_begin_group (diagnostic_context
*)
277 /* Implementation of "end_group_cb" for JSON output. */
280 json_end_group (diagnostic_context
*)
283 cur_children_array
= NULL
;
286 /* Flush the top-level array to OUTF. */
289 json_flush_to_file (FILE *outf
)
291 toplevel_array
->dump (outf
);
292 fprintf (outf
, "\n");
293 delete toplevel_array
;
294 toplevel_array
= NULL
;
297 /* Callback for final cleanup for JSON output to stderr. */
300 json_stderr_final_cb (diagnostic_context
*)
302 json_flush_to_file (stderr
);
305 static char *json_output_base_file_name
;
307 /* Callback for final cleanup for JSON output to a file. */
310 json_file_final_cb (diagnostic_context
*)
312 char *filename
= concat (json_output_base_file_name
, ".gcc.json", NULL
);
313 FILE *outf
= fopen (filename
, "w");
316 const char *errstr
= xstrerror (errno
);
317 fnotice (stderr
, "error: unable to open '%s' for writing: %s\n",
322 json_flush_to_file (outf
);
327 /* Callback for diagnostic_context::m_diagrams.m_emission_cb. */
330 json_emit_diagram (diagnostic_context
*,
331 const diagnostic_diagram
&)
336 /* Populate CONTEXT in preparation for JSON output (either to stderr, or
340 diagnostic_output_format_init_json (diagnostic_context
*context
)
342 /* Set up top-level JSON array. */
343 if (toplevel_array
== NULL
)
344 toplevel_array
= new json::array ();
346 /* Override callbacks. */
347 context
->begin_diagnostic
= json_begin_diagnostic
;
348 context
->end_diagnostic
= json_end_diagnostic
;
349 context
->begin_group_cb
= json_begin_group
;
350 context
->end_group_cb
= json_end_group
;
351 context
->print_path
= NULL
; /* handled in json_end_diagnostic. */
352 context
->m_diagrams
.m_emission_cb
= json_emit_diagram
;
354 /* The metadata is handled in JSON format, rather than as text. */
355 context
->show_cwe
= false;
356 context
->show_rules
= false;
358 /* The option is handled in JSON format, rather than as text. */
359 context
->show_option_requested
= false;
361 /* Don't colorize the text. */
362 pp_show_color (context
->printer
) = false;
365 /* Populate CONTEXT in preparation for JSON output to stderr. */
368 diagnostic_output_format_init_json_stderr (diagnostic_context
*context
)
370 diagnostic_output_format_init_json (context
);
371 context
->final_cb
= json_stderr_final_cb
;
374 /* Populate CONTEXT in preparation for JSON output to a file named
375 BASE_FILE_NAME.gcc.json. */
378 diagnostic_output_format_init_json_file (diagnostic_context
*context
,
379 const char *base_file_name
)
381 diagnostic_output_format_init_json (context
);
382 context
->final_cb
= json_file_final_cb
;
383 json_output_base_file_name
= xstrdup (base_file_name
);
390 /* We shouldn't call json_from_expanded_location on UNKNOWN_LOCATION,
391 but verify that we handle this gracefully. */
394 test_unknown_location ()
396 test_diagnostic_context dc
;
397 delete json_from_expanded_location (&dc
, UNKNOWN_LOCATION
);
400 /* Verify that we gracefully handle attempts to serialize bad
401 compound locations. */
404 test_bad_endpoints ()
406 location_t bad_endpoints
407 = make_location (BUILTINS_LOCATION
,
408 UNKNOWN_LOCATION
, UNKNOWN_LOCATION
);
410 location_range loc_range
;
411 loc_range
.m_loc
= bad_endpoints
;
412 loc_range
.m_range_display_kind
= SHOW_RANGE_WITH_CARET
;
413 loc_range
.m_label
= NULL
;
415 test_diagnostic_context dc
;
416 json::object
*obj
= json_from_location_range (&dc
, &loc_range
, 0);
417 /* We should have a "caret" value, but no "start" or "finish" values. */
418 ASSERT_TRUE (obj
!= NULL
);
419 ASSERT_TRUE (obj
->get ("caret") != NULL
);
420 ASSERT_TRUE (obj
->get ("start") == NULL
);
421 ASSERT_TRUE (obj
->get ("finish") == NULL
);
425 /* Run all of the selftests within this file. */
428 diagnostic_format_json_cc_tests ()
430 test_unknown_location ();
431 test_bad_endpoints ();
434 } // namespace selftest
436 #endif /* #if CHECKING_P */