blob: 482dbda47f73c4230f185b717dd1eea33145f616 [file] [log] [blame]
/* { dg-options "-O" } */
/* This plugin exercises the diagnostics-printing code.
The goal is to unit-test the range-printing code without needing any
correct range data within the compiler's IR. We can't use any real
diagnostics for this, so we have to fake it, hence this plugin.
There are two test files used with this code:
diagnostic-test-show-locus-ascii-bw.c
..........................-ascii-color.c
to exercise uncolored vs colored output by supplying plugin arguments
to hack in the desired behavior:
-fplugin-arg-diagnostic_plugin_test_show_locus-color
The test files contain functions, but the body of each
function is disabled using the preprocessor. The plugin detects
the functions by name, and inject diagnostics within them, using
hard-coded locations relative to the top of each function.
The plugin uses a function "get_loc" below to map from line/column
numbers to location_t, and this relies on input_location being in
the same ordinary line_map as the locations in question. The plugin
runs after parsing, so input_location will be at the end of the file.
This need for all of the test code to be in a single ordinary line map
means that each test file needs to have a very long line near the top
(potentially to cover the extra byte-count of colorized data),
to ensure that further very long lines don't start a new linemap.
This also means that we can't use macros in the test files. */
#include "gcc-plugin.h"
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "tm.h"
#include "tree.h"
#include "stringpool.h"
#include "toplev.h"
#include "basic-block.h"
#include "hash-table.h"
#include "vec.h"
#include "ggc.h"
#include "basic-block.h"
#include "tree-ssa-alias.h"
#include "internal-fn.h"
#include "gimple-fold.h"
#include "tree-eh.h"
#include "gimple-expr.h"
#include "is-a.h"
#include "gimple.h"
#include "gimple-iterator.h"
#include "tree.h"
#include "tree-pass.h"
#include "intl.h"
#include "plugin-version.h"
#include "diagnostic.h"
#include "context.h"
#include "print-tree.h"
#include "gcc-rich-location.h"
int plugin_is_GPL_compatible;
const pass_data pass_data_test_show_locus =
{
GIMPLE_PASS, /* type */
"test_show_locus", /* name */
OPTGROUP_NONE, /* optinfo_flags */
TV_NONE, /* tv_id */
PROP_ssa, /* properties_required */
0, /* properties_provided */
0, /* properties_destroyed */
0, /* todo_flags_start */
0, /* todo_flags_finish */
};
class pass_test_show_locus : public gimple_opt_pass
{
public:
pass_test_show_locus(gcc::context *ctxt)
: gimple_opt_pass(pass_data_test_show_locus, ctxt)
{}
/* opt_pass methods: */
bool gate (function *) { return true; }
virtual unsigned int execute (function *);
}; // class pass_test_show_locus
/* Given LINE_NUM and COL_NUM, generate a location_t in the
current file, relative to input_location. This relies on the
location being expressible in the same ordinary line_map as
input_location (which is typically at the end of the source file
when this is called). Hence the test files we compile with this
plugin must have an initial very long line (to avoid long lines
starting a new line map), and must not use macros.
COL_NUM uses the Emacs convention of 0-based column numbers. */
static location_t
get_loc (unsigned int line_num, unsigned int col_num)
{
/* Use input_location to get the relevant line_map */
const struct line_map_ordinary *line_map
= (const line_map_ordinary *)(linemap_lookup (line_table,
input_location));
/* Convert from 0-based column numbers to 1-based column numbers. */
location_t loc
= linemap_position_for_line_and_column (line_table,
line_map,
line_num, col_num + 1);
return loc;
}
/* Was "color" passed in as a plugin argument? */
static bool force_show_locus_color = false;
/* We want to verify the colorized output of diagnostic_show_locus,
but turning on colorization for everything confuses "dg-warning" etc.
Hence we special-case it within this plugin by using this modified
version of default_diagnostic_finalizer, which, if "color" is
passed in as a plugin argument turns on colorization, but just
for diagnostic_show_locus. */
static void
custom_diagnostic_finalizer (diagnostic_context *context,
diagnostic_info *diagnostic,
diagnostic_t)
{
bool old_show_color = pp_show_color (context->printer);
if (force_show_locus_color)
pp_show_color (context->printer) = true;
char *saved_prefix = pp_take_prefix (context->printer);
pp_set_prefix (context->printer, NULL);
pp_newline (context->printer);
diagnostic_show_locus (context, diagnostic->richloc, diagnostic->kind);
pp_show_color (context->printer) = old_show_color;
pp_set_prefix (context->printer, saved_prefix);
pp_flush (context->printer);
}
/* Add a location to RICHLOC with caret==start at START, ranging to FINISH. */
static void
add_range (rich_location *richloc, location_t start, location_t finish,
enum range_display_kind range_display_kind
= SHOW_RANGE_WITHOUT_CARET,
const range_label *label = NULL)
{
richloc->add_range (make_location (start, start, finish), range_display_kind,
label);
}
/* Exercise the diagnostic machinery to emit various warnings,
for use by diagnostic-test-show-locus-*.c.
We inject each warning relative to the start of a function,
which avoids lots of hardcoded absolute locations. */
static void
test_show_locus (function *fun)
{
tree fndecl = fun->decl;
tree identifier = DECL_NAME (fndecl);
const char *fnname = IDENTIFIER_POINTER (identifier);
location_t fnstart = fun->function_start_locus;
int fnstart_line = LOCATION_LINE (fnstart);
diagnostic_finalizer (global_dc) = custom_diagnostic_finalizer;
/* Hardcode the "terminal width", to verify the behavior of
very wide lines. */
global_dc->caret_max_width = 71;
if (0 == strcmp (fnname, "test_simple"))
{
const int line = fnstart_line + 2;
rich_location richloc (line_table, get_loc (line, 15));
add_range (&richloc, get_loc (line, 10), get_loc (line, 14));
add_range (&richloc, get_loc (line, 16), get_loc (line, 16));
warning_at (&richloc, 0, "test");
}
if (0 == strcmp (fnname, "test_simple_2"))
{
const int line = fnstart_line + 2;
rich_location richloc (line_table, get_loc (line, 24));
add_range (&richloc, get_loc (line, 6), get_loc (line, 22));
add_range (&richloc, get_loc (line, 26), get_loc (line, 43));
warning_at (&richloc, 0, "test");
}
if (0 == strcmp (fnname, "test_multiline"))
{
const int line = fnstart_line + 2;
text_range_label label ("label");
rich_location richloc (line_table, get_loc (line + 1, 7), &label);
add_range (&richloc, get_loc (line, 7), get_loc (line, 23));
add_range (&richloc, get_loc (line + 1, 9), get_loc (line + 1, 26));
warning_at (&richloc, 0, "test");
}
if (0 == strcmp (fnname, "test_many_lines"))
{
const int line = fnstart_line + 2;
text_range_label label0 ("label 0");
text_range_label label1 ("label 1");
text_range_label label2 ("label 2");
rich_location richloc (line_table, get_loc (line + 5, 7), &label0);
add_range (&richloc, get_loc (line, 7), get_loc (line + 4, 65),
SHOW_RANGE_WITHOUT_CARET, &label1);
add_range (&richloc, get_loc (line + 5, 9), get_loc (line + 10, 61),
SHOW_RANGE_WITHOUT_CARET, &label2);
warning_at (&richloc, 0, "test");
}
/* Example of a rich_location where the range is larger than
one character. */
if (0 == strcmp (fnname, "test_richloc_from_proper_range"))
{
const int line = fnstart_line + 2;
location_t start = get_loc (line, 12);
location_t finish = get_loc (line, 16);
rich_location richloc (line_table, make_location (start, start, finish));
warning_at (&richloc, 0, "test");
}
/* Example of a single-range location where the range starts
before the caret. */
if (0 == strcmp (fnname, "test_caret_within_proper_range"))
{
const int line = fnstart_line + 2;
warning_at (make_location (get_loc (line, 16), get_loc (line, 12),
get_loc (line, 20)),
0, "test");
}
/* Example of a very wide line, where the information of interest
is beyond the width of the terminal (hardcoded above), with
a secondary location that exactly fits on the left-margin. */
if (0 == strcmp (fnname, "test_very_wide_line"))
{
const int line = fnstart_line + 2;
global_dc->show_ruler_p = true;
text_range_label label0 ("label 0");
text_range_label label1 ("label 1");
rich_location richloc (line_table,
make_location (get_loc (line, 94),
get_loc (line, 90),
get_loc (line, 98)),
&label0);
richloc.add_range (get_loc (line, 35), SHOW_RANGE_WITHOUT_CARET,
&label1);
richloc.add_fixit_replace ("bar * foo");
warning_at (&richloc, 0, "test");
global_dc->show_ruler_p = false;
}
/* Likewise, but with a secondary location that's immediately before
the left margin; the location and label should be gracefully dropped. */
if (0 == strcmp (fnname, "test_very_wide_line_2"))
{
const int line = fnstart_line + 2;
global_dc->show_ruler_p = true;
text_range_label label0 ("label 0");
text_range_label label1 ("label 1");
rich_location richloc (line_table,
make_location (get_loc (line, 94),
get_loc (line, 90),
get_loc (line, 98)),
&label0);
richloc.add_fixit_replace ("bar * foo");
richloc.add_range (get_loc (line, 34), SHOW_RANGE_WITHOUT_CARET,
&label1);
warning_at (&richloc, 0, "test");
global_dc->show_ruler_p = false;
}
/* Example of multiple carets. */
if (0 == strcmp (fnname, "test_multiple_carets"))
{
const int line = fnstart_line + 2;
location_t caret_a = get_loc (line, 7);
location_t caret_b = get_loc (line, 11);
rich_location richloc (line_table, caret_a);
add_range (&richloc, caret_b, caret_b, SHOW_RANGE_WITH_CARET);
global_dc->caret_chars[0] = 'A';
global_dc->caret_chars[1] = 'B';
warning_at (&richloc, 0, "test");
global_dc->caret_chars[0] = '^';
global_dc->caret_chars[1] = '^';
}
/* Tests of rendering fixit hints. */
if (0 == strcmp (fnname, "test_fixit_insert"))
{
const int line = fnstart_line + 2;
location_t start = get_loc (line, 19);
location_t finish = get_loc (line, 22);
rich_location richloc (line_table, make_location (start, start, finish));
richloc.add_fixit_insert_before ("{");
richloc.add_fixit_insert_after ("}");
warning_at (&richloc, 0, "example of insertion hints");
}
if (0 == strcmp (fnname, "test_fixit_insert_newline"))
{
const int line = fnstart_line + 6;
location_t line_start = get_loc (line, 0);
location_t case_start = get_loc (line, 4);
location_t case_finish = get_loc (line, 11);
location_t case_loc = make_location (case_start, case_start, case_finish);
rich_location richloc (line_table, case_loc);
richloc.add_fixit_insert_before (line_start, " break;\n");
warning_at (&richloc, 0, "example of newline insertion hint");
}
if (0 == strcmp (fnname, "test_fixit_remove"))
{
const int line = fnstart_line + 2;
location_t start = get_loc (line, 8);
location_t finish = get_loc (line, 8);
rich_location richloc (line_table, make_location (start, start, finish));
source_range src_range;
src_range.m_start = start;
src_range.m_finish = finish;
richloc.add_fixit_remove (src_range);
warning_at (&richloc, 0, "example of a removal hint");
}
if (0 == strcmp (fnname, "test_fixit_replace"))
{
const int line = fnstart_line + 2;
location_t start = get_loc (line, 2);
location_t finish = get_loc (line, 19);
rich_location richloc (line_table, make_location (start, start, finish));
source_range src_range;
src_range.m_start = start;
src_range.m_finish = finish;
richloc.add_fixit_replace (src_range, "gtk_widget_show_all");
warning_at (&richloc, 0, "example of a replacement hint");
}
if (0 == strcmp (fnname, "test_mutually_exclusive_suggestions"))
{
const int line = fnstart_line + 2;
location_t start = get_loc (line, 2);
location_t finish = get_loc (line, 9);
source_range src_range;
src_range.m_start = start;
src_range.m_finish = finish;
{
rich_location richloc (line_table, make_location (start, start, finish));
richloc.add_fixit_replace (src_range, "replacement_1");
richloc.fixits_cannot_be_auto_applied ();
warning_at (&richloc, 0, "warning 1");
}
{
rich_location richloc (line_table, make_location (start, start, finish));
richloc.add_fixit_replace (src_range, "replacement_2");
richloc.fixits_cannot_be_auto_applied ();
warning_at (&richloc, 0, "warning 2");
}
}
/* Tests of gcc_rich_location::add_fixit_insert_formatted. */
if (0 == strcmp (fnname, "test_add_fixit_insert_formatted_single_line"))
{
const int line = fnstart_line + 1;
location_t insertion_point = get_loc (line, 3);
location_t indent = get_loc (line, 2);
gcc_rich_location richloc (insertion_point);
richloc.add_fixit_insert_formatted ("INSERTED-CONTENT",
insertion_point, indent);
inform (&richloc, "single-line insertion");
}
if (0 == strcmp (fnname, "test_add_fixit_insert_formatted_multiline"))
{
location_t insertion_point = fun->function_end_locus;
location_t indent = get_loc (fnstart_line + 1, 2);
gcc_rich_location richloc (insertion_point);
richloc.add_fixit_insert_formatted ("INSERTED-CONTENT",
insertion_point, indent);
inform (&richloc, "multiline insertion");
}
/* Example of two carets where both carets appear to have an off-by-one
error appearing one column early.
Seen with gfortran.dg/associate_5.f03.
In an earlier version of the printer, the printing of caret 0 aka
"1" was suppressed due to it appearing within the leading whitespace
before the text in its line. Ensure that we at least faithfully
print both carets, at the given (erroneous) locations. */
if (0 == strcmp (fnname, "test_caret_on_leading_whitespace"))
{
const int line = fnstart_line + 3;
location_t caret_a = get_loc (line, 5);
location_t caret_b = get_loc (line - 1, 19);
rich_location richloc (line_table, caret_a);
richloc.add_range (caret_b, SHOW_RANGE_WITH_CARET);
global_dc->caret_chars[0] = '1';
global_dc->caret_chars[1] = '2';
warning_at (&richloc, 0, "test");
global_dc->caret_chars[0] = '^';
global_dc->caret_chars[1] = '^';
}
/* Example of using the "%q+D" format code, which as well as printing
a quoted decl, overrides the given location to use the location of
the decl. */
if (0 == strcmp (fnname, "test_percent_q_plus_d"))
{
const int line = fnstart_line + 3;
tree local = (*fun->local_decls)[0];
warning_at (input_location, 0,
"example of plus in format code for %q+D", local);
}
/* Example of many locations and many fixits.
Underline (separately) every word in a comment, and convert them
to upper case. Give all of the ranges labels (sharing one label). */
if (0 == strcmp (fnname, "test_many_nested_locations"))
{
const char *file = LOCATION_FILE (fnstart);
const int start_line = fnstart_line + 2;
const int finish_line = start_line + 7;
location_t loc = get_loc (start_line - 1, 2);
text_range_label label ("label");
rich_location richloc (line_table, loc);
for (int line = start_line; line <= finish_line; line++)
{
char_span content = location_get_source_line (file, line);
gcc_assert (content);
/* Split line up into words. */
for (int idx = 0; idx < content.length (); idx++)
{
if (ISALPHA (content[idx]))
{
int start_idx = idx;
while (idx < content.length () && ISALPHA (content[idx]))
idx++;
if (idx == content.length () || !ISALPHA (content[idx]))
{
location_t start_of_word = get_loc (line, start_idx);
location_t end_of_word = get_loc (line, idx - 1);
location_t word
= make_location (start_of_word, start_of_word,
end_of_word);
richloc.add_range (word, SHOW_RANGE_WITH_CARET, &label);
/* Add a fixit, converting to upper case. */
char_span word_span = content.subspan (start_idx, idx - start_idx);
char *copy = word_span.xstrdup ();
for (char *ch = copy; *ch; ch++)
*ch = TOUPPER (*ch);
richloc.add_fixit_replace (word, copy);
free (copy);
}
}
}
}
/* Verify that we added enough locations to fully exercise
rich_location. We want to exceed both the
statically-allocated buffer in class rich_location,
and then trigger a reallocation of the dynamic buffer. */
gcc_assert (richloc.get_num_locations () > 3 + (2 * 16));
warning_at (&richloc, 0, "test of %i locations",
richloc.get_num_locations ());
}
}
unsigned int
pass_test_show_locus::execute (function *fun)
{
test_show_locus (fun);
return 0;
}
static gimple_opt_pass *
make_pass_test_show_locus (gcc::context *ctxt)
{
return new pass_test_show_locus (ctxt);
}
int
plugin_init (struct plugin_name_args *plugin_info,
struct plugin_gcc_version *version)
{
struct register_pass_info pass_info;
const char *plugin_name = plugin_info->base_name;
int argc = plugin_info->argc;
struct plugin_argument *argv = plugin_info->argv;
if (!plugin_default_version_check (version, &gcc_version))
return 1;
for (int i = 0; i < argc; i++)
{
if (0 == strcmp (argv[i].key, "color"))
force_show_locus_color = true;
}
pass_info.pass = make_pass_test_show_locus (g);
pass_info.reference_pass_name = "ssa";
pass_info.ref_pass_instance_number = 1;
pass_info.pos_op = PASS_POS_INSERT_AFTER;
register_callback (plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL,
&pass_info);
return 0;
}