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.h"
      51  #include "gimple-iterator.h"
      52  #include "gimple-fold.h"
      53  #include "tree-eh.h"
      54  #include "gimple-expr.h"
      55  #include "is-a.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:
      83    pass_test_show_locus(gcc::context *ctxt)
      84      : gimple_opt_pass(pass_data_test_show_locus, ctxt)
      85    {}
      86  
      87    /* opt_pass methods: */
      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
     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
     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
     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
     482  pass_test_show_locus::execute (function *fun)
     483  {
     484    test_show_locus (fun);
     485    return 0;
     486  }
     487  
     488  static gimple_opt_pass *
     489  make_pass_test_show_locus (gcc::context *ctxt)
     490  {
     491    return new pass_test_show_locus (ctxt);
     492  }
     493  
     494  int
     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  }