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