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