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