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