1 /* Determining the results of applying fix-it hints.
2    Copyright (C) 2016-2019 Free Software Foundation, Inc.
3 
4 This file is part of GCC.
5 
6 GCC is free software; you can redistribute it and/or modify it under
7 the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 3, or (at your option) any later
9 version.
10 
11 GCC is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GCC; see the file COPYING3.  If not see
18 <http://www.gnu.org/licenses/>.  */
19 
20 #include "config.h"
21 #include "system.h"
22 #include "coretypes.h"
23 #include "line-map.h"
24 #include "edit-context.h"
25 #include "pretty-print.h"
26 #include "diagnostic-color.h"
27 #include "selftest.h"
28 
29 /* This file implements a way to track the effect of fix-its,
30    via a class edit_context; the other classes are support classes for
31    edit_context.
32 
33    A complication here is that fix-its are expressed relative to coordinates
34    in the file when it was parsed, before any changes have been made, and
35    so if there's more that one fix-it to be applied, we have to adjust
36    later fix-its to allow for the changes made by earlier ones.  This
37    is done by the various "get_effective_column" methods.
38 
39    The "filename" params are required to outlive the edit_context (no
40    copy of the underlying str is taken, just the ptr).  */
41 
42 /* Forward decls.  class edit_context is declared within edit-context.h.
43    The other types are declared here.  */
44 class edit_context;
45 class edited_file;
46 class edited_line;
47 class line_event;
48 
49 /* A struct to hold the params of a print_diff call.  */
50 
51 struct diff
52 {
diffdiff53   diff (pretty_printer *pp, bool show_filenames)
54   : m_pp (pp), m_show_filenames (show_filenames) {}
55 
56   pretty_printer *m_pp;
57   bool m_show_filenames;
58 };
59 
60 /* The state of one named file within an edit_context: the filename,
61    and the lines that have been edited so far.  */
62 
63 class edited_file
64 {
65  public:
66   edited_file (const char *filename);
67   static void delete_cb (edited_file *file);
68 
get_filename()69   const char *get_filename () const { return m_filename; }
70   char *get_content ();
71 
72   bool apply_fixit (int line, int start_column,
73 		    int next_column,
74 		    const char *replacement_str,
75 		    int replacement_len);
76   int get_effective_column (int line, int column);
77 
call_print_diff(const char *,edited_file * file,void * user_data)78   static int call_print_diff (const char *, edited_file *file,
79 			      void *user_data)
80   {
81     diff *d = (diff *)user_data;
82     file->print_diff (d->m_pp, d->m_show_filenames);
83     return 0;
84   }
85 
86  private:
87   bool print_content (pretty_printer *pp);
88   void print_diff (pretty_printer *pp, bool show_filenames);
89   int print_diff_hunk (pretty_printer *pp, int old_start_of_hunk,
90 		       int old_end_of_hunk, int new_start_of_hunk);
91   edited_line *get_line (int line);
92   edited_line *get_or_insert_line (int line);
93   int get_num_lines (bool *missing_trailing_newline);
94 
95   int get_effective_line_count (int old_start_of_hunk,
96 				int old_end_of_hunk);
97 
98   void print_run_of_changed_lines (pretty_printer *pp,
99 				   int start_of_run,
100 				   int end_of_run);
101 
102   const char *m_filename;
103   typed_splay_tree<int, edited_line *> m_edited_lines;
104   int m_num_lines;
105 };
106 
107 /* A line added before an edited_line.  */
108 
109 class added_line
110 {
111  public:
added_line(const char * content,int len)112   added_line (const char *content, int len)
113   : m_content (xstrndup (content, len)), m_len (len) {}
~added_line()114   ~added_line () { free (m_content); }
115 
get_content()116   const char *get_content () const { return m_content; }
get_len()117   int get_len () const { return m_len; }
118 
119  private:
120   char *m_content;
121   int m_len;
122 };
123 
124 /* The state of one edited line within an edited_file.
125    As well as the current content of the line, it contains a record of
126    the changes, so that further changes can be applied in the correct
127    place.
128 
129    When handling fix-it hints containing newlines, new lines are added
130    as added_line predecessors to an edited_line.  Hence it's possible
131    for an "edited_line" to not actually have been changed, but to merely
132    be a placeholder for the lines added before it.  This can be tested
133    for with actuall_edited_p, and has a slight effect on how diff hunks
134    are generated.  */
135 
136 class edited_line
137 {
138  public:
139   edited_line (const char *filename, int line_num);
140   ~edited_line ();
141   static void delete_cb (edited_line *el);
142 
get_line_num()143   int get_line_num () const { return m_line_num; }
get_content()144   const char *get_content () const { return m_content; }
get_len()145   int get_len () const { return m_len; }
146 
147   int get_effective_column (int orig_column) const;
148   bool apply_fixit (int start_column,
149 		    int next_column,
150 		    const char *replacement_str,
151 		    int replacement_len);
152 
153   int get_effective_line_count () const;
154 
155   /* Has the content of this line actually changed, or are we merely
156      recording predecessor added_lines?  */
actually_edited_p()157   bool actually_edited_p () const { return m_line_events.length () > 0; }
158 
159   void print_content (pretty_printer *pp) const;
160   void print_diff_lines (pretty_printer *pp) const;
161 
162  private:
163   void ensure_capacity (int len);
164   void ensure_terminated ();
165 
166   int m_line_num;
167   char *m_content;
168   int m_len;
169   int m_alloc_sz;
170   auto_vec <line_event> m_line_events;
171   auto_vec <added_line *> m_predecessors;
172 };
173 
174 /* Class for representing edit events that have occurred on one line of
175    one file: the replacement of some text betweeen some columns
176    on the line.
177 
178    Subsequent events will need their columns adjusting if they're
179    are on this line and their column is >= the start point.  */
180 
181 class line_event
182 {
183  public:
line_event(int start,int next,int len)184   line_event (int start, int next, int len) : m_start (start),
185     m_delta (len - (next - start)) {}
186 
get_effective_column(int orig_column)187   int get_effective_column (int orig_column) const
188   {
189     if (orig_column >= m_start)
190       return orig_column += m_delta;
191     else
192       return orig_column;
193   }
194 
195  private:
196   int m_start;
197   int m_delta;
198 };
199 
200 /* Forward decls.  */
201 
202 static void
203 print_diff_line (pretty_printer *pp, char prefix_char,
204 		 const char *line, int line_size);
205 
206 /* Implementation of class edit_context.  */
207 
208 /* edit_context's ctor.  */
209 
edit_context()210 edit_context::edit_context ()
211 : m_valid (true),
212   m_files (strcmp, NULL, edited_file::delete_cb)
213 {}
214 
215 /* Add any fixits within RICHLOC to this context, recording the
216    changes that they make.  */
217 
218 void
add_fixits(rich_location * richloc)219 edit_context::add_fixits (rich_location *richloc)
220 {
221   if (!m_valid)
222     return;
223   if (richloc->seen_impossible_fixit_p ())
224     {
225       m_valid = false;
226       return;
227     }
228   for (unsigned i = 0; i < richloc->get_num_fixit_hints (); i++)
229     {
230       const fixit_hint *hint = richloc->get_fixit_hint (i);
231       if (!apply_fixit (hint))
232 	m_valid = false;
233     }
234 }
235 
236 /* Get the content of the given file, with fix-its applied.
237    If any errors occurred in this edit_context, return NULL.
238    The ptr should be freed by the caller.  */
239 
240 char *
get_content(const char * filename)241 edit_context::get_content (const char *filename)
242 {
243   if (!m_valid)
244     return NULL;
245   edited_file &file = get_or_insert_file (filename);
246   return file.get_content ();
247 }
248 
249 /* Map a location before the edits to a column number after the edits.
250    This method is for the selftests.  */
251 
252 int
get_effective_column(const char * filename,int line,int column)253 edit_context::get_effective_column (const char *filename, int line,
254 				    int column)
255 {
256   edited_file *file = get_file (filename);
257   if (!file)
258     return column;
259   return file->get_effective_column (line, column);
260 }
261 
262 /* Generate a unified diff.  The resulting string should be freed by the
263    caller.  Primarily for selftests.
264    If any errors occurred in this edit_context, return NULL.  */
265 
266 char *
generate_diff(bool show_filenames)267 edit_context::generate_diff (bool show_filenames)
268 {
269   if (!m_valid)
270     return NULL;
271 
272   pretty_printer pp;
273   print_diff (&pp, show_filenames);
274   return xstrdup (pp_formatted_text (&pp));
275 }
276 
277 /* Print a unified diff to PP, showing the changes made within the
278    context.  */
279 
280 void
print_diff(pretty_printer * pp,bool show_filenames)281 edit_context::print_diff (pretty_printer *pp, bool show_filenames)
282 {
283   if (!m_valid)
284     return;
285   diff d (pp, show_filenames);
286   m_files.foreach (edited_file::call_print_diff, &d);
287 }
288 
289 /* Attempt to apply the given fixit.  Return true if it can be
290    applied, or false otherwise.  */
291 
292 bool
apply_fixit(const fixit_hint * hint)293 edit_context::apply_fixit (const fixit_hint *hint)
294 {
295   expanded_location start = expand_location (hint->get_start_loc ());
296   expanded_location next_loc = expand_location (hint->get_next_loc ());
297   if (start.file != next_loc.file)
298     return false;
299   if (start.line != next_loc.line)
300     return false;
301   if (start.column == 0)
302     return false;
303   if (next_loc.column == 0)
304     return false;
305 
306   edited_file &file = get_or_insert_file (start.file);
307   if (!m_valid)
308     return false;
309   return file.apply_fixit (start.line, start.column, next_loc.column,
310 			   hint->get_string (),
311 			   hint->get_length ());
312 }
313 
314 /* Locate the edited_file * for FILENAME, if any
315    Return NULL if there isn't one.  */
316 
317 edited_file *
get_file(const char * filename)318 edit_context::get_file (const char *filename)
319 {
320   gcc_assert (filename);
321   return m_files.lookup (filename);
322 }
323 
324 /* Locate the edited_file for FILENAME, adding one if there isn't one.  */
325 
326 edited_file &
get_or_insert_file(const char * filename)327 edit_context::get_or_insert_file (const char *filename)
328 {
329   gcc_assert (filename);
330 
331   edited_file *file = get_file (filename);
332   if (file)
333     return *file;
334 
335   /* Not found.  */
336   file = new edited_file (filename);
337   m_files.insert (filename, file);
338   return *file;
339 }
340 
341 /* Implementation of class edited_file.  */
342 
343 /* Callback for m_edited_lines, for comparing line numbers.  */
344 
line_comparator(int a,int b)345 static int line_comparator (int a, int b)
346 {
347   return a - b;
348 }
349 
350 /* edited_file's constructor.  */
351 
edited_file(const char * filename)352 edited_file::edited_file (const char *filename)
353 : m_filename (filename),
354   m_edited_lines (line_comparator, NULL, edited_line::delete_cb),
355   m_num_lines (-1)
356 {
357 }
358 
359 /* A callback for deleting edited_file *, for use as a
360    delete_value_fn for edit_context::m_files.  */
361 
362 void
delete_cb(edited_file * file)363 edited_file::delete_cb (edited_file *file)
364 {
365   delete file;
366 }
367 
368 /* Get the content of the file, with fix-its applied.
369    The ptr should be freed by the caller.  */
370 
371 char *
get_content()372 edited_file::get_content ()
373 {
374   pretty_printer pp;
375   if (!print_content (&pp))
376     return NULL;
377   return xstrdup (pp_formatted_text (&pp));
378 }
379 
380 /* Attempt to replace columns START_COLUMN up to but not including NEXT_COLUMN
381    of LINE with the string REPLACEMENT_STR of length REPLACEMENT_LEN,
382    updating the in-memory copy of the line, and the record of edits to
383    the line.  */
384 
385 bool
apply_fixit(int line,int start_column,int next_column,const char * replacement_str,int replacement_len)386 edited_file::apply_fixit (int line, int start_column, int next_column,
387 			  const char *replacement_str,
388 			  int replacement_len)
389 {
390   edited_line *el = get_or_insert_line (line);
391   if (!el)
392     return false;
393   return el->apply_fixit (start_column, next_column, replacement_str,
394 			  replacement_len);
395 }
396 
397 /* Given line LINE, map from COLUMN in the input file to its current
398    column after edits have been applied.  */
399 
400 int
get_effective_column(int line,int column)401 edited_file::get_effective_column (int line, int column)
402 {
403   const edited_line *el = get_line (line);
404   if (!el)
405     return column;
406   return el->get_effective_column (column);
407 }
408 
409 /* Attempt to print the content of the file to PP, with edits applied.
410    Return true if successful, false otherwise.  */
411 
412 bool
print_content(pretty_printer * pp)413 edited_file::print_content (pretty_printer *pp)
414 {
415   bool missing_trailing_newline;
416   int line_count = get_num_lines (&missing_trailing_newline);
417   for (int line_num = 1; line_num <= line_count; line_num++)
418     {
419       edited_line *el = get_line (line_num);
420       if (el)
421 	el->print_content (pp);
422       else
423 	{
424 	  char_span line = location_get_source_line (m_filename, line_num);
425 	  if (!line)
426 	    return false;
427 	  for (size_t i = 0; i < line.length (); i++)
428 	    pp_character (pp, line[i]);
429 	}
430       if (line_num < line_count)
431 	pp_character (pp, '\n');
432     }
433 
434   if (!missing_trailing_newline)
435     pp_character (pp, '\n');
436 
437   return true;
438 }
439 
440 /* Print a unified diff to PP, showing any changes that have occurred
441    to this file.  */
442 
443 void
print_diff(pretty_printer * pp,bool show_filenames)444 edited_file::print_diff (pretty_printer *pp, bool show_filenames)
445 {
446   if (show_filenames)
447     {
448       pp_string (pp, colorize_start (pp_show_color (pp), "diff-filename"));
449       pp_printf (pp, "--- %s\n", m_filename);
450       pp_printf (pp, "+++ %s\n", m_filename);
451       pp_string (pp, colorize_stop (pp_show_color (pp)));
452     }
453 
454   edited_line *el = m_edited_lines.min ();
455 
456   bool missing_trailing_newline;
457   int line_count = get_num_lines (&missing_trailing_newline);
458 
459   const int context_lines = 3;
460 
461   /* Track new line numbers minus old line numbers.  */
462 
463   int line_delta = 0;
464 
465   while (el)
466     {
467       int start_of_hunk = el->get_line_num ();
468       start_of_hunk -= context_lines;
469       if (start_of_hunk < 1)
470 	start_of_hunk = 1;
471 
472       /* Locate end of hunk, merging in changed lines
473 	 that are sufficiently close.  */
474       while (true)
475 	{
476 	  edited_line *next_el
477 	    = m_edited_lines.successor (el->get_line_num ());
478 	  if (!next_el)
479 	    break;
480 
481 	  int end_of_printed_hunk = el->get_line_num () + context_lines;
482 	  if (!el->actually_edited_p ())
483 	    end_of_printed_hunk--;
484 
485 	  if (end_of_printed_hunk
486 	      >= next_el->get_line_num () - context_lines)
487 	    el = next_el;
488 	  else
489 	    break;
490 	}
491 
492       int end_of_hunk = el->get_line_num ();
493       end_of_hunk += context_lines;
494       if (!el->actually_edited_p ())
495 	end_of_hunk--;
496       if (end_of_hunk > line_count)
497 	end_of_hunk = line_count;
498 
499       int new_start_of_hunk = start_of_hunk + line_delta;
500       line_delta += print_diff_hunk (pp, start_of_hunk, end_of_hunk,
501 				     new_start_of_hunk);
502       el = m_edited_lines.successor (el->get_line_num ());
503     }
504 }
505 
506 /* Print one hunk within a unified diff to PP, covering the
507    given range of lines.  OLD_START_OF_HUNK and OLD_END_OF_HUNK are
508    line numbers in the unedited version of the file.
509    NEW_START_OF_HUNK is a line number in the edited version of the file.
510    Return the change in the line count within the hunk.  */
511 
512 int
print_diff_hunk(pretty_printer * pp,int old_start_of_hunk,int old_end_of_hunk,int new_start_of_hunk)513 edited_file::print_diff_hunk (pretty_printer *pp, int old_start_of_hunk,
514 			      int old_end_of_hunk, int new_start_of_hunk)
515 {
516   int old_num_lines = old_end_of_hunk - old_start_of_hunk + 1;
517   int new_num_lines
518     = get_effective_line_count (old_start_of_hunk, old_end_of_hunk);
519 
520   pp_string (pp, colorize_start (pp_show_color (pp), "diff-hunk"));
521   pp_printf (pp, "@@ -%i,%i +%i,%i @@\n", old_start_of_hunk, old_num_lines,
522 	     new_start_of_hunk, new_num_lines);
523   pp_string (pp, colorize_stop (pp_show_color (pp)));
524 
525   int line_num = old_start_of_hunk;
526   while (line_num <= old_end_of_hunk)
527     {
528       edited_line *el = get_line (line_num);
529       if (el)
530 	{
531 	  /* We have an edited line.
532 	     Consolidate into runs of changed lines.  */
533 	  const int first_changed_line_in_run = line_num;
534 	  while (get_line (line_num))
535 	    line_num++;
536 	  const int last_changed_line_in_run = line_num - 1;
537 	  print_run_of_changed_lines (pp, first_changed_line_in_run,
538 				      last_changed_line_in_run);
539 	}
540       else
541 	{
542 	  /* Unchanged line.  */
543 	  char_span old_line = location_get_source_line (m_filename, line_num);
544 	  print_diff_line (pp, ' ', old_line.get_buffer (), old_line.length ());
545 	  line_num++;
546 	}
547     }
548 
549   return new_num_lines - old_num_lines;
550 }
551 
552 /* Subroutine of edited_file::print_diff_hunk: given a run of lines
553    from START_OF_RUN to END_OF_RUN that all have edited_line instances,
554    print the diff to PP.  */
555 
556 void
print_run_of_changed_lines(pretty_printer * pp,int start_of_run,int end_of_run)557 edited_file::print_run_of_changed_lines (pretty_printer *pp,
558 					 int start_of_run,
559 					 int end_of_run)
560 {
561   /* Show old version of lines.  */
562   pp_string (pp, colorize_start (pp_show_color (pp),
563 				 "diff-delete"));
564   for (int line_num = start_of_run;
565        line_num <= end_of_run;
566        line_num++)
567     {
568       edited_line *el_in_run = get_line (line_num);
569       gcc_assert (el_in_run);
570       if (el_in_run->actually_edited_p ())
571 	{
572 	  char_span old_line = location_get_source_line (m_filename, line_num);
573 	  print_diff_line (pp, '-', old_line.get_buffer (),
574 			   old_line.length ());
575 	}
576     }
577   pp_string (pp, colorize_stop (pp_show_color (pp)));
578 
579   /* Show new version of lines.  */
580   pp_string (pp, colorize_start (pp_show_color (pp),
581 				 "diff-insert"));
582   for (int line_num = start_of_run;
583        line_num <= end_of_run;
584        line_num++)
585     {
586       edited_line *el_in_run = get_line (line_num);
587       gcc_assert (el_in_run);
588       el_in_run->print_diff_lines (pp);
589     }
590   pp_string (pp, colorize_stop (pp_show_color (pp)));
591 }
592 
593 /* Print one line within a diff, starting with PREFIX_CHAR,
594    followed by the LINE of content, of length LEN.  LINE is
595    not necessarily 0-terminated.  Print a trailing newline.  */
596 
597 static void
print_diff_line(pretty_printer * pp,char prefix_char,const char * line,int len)598 print_diff_line (pretty_printer *pp, char prefix_char,
599 		 const char *line, int len)
600 {
601   pp_character (pp, prefix_char);
602   for (int i = 0; i < len; i++)
603     pp_character (pp, line[i]);
604   pp_character (pp, '\n');
605 }
606 
607 /* Determine the number of lines that will be present after
608    editing for the range of lines from OLD_START_OF_HUNK to
609    OLD_END_OF_HUNK inclusive.  */
610 
611 int
get_effective_line_count(int old_start_of_hunk,int old_end_of_hunk)612 edited_file::get_effective_line_count (int old_start_of_hunk,
613 				       int old_end_of_hunk)
614 {
615   int line_count = 0;
616   for (int old_line_num = old_start_of_hunk; old_line_num <= old_end_of_hunk;
617        old_line_num++)
618     {
619       edited_line *el = get_line (old_line_num);
620       if (el)
621 	line_count += el->get_effective_line_count ();
622       else
623 	line_count++;
624     }
625   return line_count;
626 }
627 
628 /* Get the state of LINE within the file, or NULL if it is untouched.  */
629 
630 edited_line *
get_line(int line)631 edited_file::get_line (int line)
632 {
633   return m_edited_lines.lookup (line);
634 }
635 
636 /* Get the state of LINE within the file, creating a state for it
637    if necessary.  Return NULL if an error occurs.  */
638 
639 edited_line *
get_or_insert_line(int line)640 edited_file::get_or_insert_line (int line)
641 {
642   edited_line *el = get_line (line);
643   if (el)
644     return el;
645   el = new edited_line (m_filename, line);
646   if (el->get_content () == NULL)
647     {
648       delete el;
649       return NULL;
650     }
651   m_edited_lines.insert (line, el);
652   return el;
653 }
654 
655 /* Get the total number of lines in m_content, writing
656    true to *MISSING_TRAILING_NEWLINE if the final line
657    if missing a newline, false otherwise.  */
658 
659 int
get_num_lines(bool * missing_trailing_newline)660 edited_file::get_num_lines (bool *missing_trailing_newline)
661 {
662   gcc_assert (missing_trailing_newline);
663   if (m_num_lines == -1)
664     {
665       m_num_lines = 0;
666       while (true)
667 	{
668 	  char_span line
669 	    = location_get_source_line (m_filename, m_num_lines + 1);
670 	  if (line)
671 	    m_num_lines++;
672 	  else
673 	    break;
674 	}
675     }
676   *missing_trailing_newline = location_missing_trailing_newline (m_filename);
677   return m_num_lines;
678 }
679 
680 /* Implementation of class edited_line.  */
681 
682 /* edited_line's ctor.  */
683 
edited_line(const char * filename,int line_num)684 edited_line::edited_line (const char *filename, int line_num)
685 : m_line_num (line_num),
686   m_content (NULL), m_len (0), m_alloc_sz (0),
687   m_line_events (),
688   m_predecessors ()
689 {
690   char_span line = location_get_source_line (filename, line_num);
691   if (!line)
692     return;
693   m_len = line.length ();
694   ensure_capacity (m_len);
695   memcpy (m_content, line.get_buffer (), m_len);
696   ensure_terminated ();
697 }
698 
699 /* edited_line's dtor.  */
700 
~edited_line()701 edited_line::~edited_line ()
702 {
703   unsigned i;
704   added_line *pred;
705 
706   free (m_content);
707   FOR_EACH_VEC_ELT (m_predecessors, i, pred)
708     delete pred;
709 }
710 
711 /* A callback for deleting edited_line *, for use as a
712    delete_value_fn for edited_file::m_edited_lines.  */
713 
714 void
delete_cb(edited_line * el)715 edited_line::delete_cb (edited_line *el)
716 {
717   delete el;
718 }
719 
720 /* Map a location before the edits to a column number after the edits,
721    within a specific line.  */
722 
723 int
get_effective_column(int orig_column)724 edited_line::get_effective_column (int orig_column) const
725 {
726   int i;
727   line_event *event;
728   FOR_EACH_VEC_ELT (m_line_events, i, event)
729     orig_column = event->get_effective_column (orig_column);
730   return orig_column;
731 }
732 
733 /* Attempt to replace columns START_COLUMN up to but not including
734    NEXT_COLUMN of the line with the string REPLACEMENT_STR of
735    length REPLACEMENT_LEN, updating the in-memory copy of the line,
736    and the record of edits to the line.
737    Return true if successful; false if an error occurred.  */
738 
739 bool
apply_fixit(int start_column,int next_column,const char * replacement_str,int replacement_len)740 edited_line::apply_fixit (int start_column,
741 			  int next_column,
742 			  const char *replacement_str,
743 			  int replacement_len)
744 {
745   /* Handle newlines.  They will only ever be at the end of the
746      replacement text, thanks to the filtering in rich_location.  */
747   if (replacement_len > 1)
748     if (replacement_str[replacement_len - 1] == '\n')
749       {
750 	/* Stash in m_predecessors, stripping off newline.  */
751 	m_predecessors.safe_push (new added_line (replacement_str,
752 						  replacement_len - 1));
753 	return true;
754       }
755 
756   start_column = get_effective_column (start_column);
757   next_column = get_effective_column (next_column);
758 
759   int start_offset = start_column - 1;
760   int next_offset = next_column - 1;
761 
762   gcc_assert (start_offset >= 0);
763   gcc_assert (next_offset >= 0);
764 
765   if (start_column > next_column)
766     return false;
767   if (start_offset >= (m_len + 1))
768     return false;
769   if (next_offset >= (m_len + 1))
770     return false;
771 
772   size_t victim_len = next_offset - start_offset;
773 
774   /* Ensure buffer is big enough.  */
775   size_t new_len = m_len + replacement_len - victim_len;
776   ensure_capacity (new_len);
777 
778   char *suffix = m_content + next_offset;
779   gcc_assert (suffix <= m_content + m_len);
780   size_t len_suffix = (m_content + m_len) - suffix;
781 
782   /* Move successor content into position.  They overlap, so use memmove.  */
783   memmove (m_content + start_offset + replacement_len,
784 	   suffix, len_suffix);
785 
786   /* Replace target content.  They don't overlap, so use memcpy.  */
787   memcpy (m_content + start_offset,
788 	  replacement_str,
789 	  replacement_len);
790 
791   m_len = new_len;
792 
793   ensure_terminated ();
794 
795   /* Record the replacement, so that future changes to the line can have
796      their column information adjusted accordingly.  */
797   m_line_events.safe_push (line_event (start_column, next_column,
798 				       replacement_len));
799   return true;
800 }
801 
802 /* Determine the number of lines that will be present after
803    editing for this line.  Typically this is just 1, but
804    if newlines have been added before this line, they will
805    also be counted.  */
806 
807 int
get_effective_line_count()808 edited_line::get_effective_line_count () const
809 {
810   return m_predecessors.length () + 1;
811 }
812 
813 /* Subroutine of edited_file::print_content.
814    Print this line and any new lines added before it, to PP.  */
815 
816 void
print_content(pretty_printer * pp)817 edited_line::print_content (pretty_printer *pp) const
818 {
819   unsigned i;
820   added_line *pred;
821   FOR_EACH_VEC_ELT (m_predecessors, i, pred)
822     {
823       pp_string (pp, pred->get_content ());
824       pp_newline (pp);
825     }
826   pp_string (pp, m_content);
827 }
828 
829 /* Subroutine of edited_file::print_run_of_changed_lines for
830    printing diff hunks to PP.
831    Print the '+' line for this line, and any newlines added
832    before it.
833    Note that if this edited_line was actually edited, the '-'
834    line has already been printed.  If it wasn't, then we merely
835    have a placeholder edited_line for adding newlines to, and
836    we need to print a ' ' line for the edited_line as we haven't
837    printed it yet.  */
838 
839 void
print_diff_lines(pretty_printer * pp)840 edited_line::print_diff_lines (pretty_printer *pp) const
841 {
842   unsigned i;
843   added_line *pred;
844   FOR_EACH_VEC_ELT (m_predecessors, i, pred)
845     print_diff_line (pp, '+', pred->get_content (),
846 		     pred->get_len ());
847   if (actually_edited_p ())
848     print_diff_line (pp, '+', m_content, m_len);
849   else
850     print_diff_line (pp, ' ', m_content, m_len);
851 }
852 
853 /* Ensure that the buffer for m_content is at least large enough to hold
854    a string of length LEN and its 0-terminator, doubling on repeated
855    allocations.  */
856 
857 void
ensure_capacity(int len)858 edited_line::ensure_capacity (int len)
859 {
860   /* Allow 1 extra byte for 0-termination.  */
861   if (m_alloc_sz < (len + 1))
862     {
863       size_t new_alloc_sz = (len + 1) * 2;
864       m_content = (char *)xrealloc (m_content, new_alloc_sz);
865       m_alloc_sz = new_alloc_sz;
866     }
867 }
868 
869 /* Ensure that m_content is 0-terminated.  */
870 
871 void
ensure_terminated()872 edited_line::ensure_terminated ()
873 {
874   /* 0-terminate the buffer.  */
875   gcc_assert (m_len < m_alloc_sz);
876   m_content[m_len] = '\0';
877 }
878 
879 #if CHECKING_P
880 
881 /* Selftests of code-editing.  */
882 
883 namespace selftest {
884 
885 /* A wrapper class for ensuring that the underlying pointer is freed.  */
886 
887 template <typename POINTER_T>
888 class auto_free
889 {
890  public:
auto_free(POINTER_T p)891   auto_free (POINTER_T p) : m_ptr (p) {}
~auto_free()892   ~auto_free () { free (m_ptr); }
893 
POINTER_T()894   operator POINTER_T () { return m_ptr; }
895 
896  private:
897   POINTER_T m_ptr;
898 };
899 
900 /* Verify that edit_context::get_content works for unedited files.  */
901 
902 static void
test_get_content()903 test_get_content ()
904 {
905   /* Test of empty file.  */
906   {
907     const char *content = ("");
908     temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
909     edit_context edit;
910     auto_free <char *> result = edit.get_content (tmp.get_filename ());
911     ASSERT_STREQ ("", result);
912   }
913 
914   /* Test of simple content.  */
915   {
916     const char *content = ("/* before */\n"
917 			   "foo = bar.field;\n"
918 			   "/* after */\n");
919     temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
920     edit_context edit;
921     auto_free <char *> result = edit.get_content (tmp.get_filename ());
922     ASSERT_STREQ ("/* before */\n"
923 		  "foo = bar.field;\n"
924 		  "/* after */\n", result);
925   }
926 
927   /* Test of omitting the trailing newline on the final line.  */
928   {
929     const char *content = ("/* before */\n"
930 			   "foo = bar.field;\n"
931 			   "/* after */");
932     temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
933     edit_context edit;
934     auto_free <char *> result = edit.get_content (tmp.get_filename ());
935     /* We should respect the omitted trailing newline.  */
936     ASSERT_STREQ ("/* before */\n"
937 		  "foo = bar.field;\n"
938 		  "/* after */", result);
939   }
940 }
941 
942 /* Test applying an "insert" fixit, using insert_before.  */
943 
944 static void
test_applying_fixits_insert_before(const line_table_case & case_)945 test_applying_fixits_insert_before (const line_table_case &case_)
946 {
947   /* Create a tempfile and write some text to it.
948      .........................0000000001111111.
949      .........................1234567890123456.  */
950   const char *old_content = ("/* before */\n"
951 			     "foo = bar.field;\n"
952 			     "/* after */\n");
953   temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
954   const char *filename = tmp.get_filename ();
955   line_table_test ltt (case_);
956   linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);
957 
958   /* Add a comment in front of "bar.field".  */
959   location_t start = linemap_position_for_column (line_table, 7);
960   rich_location richloc (line_table, start);
961   richloc.add_fixit_insert_before ("/* inserted */");
962 
963   if (start > LINE_MAP_MAX_LOCATION_WITH_COLS)
964     return;
965 
966   edit_context edit;
967   edit.add_fixits (&richloc);
968   auto_free <char *> new_content = edit.get_content (filename);
969   if (start <= LINE_MAP_MAX_LOCATION_WITH_COLS)
970     ASSERT_STREQ ("/* before */\n"
971 		  "foo = /* inserted */bar.field;\n"
972 		  "/* after */\n", new_content);
973 
974   /* Verify that locations on other lines aren't affected by the change.  */
975   ASSERT_EQ (100, edit.get_effective_column (filename, 1, 100));
976   ASSERT_EQ (100, edit.get_effective_column (filename, 3, 100));
977 
978   /* Verify locations on the line before the change.  */
979   ASSERT_EQ (1, edit.get_effective_column (filename, 2, 1));
980   ASSERT_EQ (6, edit.get_effective_column (filename, 2, 6));
981 
982   /* Verify locations on the line at and after the change.  */
983   ASSERT_EQ (21, edit.get_effective_column (filename, 2, 7));
984   ASSERT_EQ (22, edit.get_effective_column (filename, 2, 8));
985 
986   /* Verify diff.  */
987   auto_free <char *> diff = edit.generate_diff (false);
988   ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
989 		" /* before */\n"
990 		"-foo = bar.field;\n"
991 		"+foo = /* inserted */bar.field;\n"
992 		" /* after */\n", diff);
993 }
994 
995 /* Test applying an "insert" fixit, using insert_after, with
996    a range of length > 1 (to ensure that the end-point of
997    the input range is used).  */
998 
999 static void
test_applying_fixits_insert_after(const line_table_case & case_)1000 test_applying_fixits_insert_after (const line_table_case &case_)
1001 {
1002   /* Create a tempfile and write some text to it.
1003      .........................0000000001111111.
1004      .........................1234567890123456.  */
1005   const char *old_content = ("/* before */\n"
1006 			     "foo = bar.field;\n"
1007 			     "/* after */\n");
1008   temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1009   const char *filename = tmp.get_filename ();
1010   line_table_test ltt (case_);
1011   linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);
1012 
1013   /* Add a comment after "field".  */
1014   location_t start = linemap_position_for_column (line_table, 11);
1015   location_t finish = linemap_position_for_column (line_table, 15);
1016   location_t field = make_location (start, start, finish);
1017   rich_location richloc (line_table, field);
1018   richloc.add_fixit_insert_after ("/* inserted */");
1019 
1020   if (finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
1021     return;
1022 
1023   /* Verify that the text was inserted after the end of "field". */
1024   edit_context edit;
1025   edit.add_fixits (&richloc);
1026   auto_free <char *> new_content = edit.get_content (filename);
1027   ASSERT_STREQ ("/* before */\n"
1028 		"foo = bar.field/* inserted */;\n"
1029 		"/* after */\n", new_content);
1030 
1031   /* Verify diff.  */
1032   auto_free <char *> diff = edit.generate_diff (false);
1033   ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1034 		" /* before */\n"
1035 		"-foo = bar.field;\n"
1036 		"+foo = bar.field/* inserted */;\n"
1037 		" /* after */\n", diff);
1038 }
1039 
1040 /* Test applying an "insert" fixit, using insert_after at the end of
1041    a line (contrast with test_applying_fixits_insert_after_failure
1042    below).  */
1043 
1044 static void
test_applying_fixits_insert_after_at_line_end(const line_table_case & case_)1045 test_applying_fixits_insert_after_at_line_end (const line_table_case &case_)
1046 {
1047   /* Create a tempfile and write some text to it.
1048      .........................0000000001111111.
1049      .........................1234567890123456.  */
1050   const char *old_content = ("/* before */\n"
1051 			     "foo = bar.field;\n"
1052 			     "/* after */\n");
1053   temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1054   const char *filename = tmp.get_filename ();
1055   line_table_test ltt (case_);
1056   linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);
1057 
1058   /* Add a comment after the semicolon.  */
1059   location_t loc = linemap_position_for_column (line_table, 16);
1060   rich_location richloc (line_table, loc);
1061   richloc.add_fixit_insert_after ("/* inserted */");
1062 
1063   if (loc > LINE_MAP_MAX_LOCATION_WITH_COLS)
1064     return;
1065 
1066   edit_context edit;
1067   edit.add_fixits (&richloc);
1068   auto_free <char *> new_content = edit.get_content (filename);
1069   ASSERT_STREQ ("/* before */\n"
1070 		"foo = bar.field;/* inserted */\n"
1071 		"/* after */\n", new_content);
1072 
1073   /* Verify diff.  */
1074   auto_free <char *> diff = edit.generate_diff (false);
1075   ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1076 		" /* before */\n"
1077 		"-foo = bar.field;\n"
1078 		"+foo = bar.field;/* inserted */\n"
1079 		" /* after */\n", diff);
1080 }
1081 
1082 /* Test of a failed attempt to apply an "insert" fixit, using insert_after,
1083    due to the relevant linemap ending.  Contrast with
1084    test_applying_fixits_insert_after_at_line_end above.  */
1085 
1086 static void
test_applying_fixits_insert_after_failure(const line_table_case & case_)1087 test_applying_fixits_insert_after_failure (const line_table_case &case_)
1088 {
1089   /* Create a tempfile and write some text to it.
1090      .........................0000000001111111.
1091      .........................1234567890123456.  */
1092   const char *old_content = ("/* before */\n"
1093 			     "foo = bar.field;\n"
1094 			     "/* after */\n");
1095   temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1096   const char *filename = tmp.get_filename ();
1097   line_table_test ltt (case_);
1098   linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);
1099 
1100   /* Add a comment after the semicolon.  */
1101   location_t loc = linemap_position_for_column (line_table, 16);
1102   rich_location richloc (line_table, loc);
1103 
1104   /* We want a failure of linemap_position_for_loc_and_offset.
1105      We can do this by starting a new linemap at line 3, so that
1106      there is no appropriate location value for the insertion point
1107      within the linemap for line 2.  */
1108   linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 3);
1109 
1110   /* The failure fails to happen at the transition point from
1111      packed ranges to unpacked ranges (where there are some "spare"
1112      location_t values).  Skip the test there.  */
1113   if (loc >= LINE_MAP_MAX_LOCATION_WITH_PACKED_RANGES)
1114     return;
1115 
1116   /* Offsetting "loc" should now fail (by returning the input loc. */
1117   ASSERT_EQ (loc, linemap_position_for_loc_and_offset (line_table, loc, 1));
1118 
1119   /* Hence attempting to use add_fixit_insert_after at the end of the line
1120      should now fail.  */
1121   richloc.add_fixit_insert_after ("/* inserted */");
1122   ASSERT_TRUE (richloc.seen_impossible_fixit_p ());
1123 
1124   edit_context edit;
1125   edit.add_fixits (&richloc);
1126   ASSERT_FALSE (edit.valid_p ());
1127   ASSERT_EQ (NULL, edit.get_content (filename));
1128   ASSERT_EQ (NULL, edit.generate_diff (false));
1129 }
1130 
1131 /* Test applying an "insert" fixit that adds a newline.  */
1132 
1133 static void
test_applying_fixits_insert_containing_newline(const line_table_case & case_)1134 test_applying_fixits_insert_containing_newline (const line_table_case &case_)
1135 {
1136   /* Create a tempfile and write some text to it.
1137      .........................0000000001111111.
1138      .........................1234567890123456.  */
1139   const char *old_content = ("    case 'a':\n" /* line 1. */
1140 			     "      x = a;\n"  /* line 2. */
1141 			     "    case 'b':\n" /* line 3. */
1142 			     "      x = b;\n");/* line 4. */
1143 
1144   temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1145   const char *filename = tmp.get_filename ();
1146   line_table_test ltt (case_);
1147   linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 3);
1148 
1149   /* Add a "break;" on a line by itself before line 3 i.e. before
1150      column 1 of line 3. */
1151   location_t case_start = linemap_position_for_column (line_table, 5);
1152   location_t case_finish = linemap_position_for_column (line_table, 13);
1153   location_t case_loc = make_location (case_start, case_start, case_finish);
1154   rich_location richloc (line_table, case_loc);
1155   location_t line_start = linemap_position_for_column (line_table, 1);
1156   richloc.add_fixit_insert_before (line_start, "      break;\n");
1157 
1158   if (case_finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
1159     return;
1160 
1161   edit_context edit;
1162   edit.add_fixits (&richloc);
1163   auto_free <char *> new_content = edit.get_content (filename);
1164   ASSERT_STREQ (("    case 'a':\n"
1165 		 "      x = a;\n"
1166 		 "      break;\n"
1167 		 "    case 'b':\n"
1168 		 "      x = b;\n"),
1169 		new_content);
1170 
1171   /* Verify diff.  */
1172   auto_free <char *> diff = edit.generate_diff (false);
1173   ASSERT_STREQ (("@@ -1,4 +1,5 @@\n"
1174 		 "     case 'a':\n"
1175 		 "       x = a;\n"
1176 		 "+      break;\n"
1177 		 "     case 'b':\n"
1178 		 "       x = b;\n"),
1179 		diff);
1180 }
1181 
1182 /* Test applying a "replace" fixit that grows the affected line.  */
1183 
1184 static void
test_applying_fixits_growing_replace(const line_table_case & case_)1185 test_applying_fixits_growing_replace (const line_table_case &case_)
1186 {
1187   /* Create a tempfile and write some text to it.
1188      .........................0000000001111111.
1189      .........................1234567890123456.  */
1190   const char *old_content = ("/* before */\n"
1191 			     "foo = bar.field;\n"
1192 			     "/* after */\n");
1193   temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1194   const char *filename = tmp.get_filename ();
1195   line_table_test ltt (case_);
1196   linemap_add (line_table, LC_ENTER, false, filename, 2);
1197 
1198   /* Replace "field" with "m_field".  */
1199   location_t start = linemap_position_for_column (line_table, 11);
1200   location_t finish = linemap_position_for_column (line_table, 15);
1201   location_t field = make_location (start, start, finish);
1202   rich_location richloc (line_table, field);
1203   richloc.add_fixit_replace ("m_field");
1204 
1205   edit_context edit;
1206   edit.add_fixits (&richloc);
1207   auto_free <char *> new_content = edit.get_content (filename);
1208   if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1209     {
1210       ASSERT_STREQ ("/* before */\n"
1211 		    "foo = bar.m_field;\n"
1212 		    "/* after */\n", new_content);
1213 
1214       /* Verify location of ";" after the change.  */
1215       ASSERT_EQ (18, edit.get_effective_column (filename, 2, 16));
1216 
1217       /* Verify diff.  */
1218       auto_free <char *> diff = edit.generate_diff (false);
1219       ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1220 		    " /* before */\n"
1221 		    "-foo = bar.field;\n"
1222 		    "+foo = bar.m_field;\n"
1223 		    " /* after */\n", diff);
1224     }
1225 }
1226 
1227 /* Test applying a "replace" fixit that shrinks the affected line.  */
1228 
1229 static void
test_applying_fixits_shrinking_replace(const line_table_case & case_)1230 test_applying_fixits_shrinking_replace (const line_table_case &case_)
1231 {
1232   /* Create a tempfile and write some text to it.
1233      .........................000000000111111111.
1234      .........................123456789012345678.  */
1235   const char *old_content = ("/* before */\n"
1236 			     "foo = bar.m_field;\n"
1237 			     "/* after */\n");
1238   temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1239   const char *filename = tmp.get_filename ();
1240   line_table_test ltt (case_);
1241   linemap_add (line_table, LC_ENTER, false, filename, 2);
1242 
1243   /* Replace "field" with "m_field".  */
1244   location_t start = linemap_position_for_column (line_table, 11);
1245   location_t finish = linemap_position_for_column (line_table, 17);
1246   location_t m_field = make_location (start, start, finish);
1247   rich_location richloc (line_table, m_field);
1248   richloc.add_fixit_replace ("field");
1249 
1250   edit_context edit;
1251   edit.add_fixits (&richloc);
1252   auto_free <char *> new_content = edit.get_content (filename);
1253   if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1254     {
1255       ASSERT_STREQ ("/* before */\n"
1256 		    "foo = bar.field;\n"
1257 		    "/* after */\n", new_content);
1258 
1259       /* Verify location of ";" after the change.  */
1260       ASSERT_EQ (16, edit.get_effective_column (filename, 2, 18));
1261 
1262       /* Verify diff.  */
1263       auto_free <char *> diff = edit.generate_diff (false);
1264       ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1265 		    " /* before */\n"
1266 		    "-foo = bar.m_field;\n"
1267 		    "+foo = bar.field;\n"
1268 		    " /* after */\n", diff);
1269     }
1270 }
1271 
1272 /* Replacement fix-it hint containing a newline.  */
1273 
1274 static void
test_applying_fixits_replace_containing_newline(const line_table_case & case_)1275 test_applying_fixits_replace_containing_newline (const line_table_case &case_)
1276 {
1277   /* Create a tempfile and write some text to it.
1278     .........................0000000001111.
1279     .........................1234567890123.  */
1280   const char *old_content = "foo = bar ();\n";
1281 
1282   temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1283   const char *filename = tmp.get_filename ();
1284   line_table_test ltt (case_);
1285   linemap_add (line_table, LC_ENTER, false, filename, 1);
1286 
1287   /* Replace the " = " with "\n  = ", as if we were reformatting an
1288      overly long line.  */
1289   location_t start = linemap_position_for_column (line_table, 4);
1290   location_t finish = linemap_position_for_column (line_table, 6);
1291   location_t loc = linemap_position_for_column (line_table, 13);
1292   rich_location richloc (line_table, loc);
1293   source_range range = source_range::from_locations (start, finish);
1294   richloc.add_fixit_replace (range, "\n  = ");
1295 
1296   /* Newlines are only supported within fix-it hints that
1297      are at the start of lines (for entirely new lines), hence
1298      this fix-it should not be displayed.  */
1299   ASSERT_TRUE (richloc.seen_impossible_fixit_p ());
1300 
1301   if (finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
1302     return;
1303 
1304   edit_context edit;
1305   edit.add_fixits (&richloc);
1306   auto_free <char *> new_content = edit.get_content (filename);
1307   //ASSERT_STREQ ("foo\n  = bar ();\n", new_content);
1308 }
1309 
1310 /* Test applying a "remove" fixit.  */
1311 
1312 static void
test_applying_fixits_remove(const line_table_case & case_)1313 test_applying_fixits_remove (const line_table_case &case_)
1314 {
1315   /* Create a tempfile and write some text to it.
1316      .........................000000000111111111.
1317      .........................123456789012345678.  */
1318   const char *old_content = ("/* before */\n"
1319 			     "foo = bar.m_field;\n"
1320 			     "/* after */\n");
1321   temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1322   const char *filename = tmp.get_filename ();
1323   line_table_test ltt (case_);
1324   linemap_add (line_table, LC_ENTER, false, filename, 2);
1325 
1326   /* Remove ".m_field".  */
1327   location_t start = linemap_position_for_column (line_table, 10);
1328   location_t finish = linemap_position_for_column (line_table, 17);
1329   rich_location richloc (line_table, start);
1330   source_range range;
1331   range.m_start = start;
1332   range.m_finish = finish;
1333   richloc.add_fixit_remove (range);
1334 
1335   edit_context edit;
1336   edit.add_fixits (&richloc);
1337   auto_free <char *> new_content = edit.get_content (filename);
1338   if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1339     {
1340       ASSERT_STREQ ("/* before */\n"
1341 		    "foo = bar;\n"
1342 		    "/* after */\n", new_content);
1343 
1344       /* Verify location of ";" after the change.  */
1345       ASSERT_EQ (10, edit.get_effective_column (filename, 2, 18));
1346 
1347       /* Verify diff.  */
1348       auto_free <char *> diff = edit.generate_diff (false);
1349       ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1350 		    " /* before */\n"
1351 		    "-foo = bar.m_field;\n"
1352 		    "+foo = bar;\n"
1353 		    " /* after */\n", diff);
1354     }
1355 }
1356 
1357 /* Test applying multiple fixits to one line.  */
1358 
1359 static void
test_applying_fixits_multiple(const line_table_case & case_)1360 test_applying_fixits_multiple (const line_table_case &case_)
1361 {
1362   /* Create a tempfile and write some text to it.
1363      .........................00000000011111111.
1364      .........................12345678901234567.  */
1365   const char *old_content = ("/* before */\n"
1366 			     "foo = bar.field;\n"
1367 			     "/* after */\n");
1368   temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1369   const char *filename = tmp.get_filename ();
1370   line_table_test ltt (case_);
1371   linemap_add (line_table, LC_ENTER, false, filename, 2);
1372 
1373   location_t c7 = linemap_position_for_column (line_table, 7);
1374   location_t c9 = linemap_position_for_column (line_table, 9);
1375   location_t c11 = linemap_position_for_column (line_table, 11);
1376   location_t c15 = linemap_position_for_column (line_table, 15);
1377   location_t c17 = linemap_position_for_column (line_table, 17);
1378 
1379   if (c17 > LINE_MAP_MAX_LOCATION_WITH_COLS)
1380     return;
1381 
1382   /* Add a comment in front of "bar.field".  */
1383   rich_location insert_a (line_table, c7);
1384   insert_a.add_fixit_insert_before (c7, "/* alpha */");
1385 
1386   /* Add a comment after "bar.field;".  */
1387   rich_location insert_b (line_table, c17);
1388   insert_b.add_fixit_insert_before (c17, "/* beta */");
1389 
1390   /* Replace "bar" with "pub".   */
1391   rich_location replace_a (line_table, c7);
1392   replace_a.add_fixit_replace (source_range::from_locations (c7, c9),
1393 			       "pub");
1394 
1395   /* Replace "field" with "meadow".   */
1396   rich_location replace_b (line_table, c7);
1397   replace_b.add_fixit_replace (source_range::from_locations (c11, c15),
1398 			       "meadow");
1399 
1400   edit_context edit;
1401   edit.add_fixits (&insert_a);
1402   ASSERT_EQ (100, edit.get_effective_column (filename, 1, 100));
1403   ASSERT_EQ (1, edit.get_effective_column (filename, 2, 1));
1404   ASSERT_EQ (6, edit.get_effective_column (filename, 2, 6));
1405   ASSERT_EQ (18, edit.get_effective_column (filename, 2, 7));
1406   ASSERT_EQ (27, edit.get_effective_column (filename, 2, 16));
1407   ASSERT_EQ (100, edit.get_effective_column (filename, 3, 100));
1408 
1409   edit.add_fixits (&insert_b);
1410   edit.add_fixits (&replace_a);
1411   edit.add_fixits (&replace_b);
1412 
1413   if (c17 <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1414     {
1415       auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1416       ASSERT_STREQ ("/* before */\n"
1417 		     "foo = /* alpha */pub.meadow;/* beta */\n"
1418 		     "/* after */\n",
1419 		    new_content);
1420 
1421       /* Verify diff.  */
1422       auto_free <char *> diff = edit.generate_diff (false);
1423       ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1424 		    " /* before */\n"
1425 		    "-foo = bar.field;\n"
1426 		    "+foo = /* alpha */pub.meadow;/* beta */\n"
1427 		    " /* after */\n", diff);
1428     }
1429 }
1430 
1431 /* Subroutine of test_applying_fixits_multiple_lines.
1432    Add the text "CHANGED: " to the front of the given line.  */
1433 
1434 static location_t
change_line(edit_context & edit,int line_num)1435 change_line (edit_context &edit, int line_num)
1436 {
1437   const line_map_ordinary *ord_map
1438     = LINEMAPS_LAST_ORDINARY_MAP (line_table);
1439   const int column = 1;
1440   location_t loc =
1441     linemap_position_for_line_and_column (line_table, ord_map,
1442 					  line_num, column);
1443 
1444   expanded_location exploc = expand_location (loc);
1445   if (loc <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1446     {
1447       ASSERT_EQ (line_num, exploc.line);
1448       ASSERT_EQ (column, exploc.column);
1449     }
1450 
1451   rich_location insert (line_table, loc);
1452   insert.add_fixit_insert_before ("CHANGED: ");
1453   edit.add_fixits (&insert);
1454   return loc;
1455 }
1456 
1457 /* Subroutine of test_applying_fixits_multiple_lines.
1458    Add the text "INSERTED\n" in front of the given line.  */
1459 
1460 static location_t
insert_line(edit_context & edit,int line_num)1461 insert_line (edit_context &edit, int line_num)
1462 {
1463   const line_map_ordinary *ord_map
1464     = LINEMAPS_LAST_ORDINARY_MAP (line_table);
1465   const int column = 1;
1466   location_t loc =
1467     linemap_position_for_line_and_column (line_table, ord_map,
1468 					  line_num, column);
1469 
1470   expanded_location exploc = expand_location (loc);
1471   if (loc <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1472     {
1473       ASSERT_EQ (line_num, exploc.line);
1474       ASSERT_EQ (column, exploc.column);
1475     }
1476 
1477   rich_location insert (line_table, loc);
1478   insert.add_fixit_insert_before ("INSERTED\n");
1479   edit.add_fixits (&insert);
1480   return loc;
1481 }
1482 
1483 /* Test of editing multiple lines within a long file,
1484    to ensure that diffs are generated as expected.  */
1485 
1486 static void
test_applying_fixits_multiple_lines(const line_table_case & case_)1487 test_applying_fixits_multiple_lines (const line_table_case &case_)
1488 {
1489   /* Create a tempfile and write many lines of text to it.  */
1490   named_temp_file tmp (".txt");
1491   const char *filename = tmp.get_filename ();
1492   FILE *f = fopen (filename, "w");
1493   ASSERT_NE (f, NULL);
1494   for (int i = 1; i <= 1000; i++)
1495     fprintf (f, "line %i\n", i);
1496   fclose (f);
1497 
1498   line_table_test ltt (case_);
1499   linemap_add (line_table, LC_ENTER, false, filename, 1);
1500   linemap_position_for_column (line_table, 127);
1501 
1502   edit_context edit;
1503 
1504   /* A run of consecutive lines.  */
1505   change_line (edit, 2);
1506   change_line (edit, 3);
1507   change_line (edit, 4);
1508   insert_line (edit, 5);
1509 
1510   /* A run of nearby lines, within the contextual limit.  */
1511   change_line (edit, 150);
1512   change_line (edit, 151);
1513   location_t last_loc = change_line (edit, 153);
1514 
1515   if (last_loc > LINE_MAP_MAX_LOCATION_WITH_COLS)
1516     return;
1517 
1518   /* Verify diff.  */
1519   auto_free <char *> diff = edit.generate_diff (false);
1520   ASSERT_STREQ ("@@ -1,7 +1,8 @@\n"
1521 		" line 1\n"
1522 		"-line 2\n"
1523 		"-line 3\n"
1524 		"-line 4\n"
1525 		"+CHANGED: line 2\n"
1526 		"+CHANGED: line 3\n"
1527 		"+CHANGED: line 4\n"
1528 		"+INSERTED\n"
1529 		" line 5\n"
1530 		" line 6\n"
1531 		" line 7\n"
1532 		"@@ -147,10 +148,10 @@\n"
1533 		" line 147\n"
1534 		" line 148\n"
1535 		" line 149\n"
1536 		"-line 150\n"
1537 		"-line 151\n"
1538 		"+CHANGED: line 150\n"
1539 		"+CHANGED: line 151\n"
1540 		" line 152\n"
1541 		"-line 153\n"
1542 		"+CHANGED: line 153\n"
1543 		" line 154\n"
1544 		" line 155\n"
1545 		" line 156\n", diff);
1546 
1547   /* Ensure tmp stays alive until this point, so that the tempfile
1548      persists until after the generate_diff call.  */
1549   tmp.get_filename ();
1550 }
1551 
1552 /* Test of converting an initializer for a named field from
1553    the old GCC extension to C99 syntax.
1554    Exercises a shrinking replacement followed by a growing
1555    replacement on the same line.  */
1556 
1557 static void
test_applying_fixits_modernize_named_init(const line_table_case & case_)1558 test_applying_fixits_modernize_named_init (const line_table_case &case_)
1559 {
1560   /* Create a tempfile and write some text to it.
1561      .........................00000000011111111.
1562      .........................12345678901234567.  */
1563   const char *old_content = ("/* before */\n"
1564 			     "bar    : 1,\n"
1565 			     "/* after */\n");
1566   temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1567   const char *filename = tmp.get_filename ();
1568   line_table_test ltt (case_);
1569   linemap_add (line_table, LC_ENTER, false, filename, 2);
1570 
1571   location_t c1 = linemap_position_for_column (line_table, 1);
1572   location_t c3 = linemap_position_for_column (line_table, 3);
1573   location_t c8 = linemap_position_for_column (line_table, 8);
1574 
1575   if (c8 > LINE_MAP_MAX_LOCATION_WITH_COLS)
1576     return;
1577 
1578   /* Replace "bar" with ".".  */
1579   rich_location r1 (line_table, c8);
1580   r1.add_fixit_replace (source_range::from_locations (c1, c3),
1581 			".");
1582 
1583   /* Replace ":" with "bar =".   */
1584   rich_location r2 (line_table, c8);
1585   r2.add_fixit_replace (source_range::from_locations (c8, c8),
1586 			"bar =");
1587 
1588   /* The order should not matter.  Do r1 then r2. */
1589   {
1590     edit_context edit;
1591     edit.add_fixits (&r1);
1592 
1593     /* Verify state after first replacement.  */
1594     {
1595       auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1596       /* We should now have:
1597 	 ............00000000011.
1598 	 ............12345678901.  */
1599       ASSERT_STREQ ("/* before */\n"
1600 		    ".    : 1,\n"
1601 		    "/* after */\n",
1602 		    new_content);
1603       /* Location of the "1".  */
1604       ASSERT_EQ (6, edit.get_effective_column (filename, 2, 8));
1605       /* Location of the ",".  */
1606       ASSERT_EQ (9, edit.get_effective_column (filename, 2, 11));
1607     }
1608 
1609     edit.add_fixits (&r2);
1610 
1611     auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1612     /* Verify state after second replacement.
1613        ............00000000011111111.
1614        ............12345678901234567.  */
1615     ASSERT_STREQ ("/* before */\n"
1616 		  ".    bar = 1,\n"
1617 		  "/* after */\n",
1618 		  new_content);
1619   }
1620 
1621   /* Try again, doing r2 then r1; the new_content should be the same.  */
1622   {
1623     edit_context edit;
1624     edit.add_fixits (&r2);
1625     edit.add_fixits (&r1);
1626     auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1627     /*.............00000000011111111.
1628       .............12345678901234567.  */
1629     ASSERT_STREQ ("/* before */\n"
1630 		  ".    bar = 1,\n"
1631 		  "/* after */\n",
1632 		  new_content);
1633   }
1634 }
1635 
1636 /* Test of a fixit affecting a file that can't be read.  */
1637 
1638 static void
test_applying_fixits_unreadable_file()1639 test_applying_fixits_unreadable_file ()
1640 {
1641   const char *filename = "this-does-not-exist.txt";
1642   line_table_test ltt ();
1643   linemap_add (line_table, LC_ENTER, false, filename, 1);
1644 
1645   location_t loc = linemap_position_for_column (line_table, 1);
1646 
1647   rich_location insert (line_table, loc);
1648   insert.add_fixit_insert_before ("change 1");
1649   insert.add_fixit_insert_before ("change 2");
1650 
1651   edit_context edit;
1652   /* Attempting to add the fixits affecting the unreadable file
1653      should transition the edit from valid to invalid.  */
1654   ASSERT_TRUE (edit.valid_p ());
1655   edit.add_fixits (&insert);
1656   ASSERT_FALSE (edit.valid_p ());
1657   ASSERT_EQ (NULL, edit.get_content (filename));
1658   ASSERT_EQ (NULL, edit.generate_diff (false));
1659 }
1660 
1661 /* Verify that we gracefully handle an attempt to edit a line
1662    that's beyond the end of the file.  */
1663 
1664 static void
test_applying_fixits_line_out_of_range()1665 test_applying_fixits_line_out_of_range ()
1666 {
1667   /* Create a tempfile and write some text to it.
1668      ........................00000000011111111.
1669      ........................12345678901234567.  */
1670   const char *old_content = "One-liner file\n";
1671   temp_source_file tmp (SELFTEST_LOCATION, ".txt", old_content);
1672   const char *filename = tmp.get_filename ();
1673   line_table_test ltt ();
1674   linemap_add (line_table, LC_ENTER, false, filename, 2);
1675 
1676   /* Try to insert a string in line 2.  */
1677   location_t loc = linemap_position_for_column (line_table, 1);
1678 
1679   rich_location insert (line_table, loc);
1680   insert.add_fixit_insert_before ("change");
1681 
1682   /* Verify that attempting the insertion puts an edit_context
1683      into an invalid state.  */
1684   edit_context edit;
1685   ASSERT_TRUE (edit.valid_p ());
1686   edit.add_fixits (&insert);
1687   ASSERT_FALSE (edit.valid_p ());
1688   ASSERT_EQ (NULL, edit.get_content (filename));
1689   ASSERT_EQ (NULL, edit.generate_diff (false));
1690 }
1691 
1692 /* Verify the boundary conditions of column values in fix-it
1693    hints applied to edit_context instances.  */
1694 
1695 static void
test_applying_fixits_column_validation(const line_table_case & case_)1696 test_applying_fixits_column_validation (const line_table_case &case_)
1697 {
1698   /* Create a tempfile and write some text to it.
1699      ........................00000000011111111.
1700      ........................12345678901234567.  */
1701   const char *old_content = "One-liner file\n";
1702   temp_source_file tmp (SELFTEST_LOCATION, ".txt", old_content);
1703   const char *filename = tmp.get_filename ();
1704   line_table_test ltt (case_);
1705   linemap_add (line_table, LC_ENTER, false, filename, 1);
1706 
1707   location_t c11 = linemap_position_for_column (line_table, 11);
1708   location_t c14 = linemap_position_for_column (line_table, 14);
1709   location_t c15 = linemap_position_for_column (line_table, 15);
1710   location_t c16 = linemap_position_for_column (line_table, 16);
1711 
1712   /* Verify limits of valid columns in insertion fixits.  */
1713 
1714   /* Verify inserting at the end of the line.  */
1715   {
1716     rich_location richloc (line_table, c11);
1717     richloc.add_fixit_insert_before (c15, " change");
1718 
1719     /* Col 15 is at the end of the line, so the insertion
1720        should succeed.  */
1721     edit_context edit;
1722     edit.add_fixits (&richloc);
1723     auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1724     if (c15 <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1725       ASSERT_STREQ ("One-liner file change\n", new_content);
1726     else
1727       ASSERT_EQ (NULL, new_content);
1728   }
1729 
1730   /* Verify inserting beyond the end of the line.  */
1731   {
1732     rich_location richloc (line_table, c11);
1733     richloc.add_fixit_insert_before (c16, " change");
1734 
1735     /* Col 16 is beyond the end of the line, so the insertion
1736        should fail gracefully.  */
1737     edit_context edit;
1738     ASSERT_TRUE (edit.valid_p ());
1739     edit.add_fixits (&richloc);
1740     ASSERT_FALSE (edit.valid_p ());
1741     ASSERT_EQ (NULL, edit.get_content (filename));
1742     ASSERT_EQ (NULL, edit.generate_diff (false));
1743   }
1744 
1745   /* Verify limits of valid columns in replacement fixits.  */
1746 
1747   /* Verify replacing the end of the line.  */
1748   {
1749     rich_location richloc (line_table, c11);
1750     source_range range = source_range::from_locations (c11, c14);
1751     richloc.add_fixit_replace (range, "change");
1752 
1753     /* Col 14 is at the end of the line, so the replacement
1754        should succeed.  */
1755     edit_context edit;
1756     edit.add_fixits (&richloc);
1757     auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1758     if (c14 <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1759       ASSERT_STREQ ("One-liner change\n", new_content);
1760     else
1761       ASSERT_EQ (NULL, new_content);
1762   }
1763 
1764   /* Verify going beyond the end of the line.  */
1765   {
1766     rich_location richloc (line_table, c11);
1767     source_range range = source_range::from_locations (c11, c15);
1768     richloc.add_fixit_replace (range, "change");
1769 
1770     /* Col 15 is after the end of the line, so the replacement
1771        should fail; verify that the attempt fails gracefully.  */
1772     edit_context edit;
1773     ASSERT_TRUE (edit.valid_p ());
1774     edit.add_fixits (&richloc);
1775     ASSERT_FALSE (edit.valid_p ());
1776     ASSERT_EQ (NULL, edit.get_content (filename));
1777     ASSERT_EQ (NULL, edit.generate_diff (false));
1778   }
1779 }
1780 
1781 /* Run all of the selftests within this file.  */
1782 
1783 void
edit_context_c_tests()1784 edit_context_c_tests ()
1785 {
1786   test_get_content ();
1787   for_each_line_table_case (test_applying_fixits_insert_before);
1788   for_each_line_table_case (test_applying_fixits_insert_after);
1789   for_each_line_table_case (test_applying_fixits_insert_after_at_line_end);
1790   for_each_line_table_case (test_applying_fixits_insert_after_failure);
1791   for_each_line_table_case (test_applying_fixits_insert_containing_newline);
1792   for_each_line_table_case (test_applying_fixits_growing_replace);
1793   for_each_line_table_case (test_applying_fixits_shrinking_replace);
1794   for_each_line_table_case (test_applying_fixits_replace_containing_newline);
1795   for_each_line_table_case (test_applying_fixits_remove);
1796   for_each_line_table_case (test_applying_fixits_multiple);
1797   for_each_line_table_case (test_applying_fixits_multiple_lines);
1798   for_each_line_table_case (test_applying_fixits_modernize_named_init);
1799   test_applying_fixits_unreadable_file ();
1800   test_applying_fixits_line_out_of_range ();
1801   for_each_line_table_case (test_applying_fixits_column_validation);
1802 }
1803 
1804 } // namespace selftest
1805 
1806 #endif /* CHECKING_P */
1807