1/* Diagnostic subroutines for printing source-code
2 Copyright (C) 1999-2023 Free Software Foundation, Inc.
3 Contributed by Gabriel Dos Reis <gdr@codesourcery.com>
4
5This file is part of GCC.
6
7GCC is free software; you can redistribute it and/or modify it under
8the terms of the GNU General Public License as published by the Free
9Software Foundation; either version 3, or (at your option) any later
10version.
11
12GCC is distributed in the hope that it will be useful, but WITHOUT ANY
13WARRANTY; without even the implied warranty of MERCHANTABILITY or
14FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15for more details.
16
17You should have received a copy of the GNU General Public License
18along with GCC; see the file COPYING3. If not see
19<http://www.gnu.org/licenses/>. */
20
21#include "config.h"
22#include "system.h"
23#include "coretypes.h"
24#include "version.h"
25#include "demangle.h"
26#include "intl.h"
27#include "backtrace.h"
28#include "diagnostic.h"
29#include "diagnostic-color.h"
30#include "gcc-rich-location.h"
31#include "selftest.h"
32#include "selftest-diagnostic.h"
33#include "cpplib.h"
34
35#ifdef HAVE_TERMIOS_H
36# include <termios.h>
37#endif
38
39#ifdef GWINSZ_IN_SYS_IOCTL
40# include <sys/ioctl.h>
41#endif
42
43/* Disable warnings about quoting issues in the pp_xxx calls below
44 that (intentionally) don't follow GCC diagnostic conventions. */
45#if __GNUC__ >= 10
46# pragma GCC diagnostic push
47# pragma GCC diagnostic ignored "-Wformat-diag"
48#endif
49
50/* Classes for rendering source code and diagnostics, within an
51 anonymous namespace.
52 The work is done by "class layout", which embeds and uses
53 "class colorizer" and "class layout_range" to get things done. */
54
55namespace {
56
57/* The state at a given point of the source code, assuming that we're
58 in a range: which range are we in, and whether we should draw a caret at
59 this point. */
60
61struct point_state
62{
63 int range_idx;
64 bool draw_caret_p;
65};
66
67/* A class to inject colorization codes when printing the diagnostic locus.
68
69 It has one kind of colorization for each of:
70 - normal text
71 - range 0 (the "primary location")
72 - range 1
73 - range 2
74
75 The class caches the lookup of the color codes for the above.
76
77 The class also has responsibility for tracking which of the above is
78 active, filtering out unnecessary changes. This allows
79 layout::print_source_line and layout::print_annotation_line
80 to simply request a colorization code for *every* character they print,
81 via this class, and have the filtering be done for them here. */
82
83class colorizer
84{
85 public:
86 colorizer (pretty_printer *pp,
87 diagnostic_t diagnostic_kind);
88 ~colorizer ();
89
90 void set_range (int range_idx)
91 {
92 /* Normally we emphasize the primary location, then alternate between
93 two colors for the secondary locations.
94 But if we're printing a run of events in a diagnostic path, that
95 makes no sense, so print all of them with the same colorization. */
96 if (m_diagnostic_kind == DK_DIAGNOSTIC_PATH)
97 set_state (0);
98 else
99 set_state (range_idx);
100 }
101 void set_normal_text () { set_state (STATE_NORMAL_TEXT); }
102 void set_fixit_insert () { set_state (STATE_FIXIT_INSERT); }
103 void set_fixit_delete () { set_state (STATE_FIXIT_DELETE); }
104
105 private:
106 void set_state (int state);
107 void begin_state (int state);
108 void finish_state (int state);
109 const char *get_color_by_name (const char *);
110
111 private:
112 static const int STATE_NORMAL_TEXT = -1;
113 static const int STATE_FIXIT_INSERT = -2;
114 static const int STATE_FIXIT_DELETE = -3;
115
116 pretty_printer *m_pp;
117 diagnostic_t m_diagnostic_kind;
118 int m_current_state;
119 const char *m_range1;
120 const char *m_range2;
121 const char *m_fixit_insert;
122 const char *m_fixit_delete;
123 const char *m_stop_color;
124};
125
126/* In order to handle multibyte sources properly, all of this logic needs to be
127 aware of the distinction between the number of bytes and the number of
128 display columns occupied by a character, which are not the same for non-ASCII
129 characters. For example, the Unicode pi symbol, U+03C0, is encoded in UTF-8
130 as "\xcf\x80", and thus occupies 2 bytes of space while only occupying 1
131 display column when it is output. A typical emoji, such as U+1F602 (in
132 UTF-8, "\xf0\x9f\x98\x82"), requires 4 bytes and has a display width of 2.
133
134 The below example line, which is also used for selftests below, shows how the
135 display column and byte column are related:
136
137 0000000001111111111222222 display
138 1234567890123456789012345 columns
139 SS_foo = P_bar.SS_fieldP;
140 0000000111111111222222223 byte
141 1356789012456789134567891 columns
142
143 Here SS represents the two display columns for the U+1F602 emoji, and P
144 represents the one display column for the U+03C0 pi symbol. As an example, a
145 diagnostic pointing to the final P on this line is at byte column 29 and
146 display column 24. This reflects the fact that the three extended characters
147 before the final P occupy cumulatively 5 more bytes than they do display
148 columns (a difference of 2 for each of the two SSs, and one for the other P).
149
150 One or the other of the two column units is more useful depending on the
151 context. For instance, in order to output the caret at the correct location,
152 we need to count display columns; in order to colorize a source line, we need
153 to count the bytes. All locations are provided to us as byte counts, which
154 we augment with the display column on demand so that it can be used when
155 needed. This is not the most efficient way to do things since it requires
156 looping over the whole line each time, but it should be fine for the purpose
157 of outputting diagnostics.
158
159 In order to keep straight which units (byte or display) are in use at a
160 given time, the following enum lets us specify that explicitly. */
161
162enum column_unit {
163 /* Measured in raw bytes. */
164 CU_BYTES = 0,
165
166 /* Measured in display units. */
167 CU_DISPLAY_COLS,
168
169 /* For arrays indexed by column_unit. */
170 CU_NUM_UNITS
171};
172
173/* Utility class to augment an exploc with the corresponding display column. */
174
175class exploc_with_display_col : public expanded_location
176{
177 public:
178 exploc_with_display_col (const expanded_location &exploc,
179 const cpp_char_column_policy &policy,
180 enum location_aspect aspect)
181 : expanded_location (exploc),
182 m_display_col (location_compute_display_column (exploc, policy))
183 {
184 if (exploc.column > 0)
185 {
186 /* m_display_col is now the final column of the byte.
187 If escaping has happened, we may want the first column instead. */
188 if (aspect != LOCATION_ASPECT_FINISH)
189 {
190 expanded_location prev_exploc (exploc);
191 prev_exploc.column--;
192 int prev_display_col
193 = (location_compute_display_column (exploc: prev_exploc, policy));
194 m_display_col = prev_display_col + 1;
195 }
196 }
197 }
198
199 int m_display_col;
200};
201
202
203/* A point within a layout_range; similar to an exploc_with_display_col,
204 but after filtering on file. */
205
206class layout_point
207{
208 public:
209 layout_point (const exploc_with_display_col &exploc)
210 : m_line (exploc.line)
211 {
212 m_columns[CU_BYTES] = exploc.column;
213 m_columns[CU_DISPLAY_COLS] = exploc.m_display_col;
214 }
215
216 linenum_type m_line;
217 int m_columns[CU_NUM_UNITS];
218};
219
220/* A class for use by "class layout" below: a filtered location_range. */
221
222class layout_range
223{
224 public:
225 layout_range (const exploc_with_display_col &start_exploc,
226 const exploc_with_display_col &finish_exploc,
227 enum range_display_kind range_display_kind,
228 const exploc_with_display_col &caret_exploc,
229 unsigned original_idx,
230 const range_label *label);
231
232 bool contains_point (linenum_type row, int column,
233 enum column_unit col_unit) const;
234 bool intersects_line_p (linenum_type row) const;
235
236 layout_point m_start;
237 layout_point m_finish;
238 enum range_display_kind m_range_display_kind;
239 layout_point m_caret;
240 unsigned m_original_idx;
241 const range_label *m_label;
242};
243
244/* A struct for use by layout::print_source_line for telling
245 layout::print_annotation_line the extents of the source line that
246 it printed, so that underlines can be clipped appropriately. Units
247 are 1-based display columns. */
248
249struct line_bounds
250{
251 int m_first_non_ws_disp_col;
252 int m_last_non_ws_disp_col;
253
254 line_bounds ()
255 {
256 m_first_non_ws_disp_col = INT_MAX;
257 m_last_non_ws_disp_col = 0;
258 }
259};
260
261/* A range of contiguous source lines within a layout (e.g. "lines 5-10"
262 or "line 23"). During the layout ctor, layout::calculate_line_spans
263 splits the pertinent source lines into a list of disjoint line_span
264 instances (e.g. lines 5-10, lines 15-20, line 23). */
265
266class line_span
267{
268public:
269 line_span (linenum_type first_line, linenum_type last_line)
270 : m_first_line (first_line), m_last_line (last_line)
271 {
272 gcc_assert (first_line <= last_line);
273 }
274 linenum_type get_first_line () const { return m_first_line; }
275 linenum_type get_last_line () const { return m_last_line; }
276
277 bool contains_line_p (linenum_type line) const
278 {
279 return line >= m_first_line && line <= m_last_line;
280 }
281
282 static int comparator (const void *p1, const void *p2)
283 {
284 const line_span *ls1 = (const line_span *)p1;
285 const line_span *ls2 = (const line_span *)p2;
286 int first_line_cmp = compare (lhs: ls1->m_first_line, rhs: ls2->m_first_line);
287 if (first_line_cmp)
288 return first_line_cmp;
289 return compare (lhs: ls1->m_last_line, rhs: ls2->m_last_line);
290 }
291
292 linenum_type m_first_line;
293 linenum_type m_last_line;
294};
295
296#if CHECKING_P
297
298/* Selftests for line_span. */
299
300static void
301test_line_span ()
302{
303 line_span line_one (1, 1);
304 ASSERT_EQ (1, line_one.get_first_line ());
305 ASSERT_EQ (1, line_one.get_last_line ());
306 ASSERT_FALSE (line_one.contains_line_p (0));
307 ASSERT_TRUE (line_one.contains_line_p (1));
308 ASSERT_FALSE (line_one.contains_line_p (2));
309
310 line_span lines_1_to_3 (1, 3);
311 ASSERT_EQ (1, lines_1_to_3.get_first_line ());
312 ASSERT_EQ (3, lines_1_to_3.get_last_line ());
313 ASSERT_TRUE (lines_1_to_3.contains_line_p (1));
314 ASSERT_TRUE (lines_1_to_3.contains_line_p (3));
315
316 ASSERT_EQ (0, line_span::comparator (&line_one, &line_one));
317 ASSERT_GT (line_span::comparator (&lines_1_to_3, &line_one), 0);
318 ASSERT_LT (line_span::comparator (&line_one, &lines_1_to_3), 0);
319
320 /* A linenum > 2^31. */
321 const linenum_type LARGEST_LINE = 0xffffffff;
322 line_span largest_line (LARGEST_LINE, LARGEST_LINE);
323 ASSERT_EQ (LARGEST_LINE, largest_line.get_first_line ());
324 ASSERT_EQ (LARGEST_LINE, largest_line.get_last_line ());
325
326 ASSERT_GT (line_span::comparator (&largest_line, &line_one), 0);
327 ASSERT_LT (line_span::comparator (&line_one, &largest_line), 0);
328}
329
330#endif /* #if CHECKING_P */
331
332/* A bundle of information containing how to print unicode
333 characters and bytes when quoting source code.
334
335 Provides a unified place to support escaping some subset
336 of characters to some format.
337
338 Extends char_column_policy; printing is split out to avoid
339 libcpp having to know about pretty_printer. */
340
341struct char_display_policy : public cpp_char_column_policy
342{
343 public:
344 char_display_policy (int tabstop,
345 int (*width_cb) (cppchar_t c),
346 void (*print_cb) (pretty_printer *pp,
347 const cpp_decoded_char &cp))
348 : cpp_char_column_policy (tabstop, width_cb),
349 m_print_cb (print_cb)
350 {
351 }
352
353 void (*m_print_cb) (pretty_printer *pp,
354 const cpp_decoded_char &cp);
355};
356
357/* A class to control the overall layout when printing a diagnostic.
358
359 The layout is determined within the constructor.
360 It is then printed by repeatedly calling the "print_source_line",
361 "print_annotation_line" and "print_any_fixits" methods.
362
363 We assume we have disjoint ranges. */
364
365class layout
366{
367 public:
368 layout (const diagnostic_context &context,
369 const rich_location &richloc,
370 diagnostic_t diagnostic_kind,
371 pretty_printer *pp);
372
373 bool maybe_add_location_range (const location_range *loc_range,
374 unsigned original_idx,
375 bool restrict_to_current_line_spans);
376
377 int get_num_line_spans () const { return m_line_spans.length (); }
378 const line_span *get_line_span (int idx) const { return &m_line_spans[idx]; }
379
380 int get_linenum_width () const { return m_linenum_width; }
381 int get_x_offset_display () const { return m_x_offset_display; }
382
383 void print_gap_in_line_numbering ();
384 bool print_heading_for_line_span_index_p (int line_span_idx) const;
385
386 expanded_location get_expanded_location (const line_span *) const;
387
388 void print_line (linenum_type row);
389
390 void on_bad_codepoint (const char *ptr, cppchar_t ch, size_t ch_sz);
391
392 private:
393 bool will_show_line_p (linenum_type row) const;
394 void print_leading_fixits (linenum_type row);
395 line_bounds print_source_line (linenum_type row, const char *line,
396 int line_bytes);
397 bool should_print_annotation_line_p (linenum_type row) const;
398 void start_annotation_line (char margin_char = ' ') const;
399 void print_annotation_line (linenum_type row, const line_bounds lbounds);
400 void print_any_labels (linenum_type row);
401 void print_trailing_fixits (linenum_type row);
402
403 bool annotation_line_showed_range_p (linenum_type line, int start_column,
404 int finish_column) const;
405 void show_ruler (int max_column) const;
406
407 bool validate_fixit_hint_p (const fixit_hint *hint);
408
409 void calculate_line_spans ();
410 void calculate_linenum_width ();
411 void calculate_x_offset_display ();
412
413 void print_newline ();
414
415 bool
416 get_state_at_point (/* Inputs. */
417 linenum_type row, int column,
418 int first_non_ws, int last_non_ws,
419 enum column_unit col_unit,
420 /* Outputs. */
421 point_state *out_state);
422
423 int
424 get_x_bound_for_row (linenum_type row, int caret_column,
425 int last_non_ws);
426
427 void
428 move_to_column (int *column, int dest_column, bool add_left_margin);
429
430 private:
431 bool compatible_locations_p (location_t loc_a, location_t loc_b) const;
432
433 const diagnostic_source_printing_options &m_options;
434 const line_maps *m_line_table;
435 pretty_printer *m_pp;
436 char_display_policy m_policy;
437 location_t m_primary_loc;
438 exploc_with_display_col m_exploc;
439 colorizer m_colorizer;
440 bool m_diagnostic_path_p;
441 auto_vec <layout_range> m_layout_ranges;
442 auto_vec <const fixit_hint *> m_fixit_hints;
443 auto_vec <line_span> m_line_spans;
444 int m_linenum_width;
445 int m_x_offset_display;
446 bool m_escape_on_output;
447};
448
449/* Implementation of "class colorizer". */
450
451/* The constructor for "colorizer". Lookup and store color codes for the
452 different kinds of things we might need to print. */
453
454colorizer::colorizer (pretty_printer *pp,
455 diagnostic_t diagnostic_kind) :
456 m_pp (pp),
457 m_diagnostic_kind (diagnostic_kind),
458 m_current_state (STATE_NORMAL_TEXT)
459{
460 m_range1 = get_color_by_name ("range1");
461 m_range2 = get_color_by_name ("range2");
462 m_fixit_insert = get_color_by_name ("fixit-insert");
463 m_fixit_delete = get_color_by_name ("fixit-delete");
464 m_stop_color = colorize_stop (pp_show_color (m_pp));
465}
466
467/* The destructor for "colorize". If colorization is on, print a code to
468 turn it off. */
469
470colorizer::~colorizer ()
471{
472 finish_state (state: m_current_state);
473}
474
475/* Update state, printing color codes if necessary if there's a state
476 change. */
477
478void
479colorizer::set_state (int new_state)
480{
481 if (m_current_state != new_state)
482 {
483 finish_state (state: m_current_state);
484 m_current_state = new_state;
485 begin_state (state: new_state);
486 }
487}
488
489/* Turn on any colorization for STATE. */
490
491void
492colorizer::begin_state (int state)
493{
494 switch (state)
495 {
496 case STATE_NORMAL_TEXT:
497 break;
498
499 case STATE_FIXIT_INSERT:
500 pp_string (m_pp, m_fixit_insert);
501 break;
502
503 case STATE_FIXIT_DELETE:
504 pp_string (m_pp, m_fixit_delete);
505 break;
506
507 case 0:
508 /* Make range 0 be the same color as the "kind" text
509 (error vs warning vs note). */
510 pp_string
511 (m_pp,
512 colorize_start (pp_show_color (m_pp),
513 name: diagnostic_get_color_for_kind (kind: m_diagnostic_kind)));
514 break;
515
516 case 1:
517 pp_string (m_pp, m_range1);
518 break;
519
520 case 2:
521 pp_string (m_pp, m_range2);
522 break;
523
524 default:
525 /* For ranges beyond 2, alternate between color 1 and color 2. */
526 {
527 gcc_assert (state > 2);
528 pp_string (m_pp,
529 state % 2 ? m_range1 : m_range2);
530 }
531 break;
532 }
533}
534
535/* Turn off any colorization for STATE. */
536
537void
538colorizer::finish_state (int state)
539{
540 if (state != STATE_NORMAL_TEXT)
541 pp_string (m_pp, m_stop_color);
542}
543
544/* Get the color code for NAME (or the empty string if
545 colorization is disabled). */
546
547const char *
548colorizer::get_color_by_name (const char *name)
549{
550 return colorize_start (pp_show_color (m_pp), name);
551}
552
553/* Implementation of class layout_range. */
554
555/* The constructor for class layout_range.
556 Initialize various layout_point fields from expanded_location
557 equivalents; we've already filtered on file. */
558
559layout_range::layout_range (const exploc_with_display_col &start_exploc,
560 const exploc_with_display_col &finish_exploc,
561 enum range_display_kind range_display_kind,
562 const exploc_with_display_col &caret_exploc,
563 unsigned original_idx,
564 const range_label *label)
565: m_start (start_exploc),
566 m_finish (finish_exploc),
567 m_range_display_kind (range_display_kind),
568 m_caret (caret_exploc),
569 m_original_idx (original_idx),
570 m_label (label)
571{
572}
573
574/* Is (column, row) within the given range?
575 We've already filtered on the file.
576
577 Ranges are closed (both limits are within the range).
578
579 Example A: a single-line range:
580 start: (col=22, line=2)
581 finish: (col=38, line=2)
582
583 |00000011111111112222222222333333333344444444444
584 |34567890123456789012345678901234567890123456789
585--+-----------------------------------------------
58601|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
58702|bbbbbbbbbbbbbbbbbbbSwwwwwwwwwwwwwwwFaaaaaaaaaaa
58803|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
589
590 Example B: a multiline range with
591 start: (col=14, line=3)
592 finish: (col=08, line=5)
593
594 |00000011111111112222222222333333333344444444444
595 |34567890123456789012345678901234567890123456789
596--+-----------------------------------------------
59701|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
59802|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
59903|bbbbbbbbbbbSwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
60004|wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
60105|wwwwwFaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
60206|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
603--+-----------------------------------------------
604
605 Legend:
606 - 'b' indicates a point *before* the range
607 - 'S' indicates the start of the range
608 - 'w' indicates a point within the range
609 - 'F' indicates the finish of the range (which is
610 within it).
611 - 'a' indicates a subsequent point *after* the range.
612
613 COL_UNIT controls whether we check the byte column or
614 the display column; one or the other is more convenient
615 depending on the context. */
616
617bool
618layout_range::contains_point (linenum_type row, int column,
619 enum column_unit col_unit) const
620{
621 gcc_assert (m_start.m_line <= m_finish.m_line);
622 /* ...but the equivalent isn't true for the columns;
623 consider example B in the comment above. */
624
625 if (row < m_start.m_line)
626 /* Points before the first line of the range are
627 outside it (corresponding to line 01 in example A
628 and lines 01 and 02 in example B above). */
629 return false;
630
631 if (row == m_start.m_line)
632 /* On same line as start of range (corresponding
633 to line 02 in example A and line 03 in example B). */
634 {
635 if (column < m_start.m_columns[col_unit])
636 /* Points on the starting line of the range, but
637 before the column in which it begins. */
638 return false;
639
640 if (row < m_finish.m_line)
641 /* This is a multiline range; the point
642 is within it (corresponds to line 03 in example B
643 from column 14 onwards) */
644 return true;
645 else
646 {
647 /* This is a single-line range. */
648 gcc_assert (row == m_finish.m_line);
649 return column <= m_finish.m_columns[col_unit];
650 }
651 }
652
653 /* The point is in a line beyond that containing the
654 start of the range: lines 03 onwards in example A,
655 and lines 04 onwards in example B. */
656 gcc_assert (row > m_start.m_line);
657
658 if (row > m_finish.m_line)
659 /* The point is beyond the final line of the range
660 (lines 03 onwards in example A, and lines 06 onwards
661 in example B). */
662 return false;
663
664 if (row < m_finish.m_line)
665 {
666 /* The point is in a line that's fully within a multiline
667 range (e.g. line 04 in example B). */
668 gcc_assert (m_start.m_line < m_finish.m_line);
669 return true;
670 }
671
672 gcc_assert (row == m_finish.m_line);
673
674 return column <= m_finish.m_columns[col_unit];
675}
676
677/* Does this layout_range contain any part of line ROW? */
678
679bool
680layout_range::intersects_line_p (linenum_type row) const
681{
682 gcc_assert (m_start.m_line <= m_finish.m_line);
683 if (row < m_start.m_line)
684 return false;
685 if (row > m_finish.m_line)
686 return false;
687 return true;
688}
689
690#if CHECKING_P
691
692/* Default for when we don't care what the tab expansion is set to. */
693static const int def_tabstop = 8;
694
695static cpp_char_column_policy def_policy ()
696{
697 return cpp_char_column_policy (def_tabstop, cpp_wcwidth);
698}
699
700/* Create some expanded locations for testing layout_range. The filename
701 member of the explocs is set to the empty string. This member will only be
702 inspected by the calls to location_compute_display_column() made from the
703 layout_point constructors. That function will check for an empty filename
704 argument and not attempt to open it, rather treating the non-existent data
705 as if the display width were the same as the byte count. Tests exercising a
706 real difference between byte count and display width are performed later,
707 e.g. in test_diagnostic_show_locus_one_liner_utf8(). */
708
709static layout_range
710make_range (int start_line, int start_col, int end_line, int end_col)
711{
712 const expanded_location start_exploc
713 = {.file: "", .line: start_line, .column: start_col, NULL, .sysp: false};
714 const expanded_location finish_exploc
715 = {.file: "", .line: end_line, .column: end_col, NULL, .sysp: false};
716 return layout_range (exploc_with_display_col (start_exploc, def_policy (),
717 LOCATION_ASPECT_START),
718 exploc_with_display_col (finish_exploc, def_policy (),
719 LOCATION_ASPECT_FINISH),
720 SHOW_RANGE_WITHOUT_CARET,
721 exploc_with_display_col (start_exploc, def_policy (),
722 LOCATION_ASPECT_CARET),
723 0, NULL);
724}
725
726/* Selftests for layout_range::contains_point and
727 layout_range::intersects_line_p. */
728
729/* Selftest for layout_range, where the layout_range
730 is a range with start==end i.e. a single point. */
731
732static void
733test_layout_range_for_single_point ()
734{
735 layout_range point = make_range (start_line: 7, start_col: 10, end_line: 7, end_col: 10);
736
737 /* Tests for layout_range::contains_point. */
738
739 for (int i = 0; i != CU_NUM_UNITS; ++i)
740 {
741 const enum column_unit col_unit = (enum column_unit) i;
742
743 /* Before the line. */
744 ASSERT_FALSE (point.contains_point (6, 1, col_unit));
745
746 /* On the line, but before start. */
747 ASSERT_FALSE (point.contains_point (7, 9, col_unit));
748
749 /* At the point. */
750 ASSERT_TRUE (point.contains_point (7, 10, col_unit));
751
752 /* On the line, after the point. */
753 ASSERT_FALSE (point.contains_point (7, 11, col_unit));
754
755 /* After the line. */
756 ASSERT_FALSE (point.contains_point (8, 1, col_unit));
757 }
758
759 /* Tests for layout_range::intersects_line_p. */
760 ASSERT_FALSE (point.intersects_line_p (6));
761 ASSERT_TRUE (point.intersects_line_p (7));
762 ASSERT_FALSE (point.intersects_line_p (8));
763}
764
765/* Selftest for layout_range, where the layout_range
766 is the single-line range shown as "Example A" above. */
767
768static void
769test_layout_range_for_single_line ()
770{
771 layout_range example_a = make_range (start_line: 2, start_col: 22, end_line: 2, end_col: 38);
772
773 /* Tests for layout_range::contains_point. */
774
775 for (int i = 0; i != CU_NUM_UNITS; ++i)
776 {
777 const enum column_unit col_unit = (enum column_unit) i;
778
779 /* Before the line. */
780 ASSERT_FALSE (example_a.contains_point (1, 1, col_unit));
781
782 /* On the line, but before start. */
783 ASSERT_FALSE (example_a.contains_point (2, 21, col_unit));
784
785 /* On the line, at the start. */
786 ASSERT_TRUE (example_a.contains_point (2, 22, col_unit));
787
788 /* On the line, within the range. */
789 ASSERT_TRUE (example_a.contains_point (2, 23, col_unit));
790
791 /* On the line, at the end. */
792 ASSERT_TRUE (example_a.contains_point (2, 38, col_unit));
793
794 /* On the line, after the end. */
795 ASSERT_FALSE (example_a.contains_point (2, 39, col_unit));
796
797 /* After the line. */
798 ASSERT_FALSE (example_a.contains_point (2, 39, col_unit));
799 }
800
801 /* Tests for layout_range::intersects_line_p. */
802 ASSERT_FALSE (example_a.intersects_line_p (1));
803 ASSERT_TRUE (example_a.intersects_line_p (2));
804 ASSERT_FALSE (example_a.intersects_line_p (3));
805}
806
807/* Selftest for layout_range, where the layout_range
808 is the multi-line range shown as "Example B" above. */
809
810static void
811test_layout_range_for_multiple_lines ()
812{
813 layout_range example_b = make_range (start_line: 3, start_col: 14, end_line: 5, end_col: 8);
814
815 /* Tests for layout_range::contains_point. */
816
817 for (int i = 0; i != CU_NUM_UNITS; ++i)
818 {
819 const enum column_unit col_unit = (enum column_unit) i;
820
821 /* Before first line. */
822 ASSERT_FALSE (example_b.contains_point (1, 1, col_unit));
823
824 /* On the first line, but before start. */
825 ASSERT_FALSE (example_b.contains_point (3, 13, col_unit));
826
827 /* At the start. */
828 ASSERT_TRUE (example_b.contains_point (3, 14, col_unit));
829
830 /* On the first line, within the range. */
831 ASSERT_TRUE (example_b.contains_point (3, 15, col_unit));
832
833 /* On an interior line.
834 The column number should not matter; try various boundary
835 values. */
836 ASSERT_TRUE (example_b.contains_point (4, 1, col_unit));
837 ASSERT_TRUE (example_b.contains_point (4, 7, col_unit));
838 ASSERT_TRUE (example_b.contains_point (4, 8, col_unit));
839 ASSERT_TRUE (example_b.contains_point (4, 9, col_unit));
840 ASSERT_TRUE (example_b.contains_point (4, 13, col_unit));
841 ASSERT_TRUE (example_b.contains_point (4, 14, col_unit));
842 ASSERT_TRUE (example_b.contains_point (4, 15, col_unit));
843
844 /* On the final line, before the end. */
845 ASSERT_TRUE (example_b.contains_point (5, 7, col_unit));
846
847 /* On the final line, at the end. */
848 ASSERT_TRUE (example_b.contains_point (5, 8, col_unit));
849
850 /* On the final line, after the end. */
851 ASSERT_FALSE (example_b.contains_point (5, 9, col_unit));
852
853 /* After the line. */
854 ASSERT_FALSE (example_b.contains_point (6, 1, col_unit));
855 }
856
857 /* Tests for layout_range::intersects_line_p. */
858 ASSERT_FALSE (example_b.intersects_line_p (2));
859 ASSERT_TRUE (example_b.intersects_line_p (3));
860 ASSERT_TRUE (example_b.intersects_line_p (4));
861 ASSERT_TRUE (example_b.intersects_line_p (5));
862 ASSERT_FALSE (example_b.intersects_line_p (6));
863}
864
865#endif /* #if CHECKING_P */
866
867/* Given a source line LINE of length LINE_BYTES bytes, determine the length
868 (still in bytes, not display cols) without any trailing whitespace. */
869
870static int
871get_line_bytes_without_trailing_whitespace (const char *line, int line_bytes)
872{
873 int result = line_bytes;
874 while (result > 0)
875 {
876 char ch = line[result - 1];
877 if (ch == ' ' || ch == '\t' || ch == '\r')
878 result--;
879 else
880 break;
881 }
882 gcc_assert (result >= 0);
883 gcc_assert (result <= line_bytes);
884 gcc_assert (result == 0 ||
885 (line[result - 1] != ' '
886 && line[result -1] != '\t'
887 && line[result -1] != '\r'));
888 return result;
889}
890
891#if CHECKING_P
892
893/* A helper function for testing get_line_bytes_without_trailing_whitespace. */
894
895static void
896assert_eq (const char *line, int expected_bytes)
897{
898 int actual_value
899 = get_line_bytes_without_trailing_whitespace (line, line_bytes: strlen (s: line));
900 ASSERT_EQ (actual_value, expected_bytes);
901}
902
903/* Verify that get_line_bytes_without_trailing_whitespace is sane for
904 various inputs. It is not required to handle newlines. */
905
906static void
907test_get_line_bytes_without_trailing_whitespace ()
908{
909 assert_eq (line: "", expected_bytes: 0);
910 assert_eq (line: " ", expected_bytes: 0);
911 assert_eq (line: "\t", expected_bytes: 0);
912 assert_eq (line: "\r", expected_bytes: 0);
913 assert_eq (line: "hello world", expected_bytes: 11);
914 assert_eq (line: "hello world ", expected_bytes: 11);
915 assert_eq (line: "hello world \t\t ", expected_bytes: 11);
916 assert_eq (line: "hello world\r", expected_bytes: 11);
917}
918
919#endif /* #if CHECKING_P */
920
921/* Helper function for layout's ctor, for sanitizing locations relative
922 to the primary location within a diagnostic.
923
924 Compare LOC_A and LOC_B to see if it makes sense to print underlines
925 connecting their expanded locations. Doing so is only guaranteed to
926 make sense if the locations share the same macro expansion "history"
927 i.e. they can be traced through the same macro expansions, eventually
928 reaching an ordinary map.
929
930 This may be too strong a condition, but it effectively sanitizes
931 PR c++/70105, which has an example of printing an expression where the
932 final location of the expression is in a different macro, which
933 erroneously was leading to hundreds of lines of irrelevant source
934 being printed. */
935
936bool
937layout::compatible_locations_p (location_t loc_a, location_t loc_b) const
938{
939 if (IS_ADHOC_LOC (loc: loc_a))
940 loc_a = get_location_from_adhoc_loc (m_line_table, loc_a);
941 if (IS_ADHOC_LOC (loc: loc_b))
942 loc_b = get_location_from_adhoc_loc (m_line_table, loc_b);
943
944 /* If either location is one of the special locations outside of a
945 linemap, they are only compatible if they are equal. */
946 if (loc_a < RESERVED_LOCATION_COUNT
947 || loc_b < RESERVED_LOCATION_COUNT)
948 return loc_a == loc_b;
949
950 const line_map *map_a = linemap_lookup (m_line_table, loc_a);
951 linemap_assert (map_a);
952
953 const line_map *map_b = linemap_lookup (m_line_table, loc_b);
954 linemap_assert (map_b);
955
956 /* Are they within the same map? */
957 if (map_a == map_b)
958 {
959 /* Are both within the same macro expansion? */
960 if (linemap_macro_expansion_map_p (map_a))
961 {
962 /* If so, then they're only compatible if either both are
963 from the macro definition, or both from the macro arguments. */
964 bool loc_a_from_defn
965 = linemap_location_from_macro_definition_p (m_line_table, loc_a);
966 bool loc_b_from_defn
967 = linemap_location_from_macro_definition_p (m_line_table, loc_b);
968 if (loc_a_from_defn != loc_b_from_defn)
969 return false;
970
971 /* Expand each location towards the spelling location, and
972 recurse. */
973 const line_map_macro *macro_map = linemap_check_macro (map: map_a);
974 location_t loc_a_toward_spelling
975 = linemap_macro_map_loc_unwind_toward_spelling (set: m_line_table,
976 macro_map,
977 location: loc_a);
978 location_t loc_b_toward_spelling
979 = linemap_macro_map_loc_unwind_toward_spelling (set: m_line_table,
980 macro_map,
981 location: loc_b);
982 return compatible_locations_p (loc_a: loc_a_toward_spelling,
983 loc_b: loc_b_toward_spelling);
984 }
985
986 /* Otherwise they are within the same ordinary map. */
987 return true;
988 }
989 else
990 {
991 /* Within different maps. */
992
993 /* If either is within a macro expansion, they are incompatible. */
994 if (linemap_macro_expansion_map_p (map_a)
995 || linemap_macro_expansion_map_p (map_b))
996 return false;
997
998 /* Within two different ordinary maps; they are compatible iff they
999 are in the same file. */
1000 const line_map_ordinary *ord_map_a = linemap_check_ordinary (map: map_a);
1001 const line_map_ordinary *ord_map_b = linemap_check_ordinary (map: map_b);
1002 return ord_map_a->to_file == ord_map_b->to_file;
1003 }
1004}
1005
1006/* Comparator for sorting fix-it hints. */
1007
1008static int
1009fixit_cmp (const void *p_a, const void *p_b)
1010{
1011 const fixit_hint * hint_a = *static_cast<const fixit_hint * const *> (p_a);
1012 const fixit_hint * hint_b = *static_cast<const fixit_hint * const *> (p_b);
1013 return hint_a->get_start_loc () - hint_b->get_start_loc ();
1014}
1015
1016/* Callbacks for use when not escaping the source. */
1017
1018/* The default callback for char_column_policy::m_width_cb is cpp_wcwidth. */
1019
1020/* Callback for char_display_policy::m_print_cb for printing source chars
1021 when not escaping the source. */
1022
1023static void
1024default_print_decoded_ch (pretty_printer *pp,
1025 const cpp_decoded_char &decoded_ch)
1026{
1027 for (const char *ptr = decoded_ch.m_start_byte;
1028 ptr != decoded_ch.m_next_byte; ptr++)
1029 {
1030 if (*ptr == '\0' || *ptr == '\r')
1031 {
1032 pp_space (pp);
1033 continue;
1034 }
1035
1036 pp_character (pp, *ptr);
1037 }
1038}
1039
1040/* Callbacks for use with DIAGNOSTICS_ESCAPE_FORMAT_BYTES. */
1041
1042static const int width_per_escaped_byte = 4;
1043
1044/* Callback for char_column_policy::m_width_cb for determining the
1045 display width when escaping with DIAGNOSTICS_ESCAPE_FORMAT_BYTES. */
1046
1047static int
1048escape_as_bytes_width (cppchar_t ch)
1049{
1050 if (ch < 0x80 && ISPRINT (ch))
1051 return cpp_wcwidth (c: ch);
1052 else
1053 {
1054 if (ch <= 0x7F) return 1 * width_per_escaped_byte;
1055 if (ch <= 0x7FF) return 2 * width_per_escaped_byte;
1056 if (ch <= 0xFFFF) return 3 * width_per_escaped_byte;
1057 return 4 * width_per_escaped_byte;
1058 }
1059}
1060
1061/* Callback for char_display_policy::m_print_cb for printing source chars
1062 when escaping with DIAGNOSTICS_ESCAPE_FORMAT_BYTES. */
1063
1064static void
1065escape_as_bytes_print (pretty_printer *pp,
1066 const cpp_decoded_char &decoded_ch)
1067{
1068 if (!decoded_ch.m_valid_ch)
1069 {
1070 for (const char *iter = decoded_ch.m_start_byte;
1071 iter != decoded_ch.m_next_byte; ++iter)
1072 {
1073 char buf[16];
1074 sprintf (s: buf, format: "<%02x>", (unsigned char)*iter);
1075 pp_string (pp, buf);
1076 }
1077 return;
1078 }
1079
1080 cppchar_t ch = decoded_ch.m_ch;
1081 if (ch < 0x80 && ISPRINT (ch))
1082 pp_character (pp, ch);
1083 else
1084 {
1085 for (const char *iter = decoded_ch.m_start_byte;
1086 iter < decoded_ch.m_next_byte; ++iter)
1087 {
1088 char buf[16];
1089 sprintf (s: buf, format: "<%02x>", (unsigned char)*iter);
1090 pp_string (pp, buf);
1091 }
1092 }
1093}
1094
1095/* Callbacks for use with DIAGNOSTICS_ESCAPE_FORMAT_UNICODE. */
1096
1097/* Callback for char_column_policy::m_width_cb for determining the
1098 display width when escaping with DIAGNOSTICS_ESCAPE_FORMAT_UNICODE. */
1099
1100static int
1101escape_as_unicode_width (cppchar_t ch)
1102{
1103 if (ch < 0x80 && ISPRINT (ch))
1104 return cpp_wcwidth (c: ch);
1105 else
1106 {
1107 // Width of "<U+%04x>"
1108 if (ch > 0xfffff)
1109 return 10;
1110 else if (ch > 0xffff)
1111 return 9;
1112 else
1113 return 8;
1114 }
1115}
1116
1117/* Callback for char_display_policy::m_print_cb for printing source chars
1118 when escaping with DIAGNOSTICS_ESCAPE_FORMAT_UNICODE. */
1119
1120static void
1121escape_as_unicode_print (pretty_printer *pp,
1122 const cpp_decoded_char &decoded_ch)
1123{
1124 if (!decoded_ch.m_valid_ch)
1125 {
1126 escape_as_bytes_print (pp, decoded_ch);
1127 return;
1128 }
1129
1130 cppchar_t ch = decoded_ch.m_ch;
1131 if (ch < 0x80 && ISPRINT (ch))
1132 pp_character (pp, ch);
1133 else
1134 {
1135 char buf[16];
1136 sprintf (s: buf, format: "<U+%04X>", ch);
1137 pp_string (pp, buf);
1138 }
1139}
1140
1141/* Populate a char_display_policy based on DC and RICHLOC. */
1142
1143static char_display_policy
1144make_policy (const diagnostic_context &dc,
1145 const rich_location &richloc)
1146{
1147 /* The default is to not escape non-ASCII bytes. */
1148 char_display_policy result
1149 (dc.m_tabstop, cpp_wcwidth, default_print_decoded_ch);
1150
1151 /* If the diagnostic suggests escaping non-ASCII bytes, then
1152 use policy from user-supplied options. */
1153 if (richloc.escape_on_output_p ())
1154 {
1155 result.m_undecoded_byte_width = width_per_escaped_byte;
1156 switch (dc.get_escape_format ())
1157 {
1158 default:
1159 gcc_unreachable ();
1160 case DIAGNOSTICS_ESCAPE_FORMAT_UNICODE:
1161 result.m_width_cb = escape_as_unicode_width;
1162 result.m_print_cb = escape_as_unicode_print;
1163 break;
1164 case DIAGNOSTICS_ESCAPE_FORMAT_BYTES:
1165 result.m_width_cb = escape_as_bytes_width;
1166 result.m_print_cb = escape_as_bytes_print;
1167 break;
1168 }
1169 }
1170
1171 return result;
1172}
1173
1174/* Implementation of class layout. */
1175
1176/* Constructor for class layout.
1177
1178 Filter the ranges from the rich_location to those that we can
1179 sanely print, populating m_layout_ranges and m_fixit_hints.
1180 Determine the range of lines that we will print, splitting them
1181 up into an ordered list of disjoint spans of contiguous line numbers.
1182 Determine m_x_offset_display, to ensure that the primary caret
1183 will fit within the max_width provided by the diagnostic_context. */
1184
1185layout::layout (const diagnostic_context &context,
1186 const rich_location &richloc,
1187 diagnostic_t diagnostic_kind,
1188 pretty_printer *pp)
1189: m_options (context.m_source_printing),
1190 m_line_table (richloc.get_line_table ()),
1191 m_pp (pp ? pp : context.printer),
1192 m_policy (make_policy (dc: context, richloc)),
1193 m_primary_loc (richloc.get_range (idx: 0)->m_loc),
1194 m_exploc (richloc.get_expanded_location (idx: 0), m_policy,
1195 LOCATION_ASPECT_CARET),
1196 m_colorizer (m_pp, diagnostic_kind),
1197 m_diagnostic_path_p (diagnostic_kind == DK_DIAGNOSTIC_PATH),
1198 m_layout_ranges (richloc.get_num_locations ()),
1199 m_fixit_hints (richloc.get_num_fixit_hints ()),
1200 m_line_spans (1 + richloc.get_num_locations ()),
1201 m_linenum_width (0),
1202 m_x_offset_display (0),
1203 m_escape_on_output (richloc.escape_on_output_p ())
1204{
1205 for (unsigned int idx = 0; idx < richloc.get_num_locations (); idx++)
1206 {
1207 /* This diagnostic printer can only cope with "sufficiently sane" ranges.
1208 Ignore any ranges that are awkward to handle. */
1209 const location_range *loc_range = richloc.get_range (idx);
1210 maybe_add_location_range (loc_range, original_idx: idx, restrict_to_current_line_spans: false);
1211 }
1212
1213 /* Populate m_fixit_hints, filtering to only those that are in the
1214 same file. */
1215 for (unsigned int i = 0; i < richloc.get_num_fixit_hints (); i++)
1216 {
1217 const fixit_hint *hint = richloc.get_fixit_hint (idx: i);
1218 if (validate_fixit_hint_p (hint))
1219 m_fixit_hints.safe_push (obj: hint);
1220 }
1221
1222 /* Sort m_fixit_hints. */
1223 m_fixit_hints.qsort (fixit_cmp);
1224
1225 /* Populate the indicated members. */
1226 calculate_line_spans ();
1227 calculate_linenum_width ();
1228 calculate_x_offset_display ();
1229
1230 if (m_options.show_ruler_p)
1231 show_ruler (max_column: m_x_offset_display + m_options.max_width);
1232}
1233
1234
1235/* Attempt to add LOC_RANGE to m_layout_ranges, filtering them to
1236 those that we can sanely print.
1237
1238 ORIGINAL_IDX is the index of LOC_RANGE within its rich_location,
1239 (for use as extrinsic state by label ranges FIXME).
1240
1241 If RESTRICT_TO_CURRENT_LINE_SPANS is true, then LOC_RANGE is also
1242 filtered against this layout instance's current line spans: it
1243 will only be added if the location is fully within the lines
1244 already specified by other locations.
1245
1246 Return true iff LOC_RANGE was added. */
1247
1248bool
1249layout::maybe_add_location_range (const location_range *loc_range,
1250 unsigned original_idx,
1251 bool restrict_to_current_line_spans)
1252{
1253 gcc_assert (loc_range);
1254
1255 /* Split the "range" into caret and range information. */
1256 source_range src_range = get_range_from_loc (set: m_line_table, loc: loc_range->m_loc);
1257
1258 /* Expand the various locations. */
1259 expanded_location start
1260 = linemap_client_expand_location_to_spelling_point
1261 (m_line_table, src_range.m_start, LOCATION_ASPECT_START);
1262 expanded_location finish
1263 = linemap_client_expand_location_to_spelling_point
1264 (m_line_table, src_range.m_finish, LOCATION_ASPECT_FINISH);
1265 expanded_location caret
1266 = linemap_client_expand_location_to_spelling_point
1267 (m_line_table, loc_range->m_loc, LOCATION_ASPECT_CARET);
1268
1269 /* If any part of the range isn't in the same file as the primary
1270 location of this diagnostic, ignore the range. */
1271 if (start.file != m_exploc.file)
1272 return false;
1273 if (finish.file != m_exploc.file)
1274 return false;
1275 if (loc_range->m_range_display_kind == SHOW_RANGE_WITH_CARET)
1276 if (caret.file != m_exploc.file)
1277 return false;
1278
1279 /* Sanitize the caret location for non-primary ranges. */
1280 if (m_layout_ranges.length () > 0)
1281 if (loc_range->m_range_display_kind == SHOW_RANGE_WITH_CARET)
1282 if (!compatible_locations_p (loc_a: loc_range->m_loc, loc_b: m_primary_loc))
1283 /* Discard any non-primary ranges that can't be printed
1284 sanely relative to the primary location. */
1285 return false;
1286
1287 /* Everything is now known to be in the correct source file,
1288 but it may require further sanitization. */
1289 layout_range ri (exploc_with_display_col (start, m_policy,
1290 LOCATION_ASPECT_START),
1291 exploc_with_display_col (finish, m_policy,
1292 LOCATION_ASPECT_FINISH),
1293 loc_range->m_range_display_kind,
1294 exploc_with_display_col (caret, m_policy,
1295 LOCATION_ASPECT_CARET),
1296 original_idx, loc_range->m_label);
1297
1298 /* If we have a range that finishes before it starts (perhaps
1299 from something built via macro expansion), printing the
1300 range is likely to be nonsensical. Also, attempting to do so
1301 breaks assumptions within the printing code (PR c/68473).
1302 Similarly, don't attempt to print ranges if one or both ends
1303 of the range aren't sane to print relative to the
1304 primary location (PR c++/70105). */
1305 if (start.line > finish.line
1306 || !compatible_locations_p (loc_a: src_range.m_start, loc_b: m_primary_loc)
1307 || !compatible_locations_p (loc_a: src_range.m_finish, loc_b: m_primary_loc))
1308 {
1309 /* Is this the primary location? */
1310 if (m_layout_ranges.length () == 0)
1311 {
1312 /* We want to print the caret for the primary location, but
1313 we must sanitize away m_start and m_finish. */
1314 ri.m_start = ri.m_caret;
1315 ri.m_finish = ri.m_caret;
1316 }
1317 else
1318 /* This is a non-primary range; ignore it. */
1319 return false;
1320 }
1321
1322 /* Potentially filter to just the lines already specified by other
1323 locations. This is for use by gcc_rich_location::add_location_if_nearby.
1324 The layout ctor doesn't use it, and can't because m_line_spans
1325 hasn't been set up at that point. */
1326 if (restrict_to_current_line_spans)
1327 {
1328 if (!will_show_line_p (row: start.line))
1329 return false;
1330 if (!will_show_line_p (row: finish.line))
1331 return false;
1332 if (loc_range->m_range_display_kind == SHOW_RANGE_WITH_CARET)
1333 if (!will_show_line_p (row: caret.line))
1334 return false;
1335 }
1336
1337 /* Passed all the tests; add the range to m_layout_ranges so that
1338 it will be printed. */
1339 m_layout_ranges.safe_push (obj: ri);
1340 return true;
1341}
1342
1343/* Return true iff ROW is within one of the line spans for this layout. */
1344
1345bool
1346layout::will_show_line_p (linenum_type row) const
1347{
1348 for (int line_span_idx = 0; line_span_idx < get_num_line_spans ();
1349 line_span_idx++)
1350 {
1351 const line_span *line_span = get_line_span (idx: line_span_idx);
1352 if (line_span->contains_line_p (line: row))
1353 return true;
1354 }
1355 return false;
1356}
1357
1358/* Print a line showing a gap in the line numbers, for showing the boundary
1359 between two line spans. */
1360
1361void
1362layout::print_gap_in_line_numbering ()
1363{
1364 gcc_assert (m_options.show_line_numbers_p);
1365
1366 pp_emit_prefix (m_pp);
1367
1368 for (int i = 0; i < m_linenum_width + 1; i++)
1369 pp_character (m_pp, '.');
1370
1371 pp_newline (m_pp);
1372}
1373
1374/* Return true iff we should print a heading when starting the
1375 line span with the given index. */
1376
1377bool
1378layout::print_heading_for_line_span_index_p (int line_span_idx) const
1379{
1380 /* We print a heading for every change of line span, hence for every
1381 line span after the initial one. */
1382 if (line_span_idx > 0)
1383 return true;
1384
1385 /* We also do it for the initial span if the primary location of the
1386 diagnostic is in a different span. */
1387 if (m_exploc.line > (int)get_line_span (idx: 0)->m_last_line)
1388 return true;
1389
1390 return false;
1391}
1392
1393/* Get an expanded_location for the first location of interest within
1394 the given line_span.
1395 Used when printing a heading to indicate a new line span. */
1396
1397expanded_location
1398layout::get_expanded_location (const line_span *line_span) const
1399{
1400 /* Whenever possible, use the caret location. */
1401 if (line_span->contains_line_p (line: m_exploc.line))
1402 return m_exploc;
1403
1404 /* Otherwise, use the start of the first range that's present
1405 within the line_span. */
1406 for (unsigned int i = 0; i < m_layout_ranges.length (); i++)
1407 {
1408 const layout_range *lr = &m_layout_ranges[i];
1409 if (line_span->contains_line_p (line: lr->m_start.m_line))
1410 {
1411 expanded_location exploc = m_exploc;
1412 exploc.line = lr->m_start.m_line;
1413 exploc.column = lr->m_start.m_columns[CU_BYTES];
1414 return exploc;
1415 }
1416 }
1417
1418 /* Otherwise, use the location of the first fixit-hint present within
1419 the line_span. */
1420 for (unsigned int i = 0; i < m_fixit_hints.length (); i++)
1421 {
1422 const fixit_hint *hint = m_fixit_hints[i];
1423 location_t loc = hint->get_start_loc ();
1424 expanded_location exploc = expand_location (loc);
1425 if (line_span->contains_line_p (line: exploc.line))
1426 return exploc;
1427 }
1428
1429 /* It should not be possible to have a line span that didn't
1430 contain any of the layout_range or fixit_hint instances. */
1431 gcc_unreachable ();
1432 return m_exploc;
1433}
1434
1435/* Determine if HINT is meaningful to print within this layout. */
1436
1437bool
1438layout::validate_fixit_hint_p (const fixit_hint *hint)
1439{
1440 if (LOCATION_FILE (hint->get_start_loc ()) != m_exploc.file)
1441 return false;
1442 if (LOCATION_FILE (hint->get_next_loc ()) != m_exploc.file)
1443 return false;
1444
1445 return true;
1446}
1447
1448/* Determine the range of lines affected by HINT.
1449 This assumes that HINT has already been filtered by
1450 validate_fixit_hint_p, and so affects the correct source file. */
1451
1452static line_span
1453get_line_span_for_fixit_hint (const fixit_hint *hint)
1454{
1455 gcc_assert (hint);
1456
1457 int start_line = LOCATION_LINE (hint->get_start_loc ());
1458
1459 /* For line-insertion fix-it hints, add the previous line to the
1460 span, to give the user more context on the proposed change. */
1461 if (hint->ends_with_newline_p ())
1462 if (start_line > 1)
1463 start_line--;
1464
1465 return line_span (start_line,
1466 LOCATION_LINE (hint->get_next_loc ()));
1467}
1468
1469/* We want to print the pertinent source code at a diagnostic. The
1470 rich_location can contain multiple locations. This will have been
1471 filtered into m_exploc (the caret for the primary location) and
1472 m_layout_ranges, for those ranges within the same source file.
1473
1474 We will print a subset of the lines within the source file in question,
1475 as a collection of "spans" of lines.
1476
1477 This function populates m_line_spans with an ordered, disjoint list of
1478 the line spans of interest.
1479
1480 Printing a gap between line spans takes one line, so, when printing
1481 line numbers, we allow a gap of up to one line between spans when
1482 merging, since it makes more sense to print the source line rather than a
1483 "gap-in-line-numbering" line. When not printing line numbers, it's
1484 better to be more explicit about what's going on, so keeping them as
1485 separate spans is preferred.
1486
1487 For example, if the primary range is on lines 8-10, with secondary ranges
1488 covering lines 5-6 and lines 13-15:
1489
1490 004
1491 005 |RANGE 1
1492 006 |RANGE 1
1493 007
1494 008 |PRIMARY RANGE
1495 009 |PRIMARY CARET
1496 010 |PRIMARY RANGE
1497 011
1498 012
1499 013 |RANGE 2
1500 014 |RANGE 2
1501 015 |RANGE 2
1502 016
1503
1504 With line numbering on, we want two spans: lines 5-10 and lines 13-15.
1505
1506 With line numbering off (with span headers), we want three spans: lines 5-6,
1507 lines 8-10, and lines 13-15. */
1508
1509void
1510layout::calculate_line_spans ()
1511{
1512 /* This should only be called once, by the ctor. */
1513 gcc_assert (m_line_spans.length () == 0);
1514
1515 /* Populate tmp_spans with individual spans, for each of
1516 m_exploc, and for m_layout_ranges. */
1517 auto_vec<line_span> tmp_spans (1 + m_layout_ranges.length ());
1518 tmp_spans.safe_push (obj: line_span (m_exploc.line, m_exploc.line));
1519 for (unsigned int i = 0; i < m_layout_ranges.length (); i++)
1520 {
1521 const layout_range *lr = &m_layout_ranges[i];
1522 gcc_assert (lr->m_start.m_line <= lr->m_finish.m_line);
1523 tmp_spans.safe_push (obj: line_span (lr->m_start.m_line,
1524 lr->m_finish.m_line));
1525 }
1526
1527 /* Also add spans for any fix-it hints, in case they cover other lines. */
1528 for (unsigned int i = 0; i < m_fixit_hints.length (); i++)
1529 {
1530 const fixit_hint *hint = m_fixit_hints[i];
1531 gcc_assert (hint);
1532 tmp_spans.safe_push (obj: get_line_span_for_fixit_hint (hint));
1533 }
1534
1535 /* Sort them. */
1536 tmp_spans.qsort(line_span::comparator);
1537
1538 /* Now iterate through tmp_spans, copying into m_line_spans, and
1539 combining where possible. */
1540 gcc_assert (tmp_spans.length () > 0);
1541 m_line_spans.safe_push (obj: tmp_spans[0]);
1542 for (unsigned int i = 1; i < tmp_spans.length (); i++)
1543 {
1544 line_span *current = &m_line_spans[m_line_spans.length () - 1];
1545 const line_span *next = &tmp_spans[i];
1546 gcc_assert (next->m_first_line >= current->m_first_line);
1547 const int merger_distance = m_options.show_line_numbers_p ? 1 : 0;
1548 if ((linenum_arith_t)next->m_first_line
1549 <= (linenum_arith_t)current->m_last_line + 1 + merger_distance)
1550 {
1551 /* We can merge them. */
1552 if (next->m_last_line > current->m_last_line)
1553 current->m_last_line = next->m_last_line;
1554 }
1555 else
1556 {
1557 /* No merger possible. */
1558 m_line_spans.safe_push (obj: *next);
1559 }
1560 }
1561
1562 /* Verify the result, in m_line_spans. */
1563 gcc_assert (m_line_spans.length () > 0);
1564 for (unsigned int i = 1; i < m_line_spans.length (); i++)
1565 {
1566 const line_span *prev = &m_line_spans[i - 1];
1567 const line_span *next = &m_line_spans[i];
1568 /* The individual spans must be sane. */
1569 gcc_assert (prev->m_first_line <= prev->m_last_line);
1570 gcc_assert (next->m_first_line <= next->m_last_line);
1571 /* The spans must be ordered. */
1572 gcc_assert (prev->m_first_line < next->m_first_line);
1573 /* There must be a gap of at least one line between separate spans. */
1574 gcc_assert ((prev->m_last_line + 1) < next->m_first_line);
1575 }
1576}
1577
1578/* Determine how many display columns need to be reserved for line numbers,
1579 based on the largest line number that will be needed, and populate
1580 m_linenum_width. */
1581
1582void
1583layout::calculate_linenum_width ()
1584{
1585 gcc_assert (m_line_spans.length () > 0);
1586 const line_span *last_span = &m_line_spans[m_line_spans.length () - 1];
1587 int highest_line = last_span->m_last_line;
1588 if (highest_line < 0)
1589 highest_line = 0;
1590 m_linenum_width = num_digits (highest_line);
1591 /* If we're showing jumps in the line-numbering, allow at least 3 chars. */
1592 if (m_line_spans.length () > 1)
1593 m_linenum_width = MAX (m_linenum_width, 3);
1594 /* If there's a minimum margin width, apply it (subtracting 1 for the space
1595 after the line number. */
1596 m_linenum_width = MAX (m_linenum_width, m_options.min_margin_width - 1);
1597}
1598
1599/* Calculate m_x_offset_display, which improves readability in case the source
1600 line of interest is longer than the user's display. All lines output will be
1601 shifted to the left (so that their beginning is no longer displayed) by
1602 m_x_offset_display display columns, so that the caret is in a reasonable
1603 location. */
1604
1605void
1606layout::calculate_x_offset_display ()
1607{
1608 m_x_offset_display = 0;
1609
1610 const int max_width = m_options.max_width;
1611 if (!max_width)
1612 {
1613 /* Nothing to do, the width is not capped. */
1614 return;
1615 }
1616
1617 const char_span line = location_get_source_line (file_path: m_exploc.file,
1618 line: m_exploc.line);
1619 if (!line)
1620 {
1621 /* Nothing to do, we couldn't find the source line. */
1622 return;
1623 }
1624 int caret_display_column = m_exploc.m_display_col;
1625 const int line_bytes
1626 = get_line_bytes_without_trailing_whitespace (line: line.get_buffer (),
1627 line_bytes: line.length ());
1628 int eol_display_column
1629 = cpp_display_width (data: line.get_buffer (), data_length: line_bytes, policy: m_policy);
1630 if (caret_display_column > eol_display_column
1631 || !caret_display_column)
1632 {
1633 /* This does not make sense, so don't try to do anything in this case. */
1634 return;
1635 }
1636
1637 /* Adjust caret and eol positions to include the left margin. If we are
1638 outputting line numbers, then the left margin is equal to m_linenum_width
1639 plus three for the " | " which follows it. Otherwise the left margin width
1640 is equal to 1, because layout::print_source_line() will prefix each line
1641 with a space. */
1642 const int source_display_cols = eol_display_column;
1643 int left_margin_size = 1;
1644 if (m_options.show_line_numbers_p)
1645 left_margin_size = m_linenum_width + 3;
1646 caret_display_column += left_margin_size;
1647 eol_display_column += left_margin_size;
1648
1649 if (eol_display_column <= max_width)
1650 {
1651 /* Nothing to do, everything fits in the display. */
1652 return;
1653 }
1654
1655 /* The line is too long for the display. Calculate an offset such that the
1656 caret is not too close to the right edge of the screen. It will be
1657 CARET_LINE_MARGIN display columns from the right edge, unless it is closer
1658 than that to the end of the source line anyway. */
1659 int right_margin_size = CARET_LINE_MARGIN;
1660 right_margin_size = MIN (eol_display_column - caret_display_column,
1661 right_margin_size);
1662 if (right_margin_size + left_margin_size >= max_width)
1663 {
1664 /* The max_width is very small, so anything we try to do will not be very
1665 effective; just punt in this case and output with no offset. */
1666 return;
1667 }
1668 const int max_caret_display_column = max_width - right_margin_size;
1669 if (caret_display_column > max_caret_display_column)
1670 {
1671 m_x_offset_display = caret_display_column - max_caret_display_column;
1672 /* Make sure we don't offset the line into oblivion. */
1673 static const int min_cols_visible = 2;
1674 if (source_display_cols - m_x_offset_display < min_cols_visible)
1675 m_x_offset_display = 0;
1676 }
1677}
1678
1679/* Print line ROW of source code, potentially colorized at any ranges, and
1680 return the line bounds. LINE is the source line (not necessarily
1681 0-terminated) and LINE_BYTES is its length in bytes. In order to handle both
1682 colorization and tab expansion, this function tracks the line position in
1683 both byte and display column units. */
1684
1685line_bounds
1686layout::print_source_line (linenum_type row, const char *line, int line_bytes)
1687{
1688 m_colorizer.set_normal_text ();
1689
1690 pp_emit_prefix (m_pp);
1691 if (m_options.show_line_numbers_p)
1692 {
1693 int width = num_digits (row);
1694 for (int i = 0; i < m_linenum_width - width; i++)
1695 pp_space (m_pp);
1696 pp_printf (m_pp, "%i | ", row);
1697 }
1698 else
1699 pp_space (m_pp);
1700
1701 /* We will stop printing the source line at any trailing whitespace. */
1702 line_bytes = get_line_bytes_without_trailing_whitespace (line,
1703 line_bytes);
1704
1705 /* This object helps to keep track of which display column we are at, which is
1706 necessary for computing the line bounds in display units, for doing
1707 tab expansion, and for implementing m_x_offset_display. */
1708 cpp_display_width_computation dw (line, line_bytes, m_policy);
1709
1710 /* Skip the first m_x_offset_display display columns. In case the leading
1711 portion that will be skipped ends with a character with wcwidth > 1, then
1712 it is possible we skipped too much, so account for that by padding with
1713 spaces. Note that this does the right thing too in case a tab was the last
1714 character to be skipped over; the tab is effectively replaced by the
1715 correct number of trailing spaces needed to offset by the desired number of
1716 display columns. */
1717 for (int skipped_display_cols = dw.advance_display_cols (n: m_x_offset_display);
1718 skipped_display_cols > m_x_offset_display; --skipped_display_cols)
1719 pp_space (m_pp);
1720
1721 /* Print the line and compute the line_bounds. */
1722 line_bounds lbounds;
1723 while (!dw.done ())
1724 {
1725 /* Assuming colorization is enabled for the caret and underline
1726 characters, we may also colorize the associated characters
1727 within the source line.
1728
1729 For frontends that generate range information, we color the
1730 associated characters in the source line the same as the
1731 carets and underlines in the annotation line, to make it easier
1732 for the reader to see the pertinent code.
1733
1734 For frontends that only generate carets, we don't colorize the
1735 characters above them, since this would look strange (e.g.
1736 colorizing just the first character in a token). */
1737 if (m_options.colorize_source_p)
1738 {
1739 bool in_range_p;
1740 point_state state;
1741 const int start_byte_col = dw.bytes_processed () + 1;
1742 in_range_p = get_state_at_point (row, column: start_byte_col,
1743 first_non_ws: 0, INT_MAX,
1744 col_unit: CU_BYTES,
1745 out_state: &state);
1746 if (in_range_p)
1747 m_colorizer.set_range (state.range_idx);
1748 else
1749 m_colorizer.set_normal_text ();
1750 }
1751
1752 /* Get the display width of the next character to be output, expanding
1753 tabs and replacing some control bytes with spaces as necessary. */
1754 const char *c = dw.next_byte ();
1755 const int start_disp_col = dw.display_cols_processed () + 1;
1756 cpp_decoded_char cp;
1757 const int this_display_width = dw.process_next_codepoint (out: &cp);
1758 if (*c == '\t')
1759 {
1760 /* The returned display width is the number of spaces into which the
1761 tab should be expanded. */
1762 for (int i = 0; i != this_display_width; ++i)
1763 pp_space (m_pp);
1764 continue;
1765 }
1766
1767 /* We have a (possibly multibyte) character to output; update the line
1768 bounds if it is not whitespace. */
1769 if (*c != ' ')
1770 {
1771 lbounds.m_last_non_ws_disp_col = dw.display_cols_processed ();
1772 if (lbounds.m_first_non_ws_disp_col == INT_MAX)
1773 lbounds.m_first_non_ws_disp_col = start_disp_col;
1774 }
1775
1776 /* Output the character. */
1777 m_policy.m_print_cb (m_pp, cp);
1778 c = dw.next_byte ();
1779 }
1780 print_newline ();
1781 return lbounds;
1782}
1783
1784/* Determine if we should print an annotation line for ROW.
1785 i.e. if any of m_layout_ranges contains ROW. */
1786
1787bool
1788layout::should_print_annotation_line_p (linenum_type row) const
1789{
1790 layout_range *range;
1791 int i;
1792 FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
1793 {
1794 if (range->m_range_display_kind == SHOW_LINES_WITHOUT_RANGE)
1795 return false;
1796 if (range->intersects_line_p (row))
1797 return true;
1798 }
1799 return false;
1800}
1801
1802/* Begin an annotation line. If m_show_line_numbers_p, print the left
1803 margin, which is empty for annotation lines. Otherwise, do nothing. */
1804
1805void
1806layout::start_annotation_line (char margin_char) const
1807{
1808 pp_emit_prefix (m_pp);
1809 if (m_options.show_line_numbers_p)
1810 {
1811 /* Print the margin. If MARGIN_CHAR != ' ', then print up to 3
1812 of it, right-aligned, padded with spaces. */
1813 int i;
1814 for (i = 0; i < m_linenum_width - 3; i++)
1815 pp_space (m_pp);
1816 for (; i < m_linenum_width; i++)
1817 pp_character (m_pp, margin_char);
1818 pp_string (m_pp, " |");
1819 }
1820}
1821
1822/* Print a line consisting of the caret/underlines for the given
1823 source line. */
1824
1825void
1826layout::print_annotation_line (linenum_type row, const line_bounds lbounds)
1827{
1828 int x_bound = get_x_bound_for_row (row, caret_column: m_exploc.m_display_col,
1829 last_non_ws: lbounds.m_last_non_ws_disp_col);
1830
1831 start_annotation_line ();
1832 pp_space (m_pp);
1833
1834 for (int column = 1 + m_x_offset_display; column < x_bound; column++)
1835 {
1836 bool in_range_p;
1837 point_state state;
1838 in_range_p = get_state_at_point (row, column,
1839 first_non_ws: lbounds.m_first_non_ws_disp_col,
1840 last_non_ws: lbounds.m_last_non_ws_disp_col,
1841 col_unit: CU_DISPLAY_COLS,
1842 out_state: &state);
1843 if (in_range_p)
1844 {
1845 /* Within a range. Draw either the caret or an underline. */
1846 m_colorizer.set_range (state.range_idx);
1847 if (state.draw_caret_p)
1848 {
1849 /* Draw the caret. */
1850 char caret_char;
1851 if (state.range_idx < rich_location::STATICALLY_ALLOCATED_RANGES)
1852 caret_char = m_options.caret_chars[state.range_idx];
1853 else
1854 caret_char = '^';
1855 pp_character (m_pp, caret_char);
1856 }
1857 else
1858 pp_character (m_pp, '~');
1859 }
1860 else
1861 {
1862 /* Not in a range. */
1863 m_colorizer.set_normal_text ();
1864 pp_character (m_pp, ' ');
1865 }
1866 }
1867 print_newline ();
1868}
1869
1870/* A version of label_text that can live inside a vec.
1871 Requires manual cleanup via maybe_free. */
1872
1873struct pod_label_text
1874{
1875 pod_label_text ()
1876 : m_buffer (NULL), m_caller_owned (false)
1877 {}
1878
1879 pod_label_text (label_text &&other)
1880 : m_buffer (const_cast<char*> (other.get ())),
1881 m_caller_owned (other.is_owner ())
1882 {
1883 other.release ();
1884 }
1885
1886 void maybe_free ()
1887 {
1888 if (m_caller_owned)
1889 free (ptr: m_buffer);
1890 }
1891
1892 char *m_buffer;
1893 bool m_caller_owned;
1894};
1895
1896/* Implementation detail of layout::print_any_labels.
1897
1898 A label within the given row of source. */
1899
1900class line_label
1901{
1902public:
1903 line_label (const cpp_char_column_policy &policy,
1904 int state_idx, int column,
1905 label_text text)
1906 : m_state_idx (state_idx), m_column (column),
1907 m_text (std::move (text)), m_label_line (0), m_has_vbar (true)
1908 {
1909 const int bytes = strlen (s: m_text.m_buffer);
1910 m_display_width = cpp_display_width (data: m_text.m_buffer, data_length: bytes, policy);
1911 }
1912
1913 /* Sorting is primarily by column, then by state index. */
1914 static int comparator (const void *p1, const void *p2)
1915 {
1916 const line_label *ll1 = (const line_label *)p1;
1917 const line_label *ll2 = (const line_label *)p2;
1918 int column_cmp = compare (lhs: ll1->m_column, rhs: ll2->m_column);
1919 if (column_cmp)
1920 return column_cmp;
1921 /* Order by reverse state index, so that labels are printed
1922 in order of insertion into the rich_location when the
1923 sorted list is walked backwards. */
1924 return -compare (lhs: ll1->m_state_idx, rhs: ll2->m_state_idx);
1925 }
1926
1927 int m_state_idx;
1928 int m_column;
1929 pod_label_text m_text;
1930 size_t m_display_width;
1931 int m_label_line;
1932 bool m_has_vbar;
1933};
1934
1935/* Print any labels in this row. */
1936void
1937layout::print_any_labels (linenum_type row)
1938{
1939 int i;
1940 auto_vec<line_label> labels;
1941
1942 /* Gather the labels that are to be printed into "labels". */
1943 {
1944 layout_range *range;
1945 FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
1946 {
1947 /* Most ranges don't have labels, so reject this first. */
1948 if (range->m_label == NULL)
1949 continue;
1950
1951 /* The range's caret must be on this line. */
1952 if (range->m_caret.m_line != row)
1953 continue;
1954
1955 /* Reject labels that aren't fully visible due to clipping
1956 by m_x_offset_display. */
1957 const int disp_col = range->m_caret.m_columns[CU_DISPLAY_COLS];
1958 if (disp_col <= m_x_offset_display)
1959 continue;
1960
1961 label_text text;
1962 text = range->m_label->get_text (range_idx: range->m_original_idx);
1963
1964 /* Allow for labels that return NULL from their get_text
1965 implementation (so e.g. such labels can control their own
1966 visibility). */
1967 if (text.get () == NULL)
1968 continue;
1969
1970 labels.safe_push (obj: line_label (m_policy, i, disp_col, std::move (text)));
1971 }
1972 }
1973
1974 /* Bail out if there are no labels on this row. */
1975 if (labels.length () == 0)
1976 return;
1977
1978 /* Sort them. */
1979 labels.qsort(line_label::comparator);
1980
1981 /* Figure out how many "label lines" we need, and which
1982 one each label is printed in.
1983
1984 For example, if the labels aren't too densely packed,
1985 we can fit them on the same line, giving two "label lines":
1986
1987 foo + bar
1988 ~~~ ~~~
1989 | | : label line 0
1990 l0 l1 : label line 1
1991
1992 If they would touch each other or overlap, then we need
1993 additional "label lines":
1994
1995 foo + bar
1996 ~~~ ~~~
1997 | | : label line 0
1998 | label 1 : label line 1
1999 label 0 : label line 2
2000
2001 Place the final label on label line 1, and work backwards, adding
2002 label lines as needed.
2003
2004 If multiple labels are at the same place, put them on separate
2005 label lines:
2006
2007 foo + bar
2008 ^ : label line 0
2009 | : label line 1
2010 label 0 : label line 2
2011 label 1 : label line 3. */
2012
2013 int max_label_line = 1;
2014 {
2015 int next_column = INT_MAX;
2016 line_label *label;
2017 FOR_EACH_VEC_ELT_REVERSE (labels, i, label)
2018 {
2019 /* Would this label "touch" or overlap the next label? */
2020 if (label->m_column + label->m_display_width >= (size_t)next_column)
2021 {
2022 max_label_line++;
2023
2024 /* If we've already seen labels with the same column, suppress the
2025 vertical bar for subsequent ones in this backwards iteration;
2026 hence only the one with the highest label_line has m_has_vbar set. */
2027 if (label->m_column == next_column)
2028 label->m_has_vbar = false;
2029 }
2030
2031 label->m_label_line = max_label_line;
2032 next_column = label->m_column;
2033 }
2034 }
2035
2036 /* Print the "label lines". For each label within the line, print
2037 either a vertical bar ('|') for the labels that are lower down, or the
2038 labels themselves once we've reached their line. */
2039 {
2040 for (int label_line = 0; label_line <= max_label_line; label_line++)
2041 {
2042 start_annotation_line ();
2043 pp_space (m_pp);
2044 int column = 1 + m_x_offset_display;
2045 line_label *label;
2046 FOR_EACH_VEC_ELT (labels, i, label)
2047 {
2048 if (label_line > label->m_label_line)
2049 /* We've printed all the labels for this label line. */
2050 break;
2051
2052 if (label_line == label->m_label_line)
2053 {
2054 gcc_assert (column <= label->m_column);
2055 move_to_column (column: &column, dest_column: label->m_column, add_left_margin: true);
2056 /* Colorize the text, unless it's for events in a
2057 diagnostic_path. */
2058 if (!m_diagnostic_path_p)
2059 m_colorizer.set_range (label->m_state_idx);
2060 pp_string (m_pp, label->m_text.m_buffer);
2061 m_colorizer.set_normal_text ();
2062 column += label->m_display_width;
2063 }
2064 else if (label->m_has_vbar)
2065 {
2066 gcc_assert (column <= label->m_column);
2067 move_to_column (column: &column, dest_column: label->m_column, add_left_margin: true);
2068 m_colorizer.set_range (label->m_state_idx);
2069 pp_character (m_pp, '|');
2070 m_colorizer.set_normal_text ();
2071 column++;
2072 }
2073 }
2074 print_newline ();
2075 }
2076 }
2077
2078 /* Clean up. */
2079 {
2080 line_label *label;
2081 FOR_EACH_VEC_ELT (labels, i, label)
2082 label->m_text.maybe_free ();
2083 }
2084}
2085
2086/* If there are any fixit hints inserting new lines before source line ROW,
2087 print them.
2088
2089 They are printed on lines of their own, before the source line
2090 itself, with a leading '+'. */
2091
2092void
2093layout::print_leading_fixits (linenum_type row)
2094{
2095 for (unsigned int i = 0; i < m_fixit_hints.length (); i++)
2096 {
2097 const fixit_hint *hint = m_fixit_hints[i];
2098
2099 if (!hint->ends_with_newline_p ())
2100 /* Not a newline fixit; print it in print_trailing_fixits. */
2101 continue;
2102
2103 gcc_assert (hint->insertion_p ());
2104
2105 if (hint->affects_line_p (set: m_line_table, file: m_exploc.file, line: row))
2106 {
2107 /* Printing the '+' with normal colorization
2108 and the inserted line with "insert" colorization
2109 helps them stand out from each other, and from
2110 the surrounding text. */
2111 m_colorizer.set_normal_text ();
2112 start_annotation_line (margin_char: '+');
2113 pp_character (m_pp, '+');
2114 m_colorizer.set_fixit_insert ();
2115 /* Print all but the trailing newline of the fix-it hint.
2116 We have to print the newline separately to avoid
2117 getting additional pp prefixes printed. */
2118 for (size_t i = 0; i < hint->get_length () - 1; i++)
2119 pp_character (m_pp, hint->get_string ()[i]);
2120 m_colorizer.set_normal_text ();
2121 pp_newline (m_pp);
2122 }
2123 }
2124}
2125
2126/* Subroutine of layout::print_trailing_fixits.
2127
2128 Determine if the annotation line printed for LINE contained
2129 the exact range from START_COLUMN to FINISH_COLUMN (in display units). */
2130
2131bool
2132layout::annotation_line_showed_range_p (linenum_type line, int start_column,
2133 int finish_column) const
2134{
2135 layout_range *range;
2136 int i;
2137 FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
2138 if (range->m_start.m_line == line
2139 && range->m_start.m_columns[CU_DISPLAY_COLS] == start_column
2140 && range->m_finish.m_line == line
2141 && range->m_finish.m_columns[CU_DISPLAY_COLS] == finish_column)
2142 return true;
2143 return false;
2144}
2145
2146/* Classes for printing trailing fix-it hints i.e. those that
2147 don't add new lines.
2148
2149 For insertion, these can look like:
2150
2151 new_text
2152
2153 For replacement, these can look like:
2154
2155 ------------- : underline showing affected range
2156 new_text
2157
2158 For deletion, these can look like:
2159
2160 ------------- : underline showing affected range
2161
2162 This can become confusing if they overlap, and so we need
2163 to do some preprocessing to decide what to print.
2164 We use the list of fixit_hint instances affecting the line
2165 to build a list of "correction" instances, and print the
2166 latter.
2167
2168 For example, consider a set of fix-its for converting
2169 a C-style cast to a C++ const_cast.
2170
2171 Given:
2172
2173 ..000000000111111111122222222223333333333.
2174 ..123456789012345678901234567890123456789.
2175 foo *f = (foo *)ptr->field;
2176 ^~~~~
2177
2178 and the fix-it hints:
2179 - replace col 10 (the open paren) with "const_cast<"
2180 - replace col 16 (the close paren) with "> ("
2181 - insert ")" before col 27
2182
2183 then we would get odd-looking output:
2184
2185 foo *f = (foo *)ptr->field;
2186 ^~~~~
2187 -
2188 const_cast<
2189 -
2190 > ( )
2191
2192 It would be better to detect when fixit hints are going to
2193 overlap (those that require new lines), and to consolidate
2194 the printing of such fixits, giving something like:
2195
2196 foo *f = (foo *)ptr->field;
2197 ^~~~~
2198 -----------------
2199 const_cast<foo *> (ptr->field)
2200
2201 This works by detecting when the printing would overlap, and
2202 effectively injecting no-op replace hints into the gaps between
2203 such fix-its, so that the printing joins up.
2204
2205 In the above example, the overlap of:
2206 - replace col 10 (the open paren) with "const_cast<"
2207 and:
2208 - replace col 16 (the close paren) with "> ("
2209 is fixed by injecting a no-op:
2210 - replace cols 11-15 with themselves ("foo *")
2211 and consolidating these, making:
2212 - replace cols 10-16 with "const_cast<" + "foo *" + "> ("
2213 i.e.:
2214 - replace cols 10-16 with "const_cast<foo *> ("
2215
2216 This overlaps with the final fix-it hint:
2217 - insert ")" before col 27
2218 and so we repeat the consolidation process, by injecting
2219 a no-op:
2220 - replace cols 17-26 with themselves ("ptr->field")
2221 giving:
2222 - replace cols 10-26 with "const_cast<foo *> (" + "ptr->field" + ")"
2223 i.e.:
2224 - replace cols 10-26 with "const_cast<foo *> (ptr->field)"
2225
2226 and is thus printed as desired. */
2227
2228/* A range of (byte or display) columns within a line. */
2229
2230class column_range
2231{
2232public:
2233 column_range (int start_, int finish_) : start (start_), finish (finish_)
2234 {
2235 gcc_assert (valid_p (start, finish));
2236 }
2237
2238 bool operator== (const column_range &other) const
2239 {
2240 return start == other.start && finish == other.finish;
2241 }
2242
2243 static bool valid_p (int start, int finish)
2244 {
2245 /* We must have either a range, or an insertion. */
2246 return (start <= finish || finish == start - 1);
2247 }
2248
2249 int start;
2250 int finish;
2251};
2252
2253/* Get the range of bytes or display columns that HINT would affect. */
2254static column_range
2255get_affected_range (const cpp_char_column_policy &policy,
2256 const fixit_hint *hint, enum column_unit col_unit)
2257{
2258 expanded_location exploc_start = expand_location (hint->get_start_loc ());
2259 expanded_location exploc_finish = expand_location (hint->get_next_loc ());
2260 --exploc_finish.column;
2261
2262 int start_column;
2263 int finish_column;
2264 if (col_unit == CU_DISPLAY_COLS)
2265 {
2266 start_column = location_compute_display_column (exploc: exploc_start, policy);
2267 if (hint->insertion_p ())
2268 finish_column = start_column - 1;
2269 else
2270 finish_column = location_compute_display_column (exploc: exploc_finish, policy);
2271 }
2272 else
2273 {
2274 start_column = exploc_start.column;
2275 finish_column = exploc_finish.column;
2276 }
2277 return column_range (start_column, finish_column);
2278}
2279
2280/* Get the range of display columns that would be printed for HINT. */
2281
2282static column_range
2283get_printed_columns (const cpp_char_column_policy &policy,
2284 const fixit_hint *hint)
2285{
2286 expanded_location exploc = expand_location (hint->get_start_loc ());
2287 int start_column = location_compute_display_column (exploc, policy);
2288 int hint_width = cpp_display_width (data: hint->get_string (), data_length: hint->get_length (),
2289 policy);
2290 int final_hint_column = start_column + hint_width - 1;
2291 if (hint->insertion_p ())
2292 {
2293 return column_range (start_column, final_hint_column);
2294 }
2295 else
2296 {
2297 exploc = expand_location (hint->get_next_loc ());
2298 --exploc.column;
2299 int finish_column = location_compute_display_column (exploc, policy);
2300 return column_range (start_column,
2301 MAX (finish_column, final_hint_column));
2302 }
2303}
2304
2305/* A correction on a particular line.
2306 This describes a plan for how to print one or more fixit_hint
2307 instances that affected the line, potentially consolidating hints
2308 into corrections to make the result easier for the user to read. */
2309
2310class correction
2311{
2312public:
2313 correction (column_range affected_bytes,
2314 column_range affected_columns,
2315 column_range printed_columns,
2316 const char *new_text, size_t new_text_len,
2317 const cpp_char_column_policy &policy)
2318 : m_affected_bytes (affected_bytes),
2319 m_affected_columns (affected_columns),
2320 m_printed_columns (printed_columns),
2321 m_text (xstrdup (new_text)),
2322 m_byte_length (new_text_len),
2323 m_policy (policy),
2324 m_alloc_sz (new_text_len + 1)
2325 {
2326 compute_display_cols ();
2327 }
2328
2329 ~correction () { free (ptr: m_text); }
2330
2331 bool insertion_p () const
2332 {
2333 return m_affected_bytes.start == m_affected_bytes.finish + 1;
2334 }
2335
2336 void ensure_capacity (size_t len);
2337 void ensure_terminated ();
2338
2339 void compute_display_cols ()
2340 {
2341 m_display_cols = cpp_display_width (data: m_text, data_length: m_byte_length, policy: m_policy);
2342 }
2343
2344 void overwrite (int dst_offset, const char_span &src_span)
2345 {
2346 gcc_assert (dst_offset >= 0);
2347 gcc_assert (dst_offset + src_span.length () < m_alloc_sz);
2348 memcpy (dest: m_text + dst_offset, src: src_span.get_buffer (),
2349 n: src_span.length ());
2350 }
2351
2352 /* If insert, then start: the column before which the text
2353 is to be inserted, and finish is offset by the length of
2354 the replacement.
2355 If replace, then the range of columns affected. */
2356 column_range m_affected_bytes;
2357 column_range m_affected_columns;
2358
2359 /* If insert, then start: the column before which the text
2360 is to be inserted, and finish is offset by the length of
2361 the replacement.
2362 If replace, then the range of columns affected. */
2363 column_range m_printed_columns;
2364
2365 /* The text to be inserted/used as replacement. */
2366 char *m_text;
2367 size_t m_byte_length; /* Not including null-terminator. */
2368 int m_display_cols;
2369 const cpp_char_column_policy &m_policy;
2370 size_t m_alloc_sz;
2371};
2372
2373/* Ensure that m_text can hold a string of length LEN
2374 (plus 1 for 0-termination). */
2375
2376void
2377correction::ensure_capacity (size_t len)
2378{
2379 /* Allow 1 extra byte for 0-termination. */
2380 if (m_alloc_sz < (len + 1))
2381 {
2382 size_t new_alloc_sz = (len + 1) * 2;
2383 m_text = (char *)xrealloc (m_text, new_alloc_sz);
2384 m_alloc_sz = new_alloc_sz;
2385 }
2386}
2387
2388/* Ensure that m_text is 0-terminated. */
2389
2390void
2391correction::ensure_terminated ()
2392{
2393 /* 0-terminate the buffer. */
2394 gcc_assert (m_byte_length < m_alloc_sz);
2395 m_text[m_byte_length] = '\0';
2396}
2397
2398/* A list of corrections affecting a particular line.
2399 This is used by layout::print_trailing_fixits for planning
2400 how to print the fix-it hints affecting the line. */
2401
2402class line_corrections
2403{
2404public:
2405 line_corrections (const char_display_policy &policy,
2406 const char *filename,
2407 linenum_type row)
2408 : m_policy (policy), m_filename (filename), m_row (row)
2409 {}
2410 ~line_corrections ();
2411
2412 void add_hint (const fixit_hint *hint);
2413
2414 const char_display_policy &m_policy;
2415 const char *m_filename;
2416 linenum_type m_row;
2417 auto_vec <correction *> m_corrections;
2418};
2419
2420/* struct line_corrections. */
2421
2422line_corrections::~line_corrections ()
2423{
2424 unsigned i;
2425 correction *c;
2426 FOR_EACH_VEC_ELT (m_corrections, i, c)
2427 delete c;
2428}
2429
2430/* A struct wrapping a particular source line, allowing
2431 run-time bounds-checking of accesses in a checked build. */
2432
2433class source_line
2434{
2435public:
2436 source_line (const char *filename, int line);
2437
2438 char_span as_span () { return char_span (chars, width); }
2439
2440 const char *chars;
2441 int width;
2442};
2443
2444/* source_line's ctor. */
2445
2446source_line::source_line (const char *filename, int line)
2447{
2448 char_span span = location_get_source_line (file_path: filename, line);
2449 chars = span.get_buffer ();
2450 width = span.length ();
2451}
2452
2453/* Add HINT to the corrections for this line.
2454 Attempt to consolidate nearby hints so that they will not
2455 overlap with printed. */
2456
2457void
2458line_corrections::add_hint (const fixit_hint *hint)
2459{
2460 column_range affected_bytes = get_affected_range (policy: m_policy, hint, col_unit: CU_BYTES);
2461 column_range affected_columns = get_affected_range (policy: m_policy, hint,
2462 col_unit: CU_DISPLAY_COLS);
2463 column_range printed_columns = get_printed_columns (policy: m_policy, hint);
2464
2465 /* Potentially consolidate. */
2466 if (!m_corrections.is_empty ())
2467 {
2468 correction *last_correction
2469 = m_corrections[m_corrections.length () - 1];
2470
2471 /* The following consolidation code assumes that the fix-it hints
2472 have been sorted by start (done within layout's ctor). */
2473 gcc_assert (affected_bytes.start
2474 >= last_correction->m_affected_bytes.start);
2475 gcc_assert (printed_columns.start
2476 >= last_correction->m_printed_columns.start);
2477
2478 if (printed_columns.start <= last_correction->m_printed_columns.finish
2479 && column_range::valid_p (start: last_correction->m_affected_bytes.finish + 1,
2480 finish: affected_bytes.start - 1))
2481 {
2482 /* We have two hints for which the printed forms of the hints
2483 would touch or overlap, so we need to consolidate them to avoid
2484 confusing the user.
2485 Attempt to inject a "replace" correction from immediately
2486 after the end of the last hint to immediately before the start
2487 of the next hint. */
2488 column_range between (last_correction->m_affected_bytes.finish + 1,
2489 affected_bytes.start - 1);
2490
2491 /* Try to read the source. */
2492 source_line line (m_filename, m_row);
2493 if (line.chars && between.finish < line.width)
2494 {
2495 /* Consolidate into the last correction:
2496 add a no-op "replace" of the "between" text, and
2497 add the text from the new hint. */
2498 int old_byte_len = last_correction->m_byte_length;
2499 gcc_assert (old_byte_len >= 0);
2500 int between_byte_len = between.finish + 1 - between.start;
2501 gcc_assert (between_byte_len >= 0);
2502 int new_byte_len
2503 = old_byte_len + between_byte_len + hint->get_length ();
2504 gcc_assert (new_byte_len >= 0);
2505 last_correction->ensure_capacity (len: new_byte_len);
2506 last_correction->overwrite
2507 (dst_offset: old_byte_len,
2508 src_span: line.as_span ().subspan (offset: between.start - 1,
2509 n_elts: between.finish + 1 - between.start));
2510 last_correction->overwrite (dst_offset: old_byte_len + between_byte_len,
2511 src_span: char_span (hint->get_string (),
2512 hint->get_length ()));
2513 last_correction->m_byte_length = new_byte_len;
2514 last_correction->ensure_terminated ();
2515 last_correction->m_affected_bytes.finish
2516 = affected_bytes.finish;
2517 last_correction->m_affected_columns.finish
2518 = affected_columns.finish;
2519 int prev_display_cols = last_correction->m_display_cols;
2520 last_correction->compute_display_cols ();
2521 last_correction->m_printed_columns.finish
2522 += last_correction->m_display_cols - prev_display_cols;
2523 return;
2524 }
2525 }
2526 }
2527
2528 /* If no consolidation happened, add a new correction instance. */
2529 m_corrections.safe_push (obj: new correction (affected_bytes,
2530 affected_columns,
2531 printed_columns,
2532 hint->get_string (),
2533 hint->get_length (),
2534 m_policy));
2535}
2536
2537/* If there are any fixit hints on source line ROW, print them.
2538 They are printed in order, attempting to combine them onto lines, but
2539 starting new lines if necessary.
2540 Fix-it hints that insert new lines are handled separately,
2541 in layout::print_leading_fixits. */
2542
2543void
2544layout::print_trailing_fixits (linenum_type row)
2545{
2546 /* Build a list of correction instances for the line,
2547 potentially consolidating hints (for the sake of readability). */
2548 line_corrections corrections (m_policy, m_exploc.file, row);
2549 for (unsigned int i = 0; i < m_fixit_hints.length (); i++)
2550 {
2551 const fixit_hint *hint = m_fixit_hints[i];
2552
2553 /* Newline fixits are handled by layout::print_leading_fixits. */
2554 if (hint->ends_with_newline_p ())
2555 continue;
2556
2557 if (hint->affects_line_p (set: m_line_table, file: m_exploc.file, line: row))
2558 corrections.add_hint (hint);
2559 }
2560
2561 /* Now print the corrections. */
2562 unsigned i;
2563 correction *c;
2564 int column = m_x_offset_display;
2565
2566 if (!corrections.m_corrections.is_empty ())
2567 start_annotation_line ();
2568
2569 FOR_EACH_VEC_ELT (corrections.m_corrections, i, c)
2570 {
2571 /* For now we assume each fixit hint can only touch one line. */
2572 if (c->insertion_p ())
2573 {
2574 /* This assumes the insertion just affects one line. */
2575 int start_column = c->m_printed_columns.start;
2576 move_to_column (column: &column, dest_column: start_column, add_left_margin: true);
2577 m_colorizer.set_fixit_insert ();
2578 pp_string (m_pp, c->m_text);
2579 m_colorizer.set_normal_text ();
2580 column += c->m_display_cols;
2581 }
2582 else
2583 {
2584 /* If the range of the replacement wasn't printed in the
2585 annotation line, then print an extra underline to
2586 indicate exactly what is being replaced.
2587 Always show it for removals. */
2588 int start_column = c->m_affected_columns.start;
2589 int finish_column = c->m_affected_columns.finish;
2590 if (!annotation_line_showed_range_p (line: row, start_column,
2591 finish_column)
2592 || c->m_byte_length == 0)
2593 {
2594 move_to_column (column: &column, dest_column: start_column, add_left_margin: true);
2595 m_colorizer.set_fixit_delete ();
2596 for (; column <= finish_column; column++)
2597 pp_character (m_pp, '-');
2598 m_colorizer.set_normal_text ();
2599 }
2600 /* Print the replacement text. REPLACE also covers
2601 removals, so only do this extra work (potentially starting
2602 a new line) if we have actual replacement text. */
2603 if (c->m_byte_length > 0)
2604 {
2605 move_to_column (column: &column, dest_column: start_column, add_left_margin: true);
2606 m_colorizer.set_fixit_insert ();
2607 pp_string (m_pp, c->m_text);
2608 m_colorizer.set_normal_text ();
2609 column += c->m_display_cols;
2610 }
2611 }
2612 }
2613
2614 /* Add a trailing newline, if necessary. */
2615 move_to_column (column: &column, dest_column: 0, add_left_margin: false);
2616}
2617
2618/* Disable any colorization and emit a newline. */
2619
2620void
2621layout::print_newline ()
2622{
2623 m_colorizer.set_normal_text ();
2624 pp_newline (m_pp);
2625}
2626
2627/* Return true if (ROW/COLUMN) is within a range of the layout.
2628 If it returns true, OUT_STATE is written to, with the
2629 range index, and whether we should draw the caret at
2630 (ROW/COLUMN) (as opposed to an underline). COL_UNIT controls
2631 whether all inputs and outputs are in bytes or display column units. */
2632
2633bool
2634layout::get_state_at_point (/* Inputs. */
2635 linenum_type row, int column,
2636 int first_non_ws, int last_non_ws,
2637 enum column_unit col_unit,
2638 /* Outputs. */
2639 point_state *out_state)
2640{
2641 layout_range *range;
2642 int i;
2643 FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
2644 {
2645 if (range->m_range_display_kind == SHOW_LINES_WITHOUT_RANGE)
2646 /* Bail out early, so that such ranges don't affect underlining or
2647 source colorization. */
2648 continue;
2649
2650 if (range->contains_point (row, column, col_unit))
2651 {
2652 out_state->range_idx = i;
2653
2654 /* Are we at the range's caret? is it visible? */
2655 out_state->draw_caret_p = false;
2656 if (range->m_range_display_kind == SHOW_RANGE_WITH_CARET
2657 && row == range->m_caret.m_line
2658 && column == range->m_caret.m_columns[col_unit])
2659 out_state->draw_caret_p = true;
2660
2661 /* Within a multiline range, don't display any underline
2662 in any leading or trailing whitespace on a line.
2663 We do display carets, however. */
2664 if (!out_state->draw_caret_p)
2665 if (column < first_non_ws || column > last_non_ws)
2666 return false;
2667
2668 /* We are within a range. */
2669 return true;
2670 }
2671 }
2672
2673 return false;
2674}
2675
2676/* Helper function for use by layout::print_line when printing the
2677 annotation line under the source line.
2678 Get the display column beyond the rightmost one that could contain a caret
2679 or range marker, given that we stop rendering at trailing whitespace.
2680 ROW is the source line within the given file.
2681 CARET_COLUMN is the display column of range 0's caret.
2682 LAST_NON_WS_COLUMN is the last display column containing a non-whitespace
2683 character of source (as determined when printing the source line). */
2684
2685int
2686layout::get_x_bound_for_row (linenum_type row, int caret_column,
2687 int last_non_ws_column)
2688{
2689 int result = caret_column + 1;
2690
2691 layout_range *range;
2692 int i;
2693 FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
2694 {
2695 if (row >= range->m_start.m_line)
2696 {
2697 if (range->m_finish.m_line == row)
2698 {
2699 /* On the final line within a range; ensure that
2700 we render up to the end of the range. */
2701 const int disp_col = range->m_finish.m_columns[CU_DISPLAY_COLS];
2702 if (result <= disp_col)
2703 result = disp_col + 1;
2704 }
2705 else if (row < range->m_finish.m_line)
2706 {
2707 /* Within a multiline range; ensure that we render up to the
2708 last non-whitespace column. */
2709 if (result <= last_non_ws_column)
2710 result = last_non_ws_column + 1;
2711 }
2712 }
2713 }
2714
2715 return result;
2716}
2717
2718/* Given *COLUMN as an x-coordinate, print spaces to position
2719 successive output at DEST_COLUMN, printing a newline if necessary,
2720 and updating *COLUMN. If ADD_LEFT_MARGIN, then print the (empty)
2721 left margin after any newline. */
2722
2723void
2724layout::move_to_column (int *column, int dest_column, bool add_left_margin)
2725{
2726 /* Start a new line if we need to. */
2727 if (*column > dest_column)
2728 {
2729 print_newline ();
2730 if (add_left_margin)
2731 start_annotation_line ();
2732 *column = m_x_offset_display;
2733 }
2734
2735 while (*column < dest_column)
2736 {
2737 pp_space (m_pp);
2738 (*column)++;
2739 }
2740}
2741
2742/* For debugging layout issues, render a ruler giving column numbers
2743 (after the 1-column indent). */
2744
2745void
2746layout::show_ruler (int max_column) const
2747{
2748 /* Hundreds. */
2749 if (max_column > 99)
2750 {
2751 start_annotation_line ();
2752 pp_space (m_pp);
2753 for (int column = 1 + m_x_offset_display; column <= max_column; column++)
2754 if (column % 10 == 0)
2755 pp_character (m_pp, '0' + (column / 100) % 10);
2756 else
2757 pp_space (m_pp);
2758 pp_newline (m_pp);
2759 }
2760
2761 /* Tens. */
2762 start_annotation_line ();
2763 pp_space (m_pp);
2764 for (int column = 1 + m_x_offset_display; column <= max_column; column++)
2765 if (column % 10 == 0)
2766 pp_character (m_pp, '0' + (column / 10) % 10);
2767 else
2768 pp_space (m_pp);
2769 pp_newline (m_pp);
2770
2771 /* Units. */
2772 start_annotation_line ();
2773 pp_space (m_pp);
2774 for (int column = 1 + m_x_offset_display; column <= max_column; column++)
2775 pp_character (m_pp, '0' + (column % 10));
2776 pp_newline (m_pp);
2777}
2778
2779/* Print leading fix-its (for new lines inserted before the source line)
2780 then the source line, followed by an annotation line
2781 consisting of any caret/underlines, then any fixits.
2782 If the source line can't be read, print nothing. */
2783void
2784layout::print_line (linenum_type row)
2785{
2786 char_span line = location_get_source_line (file_path: m_exploc.file, line: row);
2787 if (!line)
2788 return;
2789
2790 print_leading_fixits (row);
2791 const line_bounds lbounds
2792 = print_source_line (row, line: line.get_buffer (), line_bytes: line.length ());
2793 if (should_print_annotation_line_p (row))
2794 print_annotation_line (row, lbounds);
2795 if (m_options.show_labels_p)
2796 print_any_labels (row);
2797 print_trailing_fixits (row);
2798}
2799
2800} /* End of anonymous namespace. */
2801
2802/* If LOC is within the spans of lines that will already be printed for
2803 this gcc_rich_location, then add it as a secondary location and return true.
2804
2805 Otherwise return false. */
2806
2807bool
2808gcc_rich_location::add_location_if_nearby (location_t loc,
2809 bool restrict_to_current_line_spans,
2810 const range_label *label)
2811{
2812 /* Use the layout location-handling logic to sanitize LOC,
2813 filtering it to the current line spans within a temporary
2814 layout instance. */
2815 layout layout (*global_dc, *this, DK_ERROR, nullptr);
2816 location_range loc_range;
2817 loc_range.m_loc = loc;
2818 loc_range.m_range_display_kind = SHOW_RANGE_WITHOUT_CARET;
2819 if (!layout.maybe_add_location_range (loc_range: &loc_range, original_idx: 0,
2820 restrict_to_current_line_spans))
2821 return false;
2822
2823 add_range (loc, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label);
2824 return true;
2825}
2826
2827/* As per diagnostic_context::show_locus, but don't print anything
2828 if source printing is disabled, or if the location hasn't changed. */
2829
2830void
2831diagnostic_context::maybe_show_locus (const rich_location &richloc,
2832 diagnostic_t diagnostic_kind,
2833 pretty_printer *pp)
2834{
2835 const location_t loc = richloc.get_loc ();
2836 /* Do nothing if source-printing has been disabled. */
2837 if (!m_source_printing.enabled)
2838 return;
2839
2840 /* Don't attempt to print source for UNKNOWN_LOCATION and for builtins. */
2841 if (loc <= BUILTINS_LOCATION)
2842 return;
2843
2844 /* Don't print the same source location twice in a row, unless we have
2845 fix-it hints, or multiple locations, or a label. */
2846 if (loc == m_last_location
2847 && richloc.get_num_fixit_hints () == 0
2848 && richloc.get_num_locations () == 1
2849 && richloc.get_range (idx: 0)->m_label == NULL)
2850 return;
2851
2852 m_last_location = loc;
2853
2854 show_locus (richloc, diagnostic_kind, pp);
2855}
2856
2857/* Print the physical source code corresponding to the location of
2858 this diagnostic, with additional annotations.
2859 If PP is non-null, then use it rather than this context's printer. */
2860
2861void
2862diagnostic_context::show_locus (const rich_location &richloc,
2863 diagnostic_t diagnostic_kind,
2864 pretty_printer *pp)
2865{
2866 layout layout (*this, richloc, diagnostic_kind, pp);
2867 for (int line_span_idx = 0; line_span_idx < layout.get_num_line_spans ();
2868 line_span_idx++)
2869 {
2870 const line_span *line_span = layout.get_line_span (idx: line_span_idx);
2871 if (m_source_printing.show_line_numbers_p)
2872 {
2873 /* With line numbers, we should show whenever the line-numbering
2874 "jumps". */
2875 if (line_span_idx > 0)
2876 layout.print_gap_in_line_numbering ();
2877 }
2878 else
2879 {
2880 /* Without line numbers, we print headings for some line spans. */
2881 if (layout.print_heading_for_line_span_index_p (line_span_idx))
2882 {
2883 expanded_location exploc
2884 = layout.get_expanded_location (line_span);
2885 m_text_callbacks.start_span (this, exploc);
2886 }
2887 }
2888 /* Iterate over the lines within this span (using linenum_arith_t to
2889 avoid overflow with 0xffffffff causing an infinite loop). */
2890 linenum_arith_t last_line = line_span->get_last_line ();
2891 for (linenum_arith_t row = line_span->get_first_line ();
2892 row <= last_line; row++)
2893 layout.print_line (row);
2894 }
2895}
2896
2897#if CHECKING_P
2898
2899namespace selftest {
2900
2901/* Selftests for diagnostic_show_locus. */
2902
2903/* Verify that cpp_display_width correctly handles escaping. */
2904
2905static void
2906test_display_widths ()
2907{
2908 gcc_rich_location richloc (UNKNOWN_LOCATION);
2909
2910 /* U+03C0 "GREEK SMALL LETTER PI". */
2911 const char *pi = "\xCF\x80";
2912 /* U+1F642 "SLIGHTLY SMILING FACE". */
2913 const char *emoji = "\xF0\x9F\x99\x82";
2914 /* Stray trailing byte of a UTF-8 character. */
2915 const char *stray = "\xBF";
2916 /* U+10FFFF. */
2917 const char *max_codepoint = "\xF4\x8F\xBF\xBF";
2918
2919 /* No escaping. */
2920 {
2921 test_diagnostic_context dc;
2922 char_display_policy policy (make_policy (dc, richloc));
2923 ASSERT_EQ (cpp_display_width (pi, strlen (pi), policy), 1);
2924 ASSERT_EQ (cpp_display_width (emoji, strlen (emoji), policy), 2);
2925 ASSERT_EQ (cpp_display_width (stray, strlen (stray), policy), 1);
2926 /* Don't check width of U+10FFFF; it's in a private use plane. */
2927 }
2928
2929 richloc.set_escape_on_output (true);
2930
2931 {
2932 test_diagnostic_context dc;
2933 dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_UNICODE);
2934 char_display_policy policy (make_policy (dc, richloc));
2935 ASSERT_EQ (cpp_display_width (pi, strlen (pi), policy), 8);
2936 ASSERT_EQ (cpp_display_width (emoji, strlen (emoji), policy), 9);
2937 ASSERT_EQ (cpp_display_width (stray, strlen (stray), policy), 4);
2938 ASSERT_EQ (cpp_display_width (max_codepoint, strlen (max_codepoint),
2939 policy),
2940 strlen ("<U+10FFFF>"));
2941 }
2942
2943 {
2944 test_diagnostic_context dc;
2945 dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_BYTES);
2946 char_display_policy policy (make_policy (dc, richloc));
2947 ASSERT_EQ (cpp_display_width (pi, strlen (pi), policy), 8);
2948 ASSERT_EQ (cpp_display_width (emoji, strlen (emoji), policy), 16);
2949 ASSERT_EQ (cpp_display_width (stray, strlen (stray), policy), 4);
2950 ASSERT_EQ (cpp_display_width (max_codepoint, strlen (max_codepoint),
2951 policy),
2952 16);
2953 }
2954}
2955
2956/* For precise tests of the layout, make clear where the source line will
2957 start. test_left_margin sets the total byte count from the left side of the
2958 screen to the start of source lines, after the line number and the separator,
2959 which consists of the three characters " | ". */
2960static const int test_linenum_sep = 3;
2961static const int test_left_margin = 7;
2962
2963/* Helper function for test_layout_x_offset_display_utf8(). */
2964static void
2965test_offset_impl (int caret_byte_col, int max_width,
2966 int expected_x_offset_display,
2967 int left_margin = test_left_margin)
2968{
2969 test_diagnostic_context dc;
2970 dc.m_source_printing.max_width = max_width;
2971 /* diagnostic_context::min_margin_width sets the minimum space reserved for
2972 the line number plus one space after. */
2973 dc.m_source_printing.min_margin_width = left_margin - test_linenum_sep + 1;
2974 dc.m_source_printing.show_line_numbers_p = true;
2975 rich_location richloc (line_table,
2976 linemap_position_for_column (line_table,
2977 caret_byte_col));
2978 layout test_layout (dc, richloc, DK_ERROR, nullptr);
2979 ASSERT_EQ (left_margin - test_linenum_sep,
2980 test_layout.get_linenum_width ());
2981 ASSERT_EQ (expected_x_offset_display,
2982 test_layout.get_x_offset_display ());
2983}
2984
2985/* Test that layout::calculate_x_offset_display() works. */
2986static void
2987test_layout_x_offset_display_utf8 (const line_table_case &case_)
2988{
2989
2990 const char *content
2991 = "This line is very long, so that we can use it to test the logic for "
2992 "clipping long lines. Also this: \xf0\x9f\x98\x82\xf0\x9f\x98\x82 is a "
2993 "pair of emojis that occupies 8 bytes and 4 display columns, starting at "
2994 "column #102.\n";
2995
2996 /* Number of bytes in the line, subtracting one to remove the newline. */
2997 const int line_bytes = strlen (s: content) - 1;
2998
2999 /* Number of display columns occupied by the line; each of the 2 emojis
3000 takes up 2 fewer display columns than it does bytes. */
3001 const int line_display_cols = line_bytes - 2*2;
3002
3003 /* The column of the first emoji. Byte or display is the same as there are
3004 no multibyte characters earlier on the line. */
3005 const int emoji_col = 102;
3006
3007 temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
3008 line_table_test ltt (case_);
3009
3010 linemap_add (line_table, LC_ENTER, sysp: false, to_file: tmp.get_filename (), to_line: 1);
3011
3012 location_t line_end = linemap_position_for_column (line_table, line_bytes);
3013
3014 /* Don't attempt to run the tests if column data might be unavailable. */
3015 if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
3016 return;
3017
3018 ASSERT_STREQ (tmp.get_filename (), LOCATION_FILE (line_end));
3019 ASSERT_EQ (1, LOCATION_LINE (line_end));
3020 ASSERT_EQ (line_bytes, LOCATION_COLUMN (line_end));
3021
3022 char_span lspan = location_get_source_line (file_path: tmp.get_filename (), line: 1);
3023 ASSERT_EQ (line_display_cols,
3024 cpp_display_width (lspan.get_buffer (), lspan.length (),
3025 def_policy ()));
3026 ASSERT_EQ (line_display_cols,
3027 location_compute_display_column (expand_location (line_end),
3028 def_policy ()));
3029 ASSERT_EQ (0, memcmp (lspan.get_buffer () + (emoji_col - 1),
3030 "\xf0\x9f\x98\x82\xf0\x9f\x98\x82", 8));
3031
3032 /* (caret_byte, max_width, expected_x_offset_display, [left_margin]) */
3033
3034 /* No constraint on the width -> no offset. */
3035 test_offset_impl (caret_byte_col: emoji_col, max_width: 0, expected_x_offset_display: 0);
3036
3037 /* Caret is before the beginning -> no offset. */
3038 test_offset_impl (caret_byte_col: 0, max_width: 100, expected_x_offset_display: 0);
3039
3040 /* Caret is past the end of the line -> no offset. */
3041 test_offset_impl (caret_byte_col: line_bytes+1, max_width: 100, expected_x_offset_display: 0);
3042
3043 /* Line fits in the display -> no offset. */
3044 test_offset_impl (caret_byte_col: line_bytes, max_width: line_display_cols + test_left_margin, expected_x_offset_display: 0);
3045 test_offset_impl (caret_byte_col: emoji_col, max_width: line_display_cols + test_left_margin, expected_x_offset_display: 0);
3046
3047 /* Line is too long for the display but caret location is OK
3048 anyway -> no offset. */
3049 static const int small_width = 24;
3050 test_offset_impl (caret_byte_col: 1, max_width: small_width, expected_x_offset_display: 0);
3051
3052 /* Width constraint is very small -> no offset. */
3053 test_offset_impl (caret_byte_col: emoji_col, max_width: CARET_LINE_MARGIN, expected_x_offset_display: 0);
3054
3055 /* Line would be offset, but due to large line numbers, offsetting
3056 would remove the whole line -> no offset. */
3057 static const int huge_left_margin = 100;
3058 test_offset_impl (caret_byte_col: emoji_col, max_width: huge_left_margin, expected_x_offset_display: 0, left_margin: huge_left_margin);
3059
3060 /* Line is the same length as the display, but the line number makes it too
3061 long, so offset is required. Caret is at the end so padding on the right
3062 is not in effect. */
3063 for (int excess = 1; excess <= 3; ++excess)
3064 test_offset_impl (caret_byte_col: line_bytes, max_width: line_display_cols + test_left_margin - excess,
3065 expected_x_offset_display: excess);
3066
3067 /* Line is much too long for the display, caret is near the end ->
3068 offset should be such that the line fits in the display and caret
3069 remains the same distance from the end that it was. */
3070 for (int caret_offset = 0, max_offset = MIN (CARET_LINE_MARGIN, 10);
3071 caret_offset <= max_offset; ++caret_offset)
3072 test_offset_impl (caret_byte_col: line_bytes - caret_offset, max_width: small_width,
3073 expected_x_offset_display: line_display_cols + test_left_margin - small_width);
3074
3075 /* As previous case but caret is closer to the middle; now we want it to end
3076 up CARET_LINE_MARGIN bytes from the end. */
3077 ASSERT_GT (line_display_cols - emoji_col, CARET_LINE_MARGIN);
3078 test_offset_impl (caret_byte_col: emoji_col, max_width: small_width,
3079 expected_x_offset_display: emoji_col + test_left_margin
3080 - (small_width - CARET_LINE_MARGIN));
3081
3082 /* Test that the source line is offset as expected when printed. */
3083 {
3084 test_diagnostic_context dc;
3085 dc.m_source_printing.max_width = small_width - 6;
3086 dc.m_source_printing.min_margin_width
3087 = test_left_margin - test_linenum_sep + 1;
3088 dc.m_source_printing.show_line_numbers_p = true;
3089 dc.m_source_printing.show_ruler_p = true;
3090 rich_location richloc (line_table,
3091 linemap_position_for_column (line_table,
3092 emoji_col));
3093 layout test_layout (dc, richloc, DK_ERROR, nullptr);
3094 test_layout.print_line (row: 1);
3095 ASSERT_STREQ (" | 1 \n"
3096 " | 1 \n"
3097 " | 234567890123456789\n"
3098 " 1 | \xf0\x9f\x98\x82\xf0\x9f\x98\x82 is a pair of emojis "
3099 "that occupies 8 bytes and 4 display columns, starting at "
3100 "column #102.\n"
3101 " | ^\n\n",
3102 pp_formatted_text (dc.printer));
3103 }
3104
3105 /* Similar to the previous example, but now the offset called for would split
3106 the first emoji in the middle of the UTF-8 sequence. Check that we replace
3107 it with a padding space in this case. */
3108 {
3109 test_diagnostic_context dc;
3110 dc.m_source_printing.max_width = small_width - 5;
3111 dc.m_source_printing.min_margin_width
3112 = test_left_margin - test_linenum_sep + 1;
3113 dc.m_source_printing.show_line_numbers_p = true;
3114 dc.m_source_printing.show_ruler_p = true;
3115 rich_location richloc (line_table,
3116 linemap_position_for_column (line_table,
3117 emoji_col + 2));
3118 layout test_layout (dc, richloc, DK_ERROR, nullptr);
3119 test_layout.print_line (row: 1);
3120 ASSERT_STREQ (" | 1 1 \n"
3121 " | 1 2 \n"
3122 " | 3456789012345678901\n"
3123 " 1 | \xf0\x9f\x98\x82 is a pair of emojis "
3124 "that occupies 8 bytes and 4 display columns, starting at "
3125 "column #102.\n"
3126 " | ^\n\n",
3127 pp_formatted_text (dc.printer));
3128 }
3129
3130}
3131
3132static void
3133test_layout_x_offset_display_tab (const line_table_case &case_)
3134{
3135 const char *content
3136 = "This line is very long, so that we can use it to test the logic for "
3137 "clipping long lines. Also this: `\t' is a tab that occupies 1 byte and "
3138 "a variable number of display columns, starting at column #103.\n";
3139
3140 /* Number of bytes in the line, subtracting one to remove the newline. */
3141 const int line_bytes = strlen (s: content) - 1;
3142
3143 /* The column where the tab begins. Byte or display is the same as there are
3144 no multibyte characters earlier on the line. */
3145 const int tab_col = 103;
3146
3147 /* Effective extra size of the tab beyond what a single space would have taken
3148 up, indexed by tabstop. */
3149 static const int num_tabstops = 11;
3150 int extra_width[num_tabstops];
3151 for (int tabstop = 1; tabstop != num_tabstops; ++tabstop)
3152 {
3153 const int this_tab_size = tabstop - (tab_col - 1) % tabstop;
3154 extra_width[tabstop] = this_tab_size - 1;
3155 }
3156 /* Example of this calculation: if tabstop is 10, the tab starting at column
3157 #103 has to expand into 8 spaces, covering columns 103-110, so that the
3158 next character is at column #111. So it takes up 7 more columns than
3159 a space would have taken up. */
3160 ASSERT_EQ (7, extra_width[10]);
3161
3162 temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
3163 line_table_test ltt (case_);
3164
3165 linemap_add (line_table, LC_ENTER, sysp: false, to_file: tmp.get_filename (), to_line: 1);
3166
3167 location_t line_end = linemap_position_for_column (line_table, line_bytes);
3168
3169 /* Don't attempt to run the tests if column data might be unavailable. */
3170 if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
3171 return;
3172
3173 /* Check that cpp_display_width handles the tabs as expected. */
3174 char_span lspan = location_get_source_line (file_path: tmp.get_filename (), line: 1);
3175 ASSERT_EQ ('\t', *(lspan.get_buffer () + (tab_col - 1)));
3176 for (int tabstop = 1; tabstop != num_tabstops; ++tabstop)
3177 {
3178 cpp_char_column_policy policy (tabstop, cpp_wcwidth);
3179 ASSERT_EQ (line_bytes + extra_width[tabstop],
3180 cpp_display_width (lspan.get_buffer (), lspan.length (),
3181 policy));
3182 ASSERT_EQ (line_bytes + extra_width[tabstop],
3183 location_compute_display_column (expand_location (line_end),
3184 policy));
3185 }
3186
3187 /* Check that the tab is expanded to the expected number of spaces. */
3188 rich_location richloc (line_table,
3189 linemap_position_for_column (line_table,
3190 tab_col + 1));
3191 for (int tabstop = 1; tabstop != num_tabstops; ++tabstop)
3192 {
3193 test_diagnostic_context dc;
3194 dc.m_tabstop = tabstop;
3195 layout test_layout (dc, richloc, DK_ERROR, nullptr);
3196 test_layout.print_line (row: 1);
3197 const char *out = pp_formatted_text (dc.printer);
3198 ASSERT_EQ (NULL, strchr (out, '\t'));
3199 const char *left_quote = strchr (s: out, c: '`');
3200 const char *right_quote = strchr (s: out, c: '\'');
3201 ASSERT_NE (NULL, left_quote);
3202 ASSERT_NE (NULL, right_quote);
3203 ASSERT_EQ (right_quote - left_quote, extra_width[tabstop] + 2);
3204 }
3205
3206 /* Check that the line is offset properly and that the tab is broken up
3207 into the expected number of spaces when it is the last character skipped
3208 over. */
3209 for (int tabstop = 1; tabstop != num_tabstops; ++tabstop)
3210 {
3211 test_diagnostic_context dc;
3212 dc.m_tabstop = tabstop;
3213 static const int small_width = 24;
3214 dc.m_source_printing.max_width = small_width - 4;
3215 dc.m_source_printing.min_margin_width
3216 = test_left_margin - test_linenum_sep + 1;
3217 dc.m_source_printing.show_line_numbers_p = true;
3218 layout test_layout (dc, richloc, DK_ERROR, nullptr);
3219 test_layout.print_line (row: 1);
3220
3221 /* We have arranged things so that two columns will be printed before
3222 the caret. If the tab results in more than one space, this should
3223 produce two spaces in the output; otherwise, it will be a single space
3224 preceded by the opening quote before the tab character. */
3225 const char *output1
3226 = " 1 | ' is a tab that occupies 1 byte and a variable number of "
3227 "display columns, starting at column #103.\n"
3228 " | ^\n\n";
3229 const char *output2
3230 = " 1 | ` ' is a tab that occupies 1 byte and a variable number of "
3231 "display columns, starting at column #103.\n"
3232 " | ^\n\n";
3233 const char *expected_output = (extra_width[tabstop] ? output1 : output2);
3234 ASSERT_STREQ (expected_output, pp_formatted_text (dc.printer));
3235 }
3236}
3237
3238
3239/* Verify that diagnostic_show_locus works sanely on UNKNOWN_LOCATION. */
3240
3241static void
3242test_diagnostic_show_locus_unknown_location ()
3243{
3244 test_diagnostic_context dc;
3245 rich_location richloc (line_table, UNKNOWN_LOCATION);
3246 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3247 ASSERT_STREQ ("", pp_formatted_text (dc.printer));
3248}
3249
3250/* Verify that diagnostic_show_locus works sanely for various
3251 single-line cases.
3252
3253 All of these work on the following 1-line source file:
3254 .0000000001111111
3255 .1234567890123456
3256 "foo = bar.field;\n"
3257 which is set up by test_diagnostic_show_locus_one_liner and calls
3258 them. */
3259
3260/* Just a caret. */
3261
3262static void
3263test_one_liner_simple_caret ()
3264{
3265 test_diagnostic_context dc;
3266 location_t caret = linemap_position_for_column (line_table, 10);
3267 rich_location richloc (line_table, caret);
3268 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3269 ASSERT_STREQ (" foo = bar.field;\n"
3270 " ^\n",
3271 pp_formatted_text (dc.printer));
3272}
3273
3274/* Caret and range. */
3275
3276static void
3277test_one_liner_caret_and_range ()
3278{
3279 test_diagnostic_context dc;
3280 location_t caret = linemap_position_for_column (line_table, 10);
3281 location_t start = linemap_position_for_column (line_table, 7);
3282 location_t finish = linemap_position_for_column (line_table, 15);
3283 location_t loc = make_location (caret, start, finish);
3284 rich_location richloc (line_table, loc);
3285 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3286 ASSERT_STREQ (" foo = bar.field;\n"
3287 " ~~~^~~~~~\n",
3288 pp_formatted_text (dc.printer));
3289}
3290
3291/* Multiple ranges and carets. */
3292
3293static void
3294test_one_liner_multiple_carets_and_ranges ()
3295{
3296 test_diagnostic_context dc;
3297 location_t foo
3298 = make_location (caret: linemap_position_for_column (line_table, 2),
3299 start: linemap_position_for_column (line_table, 1),
3300 finish: linemap_position_for_column (line_table, 3));
3301 dc.m_source_printing.caret_chars[0] = 'A';
3302
3303 location_t bar
3304 = make_location (caret: linemap_position_for_column (line_table, 8),
3305 start: linemap_position_for_column (line_table, 7),
3306 finish: linemap_position_for_column (line_table, 9));
3307 dc.m_source_printing.caret_chars[1] = 'B';
3308
3309 location_t field
3310 = make_location (caret: linemap_position_for_column (line_table, 13),
3311 start: linemap_position_for_column (line_table, 11),
3312 finish: linemap_position_for_column (line_table, 15));
3313 dc.m_source_printing.caret_chars[2] = 'C';
3314
3315 rich_location richloc (line_table, foo);
3316 richloc.add_range (loc: bar, range_display_kind: SHOW_RANGE_WITH_CARET);
3317 richloc.add_range (loc: field, range_display_kind: SHOW_RANGE_WITH_CARET);
3318 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3319 ASSERT_STREQ (" foo = bar.field;\n"
3320 " ~A~ ~B~ ~~C~~\n",
3321 pp_formatted_text (dc.printer));
3322}
3323
3324/* Insertion fix-it hint: adding an "&" to the front of "bar.field". */
3325
3326static void
3327test_one_liner_fixit_insert_before ()
3328{
3329 test_diagnostic_context dc;
3330 location_t caret = linemap_position_for_column (line_table, 7);
3331 rich_location richloc (line_table, caret);
3332 richloc.add_fixit_insert_before (new_content: "&");
3333 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3334 ASSERT_STREQ (" foo = bar.field;\n"
3335 " ^\n"
3336 " &\n",
3337 pp_formatted_text (dc.printer));
3338}
3339
3340/* Insertion fix-it hint: adding a "[0]" after "foo". */
3341
3342static void
3343test_one_liner_fixit_insert_after ()
3344{
3345 test_diagnostic_context dc;
3346 location_t start = linemap_position_for_column (line_table, 1);
3347 location_t finish = linemap_position_for_column (line_table, 3);
3348 location_t foo = make_location (caret: start, start, finish);
3349 rich_location richloc (line_table, foo);
3350 richloc.add_fixit_insert_after (new_content: "[0]");
3351 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3352 ASSERT_STREQ (" foo = bar.field;\n"
3353 " ^~~\n"
3354 " [0]\n",
3355 pp_formatted_text (dc.printer));
3356}
3357
3358/* Removal fix-it hint: removal of the ".field".
3359 Also verify the interaction of pp_set_prefix with rulers and
3360 fix-it hints. */
3361
3362static void
3363test_one_liner_fixit_remove ()
3364{
3365 location_t start = linemap_position_for_column (line_table, 10);
3366 location_t finish = linemap_position_for_column (line_table, 15);
3367 location_t dot = make_location (caret: start, start, finish);
3368 rich_location richloc (line_table, dot);
3369 richloc.add_fixit_remove ();
3370
3371 /* Normal. */
3372 {
3373 test_diagnostic_context dc;
3374 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3375 ASSERT_STREQ (" foo = bar.field;\n"
3376 " ^~~~~~\n"
3377 " ------\n",
3378 pp_formatted_text (dc.printer));
3379 }
3380
3381 /* Test of adding a prefix. */
3382 {
3383 test_diagnostic_context dc;
3384 pp_prefixing_rule (dc.printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
3385 pp_set_prefix (dc.printer, xstrdup ("TEST PREFIX:"));
3386 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3387 ASSERT_STREQ ("TEST PREFIX: foo = bar.field;\n"
3388 "TEST PREFIX: ^~~~~~\n"
3389 "TEST PREFIX: ------\n",
3390 pp_formatted_text (dc.printer));
3391 }
3392
3393 /* Normal, with ruler. */
3394 {
3395 test_diagnostic_context dc;
3396 dc.m_source_printing.show_ruler_p = true;
3397 dc.m_source_printing.max_width = 104;
3398 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3399 ASSERT_STREQ (" 0 0 0 0 0 0 0 0 0 1 \n"
3400 " 1 2 3 4 5 6 7 8 9 0 \n"
3401 " 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234\n"
3402 " foo = bar.field;\n"
3403 " ^~~~~~\n"
3404 " ------\n",
3405 pp_formatted_text (dc.printer));
3406 }
3407
3408 /* Test of adding a prefix, with ruler. */
3409 {
3410 test_diagnostic_context dc;
3411 dc.m_source_printing.show_ruler_p = true;
3412 dc.m_source_printing.max_width = 50;
3413 pp_prefixing_rule (dc.printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
3414 pp_set_prefix (dc.printer, xstrdup ("TEST PREFIX:"));
3415 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3416 ASSERT_STREQ ("TEST PREFIX: 1 2 3 4 5\n"
3417 "TEST PREFIX: 12345678901234567890123456789012345678901234567890\n"
3418 "TEST PREFIX: foo = bar.field;\n"
3419 "TEST PREFIX: ^~~~~~\n"
3420 "TEST PREFIX: ------\n",
3421 pp_formatted_text (dc.printer));
3422 }
3423
3424 /* Test of adding a prefix, with ruler and line numbers. */
3425 {
3426 test_diagnostic_context dc;
3427 dc.m_source_printing.show_ruler_p = true;
3428 dc.m_source_printing.max_width = 50;
3429 dc.m_source_printing.show_line_numbers_p = true;
3430 pp_prefixing_rule (dc.printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
3431 pp_set_prefix (dc.printer, xstrdup ("TEST PREFIX:"));
3432 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3433 ASSERT_STREQ ("TEST PREFIX: | 1 2 3 4 5\n"
3434 "TEST PREFIX: | 12345678901234567890123456789012345678901234567890\n"
3435 "TEST PREFIX: 1 | foo = bar.field;\n"
3436 "TEST PREFIX: | ^~~~~~\n"
3437 "TEST PREFIX: | ------\n",
3438 pp_formatted_text (dc.printer));
3439 }
3440}
3441
3442/* Replace fix-it hint: replacing "field" with "m_field". */
3443
3444static void
3445test_one_liner_fixit_replace ()
3446{
3447 test_diagnostic_context dc;
3448 location_t start = linemap_position_for_column (line_table, 11);
3449 location_t finish = linemap_position_for_column (line_table, 15);
3450 location_t field = make_location (caret: start, start, finish);
3451 rich_location richloc (line_table, field);
3452 richloc.add_fixit_replace (new_content: "m_field");
3453 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3454 ASSERT_STREQ (" foo = bar.field;\n"
3455 " ^~~~~\n"
3456 " m_field\n",
3457 pp_formatted_text (dc.printer));
3458}
3459
3460/* Replace fix-it hint: replacing "field" with "m_field",
3461 but where the caret was elsewhere. */
3462
3463static void
3464test_one_liner_fixit_replace_non_equal_range ()
3465{
3466 test_diagnostic_context dc;
3467 location_t equals = linemap_position_for_column (line_table, 5);
3468 location_t start = linemap_position_for_column (line_table, 11);
3469 location_t finish = linemap_position_for_column (line_table, 15);
3470 rich_location richloc (line_table, equals);
3471 source_range range;
3472 range.m_start = start;
3473 range.m_finish = finish;
3474 richloc.add_fixit_replace (src_range: range, new_content: "m_field");
3475 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3476 /* The replacement range is not indicated in the annotation line, so
3477 it should be indicated via an additional underline. */
3478 ASSERT_STREQ (" foo = bar.field;\n"
3479 " ^\n"
3480 " -----\n"
3481 " m_field\n",
3482 pp_formatted_text (dc.printer));
3483}
3484
3485/* Replace fix-it hint: replacing "field" with "m_field",
3486 where the caret was elsewhere, but where a secondary range
3487 exactly covers "field". */
3488
3489static void
3490test_one_liner_fixit_replace_equal_secondary_range ()
3491{
3492 test_diagnostic_context dc;
3493 location_t equals = linemap_position_for_column (line_table, 5);
3494 location_t start = linemap_position_for_column (line_table, 11);
3495 location_t finish = linemap_position_for_column (line_table, 15);
3496 rich_location richloc (line_table, equals);
3497 location_t field = make_location (caret: start, start, finish);
3498 richloc.add_range (loc: field);
3499 richloc.add_fixit_replace (where: field, new_content: "m_field");
3500 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3501 /* The replacement range is indicated in the annotation line,
3502 so it shouldn't be indicated via an additional underline. */
3503 ASSERT_STREQ (" foo = bar.field;\n"
3504 " ^ ~~~~~\n"
3505 " m_field\n",
3506 pp_formatted_text (dc.printer));
3507}
3508
3509/* Verify that we can use ad-hoc locations when adding fixits to a
3510 rich_location. */
3511
3512static void
3513test_one_liner_fixit_validation_adhoc_locations ()
3514{
3515 /* Generate a range that's too long to be packed, so must
3516 be stored as an ad-hoc location (given the defaults
3517 of 5 bits or 0 bits of packed range); 41 columns > 2**5. */
3518 const location_t c7 = linemap_position_for_column (line_table, 7);
3519 const location_t c47 = linemap_position_for_column (line_table, 47);
3520 const location_t loc = make_location (caret: c7, start: c7, finish: c47);
3521
3522 if (c47 > LINE_MAP_MAX_LOCATION_WITH_COLS)
3523 return;
3524
3525 ASSERT_TRUE (IS_ADHOC_LOC (loc));
3526
3527 /* Insert. */
3528 {
3529 rich_location richloc (line_table, loc);
3530 richloc.add_fixit_insert_before (where: loc, new_content: "test");
3531 /* It should not have been discarded by the validator. */
3532 ASSERT_EQ (1, richloc.get_num_fixit_hints ());
3533
3534 test_diagnostic_context dc;
3535 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3536 ASSERT_STREQ (" foo = bar.field;\n"
3537 " ^~~~~~~~~~ \n"
3538 " test\n",
3539 pp_formatted_text (dc.printer));
3540 }
3541
3542 /* Remove. */
3543 {
3544 rich_location richloc (line_table, loc);
3545 source_range range = source_range::from_locations (start: loc, finish: c47);
3546 richloc.add_fixit_remove (src_range: range);
3547 /* It should not have been discarded by the validator. */
3548 ASSERT_EQ (1, richloc.get_num_fixit_hints ());
3549
3550 test_diagnostic_context dc;
3551 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3552 ASSERT_STREQ (" foo = bar.field;\n"
3553 " ^~~~~~~~~~ \n"
3554 " -----------------------------------------\n",
3555 pp_formatted_text (dc.printer));
3556 }
3557
3558 /* Replace. */
3559 {
3560 rich_location richloc (line_table, loc);
3561 source_range range = source_range::from_locations (start: loc, finish: c47);
3562 richloc.add_fixit_replace (src_range: range, new_content: "test");
3563 /* It should not have been discarded by the validator. */
3564 ASSERT_EQ (1, richloc.get_num_fixit_hints ());
3565
3566 test_diagnostic_context dc;
3567 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3568 ASSERT_STREQ (" foo = bar.field;\n"
3569 " ^~~~~~~~~~ \n"
3570 " test\n",
3571 pp_formatted_text (dc.printer));
3572 }
3573}
3574
3575/* Test of consolidating insertions at the same location. */
3576
3577static void
3578test_one_liner_many_fixits_1 ()
3579{
3580 test_diagnostic_context dc;
3581 location_t equals = linemap_position_for_column (line_table, 5);
3582 rich_location richloc (line_table, equals);
3583 for (int i = 0; i < 19; i++)
3584 richloc.add_fixit_insert_before (new_content: "a");
3585 ASSERT_EQ (1, richloc.get_num_fixit_hints ());
3586 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3587 ASSERT_STREQ (" foo = bar.field;\n"
3588 " ^\n"
3589 " aaaaaaaaaaaaaaaaaaa\n",
3590 pp_formatted_text (dc.printer));
3591}
3592
3593/* Ensure that we can add an arbitrary number of fix-it hints to a
3594 rich_location, even if they are not consolidated. */
3595
3596static void
3597test_one_liner_many_fixits_2 ()
3598{
3599 test_diagnostic_context dc;
3600 location_t equals = linemap_position_for_column (line_table, 5);
3601 rich_location richloc (line_table, equals);
3602 for (int i = 0; i < 19; i++)
3603 {
3604 location_t loc = linemap_position_for_column (line_table, (i * 2) + 1);
3605 richloc.add_fixit_insert_before (where: loc, new_content: "a");
3606 }
3607 ASSERT_EQ (19, richloc.get_num_fixit_hints ());
3608 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3609 ASSERT_STREQ (" foo = bar.field;\n"
3610 " ^\n"
3611 " a a a a a a a a a a a a a a a a a a a\n",
3612 pp_formatted_text (dc.printer));
3613}
3614
3615/* Test of labeling the ranges within a rich_location. */
3616
3617static void
3618test_one_liner_labels ()
3619{
3620 location_t foo
3621 = make_location (caret: linemap_position_for_column (line_table, 1),
3622 start: linemap_position_for_column (line_table, 1),
3623 finish: linemap_position_for_column (line_table, 3));
3624 location_t bar
3625 = make_location (caret: linemap_position_for_column (line_table, 7),
3626 start: linemap_position_for_column (line_table, 7),
3627 finish: linemap_position_for_column (line_table, 9));
3628 location_t field
3629 = make_location (caret: linemap_position_for_column (line_table, 11),
3630 start: linemap_position_for_column (line_table, 11),
3631 finish: linemap_position_for_column (line_table, 15));
3632
3633 /* Example where all the labels fit on one line. */
3634 {
3635 text_range_label label0 ("0");
3636 text_range_label label1 ("1");
3637 text_range_label label2 ("2");
3638 gcc_rich_location richloc (foo, &label0);
3639 richloc.add_range (loc: bar, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label1);
3640 richloc.add_range (loc: field, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label2);
3641
3642 {
3643 test_diagnostic_context dc;
3644 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3645 ASSERT_STREQ (" foo = bar.field;\n"
3646 " ^~~ ~~~ ~~~~~\n"
3647 " | | |\n"
3648 " 0 1 2\n",
3649 pp_formatted_text (dc.printer));
3650 }
3651
3652 /* Verify that we can disable label-printing. */
3653 {
3654 test_diagnostic_context dc;
3655 dc.m_source_printing.show_labels_p = false;
3656 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3657 ASSERT_STREQ (" foo = bar.field;\n"
3658 " ^~~ ~~~ ~~~~~\n",
3659 pp_formatted_text (dc.printer));
3660 }
3661 }
3662
3663 /* Example where the labels need extra lines. */
3664 {
3665 text_range_label label0 ("label 0");
3666 text_range_label label1 ("label 1");
3667 text_range_label label2 ("label 2");
3668 gcc_rich_location richloc (foo, &label0);
3669 richloc.add_range (loc: bar, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label1);
3670 richloc.add_range (loc: field, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label2);
3671
3672 test_diagnostic_context dc;
3673 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3674 ASSERT_STREQ (" foo = bar.field;\n"
3675 " ^~~ ~~~ ~~~~~\n"
3676 " | | |\n"
3677 " | | label 2\n"
3678 " | label 1\n"
3679 " label 0\n",
3680 pp_formatted_text (dc.printer));
3681 }
3682
3683 /* Example of boundary conditions: label 0 and 1 have just enough clearance,
3684 but label 1 just touches label 2. */
3685 {
3686 text_range_label label0 ("aaaaa");
3687 text_range_label label1 ("bbbb");
3688 text_range_label label2 ("c");
3689 gcc_rich_location richloc (foo, &label0);
3690 richloc.add_range (loc: bar, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label1);
3691 richloc.add_range (loc: field, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label2);
3692
3693 test_diagnostic_context dc;
3694 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3695 ASSERT_STREQ (" foo = bar.field;\n"
3696 " ^~~ ~~~ ~~~~~\n"
3697 " | | |\n"
3698 " | | c\n"
3699 " aaaaa bbbb\n",
3700 pp_formatted_text (dc.printer));
3701 }
3702
3703 /* Example of out-of-order ranges (thus requiring a sort). */
3704 {
3705 text_range_label label0 ("0");
3706 text_range_label label1 ("1");
3707 text_range_label label2 ("2");
3708 gcc_rich_location richloc (field, &label0);
3709 richloc.add_range (loc: bar, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label1);
3710 richloc.add_range (loc: foo, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label2);
3711
3712 test_diagnostic_context dc;
3713 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3714 ASSERT_STREQ (" foo = bar.field;\n"
3715 " ~~~ ~~~ ^~~~~\n"
3716 " | | |\n"
3717 " 2 1 0\n",
3718 pp_formatted_text (dc.printer));
3719 }
3720
3721 /* Ensure we don't ICE if multiple ranges with labels are on
3722 the same point. */
3723 {
3724 text_range_label label0 ("label 0");
3725 text_range_label label1 ("label 1");
3726 text_range_label label2 ("label 2");
3727 gcc_rich_location richloc (bar, &label0);
3728 richloc.add_range (loc: bar, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label1);
3729 richloc.add_range (loc: bar, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label2);
3730
3731 test_diagnostic_context dc;
3732 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3733 ASSERT_STREQ (" foo = bar.field;\n"
3734 " ^~~\n"
3735 " |\n"
3736 " label 0\n"
3737 " label 1\n"
3738 " label 2\n",
3739 pp_formatted_text (dc.printer));
3740 }
3741
3742 /* Example of out-of-order ranges (thus requiring a sort), where
3743 they overlap, and there are multiple ranges on the same point. */
3744 {
3745 text_range_label label_0a ("label 0a");
3746 text_range_label label_1a ("label 1a");
3747 text_range_label label_2a ("label 2a");
3748 text_range_label label_0b ("label 0b");
3749 text_range_label label_1b ("label 1b");
3750 text_range_label label_2b ("label 2b");
3751 text_range_label label_0c ("label 0c");
3752 text_range_label label_1c ("label 1c");
3753 text_range_label label_2c ("label 2c");
3754 gcc_rich_location richloc (field, &label_0a);
3755 richloc.add_range (loc: bar, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label_1a);
3756 richloc.add_range (loc: foo, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label_2a);
3757
3758 richloc.add_range (loc: field, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label_0b);
3759 richloc.add_range (loc: bar, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label_1b);
3760 richloc.add_range (loc: foo, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label_2b);
3761
3762 richloc.add_range (loc: field, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label_0c);
3763 richloc.add_range (loc: bar, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label_1c);
3764 richloc.add_range (loc: foo, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label_2c);
3765
3766 test_diagnostic_context dc;
3767 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3768 ASSERT_STREQ (" foo = bar.field;\n"
3769 " ~~~ ~~~ ^~~~~\n"
3770 " | | |\n"
3771 " | | label 0a\n"
3772 " | | label 0b\n"
3773 " | | label 0c\n"
3774 " | label 1a\n"
3775 " | label 1b\n"
3776 " | label 1c\n"
3777 " label 2a\n"
3778 " label 2b\n"
3779 " label 2c\n",
3780 pp_formatted_text (dc.printer));
3781 }
3782
3783 /* Verify that a NULL result from range_label::get_text is
3784 handled gracefully. */
3785 {
3786 text_range_label label (NULL);
3787 gcc_rich_location richloc (bar, &label);
3788
3789 test_diagnostic_context dc;
3790 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3791 ASSERT_STREQ (" foo = bar.field;\n"
3792 " ^~~\n",
3793 pp_formatted_text (dc.printer));
3794 }
3795
3796 /* TODO: example of formatted printing (needs to be in
3797 gcc-rich-location.cc due to Makefile.in issues). */
3798}
3799
3800/* Run the various one-liner tests. */
3801
3802static void
3803test_diagnostic_show_locus_one_liner (const line_table_case &case_)
3804{
3805 /* Create a tempfile and write some text to it.
3806 ....................0000000001111111.
3807 ....................1234567890123456. */
3808 const char *content = "foo = bar.field;\n";
3809 temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
3810 line_table_test ltt (case_);
3811
3812 linemap_add (line_table, LC_ENTER, sysp: false, to_file: tmp.get_filename (), to_line: 1);
3813
3814 location_t line_end = linemap_position_for_column (line_table, 16);
3815
3816 /* Don't attempt to run the tests if column data might be unavailable. */
3817 if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
3818 return;
3819
3820 ASSERT_STREQ (tmp.get_filename (), LOCATION_FILE (line_end));
3821 ASSERT_EQ (1, LOCATION_LINE (line_end));
3822 ASSERT_EQ (16, LOCATION_COLUMN (line_end));
3823
3824 test_one_liner_simple_caret ();
3825 test_one_liner_caret_and_range ();
3826 test_one_liner_multiple_carets_and_ranges ();
3827 test_one_liner_fixit_insert_before ();
3828 test_one_liner_fixit_insert_after ();
3829 test_one_liner_fixit_remove ();
3830 test_one_liner_fixit_replace ();
3831 test_one_liner_fixit_replace_non_equal_range ();
3832 test_one_liner_fixit_replace_equal_secondary_range ();
3833 test_one_liner_fixit_validation_adhoc_locations ();
3834 test_one_liner_many_fixits_1 ();
3835 test_one_liner_many_fixits_2 ();
3836 test_one_liner_labels ();
3837}
3838
3839/* Version of all one-liner tests exercising multibyte awareness. For
3840 simplicity we stick to using two multibyte characters in the test, U+1F602
3841 == "\xf0\x9f\x98\x82", which uses 4 bytes and 2 display columns, and U+03C0
3842 == "\xcf\x80", which uses 2 bytes and 1 display column. Note: all of the
3843 below asserts would be easier to read if we used UTF-8 directly in the
3844 string constants, but it seems better not to demand the host compiler
3845 support this, when it isn't otherwise necessary. Instead, whenever an
3846 extended character appears in a string, we put a line break after it so that
3847 all succeeding characters can appear visually at the correct display column.
3848
3849 All of these work on the following 1-line source file:
3850
3851 .0000000001111111111222222 display
3852 .1234567890123456789012345 columns
3853 "SS_foo = P_bar.SS_fieldP;\n"
3854 .0000000111111111222222223 byte
3855 .1356789012456789134567891 columns
3856
3857 which is set up by test_diagnostic_show_locus_one_liner and calls
3858 them. Here SS represents the two display columns for the U+1F602 emoji and
3859 P represents the one display column for the U+03C0 pi symbol. */
3860
3861/* Just a caret. */
3862
3863static void
3864test_one_liner_simple_caret_utf8 ()
3865{
3866 test_diagnostic_context dc;
3867 location_t caret = linemap_position_for_column (line_table, 18);
3868 rich_location richloc (line_table, caret);
3869 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3870 ASSERT_STREQ (" \xf0\x9f\x98\x82"
3871 "_foo = \xcf\x80"
3872 "_bar.\xf0\x9f\x98\x82"
3873 "_field\xcf\x80"
3874 ";\n"
3875 " ^\n",
3876 pp_formatted_text (dc.printer));
3877}
3878
3879/* Caret and range. */
3880static void
3881test_one_liner_caret_and_range_utf8 ()
3882{
3883 test_diagnostic_context dc;
3884 location_t caret = linemap_position_for_column (line_table, 18);
3885 location_t start = linemap_position_for_column (line_table, 12);
3886 location_t finish = linemap_position_for_column (line_table, 30);
3887 location_t loc = make_location (caret, start, finish);
3888 rich_location richloc (line_table, loc);
3889 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3890 ASSERT_STREQ (" \xf0\x9f\x98\x82"
3891 "_foo = \xcf\x80"
3892 "_bar.\xf0\x9f\x98\x82"
3893 "_field\xcf\x80"
3894 ";\n"
3895 " ~~~~~^~~~~~~~~~\n",
3896 pp_formatted_text (dc.printer));
3897}
3898
3899/* Multiple ranges and carets. */
3900
3901static void
3902test_one_liner_multiple_carets_and_ranges_utf8 ()
3903{
3904 test_diagnostic_context dc;
3905 location_t foo
3906 = make_location (caret: linemap_position_for_column (line_table, 7),
3907 start: linemap_position_for_column (line_table, 1),
3908 finish: linemap_position_for_column (line_table, 8));
3909 dc.m_source_printing.caret_chars[0] = 'A';
3910
3911 location_t bar
3912 = make_location (caret: linemap_position_for_column (line_table, 16),
3913 start: linemap_position_for_column (line_table, 12),
3914 finish: linemap_position_for_column (line_table, 17));
3915 dc.m_source_printing.caret_chars[1] = 'B';
3916
3917 location_t field
3918 = make_location (caret: linemap_position_for_column (line_table, 26),
3919 start: linemap_position_for_column (line_table, 19),
3920 finish: linemap_position_for_column (line_table, 30));
3921 dc.m_source_printing.caret_chars[2] = 'C';
3922 rich_location richloc (line_table, foo);
3923 richloc.add_range (loc: bar, range_display_kind: SHOW_RANGE_WITH_CARET);
3924 richloc.add_range (loc: field, range_display_kind: SHOW_RANGE_WITH_CARET);
3925 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3926 ASSERT_STREQ (" \xf0\x9f\x98\x82"
3927 "_foo = \xcf\x80"
3928 "_bar.\xf0\x9f\x98\x82"
3929 "_field\xcf\x80"
3930 ";\n"
3931 " ~~~~A~ ~~~B~ ~~~~~C~~~\n",
3932 pp_formatted_text (dc.printer));
3933}
3934
3935/* Insertion fix-it hint: adding an "&" to the front of "P_bar.field". */
3936
3937static void
3938test_one_liner_fixit_insert_before_utf8 ()
3939{
3940 test_diagnostic_context dc;
3941 location_t caret = linemap_position_for_column (line_table, 12);
3942 rich_location richloc (line_table, caret);
3943 richloc.add_fixit_insert_before (new_content: "&");
3944 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3945 ASSERT_STREQ (" \xf0\x9f\x98\x82"
3946 "_foo = \xcf\x80"
3947 "_bar.\xf0\x9f\x98\x82"
3948 "_field\xcf\x80"
3949 ";\n"
3950 " ^\n"
3951 " &\n",
3952 pp_formatted_text (dc.printer));
3953}
3954
3955/* Insertion fix-it hint: adding a "[0]" after "SS_foo". */
3956
3957static void
3958test_one_liner_fixit_insert_after_utf8 ()
3959{
3960 test_diagnostic_context dc;
3961 location_t start = linemap_position_for_column (line_table, 1);
3962 location_t finish = linemap_position_for_column (line_table, 8);
3963 location_t foo = make_location (caret: start, start, finish);
3964 rich_location richloc (line_table, foo);
3965 richloc.add_fixit_insert_after (new_content: "[0]");
3966 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3967 ASSERT_STREQ (" \xf0\x9f\x98\x82"
3968 "_foo = \xcf\x80"
3969 "_bar.\xf0\x9f\x98\x82"
3970 "_field\xcf\x80"
3971 ";\n"
3972 " ^~~~~~\n"
3973 " [0]\n",
3974 pp_formatted_text (dc.printer));
3975}
3976
3977/* Removal fix-it hint: removal of the ".SS_fieldP". */
3978
3979static void
3980test_one_liner_fixit_remove_utf8 ()
3981{
3982 test_diagnostic_context dc;
3983 location_t start = linemap_position_for_column (line_table, 18);
3984 location_t finish = linemap_position_for_column (line_table, 30);
3985 location_t dot = make_location (caret: start, start, finish);
3986 rich_location richloc (line_table, dot);
3987 richloc.add_fixit_remove ();
3988 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
3989 ASSERT_STREQ (" \xf0\x9f\x98\x82"
3990 "_foo = \xcf\x80"
3991 "_bar.\xf0\x9f\x98\x82"
3992 "_field\xcf\x80"
3993 ";\n"
3994 " ^~~~~~~~~~\n"
3995 " ----------\n",
3996 pp_formatted_text (dc.printer));
3997}
3998
3999/* Replace fix-it hint: replacing "SS_fieldP" with "m_SSfieldP". */
4000
4001static void
4002test_one_liner_fixit_replace_utf8 ()
4003{
4004 test_diagnostic_context dc;
4005 location_t start = linemap_position_for_column (line_table, 19);
4006 location_t finish = linemap_position_for_column (line_table, 30);
4007 location_t field = make_location (caret: start, start, finish);
4008 rich_location richloc (line_table, field);
4009 richloc.add_fixit_replace (new_content: "m_\xf0\x9f\x98\x82_field\xcf\x80");
4010 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4011 ASSERT_STREQ (" \xf0\x9f\x98\x82"
4012 "_foo = \xcf\x80"
4013 "_bar.\xf0\x9f\x98\x82"
4014 "_field\xcf\x80"
4015 ";\n"
4016 " ^~~~~~~~~\n"
4017 " m_\xf0\x9f\x98\x82"
4018 "_field\xcf\x80\n",
4019 pp_formatted_text (dc.printer));
4020}
4021
4022/* Replace fix-it hint: replacing "SS_fieldP" with "m_SSfieldP",
4023 but where the caret was elsewhere. */
4024
4025static void
4026test_one_liner_fixit_replace_non_equal_range_utf8 ()
4027{
4028 test_diagnostic_context dc;
4029 location_t equals = linemap_position_for_column (line_table, 10);
4030 location_t start = linemap_position_for_column (line_table, 19);
4031 location_t finish = linemap_position_for_column (line_table, 30);
4032 rich_location richloc (line_table, equals);
4033 source_range range;
4034 range.m_start = start;
4035 range.m_finish = finish;
4036 richloc.add_fixit_replace (src_range: range, new_content: "m_\xf0\x9f\x98\x82_field\xcf\x80");
4037 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4038 /* The replacement range is not indicated in the annotation line, so
4039 it should be indicated via an additional underline. */
4040 ASSERT_STREQ (" \xf0\x9f\x98\x82"
4041 "_foo = \xcf\x80"
4042 "_bar.\xf0\x9f\x98\x82"
4043 "_field\xcf\x80"
4044 ";\n"
4045 " ^\n"
4046 " ---------\n"
4047 " m_\xf0\x9f\x98\x82"
4048 "_field\xcf\x80\n",
4049 pp_formatted_text (dc.printer));
4050}
4051
4052/* Replace fix-it hint: replacing "SS_fieldP" with "m_SSfieldP",
4053 where the caret was elsewhere, but where a secondary range
4054 exactly covers "field". */
4055
4056static void
4057test_one_liner_fixit_replace_equal_secondary_range_utf8 ()
4058{
4059 test_diagnostic_context dc;
4060 location_t equals = linemap_position_for_column (line_table, 10);
4061 location_t start = linemap_position_for_column (line_table, 19);
4062 location_t finish = linemap_position_for_column (line_table, 30);
4063 rich_location richloc (line_table, equals);
4064 location_t field = make_location (caret: start, start, finish);
4065 richloc.add_range (loc: field);
4066 richloc.add_fixit_replace (where: field, new_content: "m_\xf0\x9f\x98\x82_field\xcf\x80");
4067 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4068 /* The replacement range is indicated in the annotation line,
4069 so it shouldn't be indicated via an additional underline. */
4070 ASSERT_STREQ (" \xf0\x9f\x98\x82"
4071 "_foo = \xcf\x80"
4072 "_bar.\xf0\x9f\x98\x82"
4073 "_field\xcf\x80"
4074 ";\n"
4075 " ^ ~~~~~~~~~\n"
4076 " m_\xf0\x9f\x98\x82"
4077 "_field\xcf\x80\n",
4078 pp_formatted_text (dc.printer));
4079}
4080
4081/* Verify that we can use ad-hoc locations when adding fixits to a
4082 rich_location. */
4083
4084static void
4085test_one_liner_fixit_validation_adhoc_locations_utf8 ()
4086{
4087 /* Generate a range that's too long to be packed, so must
4088 be stored as an ad-hoc location (given the defaults
4089 of 5 bits or 0 bits of packed range); 41 columns > 2**5. */
4090 const location_t c12 = linemap_position_for_column (line_table, 12);
4091 const location_t c52 = linemap_position_for_column (line_table, 52);
4092 const location_t loc = make_location (caret: c12, start: c12, finish: c52);
4093
4094 if (c52 > LINE_MAP_MAX_LOCATION_WITH_COLS)
4095 return;
4096
4097 ASSERT_TRUE (IS_ADHOC_LOC (loc));
4098
4099 /* Insert. */
4100 {
4101 rich_location richloc (line_table, loc);
4102 richloc.add_fixit_insert_before (where: loc, new_content: "test");
4103 /* It should not have been discarded by the validator. */
4104 ASSERT_EQ (1, richloc.get_num_fixit_hints ());
4105
4106 test_diagnostic_context dc;
4107 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4108 ASSERT_STREQ (" \xf0\x9f\x98\x82"
4109 "_foo = \xcf\x80"
4110 "_bar.\xf0\x9f\x98\x82"
4111 "_field\xcf\x80"
4112 ";\n"
4113 " ^~~~~~~~~~~~~~~~ \n"
4114 " test\n",
4115 pp_formatted_text (dc.printer));
4116 }
4117
4118 /* Remove. */
4119 {
4120 rich_location richloc (line_table, loc);
4121 source_range range = source_range::from_locations (start: loc, finish: c52);
4122 richloc.add_fixit_remove (src_range: range);
4123 /* It should not have been discarded by the validator. */
4124 ASSERT_EQ (1, richloc.get_num_fixit_hints ());
4125
4126 test_diagnostic_context dc;
4127 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4128 ASSERT_STREQ (" \xf0\x9f\x98\x82"
4129 "_foo = \xcf\x80"
4130 "_bar.\xf0\x9f\x98\x82"
4131 "_field\xcf\x80"
4132 ";\n"
4133 " ^~~~~~~~~~~~~~~~ \n"
4134 " -------------------------------------\n",
4135 pp_formatted_text (dc.printer));
4136 }
4137
4138 /* Replace. */
4139 {
4140 rich_location richloc (line_table, loc);
4141 source_range range = source_range::from_locations (start: loc, finish: c52);
4142 richloc.add_fixit_replace (src_range: range, new_content: "test");
4143 /* It should not have been discarded by the validator. */
4144 ASSERT_EQ (1, richloc.get_num_fixit_hints ());
4145
4146 test_diagnostic_context dc;
4147 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4148 ASSERT_STREQ (" \xf0\x9f\x98\x82"
4149 "_foo = \xcf\x80"
4150 "_bar.\xf0\x9f\x98\x82"
4151 "_field\xcf\x80"
4152 ";\n"
4153 " ^~~~~~~~~~~~~~~~ \n"
4154 " test\n",
4155 pp_formatted_text (dc.printer));
4156 }
4157}
4158
4159/* Test of consolidating insertions at the same location. */
4160
4161static void
4162test_one_liner_many_fixits_1_utf8 ()
4163{
4164 test_diagnostic_context dc;
4165 location_t equals = linemap_position_for_column (line_table, 10);
4166 rich_location richloc (line_table, equals);
4167 for (int i = 0; i < 19; i++)
4168 richloc.add_fixit_insert_before (new_content: i & 1 ? "@" : "\xcf\x80");
4169 ASSERT_EQ (1, richloc.get_num_fixit_hints ());
4170 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4171 ASSERT_STREQ (" \xf0\x9f\x98\x82"
4172 "_foo = \xcf\x80"
4173 "_bar.\xf0\x9f\x98\x82"
4174 "_field\xcf\x80"
4175 ";\n"
4176 " ^\n"
4177 " \xcf\x80@\xcf\x80@\xcf\x80@\xcf\x80@\xcf\x80@"
4178 "\xcf\x80@\xcf\x80@\xcf\x80@\xcf\x80@\xcf\x80\n",
4179 pp_formatted_text (dc.printer));
4180}
4181
4182/* Ensure that we can add an arbitrary number of fix-it hints to a
4183 rich_location, even if they are not consolidated. */
4184
4185static void
4186test_one_liner_many_fixits_2_utf8 ()
4187{
4188 test_diagnostic_context dc;
4189 location_t equals = linemap_position_for_column (line_table, 10);
4190 rich_location richloc (line_table, equals);
4191 const int nlocs = 19;
4192 int locs[nlocs] = {1, 5, 7, 9, 11, 14, 16, 18, 23, 25, 27, 29, 32,
4193 34, 36, 38, 40, 42, 44};
4194 for (int i = 0; i != nlocs; ++i)
4195 {
4196 location_t loc = linemap_position_for_column (line_table, locs[i]);
4197 richloc.add_fixit_insert_before (where: loc, new_content: i & 1 ? "@" : "\xcf\x80");
4198 }
4199
4200 ASSERT_EQ (nlocs, richloc.get_num_fixit_hints ());
4201 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4202 ASSERT_STREQ (" \xf0\x9f\x98\x82"
4203 "_foo = \xcf\x80"
4204 "_bar.\xf0\x9f\x98\x82"
4205 "_field\xcf\x80"
4206 ";\n"
4207 " ^\n"
4208 " \xcf\x80 @ \xcf\x80 @ \xcf\x80 @ \xcf\x80 @ \xcf\x80 @"
4209 " \xcf\x80 @ \xcf\x80 @ \xcf\x80 @ \xcf\x80 @ \xcf\x80\n",
4210 pp_formatted_text (dc.printer));
4211}
4212
4213/* Test of labeling the ranges within a rich_location. */
4214
4215static void
4216test_one_liner_labels_utf8 ()
4217{
4218 location_t foo
4219 = make_location (caret: linemap_position_for_column (line_table, 1),
4220 start: linemap_position_for_column (line_table, 1),
4221 finish: linemap_position_for_column (line_table, 8));
4222 location_t bar
4223 = make_location (caret: linemap_position_for_column (line_table, 12),
4224 start: linemap_position_for_column (line_table, 12),
4225 finish: linemap_position_for_column (line_table, 17));
4226 location_t field
4227 = make_location (caret: linemap_position_for_column (line_table, 19),
4228 start: linemap_position_for_column (line_table, 19),
4229 finish: linemap_position_for_column (line_table, 30));
4230
4231 /* Example where all the labels fit on one line. */
4232 {
4233 /* These three labels contain multibyte characters such that their byte
4234 lengths are respectively (12, 10, 18), but their display widths are only
4235 (6, 5, 9). All three fit on the line when considering the display
4236 widths, but not when considering the byte widths, so verify that we do
4237 indeed put them all on one line. */
4238 text_range_label label0
4239 ("\xcf\x80\xcf\x80\xcf\x80\xcf\x80\xcf\x80\xcf\x80");
4240 text_range_label label1
4241 ("\xf0\x9f\x98\x82\xf0\x9f\x98\x82\xcf\x80");
4242 text_range_label label2
4243 ("\xf0\x9f\x98\x82\xcf\x80\xf0\x9f\x98\x82\xf0\x9f\x98\x82\xcf\x80"
4244 "\xcf\x80");
4245 gcc_rich_location richloc (foo, &label0);
4246 richloc.add_range (loc: bar, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label1);
4247 richloc.add_range (loc: field, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label2);
4248
4249 {
4250 test_diagnostic_context dc;
4251 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4252 ASSERT_STREQ (" \xf0\x9f\x98\x82"
4253 "_foo = \xcf\x80"
4254 "_bar.\xf0\x9f\x98\x82"
4255 "_field\xcf\x80"
4256 ";\n"
4257 " ^~~~~~ ~~~~~ ~~~~~~~~~\n"
4258 " | | |\n"
4259 " \xcf\x80\xcf\x80\xcf\x80\xcf\x80\xcf\x80\xcf\x80"
4260 " \xf0\x9f\x98\x82\xf0\x9f\x98\x82\xcf\x80"
4261 " \xf0\x9f\x98\x82\xcf\x80\xf0\x9f\x98\x82"
4262 "\xf0\x9f\x98\x82\xcf\x80\xcf\x80\n",
4263 pp_formatted_text (dc.printer));
4264 }
4265
4266 }
4267
4268 /* Example where the labels need extra lines. */
4269 {
4270 text_range_label label0 ("label 0\xf0\x9f\x98\x82");
4271 text_range_label label1 ("label 1\xcf\x80");
4272 text_range_label label2 ("label 2\xcf\x80");
4273 gcc_rich_location richloc (foo, &label0);
4274 richloc.add_range (loc: bar, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label1);
4275 richloc.add_range (loc: field, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label2);
4276
4277 test_diagnostic_context dc;
4278 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4279
4280 ASSERT_STREQ (" \xf0\x9f\x98\x82"
4281 "_foo = \xcf\x80"
4282 "_bar.\xf0\x9f\x98\x82"
4283 "_field\xcf\x80"
4284 ";\n"
4285 " ^~~~~~ ~~~~~ ~~~~~~~~~\n"
4286 " | | |\n"
4287 " | | label 2\xcf\x80\n"
4288 " | label 1\xcf\x80\n"
4289 " label 0\xf0\x9f\x98\x82\n",
4290 pp_formatted_text (dc.printer));
4291 }
4292
4293 /* Example of boundary conditions: label 0 and 1 have just enough clearance,
4294 but label 1 just touches label 2. */
4295 {
4296 text_range_label label0 ("aaaaa\xf0\x9f\x98\x82\xcf\x80");
4297 text_range_label label1 ("bb\xf0\x9f\x98\x82\xf0\x9f\x98\x82");
4298 text_range_label label2 ("c");
4299 gcc_rich_location richloc (foo, &label0);
4300 richloc.add_range (loc: bar, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label1);
4301 richloc.add_range (loc: field, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label2);
4302
4303 test_diagnostic_context dc;
4304 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4305 ASSERT_STREQ (" \xf0\x9f\x98\x82"
4306 "_foo = \xcf\x80"
4307 "_bar.\xf0\x9f\x98\x82"
4308 "_field\xcf\x80"
4309 ";\n"
4310 " ^~~~~~ ~~~~~ ~~~~~~~~~\n"
4311 " | | |\n"
4312 " | | c\n"
4313 " aaaaa\xf0\x9f\x98\x82\xcf\x80"
4314 " bb\xf0\x9f\x98\x82\xf0\x9f\x98\x82\n",
4315 pp_formatted_text (dc.printer));
4316 }
4317
4318 /* Example of escaping the source lines. */
4319 {
4320 text_range_label label0 ("label 0\xf0\x9f\x98\x82");
4321 text_range_label label1 ("label 1\xcf\x80");
4322 text_range_label label2 ("label 2\xcf\x80");
4323 gcc_rich_location richloc (foo, &label0);
4324 richloc.add_range (loc: bar, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label1);
4325 richloc.add_range (loc: field, range_display_kind: SHOW_RANGE_WITHOUT_CARET, label: &label2);
4326 richloc.set_escape_on_output (true);
4327
4328 {
4329 test_diagnostic_context dc;
4330 dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_UNICODE);
4331 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4332 ASSERT_STREQ (" <U+1F602>_foo = <U+03C0>_bar.<U+1F602>_field<U+03C0>;\n"
4333 " ^~~~~~~~~~~~~ ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~\n"
4334 " | | |\n"
4335 " | | label 2\xcf\x80\n"
4336 " | label 1\xcf\x80\n"
4337 " label 0\xf0\x9f\x98\x82\n",
4338 pp_formatted_text (dc.printer));
4339 }
4340 {
4341 test_diagnostic_context dc;
4342 dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_BYTES);
4343 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4344 ASSERT_STREQ
4345 (" <f0><9f><98><82>_foo = <cf><80>_bar.<f0><9f><98><82>_field<cf><80>;\n"
4346 " ^~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
4347 " | | |\n"
4348 " | | label 2\xcf\x80\n"
4349 " | label 1\xcf\x80\n"
4350 " label 0\xf0\x9f\x98\x82\n",
4351 pp_formatted_text (dc.printer));
4352 }
4353 }
4354}
4355
4356/* Make sure that colorization codes don't interrupt a multibyte
4357 sequence, which would corrupt it. */
4358static void
4359test_one_liner_colorized_utf8 ()
4360{
4361 test_diagnostic_context dc;
4362 dc.m_source_printing.colorize_source_p = true;
4363 diagnostic_color_init (context: &dc, value: DIAGNOSTICS_COLOR_YES);
4364 const location_t pi = linemap_position_for_column (line_table, 12);
4365 rich_location richloc (line_table, pi);
4366 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4367
4368 /* In order to avoid having the test depend on exactly how the colorization
4369 was effected, just confirm there are two pi characters in the output. */
4370 const char *result = pp_formatted_text (dc.printer);
4371 const char *null_term = result + strlen (s: result);
4372 const char *first_pi = strstr (haystack: result, needle: "\xcf\x80");
4373 ASSERT_TRUE (first_pi && first_pi <= null_term - 2);
4374 ASSERT_STR_CONTAINS (first_pi + 2, "\xcf\x80");
4375}
4376
4377/* Run the various one-liner tests. */
4378
4379static void
4380test_diagnostic_show_locus_one_liner_utf8 (const line_table_case &case_)
4381{
4382 /* Create a tempfile and write some text to it. */
4383 const char *content
4384 /* Display columns.
4385 0000000000000000000000011111111111111111111111111111112222222222222
4386 1111111122222222345678900000000123456666666677777777890123444444445 */
4387 = "\xf0\x9f\x98\x82_foo = \xcf\x80_bar.\xf0\x9f\x98\x82_field\xcf\x80;\n";
4388 /* 0000000000000000000001111111111111111111222222222222222222222233333
4389 1111222233334444567890122223333456789999000011112222345678999900001
4390 Byte columns. */
4391 temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
4392 line_table_test ltt (case_);
4393
4394 linemap_add (line_table, LC_ENTER, sysp: false, to_file: tmp.get_filename (), to_line: 1);
4395
4396 location_t line_end = linemap_position_for_column (line_table, 31);
4397
4398 /* Don't attempt to run the tests if column data might be unavailable. */
4399 if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
4400 return;
4401
4402 ASSERT_STREQ (tmp.get_filename (), LOCATION_FILE (line_end));
4403 ASSERT_EQ (1, LOCATION_LINE (line_end));
4404 ASSERT_EQ (31, LOCATION_COLUMN (line_end));
4405
4406 char_span lspan = location_get_source_line (file_path: tmp.get_filename (), line: 1);
4407 ASSERT_EQ (25, cpp_display_width (lspan.get_buffer (), lspan.length (),
4408 def_policy ()));
4409 ASSERT_EQ (25, location_compute_display_column (expand_location (line_end),
4410 def_policy ()));
4411
4412 test_one_liner_simple_caret_utf8 ();
4413 test_one_liner_caret_and_range_utf8 ();
4414 test_one_liner_multiple_carets_and_ranges_utf8 ();
4415 test_one_liner_fixit_insert_before_utf8 ();
4416 test_one_liner_fixit_insert_after_utf8 ();
4417 test_one_liner_fixit_remove_utf8 ();
4418 test_one_liner_fixit_replace_utf8 ();
4419 test_one_liner_fixit_replace_non_equal_range_utf8 ();
4420 test_one_liner_fixit_replace_equal_secondary_range_utf8 ();
4421 test_one_liner_fixit_validation_adhoc_locations_utf8 ();
4422 test_one_liner_many_fixits_1_utf8 ();
4423 test_one_liner_many_fixits_2_utf8 ();
4424 test_one_liner_labels_utf8 ();
4425 test_one_liner_colorized_utf8 ();
4426}
4427
4428/* Verify that gcc_rich_location::add_location_if_nearby works. */
4429
4430static void
4431test_add_location_if_nearby (const line_table_case &case_)
4432{
4433 /* Create a tempfile and write some text to it.
4434 ...000000000111111111122222222223333333333.
4435 ...123456789012345678901234567890123456789. */
4436 const char *content
4437 = ("struct same_line { double x; double y; ;\n" /* line 1. */
4438 "struct different_line\n" /* line 2. */
4439 "{\n" /* line 3. */
4440 " double x;\n" /* line 4. */
4441 " double y;\n" /* line 5. */
4442 ";\n"); /* line 6. */
4443 temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
4444 line_table_test ltt (case_);
4445
4446 const line_map_ordinary *ord_map
4447 = linemap_check_ordinary (map: linemap_add (line_table, LC_ENTER, sysp: false,
4448 to_file: tmp.get_filename (), to_line: 0));
4449
4450 linemap_line_start (set: line_table, to_line: 1, max_column_hint: 100);
4451
4452 const location_t final_line_end
4453 = linemap_position_for_line_and_column (set: line_table, ord_map, 6, 7);
4454
4455 /* Don't attempt to run the tests if column data might be unavailable. */
4456 if (final_line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
4457 return;
4458
4459 /* Test of add_location_if_nearby on the same line as the
4460 primary location. */
4461 {
4462 const location_t missing_close_brace_1_39
4463 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 39);
4464 const location_t matching_open_brace_1_18
4465 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 18);
4466 gcc_rich_location richloc (missing_close_brace_1_39);
4467 bool added = richloc.add_location_if_nearby (loc: matching_open_brace_1_18);
4468 ASSERT_TRUE (added);
4469 ASSERT_EQ (2, richloc.get_num_locations ());
4470 test_diagnostic_context dc;
4471 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4472 ASSERT_STREQ (" struct same_line { double x; double y; ;\n"
4473 " ~ ^\n",
4474 pp_formatted_text (dc.printer));
4475 }
4476
4477 /* Test of add_location_if_nearby on a different line to the
4478 primary location. */
4479 {
4480 const location_t missing_close_brace_6_1
4481 = linemap_position_for_line_and_column (set: line_table, ord_map, 6, 1);
4482 const location_t matching_open_brace_3_1
4483 = linemap_position_for_line_and_column (set: line_table, ord_map, 3, 1);
4484 gcc_rich_location richloc (missing_close_brace_6_1);
4485 bool added = richloc.add_location_if_nearby (loc: matching_open_brace_3_1);
4486 ASSERT_FALSE (added);
4487 ASSERT_EQ (1, richloc.get_num_locations ());
4488 }
4489}
4490
4491/* Verify that we print fixits even if they only affect lines
4492 outside those covered by the ranges in the rich_location. */
4493
4494static void
4495test_diagnostic_show_locus_fixit_lines (const line_table_case &case_)
4496{
4497 /* Create a tempfile and write some text to it.
4498 ...000000000111111111122222222223333333333.
4499 ...123456789012345678901234567890123456789. */
4500 const char *content
4501 = ("struct point { double x; double y; };\n" /* line 1. */
4502 "struct point origin = {x: 0.0,\n" /* line 2. */
4503 " y\n" /* line 3. */
4504 "\n" /* line 4. */
4505 "\n" /* line 5. */
4506 " : 0.0};\n"); /* line 6. */
4507 temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
4508 line_table_test ltt (case_);
4509
4510 const line_map_ordinary *ord_map
4511 = linemap_check_ordinary (map: linemap_add (line_table, LC_ENTER, sysp: false,
4512 to_file: tmp.get_filename (), to_line: 0));
4513
4514 linemap_line_start (set: line_table, to_line: 1, max_column_hint: 100);
4515
4516 const location_t final_line_end
4517 = linemap_position_for_line_and_column (set: line_table, ord_map, 6, 36);
4518
4519 /* Don't attempt to run the tests if column data might be unavailable. */
4520 if (final_line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
4521 return;
4522
4523 /* A pair of tests for modernizing the initializers to C99-style. */
4524
4525 /* The one-liner case (line 2). */
4526 {
4527 test_diagnostic_context dc;
4528 const location_t x
4529 = linemap_position_for_line_and_column (set: line_table, ord_map, 2, 24);
4530 const location_t colon
4531 = linemap_position_for_line_and_column (set: line_table, ord_map, 2, 25);
4532 rich_location richloc (line_table, colon);
4533 richloc.add_fixit_insert_before (where: x, new_content: ".");
4534 richloc.add_fixit_replace (where: colon, new_content: "=");
4535 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4536 ASSERT_STREQ (" struct point origin = {x: 0.0,\n"
4537 " ^\n"
4538 " .=\n",
4539 pp_formatted_text (dc.printer));
4540 }
4541
4542 /* The multiline case. The caret for the rich_location is on line 6;
4543 verify that insertion fixit on line 3 is still printed (and that
4544 span starts are printed due to the gap between the span at line 3
4545 and that at line 6). */
4546 {
4547 test_diagnostic_context dc;
4548 const location_t y
4549 = linemap_position_for_line_and_column (set: line_table, ord_map, 3, 24);
4550 const location_t colon
4551 = linemap_position_for_line_and_column (set: line_table, ord_map, 6, 25);
4552 rich_location richloc (line_table, colon);
4553 richloc.add_fixit_insert_before (where: y, new_content: ".");
4554 richloc.add_fixit_replace (where: colon, new_content: "=");
4555 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4556 ASSERT_STREQ ("FILENAME:3:24:\n"
4557 " y\n"
4558 " .\n"
4559 "FILENAME:6:25:\n"
4560 " : 0.0};\n"
4561 " ^\n"
4562 " =\n",
4563 pp_formatted_text (dc.printer));
4564 }
4565
4566 /* As above, but verify the behavior of multiple line spans
4567 with line-numbering enabled. */
4568 {
4569 const location_t y
4570 = linemap_position_for_line_and_column (set: line_table, ord_map, 3, 24);
4571 const location_t colon
4572 = linemap_position_for_line_and_column (set: line_table, ord_map, 6, 25);
4573 rich_location richloc (line_table, colon);
4574 richloc.add_fixit_insert_before (where: y, new_content: ".");
4575 richloc.add_fixit_replace (where: colon, new_content: "=");
4576 test_diagnostic_context dc;
4577 dc.m_source_printing.show_line_numbers_p = true;
4578 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4579 ASSERT_STREQ (" 3 | y\n"
4580 " | .\n"
4581 "......\n"
4582 " 6 | : 0.0};\n"
4583 " | ^\n"
4584 " | =\n",
4585 pp_formatted_text (dc.printer));
4586 }
4587}
4588
4589
4590/* Verify that fix-it hints are appropriately consolidated.
4591
4592 If any fix-it hints in a rich_location involve locations beyond
4593 LINE_MAP_MAX_LOCATION_WITH_COLS, then we can't reliably apply
4594 the fix-it as a whole, so there should be none.
4595
4596 Otherwise, verify that consecutive "replace" and "remove" fix-its
4597 are merged, and that other fix-its remain separate. */
4598
4599static void
4600test_fixit_consolidation (const line_table_case &case_)
4601{
4602 line_table_test ltt (case_);
4603
4604 linemap_add (line_table, LC_ENTER, sysp: false, to_file: "test.c", to_line: 1);
4605
4606 const location_t c10 = linemap_position_for_column (line_table, 10);
4607 const location_t c15 = linemap_position_for_column (line_table, 15);
4608 const location_t c16 = linemap_position_for_column (line_table, 16);
4609 const location_t c17 = linemap_position_for_column (line_table, 17);
4610 const location_t c20 = linemap_position_for_column (line_table, 20);
4611 const location_t c21 = linemap_position_for_column (line_table, 21);
4612 const location_t caret = c10;
4613
4614 /* Insert + insert. */
4615 {
4616 rich_location richloc (line_table, caret);
4617 richloc.add_fixit_insert_before (where: c10, new_content: "foo");
4618 richloc.add_fixit_insert_before (where: c15, new_content: "bar");
4619
4620 if (c15 > LINE_MAP_MAX_LOCATION_WITH_COLS)
4621 /* Bogus column info for 2nd fixit, so no fixits. */
4622 ASSERT_EQ (0, richloc.get_num_fixit_hints ());
4623 else
4624 /* They should not have been merged. */
4625 ASSERT_EQ (2, richloc.get_num_fixit_hints ());
4626 }
4627
4628 /* Insert + replace. */
4629 {
4630 rich_location richloc (line_table, caret);
4631 richloc.add_fixit_insert_before (where: c10, new_content: "foo");
4632 richloc.add_fixit_replace (src_range: source_range::from_locations (start: c15, finish: c17),
4633 new_content: "bar");
4634
4635 if (c17 > LINE_MAP_MAX_LOCATION_WITH_COLS)
4636 /* Bogus column info for 2nd fixit, so no fixits. */
4637 ASSERT_EQ (0, richloc.get_num_fixit_hints ());
4638 else
4639 /* They should not have been merged. */
4640 ASSERT_EQ (2, richloc.get_num_fixit_hints ());
4641 }
4642
4643 /* Replace + non-consecutive insert. */
4644 {
4645 rich_location richloc (line_table, caret);
4646 richloc.add_fixit_replace (src_range: source_range::from_locations (start: c10, finish: c15),
4647 new_content: "bar");
4648 richloc.add_fixit_insert_before (where: c17, new_content: "foo");
4649
4650 if (c17 > LINE_MAP_MAX_LOCATION_WITH_COLS)
4651 /* Bogus column info for 2nd fixit, so no fixits. */
4652 ASSERT_EQ (0, richloc.get_num_fixit_hints ());
4653 else
4654 /* They should not have been merged. */
4655 ASSERT_EQ (2, richloc.get_num_fixit_hints ());
4656 }
4657
4658 /* Replace + non-consecutive replace. */
4659 {
4660 rich_location richloc (line_table, caret);
4661 richloc.add_fixit_replace (src_range: source_range::from_locations (start: c10, finish: c15),
4662 new_content: "foo");
4663 richloc.add_fixit_replace (src_range: source_range::from_locations (start: c17, finish: c20),
4664 new_content: "bar");
4665
4666 if (c20 > LINE_MAP_MAX_LOCATION_WITH_COLS)
4667 /* Bogus column info for 2nd fixit, so no fixits. */
4668 ASSERT_EQ (0, richloc.get_num_fixit_hints ());
4669 else
4670 /* They should not have been merged. */
4671 ASSERT_EQ (2, richloc.get_num_fixit_hints ());
4672 }
4673
4674 /* Replace + consecutive replace. */
4675 {
4676 rich_location richloc (line_table, caret);
4677 richloc.add_fixit_replace (src_range: source_range::from_locations (start: c10, finish: c15),
4678 new_content: "foo");
4679 richloc.add_fixit_replace (src_range: source_range::from_locations (start: c16, finish: c20),
4680 new_content: "bar");
4681
4682 if (c20 > LINE_MAP_MAX_LOCATION_WITH_COLS)
4683 /* Bogus column info for 2nd fixit, so no fixits. */
4684 ASSERT_EQ (0, richloc.get_num_fixit_hints ());
4685 else
4686 {
4687 /* They should have been merged into a single "replace". */
4688 ASSERT_EQ (1, richloc.get_num_fixit_hints ());
4689 const fixit_hint *hint = richloc.get_fixit_hint (idx: 0);
4690 ASSERT_STREQ ("foobar", hint->get_string ());
4691 ASSERT_EQ (c10, hint->get_start_loc ());
4692 ASSERT_EQ (c21, hint->get_next_loc ());
4693 }
4694 }
4695
4696 /* Replace + consecutive removal. */
4697 {
4698 rich_location richloc (line_table, caret);
4699 richloc.add_fixit_replace (src_range: source_range::from_locations (start: c10, finish: c15),
4700 new_content: "foo");
4701 richloc.add_fixit_remove (src_range: source_range::from_locations (start: c16, finish: c20));
4702
4703 if (c20 > LINE_MAP_MAX_LOCATION_WITH_COLS)
4704 /* Bogus column info for 2nd fixit, so no fixits. */
4705 ASSERT_EQ (0, richloc.get_num_fixit_hints ());
4706 else
4707 {
4708 /* They should have been merged into a single replace, with the
4709 range extended to cover that of the removal. */
4710 ASSERT_EQ (1, richloc.get_num_fixit_hints ());
4711 const fixit_hint *hint = richloc.get_fixit_hint (idx: 0);
4712 ASSERT_STREQ ("foo", hint->get_string ());
4713 ASSERT_EQ (c10, hint->get_start_loc ());
4714 ASSERT_EQ (c21, hint->get_next_loc ());
4715 }
4716 }
4717
4718 /* Consecutive removals. */
4719 {
4720 rich_location richloc (line_table, caret);
4721 richloc.add_fixit_remove (src_range: source_range::from_locations (start: c10, finish: c15));
4722 richloc.add_fixit_remove (src_range: source_range::from_locations (start: c16, finish: c20));
4723
4724 if (c20 > LINE_MAP_MAX_LOCATION_WITH_COLS)
4725 /* Bogus column info for 2nd fixit, so no fixits. */
4726 ASSERT_EQ (0, richloc.get_num_fixit_hints ());
4727 else
4728 {
4729 /* They should have been merged into a single "replace-with-empty". */
4730 ASSERT_EQ (1, richloc.get_num_fixit_hints ());
4731 const fixit_hint *hint = richloc.get_fixit_hint (idx: 0);
4732 ASSERT_STREQ ("", hint->get_string ());
4733 ASSERT_EQ (c10, hint->get_start_loc ());
4734 ASSERT_EQ (c21, hint->get_next_loc ());
4735 }
4736 }
4737}
4738
4739/* Verify that the line_corrections machinery correctly prints
4740 overlapping fixit-hints. */
4741
4742static void
4743test_overlapped_fixit_printing (const line_table_case &case_)
4744{
4745 /* Create a tempfile and write some text to it.
4746 ...000000000111111111122222222223333333333.
4747 ...123456789012345678901234567890123456789. */
4748 const char *content
4749 = (" foo *f = (foo *)ptr->field;\n");
4750 temp_source_file tmp (SELFTEST_LOCATION, ".C", content);
4751 line_table_test ltt (case_);
4752
4753 const line_map_ordinary *ord_map
4754 = linemap_check_ordinary (map: linemap_add (line_table, LC_ENTER, sysp: false,
4755 to_file: tmp.get_filename (), to_line: 0));
4756
4757 linemap_line_start (set: line_table, to_line: 1, max_column_hint: 100);
4758
4759 const location_t final_line_end
4760 = linemap_position_for_line_and_column (set: line_table, ord_map, 6, 36);
4761
4762 /* Don't attempt to run the tests if column data might be unavailable. */
4763 if (final_line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
4764 return;
4765
4766 /* A test for converting a C-style cast to a C++-style cast. */
4767 const location_t open_paren
4768 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 12);
4769 const location_t close_paren
4770 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 18);
4771 const location_t expr_start
4772 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 19);
4773 const location_t expr_finish
4774 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 28);
4775 const location_t expr = make_location (caret: expr_start, start: expr_start, finish: expr_finish);
4776
4777 /* Various examples of fix-it hints that aren't themselves consolidated,
4778 but for which the *printing* may need consolidation. */
4779
4780 /* Example where 3 fix-it hints are printed as one. */
4781 {
4782 test_diagnostic_context dc;
4783 rich_location richloc (line_table, expr);
4784 richloc.add_fixit_replace (where: open_paren, new_content: "const_cast<");
4785 richloc.add_fixit_replace (where: close_paren, new_content: "> (");
4786 richloc.add_fixit_insert_after (new_content: ")");
4787
4788 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4789 ASSERT_STREQ (" foo *f = (foo *)ptr->field;\n"
4790 " ^~~~~~~~~~\n"
4791 " -----------------\n"
4792 " const_cast<foo *> (ptr->field)\n",
4793 pp_formatted_text (dc.printer));
4794
4795 /* Unit-test the line_corrections machinery. */
4796 char_display_policy policy (make_policy (dc, richloc));
4797 ASSERT_EQ (3, richloc.get_num_fixit_hints ());
4798 const fixit_hint *hint_0 = richloc.get_fixit_hint (idx: 0);
4799 ASSERT_EQ (column_range (12, 12),
4800 get_affected_range (policy, hint_0, CU_BYTES));
4801 ASSERT_EQ (column_range (12, 12),
4802 get_affected_range (policy, hint_0, CU_DISPLAY_COLS));
4803 ASSERT_EQ (column_range (12, 22), get_printed_columns (policy, hint_0));
4804 const fixit_hint *hint_1 = richloc.get_fixit_hint (idx: 1);
4805 ASSERT_EQ (column_range (18, 18),
4806 get_affected_range (policy, hint_1, CU_BYTES));
4807 ASSERT_EQ (column_range (18, 18),
4808 get_affected_range (policy, hint_1, CU_DISPLAY_COLS));
4809 ASSERT_EQ (column_range (18, 20), get_printed_columns (policy, hint_1));
4810 const fixit_hint *hint_2 = richloc.get_fixit_hint (idx: 2);
4811 ASSERT_EQ (column_range (29, 28),
4812 get_affected_range (policy, hint_2, CU_BYTES));
4813 ASSERT_EQ (column_range (29, 28),
4814 get_affected_range (policy, hint_2, CU_DISPLAY_COLS));
4815 ASSERT_EQ (column_range (29, 29), get_printed_columns (policy, hint_2));
4816
4817 /* Add each hint in turn to a line_corrections instance,
4818 and verify that they are consolidated into one correction instance
4819 as expected. */
4820 line_corrections lc (policy, tmp.get_filename (), 1);
4821
4822 /* The first replace hint by itself. */
4823 lc.add_hint (hint: hint_0);
4824 ASSERT_EQ (1, lc.m_corrections.length ());
4825 ASSERT_EQ (column_range (12, 12), lc.m_corrections[0]->m_affected_bytes);
4826 ASSERT_EQ (column_range (12, 12), lc.m_corrections[0]->m_affected_columns);
4827 ASSERT_EQ (column_range (12, 22), lc.m_corrections[0]->m_printed_columns);
4828 ASSERT_STREQ ("const_cast<", lc.m_corrections[0]->m_text);
4829
4830 /* After the second replacement hint, they are printed together
4831 as a replacement (along with the text between them). */
4832 lc.add_hint (hint: hint_1);
4833 ASSERT_EQ (1, lc.m_corrections.length ());
4834 ASSERT_STREQ ("const_cast<foo *> (", lc.m_corrections[0]->m_text);
4835 ASSERT_EQ (column_range (12, 18), lc.m_corrections[0]->m_affected_bytes);
4836 ASSERT_EQ (column_range (12, 18), lc.m_corrections[0]->m_affected_columns);
4837 ASSERT_EQ (column_range (12, 30), lc.m_corrections[0]->m_printed_columns);
4838
4839 /* After the final insertion hint, they are all printed together
4840 as a replacement (along with the text between them). */
4841 lc.add_hint (hint: hint_2);
4842 ASSERT_STREQ ("const_cast<foo *> (ptr->field)",
4843 lc.m_corrections[0]->m_text);
4844 ASSERT_EQ (1, lc.m_corrections.length ());
4845 ASSERT_EQ (column_range (12, 28), lc.m_corrections[0]->m_affected_bytes);
4846 ASSERT_EQ (column_range (12, 28), lc.m_corrections[0]->m_affected_columns);
4847 ASSERT_EQ (column_range (12, 41), lc.m_corrections[0]->m_printed_columns);
4848 }
4849
4850 /* Example where two are consolidated during printing. */
4851 {
4852 test_diagnostic_context dc;
4853 rich_location richloc (line_table, expr);
4854 richloc.add_fixit_replace (where: open_paren, new_content: "CAST (");
4855 richloc.add_fixit_replace (where: close_paren, new_content: ") (");
4856 richloc.add_fixit_insert_after (new_content: ")");
4857
4858 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4859 ASSERT_STREQ (" foo *f = (foo *)ptr->field;\n"
4860 " ^~~~~~~~~~\n"
4861 " -\n"
4862 " CAST (-\n"
4863 " ) ( )\n",
4864 pp_formatted_text (dc.printer));
4865 }
4866
4867 /* Example where none are consolidated during printing. */
4868 {
4869 test_diagnostic_context dc;
4870 rich_location richloc (line_table, expr);
4871 richloc.add_fixit_replace (where: open_paren, new_content: "CST (");
4872 richloc.add_fixit_replace (where: close_paren, new_content: ") (");
4873 richloc.add_fixit_insert_after (new_content: ")");
4874
4875 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4876 ASSERT_STREQ (" foo *f = (foo *)ptr->field;\n"
4877 " ^~~~~~~~~~\n"
4878 " -\n"
4879 " CST ( -\n"
4880 " ) ( )\n",
4881 pp_formatted_text (dc.printer));
4882 }
4883
4884 /* Example of deletion fix-it hints. */
4885 {
4886 test_diagnostic_context dc;
4887 rich_location richloc (line_table, expr);
4888 richloc.add_fixit_insert_before (where: open_paren, new_content: "(bar *)");
4889 source_range victim = {.m_start: open_paren, .m_finish: close_paren};
4890 richloc.add_fixit_remove (src_range: victim);
4891
4892 /* This case is actually handled by fixit-consolidation,
4893 rather than by line_corrections. */
4894 ASSERT_EQ (1, richloc.get_num_fixit_hints ());
4895
4896 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4897 ASSERT_STREQ (" foo *f = (foo *)ptr->field;\n"
4898 " ^~~~~~~~~~\n"
4899 " -------\n"
4900 " (bar *)\n",
4901 pp_formatted_text (dc.printer));
4902 }
4903
4904 /* Example of deletion fix-it hints that would overlap. */
4905 {
4906 test_diagnostic_context dc;
4907 rich_location richloc (line_table, expr);
4908 richloc.add_fixit_insert_before (where: open_paren, new_content: "(longer *)");
4909 source_range victim = {.m_start: expr_start, .m_finish: expr_finish};
4910 richloc.add_fixit_remove (src_range: victim);
4911
4912 /* These fixits are not consolidated. */
4913 ASSERT_EQ (2, richloc.get_num_fixit_hints ());
4914
4915 /* But the corrections are. */
4916 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4917 ASSERT_STREQ (" foo *f = (foo *)ptr->field;\n"
4918 " ^~~~~~~~~~\n"
4919 " -----------------\n"
4920 " (longer *)(foo *)\n",
4921 pp_formatted_text (dc.printer));
4922 }
4923
4924 /* Example of insertion fix-it hints that would overlap. */
4925 {
4926 test_diagnostic_context dc;
4927 rich_location richloc (line_table, expr);
4928 richloc.add_fixit_insert_before (where: open_paren, new_content: "LONGER THAN THE CAST");
4929 richloc.add_fixit_insert_after (where: close_paren, new_content: "TEST");
4930
4931 /* The first insertion is long enough that if printed naively,
4932 it would overlap with the second.
4933 Verify that they are printed as a single replacement. */
4934 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
4935 ASSERT_STREQ (" foo *f = (foo *)ptr->field;\n"
4936 " ^~~~~~~~~~\n"
4937 " -------\n"
4938 " LONGER THAN THE CAST(foo *)TEST\n",
4939 pp_formatted_text (dc.printer));
4940 }
4941}
4942
4943/* Multibyte-aware version of preceding tests. See comments above
4944 test_one_liner_simple_caret_utf8() too, we use the same two multibyte
4945 characters here. */
4946
4947static void
4948test_overlapped_fixit_printing_utf8 (const line_table_case &case_)
4949{
4950 /* Create a tempfile and write some text to it. */
4951
4952 const char *content
4953 /* Display columns.
4954 00000000000000000000000111111111111111111111111222222222222222223
4955 12344444444555555556789012344444444555555556789012345678999999990 */
4956 = " f\xf0\x9f\x98\x82 *f = (f\xf0\x9f\x98\x82 *)ptr->field\xcf\x80;\n";
4957 /* 00000000000000000000011111111111111111111112222222222333333333333
4958 12344445555666677778901234566667777888899990123456789012333344445
4959 Byte columns. */
4960
4961 temp_source_file tmp (SELFTEST_LOCATION, ".C", content);
4962 line_table_test ltt (case_);
4963
4964 const line_map_ordinary *ord_map
4965 = linemap_check_ordinary (map: linemap_add (line_table, LC_ENTER, sysp: false,
4966 to_file: tmp.get_filename (), to_line: 0));
4967
4968 linemap_line_start (set: line_table, to_line: 1, max_column_hint: 100);
4969
4970 const location_t final_line_end
4971 = linemap_position_for_line_and_column (set: line_table, ord_map, 6, 50);
4972
4973 /* Don't attempt to run the tests if column data might be unavailable. */
4974 if (final_line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
4975 return;
4976
4977 /* A test for converting a C-style cast to a C++-style cast. */
4978 const location_t open_paren
4979 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 14);
4980 const location_t close_paren
4981 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 22);
4982 const location_t expr_start
4983 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 23);
4984 const location_t expr_finish
4985 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 34);
4986 const location_t expr = make_location (caret: expr_start, start: expr_start, finish: expr_finish);
4987
4988 /* Various examples of fix-it hints that aren't themselves consolidated,
4989 but for which the *printing* may need consolidation. */
4990
4991 /* Example where 3 fix-it hints are printed as one. */
4992 {
4993 test_diagnostic_context dc;
4994 rich_location richloc (line_table, expr);
4995 richloc.add_fixit_replace (where: open_paren, new_content: "const_cast<");
4996 richloc.add_fixit_replace (where: close_paren, new_content: "> (");
4997 richloc.add_fixit_insert_after (new_content: ")");
4998
4999 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5000 ASSERT_STREQ (" f\xf0\x9f\x98\x82"
5001 " *f = (f\xf0\x9f\x98\x82"
5002 " *)ptr->field\xcf\x80"
5003 ";\n"
5004 " ^~~~~~~~~~~\n"
5005 " ------------------\n"
5006 " const_cast<f\xf0\x9f\x98\x82"
5007 " *> (ptr->field\xcf\x80"
5008 ")\n",
5009 pp_formatted_text (dc.printer));
5010
5011 /* Unit-test the line_corrections machinery. */
5012 char_display_policy policy (make_policy (dc, richloc));
5013 ASSERT_EQ (3, richloc.get_num_fixit_hints ());
5014 const fixit_hint *hint_0 = richloc.get_fixit_hint (idx: 0);
5015 ASSERT_EQ (column_range (14, 14),
5016 get_affected_range (policy, hint_0, CU_BYTES));
5017 ASSERT_EQ (column_range (12, 12),
5018 get_affected_range (policy, hint_0, CU_DISPLAY_COLS));
5019 ASSERT_EQ (column_range (12, 22), get_printed_columns (policy, hint_0));
5020 const fixit_hint *hint_1 = richloc.get_fixit_hint (idx: 1);
5021 ASSERT_EQ (column_range (22, 22),
5022 get_affected_range (policy, hint_1, CU_BYTES));
5023 ASSERT_EQ (column_range (18, 18),
5024 get_affected_range (policy, hint_1, CU_DISPLAY_COLS));
5025 ASSERT_EQ (column_range (18, 20), get_printed_columns (policy, hint_1));
5026 const fixit_hint *hint_2 = richloc.get_fixit_hint (idx: 2);
5027 ASSERT_EQ (column_range (35, 34),
5028 get_affected_range (policy, hint_2, CU_BYTES));
5029 ASSERT_EQ (column_range (30, 29),
5030 get_affected_range (policy, hint_2, CU_DISPLAY_COLS));
5031 ASSERT_EQ (column_range (30, 30), get_printed_columns (policy, hint_2));
5032
5033 /* Add each hint in turn to a line_corrections instance,
5034 and verify that they are consolidated into one correction instance
5035 as expected. */
5036 line_corrections lc (policy, tmp.get_filename (), 1);
5037
5038 /* The first replace hint by itself. */
5039 lc.add_hint (hint: hint_0);
5040 ASSERT_EQ (1, lc.m_corrections.length ());
5041 ASSERT_EQ (column_range (14, 14), lc.m_corrections[0]->m_affected_bytes);
5042 ASSERT_EQ (column_range (12, 12), lc.m_corrections[0]->m_affected_columns);
5043 ASSERT_EQ (column_range (12, 22), lc.m_corrections[0]->m_printed_columns);
5044 ASSERT_STREQ ("const_cast<", lc.m_corrections[0]->m_text);
5045
5046 /* After the second replacement hint, they are printed together
5047 as a replacement (along with the text between them). */
5048 lc.add_hint (hint: hint_1);
5049 ASSERT_EQ (1, lc.m_corrections.length ());
5050 ASSERT_STREQ ("const_cast<f\xf0\x9f\x98\x82 *> (",
5051 lc.m_corrections[0]->m_text);
5052 ASSERT_EQ (column_range (14, 22), lc.m_corrections[0]->m_affected_bytes);
5053 ASSERT_EQ (column_range (12, 18), lc.m_corrections[0]->m_affected_columns);
5054 ASSERT_EQ (column_range (12, 30), lc.m_corrections[0]->m_printed_columns);
5055
5056 /* After the final insertion hint, they are all printed together
5057 as a replacement (along with the text between them). */
5058 lc.add_hint (hint: hint_2);
5059 ASSERT_STREQ ("const_cast<f\xf0\x9f\x98\x82 *> (ptr->field\xcf\x80)",
5060 lc.m_corrections[0]->m_text);
5061 ASSERT_EQ (1, lc.m_corrections.length ());
5062 ASSERT_EQ (column_range (14, 34), lc.m_corrections[0]->m_affected_bytes);
5063 ASSERT_EQ (column_range (12, 29), lc.m_corrections[0]->m_affected_columns);
5064 ASSERT_EQ (column_range (12, 42), lc.m_corrections[0]->m_printed_columns);
5065 }
5066
5067 /* Example where two are consolidated during printing. */
5068 {
5069 test_diagnostic_context dc;
5070 rich_location richloc (line_table, expr);
5071 richloc.add_fixit_replace (where: open_paren, new_content: "CAST (");
5072 richloc.add_fixit_replace (where: close_paren, new_content: ") (");
5073 richloc.add_fixit_insert_after (new_content: ")");
5074
5075 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5076 ASSERT_STREQ (" f\xf0\x9f\x98\x82"
5077 " *f = (f\xf0\x9f\x98\x82"
5078 " *)ptr->field\xcf\x80"
5079 ";\n"
5080 " ^~~~~~~~~~~\n"
5081 " -\n"
5082 " CAST (-\n"
5083 " ) ( )\n",
5084 pp_formatted_text (dc.printer));
5085 }
5086
5087 /* Example where none are consolidated during printing. */
5088 {
5089 test_diagnostic_context dc;
5090 rich_location richloc (line_table, expr);
5091 richloc.add_fixit_replace (where: open_paren, new_content: "CST (");
5092 richloc.add_fixit_replace (where: close_paren, new_content: ") (");
5093 richloc.add_fixit_insert_after (new_content: ")");
5094
5095 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5096 ASSERT_STREQ (" f\xf0\x9f\x98\x82"
5097 " *f = (f\xf0\x9f\x98\x82"
5098 " *)ptr->field\xcf\x80"
5099 ";\n"
5100 " ^~~~~~~~~~~\n"
5101 " -\n"
5102 " CST ( -\n"
5103 " ) ( )\n",
5104 pp_formatted_text (dc.printer));
5105 }
5106
5107 /* Example of deletion fix-it hints. */
5108 {
5109 test_diagnostic_context dc;
5110 rich_location richloc (line_table, expr);
5111 richloc.add_fixit_insert_before (where: open_paren, new_content: "(bar\xf0\x9f\x98\x82 *)");
5112 source_range victim = {.m_start: open_paren, .m_finish: close_paren};
5113 richloc.add_fixit_remove (src_range: victim);
5114
5115 /* This case is actually handled by fixit-consolidation,
5116 rather than by line_corrections. */
5117 ASSERT_EQ (1, richloc.get_num_fixit_hints ());
5118
5119 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5120 ASSERT_STREQ (" f\xf0\x9f\x98\x82"
5121 " *f = (f\xf0\x9f\x98\x82"
5122 " *)ptr->field\xcf\x80"
5123 ";\n"
5124 " ^~~~~~~~~~~\n"
5125 " -------\n"
5126 " (bar\xf0\x9f\x98\x82"
5127 " *)\n",
5128 pp_formatted_text (dc.printer));
5129 }
5130
5131 /* Example of deletion fix-it hints that would overlap. */
5132 {
5133 test_diagnostic_context dc;
5134 rich_location richloc (line_table, expr);
5135 richloc.add_fixit_insert_before (where: open_paren, new_content: "(long\xf0\x9f\x98\x82 *)");
5136 source_range victim = {.m_start: expr_start, .m_finish: expr_finish};
5137 richloc.add_fixit_remove (src_range: victim);
5138
5139 /* These fixits are not consolidated. */
5140 ASSERT_EQ (2, richloc.get_num_fixit_hints ());
5141
5142 /* But the corrections are. */
5143 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5144 ASSERT_STREQ (" f\xf0\x9f\x98\x82"
5145 " *f = (f\xf0\x9f\x98\x82"
5146 " *)ptr->field\xcf\x80"
5147 ";\n"
5148 " ^~~~~~~~~~~\n"
5149 " ------------------\n"
5150 " (long\xf0\x9f\x98\x82"
5151 " *)(f\xf0\x9f\x98\x82"
5152 " *)\n",
5153 pp_formatted_text (dc.printer));
5154 }
5155
5156 /* Example of insertion fix-it hints that would overlap. */
5157 {
5158 test_diagnostic_context dc;
5159 rich_location richloc (line_table, expr);
5160 richloc.add_fixit_insert_before
5161 (where: open_paren, new_content: "L\xf0\x9f\x98\x82NGER THAN THE CAST");
5162 richloc.add_fixit_insert_after (where: close_paren, new_content: "TEST");
5163
5164 /* The first insertion is long enough that if printed naively,
5165 it would overlap with the second.
5166 Verify that they are printed as a single replacement. */
5167 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5168 ASSERT_STREQ (" f\xf0\x9f\x98\x82"
5169 " *f = (f\xf0\x9f\x98\x82"
5170 " *)ptr->field\xcf\x80"
5171 ";\n"
5172 " ^~~~~~~~~~~\n"
5173 " -------\n"
5174 " L\xf0\x9f\x98\x82"
5175 "NGER THAN THE CAST(f\xf0\x9f\x98\x82"
5176 " *)TEST\n",
5177 pp_formatted_text (dc.printer));
5178 }
5179}
5180
5181/* Verify that the line_corrections machinery correctly prints
5182 overlapping fixit-hints that have been added in the wrong
5183 order.
5184 Adapted from PR c/81405 seen on gcc.dg/init-excess-1.c*/
5185
5186static void
5187test_overlapped_fixit_printing_2 (const line_table_case &case_)
5188{
5189 /* Create a tempfile and write some text to it.
5190 ...000000000111111111122222222223333333333.
5191 ...123456789012345678901234567890123456789. */
5192 const char *content
5193 = ("int a5[][0][0] = { 1, 2 };\n");
5194 temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
5195 line_table_test ltt (case_);
5196
5197 const line_map_ordinary *ord_map
5198 = linemap_check_ordinary (map: linemap_add (line_table, LC_ENTER, sysp: false,
5199 to_file: tmp.get_filename (), to_line: 0));
5200
5201 linemap_line_start (set: line_table, to_line: 1, max_column_hint: 100);
5202
5203 const location_t final_line_end
5204 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 100);
5205
5206 /* Don't attempt to run the tests if column data might be unavailable. */
5207 if (final_line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
5208 return;
5209
5210 const location_t col_1
5211 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 1);
5212 const location_t col_20
5213 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 20);
5214 const location_t col_21
5215 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 21);
5216 const location_t col_23
5217 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 23);
5218 const location_t col_25
5219 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 25);
5220
5221 /* Two insertions, in the wrong order. */
5222 {
5223 test_diagnostic_context dc;
5224
5225 rich_location richloc (line_table, col_20);
5226 richloc.add_fixit_insert_before (where: col_23, new_content: "{");
5227 richloc.add_fixit_insert_before (where: col_21, new_content: "}");
5228
5229 /* These fixits should be accepted; they can't be consolidated. */
5230 char_display_policy policy (make_policy (dc, richloc));
5231 ASSERT_EQ (2, richloc.get_num_fixit_hints ());
5232 const fixit_hint *hint_0 = richloc.get_fixit_hint (idx: 0);
5233 ASSERT_EQ (column_range (23, 22),
5234 get_affected_range (policy, hint_0, CU_BYTES));
5235 ASSERT_EQ (column_range (23, 23), get_printed_columns (policy, hint_0));
5236 const fixit_hint *hint_1 = richloc.get_fixit_hint (idx: 1);
5237 ASSERT_EQ (column_range (21, 20),
5238 get_affected_range (policy, hint_1, CU_BYTES));
5239 ASSERT_EQ (column_range (21, 21), get_printed_columns (policy, hint_1));
5240
5241 /* Verify that they're printed correctly. */
5242 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5243 ASSERT_STREQ (" int a5[][0][0] = { 1, 2 };\n"
5244 " ^\n"
5245 " } {\n",
5246 pp_formatted_text (dc.printer));
5247 }
5248
5249 /* Various overlapping insertions, some occurring "out of order"
5250 (reproducing the fix-it hints from PR c/81405). */
5251 {
5252 test_diagnostic_context dc;
5253 rich_location richloc (line_table, col_20);
5254
5255 richloc.add_fixit_insert_before (where: col_20, new_content: "{{");
5256 richloc.add_fixit_insert_before (where: col_21, new_content: "}}");
5257 richloc.add_fixit_insert_before (where: col_23, new_content: "{");
5258 richloc.add_fixit_insert_before (where: col_21, new_content: "}");
5259 richloc.add_fixit_insert_before (where: col_23, new_content: "{{");
5260 richloc.add_fixit_insert_before (where: col_25, new_content: "}");
5261 richloc.add_fixit_insert_before (where: col_21, new_content: "}");
5262 richloc.add_fixit_insert_before (where: col_1, new_content: "{");
5263 richloc.add_fixit_insert_before (where: col_25, new_content: "}");
5264 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5265 ASSERT_STREQ (" int a5[][0][0] = { 1, 2 };\n"
5266 " ^\n"
5267 " { -----\n"
5268 " {{1}}}}, {{{2 }}\n",
5269 pp_formatted_text (dc.printer));
5270 }
5271}
5272
5273/* Insertion fix-it hint: adding a "break;" on a line by itself. */
5274
5275static void
5276test_fixit_insert_containing_newline (const line_table_case &case_)
5277{
5278 /* Create a tempfile and write some text to it.
5279 .........................0000000001111111.
5280 .........................1234567890123456. */
5281 const char *old_content = (" case 'a':\n" /* line 1. */
5282 " x = a;\n" /* line 2. */
5283 " case 'b':\n" /* line 3. */
5284 " x = b;\n");/* line 4. */
5285
5286 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
5287 line_table_test ltt (case_);
5288 linemap_add (line_table, LC_ENTER, sysp: false, to_file: tmp.get_filename (), to_line: 3);
5289
5290 location_t case_start = linemap_position_for_column (line_table, 5);
5291 location_t case_finish = linemap_position_for_column (line_table, 13);
5292 location_t case_loc = make_location (caret: case_start, start: case_start, finish: case_finish);
5293 location_t line_start = linemap_position_for_column (line_table, 1);
5294
5295 if (case_finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
5296 return;
5297
5298 /* Add a "break;" on a line by itself before line 3 i.e. before
5299 column 1 of line 3. */
5300 {
5301 rich_location richloc (line_table, case_loc);
5302 richloc.add_fixit_insert_before (where: line_start, new_content: " break;\n");
5303
5304 /* Without line numbers. */
5305 {
5306 test_diagnostic_context dc;
5307 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5308 ASSERT_STREQ (" x = a;\n"
5309 "+ break;\n"
5310 " case 'b':\n"
5311 " ^~~~~~~~~\n",
5312 pp_formatted_text (dc.printer));
5313 }
5314
5315 /* With line numbers. */
5316 {
5317 test_diagnostic_context dc;
5318 dc.m_source_printing.show_line_numbers_p = true;
5319 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5320 ASSERT_STREQ (" 2 | x = a;\n"
5321 " +++ |+ break;\n"
5322 " 3 | case 'b':\n"
5323 " | ^~~~~~~~~\n",
5324 pp_formatted_text (dc.printer));
5325 }
5326 }
5327
5328 /* Verify that attempts to add text with a newline fail when the
5329 insertion point is *not* at the start of a line. */
5330 {
5331 rich_location richloc (line_table, case_loc);
5332 richloc.add_fixit_insert_before (where: case_start, new_content: "break;\n");
5333 ASSERT_TRUE (richloc.seen_impossible_fixit_p ());
5334 test_diagnostic_context dc;
5335 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5336 ASSERT_STREQ (" case 'b':\n"
5337 " ^~~~~~~~~\n",
5338 pp_formatted_text (dc.printer));
5339 }
5340}
5341
5342/* Insertion fix-it hint: adding a "#include <stdio.h>\n" to the top
5343 of the file, where the fix-it is printed in a different line-span
5344 to the primary range of the diagnostic. */
5345
5346static void
5347test_fixit_insert_containing_newline_2 (const line_table_case &case_)
5348{
5349 /* Create a tempfile and write some text to it.
5350 .........................0000000001111111.
5351 .........................1234567890123456. */
5352 const char *old_content = ("test (int ch)\n" /* line 1. */
5353 "{\n" /* line 2. */
5354 " putchar (ch);\n" /* line 3. */
5355 "}\n"); /* line 4. */
5356
5357 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
5358 line_table_test ltt (case_);
5359
5360 const line_map_ordinary *ord_map = linemap_check_ordinary
5361 (map: linemap_add (line_table, LC_ENTER, sysp: false, to_file: tmp.get_filename (), to_line: 0));
5362 linemap_line_start (set: line_table, to_line: 1, max_column_hint: 100);
5363
5364 /* The primary range is the "putchar" token. */
5365 location_t putchar_start
5366 = linemap_position_for_line_and_column (set: line_table, ord_map, 3, 2);
5367 location_t putchar_finish
5368 = linemap_position_for_line_and_column (set: line_table, ord_map, 3, 8);
5369 location_t putchar_loc
5370 = make_location (caret: putchar_start, start: putchar_start, finish: putchar_finish);
5371 rich_location richloc (line_table, putchar_loc);
5372
5373 /* Add a "#include <stdio.h>" on a line by itself at the top of the file. */
5374 location_t file_start
5375 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 1);
5376 richloc.add_fixit_insert_before (where: file_start, new_content: "#include <stdio.h>\n");
5377
5378 if (putchar_finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
5379 return;
5380
5381 {
5382 test_diagnostic_context dc;
5383 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5384 ASSERT_STREQ ("FILENAME:1:1:\n"
5385 "+#include <stdio.h>\n"
5386 " test (int ch)\n"
5387 "FILENAME:3:2:\n"
5388 " putchar (ch);\n"
5389 " ^~~~~~~\n",
5390 pp_formatted_text (dc.printer));
5391 }
5392
5393 /* With line-numbering, the line spans are close enough to be
5394 consolidated, since it makes little sense to skip line 2. */
5395 {
5396 test_diagnostic_context dc;
5397 dc.m_source_printing.show_line_numbers_p = true;
5398 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5399 ASSERT_STREQ (" +++ |+#include <stdio.h>\n"
5400 " 1 | test (int ch)\n"
5401 " 2 | {\n"
5402 " 3 | putchar (ch);\n"
5403 " | ^~~~~~~\n",
5404 pp_formatted_text (dc.printer));
5405 }
5406}
5407
5408/* Replacement fix-it hint containing a newline.
5409 This will fail, as newlines are only supported when inserting at the
5410 beginning of a line. */
5411
5412static void
5413test_fixit_replace_containing_newline (const line_table_case &case_)
5414{
5415 /* Create a tempfile and write some text to it.
5416 .........................0000000001111.
5417 .........................1234567890123. */
5418 const char *old_content = "foo = bar ();\n";
5419
5420 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
5421 line_table_test ltt (case_);
5422 linemap_add (line_table, LC_ENTER, sysp: false, to_file: tmp.get_filename (), to_line: 1);
5423
5424 /* Replace the " = " with "\n = ", as if we were reformatting an
5425 overly long line. */
5426 location_t start = linemap_position_for_column (line_table, 4);
5427 location_t finish = linemap_position_for_column (line_table, 6);
5428 location_t loc = linemap_position_for_column (line_table, 13);
5429 rich_location richloc (line_table, loc);
5430 source_range range = source_range::from_locations (start, finish);
5431 richloc.add_fixit_replace (src_range: range, new_content: "\n =");
5432
5433 /* Arbitrary newlines are not yet supported within fix-it hints, so
5434 the fix-it should not be displayed. */
5435 ASSERT_TRUE (richloc.seen_impossible_fixit_p ());
5436
5437 if (finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
5438 return;
5439
5440 test_diagnostic_context dc;
5441 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5442 ASSERT_STREQ (" foo = bar ();\n"
5443 " ^\n",
5444 pp_formatted_text (dc.printer));
5445}
5446
5447/* Fix-it hint, attempting to delete a newline.
5448 This will fail, as we currently only support fix-it hints that
5449 affect one line at a time. */
5450
5451static void
5452test_fixit_deletion_affecting_newline (const line_table_case &case_)
5453{
5454 /* Create a tempfile and write some text to it.
5455 ..........................0000000001111.
5456 ..........................1234567890123. */
5457 const char *old_content = ("foo = bar (\n"
5458 " );\n");
5459
5460 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
5461 line_table_test ltt (case_);
5462 const line_map_ordinary *ord_map = linemap_check_ordinary
5463 (map: linemap_add (line_table, LC_ENTER, sysp: false, to_file: tmp.get_filename (), to_line: 0));
5464 linemap_line_start (set: line_table, to_line: 1, max_column_hint: 100);
5465
5466 /* Attempt to delete the " (\n...)". */
5467 location_t start
5468 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 10);
5469 location_t caret
5470 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 11);
5471 location_t finish
5472 = linemap_position_for_line_and_column (set: line_table, ord_map, 2, 7);
5473 location_t loc = make_location (caret, start, finish);
5474 rich_location richloc (line_table, loc);
5475 richloc. add_fixit_remove ();
5476
5477 /* Fix-it hints that affect more than one line are not yet supported, so
5478 the fix-it should not be displayed. */
5479 ASSERT_TRUE (richloc.seen_impossible_fixit_p ());
5480
5481 if (finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
5482 return;
5483
5484 test_diagnostic_context dc;
5485 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5486 ASSERT_STREQ (" foo = bar (\n"
5487 " ~^\n"
5488 " );\n"
5489 " ~ \n",
5490 pp_formatted_text (dc.printer));
5491}
5492
5493static void
5494test_tab_expansion (const line_table_case &case_)
5495{
5496 /* Create a tempfile and write some text to it. This example uses a tabstop
5497 of 8, as the column numbers attempt to indicate:
5498
5499 .....................000.01111111111.22222333333 display
5500 .....................123.90123456789.56789012345 columns */
5501 const char *content = " \t This: `\t' is a tab.\n";
5502 /* ....................000 00000011111 11111222222 byte
5503 ....................123 45678901234 56789012345 columns */
5504
5505 const int tabstop = 8;
5506 cpp_char_column_policy policy (tabstop, cpp_wcwidth);
5507 const int first_non_ws_byte_col = 7;
5508 const int right_quote_byte_col = 15;
5509 const int last_byte_col = 25;
5510 ASSERT_EQ (35, cpp_display_width (content, last_byte_col, policy));
5511
5512 temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
5513 line_table_test ltt (case_);
5514 linemap_add (line_table, LC_ENTER, sysp: false, to_file: tmp.get_filename (), to_line: 1);
5515
5516 /* Don't attempt to run the tests if column data might be unavailable. */
5517 location_t line_end = linemap_position_for_column (line_table, last_byte_col);
5518 if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
5519 return;
5520
5521 /* Check that the leading whitespace with mixed tabs and spaces is expanded
5522 into 11 spaces. Recall that print_line() also puts one space before
5523 everything too. */
5524 {
5525 test_diagnostic_context dc;
5526 dc.m_tabstop = tabstop;
5527 rich_location richloc (line_table,
5528 linemap_position_for_column (line_table,
5529 first_non_ws_byte_col));
5530 layout test_layout (dc, richloc, DK_ERROR, nullptr);
5531 test_layout.print_line (row: 1);
5532 ASSERT_STREQ (" This: ` ' is a tab.\n"
5533 " ^\n",
5534 pp_formatted_text (dc.printer));
5535 }
5536
5537 /* Confirm the display width was tracked correctly across the internal tab
5538 as well. */
5539 {
5540 test_diagnostic_context dc;
5541 dc.m_tabstop = tabstop;
5542 rich_location richloc (line_table,
5543 linemap_position_for_column (line_table,
5544 right_quote_byte_col));
5545 layout test_layout (dc, richloc, DK_ERROR, nullptr);
5546 test_layout.print_line (row: 1);
5547 ASSERT_STREQ (" This: ` ' is a tab.\n"
5548 " ^\n",
5549 pp_formatted_text (dc.printer));
5550 }
5551}
5552
5553/* Verify that the escaping machinery can cope with a variety of different
5554 invalid bytes. */
5555
5556static void
5557test_escaping_bytes_1 (const line_table_case &case_)
5558{
5559 const char content[] = "before\0\1\2\3\v\x80\xff""after\n";
5560 const size_t sz = sizeof (content);
5561 temp_source_file tmp (SELFTEST_LOCATION, ".c", content, sz);
5562 line_table_test ltt (case_);
5563 const line_map_ordinary *ord_map = linemap_check_ordinary
5564 (map: linemap_add (line_table, LC_ENTER, sysp: false, to_file: tmp.get_filename (), to_line: 0));
5565 linemap_line_start (set: line_table, to_line: 1, max_column_hint: 100);
5566
5567 location_t finish
5568 = linemap_position_for_line_and_column (set: line_table, ord_map, 1,
5569 strlen (s: content));
5570
5571 if (finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
5572 return;
5573
5574 /* Locations of the NUL and \v bytes. */
5575 location_t nul_loc
5576 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 7);
5577 location_t v_loc
5578 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 11);
5579 gcc_rich_location richloc (nul_loc);
5580 richloc.add_range (loc: v_loc);
5581
5582 {
5583 test_diagnostic_context dc;
5584 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5585 ASSERT_STREQ (" before \1\2\3\v\x80\xff""after\n"
5586 " ^ ~\n",
5587 pp_formatted_text (dc.printer));
5588 }
5589 richloc.set_escape_on_output (true);
5590 {
5591 test_diagnostic_context dc;
5592 dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_UNICODE);
5593 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5594 ASSERT_STREQ
5595 (" before<U+0000><U+0001><U+0002><U+0003><U+000B><80><ff>after\n"
5596 " ^~~~~~~~ ~~~~~~~~\n",
5597 pp_formatted_text (dc.printer));
5598 }
5599 {
5600 test_diagnostic_context dc;
5601 dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_BYTES);
5602 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5603 ASSERT_STREQ (" before<00><01><02><03><0b><80><ff>after\n"
5604 " ^~~~ ~~~~\n",
5605 pp_formatted_text (dc.printer));
5606 }
5607}
5608
5609/* As above, but verify that we handle the initial byte of a line
5610 correctly. */
5611
5612static void
5613test_escaping_bytes_2 (const line_table_case &case_)
5614{
5615 const char content[] = "\0after\n";
5616 const size_t sz = sizeof (content);
5617 temp_source_file tmp (SELFTEST_LOCATION, ".c", content, sz);
5618 line_table_test ltt (case_);
5619 const line_map_ordinary *ord_map = linemap_check_ordinary
5620 (map: linemap_add (line_table, LC_ENTER, sysp: false, to_file: tmp.get_filename (), to_line: 0));
5621 linemap_line_start (set: line_table, to_line: 1, max_column_hint: 100);
5622
5623 location_t finish
5624 = linemap_position_for_line_and_column (set: line_table, ord_map, 1,
5625 strlen (s: content));
5626
5627 if (finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
5628 return;
5629
5630 /* Location of the NUL byte. */
5631 location_t nul_loc
5632 = linemap_position_for_line_and_column (set: line_table, ord_map, 1, 1);
5633 gcc_rich_location richloc (nul_loc);
5634
5635 {
5636 test_diagnostic_context dc;
5637 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5638 ASSERT_STREQ (" after\n"
5639 " ^\n",
5640 pp_formatted_text (dc.printer));
5641 }
5642 richloc.set_escape_on_output (true);
5643 {
5644 test_diagnostic_context dc;
5645 dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_UNICODE);
5646 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5647 ASSERT_STREQ (" <U+0000>after\n"
5648 " ^~~~~~~~\n",
5649 pp_formatted_text (dc.printer));
5650 }
5651 {
5652 test_diagnostic_context dc;
5653 dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_BYTES);
5654 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5655 ASSERT_STREQ (" <00>after\n"
5656 " ^~~~\n",
5657 pp_formatted_text (dc.printer));
5658 }
5659}
5660
5661/* Verify that line numbers are correctly printed for the case of
5662 a multiline range in which the width of the line numbers changes
5663 (e.g. from "9" to "10"). */
5664
5665static void
5666test_line_numbers_multiline_range ()
5667{
5668 /* Create a tempfile and write some text to it. */
5669 pretty_printer pp;
5670 for (int i = 0; i < 20; i++)
5671 /* .........0000000001111111.
5672 .............1234567890123456. */
5673 pp_printf (&pp, "this is line %i\n", i + 1);
5674 temp_source_file tmp (SELFTEST_LOCATION, ".txt", pp_formatted_text (&pp));
5675 line_table_test ltt;
5676
5677 const line_map_ordinary *ord_map = linemap_check_ordinary
5678 (map: linemap_add (line_table, LC_ENTER, sysp: false, to_file: tmp.get_filename (), to_line: 0));
5679 linemap_line_start (set: line_table, to_line: 1, max_column_hint: 100);
5680
5681 /* Create a multi-line location, starting at the "line" of line 9, with
5682 a caret on the "is" of line 10, finishing on the "this" line 11. */
5683
5684 location_t start
5685 = linemap_position_for_line_and_column (set: line_table, ord_map, 9, 9);
5686 location_t caret
5687 = linemap_position_for_line_and_column (set: line_table, ord_map, 10, 6);
5688 location_t finish
5689 = linemap_position_for_line_and_column (set: line_table, ord_map, 11, 4);
5690 location_t loc = make_location (caret, start, finish);
5691
5692 test_diagnostic_context dc;
5693 dc.m_source_printing.show_line_numbers_p = true;
5694 dc.m_source_printing.min_margin_width = 0;
5695 gcc_rich_location richloc (loc);
5696 diagnostic_show_locus (context: &dc, richloc: &richloc, diagnostic_kind: DK_ERROR);
5697 ASSERT_STREQ (" 9 | this is line 9\n"
5698 " | ~~~~~~\n"
5699 "10 | this is line 10\n"
5700 " | ~~~~~^~~~~~~~~~\n"
5701 "11 | this is line 11\n"
5702 " | ~~~~ \n",
5703 pp_formatted_text (dc.printer));
5704}
5705
5706/* Run all of the selftests within this file. */
5707
5708void
5709diagnostic_show_locus_cc_tests ()
5710{
5711 test_line_span ();
5712
5713 test_layout_range_for_single_point ();
5714 test_layout_range_for_single_line ();
5715 test_layout_range_for_multiple_lines ();
5716
5717 test_display_widths ();
5718
5719 for_each_line_table_case (testcase: test_layout_x_offset_display_utf8);
5720 for_each_line_table_case (testcase: test_layout_x_offset_display_tab);
5721
5722 test_get_line_bytes_without_trailing_whitespace ();
5723
5724 test_diagnostic_show_locus_unknown_location ();
5725
5726 for_each_line_table_case (testcase: test_diagnostic_show_locus_one_liner);
5727 for_each_line_table_case (testcase: test_diagnostic_show_locus_one_liner_utf8);
5728 for_each_line_table_case (testcase: test_add_location_if_nearby);
5729 for_each_line_table_case (testcase: test_diagnostic_show_locus_fixit_lines);
5730 for_each_line_table_case (testcase: test_fixit_consolidation);
5731 for_each_line_table_case (testcase: test_overlapped_fixit_printing);
5732 for_each_line_table_case (testcase: test_overlapped_fixit_printing_utf8);
5733 for_each_line_table_case (testcase: test_overlapped_fixit_printing_2);
5734 for_each_line_table_case (testcase: test_fixit_insert_containing_newline);
5735 for_each_line_table_case (testcase: test_fixit_insert_containing_newline_2);
5736 for_each_line_table_case (testcase: test_fixit_replace_containing_newline);
5737 for_each_line_table_case (testcase: test_fixit_deletion_affecting_newline);
5738 for_each_line_table_case (testcase: test_tab_expansion);
5739 for_each_line_table_case (testcase: test_escaping_bytes_1);
5740 for_each_line_table_case (testcase: test_escaping_bytes_2);
5741
5742 test_line_numbers_multiline_range ();
5743}
5744
5745} // namespace selftest
5746
5747#endif /* #if CHECKING_P */
5748
5749#if __GNUC__ >= 10
5750# pragma GCC diagnostic pop
5751#endif
5752

source code of gcc/diagnostic-show-locus.cc