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