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