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