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