Branch data Line data Source code
1 : : /* JSON output for diagnostics
2 : : Copyright (C) 2018-2025 Free Software Foundation, Inc.
3 : : Contributed by David Malcolm <dmalcolm@redhat.com>.
4 : :
5 : : This file is part of GCC.
6 : :
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
10 : : version.
11 : :
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
15 : : for more details.
16 : :
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/>. */
20 : :
21 : :
22 : : #include "config.h"
23 : : #define INCLUDE_VECTOR
24 : : #include "system.h"
25 : : #include "coretypes.h"
26 : : #include "diagnostic.h"
27 : : #include "selftest-diagnostic.h"
28 : : #include "diagnostic-metadata.h"
29 : : #include "diagnostic-path.h"
30 : : #include "diagnostic-format.h"
31 : : #include "diagnostic-buffer.h"
32 : : #include "json.h"
33 : : #include "selftest.h"
34 : : #include "logical-location.h"
35 : : #include "make-unique.h"
36 : :
37 : : class json_output_format;
38 : :
39 : : /* Concrete buffering implementation subclass for JSON output. */
40 : :
41 : : class diagnostic_json_format_buffer : public diagnostic_per_format_buffer
42 : : {
43 : : public:
44 : : friend class json_output_format;
45 : :
46 : 1 : diagnostic_json_format_buffer (json_output_format &format)
47 : 1 : : m_format (format)
48 : : {}
49 : :
50 : : void dump (FILE *out, int indent) const final override;
51 : : bool empty_p () const final override;
52 : : void move_to (diagnostic_per_format_buffer &dest) final override;
53 : : void clear () final override;
54 : : void flush () final override;
55 : :
56 : : private:
57 : : json_output_format &m_format;
58 : : std::vector<std::unique_ptr<json::object>> m_results;
59 : : };
60 : :
61 : : /* Subclass of diagnostic_output_format for JSON output. */
62 : :
63 : : class json_output_format : public diagnostic_output_format
64 : : {
65 : : public:
66 : : friend class diagnostic_json_format_buffer;
67 : :
68 : 0 : void dump (FILE *out, int indent) const override
69 : : {
70 : 0 : fprintf (out, "%*sjson_output_format\n", indent, "");
71 : 0 : diagnostic_output_format::dump (out, indent);
72 : 0 : }
73 : :
74 : : std::unique_ptr<diagnostic_per_format_buffer>
75 : 1 : make_per_format_buffer () final override
76 : : {
77 : 1 : return ::make_unique<diagnostic_json_format_buffer> (*this);
78 : : }
79 : 54 : void set_buffer (diagnostic_per_format_buffer *base_buffer) final override
80 : : {
81 : 54 : diagnostic_json_format_buffer *buffer
82 : : = static_cast<diagnostic_json_format_buffer *> (base_buffer);
83 : 54 : m_buffer = buffer;
84 : 54 : }
85 : :
86 : 52 : void on_begin_group () final override
87 : : {
88 : : /* No-op. */
89 : 52 : }
90 : 52 : void on_end_group () final override
91 : : {
92 : 52 : m_cur_group = nullptr;
93 : 52 : m_cur_children_array = nullptr;
94 : 52 : }
95 : : void
96 : : on_report_diagnostic (const diagnostic_info &diagnostic,
97 : : diagnostic_t orig_diag_kind) final override;
98 : 0 : void on_diagram (const diagnostic_diagram &) final override
99 : : {
100 : : /* No-op. */
101 : 0 : }
102 : 59 : void after_diagnostic (const diagnostic_info &) final override
103 : : {
104 : : /* No-op. */
105 : 59 : }
106 : 0 : void update_printer () final override
107 : : {
108 : 0 : m_printer = m_context.clone_printer ();
109 : 0 : pp_show_color (m_printer.get ()) = false;
110 : 0 : }
111 : 0 : bool follows_reference_printer_p () const final override
112 : : {
113 : 0 : return false;
114 : : }
115 : :
116 : : protected:
117 : 44 : json_output_format (diagnostic_context &context,
118 : : bool formatted)
119 : 44 : : diagnostic_output_format (context),
120 : 44 : m_buffer (nullptr),
121 : 44 : m_toplevel_array (::make_unique<json::array> ()),
122 : 44 : m_cur_group (nullptr),
123 : 44 : m_cur_children_array (nullptr),
124 : 44 : m_formatted (formatted)
125 : : {
126 : 44 : }
127 : :
128 : : /* Flush the top-level array to OUTF. */
129 : : void
130 : 44 : flush_to_file (FILE *outf)
131 : : {
132 : 44 : m_toplevel_array->dump (outf, m_formatted);
133 : 44 : fprintf (outf, "\n");
134 : 44 : m_toplevel_array = nullptr;
135 : 44 : }
136 : :
137 : : private:
138 : : diagnostic_json_format_buffer *m_buffer;
139 : :
140 : : /* The top-level JSON array of pending diagnostics. */
141 : : std::unique_ptr<json::array> m_toplevel_array;
142 : :
143 : : /* The JSON object for the current diagnostic group. */
144 : : json::object *m_cur_group; // borrowed
145 : :
146 : : /* The JSON array for the "children" array within the current diagnostic
147 : : group. */
148 : : json::array *m_cur_children_array; // borrowed
149 : :
150 : : bool m_formatted;
151 : : };
152 : :
153 : : /* Generate a JSON object for LOC. */
154 : :
155 : : static std::unique_ptr<json::object>
156 : 157 : json_from_expanded_location (diagnostic_context &context, location_t loc)
157 : : {
158 : 157 : expanded_location exploc = expand_location (loc);
159 : 157 : std::unique_ptr<json::object> result = ::make_unique <json::object> ();
160 : 157 : if (exploc.file)
161 : 153 : result->set_string ("file", exploc.file);
162 : 157 : result->set_integer ("line", exploc.line);
163 : :
164 : 157 : const enum diagnostics_column_unit orig_unit = context.m_column_unit;
165 : 157 : struct
166 : : {
167 : : const char *name;
168 : : enum diagnostics_column_unit unit;
169 : 157 : } column_fields[] = {
170 : : {"display-column", DIAGNOSTICS_COLUMN_UNIT_DISPLAY},
171 : : {"byte-column", DIAGNOSTICS_COLUMN_UNIT_BYTE}
172 : : };
173 : 157 : int the_column = INT_MIN;
174 : 471 : for (int i = 0; i != ARRAY_SIZE (column_fields); ++i)
175 : : {
176 : 314 : context.m_column_unit = column_fields[i].unit;
177 : 314 : diagnostic_column_policy col_policy (context);
178 : 314 : const int col = col_policy.converted_column (exploc);
179 : 314 : result->set_integer (column_fields[i].name, col);
180 : 314 : if (column_fields[i].unit == orig_unit)
181 : 157 : the_column = col;
182 : : }
183 : 157 : gcc_assert (the_column != INT_MIN);
184 : 157 : result->set_integer ("column", the_column);
185 : 157 : context.m_column_unit = orig_unit;
186 : 157 : return result;
187 : : }
188 : :
189 : : /* Generate a JSON object for LOC_RANGE. */
190 : :
191 : : static std::unique_ptr<json::object>
192 : 68 : json_from_location_range (diagnostic_context &context,
193 : : const location_range *loc_range, unsigned range_idx)
194 : : {
195 : 68 : location_t caret_loc = get_pure_location (loc_range->m_loc);
196 : :
197 : 68 : if (caret_loc == UNKNOWN_LOCATION)
198 : 0 : return nullptr;
199 : :
200 : 68 : location_t start_loc = get_start (loc_range->m_loc);
201 : 68 : location_t finish_loc = get_finish (loc_range->m_loc);
202 : :
203 : 68 : std::unique_ptr<json::object> result = ::make_unique <json::object> ();
204 : 68 : result->set ("caret",
205 : 68 : json_from_expanded_location (context, caret_loc));
206 : 68 : if (start_loc != caret_loc
207 : 68 : && start_loc != UNKNOWN_LOCATION)
208 : 12 : result->set ("start",
209 : 12 : json_from_expanded_location (context, start_loc));
210 : 68 : if (finish_loc != caret_loc
211 : 68 : && finish_loc != UNKNOWN_LOCATION)
212 : 50 : result->set ("finish",
213 : 50 : json_from_expanded_location (context, finish_loc));
214 : :
215 : 68 : if (loc_range->m_label)
216 : : {
217 : 0 : label_text text (loc_range->m_label->get_text (range_idx));
218 : 0 : if (text.get ())
219 : 0 : result->set_string ("label", text.get ());
220 : 0 : }
221 : :
222 : 68 : return result;
223 : 68 : }
224 : :
225 : : /* Generate a JSON object for HINT. */
226 : :
227 : : static std::unique_ptr<json::object>
228 : 6 : json_from_fixit_hint (diagnostic_context &context, const fixit_hint *hint)
229 : : {
230 : 6 : std::unique_ptr<json::object> fixit_obj = ::make_unique <json::object> ();
231 : :
232 : 6 : location_t start_loc = hint->get_start_loc ();
233 : 6 : fixit_obj->set ("start",
234 : 6 : json_from_expanded_location (context, start_loc));
235 : 6 : location_t next_loc = hint->get_next_loc ();
236 : 6 : fixit_obj->set ("next",
237 : 6 : json_from_expanded_location (context, next_loc). release ());
238 : 6 : fixit_obj->set_string ("string", hint->get_string ());
239 : :
240 : 6 : return fixit_obj;
241 : : }
242 : :
243 : : /* Generate a JSON object for METADATA. */
244 : :
245 : : static std::unique_ptr<json::object>
246 : 4 : json_from_metadata (const diagnostic_metadata *metadata)
247 : : {
248 : 4 : std::unique_ptr<json::object> metadata_obj = ::make_unique <json::object> ();
249 : :
250 : 4 : if (metadata->get_cwe ())
251 : 4 : metadata_obj->set_integer ("cwe", metadata->get_cwe ());
252 : :
253 : 4 : return metadata_obj;
254 : : }
255 : :
256 : : /* Make a JSON value for PATH. */
257 : :
258 : : static std::unique_ptr<json::array>
259 : 5 : make_json_for_path (diagnostic_context &context,
260 : : pretty_printer *ref_pp,
261 : : const diagnostic_path *path)
262 : : {
263 : 5 : std::unique_ptr<json::array> path_array = ::make_unique<json::array> ();
264 : 16 : for (unsigned i = 0; i < path->num_events (); i++)
265 : : {
266 : 11 : const diagnostic_event &event = path->get_event (i);
267 : :
268 : 11 : std::unique_ptr<json::object> event_obj = ::make_unique <json::object> ();
269 : 11 : if (event.get_location ())
270 : 11 : event_obj->set ("location",
271 : 11 : json_from_expanded_location (context,
272 : 11 : event.get_location ()));
273 : 11 : auto pp = ref_pp->clone ();
274 : 11 : event.print_desc (*pp.get ());
275 : 11 : event_obj->set_string ("description", pp_formatted_text (pp.get ()));
276 : 11 : if (const logical_location *logical_loc = event.get_logical_location ())
277 : : {
278 : 7 : label_text name (logical_loc->get_name_for_path_output ());
279 : 7 : event_obj->set_string ("function", name.get ());
280 : 7 : }
281 : 11 : event_obj->set_integer ("depth", event.get_stack_depth ());
282 : 11 : path_array->append (std::move (event_obj));
283 : 11 : }
284 : 5 : return path_array;
285 : : }
286 : :
287 : : /* class diagnostic_json_format_buffer : public diagnostic_per_format_buffer. */
288 : :
289 : : void
290 : 0 : diagnostic_json_format_buffer::dump (FILE *out, int indent) const
291 : : {
292 : 0 : fprintf (out, "%*sdiagnostic_json_format_buffer:\n", indent, "");
293 : 0 : int idx = 0;
294 : 0 : for (auto &result : m_results)
295 : : {
296 : 0 : fprintf (out, "%*sresult[%i]:\n", indent + 2, "", idx);
297 : 0 : result->dump (out, true);
298 : 0 : fprintf (out, "\n");
299 : 0 : ++idx;
300 : : }
301 : 0 : }
302 : :
303 : : bool
304 : 5 : diagnostic_json_format_buffer::empty_p () const
305 : : {
306 : 5 : return m_results.empty ();
307 : : }
308 : :
309 : : void
310 : 0 : diagnostic_json_format_buffer::move_to (diagnostic_per_format_buffer &base)
311 : : {
312 : 0 : diagnostic_json_format_buffer &dest
313 : : = static_cast<diagnostic_json_format_buffer &> (base);
314 : 0 : for (auto &&result : m_results)
315 : 0 : dest.m_results.push_back (std::move (result));
316 : 0 : m_results.clear ();
317 : 0 : }
318 : :
319 : : void
320 : 14 : diagnostic_json_format_buffer::clear ()
321 : : {
322 : 14 : m_results.clear ();
323 : 14 : }
324 : :
325 : : void
326 : 0 : diagnostic_json_format_buffer::flush ()
327 : : {
328 : 0 : for (auto &&result : m_results)
329 : 0 : m_format.m_toplevel_array->append (std::move (result));
330 : 0 : m_results.clear ();
331 : 0 : }
332 : :
333 : : /* Implementation of "on_report_diagnostic" vfunc for JSON output.
334 : : Generate a JSON object for DIAGNOSTIC, and store for output
335 : : within current diagnostic group. */
336 : :
337 : : void
338 : 64 : json_output_format::on_report_diagnostic (const diagnostic_info &diagnostic,
339 : : diagnostic_t orig_diag_kind)
340 : : {
341 : 64 : pretty_printer *const pp = get_printer ();
342 : 64 : pp_output_formatted_text (pp, m_context.get_urlifier ());
343 : :
344 : 64 : json::object *diag_obj = new json::object ();
345 : :
346 : : /* Get "kind" of diagnostic. */
347 : 64 : {
348 : : /* Lose the trailing ": ". */
349 : 64 : const char *kind_text = get_diagnostic_kind_text (diagnostic.kind);
350 : 64 : size_t len = strlen (kind_text);
351 : 64 : gcc_assert (len > 2);
352 : 64 : gcc_assert (kind_text[len - 2] == ':');
353 : 64 : gcc_assert (kind_text[len - 1] == ' ');
354 : 64 : char *rstrip = xstrdup (kind_text);
355 : 64 : rstrip[len - 2] = '\0';
356 : 64 : diag_obj->set_string ("kind", rstrip);
357 : 64 : free (rstrip);
358 : : }
359 : :
360 : : // FIXME: encoding of the message (json::string requires UTF-8)
361 : 64 : diag_obj->set_string ("message", pp_formatted_text (pp));
362 : 64 : pp_clear_output_area (pp);
363 : :
364 : 64 : if (char *option_text = m_context.make_option_name (diagnostic.option_id,
365 : : orig_diag_kind,
366 : 64 : diagnostic.kind))
367 : : {
368 : 25 : diag_obj->set_string ("option", option_text);
369 : 25 : free (option_text);
370 : : }
371 : :
372 : 64 : if (char *option_url = m_context.make_option_url (diagnostic.option_id))
373 : : {
374 : 25 : diag_obj->set_string ("option_url", option_url);
375 : 25 : free (option_url);
376 : : }
377 : :
378 : 64 : if (m_buffer)
379 : : {
380 : 5 : gcc_assert (!m_cur_group);
381 : 5 : m_buffer->m_results.push_back (std::unique_ptr<json::object> (diag_obj));
382 : : }
383 : : else
384 : : {
385 : : /* If we've already emitted a diagnostic within this auto_diagnostic_group,
386 : : then add diag_obj to its "children" array. */
387 : 59 : if (m_cur_group)
388 : : {
389 : 12 : gcc_assert (m_cur_children_array);
390 : 12 : m_cur_children_array->append (diag_obj);
391 : : }
392 : : else
393 : : {
394 : : /* Otherwise, make diag_obj be the top-level object within the group;
395 : : add a "children" array and record the column origin. */
396 : 47 : m_cur_group = diag_obj;
397 : 47 : std::unique_ptr<json::array> children_array
398 : 47 : = ::make_unique<json::array> ();
399 : 47 : m_cur_children_array = children_array.get (); // borrowed
400 : 47 : diag_obj->set ("children", std::move (children_array));
401 : 47 : diag_obj->set_integer ("column-origin", m_context.m_column_origin);
402 : 47 : m_toplevel_array->append (diag_obj);
403 : 47 : }
404 : : }
405 : :
406 : : /* diag_obj is now owned by either m_cur_children_array or
407 : : m_toplevel_array; further uses of diag_obj are borrowing it. */
408 : :
409 : 64 : const rich_location *richloc = diagnostic.richloc;
410 : :
411 : 64 : {
412 : 64 : std::unique_ptr<json::array> loc_array = ::make_unique<json::array> ();
413 : 128 : for (unsigned int i = 0; i < richloc->get_num_locations (); i++)
414 : : {
415 : 64 : const location_range *loc_range = richloc->get_range (i);
416 : 64 : if (std::unique_ptr<json::object> loc_obj
417 : 64 : = json_from_location_range (m_context, loc_range, i))
418 : 64 : loc_array->append (std::move (loc_obj));
419 : : }
420 : 64 : diag_obj->set ("locations", std::move (loc_array));
421 : 64 : }
422 : :
423 : 64 : if (richloc->get_num_fixit_hints ())
424 : : {
425 : 6 : std::unique_ptr<json::array> fixit_array = ::make_unique<json::array> ();
426 : 12 : for (unsigned int i = 0; i < richloc->get_num_fixit_hints (); i++)
427 : : {
428 : 6 : const fixit_hint *hint = richloc->get_fixit_hint (i);
429 : 6 : fixit_array->append (json_from_fixit_hint (m_context, hint));
430 : : }
431 : 6 : diag_obj->set ("fixits", std::move (fixit_array));
432 : 6 : }
433 : :
434 : : /* TODO: tree-ish things:
435 : : TODO: functions
436 : : TODO: inlining information
437 : : TODO: macro expansion information. */
438 : :
439 : 64 : if (diagnostic.metadata)
440 : 4 : diag_obj->set ("metadata", json_from_metadata (diagnostic.metadata));
441 : :
442 : 64 : const diagnostic_path *path = richloc->get_path ();
443 : 64 : if (path)
444 : 5 : diag_obj->set ("path", make_json_for_path (m_context, get_printer (), path));
445 : :
446 : 64 : diag_obj->set_bool ("escape-source", richloc->escape_on_output_p ());
447 : 64 : }
448 : :
449 : : class json_stderr_output_format : public json_output_format
450 : : {
451 : : public:
452 : 32 : json_stderr_output_format (diagnostic_context &context,
453 : : bool formatted)
454 : 32 : : json_output_format (context, formatted)
455 : : {
456 : : }
457 : 64 : ~json_stderr_output_format ()
458 : 32 : {
459 : 32 : flush_to_file (stderr);
460 : 64 : }
461 : 0 : bool machine_readable_stderr_p () const final override
462 : : {
463 : 0 : return true;
464 : : }
465 : : };
466 : :
467 : : class json_file_output_format : public json_output_format
468 : : {
469 : : public:
470 : 12 : json_file_output_format (diagnostic_context &context,
471 : : bool formatted,
472 : : const char *base_file_name)
473 : 12 : : json_output_format (context, formatted),
474 : 12 : m_base_file_name (xstrdup (base_file_name))
475 : : {
476 : 12 : }
477 : :
478 : 24 : ~json_file_output_format ()
479 : 12 : {
480 : 12 : char *filename = concat (m_base_file_name, ".gcc.json", nullptr);
481 : 12 : free (m_base_file_name);
482 : 12 : m_base_file_name = nullptr;
483 : 12 : FILE *outf = fopen (filename, "w");
484 : 12 : if (!outf)
485 : : {
486 : 0 : const char *errstr = xstrerror (errno);
487 : 0 : fnotice (stderr, "error: unable to open '%s' for writing: %s\n",
488 : : filename, errstr);
489 : 0 : free (filename);
490 : 0 : return;
491 : : }
492 : 12 : flush_to_file (outf);
493 : 12 : fclose (outf);
494 : 12 : free (filename);
495 : 24 : }
496 : 0 : bool machine_readable_stderr_p () const final override
497 : : {
498 : 0 : return false;
499 : : }
500 : :
501 : : private:
502 : : char *m_base_file_name;
503 : : };
504 : :
505 : : /* Populate CONTEXT in preparation for JSON output (either to stderr, or
506 : : to a file). */
507 : :
508 : : static void
509 : 44 : diagnostic_output_format_init_json (diagnostic_context &context,
510 : : std::unique_ptr<json_output_format> fmt)
511 : : {
512 : : /* Don't colorize the text. */
513 : 44 : pp_show_color (fmt->get_printer ()) = false;
514 : 44 : context.set_show_highlight_colors (false);
515 : :
516 : 44 : context.set_output_format (std::move (fmt));
517 : 44 : }
518 : :
519 : : /* Populate CONTEXT in preparation for JSON output to stderr. */
520 : :
521 : : void
522 : 32 : diagnostic_output_format_init_json_stderr (diagnostic_context &context,
523 : : bool formatted)
524 : : {
525 : 32 : diagnostic_output_format_init_json
526 : 32 : (context,
527 : 64 : ::make_unique<json_stderr_output_format> (context,
528 : : formatted));
529 : 32 : }
530 : :
531 : : /* Populate CONTEXT in preparation for JSON output to a file named
532 : : BASE_FILE_NAME.gcc.json. */
533 : :
534 : : void
535 : 12 : diagnostic_output_format_init_json_file (diagnostic_context &context,
536 : : bool formatted,
537 : : const char *base_file_name)
538 : : {
539 : 12 : diagnostic_output_format_init_json
540 : 12 : (context,
541 : 24 : ::make_unique<json_file_output_format> (context,
542 : : formatted,
543 : : base_file_name));
544 : 12 : }
545 : :
546 : : #if CHECKING_P
547 : :
548 : : namespace selftest {
549 : :
550 : : /* We shouldn't call json_from_expanded_location on UNKNOWN_LOCATION,
551 : : but verify that we handle this gracefully. */
552 : :
553 : : static void
554 : 4 : test_unknown_location ()
555 : : {
556 : 4 : test_diagnostic_context dc;
557 : 4 : json_from_expanded_location (dc, UNKNOWN_LOCATION);
558 : 4 : }
559 : :
560 : : /* Verify that we gracefully handle attempts to serialize bad
561 : : compound locations. */
562 : :
563 : : static void
564 : 4 : test_bad_endpoints ()
565 : : {
566 : 4 : location_t bad_endpoints
567 : 4 : = make_location (BUILTINS_LOCATION,
568 : : UNKNOWN_LOCATION, UNKNOWN_LOCATION);
569 : :
570 : 4 : location_range loc_range;
571 : 4 : loc_range.m_loc = bad_endpoints;
572 : 4 : loc_range.m_range_display_kind = SHOW_RANGE_WITH_CARET;
573 : 4 : loc_range.m_label = nullptr;
574 : :
575 : 4 : test_diagnostic_context dc;
576 : 4 : std::unique_ptr<json::object> obj
577 : 4 : = json_from_location_range (dc, &loc_range, 0);
578 : : /* We should have a "caret" value, but no "start" or "finish" values. */
579 : 4 : ASSERT_TRUE (obj != nullptr);
580 : 4 : ASSERT_TRUE (obj->get ("caret") != nullptr);
581 : 4 : ASSERT_TRUE (obj->get ("start") == nullptr);
582 : 4 : ASSERT_TRUE (obj->get ("finish") == nullptr);
583 : 4 : }
584 : :
585 : : /* Run all of the selftests within this file. */
586 : :
587 : : void
588 : 4 : diagnostic_format_json_cc_tests ()
589 : : {
590 : 4 : test_unknown_location ();
591 : 4 : test_bad_endpoints ();
592 : 4 : }
593 : :
594 : : } // namespace selftest
595 : :
596 : : #endif /* #if CHECKING_P */
|