1 /* JSON output for diagnostics
2    Copyright (C) 2018-2021 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 "json.h"
29 #include "selftest.h"
30 
31 /* The top-level JSON array of pending diagnostics.  */
32 
33 static json::array *toplevel_array;
34 
35 /* The JSON object for the current diagnostic group.  */
36 
37 static json::object *cur_group;
38 
39 /* The JSON array for the "children" array within the current diagnostic
40    group.  */
41 
42 static json::array *cur_children_array;
43 
44 /* Generate a JSON object for LOC.  */
45 
46 json::value *
json_from_expanded_location(diagnostic_context * context,location_t loc)47 json_from_expanded_location (diagnostic_context *context, location_t loc)
48 {
49   expanded_location exploc = expand_location (loc);
50   json::object *result = new json::object ();
51   if (exploc.file)
52     result->set ("file", new json::string (exploc.file));
53   result->set ("line", new json::integer_number (exploc.line));
54 
55   const enum diagnostics_column_unit orig_unit = context->column_unit;
56   struct
57   {
58     const char *name;
59     enum diagnostics_column_unit unit;
60   } column_fields[] = {
61     {"display-column", DIAGNOSTICS_COLUMN_UNIT_DISPLAY},
62     {"byte-column", DIAGNOSTICS_COLUMN_UNIT_BYTE}
63   };
64   int the_column = INT_MIN;
65   for (int i = 0; i != sizeof column_fields / sizeof (*column_fields); ++i)
66     {
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)
71 	the_column = col;
72     }
73   gcc_assert (the_column != INT_MIN);
74   result->set ("column", new json::integer_number (the_column));
75   context->column_unit = orig_unit;
76   return result;
77 }
78 
79 /* Generate a JSON object for LOC_RANGE.  */
80 
81 static json::object *
json_from_location_range(diagnostic_context * context,const location_range * loc_range,unsigned range_idx)82 json_from_location_range (diagnostic_context *context,
83 			  const location_range *loc_range, unsigned range_idx)
84 {
85   location_t caret_loc = get_pure_location (loc_range->m_loc);
86 
87   if (caret_loc == UNKNOWN_LOCATION)
88     return NULL;
89 
90   location_t start_loc = get_start (loc_range->m_loc);
91   location_t finish_loc = get_finish (loc_range->m_loc);
92 
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));
101 
102   if (loc_range->m_label)
103     {
104       label_text text;
105       text = loc_range->m_label->get_text (range_idx);
106       if (text.m_buffer)
107 	result->set ("label", new json::string (text.m_buffer));
108       text.maybe_free ();
109     }
110 
111   return result;
112 }
113 
114 /* Generate a JSON object for HINT.  */
115 
116 static json::object *
json_from_fixit_hint(diagnostic_context * context,const fixit_hint * hint)117 json_from_fixit_hint (diagnostic_context *context, const fixit_hint *hint)
118 {
119   json::object *fixit_obj = new json::object ();
120 
121   location_t start_loc = hint->get_start_loc ();
122   fixit_obj->set ("start", json_from_expanded_location (context, start_loc));
123   location_t next_loc = hint->get_next_loc ();
124   fixit_obj->set ("next", json_from_expanded_location (context, next_loc));
125   fixit_obj->set ("string", new json::string (hint->get_string ()));
126 
127   return fixit_obj;
128 }
129 
130 /* Generate a JSON object for METADATA.  */
131 
132 static json::object *
json_from_metadata(const diagnostic_metadata * metadata)133 json_from_metadata (const diagnostic_metadata *metadata)
134 {
135   json::object *metadata_obj = new json::object ();
136 
137   if (metadata->get_cwe ())
138     metadata_obj->set ("cwe",
139 		       new json::integer_number (metadata->get_cwe ()));
140 
141   return metadata_obj;
142 }
143 
144 /* No-op implementation of "begin_diagnostic" for JSON output.  */
145 
146 static void
json_begin_diagnostic(diagnostic_context *,diagnostic_info *)147 json_begin_diagnostic (diagnostic_context *, diagnostic_info *)
148 {
149 }
150 
151 /* Implementation of "end_diagnostic" for JSON output.
152    Generate a JSON object for DIAGNOSTIC, and store for output
153    within current diagnostic group.  */
154 
155 static void
json_end_diagnostic(diagnostic_context * context,diagnostic_info * diagnostic,diagnostic_t orig_diag_kind)156 json_end_diagnostic (diagnostic_context *context, diagnostic_info *diagnostic,
157 		     diagnostic_t orig_diag_kind)
158 {
159   json::object *diag_obj = new json::object ();
160 
161   /* Get "kind" of diagnostic.  */
162   {
163     static const char *const diagnostic_kind_text[] = {
164 #define DEFINE_DIAGNOSTIC_KIND(K, T, C) (T),
165 #include "diagnostic.def"
166 #undef DEFINE_DIAGNOSTIC_KIND
167       "must-not-happen"
168     };
169     /* Lose the trailing ": ".  */
170     const char *kind_text = diagnostic_kind_text[diagnostic->kind];
171     size_t len = strlen (kind_text);
172     gcc_assert (len > 2);
173     gcc_assert (kind_text[len - 2] == ':');
174     gcc_assert (kind_text[len - 1] == ' ');
175     char *rstrip = xstrdup (kind_text);
176     rstrip[len - 2] = '\0';
177     diag_obj->set ("kind", new json::string (rstrip));
178     free (rstrip);
179   }
180 
181   // FIXME: encoding of the message (json::string requires UTF-8)
182   diag_obj->set ("message",
183 		 new json::string (pp_formatted_text (context->printer)));
184   pp_clear_output_area (context->printer);
185 
186   char *option_text;
187   option_text = context->option_name (context, diagnostic->option_index,
188 				      orig_diag_kind, diagnostic->kind);
189   if (option_text)
190     {
191       diag_obj->set ("option", new json::string (option_text));
192       free (option_text);
193     }
194 
195   if (context->get_option_url)
196     {
197       char *option_url = context->get_option_url (context,
198 						  diagnostic->option_index);
199       if (option_url)
200 	{
201 	  diag_obj->set ("option_url", new json::string (option_url));
202 	  free (option_url);
203 	}
204     }
205 
206   /* If we've already emitted a diagnostic within this auto_diagnostic_group,
207      then add diag_obj to its "children" array.  */
208   if (cur_group)
209     {
210       gcc_assert (cur_children_array);
211       cur_children_array->append (diag_obj);
212     }
213   else
214     {
215       /* Otherwise, make diag_obj be the top-level object within the group;
216 	 add a "children" array and record the column origin.  */
217       toplevel_array->append (diag_obj);
218       cur_group = diag_obj;
219       cur_children_array = new json::array ();
220       diag_obj->set ("children", cur_children_array);
221       diag_obj->set ("column-origin",
222 		     new json::integer_number (context->column_origin));
223     }
224 
225   const rich_location *richloc = diagnostic->richloc;
226 
227   json::array *loc_array = new json::array ();
228   diag_obj->set ("locations", loc_array);
229 
230   for (unsigned int i = 0; i < richloc->get_num_locations (); i++)
231     {
232       const location_range *loc_range = richloc->get_range (i);
233       json::object *loc_obj = json_from_location_range (context, loc_range, i);
234       if (loc_obj)
235 	loc_array->append (loc_obj);
236     }
237 
238   if (richloc->get_num_fixit_hints ())
239     {
240       json::array *fixit_array = new json::array ();
241       diag_obj->set ("fixits", fixit_array);
242       for (unsigned int i = 0; i < richloc->get_num_fixit_hints (); i++)
243 	{
244 	  const fixit_hint *hint = richloc->get_fixit_hint (i);
245 	  json::object *fixit_obj = json_from_fixit_hint (context, hint);
246 	  fixit_array->append (fixit_obj);
247 	}
248     }
249 
250   /* TODO: tree-ish things:
251      TODO: functions
252      TODO: inlining information
253      TODO: macro expansion information.  */
254 
255   if (diagnostic->metadata)
256     {
257       json::object *metadata_obj = json_from_metadata (diagnostic->metadata);
258       diag_obj->set ("metadata", metadata_obj);
259     }
260 
261   const diagnostic_path *path = richloc->get_path ();
262   if (path && context->make_json_for_path)
263     {
264       json::value *path_value = context->make_json_for_path (context, path);
265       diag_obj->set ("path", path_value);
266     }
267 }
268 
269 /* No-op implementation of "begin_group_cb" for JSON output.  */
270 
271 static void
json_begin_group(diagnostic_context *)272 json_begin_group (diagnostic_context *)
273 {
274 }
275 
276 /* Implementation of "end_group_cb" for JSON output.  */
277 
278 static void
json_end_group(diagnostic_context *)279 json_end_group (diagnostic_context *)
280 {
281   cur_group = NULL;
282   cur_children_array = NULL;
283 }
284 
285 /* Callback for final cleanup for JSON output.  */
286 
287 static void
json_final_cb(diagnostic_context *)288 json_final_cb (diagnostic_context *)
289 {
290   /* Flush the top-level array.  */
291   toplevel_array->dump (stderr);
292   fprintf (stderr, "\n");
293   delete toplevel_array;
294   toplevel_array = NULL;
295 }
296 
297 /* Set the output format for CONTEXT to FORMAT.  */
298 
299 void
diagnostic_output_format_init(diagnostic_context * context,enum diagnostics_output_format format)300 diagnostic_output_format_init (diagnostic_context *context,
301 			       enum diagnostics_output_format format)
302 {
303   switch (format)
304     {
305     default:
306       gcc_unreachable ();
307     case DIAGNOSTICS_OUTPUT_FORMAT_TEXT:
308       /* The default; do nothing.  */
309       break;
310 
311     case DIAGNOSTICS_OUTPUT_FORMAT_JSON:
312       {
313 	/* Set up top-level JSON array.  */
314 	if (toplevel_array == NULL)
315 	  toplevel_array = new json::array ();
316 
317 	/* Override callbacks.  */
318 	context->begin_diagnostic = json_begin_diagnostic;
319 	context->end_diagnostic = json_end_diagnostic;
320 	context->begin_group_cb = json_begin_group;
321 	context->end_group_cb =  json_end_group;
322 	context->final_cb =  json_final_cb;
323 	context->print_path = NULL; /* handled in json_end_diagnostic.  */
324 
325 	/* The metadata is handled in JSON format, rather than as text.  */
326 	context->show_cwe = false;
327 
328 	/* The option is handled in JSON format, rather than as text.  */
329 	context->show_option_requested = false;
330 
331 	/* Don't colorize the text.  */
332 	pp_show_color (context->printer) = false;
333       }
334       break;
335     }
336 }
337 
338 #if CHECKING_P
339 
340 namespace selftest {
341 
342 /* We shouldn't call json_from_expanded_location on UNKNOWN_LOCATION,
343    but verify that we handle this gracefully.  */
344 
345 static void
test_unknown_location()346 test_unknown_location ()
347 {
348   test_diagnostic_context dc;
349   delete json_from_expanded_location (&dc, UNKNOWN_LOCATION);
350 }
351 
352 /* Verify that we gracefully handle attempts to serialize bad
353    compound locations.  */
354 
355 static void
test_bad_endpoints()356 test_bad_endpoints ()
357 {
358   location_t bad_endpoints
359     = make_location (BUILTINS_LOCATION,
360 		     UNKNOWN_LOCATION, UNKNOWN_LOCATION);
361 
362   location_range loc_range;
363   loc_range.m_loc = bad_endpoints;
364   loc_range.m_range_display_kind = SHOW_RANGE_WITH_CARET;
365   loc_range.m_label = NULL;
366 
367   test_diagnostic_context dc;
368   json::object *obj = json_from_location_range (&dc, &loc_range, 0);
369   /* We should have a "caret" value, but no "start" or "finish" values.  */
370   ASSERT_TRUE (obj != NULL);
371   ASSERT_TRUE (obj->get ("caret") != NULL);
372   ASSERT_TRUE (obj->get ("start") == NULL);
373   ASSERT_TRUE (obj->get ("finish") == NULL);
374   delete obj;
375 }
376 
377 /* Run all of the selftests within this file.  */
378 
379 void
diagnostic_format_json_cc_tests()380 diagnostic_format_json_cc_tests ()
381 {
382   test_unknown_location ();
383   test_bad_endpoints ();
384 }
385 
386 } // namespace selftest
387 
388 #endif /* #if CHECKING_P */
389