]> git.ipfire.org Git - thirdparty/gcc.git/blob - gcc/diagnostic-format-json.cc
Print "Global Exported" to dump_file from set_range_info.
[thirdparty/gcc.git] / gcc / diagnostic-format-json.cc
1 /* JSON output for diagnostics
2 Copyright (C) 2018-2024 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 #include "system.h"
24 #include "coretypes.h"
25 #include "diagnostic.h"
26 #include "selftest-diagnostic.h"
27 #include "diagnostic-metadata.h"
28 #include "diagnostic-path.h"
29 #include "json.h"
30 #include "selftest.h"
31 #include "logical-location.h"
32
33 /* Subclass of diagnostic_output_format for JSON output. */
34
35 class json_output_format : public diagnostic_output_format
36 {
37 public:
38 void on_begin_group () final override
39 {
40 /* No-op. */
41 }
42 void on_end_group () final override
43 {
44 m_cur_group = nullptr;
45 m_cur_children_array = nullptr;
46 }
47 void
48 on_begin_diagnostic (const diagnostic_info &) final override
49 {
50 /* No-op. */
51 }
52 void
53 on_end_diagnostic (const diagnostic_info &diagnostic,
54 diagnostic_t orig_diag_kind) final override;
55 void on_diagram (const diagnostic_diagram &) final override
56 {
57 /* No-op. */
58 }
59
60 protected:
61 json_output_format (diagnostic_context &context,
62 bool formatted)
63 : diagnostic_output_format (context),
64 m_toplevel_array (new json::array ()),
65 m_cur_group (nullptr),
66 m_cur_children_array (nullptr),
67 m_formatted (formatted)
68 {
69 }
70
71 /* Flush the top-level array to OUTF. */
72 void
73 flush_to_file (FILE *outf)
74 {
75 m_toplevel_array->dump (outf, m_formatted);
76 fprintf (outf, "\n");
77 delete m_toplevel_array;
78 m_toplevel_array = nullptr;
79 }
80
81 private:
82 /* The top-level JSON array of pending diagnostics. */
83 json::array *m_toplevel_array;
84
85 /* The JSON object for the current diagnostic group. */
86 json::object *m_cur_group;
87
88 /* The JSON array for the "children" array within the current diagnostic
89 group. */
90 json::array *m_cur_children_array;
91
92 bool m_formatted;
93 };
94
95 /* Generate a JSON object for LOC. */
96
97 json::value *
98 json_from_expanded_location (diagnostic_context *context, location_t loc)
99 {
100 expanded_location exploc = expand_location (loc);
101 json::object *result = new json::object ();
102 if (exploc.file)
103 result->set_string ("file", exploc.file);
104 result->set_integer ("line", exploc.line);
105
106 const enum diagnostics_column_unit orig_unit = context->m_column_unit;
107 struct
108 {
109 const char *name;
110 enum diagnostics_column_unit unit;
111 } column_fields[] = {
112 {"display-column", DIAGNOSTICS_COLUMN_UNIT_DISPLAY},
113 {"byte-column", DIAGNOSTICS_COLUMN_UNIT_BYTE}
114 };
115 int the_column = INT_MIN;
116 for (int i = 0; i != ARRAY_SIZE (column_fields); ++i)
117 {
118 context->m_column_unit = column_fields[i].unit;
119 const int col = context->converted_column (exploc);
120 result->set_integer (column_fields[i].name, col);
121 if (column_fields[i].unit == orig_unit)
122 the_column = col;
123 }
124 gcc_assert (the_column != INT_MIN);
125 result->set_integer ("column", the_column);
126 context->m_column_unit = orig_unit;
127 return result;
128 }
129
130 /* Generate a JSON object for LOC_RANGE. */
131
132 static json::object *
133 json_from_location_range (diagnostic_context *context,
134 const location_range *loc_range, unsigned range_idx)
135 {
136 location_t caret_loc = get_pure_location (loc_range->m_loc);
137
138 if (caret_loc == UNKNOWN_LOCATION)
139 return NULL;
140
141 location_t start_loc = get_start (loc_range->m_loc);
142 location_t finish_loc = get_finish (loc_range->m_loc);
143
144 json::object *result = new json::object ();
145 result->set ("caret", json_from_expanded_location (context, caret_loc));
146 if (start_loc != caret_loc
147 && start_loc != UNKNOWN_LOCATION)
148 result->set ("start", json_from_expanded_location (context, start_loc));
149 if (finish_loc != caret_loc
150 && finish_loc != UNKNOWN_LOCATION)
151 result->set ("finish", json_from_expanded_location (context, finish_loc));
152
153 if (loc_range->m_label)
154 {
155 label_text text (loc_range->m_label->get_text (range_idx));
156 if (text.get ())
157 result->set_string ("label", text.get ());
158 }
159
160 return result;
161 }
162
163 /* Generate a JSON object for HINT. */
164
165 static json::object *
166 json_from_fixit_hint (diagnostic_context *context, const fixit_hint *hint)
167 {
168 json::object *fixit_obj = new json::object ();
169
170 location_t start_loc = hint->get_start_loc ();
171 fixit_obj->set ("start", json_from_expanded_location (context, start_loc));
172 location_t next_loc = hint->get_next_loc ();
173 fixit_obj->set ("next", json_from_expanded_location (context, next_loc));
174 fixit_obj->set_string ("string", hint->get_string ());
175
176 return fixit_obj;
177 }
178
179 /* Generate a JSON object for METADATA. */
180
181 static json::object *
182 json_from_metadata (const diagnostic_metadata *metadata)
183 {
184 json::object *metadata_obj = new json::object ();
185
186 if (metadata->get_cwe ())
187 metadata_obj->set_integer ("cwe", metadata->get_cwe ());
188
189 return metadata_obj;
190 }
191
192 /* Make a JSON value for PATH. */
193
194 static json::value *
195 make_json_for_path (diagnostic_context *context,
196 const diagnostic_path *path)
197 {
198 json::array *path_array = new json::array ();
199 for (unsigned i = 0; i < path->num_events (); i++)
200 {
201 const diagnostic_event &event = path->get_event (i);
202
203 json::object *event_obj = new json::object ();
204 if (event.get_location ())
205 event_obj->set ("location",
206 json_from_expanded_location (context,
207 event.get_location ()));
208 label_text event_text (event.get_desc (false));
209 event_obj->set_string ("description", event_text.get ());
210 if (const logical_location *logical_loc = event.get_logical_location ())
211 {
212 label_text name (logical_loc->get_name_for_path_output ());
213 event_obj->set_string ("function", name.get ());
214 }
215 event_obj->set_integer ("depth", event.get_stack_depth ());
216 path_array->append (event_obj);
217 }
218 return path_array;
219 }
220
221
222 /* Implementation of "on_end_diagnostic" vfunc for JSON output.
223 Generate a JSON object for DIAGNOSTIC, and store for output
224 within current diagnostic group. */
225
226 void
227 json_output_format::on_end_diagnostic (const diagnostic_info &diagnostic,
228 diagnostic_t orig_diag_kind)
229 {
230 json::object *diag_obj = new json::object ();
231
232 /* Get "kind" of diagnostic. */
233 {
234 static const char *const diagnostic_kind_text[] = {
235 #define DEFINE_DIAGNOSTIC_KIND(K, T, C) (T),
236 #include "diagnostic.def"
237 #undef DEFINE_DIAGNOSTIC_KIND
238 "must-not-happen"
239 };
240 /* Lose the trailing ": ". */
241 const char *kind_text = diagnostic_kind_text[diagnostic.kind];
242 size_t len = strlen (kind_text);
243 gcc_assert (len > 2);
244 gcc_assert (kind_text[len - 2] == ':');
245 gcc_assert (kind_text[len - 1] == ' ');
246 char *rstrip = xstrdup (kind_text);
247 rstrip[len - 2] = '\0';
248 diag_obj->set_string ("kind", rstrip);
249 free (rstrip);
250 }
251
252 // FIXME: encoding of the message (json::string requires UTF-8)
253 diag_obj->set_string ("message", pp_formatted_text (m_context.printer));
254 pp_clear_output_area (m_context.printer);
255
256 if (char *option_text = m_context.make_option_name (diagnostic.option_index,
257 orig_diag_kind,
258 diagnostic.kind))
259 {
260 diag_obj->set_string ("option", option_text);
261 free (option_text);
262 }
263
264 if (char *option_url = m_context.make_option_url (diagnostic.option_index))
265 {
266 diag_obj->set_string ("option_url", option_url);
267 free (option_url);
268 }
269
270 /* If we've already emitted a diagnostic within this auto_diagnostic_group,
271 then add diag_obj to its "children" array. */
272 if (m_cur_group)
273 {
274 gcc_assert (m_cur_children_array);
275 m_cur_children_array->append (diag_obj);
276 }
277 else
278 {
279 /* Otherwise, make diag_obj be the top-level object within the group;
280 add a "children" array and record the column origin. */
281 m_toplevel_array->append (diag_obj);
282 m_cur_group = diag_obj;
283 m_cur_children_array = new json::array ();
284 diag_obj->set ("children", m_cur_children_array);
285 diag_obj->set_integer ("column-origin", m_context.m_column_origin);
286 }
287
288 const rich_location *richloc = diagnostic.richloc;
289
290 json::array *loc_array = new json::array ();
291 diag_obj->set ("locations", loc_array);
292
293 for (unsigned int i = 0; i < richloc->get_num_locations (); i++)
294 {
295 const location_range *loc_range = richloc->get_range (i);
296 json::object *loc_obj
297 = json_from_location_range (&m_context, loc_range, i);
298 if (loc_obj)
299 loc_array->append (loc_obj);
300 }
301
302 if (richloc->get_num_fixit_hints ())
303 {
304 json::array *fixit_array = new json::array ();
305 diag_obj->set ("fixits", fixit_array);
306 for (unsigned int i = 0; i < richloc->get_num_fixit_hints (); i++)
307 {
308 const fixit_hint *hint = richloc->get_fixit_hint (i);
309 json::object *fixit_obj = json_from_fixit_hint (&m_context, hint);
310 fixit_array->append (fixit_obj);
311 }
312 }
313
314 /* TODO: tree-ish things:
315 TODO: functions
316 TODO: inlining information
317 TODO: macro expansion information. */
318
319 if (diagnostic.metadata)
320 {
321 json::object *metadata_obj = json_from_metadata (diagnostic.metadata);
322 diag_obj->set ("metadata", metadata_obj);
323 }
324
325 const diagnostic_path *path = richloc->get_path ();
326 if (path)
327 {
328 json::value *path_value = make_json_for_path (&m_context, path);
329 diag_obj->set ("path", path_value);
330 }
331
332 diag_obj->set ("escape-source",
333 new json::literal (richloc->escape_on_output_p ()));
334 }
335
336 class json_stderr_output_format : public json_output_format
337 {
338 public:
339 json_stderr_output_format (diagnostic_context &context,
340 bool formatted)
341 : json_output_format (context, formatted)
342 {
343 }
344 ~json_stderr_output_format ()
345 {
346 flush_to_file (stderr);
347 }
348 bool machine_readable_stderr_p () const final override
349 {
350 return true;
351 }
352 };
353
354 class json_file_output_format : public json_output_format
355 {
356 public:
357 json_file_output_format (diagnostic_context &context,
358 bool formatted,
359 const char *base_file_name)
360 : json_output_format (context, formatted),
361 m_base_file_name (xstrdup (base_file_name))
362 {
363 }
364
365 ~json_file_output_format ()
366 {
367 char *filename = concat (m_base_file_name, ".gcc.json", NULL);
368 free (m_base_file_name);
369 m_base_file_name = nullptr;
370 FILE *outf = fopen (filename, "w");
371 if (!outf)
372 {
373 const char *errstr = xstrerror (errno);
374 fnotice (stderr, "error: unable to open '%s' for writing: %s\n",
375 filename, errstr);
376 free (filename);
377 return;
378 }
379 flush_to_file (outf);
380 fclose (outf);
381 free (filename);
382 }
383 bool machine_readable_stderr_p () const final override
384 {
385 return false;
386 }
387
388 private:
389 char *m_base_file_name;
390 };
391
392 /* Populate CONTEXT in preparation for JSON output (either to stderr, or
393 to a file). */
394
395 static void
396 diagnostic_output_format_init_json (diagnostic_context *context)
397 {
398 /* Suppress normal textual path output. */
399 context->set_path_format (DPF_NONE);
400
401 /* The metadata is handled in JSON format, rather than as text. */
402 context->set_show_cwe (false);
403 context->set_show_rules (false);
404
405 /* The option is handled in JSON format, rather than as text. */
406 context->set_show_option_requested (false);
407
408 /* Don't colorize the text. */
409 pp_show_color (context->printer) = false;
410 }
411
412 /* Populate CONTEXT in preparation for JSON output to stderr. */
413
414 void
415 diagnostic_output_format_init_json_stderr (diagnostic_context *context,
416 bool formatted)
417 {
418 diagnostic_output_format_init_json (context);
419 context->set_output_format (new json_stderr_output_format (*context,
420 formatted));
421 }
422
423 /* Populate CONTEXT in preparation for JSON output to a file named
424 BASE_FILE_NAME.gcc.json. */
425
426 void
427 diagnostic_output_format_init_json_file (diagnostic_context *context,
428 bool formatted,
429 const char *base_file_name)
430 {
431 diagnostic_output_format_init_json (context);
432 context->set_output_format (new json_file_output_format (*context,
433 formatted,
434 base_file_name));
435 }
436
437 #if CHECKING_P
438
439 namespace selftest {
440
441 /* We shouldn't call json_from_expanded_location on UNKNOWN_LOCATION,
442 but verify that we handle this gracefully. */
443
444 static void
445 test_unknown_location ()
446 {
447 test_diagnostic_context dc;
448 delete json_from_expanded_location (&dc, UNKNOWN_LOCATION);
449 }
450
451 /* Verify that we gracefully handle attempts to serialize bad
452 compound locations. */
453
454 static void
455 test_bad_endpoints ()
456 {
457 location_t bad_endpoints
458 = make_location (BUILTINS_LOCATION,
459 UNKNOWN_LOCATION, UNKNOWN_LOCATION);
460
461 location_range loc_range;
462 loc_range.m_loc = bad_endpoints;
463 loc_range.m_range_display_kind = SHOW_RANGE_WITH_CARET;
464 loc_range.m_label = NULL;
465
466 test_diagnostic_context dc;
467 json::object *obj = json_from_location_range (&dc, &loc_range, 0);
468 /* We should have a "caret" value, but no "start" or "finish" values. */
469 ASSERT_TRUE (obj != NULL);
470 ASSERT_TRUE (obj->get ("caret") != NULL);
471 ASSERT_TRUE (obj->get ("start") == NULL);
472 ASSERT_TRUE (obj->get ("finish") == NULL);
473 delete obj;
474 }
475
476 /* Run all of the selftests within this file. */
477
478 void
479 diagnostic_format_json_cc_tests ()
480 {
481 test_unknown_location ();
482 test_bad_endpoints ();
483 }
484
485 } // namespace selftest
486
487 #endif /* #if CHECKING_P */