1 /* { dg-options "-O" } */
2 
3 /* This plugin exercises the diagnostics-printing code.
4 
5    The goal is to unit-test the range-printing code without needing any
6    correct range data within the compiler's IR.  We can't use any real
7    diagnostics for this, so we have to fake it, hence this plugin.
8 
9    There are two test files used with this code:
10 
11      diagnostic-test-show-locus-ascii-bw.c
12      ..........................-ascii-color.c
13 
14    to exercise uncolored vs colored output by supplying plugin arguments
15    to hack in the desired behavior:
16 
17      -fplugin-arg-diagnostic_plugin_test_show_locus-color
18 
19    The test files contain functions, but the body of each
20    function is disabled using the preprocessor.  The plugin detects
21    the functions by name, and inject diagnostics within them, using
22    hard-coded locations relative to the top of each function.
23 
24    The plugin uses a function "get_loc" below to map from line/column
25    numbers to location_t, and this relies on input_location being in
26    the same ordinary line_map as the locations in question.  The plugin
27    runs after parsing, so input_location will be at the end of the file.
28 
29    This need for all of the test code to be in a single ordinary line map
30    means that each test file needs to have a very long line near the top
31    (potentially to cover the extra byte-count of colorized data),
32    to ensure that further very long lines don't start a new linemap.
33    This also means that we can't use macros in the test files.  */
34 
35 #include "gcc-plugin.h"
36 #include "config.h"
37 #include "system.h"
38 #include "coretypes.h"
39 #include "tm.h"
40 #include "tree.h"
41 #include "stringpool.h"
42 #include "toplev.h"
43 #include "basic-block.h"
44 #include "hash-table.h"
45 #include "vec.h"
46 #include "ggc.h"
47 #include "basic-block.h"
48 #include "tree-ssa-alias.h"
49 #include "internal-fn.h"
50 #include "gimple-fold.h"
51 #include "tree-eh.h"
52 #include "gimple-expr.h"
53 #include "is-a.h"
54 #include "gimple.h"
55 #include "gimple-iterator.h"
56 #include "tree.h"
57 #include "tree-pass.h"
58 #include "intl.h"
59 #include "plugin-version.h"
60 #include "diagnostic.h"
61 #include "context.h"
62 #include "print-tree.h"
63 #include "gcc-rich-location.h"
64 
65 int plugin_is_GPL_compatible;
66 
67 const pass_data pass_data_test_show_locus =
68 {
69   GIMPLE_PASS, /* type */
70   "test_show_locus", /* name */
71   OPTGROUP_NONE, /* optinfo_flags */
72   TV_NONE, /* tv_id */
73   PROP_ssa, /* properties_required */
74   0, /* properties_provided */
75   0, /* properties_destroyed */
76   0, /* todo_flags_start */
77   0, /* todo_flags_finish */
78 };
79 
80 class pass_test_show_locus : public gimple_opt_pass
81 {
82 public:
pass_test_show_locus(gcc::context * ctxt)83   pass_test_show_locus(gcc::context *ctxt)
84     : gimple_opt_pass(pass_data_test_show_locus, ctxt)
85   {}
86 
87   /* opt_pass methods: */
gate(function *)88   bool gate (function *) { return true; }
89   virtual unsigned int execute (function *);
90 
91 }; // class pass_test_show_locus
92 
93 /* Given LINE_NUM and COL_NUM, generate a location_t in the
94    current file, relative to input_location.  This relies on the
95    location being expressible in the same ordinary line_map as
96    input_location (which is typically at the end of the source file
97    when this is called).  Hence the test files we compile with this
98    plugin must have an initial very long line (to avoid long lines
99    starting a new line map), and must not use macros.
100 
101    COL_NUM uses the Emacs convention of 0-based column numbers.  */
102 
103 static location_t
get_loc(unsigned int line_num,unsigned int col_num)104 get_loc (unsigned int line_num, unsigned int col_num)
105 {
106   /* Use input_location to get the relevant line_map */
107   const struct line_map_ordinary *line_map
108     = (const line_map_ordinary *)(linemap_lookup (line_table,
109 						  input_location));
110 
111   /* Convert from 0-based column numbers to 1-based column numbers.  */
112   location_t loc
113     = linemap_position_for_line_and_column (line_table,
114 					    line_map,
115 					    line_num, col_num + 1);
116 
117   return loc;
118 }
119 
120 /* Was "color" passed in as a plugin argument?  */
121 static bool force_show_locus_color = false;
122 
123 /* We want to verify the colorized output of diagnostic_show_locus,
124    but turning on colorization for everything confuses "dg-warning" etc.
125    Hence we special-case it within this plugin by using this modified
126    version of default_diagnostic_finalizer, which, if "color" is
127    passed in as a plugin argument turns on colorization, but just
128    for diagnostic_show_locus.  */
129 
130 static void
custom_diagnostic_finalizer(diagnostic_context * context,diagnostic_info * diagnostic,diagnostic_t)131 custom_diagnostic_finalizer (diagnostic_context *context,
132 			     diagnostic_info *diagnostic,
133 			     diagnostic_t)
134 {
135   bool old_show_color = pp_show_color (context->printer);
136   if (force_show_locus_color)
137     pp_show_color (context->printer) = true;
138   char *saved_prefix = pp_take_prefix (context->printer);
139   pp_set_prefix (context->printer, NULL);
140   pp_newline (context->printer);
141   diagnostic_show_locus (context, diagnostic->richloc, diagnostic->kind);
142   pp_show_color (context->printer) = old_show_color;
143   pp_set_prefix (context->printer, saved_prefix);
144   pp_flush (context->printer);
145 }
146 
147 /* Add a location to RICHLOC with caret==start at START, ranging to FINISH.  */
148 
149 static void
150 add_range (rich_location *richloc, location_t start, location_t finish,
151 	   enum range_display_kind range_display_kind
152 	     = SHOW_RANGE_WITHOUT_CARET,
153 	   const range_label *label = NULL)
154 {
155   richloc->add_range (make_location (start, start, finish), range_display_kind,
156 		      label);
157 }
158 
159 /* Exercise the diagnostic machinery to emit various warnings,
160    for use by diagnostic-test-show-locus-*.c.
161 
162    We inject each warning relative to the start of a function,
163    which avoids lots of hardcoded absolute locations.  */
164 
165 static void
test_show_locus(function * fun)166 test_show_locus (function *fun)
167 {
168   tree fndecl = fun->decl;
169   tree identifier = DECL_NAME (fndecl);
170   const char *fnname = IDENTIFIER_POINTER (identifier);
171   location_t fnstart = fun->function_start_locus;
172   int fnstart_line = LOCATION_LINE (fnstart);
173 
174   diagnostic_finalizer (global_dc) = custom_diagnostic_finalizer;
175 
176   /* Hardcode the "terminal width", to verify the behavior of
177      very wide lines.  */
178   global_dc->caret_max_width = 71;
179 
180   if (0 == strcmp (fnname, "test_simple"))
181     {
182       const int line = fnstart_line + 2;
183       rich_location richloc (line_table, get_loc (line, 15));
184       add_range (&richloc, get_loc (line, 10), get_loc (line, 14));
185       add_range (&richloc, get_loc (line, 16), get_loc (line, 16));
186       warning_at (&richloc, 0, "test");
187     }
188 
189   if (0 == strcmp (fnname, "test_simple_2"))
190     {
191       const int line = fnstart_line + 2;
192       rich_location richloc (line_table, get_loc (line, 24));
193       add_range (&richloc, get_loc (line, 6), get_loc (line, 22));
194       add_range (&richloc, get_loc (line, 26), get_loc (line, 43));
195       warning_at (&richloc, 0, "test");
196     }
197 
198   if (0 == strcmp (fnname, "test_multiline"))
199     {
200       const int line = fnstart_line + 2;
201       text_range_label label ("label");
202       rich_location richloc (line_table, get_loc (line + 1, 7), &label);
203       add_range (&richloc, get_loc (line, 7), get_loc (line, 23));
204       add_range (&richloc, get_loc (line + 1, 9), get_loc (line + 1, 26));
205       warning_at (&richloc, 0, "test");
206     }
207 
208   if (0 == strcmp (fnname, "test_many_lines"))
209     {
210       const int line = fnstart_line + 2;
211       text_range_label label0 ("label 0");
212       text_range_label label1 ("label 1");
213       text_range_label label2 ("label 2");
214       rich_location richloc (line_table, get_loc (line + 5, 7), &label0);
215       add_range (&richloc, get_loc (line, 7), get_loc (line + 4, 65),
216 		 SHOW_RANGE_WITHOUT_CARET, &label1);
217       add_range (&richloc, get_loc (line + 5, 9), get_loc (line + 10, 61),
218 		 SHOW_RANGE_WITHOUT_CARET, &label2);
219       warning_at (&richloc, 0, "test");
220     }
221 
222   /* Example of a rich_location where the range is larger than
223      one character.  */
224   if (0 == strcmp (fnname, "test_richloc_from_proper_range"))
225     {
226       const int line = fnstart_line + 2;
227       location_t start = get_loc (line, 12);
228       location_t finish = get_loc (line, 16);
229       rich_location richloc (line_table, make_location (start, start, finish));
230       warning_at (&richloc, 0, "test");
231     }
232 
233   /* Example of a single-range location where the range starts
234      before the caret.  */
235   if (0 == strcmp (fnname, "test_caret_within_proper_range"))
236     {
237       const int line = fnstart_line + 2;
238       warning_at (make_location (get_loc (line, 16), get_loc (line, 12),
239 				 get_loc (line, 20)),
240 		  0, "test");
241     }
242 
243   /* Example of a very wide line, where the information of interest
244      is beyond the width of the terminal (hardcoded above), with
245      a secondary location that exactly fits on the left-margin.  */
246   if (0 == strcmp (fnname, "test_very_wide_line"))
247     {
248       const int line = fnstart_line + 2;
249       global_dc->show_ruler_p = true;
250       text_range_label label0 ("label 0");
251       text_range_label label1 ("label 1");
252       rich_location richloc (line_table,
253 			     make_location (get_loc (line, 94),
254 					    get_loc (line, 90),
255 					    get_loc (line, 98)),
256 			     &label0);
257       richloc.add_range (get_loc (line, 35), SHOW_RANGE_WITHOUT_CARET,
258 			 &label1);
259       richloc.add_fixit_replace ("bar * foo");
260       warning_at (&richloc, 0, "test");
261       global_dc->show_ruler_p = false;
262     }
263 
264   /* Likewise, but with a secondary location that's immediately before
265      the left margin; the location and label should be gracefully dropped.  */
266   if (0 == strcmp (fnname, "test_very_wide_line_2"))
267     {
268       const int line = fnstart_line + 2;
269       global_dc->show_ruler_p = true;
270       text_range_label label0 ("label 0");
271       text_range_label label1 ("label 1");
272       rich_location richloc (line_table,
273 			     make_location (get_loc (line, 94),
274 					    get_loc (line, 90),
275 					    get_loc (line, 98)),
276 			     &label0);
277       richloc.add_fixit_replace ("bar * foo");
278       richloc.add_range (get_loc (line, 34), SHOW_RANGE_WITHOUT_CARET,
279 			 &label1);
280       warning_at (&richloc, 0, "test");
281       global_dc->show_ruler_p = false;
282     }
283 
284   /* Example of multiple carets.  */
285   if (0 == strcmp (fnname, "test_multiple_carets"))
286     {
287       const int line = fnstart_line + 2;
288       location_t caret_a = get_loc (line, 7);
289       location_t caret_b = get_loc (line, 11);
290       rich_location richloc (line_table, caret_a);
291       add_range (&richloc, caret_b, caret_b, SHOW_RANGE_WITH_CARET);
292       global_dc->caret_chars[0] = 'A';
293       global_dc->caret_chars[1] = 'B';
294       warning_at (&richloc, 0, "test");
295       global_dc->caret_chars[0] = '^';
296       global_dc->caret_chars[1] = '^';
297     }
298 
299   /* Tests of rendering fixit hints.  */
300   if (0 == strcmp (fnname, "test_fixit_insert"))
301     {
302       const int line = fnstart_line + 2;
303       location_t start = get_loc (line, 19);
304       location_t finish = get_loc (line, 22);
305       rich_location richloc (line_table, make_location (start, start, finish));
306       richloc.add_fixit_insert_before ("{");
307       richloc.add_fixit_insert_after ("}");
308       warning_at (&richloc, 0, "example of insertion hints");
309     }
310 
311   if (0 == strcmp (fnname, "test_fixit_insert_newline"))
312     {
313       const int line = fnstart_line + 6;
314       location_t line_start = get_loc (line, 0);
315       location_t case_start = get_loc (line, 4);
316       location_t case_finish = get_loc (line, 11);
317       location_t case_loc = make_location (case_start, case_start, case_finish);
318       rich_location richloc (line_table, case_loc);
319       richloc.add_fixit_insert_before (line_start, "      break;\n");
320       warning_at (&richloc, 0, "example of newline insertion hint");
321     }
322 
323   if (0 == strcmp (fnname, "test_fixit_remove"))
324     {
325       const int line = fnstart_line + 2;
326       location_t start = get_loc (line, 8);
327       location_t finish = get_loc (line, 8);
328       rich_location richloc (line_table, make_location (start, start, finish));
329       source_range src_range;
330       src_range.m_start = start;
331       src_range.m_finish = finish;
332       richloc.add_fixit_remove (src_range);
333       warning_at (&richloc, 0, "example of a removal hint");
334     }
335 
336   if (0 == strcmp (fnname, "test_fixit_replace"))
337     {
338       const int line = fnstart_line + 2;
339       location_t start = get_loc (line, 2);
340       location_t finish = get_loc (line, 19);
341       rich_location richloc (line_table, make_location (start, start, finish));
342       source_range src_range;
343       src_range.m_start = start;
344       src_range.m_finish = finish;
345       richloc.add_fixit_replace (src_range, "gtk_widget_show_all");
346       warning_at (&richloc, 0, "example of a replacement hint");
347     }
348 
349   if (0 == strcmp (fnname, "test_mutually_exclusive_suggestions"))
350     {
351       const int line = fnstart_line + 2;
352       location_t start = get_loc (line, 2);
353       location_t finish = get_loc (line, 9);
354       source_range src_range;
355       src_range.m_start = start;
356       src_range.m_finish = finish;
357 
358       {
359 	rich_location richloc (line_table, make_location (start, start, finish));
360 	richloc.add_fixit_replace (src_range, "replacement_1");
361 	richloc.fixits_cannot_be_auto_applied ();
362 	warning_at (&richloc, 0, "warning 1");
363       }
364 
365       {
366 	rich_location richloc (line_table, make_location (start, start, finish));
367 	richloc.add_fixit_replace (src_range, "replacement_2");
368 	richloc.fixits_cannot_be_auto_applied ();
369 	warning_at (&richloc, 0, "warning 2");
370       }
371     }
372 
373   /* Tests of gcc_rich_location::add_fixit_insert_formatted.  */
374 
375   if (0 == strcmp (fnname, "test_add_fixit_insert_formatted_single_line"))
376     {
377       const int line = fnstart_line + 1;
378       location_t insertion_point = get_loc (line, 3);
379       location_t indent = get_loc (line, 2);
380       gcc_rich_location richloc (insertion_point);
381       richloc.add_fixit_insert_formatted ("INSERTED-CONTENT",
382 					  insertion_point, indent);
383       inform (&richloc, "single-line insertion");
384     }
385 
386   if (0 == strcmp (fnname, "test_add_fixit_insert_formatted_multiline"))
387     {
388       location_t insertion_point = fun->function_end_locus;
389       location_t indent = get_loc (fnstart_line + 1, 2);
390       gcc_rich_location richloc (insertion_point);
391       richloc.add_fixit_insert_formatted ("INSERTED-CONTENT",
392 					  insertion_point, indent);
393       inform (&richloc, "multiline insertion");
394     }
395 
396   /* Example of two carets where both carets appear to have an off-by-one
397      error appearing one column early.
398      Seen with gfortran.dg/associate_5.f03.
399      In an earlier version of the printer, the printing of caret 0 aka
400      "1" was suppressed due to it appearing within the leading whitespace
401      before the text in its line.  Ensure that we at least faithfully
402      print both carets, at the given (erroneous) locations.  */
403   if (0 == strcmp (fnname, "test_caret_on_leading_whitespace"))
404     {
405       const int line = fnstart_line + 3;
406       location_t caret_a = get_loc (line, 5);
407       location_t caret_b = get_loc (line - 1, 19);
408       rich_location richloc (line_table, caret_a);
409       richloc.add_range (caret_b, SHOW_RANGE_WITH_CARET);
410       global_dc->caret_chars[0] = '1';
411       global_dc->caret_chars[1] = '2';
412       warning_at (&richloc, 0, "test");
413       global_dc->caret_chars[0] = '^';
414       global_dc->caret_chars[1] = '^';
415     }
416 
417   /* Example of using the "%q+D" format code, which as well as printing
418      a quoted decl, overrides the given location to use the location of
419      the decl.  */
420   if (0 == strcmp (fnname, "test_percent_q_plus_d"))
421     {
422       const int line = fnstart_line + 3;
423       tree local = (*fun->local_decls)[0];
424       warning_at (input_location, 0,
425 		  "example of plus in format code for %q+D", local);
426     }
427 
428   /* Example of many locations and many fixits.
429      Underline (separately) every word in a comment, and convert them
430      to upper case.  Give all of the ranges labels (sharing one label).  */
431   if (0 == strcmp (fnname, "test_many_nested_locations"))
432     {
433       const char *file = LOCATION_FILE (fnstart);
434       const int start_line = fnstart_line + 2;
435       const int finish_line = start_line + 7;
436       location_t loc = get_loc (start_line - 1, 2);
437       text_range_label label ("label");
438       rich_location richloc (line_table, loc);
439       for (int line = start_line; line <= finish_line; line++)
440 	{
441 	  char_span content = location_get_source_line (file, line);
442 	  gcc_assert (content);
443 	  /* Split line up into words.  */
444 	  for (int idx = 0; idx < content.length (); idx++)
445 	    {
446 	      if (ISALPHA (content[idx]))
447 		{
448 		  int start_idx = idx;
449 		  while (idx < content.length () && ISALPHA (content[idx]))
450 		    idx++;
451 		  if (idx == content.length () || !ISALPHA (content[idx]))
452 		    {
453 		      location_t start_of_word = get_loc (line, start_idx);
454 		      location_t end_of_word = get_loc (line, idx - 1);
455 		      location_t word
456 			= make_location (start_of_word, start_of_word,
457 					 end_of_word);
458 		      richloc.add_range (word, SHOW_RANGE_WITH_CARET, &label);
459 
460 		      /* Add a fixit, converting to upper case.  */
461 		      char_span word_span = content.subspan (start_idx, idx - start_idx);
462 		      char *copy = word_span.xstrdup ();
463 		      for (char *ch = copy; *ch; ch++)
464 			*ch = TOUPPER (*ch);
465 		      richloc.add_fixit_replace (word, copy);
466 		      free (copy);
467 		    }
468 		}
469 	    }
470 	}
471       /* Verify that we added enough locations to fully exercise
472 	 rich_location.  We want to exceed both the
473 	 statically-allocated buffer in class rich_location,
474 	 and then trigger a reallocation of the dynamic buffer.  */
475       gcc_assert (richloc.get_num_locations () > 3 + (2 * 16));
476       warning_at (&richloc, 0, "test of %i locations",
477 		  richloc.get_num_locations ());
478     }
479 }
480 
481 unsigned int
execute(function * fun)482 pass_test_show_locus::execute (function *fun)
483 {
484   test_show_locus (fun);
485   return 0;
486 }
487 
488 static gimple_opt_pass *
make_pass_test_show_locus(gcc::context * ctxt)489 make_pass_test_show_locus (gcc::context *ctxt)
490 {
491   return new pass_test_show_locus (ctxt);
492 }
493 
494 int
plugin_init(struct plugin_name_args * plugin_info,struct plugin_gcc_version * version)495 plugin_init (struct plugin_name_args *plugin_info,
496 	     struct plugin_gcc_version *version)
497 {
498   struct register_pass_info pass_info;
499   const char *plugin_name = plugin_info->base_name;
500   int argc = plugin_info->argc;
501   struct plugin_argument *argv = plugin_info->argv;
502 
503   if (!plugin_default_version_check (version, &gcc_version))
504     return 1;
505 
506   for (int i = 0; i < argc; i++)
507     {
508       if (0 == strcmp (argv[i].key, "color"))
509 	force_show_locus_color = true;
510     }
511 
512   pass_info.pass = make_pass_test_show_locus (g);
513   pass_info.reference_pass_name = "ssa";
514   pass_info.ref_pass_instance_number = 1;
515   pass_info.pos_op = PASS_POS_INSERT_AFTER;
516   register_callback (plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL,
517 		     &pass_info);
518 
519   return 0;
520 }
521