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