diff options
Diffstat (limited to 'gcc/diagnostics/source-printing.cc')
-rw-r--r-- | gcc/diagnostics/source-printing.cc | 7052 |
1 files changed, 7052 insertions, 0 deletions
diff --git a/gcc/diagnostics/source-printing.cc b/gcc/diagnostics/source-printing.cc new file mode 100644 index 0000000..94b1c2d --- /dev/null +++ b/gcc/diagnostics/source-printing.cc @@ -0,0 +1,7052 @@ +/* Diagnostic subroutines for printing source-code + Copyright (C) 1999-2025 Free Software Foundation, Inc. + Contributed by Gabriel Dos Reis <gdr@codesourcery.com> + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#define INCLUDE_MAP +#define INCLUDE_STRING +#define INCLUDE_VECTOR +#include "system.h" +#include "coretypes.h" +#include "version.h" +#include "intl.h" +#include "diagnostic.h" +#include "diagnostics/color.h" +#include "gcc-rich-location.h" +#include "text-range-label.h" +#include "selftest.h" +#include "diagnostics/selftest-context.h" +#include "diagnostics/selftest-source-printing.h" +#include "cpplib.h" +#include "text-art/types.h" +#include "text-art/theme.h" +#include "diagnostics/source-printing-effects.h" +#include "diagnostics/file-cache.h" +#include "xml.h" +#include "xml-printer.h" + +#ifdef HAVE_TERMIOS_H +# include <termios.h> +#endif + +#ifdef GWINSZ_IN_SYS_IOCTL +# include <sys/ioctl.h> +#endif + +/* Disable warnings about quoting issues in the pp_xxx calls below + that (intentionally) don't follow GCC diagnostic conventions. */ +#if __GNUC__ >= 10 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wformat-diag" +#endif + +/* Classes for rendering source code and diagnostics, within an + anonymous namespace. + The work is done by "class layout", which embeds and uses + "class colorizer" and "class layout_range" to get things done. */ + +namespace { + +/* The state at a given point of the source code, assuming that we're + in a range: which range are we in, and whether we should draw a caret at + this point. */ + +struct point_state +{ + int range_idx; + bool draw_caret_p; +}; + +/* A class to inject colorization codes when printing the diagnostic locus + as text. + + It has one kind of colorization for each of: + - normal text + - range 0 (the "primary location") + - range 1 + - range 2 + + The class caches the lookup of the color codes for the above. + + The class also has responsibility for tracking which of the above is + active, filtering out unnecessary changes. This allows + layout_printer::print_source_line and layout_printer::print_annotation_line + to simply request a colorization code for *every* character they print, + via this class, and have the filtering be done for them here. */ + +class colorizer +{ + public: + colorizer (pretty_printer &pp, + const rich_location &richloc, + enum diagnostics::kind diagnostic_kind); + ~colorizer (); + + void set_range (int range_idx) + { + /* If we have a specific highlight color for the range, use it. */ + if (pp_show_highlight_colors (&m_pp)) + { + const location_range *const loc_range = m_richloc.get_range (range_idx); + if (loc_range->m_highlight_color) + { + set_named_color (loc_range->m_highlight_color); + return; + } + } + + /* Otherwise, we emphasize the primary location, then alternate between + two colors for the secondary locations. + But if we're printing a run of events in a diagnostic path, that + makes no sense, so print all of them with the same colorization. */ + if (m_diagnostic_kind == diagnostics::kind::path) + set_state (0); + else + set_state (range_idx); + } + void set_cfg_edge () { set_state (0); } + void set_normal_text () { set_state (STATE_NORMAL_TEXT); } + void set_fixit_insert () { set_state (STATE_FIXIT_INSERT); } + void set_fixit_delete () { set_state (STATE_FIXIT_DELETE); } + void set_named_color (const char *color); + + private: + void set_state (int state); + void begin_state (int state); + void finish_state (int state); + const char *get_color_by_name (const char *); + + private: + static const int STATE_NORMAL_TEXT = -1; + static const int STATE_FIXIT_INSERT = -2; + static const int STATE_FIXIT_DELETE = -3; + static const int STATE_NAMED_COLOR = -4; + + pretty_printer &m_pp; + const rich_location &m_richloc; + enum diagnostics::kind m_diagnostic_kind; + int m_current_state; + const char *m_range1; + const char *m_range2; + const char *m_fixit_insert; + const char *m_fixit_delete; + const char *m_stop_color; + std::string m_current_named_color; +}; + +/* In order to handle multibyte sources properly, all of this logic needs to be + aware of the distinction between the number of bytes and the number of + display columns occupied by a character, which are not the same for non-ASCII + characters. For example, the Unicode pi symbol, U+03C0, is encoded in UTF-8 + as "\xcf\x80", and thus occupies 2 bytes of space while only occupying 1 + display column when it is output. A typical emoji, such as U+1F602 (in + UTF-8, "\xf0\x9f\x98\x82"), requires 4 bytes and has a display width of 2. + + The below example line, which is also used for selftests below, shows how the + display column and byte column are related: + + 0000000001111111111222222 display + 1234567890123456789012345 columns + SS_foo = P_bar.SS_fieldP; + 0000000111111111222222223 byte + 1356789012456789134567891 columns + + Here SS represents the two display columns for the U+1F602 emoji, and P + represents the one display column for the U+03C0 pi symbol. As an example, a + diagnostic pointing to the final P on this line is at byte column 29 and + display column 24. This reflects the fact that the three extended characters + before the final P occupy cumulatively 5 more bytes than they do display + columns (a difference of 2 for each of the two SSs, and one for the other P). + + One or the other of the two column units is more useful depending on the + context. For instance, in order to output the caret at the correct location, + we need to count display columns; in order to colorize a source line, we need + to count the bytes. All locations are provided to us as byte counts, which + we augment with the display column on demand so that it can be used when + needed. This is not the most efficient way to do things since it requires + looping over the whole line each time, but it should be fine for the purpose + of outputting diagnostics. + + In order to keep straight which units (byte or display) are in use at a + given time, the following enum lets us specify that explicitly. */ + +enum column_unit { + /* Measured in raw bytes. */ + CU_BYTES = 0, + + /* Measured in display units. */ + CU_DISPLAY_COLS, + + /* For arrays indexed by column_unit. */ + CU_NUM_UNITS +}; + +/* Utility class to augment an exploc with the corresponding display column. */ + +class exploc_with_display_col : public expanded_location +{ + public: + exploc_with_display_col (diagnostics::file_cache &fc, + const expanded_location &exploc, + const cpp_char_column_policy &policy, + enum location_aspect aspect) + : expanded_location (exploc), + m_display_col (location_compute_display_column (fc, exploc, policy)) + { + if (exploc.column > 0) + { + /* m_display_col is now the final column of the byte. + If escaping has happened, we may want the first column instead. */ + if (aspect != LOCATION_ASPECT_FINISH) + { + expanded_location prev_exploc (exploc); + prev_exploc.column--; + int prev_display_col + = (location_compute_display_column (fc, prev_exploc, policy)); + m_display_col = prev_display_col + 1; + } + } + } + + int m_display_col; +}; + + +/* A point within a layout_range; similar to an exploc_with_display_col, + but after filtering on file. */ + +class layout_point +{ + public: + layout_point (const exploc_with_display_col &exploc) + : m_line (exploc.line) + { + m_columns[CU_BYTES] = exploc.column; + m_columns[CU_DISPLAY_COLS] = exploc.m_display_col; + } + + linenum_type m_line; + int m_columns[CU_NUM_UNITS]; +}; + +/* A class for use by "class layout" below: a filtered location_range. */ + +class layout_range +{ + public: + layout_range (const exploc_with_display_col &start_exploc, + const exploc_with_display_col &finish_exploc, + enum range_display_kind range_display_kind, + const exploc_with_display_col &caret_exploc, + unsigned original_idx, + const range_label *label); + + bool contains_point (linenum_type row, int column, + enum column_unit col_unit) const; + bool intersects_line_p (linenum_type row) const; + + bool has_in_edge () const; + bool has_out_edge () const; + + layout_point m_start; + layout_point m_finish; + enum range_display_kind m_range_display_kind; + layout_point m_caret; + unsigned m_original_idx; + const range_label *m_label; +}; + +/* A struct for use by layout::print_source_line for telling + layout::print_annotation_line the extents of the source line that + it printed, so that underlines can be clipped appropriately. Units + are 1-based display columns. */ + +struct line_bounds +{ + int m_first_non_ws_disp_col; + int m_last_non_ws_disp_col; + + line_bounds () + { + m_first_non_ws_disp_col = INT_MAX; + m_last_non_ws_disp_col = 0; + } +}; + +/* A range of contiguous source lines within a layout (e.g. "lines 5-10" + or "line 23"). During the layout ctor, layout::calculate_line_spans + splits the pertinent source lines into a list of disjoint line_span + instances (e.g. lines 5-10, lines 15-20, line 23). */ + +class line_span +{ +public: + line_span (linenum_type first_line, linenum_type last_line) + : m_first_line (first_line), m_last_line (last_line) + { + gcc_assert (first_line <= last_line); + } + linenum_type get_first_line () const { return m_first_line; } + linenum_type get_last_line () const { return m_last_line; } + + bool contains_line_p (linenum_type line) const + { + return line >= m_first_line && line <= m_last_line; + } + + static int comparator (const void *p1, const void *p2) + { + const line_span *ls1 = (const line_span *)p1; + const line_span *ls2 = (const line_span *)p2; + int first_line_cmp = compare (ls1->m_first_line, ls2->m_first_line); + if (first_line_cmp) + return first_line_cmp; + return compare (ls1->m_last_line, ls2->m_last_line); + } + + linenum_type m_first_line; + linenum_type m_last_line; +}; + +#if CHECKING_P + +/* Selftests for line_span. */ + +static void +test_line_span () +{ + line_span line_one (1, 1); + ASSERT_EQ (1, line_one.get_first_line ()); + ASSERT_EQ (1, line_one.get_last_line ()); + ASSERT_FALSE (line_one.contains_line_p (0)); + ASSERT_TRUE (line_one.contains_line_p (1)); + ASSERT_FALSE (line_one.contains_line_p (2)); + + line_span lines_1_to_3 (1, 3); + ASSERT_EQ (1, lines_1_to_3.get_first_line ()); + ASSERT_EQ (3, lines_1_to_3.get_last_line ()); + ASSERT_TRUE (lines_1_to_3.contains_line_p (1)); + ASSERT_TRUE (lines_1_to_3.contains_line_p (3)); + + ASSERT_EQ (0, line_span::comparator (&line_one, &line_one)); + ASSERT_GT (line_span::comparator (&lines_1_to_3, &line_one), 0); + ASSERT_LT (line_span::comparator (&line_one, &lines_1_to_3), 0); + + /* A linenum > 2^31. */ + const linenum_type LARGEST_LINE = 0xffffffff; + line_span largest_line (LARGEST_LINE, LARGEST_LINE); + ASSERT_EQ (LARGEST_LINE, largest_line.get_first_line ()); + ASSERT_EQ (LARGEST_LINE, largest_line.get_last_line ()); + + ASSERT_GT (line_span::comparator (&largest_line, &line_one), 0); + ASSERT_LT (line_span::comparator (&line_one, &largest_line), 0); +} + +#endif /* #if CHECKING_P */ + +/* A bundle of information containing how to print unicode + characters and bytes when quoting source code. + + Provides a unified place to support escaping some subset + of characters to some format. + + Extends char_column_policy; printing is split out to avoid + libcpp having to know about pretty_printer. */ + +struct char_display_policy : public cpp_char_column_policy +{ + public: + char_display_policy (int tabstop, + int (*width_cb) (cppchar_t c), + void (*print_text_cb) (diagnostics::to_text &text_out, + const cpp_decoded_char &cp), + void (*print_html_cb) (diagnostics::to_html &html_out, + const cpp_decoded_char &cp)) + : cpp_char_column_policy (tabstop, width_cb), + m_print_text_cb (print_text_cb), + m_print_html_cb (print_html_cb) + { + } + + void (*m_print_text_cb) (diagnostics::to_text &text_out, + const cpp_decoded_char &cp); + void (*m_print_html_cb) (diagnostics::to_html &html_out, + const cpp_decoded_char &cp); +}; + +template <typename TextOrHtml> class layout_printer; + +} // anonymous namespace + +namespace diagnostics { + +/* This code is written generically to write either: + - text, to a pretty_printer, potentially with colorization codes, or + - html, to an xml::printer, with nested HTML tags. + + This is handled via a "TextOrHtml" template, which is either to_text + or to_html. */ + +/* Writing text output. */ + +struct to_text +{ + friend class layout_printer<to_text>; + + // This is a RAII class for HTML, but is a no-op for text. + struct auto_check_tag_nesting + { + auto_check_tag_nesting (to_text &) {} + }; + + to_text (pretty_printer &pp, + colorizer &colorizer) + : m_pp (pp), + m_colorizer (&colorizer) + { + m_saved_rule = pp_prefixing_rule (&m_pp); + pp_prefixing_rule (&m_pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE; + } + to_text (pretty_printer &pp, + colorizer *colorizer) + : m_pp (pp), + m_colorizer (colorizer) + { + m_saved_rule = pp_prefixing_rule (&m_pp); + pp_prefixing_rule (&m_pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE; + } + ~to_text () + { + + pp_prefixing_rule (&m_pp) = m_saved_rule; + } + + static bool is_text () { return true; } + static bool is_html () { return false; } + + void emit_text_prefix () + { + pp_emit_prefix (&m_pp); + } + + void push_html_tag (std::string, bool) + { + // no-op for text + } + void push_html_tag_with_class (std::string, std::string, bool) + { + // no-op for text + } + void pop_html_tag (const char *) + { + // no-op for text + } + + void add_html_tag_with_class (std::string, std::string, bool) + { + // no-op for text + } + + void add_space () + { + pp_space (&m_pp); + } + + void add_character (cppchar_t ch) + { + pp_unicode_character (&m_pp, ch); + } + + void add_utf8_byte (char b) + { + pp_character (&m_pp, b); + } + + void add_text (const char *text) + { + pp_string (&m_pp, text); + } + + void print_decoded_char (const char_display_policy &char_policy, + cpp_decoded_char cp) + { + char_policy.m_print_text_cb (*this, cp); + } + + void colorize_text_ensure_normal () + { + gcc_assert (m_colorizer); + m_colorizer->set_normal_text (); + } + + void colorize_text_for_range_idx (int range_idx) + { + gcc_assert (m_colorizer); + m_colorizer->set_range (range_idx); + } + + void colorize_text_for_cfg_edge () + { + gcc_assert (m_colorizer); + m_colorizer->set_cfg_edge (); + } + + void colorize_text_for_fixit_insert () + { + gcc_assert (m_colorizer); + m_colorizer->set_fixit_insert (); + } + + void colorize_text_for_fixit_delete () + { + gcc_assert (m_colorizer); + m_colorizer->set_fixit_delete (); + } + + void + invoke_start_span_fn (const source_print_policy &source_policy, + const location_print_policy &loc_policy, + const expanded_location &exploc) + { + source_policy.get_text_start_span_fn () (loc_policy, *this, exploc); + } + + // Text-specific functions + void add_newline () + { + pp_newline (&m_pp); + } + + pretty_printer &m_pp; +private: + colorizer *m_colorizer; + diagnostic_prefixing_rule_t m_saved_rule; +}; + +/* Writing HTML output. */ + +struct to_html +{ + friend class layout_printer<to_html>; + + // RAII class for ensuring that the tags nested correctly + struct auto_check_tag_nesting : public xml::auto_check_tag_nesting + { + public: + auto_check_tag_nesting (to_html &out) + : xml::auto_check_tag_nesting (out.m_xp) + { + } + }; + + to_html (xml::printer &xp, + const rich_location *richloc, + html_label_writer *html_label_writer) + : m_xp (xp), + m_richloc (richloc), + m_html_label_writer (html_label_writer) + {} + + static bool is_text () { return false; } + static bool is_html () { return true; } + + void emit_text_prefix () + { + // no-op for HTML + } + + void push_html_tag (std::string name, + bool preserve_whitespace) + { + m_xp.push_tag (std::move (name), preserve_whitespace); + } + + void push_html_tag_with_class (std::string name, + std::string class_, + bool preserve_whitespace) + { + m_xp.push_tag_with_class (std::move (name), + std::move (class_), + preserve_whitespace); + } + + void pop_html_tag (const char *expected_name) + { + m_xp.pop_tag (expected_name); + } + + void add_html_tag_with_class (std::string name, + std::string class_, + bool preserve_whitespace) + { + auto element = std::make_unique<xml::element> (std::move (name), + preserve_whitespace); + element->set_attr ("class", std::move (class_)); + m_xp.append (std::move (element)); + } + + void add_raw_html (const char *src) + { + m_xp.add_raw (src); + } + + void add_space () + { + m_xp.add_text (" "); + } + + void add_character (cppchar_t ch) + { + pp_clear_output_area (&m_scratch_pp); + pp_unicode_character (&m_scratch_pp, ch); + m_xp.add_text_from_pp (m_scratch_pp); + } + + void add_utf8_byte (char b) + { + m_xp.add_text (std::string (1, b)); + } + + void add_text (const char *text) + { + m_xp.add_text (text); + } + + void print_decoded_char (const char_display_policy &char_policy, + cpp_decoded_char cp) + { + char_policy.m_print_html_cb (*this, cp); + } + + void colorize_text_ensure_normal () + { + // no-op for HTML + } + + void colorize_text_for_cfg_edge () + { + // no-op for HTML + } + + void colorize_text_for_fixit_insert () + { + // no-op for HTML + } + + void colorize_text_for_fixit_delete () + { + // no-op for HTML + } + + void + invoke_start_span_fn (const source_print_policy &source_policy, + const location_print_policy &loc_policy, + const expanded_location &exploc) + { + source_policy.get_html_start_span_fn () (loc_policy, *this, exploc); + } + + const location_range * + get_location_range_by_idx (int range_idx) + { + if (!m_richloc) + return nullptr; + return m_richloc->get_range (range_idx); + } + + const char * + get_highlight_color_for_range_idx (int range_idx) + { + const location_range *const loc_range + = get_location_range_by_idx (range_idx); + if (!loc_range) + return nullptr; + return loc_range->m_highlight_color; + } + + xml::printer &m_xp; + const rich_location *m_richloc; +private: + html_label_writer *m_html_label_writer; + pretty_printer m_scratch_pp; +}; + +void +location_print_policy:: +print_text_span_start (const diagnostics::context &dc, + pretty_printer &pp, + const expanded_location &exploc) +{ + to_text out (pp, nullptr); + source_print_policy source_policy (dc); + source_policy.get_text_start_span_fn () (*this, out, exploc); +} + +void +location_print_policy:: +print_html_span_start (const diagnostics::context &dc, + xml::printer &xp, + const expanded_location &exploc) +{ + to_html out (xp, nullptr, nullptr); + source_print_policy source_policy (dc); + source_policy.get_html_start_span_fn () (*this, out, exploc); +} + +pretty_printer * +get_printer (to_text &out) +{ + return &out.m_pp; +} + +template<> +void +default_start_span_fn<to_text> (const location_print_policy &loc_policy, + to_text &out, + expanded_location exploc) +{ + const column_policy &column_policy_ = loc_policy.get_column_policy (); + label_text text + = column_policy_.get_location_text (exploc, + loc_policy.show_column_p (), + pp_show_color (&out.m_pp)); + pp_string (&out.m_pp, text.get ()); + pp_newline (&out.m_pp); +} + +template<> +void +default_start_span_fn<to_html> (const location_print_policy &loc_policy, + to_html &out, + expanded_location exploc) +{ + const column_policy &column_policy_ + = loc_policy.get_column_policy (); + label_text text + = column_policy_.get_location_text (exploc, + loc_policy.show_column_p (), + false); + out.m_xp.push_tag_with_class ("span", "location", true); + out.m_xp.add_text (text.get ()); + out.m_xp.pop_tag ("span"); +} + +} // namespace diagnostics + +namespace { + +/* A class to control the overall layout when printing a diagnostic. + + The layout is determined within the constructor. + + Printing the layout is handled by class layout_printer. This separation + is to avoid depending on the pretty_printer in the layout. + + We assume we have disjoint ranges. */ + +class layout +{ + public: + friend class layout_printer<diagnostics::to_text>; + friend class layout_printer<diagnostics::to_html>; + + layout (const diagnostics::source_print_policy &source_policy, + const rich_location &richloc, + diagnostics::source_effect_info *effect_info = nullptr); + + bool maybe_add_location_range (const location_range *loc_range, + unsigned original_idx, + bool restrict_to_current_line_spans); + + int get_num_line_spans () const { return m_line_spans.length (); } + const line_span *get_line_span (int idx) const { return &m_line_spans[idx]; } + + int get_linenum_width () const { return m_linenum_width; } + int get_x_offset_display () const { return m_x_offset_display; } + + bool print_heading_for_line_span_index_p (int line_span_idx) const; + + expanded_location get_expanded_location (const line_span *) const; + + void on_bad_codepoint (const char *ptr, cppchar_t ch, size_t ch_sz); + + private: + bool will_show_line_p (linenum_type row) const; + bool should_print_annotation_line_p (linenum_type row) const; + + bool annotation_line_showed_range_p (linenum_type line, int start_column, + int finish_column) const; + bool validate_fixit_hint_p (const fixit_hint *hint); + + void calculate_line_spans (); + void calculate_linenum_width (); + void calculate_x_offset_display (); + + bool + get_state_at_point (/* Inputs. */ + linenum_type row, int column, + int first_non_ws, int last_non_ws, + enum column_unit col_unit, + /* Outputs. */ + point_state *out_state) const; + + int + get_x_bound_for_row (linenum_type row, int caret_column, + int last_non_ws) const; + + private: + bool compatible_locations_p (location_t loc_a, location_t loc_b) const; + + const diagnostics::source_printing_options &m_options; + const line_maps *m_line_table; + diagnostics::file_cache &m_file_cache; + const text_art::ascii_theme m_fallback_theme; + const text_art::theme &m_theme; + diagnostics::source_effect_info *m_effect_info; + char_display_policy m_char_policy; + location_t m_primary_loc; + exploc_with_display_col m_exploc; + auto_vec <layout_range> m_layout_ranges; + auto_vec <const fixit_hint *> m_fixit_hints; + auto_vec <line_span> m_line_spans; + int m_linenum_width; + int m_x_offset_display; + bool m_escape_on_output; +}; + +class line_label; + +enum class margin_kind +{ + normal, + insertion, + ruler +}; + +/* A bundle of state for printing a particular layout + to a particular TextOrHtml (either to_text or to_html). */ +template <typename TextOrHtml> +class layout_printer +{ +public: + layout_printer (TextOrHtml &text_or_html, + const layout &layout, + bool is_diagnostic_path); + + void print (const diagnostics::source_print_policy &source_policy); + +private: + const diagnostics::source_printing_options & + get_options () const { return m_layout.m_options; } + + const text_art::theme & + get_theme () const { return m_layout.m_theme; } + + void show_ruler (int max_column); + void print_gap_in_line_numbering (); + void print_leading_fixits (linenum_type row); + void print_line (linenum_type row); + line_bounds print_source_line (linenum_type row, const char *line, + int line_bytes); + void print_leftmost_column (); + void start_annotation_line (enum margin_kind); + void end_line (); + void print_annotation_line (linenum_type row, const line_bounds lbounds); + void print_any_labels (linenum_type row); + void begin_label (int state_idx, bool is_label_text); + void end_label (int state_idx, bool is_label_text); + void print_trailing_fixits (linenum_type row); + + void + move_to_column (int *column, int dest_column, bool add_left_margin); + + void print_any_right_to_left_edge_lines (); + + void set_in_range (int range_idx); + void set_outside_range (); + +private: + TextOrHtml &m_text_or_html; + const layout &m_layout; + bool m_is_diagnostic_path; + + bool m_was_in_range_p; + int m_last_range_idx; + + /* Fields for handling links between labels (e.g. for showing CFG edges + in execution paths). + Note that the logic for printing such links makes various simplifying + assumptions about the set of labels in the rich_location, and users + of this code will need to split up labels into separate rich_location + instances to respect these assumptions, or the output will look wrong. + See the diagnostic_path-printing code for more information. */ + + /* An enum for describing the state of the leftmost column, + used for showing links between labels. + Consider e.g. + .x0000000001111111111222222222233333333334444444444. + .x1234567890123456789012345678901234567890123456789. + | | <- none + | (9) following ‘false’ branch... ->-+ <- none + | | <- none + | | <- none + |+----------------------------------------+ <- rewinding to lhs + || result->i = i; <- at lhs + || ~~~~~~~~~~^~~ <- at lhs + || | <- at lhs + |+----------->(10) ...to here <- indenting to dest + ^^ + || + |leftmost column ("x" above). + "margin". */ + enum class link_lhs_state { + none, + rewinding_to_lhs, + at_lhs, + indenting_to_dest + } m_link_lhs_state; + + /* The column of the current link on the RHS, if any, or + -1 if there is none. + Consider e.g. + .x0000000001111111111222222222233333333334444444444. + .x1234567890123456789012345678901234567890123456789. + | | <- -1 + | (10) following ‘false’ branch... ->-+ <- 42 + | | <- 42 + | | <- 42 + |+-----------------------------------------+ <- 42 + || result->i = i; <- -1 + || ~~~~~~~~~~^~~ <- -1 + || | <- -1 + |+----------->(11) ...to here <- -1. */ + int m_link_rhs_column; +}; + +/* Implementation of "class colorizer". */ + +/* The constructor for "colorizer". Lookup and store color codes for the + different kinds of things we might need to print. */ + +colorizer::colorizer (pretty_printer &pp, + const rich_location &richloc, + enum diagnostics::kind diagnostic_kind) : + m_pp (pp), + m_richloc (richloc), + m_diagnostic_kind (diagnostic_kind), + m_current_state (STATE_NORMAL_TEXT) +{ + m_range1 = get_color_by_name ("range1"); + m_range2 = get_color_by_name ("range2"); + m_fixit_insert = get_color_by_name ("fixit-insert"); + m_fixit_delete = get_color_by_name ("fixit-delete"); + m_stop_color = colorize_stop (pp_show_color (&m_pp)); +} + +/* The destructor for "colorize". If colorization is on, print a code to + turn it off. */ + +colorizer::~colorizer () +{ + finish_state (m_current_state); +} + +/* Update state, changing to the specific named color and printing its + color codes. */ + +void +colorizer::set_named_color (const char *color) +{ + if (m_current_state == STATE_NAMED_COLOR + && color == m_current_named_color) + return; + finish_state (m_current_state); + m_current_state = STATE_NAMED_COLOR; + pp_string (&m_pp, colorize_start (pp_show_color (&m_pp), color)); + m_current_named_color = color; +} + +/* Update state, printing color codes if necessary if there's a state + change. */ + +void +colorizer::set_state (int new_state) +{ + if (m_current_state != new_state) + { + finish_state (m_current_state); + m_current_state = new_state; + begin_state (new_state); + } +} + +/* Turn on any colorization for STATE. */ + +void +colorizer::begin_state (int state) +{ + switch (state) + { + case STATE_NORMAL_TEXT: + break; + + case STATE_FIXIT_INSERT: + pp_string (&m_pp, m_fixit_insert); + break; + + case STATE_FIXIT_DELETE: + pp_string (&m_pp, m_fixit_delete); + break; + + case STATE_NAMED_COLOR: + /* Should be handled by colorizer::set_named_color. */ + gcc_unreachable (); + + case 0: + /* Make range 0 be the same color as the "kind" text + (error vs warning vs note). */ + pp_string + (&m_pp, + colorize_start (pp_show_color (&m_pp), + get_color_for_kind (m_diagnostic_kind))); + break; + + case 1: + pp_string (&m_pp, m_range1); + break; + + case 2: + pp_string (&m_pp, m_range2); + break; + + default: + /* For ranges beyond 2, alternate between color 1 and color 2. */ + { + gcc_assert (state > 2); + pp_string (&m_pp, + state % 2 ? m_range1 : m_range2); + } + break; + } +} + +/* Turn off any colorization for STATE. */ + +void +colorizer::finish_state (int state) +{ + if (state != STATE_NORMAL_TEXT) + pp_string (&m_pp, m_stop_color); +} + +/* Get the color code for NAME (or the empty string if + colorization is disabled). */ + +const char * +colorizer::get_color_by_name (const char *name) +{ + return colorize_start (pp_show_color (&m_pp), name); +} + +/* Implementation of class layout_range. */ + +/* The constructor for class layout_range. + Initialize various layout_point fields from expanded_location + equivalents; we've already filtered on file. */ + +layout_range::layout_range (const exploc_with_display_col &start_exploc, + const exploc_with_display_col &finish_exploc, + enum range_display_kind range_display_kind, + const exploc_with_display_col &caret_exploc, + unsigned original_idx, + const range_label *label) +: m_start (start_exploc), + m_finish (finish_exploc), + m_range_display_kind (range_display_kind), + m_caret (caret_exploc), + m_original_idx (original_idx), + m_label (label) +{ +} + +/* Is (column, row) within the given range? + We've already filtered on the file. + + Ranges are closed (both limits are within the range). + + Example A: a single-line range: + start: (col=22, line=2) + finish: (col=38, line=2) + + |00000011111111112222222222333333333344444444444 + |34567890123456789012345678901234567890123456789 +--+----------------------------------------------- +01|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +02|bbbbbbbbbbbbbbbbbbbSwwwwwwwwwwwwwwwFaaaaaaaaaaa +03|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + Example B: a multiline range with + start: (col=14, line=3) + finish: (col=08, line=5) + + |00000011111111112222222222333333333344444444444 + |34567890123456789012345678901234567890123456789 +--+----------------------------------------------- +01|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +02|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +03|bbbbbbbbbbbSwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww +04|wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww +05|wwwwwFaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +06|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +--+----------------------------------------------- + + Legend: + - 'b' indicates a point *before* the range + - 'S' indicates the start of the range + - 'w' indicates a point within the range + - 'F' indicates the finish of the range (which is + within it). + - 'a' indicates a subsequent point *after* the range. + + COL_UNIT controls whether we check the byte column or + the display column; one or the other is more convenient + depending on the context. */ + +bool +layout_range::contains_point (linenum_type row, int column, + enum column_unit col_unit) const +{ + gcc_assert (m_start.m_line <= m_finish.m_line); + /* ...but the equivalent isn't true for the columns; + consider example B in the comment above. */ + + if (row < m_start.m_line) + /* Points before the first line of the range are + outside it (corresponding to line 01 in example A + and lines 01 and 02 in example B above). */ + return false; + + if (row == m_start.m_line) + /* On same line as start of range (corresponding + to line 02 in example A and line 03 in example B). */ + { + if (column < m_start.m_columns[col_unit]) + /* Points on the starting line of the range, but + before the column in which it begins. */ + return false; + + if (row < m_finish.m_line) + /* This is a multiline range; the point + is within it (corresponds to line 03 in example B + from column 14 onwards) */ + return true; + else + { + /* This is a single-line range. */ + gcc_assert (row == m_finish.m_line); + return column <= m_finish.m_columns[col_unit]; + } + } + + /* The point is in a line beyond that containing the + start of the range: lines 03 onwards in example A, + and lines 04 onwards in example B. */ + gcc_assert (row > m_start.m_line); + + if (row > m_finish.m_line) + /* The point is beyond the final line of the range + (lines 03 onwards in example A, and lines 06 onwards + in example B). */ + return false; + + if (row < m_finish.m_line) + { + /* The point is in a line that's fully within a multiline + range (e.g. line 04 in example B). */ + gcc_assert (m_start.m_line < m_finish.m_line); + return true; + } + + gcc_assert (row == m_finish.m_line); + + return column <= m_finish.m_columns[col_unit]; +} + +/* Does this layout_range contain any part of line ROW? */ + +bool +layout_range::intersects_line_p (linenum_type row) const +{ + gcc_assert (m_start.m_line <= m_finish.m_line); + if (row < m_start.m_line) + return false; + if (row > m_finish.m_line) + return false; + return true; +} + +/* Return true if this layout_range should have an in-edge. */ + +bool +layout_range::has_in_edge () const +{ + if (!m_label) + return false; + const diagnostics::label_effects *effects + = m_label->get_effects (m_original_idx); + if (!effects) + return false; + + return effects->has_in_edge (m_original_idx); +} + +/* Return true if this layout_range should have an out-edge. */ + +bool +layout_range::has_out_edge () const +{ + if (!m_label) + return false; + const diagnostics::label_effects *effects + = m_label->get_effects (m_original_idx); + if (!effects) + return false; + + return effects->has_out_edge (m_original_idx); +} + +#if CHECKING_P + +/* Default for when we don't care what the tab expansion is set to. */ +static const int def_tabstop = 8; + +static cpp_char_column_policy def_policy () +{ + return cpp_char_column_policy (def_tabstop, cpp_wcwidth); +} + +/* Create some expanded locations for testing layout_range. The filename + member of the explocs is set to the empty string. This member will only be + inspected by the calls to location_compute_display_column() made from the + layout_point constructors. That function will check for an empty filename + argument and not attempt to open it, rather treating the non-existent data + as if the display width were the same as the byte count. Tests exercising a + real difference between byte count and display width are performed later, + e.g. in test_diagnostic_show_locus_one_liner_utf8(). */ + +static layout_range +make_range (diagnostics::file_cache &fc, + int start_line, int start_col, int end_line, int end_col) +{ + const expanded_location start_exploc + = {"", start_line, start_col, nullptr, false}; + const expanded_location finish_exploc + = {"", end_line, end_col, nullptr, false}; + return layout_range (exploc_with_display_col (fc, + start_exploc, def_policy (), + LOCATION_ASPECT_START), + exploc_with_display_col (fc, + finish_exploc, def_policy (), + LOCATION_ASPECT_FINISH), + SHOW_RANGE_WITHOUT_CARET, + exploc_with_display_col (fc, + start_exploc, def_policy (), + LOCATION_ASPECT_CARET), + 0, nullptr); +} + +/* Selftests for layout_range::contains_point and + layout_range::intersects_line_p. */ + +/* Selftest for layout_range, where the layout_range + is a range with start==end i.e. a single point. */ + +static void +test_layout_range_for_single_point () +{ + diagnostics::file_cache fc; + layout_range point = make_range (fc, 7, 10, 7, 10); + + /* Tests for layout_range::contains_point. */ + + for (int i = 0; i != CU_NUM_UNITS; ++i) + { + const enum column_unit col_unit = (enum column_unit) i; + + /* Before the line. */ + ASSERT_FALSE (point.contains_point (6, 1, col_unit)); + + /* On the line, but before start. */ + ASSERT_FALSE (point.contains_point (7, 9, col_unit)); + + /* At the point. */ + ASSERT_TRUE (point.contains_point (7, 10, col_unit)); + + /* On the line, after the point. */ + ASSERT_FALSE (point.contains_point (7, 11, col_unit)); + + /* After the line. */ + ASSERT_FALSE (point.contains_point (8, 1, col_unit)); + } + + /* Tests for layout_range::intersects_line_p. */ + ASSERT_FALSE (point.intersects_line_p (6)); + ASSERT_TRUE (point.intersects_line_p (7)); + ASSERT_FALSE (point.intersects_line_p (8)); +} + +/* Selftest for layout_range, where the layout_range + is the single-line range shown as "Example A" above. */ + +static void +test_layout_range_for_single_line () +{ + diagnostics::file_cache fc; + layout_range example_a = make_range (fc, 2, 22, 2, 38); + + /* Tests for layout_range::contains_point. */ + + for (int i = 0; i != CU_NUM_UNITS; ++i) + { + const enum column_unit col_unit = (enum column_unit) i; + + /* Before the line. */ + ASSERT_FALSE (example_a.contains_point (1, 1, col_unit)); + + /* On the line, but before start. */ + ASSERT_FALSE (example_a.contains_point (2, 21, col_unit)); + + /* On the line, at the start. */ + ASSERT_TRUE (example_a.contains_point (2, 22, col_unit)); + + /* On the line, within the range. */ + ASSERT_TRUE (example_a.contains_point (2, 23, col_unit)); + + /* On the line, at the end. */ + ASSERT_TRUE (example_a.contains_point (2, 38, col_unit)); + + /* On the line, after the end. */ + ASSERT_FALSE (example_a.contains_point (2, 39, col_unit)); + + /* After the line. */ + ASSERT_FALSE (example_a.contains_point (2, 39, col_unit)); + } + + /* Tests for layout_range::intersects_line_p. */ + ASSERT_FALSE (example_a.intersects_line_p (1)); + ASSERT_TRUE (example_a.intersects_line_p (2)); + ASSERT_FALSE (example_a.intersects_line_p (3)); +} + +/* Selftest for layout_range, where the layout_range + is the multi-line range shown as "Example B" above. */ + +static void +test_layout_range_for_multiple_lines () +{ + diagnostics::file_cache fc; + layout_range example_b = make_range (fc, 3, 14, 5, 8); + + /* Tests for layout_range::contains_point. */ + + for (int i = 0; i != CU_NUM_UNITS; ++i) + { + const enum column_unit col_unit = (enum column_unit) i; + + /* Before first line. */ + ASSERT_FALSE (example_b.contains_point (1, 1, col_unit)); + + /* On the first line, but before start. */ + ASSERT_FALSE (example_b.contains_point (3, 13, col_unit)); + + /* At the start. */ + ASSERT_TRUE (example_b.contains_point (3, 14, col_unit)); + + /* On the first line, within the range. */ + ASSERT_TRUE (example_b.contains_point (3, 15, col_unit)); + + /* On an interior line. + The column number should not matter; try various boundary + values. */ + ASSERT_TRUE (example_b.contains_point (4, 1, col_unit)); + ASSERT_TRUE (example_b.contains_point (4, 7, col_unit)); + ASSERT_TRUE (example_b.contains_point (4, 8, col_unit)); + ASSERT_TRUE (example_b.contains_point (4, 9, col_unit)); + ASSERT_TRUE (example_b.contains_point (4, 13, col_unit)); + ASSERT_TRUE (example_b.contains_point (4, 14, col_unit)); + ASSERT_TRUE (example_b.contains_point (4, 15, col_unit)); + + /* On the final line, before the end. */ + ASSERT_TRUE (example_b.contains_point (5, 7, col_unit)); + + /* On the final line, at the end. */ + ASSERT_TRUE (example_b.contains_point (5, 8, col_unit)); + + /* On the final line, after the end. */ + ASSERT_FALSE (example_b.contains_point (5, 9, col_unit)); + + /* After the line. */ + ASSERT_FALSE (example_b.contains_point (6, 1, col_unit)); + } + + /* Tests for layout_range::intersects_line_p. */ + ASSERT_FALSE (example_b.intersects_line_p (2)); + ASSERT_TRUE (example_b.intersects_line_p (3)); + ASSERT_TRUE (example_b.intersects_line_p (4)); + ASSERT_TRUE (example_b.intersects_line_p (5)); + ASSERT_FALSE (example_b.intersects_line_p (6)); +} + +#endif /* #if CHECKING_P */ + +/* Given a source line LINE of length LINE_BYTES bytes, determine the length + (still in bytes, not display cols) without any trailing whitespace. */ + +static int +get_line_bytes_without_trailing_whitespace (const char *line, int line_bytes) +{ + int result = line_bytes; + while (result > 0) + { + char ch = line[result - 1]; + if (ch == ' ' || ch == '\t' || ch == '\r') + result--; + else + break; + } + gcc_assert (result >= 0); + gcc_assert (result <= line_bytes); + gcc_assert (result == 0 || + (line[result - 1] != ' ' + && line[result -1] != '\t' + && line[result -1] != '\r')); + return result; +} + +#if CHECKING_P + +/* A helper function for testing get_line_bytes_without_trailing_whitespace. */ + +static void +assert_eq (const char *line, int expected_bytes) +{ + int actual_value + = get_line_bytes_without_trailing_whitespace (line, strlen (line)); + ASSERT_EQ (actual_value, expected_bytes); +} + +/* Verify that get_line_bytes_without_trailing_whitespace is sane for + various inputs. It is not required to handle newlines. */ + +static void +test_get_line_bytes_without_trailing_whitespace () +{ + assert_eq ("", 0); + assert_eq (" ", 0); + assert_eq ("\t", 0); + assert_eq ("\r", 0); + assert_eq ("hello world", 11); + assert_eq ("hello world ", 11); + assert_eq ("hello world \t\t ", 11); + assert_eq ("hello world\r", 11); +} + +#endif /* #if CHECKING_P */ + +/* Helper function for layout's ctor, for sanitizing locations relative + to the primary location within a diagnostic. + + Compare LOC_A and LOC_B to see if it makes sense to print underlines + connecting their expanded locations. Doing so is only guaranteed to + make sense if the locations share the same macro expansion "history" + i.e. they can be traced through the same macro expansions, eventually + reaching an ordinary map. + + This may be too strong a condition, but it effectively sanitizes + PR c++/70105, which has an example of printing an expression where the + final location of the expression is in a different macro, which + erroneously was leading to hundreds of lines of irrelevant source + being printed. */ + +bool +layout::compatible_locations_p (location_t loc_a, location_t loc_b) const +{ + if (IS_ADHOC_LOC (loc_a)) + loc_a = get_location_from_adhoc_loc (m_line_table, loc_a); + if (IS_ADHOC_LOC (loc_b)) + loc_b = get_location_from_adhoc_loc (m_line_table, loc_b); + + /* If either location is one of the special locations outside of a + linemap, they are only compatible if they are equal. */ + if (loc_a < RESERVED_LOCATION_COUNT + || loc_b < RESERVED_LOCATION_COUNT) + return loc_a == loc_b; + + const line_map *map_a = linemap_lookup (m_line_table, loc_a); + linemap_assert (map_a); + + const line_map *map_b = linemap_lookup (m_line_table, loc_b); + linemap_assert (map_b); + + /* Are they within the same map? */ + if (map_a == map_b) + { + /* Are both within the same macro expansion? */ + if (linemap_macro_expansion_map_p (map_a)) + { + /* If so, then they're only compatible if either both are + from the macro definition, or both from the macro arguments. */ + bool loc_a_from_defn + = linemap_location_from_macro_definition_p (m_line_table, loc_a); + bool loc_b_from_defn + = linemap_location_from_macro_definition_p (m_line_table, loc_b); + if (loc_a_from_defn != loc_b_from_defn) + return false; + + /* Expand each location towards the spelling location, and + recurse. */ + const line_map_macro *macro_map = linemap_check_macro (map_a); + location_t loc_a_toward_spelling + = linemap_macro_map_loc_unwind_toward_spelling (m_line_table, + macro_map, + loc_a); + location_t loc_b_toward_spelling + = linemap_macro_map_loc_unwind_toward_spelling (m_line_table, + macro_map, + loc_b); + return compatible_locations_p (loc_a_toward_spelling, + loc_b_toward_spelling); + } + + /* Otherwise they are within the same ordinary map. */ + return true; + } + else + { + /* Within different maps. */ + + /* If either is within a macro expansion, they are incompatible. */ + if (linemap_macro_expansion_map_p (map_a) + || linemap_macro_expansion_map_p (map_b)) + return false; + + /* Within two different ordinary maps; they are compatible iff they + are in the same file. */ + const line_map_ordinary *ord_map_a = linemap_check_ordinary (map_a); + const line_map_ordinary *ord_map_b = linemap_check_ordinary (map_b); + return ord_map_a->to_file == ord_map_b->to_file; + } +} + +/* Comparator for sorting fix-it hints. */ + +static int +fixit_cmp (const void *p_a, const void *p_b) +{ + const fixit_hint * hint_a = *static_cast<const fixit_hint * const *> (p_a); + const fixit_hint * hint_b = *static_cast<const fixit_hint * const *> (p_b); + return hint_a->get_start_loc () - hint_b->get_start_loc (); +} + +/* Callbacks for use when not escaping the source. */ + +/* The default callback for char_column_policy::m_width_cb is cpp_wcwidth. */ + +/* Callback for char_display_policy::m_print_cb for printing source chars + when not escaping the source. */ + +template <class TextOrHtml> +static void +default_print_decoded_ch (TextOrHtml &text_or_html, + const cpp_decoded_char &decoded_ch) +{ + for (const char *ptr = decoded_ch.m_start_byte; + ptr != decoded_ch.m_next_byte; ptr++) + { + if (*ptr == '\0' || *ptr == '\r') + { + text_or_html.add_space (); + continue; + } + + text_or_html.add_utf8_byte (*ptr); + } +} + +/* Callbacks for use with DIAGNOSTICS_ESCAPE_FORMAT_BYTES. */ + +static const int width_per_escaped_byte = 4; + +/* Callback for char_column_policy::m_width_cb for determining the + display width when escaping with DIAGNOSTICS_ESCAPE_FORMAT_BYTES. */ + +static int +escape_as_bytes_width (cppchar_t ch) +{ + if (ch < 0x80 && ISPRINT (ch)) + return cpp_wcwidth (ch); + else + { + if (ch <= 0x7F) return 1 * width_per_escaped_byte; + if (ch <= 0x7FF) return 2 * width_per_escaped_byte; + if (ch <= 0xFFFF) return 3 * width_per_escaped_byte; + return 4 * width_per_escaped_byte; + } +} + +/* Callback for char_display_policy::m_print_cb for printing source chars + when escaping with DIAGNOSTICS_ESCAPE_FORMAT_BYTES. */ + +template <typename TextOrHtml> +static void +escape_as_bytes_print (TextOrHtml &text_or_html, + const cpp_decoded_char &decoded_ch) +{ + if (!decoded_ch.m_valid_ch) + { + for (const char *iter = decoded_ch.m_start_byte; + iter != decoded_ch.m_next_byte; ++iter) + { + char buf[16]; + sprintf (buf, "<%02x>", (unsigned char)*iter); + text_or_html.add_text (buf); + } + return; + } + + cppchar_t ch = decoded_ch.m_ch; + if (ch < 0x80 && ISPRINT (ch)) + text_or_html.add_character (ch); + else + { + for (const char *iter = decoded_ch.m_start_byte; + iter < decoded_ch.m_next_byte; ++iter) + { + char buf[16]; + sprintf (buf, "<%02x>", (unsigned char)*iter); + text_or_html.add_text (buf); + } + } +} + +/* Callbacks for use with DIAGNOSTICS_ESCAPE_FORMAT_UNICODE. */ + +/* Callback for char_column_policy::m_width_cb for determining the + display width when escaping with DIAGNOSTICS_ESCAPE_FORMAT_UNICODE. */ + +static int +escape_as_unicode_width (cppchar_t ch) +{ + if (ch < 0x80 && ISPRINT (ch)) + return cpp_wcwidth (ch); + else + { + // Width of "<U+%04x>" + if (ch > 0xfffff) + return 10; + else if (ch > 0xffff) + return 9; + else + return 8; + } +} + +/* Callback for char_display_policy::m_print_cb for printing source chars + when escaping with DIAGNOSTICS_ESCAPE_FORMAT_UNICODE. */ + +template <typename TextOrHtml> +static void +escape_as_unicode_print (TextOrHtml &text_or_html, + const cpp_decoded_char &decoded_ch) +{ + if (!decoded_ch.m_valid_ch) + { + escape_as_bytes_print<TextOrHtml> (text_or_html, decoded_ch); + return; + } + + cppchar_t ch = decoded_ch.m_ch; + if (ch < 0x80 && ISPRINT (ch)) + text_or_html.add_character (ch); + else + { + char buf[16]; + sprintf (buf, "<U+%04X>", ch); + text_or_html.add_text (buf); + } +} + +/* Populate a char_display_policy based on SOURCE_POLICY and RICHLOC. */ + +static char_display_policy +make_char_policy (const diagnostics::source_print_policy &source_policy, + const rich_location &richloc) +{ + /* The default is to not escape non-ASCII bytes. */ + char_display_policy result + (source_policy.get_column_policy ().get_tabstop (), + cpp_wcwidth, + default_print_decoded_ch<diagnostics::to_text>, + default_print_decoded_ch<diagnostics::to_html>); + + /* If the diagnostic suggests escaping non-ASCII bytes, then + use policy from user-supplied options. */ + if (richloc.escape_on_output_p ()) + { + result.m_undecoded_byte_width = width_per_escaped_byte; + switch (source_policy.get_escape_format ()) + { + default: + gcc_unreachable (); + case DIAGNOSTICS_ESCAPE_FORMAT_UNICODE: + result.m_width_cb = escape_as_unicode_width; + result.m_print_text_cb + = escape_as_unicode_print<diagnostics::to_text>; + result.m_print_html_cb + = escape_as_unicode_print<diagnostics::to_html>; + break; + case DIAGNOSTICS_ESCAPE_FORMAT_BYTES: + result.m_width_cb = escape_as_bytes_width; + result.m_print_text_cb = escape_as_bytes_print<diagnostics::to_text>; + result.m_print_html_cb = escape_as_bytes_print<diagnostics::to_html>; + break; + } + } + + return result; +} + +/* Implementation of class layout. */ + +/* Constructor for class layout. + + Filter the ranges from the rich_location to those that we can + sanely print, populating m_layout_ranges and m_fixit_hints. + Determine the range of lines that we will print, splitting them + up into an ordered list of disjoint spans of contiguous line numbers. + Determine m_x_offset_display, to ensure that the primary caret + will fit within the max_width provided by the diagnostics::context. */ + +layout::layout (const diagnostics::source_print_policy &source_policy, + const rich_location &richloc, + diagnostics::source_effect_info *effect_info) +: m_options (source_policy.get_options ()), + m_line_table (richloc.get_line_table ()), + m_file_cache (source_policy.get_file_cache ()), + /* Ensure we have a non-null m_theme. */ + m_theme (source_policy.get_diagram_theme () + ? *source_policy.get_diagram_theme () + : *static_cast <const text_art::theme *> (&m_fallback_theme)), + m_effect_info (effect_info), + m_char_policy (make_char_policy (source_policy, richloc)), + m_primary_loc (richloc.get_range (0)->m_loc), + m_exploc (m_file_cache, + richloc.get_expanded_location (0), m_char_policy, + LOCATION_ASPECT_CARET), + m_layout_ranges (richloc.get_num_locations ()), + m_fixit_hints (richloc.get_num_fixit_hints ()), + m_line_spans (1 + richloc.get_num_locations ()), + m_linenum_width (0), + m_x_offset_display (0), + m_escape_on_output (richloc.escape_on_output_p ()) +{ + for (unsigned int idx = 0; idx < richloc.get_num_locations (); idx++) + { + /* This diagnostic printer can only cope with "sufficiently sane" ranges. + Ignore any ranges that are awkward to handle. */ + const location_range *loc_range = richloc.get_range (idx); + maybe_add_location_range (loc_range, idx, false); + } + + /* Populate m_fixit_hints, filtering to only those that are in the + same file. */ + for (unsigned int i = 0; i < richloc.get_num_fixit_hints (); i++) + { + const fixit_hint *hint = richloc.get_fixit_hint (i); + if (validate_fixit_hint_p (hint)) + m_fixit_hints.safe_push (hint); + } + + /* Sort m_fixit_hints. */ + m_fixit_hints.qsort (fixit_cmp); + + /* Populate the indicated members. */ + calculate_line_spans (); + calculate_linenum_width (); + calculate_x_offset_display (); +} + + +/* Attempt to add LOC_RANGE to m_layout_ranges, filtering them to + those that we can sanely print. + + ORIGINAL_IDX is the index of LOC_RANGE within its rich_location, + (for use as extrinsic state by label ranges). + + If RESTRICT_TO_CURRENT_LINE_SPANS is true, then LOC_RANGE is also + filtered against this layout instance's current line spans: it + will only be added if the location is fully within the lines + already specified by other locations. + + Return true iff LOC_RANGE was added. */ + +bool +layout::maybe_add_location_range (const location_range *loc_range, + unsigned original_idx, + bool restrict_to_current_line_spans) +{ + gcc_assert (loc_range); + + /* Split the "range" into caret and range information. */ + source_range src_range = get_range_from_loc (m_line_table, loc_range->m_loc); + + /* Expand the various locations. */ + expanded_location start + = linemap_client_expand_location_to_spelling_point + (m_line_table, src_range.m_start, LOCATION_ASPECT_START); + expanded_location finish + = linemap_client_expand_location_to_spelling_point + (m_line_table, src_range.m_finish, LOCATION_ASPECT_FINISH); + expanded_location caret + = linemap_client_expand_location_to_spelling_point + (m_line_table, loc_range->m_loc, LOCATION_ASPECT_CARET); + + /* If any part of the range isn't in the same file as the primary + location of this diagnostic, ignore the range. */ + if (start.file != m_exploc.file) + return false; + if (finish.file != m_exploc.file) + return false; + if (loc_range->m_range_display_kind == SHOW_RANGE_WITH_CARET) + if (caret.file != m_exploc.file) + return false; + + /* Sanitize the caret location for non-primary ranges. */ + if (m_layout_ranges.length () > 0) + if (loc_range->m_range_display_kind == SHOW_RANGE_WITH_CARET) + if (!compatible_locations_p (loc_range->m_loc, m_primary_loc)) + /* Discard any non-primary ranges that can't be printed + sanely relative to the primary location. */ + return false; + + /* If there's no column information, then don't try to print + annotation lines for this range. */ + enum range_display_kind range_display_kind + = loc_range->m_range_display_kind; + if (start.column == 0 + || finish.column == 0 + || caret.column == 0) + range_display_kind = SHOW_LINES_WITHOUT_RANGE; + + /* Everything is now known to be in the correct source file, + but it may require further sanitization. */ + layout_range ri (exploc_with_display_col (m_file_cache, + start, m_char_policy, + LOCATION_ASPECT_START), + exploc_with_display_col (m_file_cache, + finish, m_char_policy, + LOCATION_ASPECT_FINISH), + range_display_kind, + exploc_with_display_col (m_file_cache, + caret, m_char_policy, + LOCATION_ASPECT_CARET), + original_idx, loc_range->m_label); + + /* If we have a range that finishes before it starts (perhaps + from something built via macro expansion), printing the + range is likely to be nonsensical. Also, attempting to do so + breaks assumptions within the printing code (PR c/68473). + Similarly, don't attempt to print ranges if one or both ends + of the range aren't sane to print relative to the + primary location (PR c++/70105). */ + if (start.line > finish.line + || !compatible_locations_p (src_range.m_start, m_primary_loc) + || !compatible_locations_p (src_range.m_finish, m_primary_loc)) + { + /* Is this the primary location? */ + if (m_layout_ranges.length () == 0) + { + /* We want to print the caret for the primary location, but + we must sanitize away m_start and m_finish. */ + ri.m_start = ri.m_caret; + ri.m_finish = ri.m_caret; + } + else + /* This is a non-primary range; ignore it. */ + return false; + } + + /* Potentially filter to just the lines already specified by other + locations. This is for use by gcc_rich_location::add_location_if_nearby. + The layout ctor doesn't use it, and can't because m_line_spans + hasn't been set up at that point. */ + if (restrict_to_current_line_spans) + { + if (!will_show_line_p (start.line)) + return false; + if (!will_show_line_p (finish.line)) + return false; + if (loc_range->m_range_display_kind == SHOW_RANGE_WITH_CARET) + if (!will_show_line_p (caret.line)) + return false; + } + + /* Passed all the tests; add the range to m_layout_ranges so that + it will be printed. */ + m_layout_ranges.safe_push (ri); + return true; +} + +/* Return true iff ROW is within one of the line spans for this layout. */ + +bool +layout::will_show_line_p (linenum_type row) const +{ + for (int line_span_idx = 0; line_span_idx < get_num_line_spans (); + line_span_idx++) + { + const line_span *line_span = get_line_span (line_span_idx); + if (line_span->contains_line_p (row)) + return true; + } + return false; +} + +/* Print a line showing a gap in the line numbers, for showing the boundary + between two line spans. */ + +template<> +void +layout_printer<diagnostics::to_text>::print_gap_in_line_numbering () +{ + gcc_assert (m_layout.m_options.show_line_numbers_p); + + m_text_or_html.emit_text_prefix (); + + for (int i = 0; i < m_layout.get_linenum_width () + 1; i++) + m_text_or_html.add_character ('.'); + + m_text_or_html.add_newline (); +} + +template<> +void +layout_printer<diagnostics::to_html>::print_gap_in_line_numbering () +{ + gcc_assert (m_layout.m_options.show_line_numbers_p); + + m_text_or_html.add_raw_html + ("<tbody class=\"line-span-jump\">\n" + "<tr class=\"line-span-jump-row\">" + "<td class=\"linenum-gap\">[...]</td>" + "<td class=\"source-gap\"/></tr>\n" + "</tbody>\n"); +} + +/* Return true iff we should print a heading when starting the + line span with the given index. */ + +bool +layout::print_heading_for_line_span_index_p (int line_span_idx) const +{ + /* We print a heading for every change of line span, hence for every + line span after the initial one. */ + if (line_span_idx > 0) + return true; + + /* We also do it for the initial span if the primary location of the + diagnostic is in a different span. */ + if (m_exploc.line > (int)get_line_span (0)->m_last_line) + return true; + + return false; +} + +/* Get an expanded_location for the first location of interest within + the given line_span. + Used when printing a heading to indicate a new line span. */ + +expanded_location +layout::get_expanded_location (const line_span *line_span) const +{ + /* Whenever possible, use the caret location. */ + if (line_span->contains_line_p (m_exploc.line)) + return m_exploc; + + /* Otherwise, use the start of the first range that's present + within the line_span. */ + for (unsigned int i = 0; i < m_layout_ranges.length (); i++) + { + const layout_range *lr = &m_layout_ranges[i]; + if (line_span->contains_line_p (lr->m_start.m_line)) + { + expanded_location exploc = m_exploc; + exploc.line = lr->m_start.m_line; + exploc.column = lr->m_start.m_columns[CU_BYTES]; + return exploc; + } + } + + /* Otherwise, use the location of the first fixit-hint present within + the line_span. */ + for (unsigned int i = 0; i < m_fixit_hints.length (); i++) + { + const fixit_hint *hint = m_fixit_hints[i]; + location_t loc = hint->get_start_loc (); + expanded_location exploc = expand_location (loc); + if (line_span->contains_line_p (exploc.line)) + return exploc; + } + + /* It should not be possible to have a line span that didn't + contain any of the layout_range or fixit_hint instances. */ + gcc_unreachable (); + return m_exploc; +} + +/* Determine if HINT is meaningful to print within this layout. */ + +bool +layout::validate_fixit_hint_p (const fixit_hint *hint) +{ + if (LOCATION_FILE (hint->get_start_loc ()) != m_exploc.file) + return false; + if (LOCATION_FILE (hint->get_next_loc ()) != m_exploc.file) + return false; + + return true; +} + +/* Determine the range of lines affected by HINT. + This assumes that HINT has already been filtered by + validate_fixit_hint_p, and so affects the correct source file. */ + +static line_span +get_line_span_for_fixit_hint (const fixit_hint *hint) +{ + gcc_assert (hint); + + int start_line = LOCATION_LINE (hint->get_start_loc ()); + + /* For line-insertion fix-it hints, add the previous line to the + span, to give the user more context on the proposed change. */ + if (hint->ends_with_newline_p ()) + if (start_line > 1) + start_line--; + + return line_span (start_line, + LOCATION_LINE (hint->get_next_loc ())); +} + +/* We want to print the pertinent source code at a diagnostic. The + rich_location can contain multiple locations. This will have been + filtered into m_exploc (the caret for the primary location) and + m_layout_ranges, for those ranges within the same source file. + + We will print a subset of the lines within the source file in question, + as a collection of "spans" of lines. + + This function populates m_line_spans with an ordered, disjoint list of + the line spans of interest. + + Printing a gap between line spans takes one line, so, when printing + line numbers, we allow a gap of up to one line between spans when + merging, since it makes more sense to print the source line rather than a + "gap-in-line-numbering" line. When not printing line numbers, it's + better to be more explicit about what's going on, so keeping them as + separate spans is preferred. + + For example, if the primary range is on lines 8-10, with secondary ranges + covering lines 5-6 and lines 13-15: + + 004 + 005 |RANGE 1 + 006 |RANGE 1 + 007 + 008 |PRIMARY RANGE + 009 |PRIMARY CARET + 010 |PRIMARY RANGE + 011 + 012 + 013 |RANGE 2 + 014 |RANGE 2 + 015 |RANGE 2 + 016 + + With line numbering on, we want two spans: lines 5-10 and lines 13-15. + + With line numbering off (with span headers), we want three spans: lines 5-6, + lines 8-10, and lines 13-15. */ + +void +layout::calculate_line_spans () +{ + /* This should only be called once, by the ctor. */ + gcc_assert (m_line_spans.length () == 0); + + /* Populate tmp_spans with individual spans, for each of + m_exploc, and for m_layout_ranges. */ + auto_vec<line_span> tmp_spans (1 + m_layout_ranges.length ()); + tmp_spans.safe_push (line_span (m_exploc.line, m_exploc.line)); + for (unsigned int i = 0; i < m_layout_ranges.length (); i++) + { + const layout_range *lr = &m_layout_ranges[i]; + gcc_assert (lr->m_start.m_line <= lr->m_finish.m_line); + tmp_spans.safe_push (line_span (lr->m_start.m_line, + lr->m_finish.m_line)); + } + + /* Also add spans for any fix-it hints, in case they cover other lines. */ + for (unsigned int i = 0; i < m_fixit_hints.length (); i++) + { + const fixit_hint *hint = m_fixit_hints[i]; + gcc_assert (hint); + tmp_spans.safe_push (get_line_span_for_fixit_hint (hint)); + } + + /* Sort them. */ + tmp_spans.qsort(line_span::comparator); + + /* Now iterate through tmp_spans, copying into m_line_spans, and + combining where possible. */ + gcc_assert (tmp_spans.length () > 0); + m_line_spans.safe_push (tmp_spans[0]); + for (unsigned int i = 1; i < tmp_spans.length (); i++) + { + line_span *current = &m_line_spans[m_line_spans.length () - 1]; + const line_span *next = &tmp_spans[i]; + gcc_assert (next->m_first_line >= current->m_first_line); + const int merger_distance = m_options.show_line_numbers_p ? 1 : 0; + if ((linenum_arith_t)next->m_first_line + <= (linenum_arith_t)current->m_last_line + 1 + merger_distance) + { + /* We can merge them. */ + if (next->m_last_line > current->m_last_line) + current->m_last_line = next->m_last_line; + } + else + { + /* No merger possible. */ + m_line_spans.safe_push (*next); + } + } + + /* Verify the result, in m_line_spans. */ + gcc_assert (m_line_spans.length () > 0); + for (unsigned int i = 1; i < m_line_spans.length (); i++) + { + const line_span *prev = &m_line_spans[i - 1]; + const line_span *next = &m_line_spans[i]; + /* The individual spans must be sane. */ + gcc_assert (prev->m_first_line <= prev->m_last_line); + gcc_assert (next->m_first_line <= next->m_last_line); + /* The spans must be ordered. */ + gcc_assert (prev->m_first_line < next->m_first_line); + /* There must be a gap of at least one line between separate spans. */ + gcc_assert ((prev->m_last_line + 1) < next->m_first_line); + } +} + +/* Determine how many display columns need to be reserved for line numbers, + based on the largest line number that will be needed, and populate + m_linenum_width. */ + +void +layout::calculate_linenum_width () +{ + gcc_assert (m_line_spans.length () > 0); + const line_span *last_span = &m_line_spans[m_line_spans.length () - 1]; + int highest_line = last_span->m_last_line; + if (highest_line < 0) + highest_line = 0; + m_linenum_width = diagnostics::num_digits (highest_line); + /* If we're showing jumps in the line-numbering, allow at least 3 chars. */ + if (m_line_spans.length () > 1) + m_linenum_width = MAX (m_linenum_width, 3); + /* If there's a minimum margin width, apply it (subtracting 1 for the space + after the line number. */ + m_linenum_width = MAX (m_linenum_width, m_options.min_margin_width - 1); +} + +/* Calculate m_x_offset_display, which improves readability in case the source + line of interest is longer than the user's display. All lines output will be + shifted to the left (so that their beginning is no longer displayed) by + m_x_offset_display display columns, so that the caret is in a reasonable + location. */ + +void +layout::calculate_x_offset_display () +{ + m_x_offset_display = 0; + + const int max_width = m_options.max_width; + if (!max_width) + { + /* Nothing to do, the width is not capped. */ + return; + } + + const diagnostics::char_span line + = m_file_cache.get_source_line (m_exploc.file, + m_exploc.line); + if (!line) + { + /* Nothing to do, we couldn't find the source line. */ + return; + } + int caret_display_column = m_exploc.m_display_col; + const int line_bytes + = get_line_bytes_without_trailing_whitespace (line.get_buffer (), + line.length ()); + int eol_display_column + = cpp_display_width (line.get_buffer (), line_bytes, m_char_policy); + if (caret_display_column > eol_display_column + || !caret_display_column) + { + /* This does not make sense, so don't try to do anything in this case. */ + return; + } + + /* Adjust caret and eol positions to include the left margin. If we are + outputting line numbers, then the left margin is equal to m_linenum_width + plus three for the " | " which follows it. Otherwise the left margin width + is equal to 1, because layout::print_source_line() will prefix each line + with a space. */ + const int source_display_cols = eol_display_column; + int left_margin_size = 1; + if (m_options.show_line_numbers_p) + left_margin_size = m_linenum_width + 3; + caret_display_column += left_margin_size; + eol_display_column += left_margin_size; + + if (eol_display_column <= max_width) + { + /* Nothing to do, everything fits in the display. */ + return; + } + + /* The line is too long for the display. Calculate an offset such that the + caret is not too close to the right edge of the screen. It will be + CARET_LINE_MARGIN display columns from the right edge, unless it is closer + than that to the end of the source line anyway. */ + int right_margin_size = CARET_LINE_MARGIN; + right_margin_size = MIN (eol_display_column - caret_display_column, + right_margin_size); + if (right_margin_size + left_margin_size >= max_width) + { + /* The max_width is very small, so anything we try to do will not be very + effective; just punt in this case and output with no offset. */ + return; + } + const int max_caret_display_column = max_width - right_margin_size; + if (caret_display_column > max_caret_display_column) + { + m_x_offset_display = caret_display_column - max_caret_display_column; + /* Make sure we don't offset the line into oblivion. */ + static const int min_cols_visible = 2; + if (source_display_cols - m_x_offset_display < min_cols_visible) + m_x_offset_display = 0; + } +} + +/* Print line ROW of source code, potentially colorized at any ranges, and + return the line bounds. LINE is the source line (not necessarily + 0-terminated) and LINE_BYTES is its length in bytes. In order to handle both + colorization and tab expansion, this function tracks the line position in + both byte and display column units. */ + +template<typename TextOrHtml> +line_bounds +layout_printer<TextOrHtml>::print_source_line (linenum_type row, + const char *line, + int line_bytes) +{ + m_text_or_html.colorize_text_ensure_normal (); + m_text_or_html.push_html_tag ("tr", true); + m_text_or_html.emit_text_prefix (); + if (m_layout.m_options.show_line_numbers_p) + { + m_text_or_html.push_html_tag_with_class ("td", "linenum", true); + int width = diagnostics::num_digits (row); + for (int i = 0; i < m_layout.get_linenum_width () - width; i++) + m_text_or_html.add_space (); + char buf[20]; + sprintf (buf, "%i", row); + m_text_or_html.add_text (buf); + if (TextOrHtml::is_text ()) + m_text_or_html.add_text (" |"); + m_text_or_html.pop_html_tag ("td"); + } + + m_text_or_html.push_html_tag_with_class ("td", "left-margin", true); + print_leftmost_column (); + m_text_or_html.pop_html_tag ("td"); + + /* We will stop printing the source line at any trailing whitespace. */ + line_bytes = get_line_bytes_without_trailing_whitespace (line, + line_bytes); + + /* This object helps to keep track of which display column we are at, which is + necessary for computing the line bounds in display units, for doing + tab expansion, and for implementing m_x_offset_display. */ + cpp_display_width_computation dw (line, line_bytes, m_layout.m_char_policy); + + m_text_or_html.push_html_tag_with_class ("td", "source", true); + + /* Skip the first m_x_offset_display display columns. In case the leading + portion that will be skipped ends with a character with wcwidth > 1, then + it is possible we skipped too much, so account for that by padding with + spaces. Note that this does the right thing too in case a tab was the last + character to be skipped over; the tab is effectively replaced by the + correct number of trailing spaces needed to offset by the desired number of + display columns. */ + for (int skipped_display_cols + = dw.advance_display_cols (m_layout.m_x_offset_display); + skipped_display_cols > m_layout.m_x_offset_display; --skipped_display_cols) + m_text_or_html.add_space (); + + /* Print the line and compute the line_bounds. */ + line_bounds lbounds; + while (!dw.done ()) + { + /* Assuming colorization is enabled for the caret and underline + characters, we may also colorize the associated characters + within the source line. + + For frontends that generate range information, we color the + associated characters in the source line the same as the + carets and underlines in the annotation line, to make it easier + for the reader to see the pertinent code. + + For frontends that only generate carets, we don't colorize the + characters above them, since this would look strange (e.g. + colorizing just the first character in a token). */ + if (m_layout.m_options.colorize_source_p) + { + bool in_range_p; + point_state state; + const int start_byte_col = dw.bytes_processed () + 1; + in_range_p = m_layout.get_state_at_point (row, start_byte_col, + 0, INT_MAX, + CU_BYTES, + &state); + if (in_range_p) + set_in_range (state.range_idx); + else + set_outside_range (); + } + + /* Get the display width of the next character to be output, expanding + tabs and replacing some control bytes with spaces as necessary. */ + const char *c = dw.next_byte (); + const int start_disp_col = dw.display_cols_processed () + 1; + cpp_decoded_char cp; + const int this_display_width = dw.process_next_codepoint (&cp); + if (*c == '\t') + { + /* The returned display width is the number of spaces into which the + tab should be expanded. */ + for (int i = 0; i != this_display_width; ++i) + m_text_or_html.add_space (); + continue; + } + + /* We have a (possibly multibyte) character to output; update the line + bounds if it is not whitespace. */ + if (*c != ' ') + { + lbounds.m_last_non_ws_disp_col = dw.display_cols_processed (); + if (lbounds.m_first_non_ws_disp_col == INT_MAX) + lbounds.m_first_non_ws_disp_col = start_disp_col; + } + + /* Output the character. */ + m_text_or_html.print_decoded_char (m_layout.m_char_policy, cp); + c = dw.next_byte (); + } + set_outside_range (); + end_line (); + return lbounds; +} + +/* Determine if we should print an annotation line for ROW. + i.e. if any of m_layout_ranges contains ROW. */ + +bool +layout::should_print_annotation_line_p (linenum_type row) const +{ + layout_range *range; + int i; + FOR_EACH_VEC_ELT (m_layout_ranges, i, range) + { + if (range->m_range_display_kind == SHOW_LINES_WITHOUT_RANGE) + return false; + if (range->intersects_line_p (row)) + return true; + } + return false; +} + +/* Print the leftmost column after the margin, which is used for showing + links between labels (e.g. for CFG edges in execution paths). */ + +template<typename TextOrHtml> +void +layout_printer<TextOrHtml>::print_leftmost_column () +{ + if (!get_options ().show_event_links_p) + gcc_assert (m_link_lhs_state == link_lhs_state::none); + + switch (m_link_lhs_state) + { + default: + gcc_unreachable (); + case link_lhs_state::none: + m_text_or_html.add_space (); + break; + case link_lhs_state::rewinding_to_lhs: + { + m_text_or_html.colorize_text_for_cfg_edge (); + const cppchar_t ch = get_theme ().get_cppchar + (text_art::theme::cell_kind::CFG_FROM_LEFT_TO_DOWN); + m_text_or_html.add_character (ch); + m_text_or_html.colorize_text_ensure_normal (); + } + break; + case link_lhs_state::at_lhs: + { + m_text_or_html.colorize_text_for_cfg_edge (); + const cppchar_t ch = get_theme ().get_cppchar + (text_art::theme::cell_kind::CFG_DOWN); + m_text_or_html.add_character (ch); + m_text_or_html.colorize_text_ensure_normal (); + } + break; + case link_lhs_state::indenting_to_dest: + { + m_text_or_html.colorize_text_for_cfg_edge (); + const cppchar_t ch = get_theme ().get_cppchar + (text_art::theme::cell_kind::CFG_FROM_DOWN_TO_RIGHT); + m_text_or_html.add_character (ch); + m_text_or_html.colorize_text_ensure_normal (); + } + break; + } +} + +/* Begin an annotation line for either text or html output + + If m_show_line_numbers_p, print the left margin, which is empty + for annotation lines. + After any left margin, print a leftmost column, which is used for + showing links between labels (e.g. for CFG edges in execution paths). + + For text output, this also first prints the text prefix. + For html output, this also pushes <tr> and <td> open tags, where the + <td> is for the coming annotations. */ + +template<typename TextOrHtml> +void +layout_printer<TextOrHtml>::start_annotation_line (enum margin_kind margin) +{ + m_text_or_html.emit_text_prefix (); + m_text_or_html.push_html_tag ("tr", true); + + char margin_char = (margin == margin_kind::insertion + ? '+' + : ' '); + + if (get_options ().show_line_numbers_p) + { + /* Print the margin. If MARGIN_CHAR != ' ', then print up to 3 + of it, right-aligned, padded with spaces. */ + m_text_or_html.push_html_tag_with_class ("td", "linenum", true); + int i; + for (i = 0; i < m_layout.m_linenum_width - 3; i++) + m_text_or_html.add_space (); + for (; i < m_layout.m_linenum_width; i++) + m_text_or_html.add_character (margin_char); + if (TextOrHtml::is_text ()) + m_text_or_html.add_text (" |"); + m_text_or_html.pop_html_tag ("td"); + } + + m_text_or_html.push_html_tag_with_class ("td", "left-margin", true); + if (margin == margin_kind::insertion) + m_text_or_html.add_character (margin_char); + else + print_leftmost_column (); + m_text_or_html.pop_html_tag ("td"); + + m_text_or_html.push_html_tag_with_class ("td", + (margin == margin_kind::ruler + ? "ruler" + : "annotation"), + true); +} + +/* End a source or annotation line: text implementation. + Reset any colorization and emit a newline. */ + +template<> +void +layout_printer<diagnostics::to_text>::end_line () +{ + m_text_or_html.colorize_text_ensure_normal (); + m_text_or_html.add_newline (); +} + +/* End a source or annotation line: HTML implementation. + Close the <td> and <tr> tags. */ + +template<> +void +layout_printer<diagnostics::to_html>::end_line () +{ + m_text_or_html.pop_html_tag ("td"); + m_text_or_html.pop_html_tag ("tr"); +} + +/* Handle the various transitions between being-in-range and + not-being-in-a-range, and between ranges. */ + +template<typename TextOrHtml> +void +layout_printer<TextOrHtml>::set_in_range (int range_idx) +{ + if (m_was_in_range_p) + { + if (m_last_range_idx != range_idx) + { + /* transition between ranges. */ + end_label (m_last_range_idx, false); + begin_label (range_idx, false); + } + } + else + { + /* transition from "not in a range" to "in a range". */ + begin_label (range_idx, false); + m_was_in_range_p = true; + } + m_last_range_idx = range_idx; +} + +template<typename TextOrHtml> +void +layout_printer<TextOrHtml>::set_outside_range () +{ + if (m_was_in_range_p) + /* transition from "in a range" to "not in a range". */ + end_label (m_last_range_idx, false); + m_was_in_range_p = false; +} + +/* Print a line consisting of the caret/underlines for the given + source line. */ + +template<typename TextOrHtml> +void +layout_printer<TextOrHtml>::print_annotation_line (linenum_type row, + const line_bounds lbounds) +{ + int x_bound = m_layout.get_x_bound_for_row (row, + m_layout.m_exploc.m_display_col, + lbounds.m_last_non_ws_disp_col); + + start_annotation_line (margin_kind::normal); + + for (int column = 1 + m_layout.m_x_offset_display; column < x_bound; column++) + { + bool in_range_p; + point_state state; + in_range_p = m_layout.get_state_at_point (row, column, + lbounds.m_first_non_ws_disp_col, + lbounds.m_last_non_ws_disp_col, + CU_DISPLAY_COLS, + &state); + if (in_range_p) + set_in_range (state.range_idx); + else + set_outside_range (); + + if (in_range_p) + { + /* Within a range. Draw either the caret or an underline. */ + if (state.draw_caret_p) + { + /* Draw the caret. */ + char caret_char; + if (state.range_idx < rich_location::STATICALLY_ALLOCATED_RANGES) + caret_char = get_options ().caret_chars[state.range_idx]; + else + caret_char = '^'; + m_text_or_html.add_character (caret_char); + } + else + m_text_or_html.add_character ('~'); + } + else + { + /* Not in a range. */ + m_text_or_html.add_character (' '); + } + } + + set_outside_range (); + + end_line (); +} + +/* A version of label_text that can live inside a vec. + Requires manual cleanup via maybe_free. */ + +struct pod_label_text +{ + pod_label_text () + : m_buffer (nullptr), m_caller_owned (false) + {} + + pod_label_text (label_text &&other) + : m_buffer (const_cast<char*> (other.get ())), + m_caller_owned (other.is_owner ()) + { + other.release (); + } + + void maybe_free () + { + if (m_caller_owned) + free (m_buffer); + } + + char *m_buffer; + bool m_caller_owned; +}; + +/* Implementation detail of layout::print_any_labels. + + A label within the given row of source. */ + +class line_label +{ +public: + line_label (unsigned original_range_idx, + int state_idx, int column, + label_text text, + bool has_in_edge, + bool has_out_edge) + : m_original_range_idx (original_range_idx), + m_state_idx (state_idx), m_column (column), + m_text (std::move (text)), m_label_line (0), m_has_vbar (true), + m_has_in_edge (has_in_edge), + m_has_out_edge (has_out_edge) + { + /* Using styled_string rather than cpp_display_width here + lets us skip SGR formatting characters for color and URLs. + It doesn't handle tabs and unicode escaping, but we don't + expect to see either of those in labels. */ + text_art::style_manager sm; + text_art::styled_string str (sm, m_text.m_buffer); + m_display_width = str.calc_canvas_width (); + } + + /* Sorting is primarily by column, then by state index. */ + static int comparator (const void *p1, const void *p2) + { + const line_label *ll1 = (const line_label *)p1; + const line_label *ll2 = (const line_label *)p2; + int column_cmp = compare (ll1->m_column, ll2->m_column); + if (column_cmp) + return column_cmp; + /* Order by reverse state index, so that labels are printed + in order of insertion into the rich_location when the + sorted list is walked backwards. */ + return -compare (ll1->m_state_idx, ll2->m_state_idx); + } + + unsigned m_original_range_idx; + int m_state_idx; + int m_column; + pod_label_text m_text; + size_t m_display_width; + int m_label_line; + bool m_has_vbar; + bool m_has_in_edge; + bool m_has_out_edge; +}; + +/* Implementations of layout_printer::{begin,end}_label for + to_text and to_html. + + RANGE_IDX is the index of the range within the rich_location. + + IS_LABEL_TEXT is true for the text of the label, + false when quoting the source code, underlining the source + code, and for the vertical bars connecting the underlines + to the text of the label. */ + +template<> +void +layout_printer<diagnostics::to_text>::begin_label (int range_idx, + bool is_label_text) +{ + /* Colorize the text, unless it's for labels for events in a + diagnostic path. */ + if (is_label_text && m_is_diagnostic_path) + return; + + gcc_assert (m_text_or_html.m_colorizer); + m_text_or_html.m_colorizer->set_range (range_idx); +} + +template<> +void +layout_printer<diagnostics::to_html>::begin_label (int range_idx, + bool is_label_text) +{ + if (is_label_text && m_text_or_html.m_html_label_writer) + m_text_or_html.m_html_label_writer->begin_label (); + + if (const char *highlight_color + = m_text_or_html.get_highlight_color_for_range_idx (range_idx)) + m_text_or_html.m_xp.push_tag_with_class ("span", highlight_color); +} + +template<> +void +layout_printer<diagnostics::to_text>::end_label (int, bool) +{ + m_text_or_html.colorize_text_ensure_normal (); +} + +template<> +void +layout_printer<diagnostics::to_html>::end_label (int range_idx, + bool is_label_text) +{ + if (m_text_or_html.get_highlight_color_for_range_idx (range_idx)) + m_text_or_html.m_xp.pop_tag ("span"); + + if (is_label_text && m_text_or_html.m_html_label_writer) + m_text_or_html.m_html_label_writer->end_label (); +} + +/* Print any labels in this row. */ +template <typename TextOrHtml> +void +layout_printer<TextOrHtml>::print_any_labels (linenum_type row) +{ + int i; + auto_vec<line_label> labels; + + /* Gather the labels that are to be printed into "labels". */ + { + layout_range *range; + FOR_EACH_VEC_ELT (m_layout.m_layout_ranges, i, range) + { + /* Most ranges don't have labels, so reject this first. */ + if (range->m_label == nullptr) + continue; + + /* The range's caret must be on this line. */ + if (range->m_caret.m_line != row) + continue; + + /* Reject labels that aren't fully visible due to clipping + by m_x_offset_display. */ + const int disp_col = range->m_caret.m_columns[CU_DISPLAY_COLS]; + if (disp_col <= m_layout.m_x_offset_display) + continue; + + label_text text; + text = range->m_label->get_text (range->m_original_idx); + + /* Allow for labels that return nullptr from their get_text + implementation (so e.g. such labels can control their own + visibility). */ + if (text.get () == nullptr) + continue; + + labels.safe_push (line_label (range->m_original_idx, + i, disp_col, std::move (text), + range->has_in_edge (), + range->has_out_edge ())); + } + } + + /* Bail out if there are no labels on this row. */ + if (labels.length () == 0) + return; + + /* Sort them. */ + labels.qsort(line_label::comparator); + + /* Figure out how many "label lines" we need, and which + one each label is printed in. + + For example, if the labels aren't too densely packed, + we can fit them on the same line, giving two "label lines": + + foo + bar + ~~~ ~~~ + | | : label line 0 + l0 l1 : label line 1 + + If they would touch each other or overlap, then we need + additional "label lines": + + foo + bar + ~~~ ~~~ + | | : label line 0 + | label 1 : label line 1 + label 0 : label line 2 + + Place the final label on label line 1, and work backwards, adding + label lines as needed. + + If multiple labels are at the same place, put them on separate + label lines: + + foo + bar + ^ : label line 0 + | : label line 1 + label 0 : label line 2 + label 1 : label line 3. */ + + int max_label_line = 1; + int label_line_with_in_edge = -1; + { + int next_column = INT_MAX; + line_label *label; + FOR_EACH_VEC_ELT_REVERSE (labels, i, label) + { + /* Would this label "touch" or overlap the next label? */ + if (label->m_column + label->m_display_width >= (size_t)next_column) + { + max_label_line++; + + /* If we've already seen labels with the same column, suppress the + vertical bar for subsequent ones in this backwards iteration; + hence only the one with the highest label_line has m_has_vbar set. */ + if (label->m_column == next_column) + label->m_has_vbar = false; + } + + label->m_label_line = max_label_line; + if (get_options ().show_event_links_p) + if (label->m_has_in_edge) + label_line_with_in_edge = max_label_line; + next_column = label->m_column; + } + } + + gcc_assert (labels.length () > 0); + + /* Print the "label lines". For each label within the line, print + either a vertical bar ('|') for the labels that are lower down, or the + labels themselves once we've reached their line. */ + { + for (int label_line = 0; label_line <= max_label_line; label_line++) + { + if (label_line == label_line_with_in_edge) + { + gcc_assert (get_options ().show_event_links_p); + m_link_lhs_state = link_lhs_state::indenting_to_dest; + } + start_annotation_line (margin_kind::normal); + + int column = 1 + m_layout.m_x_offset_display; + line_label *label; + FOR_EACH_VEC_ELT (labels, i, label) + { + if (label_line > label->m_label_line) + /* We've printed all the labels for this label line. */ + break; + + if (label_line == label->m_label_line) + { + gcc_assert (column <= label->m_column); + + if (label_line == label_line_with_in_edge) + { + /* Print a prefix showing an incoming + link from another label. + .|+----------->(10) ...to here + . ^~~~~~~~~~~~~ + . this text. */ + gcc_assert (get_options ().show_event_links_p); + m_text_or_html.colorize_text_for_cfg_edge (); + const cppchar_t right= get_theme ().get_cppchar + (text_art::theme::cell_kind::CFG_RIGHT); + while (column < label->m_column - 1) + { + m_text_or_html.add_character (right); + column++; + } + if (column == label->m_column - 1) + { + m_text_or_html.add_character ('>'); + column++; + } + m_text_or_html.colorize_text_ensure_normal (); + m_link_lhs_state = link_lhs_state::none; + label_line_with_in_edge = -1; + } + else + move_to_column (&column, label->m_column, true); + gcc_assert (column == label->m_column); + + begin_label (label->m_state_idx, true); + m_text_or_html.add_text (label->m_text.m_buffer); + end_label (label->m_state_idx, true); + + column += label->m_display_width; + if (get_options ().show_event_links_p && label->m_has_out_edge) + { + /* Print a suffix showing the start of a linkage + to another label e.g. " ->-+" which will be the + first part of e.g. + . (9) following ‘false’ branch... ->-+ <- HERE + . | + . | + . */ + const cppchar_t right= get_theme ().get_cppchar + (text_art::theme::cell_kind::CFG_RIGHT); + const cppchar_t from_right_to_down= get_theme ().get_cppchar + (text_art::theme::cell_kind::CFG_FROM_RIGHT_TO_DOWN); + m_text_or_html.colorize_text_for_cfg_edge (); + m_text_or_html.add_space (); + m_text_or_html.add_character (right); + m_text_or_html.add_character ('>'); + m_text_or_html.add_character (right); + m_text_or_html.add_character (from_right_to_down); + m_text_or_html.colorize_text_ensure_normal (); + column += 5; + m_link_rhs_column = column - 1; + } + } + else if (label->m_has_vbar) + { + gcc_assert (column <= label->m_column); + move_to_column (&column, label->m_column, true); + begin_label (label->m_state_idx, false); + m_text_or_html.add_character ('|'); + end_label (label->m_state_idx, false); + column++; + } + } + + /* If we have a vertical link line on the RHS, print the + '|' on this annotation line after the labels. */ + if (m_link_rhs_column != -1 && column < m_link_rhs_column) + { + move_to_column (&column, m_link_rhs_column, true); + m_text_or_html.colorize_text_for_cfg_edge (); + const cppchar_t down= get_theme ().get_cppchar + (text_art::theme::cell_kind::CFG_DOWN); + m_text_or_html.add_character (down); + m_text_or_html.colorize_text_ensure_normal (); + } + + end_line (); + } + } + + /* If we have a vertical link line on the RHS, print a trailing + annotation line showing the vertical line. */ + if (m_link_rhs_column != -1) + { + int column = 1 + m_layout.m_x_offset_display; + start_annotation_line (margin_kind::normal); + move_to_column (&column, m_link_rhs_column, true); + m_text_or_html.colorize_text_for_cfg_edge (); + const cppchar_t down= get_theme ().get_cppchar + (text_art::theme::cell_kind::CFG_DOWN); + m_text_or_html.add_character (down); + end_line (); + } + + /* Clean up. */ + { + line_label *label; + FOR_EACH_VEC_ELT (labels, i, label) + label->m_text.maybe_free (); + } +} + +/* If there are any fixit hints inserting new lines before source line ROW, + print them. + + They are printed on lines of their own, before the source line + itself, with a leading '+'. */ + +template <typename TextOrHtml> +void +layout_printer<TextOrHtml>::print_leading_fixits (linenum_type row) +{ + for (unsigned int i = 0; i < m_layout.m_fixit_hints.length (); i++) + { + const fixit_hint *hint = m_layout.m_fixit_hints[i]; + + if (!hint->ends_with_newline_p ()) + /* Not a newline fixit; print it in print_trailing_fixits. */ + continue; + + gcc_assert (hint->insertion_p ()); + + if (hint->affects_line_p (m_layout.m_line_table, + m_layout.m_exploc.file, + row)) + { + /* Printing the '+' with normal colorization + and the inserted line with "insert" colorization + helps them stand out from each other, and from + the surrounding text. */ + m_text_or_html.colorize_text_ensure_normal (); + start_annotation_line (margin_kind::insertion); + m_text_or_html.colorize_text_for_fixit_insert (); + /* Print all but the trailing newline of the fix-it hint. + We have to print the newline separately to avoid + getting additional pp prefixes printed. */ + for (size_t i = 0; i < hint->get_length () - 1; i++) + m_text_or_html.add_character (hint->get_string ()[i]); + end_line (); + } + } +} + +/* Subroutine of layout::print_trailing_fixits. + + Determine if the annotation line printed for LINE contained + the exact range from START_COLUMN to FINISH_COLUMN (in display units). */ + +bool +layout::annotation_line_showed_range_p (linenum_type line, int start_column, + int finish_column) const +{ + layout_range *range; + int i; + FOR_EACH_VEC_ELT (m_layout_ranges, i, range) + if (range->m_start.m_line == line + && range->m_start.m_columns[CU_DISPLAY_COLS] == start_column + && range->m_finish.m_line == line + && range->m_finish.m_columns[CU_DISPLAY_COLS] == finish_column) + return true; + return false; +} + +/* Classes for printing trailing fix-it hints i.e. those that + don't add new lines. + + For insertion, these can look like: + + new_text + + For replacement, these can look like: + + ------------- : underline showing affected range + new_text + + For deletion, these can look like: + + ------------- : underline showing affected range + + This can become confusing if they overlap, and so we need + to do some preprocessing to decide what to print. + We use the list of fixit_hint instances affecting the line + to build a list of "correction" instances, and print the + latter. + + For example, consider a set of fix-its for converting + a C-style cast to a C++ const_cast. + + Given: + + ..000000000111111111122222222223333333333. + ..123456789012345678901234567890123456789. + foo *f = (foo *)ptr->field; + ^~~~~ + + and the fix-it hints: + - replace col 10 (the open paren) with "const_cast<" + - replace col 16 (the close paren) with "> (" + - insert ")" before col 27 + + then we would get odd-looking output: + + foo *f = (foo *)ptr->field; + ^~~~~ + - + const_cast< + - + > ( ) + + It would be better to detect when fixit hints are going to + overlap (those that require new lines), and to consolidate + the printing of such fixits, giving something like: + + foo *f = (foo *)ptr->field; + ^~~~~ + ----------------- + const_cast<foo *> (ptr->field) + + This works by detecting when the printing would overlap, and + effectively injecting no-op replace hints into the gaps between + such fix-its, so that the printing joins up. + + In the above example, the overlap of: + - replace col 10 (the open paren) with "const_cast<" + and: + - replace col 16 (the close paren) with "> (" + is fixed by injecting a no-op: + - replace cols 11-15 with themselves ("foo *") + and consolidating these, making: + - replace cols 10-16 with "const_cast<" + "foo *" + "> (" + i.e.: + - replace cols 10-16 with "const_cast<foo *> (" + + This overlaps with the final fix-it hint: + - insert ")" before col 27 + and so we repeat the consolidation process, by injecting + a no-op: + - replace cols 17-26 with themselves ("ptr->field") + giving: + - replace cols 10-26 with "const_cast<foo *> (" + "ptr->field" + ")" + i.e.: + - replace cols 10-26 with "const_cast<foo *> (ptr->field)" + + and is thus printed as desired. */ + +/* A range of (byte or display) columns within a line. */ + +class column_range +{ +public: + column_range (int start_, int finish_) : start (start_), finish (finish_) + { + gcc_assert (valid_p (start, finish)); + } + + bool operator== (const column_range &other) const + { + return start == other.start && finish == other.finish; + } + + static bool valid_p (int start, int finish) + { + /* We must have either a range, or an insertion. */ + return (start <= finish || finish == start - 1); + } + + int start; + int finish; +}; + +/* Get the range of bytes or display columns that HINT would affect. */ +static column_range +get_affected_range (diagnostics::file_cache &fc, + const cpp_char_column_policy &policy, + const fixit_hint *hint, enum column_unit col_unit) +{ + expanded_location exploc_start = expand_location (hint->get_start_loc ()); + expanded_location exploc_finish = expand_location (hint->get_next_loc ()); + --exploc_finish.column; + + int start_column; + int finish_column; + if (col_unit == CU_DISPLAY_COLS) + { + start_column = location_compute_display_column (fc, exploc_start, policy); + if (hint->insertion_p ()) + finish_column = start_column - 1; + else + finish_column + = location_compute_display_column (fc, exploc_finish, policy); + } + else + { + start_column = exploc_start.column; + finish_column = exploc_finish.column; + } + return column_range (start_column, finish_column); +} + +/* Get the range of display columns that would be printed for HINT. */ + +static column_range +get_printed_columns (diagnostics::file_cache &fc, + const cpp_char_column_policy &policy, + const fixit_hint *hint) +{ + expanded_location exploc = expand_location (hint->get_start_loc ()); + int start_column = location_compute_display_column (fc, exploc, policy); + int hint_width = cpp_display_width (hint->get_string (), hint->get_length (), + policy); + int final_hint_column = start_column + hint_width - 1; + if (hint->insertion_p ()) + { + return column_range (start_column, final_hint_column); + } + else + { + exploc = expand_location (hint->get_next_loc ()); + --exploc.column; + int finish_column = location_compute_display_column (fc, exploc, policy); + return column_range (start_column, + MAX (finish_column, final_hint_column)); + } +} + +/* A correction on a particular line. + This describes a plan for how to print one or more fixit_hint + instances that affected the line, potentially consolidating hints + into corrections to make the result easier for the user to read. */ + +class correction +{ +public: + correction (column_range affected_bytes, + column_range affected_columns, + column_range printed_columns, + const char *new_text, size_t new_text_len, + const cpp_char_column_policy &policy) + : m_affected_bytes (affected_bytes), + m_affected_columns (affected_columns), + m_printed_columns (printed_columns), + m_text (xstrdup (new_text)), + m_byte_length (new_text_len), + m_policy (policy), + m_alloc_sz (new_text_len + 1) + { + compute_display_cols (); + } + + ~correction () { free (m_text); } + + bool insertion_p () const + { + return m_affected_bytes.start == m_affected_bytes.finish + 1; + } + + void ensure_capacity (size_t len); + void ensure_terminated (); + + void compute_display_cols () + { + m_display_cols = cpp_display_width (m_text, m_byte_length, m_policy); + } + + void overwrite (int dst_offset, const diagnostics::char_span &src_span) + { + gcc_assert (dst_offset >= 0); + gcc_assert (dst_offset + src_span.length () < m_alloc_sz); + memcpy (m_text + dst_offset, src_span.get_buffer (), + src_span.length ()); + } + + /* If insert, then start: the column before which the text + is to be inserted, and finish is offset by the length of + the replacement. + If replace, then the range of columns affected. */ + column_range m_affected_bytes; + column_range m_affected_columns; + + /* If insert, then start: the column before which the text + is to be inserted, and finish is offset by the length of + the replacement. + If replace, then the range of columns affected. */ + column_range m_printed_columns; + + /* The text to be inserted/used as replacement. */ + char *m_text; + size_t m_byte_length; /* Not including null-terminator. */ + int m_display_cols; + const cpp_char_column_policy &m_policy; + size_t m_alloc_sz; +}; + +/* Ensure that m_text can hold a string of length LEN + (plus 1 for 0-termination). */ + +void +correction::ensure_capacity (size_t len) +{ + /* Allow 1 extra byte for 0-termination. */ + if (m_alloc_sz < (len + 1)) + { + size_t new_alloc_sz = (len + 1) * 2; + m_text = (char *)xrealloc (m_text, new_alloc_sz); + m_alloc_sz = new_alloc_sz; + } +} + +/* Ensure that m_text is 0-terminated. */ + +void +correction::ensure_terminated () +{ + /* 0-terminate the buffer. */ + gcc_assert (m_byte_length < m_alloc_sz); + m_text[m_byte_length] = '\0'; +} + +/* A list of corrections affecting a particular line. + This is used by layout::print_trailing_fixits for planning + how to print the fix-it hints affecting the line. */ + +class line_corrections +{ +public: + line_corrections (diagnostics::file_cache &fc, + const char_display_policy &policy, + const char *filename, + linenum_type row) + : m_file_cache (fc), + m_policy (policy), m_filename (filename), m_row (row) + {} + ~line_corrections (); + + void add_hint (const fixit_hint *hint); + + diagnostics::file_cache &m_file_cache; + const char_display_policy &m_policy; + const char *m_filename; + linenum_type m_row; + auto_vec <correction *> m_corrections; +}; + +/* struct line_corrections. */ + +line_corrections::~line_corrections () +{ + unsigned i; + correction *c; + FOR_EACH_VEC_ELT (m_corrections, i, c) + delete c; +} + +/* A struct wrapping a particular source line, allowing + run-time bounds-checking of accesses in a checked build. */ + +class source_line +{ +public: + source_line (diagnostics::file_cache &fc, const char *filename, int line); + + diagnostics::char_span as_span () + { + return diagnostics::char_span (chars, width); + } + + const char *chars; + int width; +}; + +/* source_line's ctor. */ + +source_line::source_line (diagnostics::file_cache &fc, + const char *filename, + int line) +{ + diagnostics::char_span span = fc.get_source_line (filename, line); + chars = span.get_buffer (); + width = span.length (); +} + +/* Add HINT to the corrections for this line. + Attempt to consolidate nearby hints so that they will not + overlap with printed. */ + +void +line_corrections::add_hint (const fixit_hint *hint) +{ + column_range affected_bytes + = get_affected_range (m_file_cache, m_policy, hint, CU_BYTES); + column_range affected_columns + = get_affected_range (m_file_cache, m_policy, hint, CU_DISPLAY_COLS); + column_range printed_columns + = get_printed_columns (m_file_cache, m_policy, hint); + + /* Potentially consolidate. */ + if (!m_corrections.is_empty ()) + { + correction *last_correction + = m_corrections[m_corrections.length () - 1]; + + /* The following consolidation code assumes that the fix-it hints + have been sorted by start (done within layout's ctor). */ + gcc_assert (affected_bytes.start + >= last_correction->m_affected_bytes.start); + gcc_assert (printed_columns.start + >= last_correction->m_printed_columns.start); + + if (printed_columns.start <= last_correction->m_printed_columns.finish + && column_range::valid_p (last_correction->m_affected_bytes.finish + 1, + affected_bytes.start - 1)) + { + /* We have two hints for which the printed forms of the hints + would touch or overlap, so we need to consolidate them to avoid + confusing the user. + Attempt to inject a "replace" correction from immediately + after the end of the last hint to immediately before the start + of the next hint. */ + column_range between (last_correction->m_affected_bytes.finish + 1, + affected_bytes.start - 1); + + /* Try to read the source. */ + source_line line (m_file_cache, m_filename, m_row); + if (line.chars && between.finish < line.width) + { + /* Consolidate into the last correction: + add a no-op "replace" of the "between" text, and + add the text from the new hint. */ + int old_byte_len = last_correction->m_byte_length; + gcc_assert (old_byte_len >= 0); + int between_byte_len = between.finish + 1 - between.start; + gcc_assert (between_byte_len >= 0); + int new_byte_len + = old_byte_len + between_byte_len + hint->get_length (); + gcc_assert (new_byte_len >= 0); + last_correction->ensure_capacity (new_byte_len); + last_correction->overwrite + (old_byte_len, + line.as_span ().subspan (between.start - 1, + between.finish + 1 - between.start)); + last_correction->overwrite + (old_byte_len + between_byte_len, + diagnostics::char_span (hint->get_string (), + hint->get_length ())); + last_correction->m_byte_length = new_byte_len; + last_correction->ensure_terminated (); + last_correction->m_affected_bytes.finish + = affected_bytes.finish; + last_correction->m_affected_columns.finish + = affected_columns.finish; + int prev_display_cols = last_correction->m_display_cols; + last_correction->compute_display_cols (); + last_correction->m_printed_columns.finish + += last_correction->m_display_cols - prev_display_cols; + return; + } + } + } + + /* If no consolidation happened, add a new correction instance. */ + m_corrections.safe_push (new correction (affected_bytes, + affected_columns, + printed_columns, + hint->get_string (), + hint->get_length (), + m_policy)); +} + +/* If there are any fixit hints on source line ROW, print them. + They are printed in order, attempting to combine them onto lines, but + starting new lines if necessary. + Fix-it hints that insert new lines are handled separately, + in layout::print_leading_fixits. */ + +template<typename TextOrHtml> +void +layout_printer<TextOrHtml>::print_trailing_fixits (linenum_type row) +{ + typename TextOrHtml::auto_check_tag_nesting sentinel (m_text_or_html); + + /* Build a list of correction instances for the line, + potentially consolidating hints (for the sake of readability). */ + line_corrections corrections (m_layout.m_file_cache, m_layout.m_char_policy, + m_layout.m_exploc.file, row); + for (unsigned int i = 0; i < m_layout.m_fixit_hints.length (); i++) + { + const fixit_hint *hint = m_layout.m_fixit_hints[i]; + + /* Newline fixits are handled by layout::print_leading_fixits. */ + if (hint->ends_with_newline_p ()) + continue; + + if (hint->affects_line_p (m_layout.m_line_table, + m_layout.m_exploc.file, + row)) + corrections.add_hint (hint); + } + + /* Now print the corrections. */ + unsigned i; + correction *c; + int column = 1 + m_layout.m_x_offset_display; + + if (!corrections.m_corrections.is_empty ()) + start_annotation_line (margin_kind::normal); + + FOR_EACH_VEC_ELT (corrections.m_corrections, i, c) + { + /* For now we assume each fixit hint can only touch one line. */ + if (c->insertion_p ()) + { + /* This assumes the insertion just affects one line. */ + int start_column = c->m_printed_columns.start; + move_to_column (&column, start_column, true); + m_text_or_html.colorize_text_for_fixit_insert (); + m_text_or_html.add_text (c->m_text); + m_text_or_html.colorize_text_ensure_normal (); + column += c->m_display_cols; + } + else + { + /* If the range of the replacement wasn't printed in the + annotation line, then print an extra underline to + indicate exactly what is being replaced. + Always show it for removals. */ + int start_column = c->m_affected_columns.start; + int finish_column = c->m_affected_columns.finish; + if (!m_layout.annotation_line_showed_range_p (row, start_column, + finish_column) + || c->m_byte_length == 0) + { + move_to_column (&column, start_column, true); + m_text_or_html.colorize_text_for_fixit_delete (); + for (; column <= finish_column; column++) + m_text_or_html.add_character ('-'); + m_text_or_html.colorize_text_ensure_normal (); + } + /* Print the replacement text. REPLACE also covers + removals, so only do this extra work (potentially starting + a new line) if we have actual replacement text. */ + if (c->m_byte_length > 0) + { + move_to_column (&column, start_column, true); + m_text_or_html.colorize_text_for_fixit_insert (); + m_text_or_html.add_text (c->m_text); + m_text_or_html.colorize_text_ensure_normal (); + column += c->m_display_cols; + } + } + } + + /* Add a trailing newline, if necessary. */ + move_to_column (&column, 1 + m_layout.m_x_offset_display, false); +} + +/* Return true if (ROW/COLUMN) is within a range of the layout. + If it returns true, OUT_STATE is written to, with the + range index, and whether we should draw the caret at + (ROW/COLUMN) (as opposed to an underline). COL_UNIT controls + whether all inputs and outputs are in bytes or display column units. */ + +bool +layout::get_state_at_point (/* Inputs. */ + linenum_type row, int column, + int first_non_ws, int last_non_ws, + enum column_unit col_unit, + /* Outputs. */ + point_state *out_state) const +{ + layout_range *range; + int i; + FOR_EACH_VEC_ELT (m_layout_ranges, i, range) + { + if (range->m_range_display_kind == SHOW_LINES_WITHOUT_RANGE) + /* Bail out early, so that such ranges don't affect underlining or + source colorization. */ + continue; + + if (range->contains_point (row, column, col_unit)) + { + out_state->range_idx = i; + + /* Are we at the range's caret? is it visible? */ + out_state->draw_caret_p = false; + if (range->m_range_display_kind == SHOW_RANGE_WITH_CARET + && row == range->m_caret.m_line + && column == range->m_caret.m_columns[col_unit]) + out_state->draw_caret_p = true; + + /* Within a multiline range, don't display any underline + in any leading or trailing whitespace on a line. + We do display carets, however. */ + if (!out_state->draw_caret_p) + if (column < first_non_ws || column > last_non_ws) + return false; + + /* We are within a range. */ + return true; + } + } + + return false; +} + +/* Helper function for use by layout::print_line when printing the + annotation line under the source line. + Get the display column beyond the rightmost one that could contain a caret + or range marker, given that we stop rendering at trailing whitespace. + ROW is the source line within the given file. + CARET_COLUMN is the display column of range 0's caret. + LAST_NON_WS_COLUMN is the last display column containing a non-whitespace + character of source (as determined when printing the source line). */ + +int +layout::get_x_bound_for_row (linenum_type row, int caret_column, + int last_non_ws_column) const +{ + int result = caret_column + 1; + + layout_range *range; + int i; + FOR_EACH_VEC_ELT (m_layout_ranges, i, range) + { + if (row >= range->m_start.m_line) + { + if (range->m_finish.m_line == row) + { + /* On the final line within a range; ensure that + we render up to the end of the range. */ + const int disp_col = range->m_finish.m_columns[CU_DISPLAY_COLS]; + if (result <= disp_col) + result = disp_col + 1; + } + else if (row < range->m_finish.m_line) + { + /* Within a multiline range; ensure that we render up to the + last non-whitespace column. */ + if (result <= last_non_ws_column) + result = last_non_ws_column + 1; + } + } + } + + return result; +} + +/* Given *COLUMN as an x-coordinate, print spaces to position + successive output at DEST_COLUMN, printing a newline if necessary, + and updating *COLUMN. If ADD_LEFT_MARGIN, then print the (empty) + left margin after any newline. */ + +template<typename TextOrHtml> +void +layout_printer<TextOrHtml>::move_to_column (int *column, + int dest_column, + bool add_left_margin) +{ + /* Start a new line if we need to. */ + if (*column > dest_column) + { + end_line (); + if (add_left_margin) + start_annotation_line (margin_kind::normal); + *column = 1 + m_layout.m_x_offset_display; + } + + while (*column < dest_column) + { + /* For debugging column issues, it can be helpful to replace this + add_space call with + m_text_or_html.add_character ('0' + (*column % 10)); + to visualize the changing value of "*column". */ + m_text_or_html.add_space (); + (*column)++; + } +} + +/* For debugging layout issues, render a ruler giving column numbers + (after the 1-column indent). */ + +template<typename TextOrHtml> +void +layout_printer<TextOrHtml>::show_ruler (int max_column) +{ + m_text_or_html.push_html_tag_with_class("thead", "ruler", false); + + /* Hundreds. */ + if (max_column > 99) + { + start_annotation_line (margin_kind::ruler); + for (int column = 1 + m_layout.m_x_offset_display; + column <= max_column; + ++column) + if (column % 10 == 0) + m_text_or_html.add_character ('0' + (column / 100) % 10); + else + m_text_or_html.add_space (); + end_line (); + } + + /* Tens. */ + start_annotation_line (margin_kind::ruler); + for (int column = 1 + m_layout.m_x_offset_display; + column <= max_column; + ++column) + if (column % 10 == 0) + m_text_or_html.add_character ('0' + (column / 10) % 10); + else + m_text_or_html.add_space (); + end_line (); + + /* Units. */ + start_annotation_line (margin_kind::ruler); + for (int column = 1 + m_layout.m_x_offset_display; + column <= max_column; + ++column) + m_text_or_html.add_character ('0' + (column % 10)); + end_line (); + + m_text_or_html.pop_html_tag("thead"); // thead +} + +/* Print leading fix-its (for new lines inserted before the source line) + then the source line, followed by an annotation line + consisting of any caret/underlines, then any fixits. + If the source line can't be read, print nothing. */ +template<typename TextOrHtml> +void +layout_printer<TextOrHtml>::print_line (linenum_type row) +{ + typename TextOrHtml::auto_check_tag_nesting sentinel (m_text_or_html); + + diagnostics::char_span line + = m_layout.m_file_cache.get_source_line (m_layout.m_exploc.file, row); + if (!line) + return; + + print_any_right_to_left_edge_lines (); + print_leading_fixits (row); + const line_bounds lbounds + = print_source_line (row, line.get_buffer (), line.length ()); + if (m_layout.should_print_annotation_line_p (row)) + print_annotation_line (row, lbounds); + if (get_options ().show_labels_p) + print_any_labels (row); + print_trailing_fixits (row); +} + +/* If there's a link column in the RHS, print something like this: + " │\n" + "┌──────────────────────────────────────────┘\n" + showing the link entering at the top right and emerging + at the bottom left. */ + +template<typename TextOrHtml> +void +layout_printer<TextOrHtml>::print_any_right_to_left_edge_lines () +{ + if (m_link_rhs_column == -1) + /* Can also happen if the out-edge had UNKNOWN_LOCATION. */ + return; + + gcc_assert (get_options ().show_event_links_p); + + /* Print the line with "|". */ + start_annotation_line (margin_kind::normal); + + int column = 1 + m_layout.m_x_offset_display; + move_to_column (&column, m_link_rhs_column, true); + m_text_or_html.colorize_text_for_cfg_edge (); + const cppchar_t down= get_theme ().get_cppchar + (text_art::theme::cell_kind::CFG_DOWN); + m_text_or_html.add_character (down); + end_line (); + + /* Print the line with "┌──────────────────────────────────────────┘". */ + m_link_lhs_state = link_lhs_state::rewinding_to_lhs; + start_annotation_line (margin_kind::normal); + m_text_or_html.colorize_text_for_cfg_edge (); + const cppchar_t left= get_theme ().get_cppchar + (text_art::theme::cell_kind::CFG_LEFT); + for (int column = 1 + m_layout.m_x_offset_display; + column < m_link_rhs_column; + ++column) + m_text_or_html.add_character (left); + const cppchar_t from_down_to_left = get_theme ().get_cppchar + (text_art::theme::cell_kind::CFG_FROM_DOWN_TO_LEFT); + m_text_or_html.add_character (from_down_to_left); + end_line (); + + /* We now have a link line on the LHS, + and no longer have one on the RHS. */ + m_link_lhs_state = link_lhs_state::at_lhs; + m_link_rhs_column = -1; +} + +template<typename TextOrHtml> +layout_printer<TextOrHtml>::layout_printer (TextOrHtml &text_or_html, + const layout &layout, + bool is_diagnostic_path) +: m_text_or_html (text_or_html), + m_layout (layout), + m_is_diagnostic_path (is_diagnostic_path), + m_was_in_range_p (false), + m_last_range_idx (0), + m_link_lhs_state (link_lhs_state::none), + m_link_rhs_column (-1) +{ + if (get_options ().show_event_links_p) + if (auto effect_info = m_layout.m_effect_info) + if (effect_info->m_leading_in_edge_column) + m_link_rhs_column = effect_info->m_leading_in_edge_column; +} + +} /* End of anonymous namespace. */ + +/* If LOC is within the spans of lines that will already be printed for + this gcc_rich_location, then add it as a secondary location and return true. + + Otherwise return false. + + Use POLICY for determining how spans of lines would be printed. */ + +bool +gcc_rich_location:: +add_location_if_nearby (const diagnostics::source_print_policy &policy, + location_t loc, + bool restrict_to_current_line_spans, + const range_label *label) +{ + /* Use the layout location-handling logic to sanitize LOC, + filtering it to the current line spans within a temporary + layout instance. */ + + layout layout (policy, *this); + location_range loc_range; + loc_range.m_loc = loc; + loc_range.m_range_display_kind = SHOW_RANGE_WITHOUT_CARET; + loc_range.m_label = nullptr; + if (!layout.maybe_add_location_range (&loc_range, 0, + restrict_to_current_line_spans)) + return false; + + add_range (loc, SHOW_RANGE_WITHOUT_CARET, label); + return true; +} + +bool +gcc_rich_location:: +add_location_if_nearby (const diagnostics::context &dc, + location_t loc, + bool restrict_to_current_line_spans, + const range_label *label) +{ + diagnostics::source_print_policy + source_policy (dc, + dc.get_source_printing_options ()); + return add_location_if_nearby (source_policy, loc, + restrict_to_current_line_spans, label); +} + +namespace diagnostics { + +/* As per diagnostics::source_print_policy::print, but don't print anything + if source printing is disabled, or if the location hasn't changed. */ + +void +context::maybe_show_locus (const rich_location &richloc, + const source_printing_options &opts, + enum kind diagnostic_kind, + pretty_printer &pp, + source_effect_info *effects) +{ + const location_t loc = richloc.get_loc (); + /* Do nothing if source-printing has been disabled. */ + if (!opts.enabled) + return; + + /* Don't attempt to print source for UNKNOWN_LOCATION and for builtins. */ + if (loc <= BUILTINS_LOCATION) + return; + + /* Don't print the same source location twice in a row, unless we have + fix-it hints, or multiple locations, or a label. */ + if (loc == m_last_location + && richloc.get_num_fixit_hints () == 0 + && richloc.get_num_locations () == 1 + && richloc.get_range (0)->m_label == nullptr) + return; + + m_last_location = loc; + + source_print_policy source_policy (*this, opts); + source_policy.print (pp, richloc, diagnostic_kind, effects); +} + +/* As above, but print in HTML form to XP. + If non-null, use LABEL_WRITER when writing labelled ranges. */ + +void +context::maybe_show_locus_as_html (const rich_location &richloc, + const source_printing_options &opts, + enum kind diagnostic_kind, + xml::printer &xp, + source_effect_info *effects, + html_label_writer *label_writer) +{ + const location_t loc = richloc.get_loc (); + /* Do nothing if source-printing has been disabled. */ + if (!opts.enabled) + return; + + /* Don't attempt to print source for UNKNOWN_LOCATION and for builtins. */ + if (loc <= BUILTINS_LOCATION) + return; + + /* Don't print the same source location twice in a row, unless we have + fix-it hints, or multiple locations, or a label. */ + if (loc == m_last_location + && richloc.get_num_fixit_hints () == 0 + && richloc.get_num_locations () == 1 + && richloc.get_range (0)->m_label == nullptr) + return; + + m_last_location = loc; + + source_print_policy source_policy (*this, opts); + source_policy.print_as_html (xp, richloc, diagnostic_kind, effects, + label_writer); +} + +source_print_policy:: +source_print_policy (const context &dc) +: m_options (dc.m_source_printing), + m_location_policy (dc), + m_text_start_span_cb (dc.m_text_callbacks.m_text_start_span), + m_html_start_span_cb (dc.m_text_callbacks.m_html_start_span), + m_file_cache (dc.get_file_cache ()), + m_diagram_theme (dc.get_diagram_theme ()), + m_escape_format (dc.get_escape_format ()) +{ +} + +source_print_policy::source_print_policy (const context &dc, + const source_printing_options &opts) +: m_options (opts), + m_location_policy (dc), + m_text_start_span_cb (dc.m_text_callbacks.m_text_start_span), + m_html_start_span_cb (dc.m_text_callbacks.m_html_start_span), + m_file_cache (dc.get_file_cache ()), + m_diagram_theme (dc.get_diagram_theme ()), + m_escape_format (dc.get_escape_format ()) +{ +} + +/* Print to PP the physical source code corresponding to the location(s) + in RICHLOC, with additional annotations, as if for a diagnostic of the + given DIAGNOSTIC_KIND. + If EFFECTS is non-null, then use and update it. */ + +void +source_print_policy::print (pretty_printer &pp, + const rich_location &richloc, + enum kind diagnostic_kind, + source_effect_info *effects) const +{ + layout layout (*this, richloc, effects); + colorizer col (pp, richloc, diagnostic_kind); + to_text text_or_html (pp, col); + layout_printer<to_text> lp (text_or_html, layout, + diagnostic_kind == diagnostics::kind::path); + lp.print (*this); +} + +/* As above, but print in HTML form to XP. + If non-null, use LABEL_WRITER when writing labelled ranges. */ + +void +source_print_policy::print_as_html (xml::printer &xp, + const rich_location &richloc, + enum kind diagnostic_kind, + source_effect_info *effects, + html_label_writer *label_writer) const +{ + layout layout (*this, richloc, effects); + to_html text_or_html (xp, &richloc, label_writer); + layout_printer<to_html> lp (text_or_html, layout, + diagnostic_kind == diagnostics::kind::path); + xml::auto_check_tag_nesting sentinel (xp); + lp.print (*this); +} + +} // namespace diagnostics + +template <typename TextOrHtml> +void +layout_printer<TextOrHtml>:: +print (const diagnostics::source_print_policy &source_policy) +{ + typename TextOrHtml::auto_check_tag_nesting sentinel (m_text_or_html); + + m_text_or_html.push_html_tag_with_class ("table", "locus", false); + + if (get_options ().show_ruler_p) + show_ruler (m_layout.m_x_offset_display + get_options ().max_width); + + for (int line_span_idx = 0; line_span_idx < m_layout.get_num_line_spans (); + line_span_idx++) + { + const line_span *line_span = m_layout.get_line_span (line_span_idx); + if (get_options ().show_line_numbers_p) + { + /* With line numbers, we should show whenever the line-numbering + "jumps". */ + if (line_span_idx > 0) + print_gap_in_line_numbering (); + } + else + { + /* Without line numbers, we print headings for some line spans. */ + if (m_layout.print_heading_for_line_span_index_p (line_span_idx)) + { + expanded_location exploc + = m_layout.get_expanded_location (line_span); + const diagnostics::location_print_policy & + loc_policy = source_policy.get_location_policy (); + m_text_or_html.invoke_start_span_fn (source_policy, loc_policy, exploc); + } + } + + m_text_or_html.push_html_tag_with_class ("tbody", "line-span", false); + + /* Iterate over the lines within this span (using linenum_arith_t to + avoid overflow with 0xffffffff causing an infinite loop). */ + linenum_arith_t last_line = line_span->get_last_line (); + for (linenum_arith_t row = line_span->get_first_line (); + row <= last_line; row++) + print_line (row); + + m_text_or_html.pop_html_tag ("tbody"); + } + + if (auto effect_info = m_layout.m_effect_info) + effect_info->m_trailing_out_edge_column = m_link_rhs_column; + + m_text_or_html.pop_html_tag ("table"); +} + +#if CHECKING_P + +namespace diagnostics { +namespace selftest { + +using test_context = diagnostics::selftest::test_context; + +using line_table_case = ::selftest::line_table_case; +using line_table_test = ::selftest::line_table_test; +using temp_source_file = ::selftest::temp_source_file; + +static std::unique_ptr<xml::node> +make_element_for_locus (const rich_location &rich_loc, + enum kind kind, + diagnostics::context &dc) +{ + dc.m_last_location = UNKNOWN_LOCATION; + + xml::element wrapper ("wrapper", false); + xml::printer xp (wrapper); + dc.maybe_show_locus_as_html (rich_loc, + dc.get_source_printing_options (), + kind, + xp, + nullptr, + nullptr); // label_writer + if (wrapper.m_children.size () > 0) + return std::move (wrapper.m_children[0]); + else + return nullptr; +} + +static label_text +make_raw_html_for_locus (const rich_location &rich_loc, + enum kind kind, + diagnostics::context &dc) +{ + auto node = make_element_for_locus (rich_loc, kind, dc); + pretty_printer pp; + if (node) + node->write_as_xml (&pp, 0, true); + return label_text::take (xstrdup (pp_formatted_text (&pp))); +} + +/* Selftests for diagnostic_show_locus. */ + +source_printing_fixture:: +source_printing_fixture (const line_table_case &case_, + const char *content) +: m_content (content), + m_tmp_source_file (SELFTEST_LOCATION, ".c", content), + m_ltt (case_), + m_fc () +{ + linemap_add (line_table, LC_ENTER, false, + m_tmp_source_file.get_filename (), 1); +} + +/* Populate a char_display_policy based on DC and RICHLOC. */ + +static char_display_policy +make_char_policy (const diagnostics::context &dc, + const rich_location &richloc) +{ + diagnostics::source_print_policy source_policy (dc); + return ::make_char_policy (source_policy, richloc); +} + +/* Verify that cpp_display_width correctly handles escaping. */ + +static void +test_display_widths () +{ + gcc_rich_location richloc (UNKNOWN_LOCATION); + + /* U+03C0 "GREEK SMALL LETTER PI". */ + const char *pi = "\xCF\x80"; + /* U+1F642 "SLIGHTLY SMILING FACE". */ + const char *emoji = "\xF0\x9F\x99\x82"; + /* Stray trailing byte of a UTF-8 character. */ + const char *stray = "\xBF"; + /* U+10FFFF. */ + const char *max_codepoint = "\xF4\x8F\xBF\xBF"; + + /* No escaping. */ + { + test_context dc; + char_display_policy policy (make_char_policy (dc, richloc)); + ASSERT_EQ (cpp_display_width (pi, strlen (pi), policy), 1); + ASSERT_EQ (cpp_display_width (emoji, strlen (emoji), policy), 2); + ASSERT_EQ (cpp_display_width (stray, strlen (stray), policy), 1); + /* Don't check width of U+10FFFF; it's in a private use plane. */ + } + + richloc.set_escape_on_output (true); + + { + test_context dc; + dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_UNICODE); + char_display_policy policy (make_char_policy (dc, richloc)); + ASSERT_EQ (cpp_display_width (pi, strlen (pi), policy), 8); + ASSERT_EQ (cpp_display_width (emoji, strlen (emoji), policy), 9); + ASSERT_EQ (cpp_display_width (stray, strlen (stray), policy), 4); + ASSERT_EQ (cpp_display_width (max_codepoint, strlen (max_codepoint), + policy), + strlen ("<U+10FFFF>")); + } + + { + test_context dc; + dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_BYTES); + char_display_policy policy (make_char_policy (dc, richloc)); + ASSERT_EQ (cpp_display_width (pi, strlen (pi), policy), 8); + ASSERT_EQ (cpp_display_width (emoji, strlen (emoji), policy), 16); + ASSERT_EQ (cpp_display_width (stray, strlen (stray), policy), 4); + ASSERT_EQ (cpp_display_width (max_codepoint, strlen (max_codepoint), + policy), + 16); + } +} + +/* For precise tests of the layout, make clear where the source line will + start. test_left_margin sets the total byte count from the left side of the + screen to the start of source lines, after the line number and the separator, + which consists of the three characters " | ". */ +static const int test_linenum_sep = 3; +static const int test_left_margin = 7; + +/* Helper function for test_layout_x_offset_display_utf8(). */ +static void +test_offset_impl (int caret_byte_col, int max_width, + int expected_x_offset_display, + int left_margin = test_left_margin) +{ + test_context dc; + auto &source_printing_opts = dc.get_source_printing_options (); + source_printing_opts.max_width = max_width; + /* min_margin_width sets the minimum space reserved for + the line number plus one space after. */ + source_printing_opts.min_margin_width = left_margin - test_linenum_sep + 1; + dc.show_line_numbers (true); + diagnostics::source_print_policy source_policy (dc); + rich_location richloc (line_table, + linemap_position_for_column (line_table, + caret_byte_col)); + layout test_layout (source_policy, richloc, nullptr); + ASSERT_EQ (left_margin - test_linenum_sep, + test_layout.get_linenum_width ()); + ASSERT_EQ (expected_x_offset_display, + test_layout.get_x_offset_display ()); +} + +/* Test that layout::calculate_x_offset_display() works. */ +static void +test_layout_x_offset_display_utf8 (const line_table_case &case_) +{ + + const char *content + = "This line is very long, so that we can use it to test the logic for " + "clipping long lines. Also this: \xf0\x9f\x98\x82\xf0\x9f\x98\x82 is a " + "pair of emojis that occupies 8 bytes and 4 display columns, starting at " + "column #102.\n"; + + /* Number of bytes in the line, subtracting one to remove the newline. */ + const int line_bytes = strlen (content) - 1; + + /* Number of display columns occupied by the line; each of the 2 emojis + takes up 2 fewer display columns than it does bytes. */ + const int line_display_cols = line_bytes - 2*2; + + /* The column of the first emoji. Byte or display is the same as there are + no multibyte characters earlier on the line. */ + const int emoji_col = 102; + + source_printing_fixture f (case_, content); + + linemap_add (line_table, LC_ENTER, false, f.get_filename (), 1); + + location_t line_end = linemap_position_for_column (line_table, line_bytes); + + /* Don't attempt to run the tests if column data might be unavailable. */ + if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS) + return; + + ASSERT_STREQ (f.get_filename (), LOCATION_FILE (line_end)); + ASSERT_EQ (1, LOCATION_LINE (line_end)); + ASSERT_EQ (line_bytes, LOCATION_COLUMN (line_end)); + + diagnostics::char_span lspan = f.m_fc.get_source_line (f.get_filename (), 1); + ASSERT_EQ (line_display_cols, + cpp_display_width (lspan.get_buffer (), lspan.length (), + def_policy ())); + ASSERT_EQ (line_display_cols, + location_compute_display_column (f.m_fc, + expand_location (line_end), + def_policy ())); + ASSERT_EQ (0, memcmp (lspan.get_buffer () + (emoji_col - 1), + "\xf0\x9f\x98\x82\xf0\x9f\x98\x82", 8)); + + /* (caret_byte, max_width, expected_x_offset_display, [left_margin]) */ + + /* No constraint on the width -> no offset. */ + test_offset_impl (emoji_col, 0, 0); + + /* Caret is before the beginning -> no offset. */ + test_offset_impl (0, 100, 0); + + /* Caret is past the end of the line -> no offset. */ + test_offset_impl (line_bytes+1, 100, 0); + + /* Line fits in the display -> no offset. */ + test_offset_impl (line_bytes, line_display_cols + test_left_margin, 0); + test_offset_impl (emoji_col, line_display_cols + test_left_margin, 0); + + /* Line is too long for the display but caret location is OK + anyway -> no offset. */ + static const int small_width = 24; + test_offset_impl (1, small_width, 0); + + /* Width constraint is very small -> no offset. */ + test_offset_impl (emoji_col, CARET_LINE_MARGIN, 0); + + /* Line would be offset, but due to large line numbers, offsetting + would remove the whole line -> no offset. */ + static const int huge_left_margin = 100; + test_offset_impl (emoji_col, huge_left_margin, 0, huge_left_margin); + + /* Line is the same length as the display, but the line number makes it too + long, so offset is required. Caret is at the end so padding on the right + is not in effect. */ + for (int excess = 1; excess <= 3; ++excess) + test_offset_impl (line_bytes, line_display_cols + test_left_margin - excess, + excess); + + /* Line is much too long for the display, caret is near the end -> + offset should be such that the line fits in the display and caret + remains the same distance from the end that it was. */ + for (int caret_offset = 0, max_offset = MIN (CARET_LINE_MARGIN, 10); + caret_offset <= max_offset; ++caret_offset) + test_offset_impl (line_bytes - caret_offset, small_width, + line_display_cols + test_left_margin - small_width); + + /* As previous case but caret is closer to the middle; now we want it to end + up CARET_LINE_MARGIN bytes from the end. */ + ASSERT_GT (line_display_cols - emoji_col, CARET_LINE_MARGIN); + test_offset_impl (emoji_col, small_width, + emoji_col + test_left_margin + - (small_width - CARET_LINE_MARGIN)); + + /* Test that the source line is offset as expected when printed. */ + { + test_context dc; + auto &source_printing_opts = dc.get_source_printing_options (); + source_printing_opts.max_width = small_width - 6; + source_printing_opts.min_margin_width + = test_left_margin - test_linenum_sep + 1; + dc.show_line_numbers (true); + dc.show_ruler (true); + diagnostics::source_print_policy policy (dc); + rich_location richloc (line_table, + linemap_position_for_column (line_table, + emoji_col)); + layout test_layout (policy, richloc, nullptr); + colorizer col (*dc.get_reference_printer (), + richloc, diagnostics::kind::error); + diagnostics::to_text text_or_html (*dc.get_reference_printer (), col); + layout_printer<diagnostics::to_text> lp (text_or_html, test_layout, false); + lp.print (policy); + ASSERT_STREQ (" | 1 \n" + " | 1 \n" + " | 234567890123456789\n" + " 1 | \xf0\x9f\x98\x82\xf0\x9f\x98\x82 is a pair of emojis " + "that occupies 8 bytes and 4 display columns, starting at " + "column #102.\n" + " | ^\n", + pp_formatted_text (dc.get_reference_printer ())); + } + + /* Similar to the previous example, but now the offset called for would split + the first emoji in the middle of the UTF-8 sequence. Check that we replace + it with a padding space in this case. */ + { + test_context dc; + auto &source_printing_opts = dc.get_source_printing_options (); + source_printing_opts.max_width = small_width - 5; + source_printing_opts.min_margin_width + = test_left_margin - test_linenum_sep + 1; + dc.show_line_numbers (true); + dc.show_ruler (true); + diagnostics::source_print_policy policy (dc); + rich_location richloc (line_table, + linemap_position_for_column (line_table, + emoji_col + 2)); + layout test_layout (dc, richloc, nullptr); + colorizer col (*dc.get_reference_printer (), + richloc, diagnostics::kind::error); + diagnostics::to_text text_or_html (*dc.get_reference_printer (), col); + layout_printer<diagnostics::to_text> lp (text_or_html, test_layout, false); + lp.print (policy); + ASSERT_STREQ (" | 1 1 \n" + " | 1 2 \n" + " | 3456789012345678901\n" + " 1 | \xf0\x9f\x98\x82 is a pair of emojis " + "that occupies 8 bytes and 4 display columns, starting at " + "column #102.\n" + " | ^\n", + pp_formatted_text (dc.get_reference_printer ())); + } + +} + +static void +test_layout_x_offset_display_tab (const line_table_case &case_) +{ + const char *content + = "This line is very long, so that we can use it to test the logic for " + "clipping long lines. Also this: `\t' is a tab that occupies 1 byte and " + "a variable number of display columns, starting at column #103.\n"; + + /* Number of bytes in the line, subtracting one to remove the newline. */ + const int line_bytes = strlen (content) - 1; + + /* The column where the tab begins. Byte or display is the same as there are + no multibyte characters earlier on the line. */ + const int tab_col = 103; + + /* Effective extra size of the tab beyond what a single space would have taken + up, indexed by tabstop. */ + static const int num_tabstops = 11; + int extra_width[num_tabstops]; + for (int tabstop = 1; tabstop != num_tabstops; ++tabstop) + { + const int this_tab_size = tabstop - (tab_col - 1) % tabstop; + extra_width[tabstop] = this_tab_size - 1; + } + /* Example of this calculation: if tabstop is 10, the tab starting at column + #103 has to expand into 8 spaces, covering columns 103-110, so that the + next character is at column #111. So it takes up 7 more columns than + a space would have taken up. */ + ASSERT_EQ (7, extra_width[10]); + + temp_source_file tmp (SELFTEST_LOCATION, ".c", content); + diagnostics::file_cache fc; + line_table_test ltt (case_); + + linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 1); + + location_t line_end = linemap_position_for_column (line_table, line_bytes); + + /* Don't attempt to run the tests if column data might be unavailable. */ + if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS) + return; + + /* Check that cpp_display_width handles the tabs as expected. */ + diagnostics::char_span lspan = fc.get_source_line (tmp.get_filename (), 1); + ASSERT_EQ ('\t', *(lspan.get_buffer () + (tab_col - 1))); + for (int tabstop = 1; tabstop != num_tabstops; ++tabstop) + { + cpp_char_column_policy policy (tabstop, cpp_wcwidth); + ASSERT_EQ (line_bytes + extra_width[tabstop], + cpp_display_width (lspan.get_buffer (), lspan.length (), + policy)); + ASSERT_EQ (line_bytes + extra_width[tabstop], + location_compute_display_column (fc, + expand_location (line_end), + policy)); + } + + /* Check that the tab is expanded to the expected number of spaces. */ + rich_location richloc (line_table, + linemap_position_for_column (line_table, + tab_col + 1)); + for (int tabstop = 1; tabstop != num_tabstops; ++tabstop) + { + test_context dc; + dc.m_tabstop = tabstop; + diagnostics::source_print_policy policy (dc); + layout test_layout (policy, richloc, nullptr); + colorizer col (*dc.get_reference_printer (), + richloc, diagnostics::kind::error); + diagnostics::to_text text_or_html (*dc.get_reference_printer (), col); + layout_printer<diagnostics::to_text> lp + (text_or_html, test_layout, false); + lp.print (policy); + const char *out = pp_formatted_text (dc.get_reference_printer ()); + ASSERT_EQ (nullptr, strchr (out, '\t')); + const char *left_quote = strchr (out, '`'); + const char *right_quote = strchr (out, '\''); + ASSERT_NE (nullptr, left_quote); + ASSERT_NE (nullptr, right_quote); + ASSERT_EQ (right_quote - left_quote, extra_width[tabstop] + 2); + } + + /* Check that the line is offset properly and that the tab is broken up + into the expected number of spaces when it is the last character skipped + over. */ + for (int tabstop = 1; tabstop != num_tabstops; ++tabstop) + { + test_context dc; + dc.m_tabstop = tabstop; + static const int small_width = 24; + auto &source_printing_opts = dc.get_source_printing_options (); + source_printing_opts.max_width = small_width - 4; + source_printing_opts.min_margin_width + = test_left_margin - test_linenum_sep + 1; + dc.show_line_numbers (true); + diagnostics::source_print_policy policy (dc); + layout test_layout (policy, richloc, nullptr); + colorizer col (*dc.get_reference_printer (), + richloc, diagnostics::kind::error); + diagnostics::to_text text_or_html (*dc.get_reference_printer (), col); + layout_printer<diagnostics::to_text> lp + (text_or_html, test_layout, false); + lp.print (policy); + + /* We have arranged things so that two columns will be printed before + the caret. If the tab results in more than one space, this should + produce two spaces in the output; otherwise, it will be a single space + preceded by the opening quote before the tab character. */ + const char *output1 + = " 1 | ' is a tab that occupies 1 byte and a variable number of " + "display columns, starting at column #103.\n" + " | ^\n"; + const char *output2 + = " 1 | ` ' is a tab that occupies 1 byte and a variable number of " + "display columns, starting at column #103.\n" + " | ^\n"; + const char *expected_output = (extra_width[tabstop] ? output1 : output2); + ASSERT_STREQ (expected_output, pp_formatted_text (dc.get_reference_printer ())); + } +} + + +/* Verify that diagnostic_show_locus works sanely on UNKNOWN_LOCATION. */ + +static void +test_diagnostic_show_locus_unknown_location () +{ + test_context dc; + rich_location richloc (line_table, UNKNOWN_LOCATION); + ASSERT_STREQ ("", dc.test_show_locus (richloc)); +} + +/* Verify that diagnostic_show_locus works sanely for various + single-line cases. + + All of these work on the following 1-line source file: + .0000000001111111 + .1234567890123456 + "foo = bar.field;\n" + which is set up by test_diagnostic_show_locus_one_liner and calls + them. */ + +/* Just a caret. */ + +static void +test_one_liner_simple_caret () +{ + test_context dc; + location_t caret = linemap_position_for_column (line_table, 10); + rich_location richloc (line_table, caret); + ASSERT_STREQ (" foo = bar.field;\n" + " ^\n", + dc.test_show_locus (richloc)); +} + +/* No column information (column == 0). + No annotation line should be printed. */ + +static void +test_one_liner_no_column () +{ + test_context dc; + location_t caret = linemap_position_for_column (line_table, 0); + rich_location richloc (line_table, caret); + ASSERT_STREQ (" foo = bar.field;\n", + dc.test_show_locus (richloc)); +} + +/* Caret and range. */ + +static void +test_one_liner_caret_and_range () +{ + test_context dc; + location_t caret = linemap_position_for_column (line_table, 10); + location_t start = linemap_position_for_column (line_table, 7); + location_t finish = linemap_position_for_column (line_table, 15); + location_t loc = make_location (caret, start, finish); + rich_location richloc (line_table, loc); + ASSERT_STREQ (" foo = bar.field;\n" + " ~~~^~~~~~\n", + dc.test_show_locus (richloc)); + + { + test_context dc; + auto out = make_raw_html_for_locus (richloc, diagnostics::kind::error, dc); + ASSERT_STREQ + ("<table class=\"locus\">\n" + " <tbody class=\"line-span\">\n" + " <tr><td class=\"left-margin\"> </td><td class=\"source\">foo = bar.field;</td></tr>\n" + " <tr><td class=\"left-margin\"> </td><td class=\"annotation\"> ~~~^~~~~~</td></tr>\n" + " </tbody>\n" + "</table>\n", + out.get ()); + } + { + test_context dc; + dc.show_line_numbers (true); + auto out = make_raw_html_for_locus (richloc, diagnostics::kind::error, dc); + ASSERT_STREQ + ("<table class=\"locus\">\n" + " <tbody class=\"line-span\">\n" + " <tr><td class=\"linenum\"> 1</td><td class=\"left-margin\"> </td><td class=\"source\">foo = bar.field;</td></tr>\n" + " <tr><td class=\"linenum\"> </td><td class=\"left-margin\"> </td><td class=\"annotation\"> ~~~^~~~~~</td></tr>\n" + " </tbody>\n" + "</table>\n", + out.get ()); + } +} + +/* Multiple ranges and carets. */ + +static void +test_one_liner_multiple_carets_and_ranges () +{ + test_context dc; + location_t foo + = make_location (linemap_position_for_column (line_table, 2), + linemap_position_for_column (line_table, 1), + linemap_position_for_column (line_table, 3)); + dc.set_caret_char (0, 'A'); + + location_t bar + = make_location (linemap_position_for_column (line_table, 8), + linemap_position_for_column (line_table, 7), + linemap_position_for_column (line_table, 9)); + dc.set_caret_char (1, 'B'); + + location_t field + = make_location (linemap_position_for_column (line_table, 13), + linemap_position_for_column (line_table, 11), + linemap_position_for_column (line_table, 15)); + dc.set_caret_char (2, 'C'); + + rich_location richloc (line_table, foo); + richloc.add_range (bar, SHOW_RANGE_WITH_CARET); + richloc.add_range (field, SHOW_RANGE_WITH_CARET); + ASSERT_STREQ (" foo = bar.field;\n" + " ~A~ ~B~ ~~C~~\n", + dc.test_show_locus (richloc)); +} + +/* Insertion fix-it hint: adding an "&" to the front of "bar.field". */ + +static void +test_one_liner_fixit_insert_before () +{ + test_context dc; + location_t caret = linemap_position_for_column (line_table, 7); + rich_location richloc (line_table, caret); + richloc.add_fixit_insert_before ("&"); + ASSERT_STREQ (" foo = bar.field;\n" + " ^\n" + " &\n", + dc.test_show_locus (richloc)); +} + +/* Insertion fix-it hint: adding a "[0]" after "foo". */ + +static void +test_one_liner_fixit_insert_after () +{ + test_context dc; + location_t start = linemap_position_for_column (line_table, 1); + location_t finish = linemap_position_for_column (line_table, 3); + location_t foo = make_location (start, start, finish); + rich_location richloc (line_table, foo); + richloc.add_fixit_insert_after ("[0]"); + ASSERT_STREQ (" foo = bar.field;\n" + " ^~~\n" + " [0]\n", + dc.test_show_locus (richloc)); +} + +/* Removal fix-it hint: removal of the ".field". + Also verify the interaction of pp_set_prefix with rulers and + fix-it hints. */ + +static void +test_one_liner_fixit_remove () +{ + location_t start = linemap_position_for_column (line_table, 10); + location_t finish = linemap_position_for_column (line_table, 15); + location_t dot = make_location (start, start, finish); + rich_location richloc (line_table, dot); + richloc.add_fixit_remove (); + + /* Normal. */ + { + test_context dc; + ASSERT_STREQ (" foo = bar.field;\n" + " ^~~~~~\n" + " ------\n", + dc.test_show_locus (richloc)); + } + + /* Test of adding a prefix. */ + { + test_context dc; + pp_prefixing_rule (dc.get_reference_printer ()) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE; + pp_set_prefix (dc.get_reference_printer (), xstrdup ("TEST PREFIX:")); + ASSERT_STREQ ("TEST PREFIX: foo = bar.field;\n" + "TEST PREFIX: ^~~~~~\n" + "TEST PREFIX: ------\n", + dc.test_show_locus (richloc)); + } + + /* Normal, with ruler. */ + { + test_context dc; + auto &source_printing_opts = dc.get_source_printing_options (); + dc.show_ruler (true); + source_printing_opts.max_width = 104; + ASSERT_STREQ (" 0 0 0 0 0 0 0 0 0 1 \n" + " 1 2 3 4 5 6 7 8 9 0 \n" + " 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234\n" + " foo = bar.field;\n" + " ^~~~~~\n" + " ------\n", + dc.test_show_locus (richloc)); + } + + /* Test of adding a prefix, with ruler. */ + { + test_context dc; + auto &source_printing_opts = dc.get_source_printing_options (); + dc.show_ruler (true); + source_printing_opts.max_width = 50; + pp_prefixing_rule (dc.get_reference_printer ()) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE; + pp_set_prefix (dc.get_reference_printer (), xstrdup ("TEST PREFIX:")); + ASSERT_STREQ ("TEST PREFIX: 1 2 3 4 5\n" + "TEST PREFIX: 12345678901234567890123456789012345678901234567890\n" + "TEST PREFIX: foo = bar.field;\n" + "TEST PREFIX: ^~~~~~\n" + "TEST PREFIX: ------\n", + dc.test_show_locus (richloc)); + } + + /* Test of adding a prefix, with ruler and line numbers. */ + { + test_context dc; + auto &source_printing_opts = dc.get_source_printing_options (); + dc.show_ruler (true); + source_printing_opts.max_width = 50; + dc.show_line_numbers (true); + pp_prefixing_rule (dc.get_reference_printer ()) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE; + pp_set_prefix (dc.get_reference_printer (), xstrdup ("TEST PREFIX:")); + ASSERT_STREQ ("TEST PREFIX: | 1 2 3 4 5\n" + "TEST PREFIX: | 12345678901234567890123456789012345678901234567890\n" + "TEST PREFIX: 1 | foo = bar.field;\n" + "TEST PREFIX: | ^~~~~~\n" + "TEST PREFIX: | ------\n", + dc.test_show_locus (richloc)); + } +} + +/* Replace fix-it hint: replacing "field" with "m_field". */ + +static void +test_one_liner_fixit_replace () +{ + test_context dc; + location_t start = linemap_position_for_column (line_table, 11); + location_t finish = linemap_position_for_column (line_table, 15); + location_t field = make_location (start, start, finish); + rich_location richloc (line_table, field); + richloc.add_fixit_replace ("m_field"); + ASSERT_STREQ (" foo = bar.field;\n" + " ^~~~~\n" + " m_field\n", + dc.test_show_locus (richloc)); +} + +/* Replace fix-it hint: replacing "field" with "m_field", + but where the caret was elsewhere. */ + +static void +test_one_liner_fixit_replace_non_equal_range () +{ + test_context dc; + location_t equals = linemap_position_for_column (line_table, 5); + location_t start = linemap_position_for_column (line_table, 11); + location_t finish = linemap_position_for_column (line_table, 15); + rich_location richloc (line_table, equals); + source_range range; + range.m_start = start; + range.m_finish = finish; + richloc.add_fixit_replace (range, "m_field"); + /* The replacement range is not indicated in the annotation line, so + it should be indicated via an additional underline. */ + ASSERT_STREQ (" foo = bar.field;\n" + " ^\n" + " -----\n" + " m_field\n", + dc.test_show_locus (richloc)); +} + +/* Replace fix-it hint: replacing "field" with "m_field", + where the caret was elsewhere, but where a secondary range + exactly covers "field". */ + +static void +test_one_liner_fixit_replace_equal_secondary_range () +{ + test_context dc; + location_t equals = linemap_position_for_column (line_table, 5); + location_t start = linemap_position_for_column (line_table, 11); + location_t finish = linemap_position_for_column (line_table, 15); + rich_location richloc (line_table, equals); + location_t field = make_location (start, start, finish); + richloc.add_range (field); + richloc.add_fixit_replace (field, "m_field"); + /* The replacement range is indicated in the annotation line, + so it shouldn't be indicated via an additional underline. */ + ASSERT_STREQ (" foo = bar.field;\n" + " ^ ~~~~~\n" + " m_field\n", + dc.test_show_locus (richloc)); +} + +/* Verify that we can use ad-hoc locations when adding fixits to a + rich_location. */ + +static void +test_one_liner_fixit_validation_adhoc_locations () +{ + /* Generate a range that's too long to be packed, so must + be stored as an ad-hoc location (given the defaults + of 5 or 7 bits or 0 bits of packed range); 150 columns > 2**7. */ + const location_t c7 = linemap_position_for_column (line_table, 7); + const location_t c157 = linemap_position_for_column (line_table, 157); + const location_t loc = make_location (c7, c7, c157); + + if (c157 > LINE_MAP_MAX_LOCATION_WITH_COLS) + return; + + ASSERT_TRUE (IS_ADHOC_LOC (loc)); + + /* Insert. */ + { + rich_location richloc (line_table, loc); + richloc.add_fixit_insert_before (loc, "test"); + /* It should not have been discarded by the validator. */ + ASSERT_EQ (1, richloc.get_num_fixit_hints ()); + + test_context dc; + ASSERT_STREQ (" foo = bar.field;\n" + " ^~~~~~~~~~ " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " \n" + " test\n", + dc.test_show_locus (richloc)); + } + + /* Remove. */ + { + rich_location richloc (line_table, loc); + source_range range = source_range::from_locations (loc, c157); + richloc.add_fixit_remove (range); + /* It should not have been discarded by the validator. */ + ASSERT_EQ (1, richloc.get_num_fixit_hints ()); + + test_context dc; + ASSERT_STREQ (" foo = bar.field;\n" + " ^~~~~~~~~~ " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " \n" + " -----------------------------------------" + "----------" + "----------" + "----------" + "----------" + "----------" + "----------" + "----------" + "----------" + "----------" + "----------" + "----------\n", + dc.test_show_locus (richloc)); + } + + /* Replace. */ + { + rich_location richloc (line_table, loc); + source_range range = source_range::from_locations (loc, c157); + richloc.add_fixit_replace (range, "test"); + /* It should not have been discarded by the validator. */ + ASSERT_EQ (1, richloc.get_num_fixit_hints ()); + + test_context dc; + ASSERT_STREQ (" foo = bar.field;\n" + " ^~~~~~~~~~ " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " \n" + " test\n", + dc.test_show_locus (richloc)); + } +} + +/* Test of consolidating insertions at the same location. */ + +static void +test_one_liner_many_fixits_1 () +{ + test_context dc; + location_t equals = linemap_position_for_column (line_table, 5); + rich_location richloc (line_table, equals); + for (int i = 0; i < 19; i++) + richloc.add_fixit_insert_before ("a"); + ASSERT_EQ (1, richloc.get_num_fixit_hints ()); + ASSERT_STREQ (" foo = bar.field;\n" + " ^\n" + " aaaaaaaaaaaaaaaaaaa\n", + dc.test_show_locus (richloc)); +} + +/* Ensure that we can add an arbitrary number of fix-it hints to a + rich_location, even if they are not consolidated. */ + +static void +test_one_liner_many_fixits_2 () +{ + test_context dc; + location_t equals = linemap_position_for_column (line_table, 5); + rich_location richloc (line_table, equals); + for (int i = 0; i < 19; i++) + { + location_t loc = linemap_position_for_column (line_table, (i * 2) + 1); + richloc.add_fixit_insert_before (loc, "a"); + } + ASSERT_EQ (19, richloc.get_num_fixit_hints ()); + ASSERT_STREQ (" foo = bar.field;\n" + " ^\n" + " a a a a a a a a a a a a a a a a a a a\n", + dc.test_show_locus (richloc)); +} + +/* Test of labeling the ranges within a rich_location. */ + +static void +test_one_liner_labels () +{ + location_t foo + = make_location (linemap_position_for_column (line_table, 1), + linemap_position_for_column (line_table, 1), + linemap_position_for_column (line_table, 3)); + location_t bar + = make_location (linemap_position_for_column (line_table, 7), + linemap_position_for_column (line_table, 7), + linemap_position_for_column (line_table, 9)); + location_t field + = make_location (linemap_position_for_column (line_table, 11), + linemap_position_for_column (line_table, 11), + linemap_position_for_column (line_table, 15)); + + /* Example where all the labels fit on one line. */ + { + text_range_label label0 ("0"); + text_range_label label1 ("1"); + text_range_label label2 ("2"); + gcc_rich_location richloc (foo, &label0, nullptr); + richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1); + richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2); + + { + test_context dc; + ASSERT_STREQ (" foo = bar.field;\n" + " ^~~ ~~~ ~~~~~\n" + " | | |\n" + " 0 1 2\n", + dc.test_show_locus (richloc)); + } + + /* Verify that we can disable label-printing. */ + { + test_context dc; + dc.show_labels (false); + ASSERT_STREQ (" foo = bar.field;\n" + " ^~~ ~~~ ~~~~~\n", + dc.test_show_locus (richloc)); + } + } + + /* Example where the labels need extra lines. */ + { + text_range_label label0 ("label 0"); + text_range_label label1 ("label 1"); + text_range_label label2 ("label 2"); + gcc_rich_location richloc (foo, &label0, nullptr); + richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1); + richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2); + + test_context dc; + ASSERT_STREQ (" foo = bar.field;\n" + " ^~~ ~~~ ~~~~~\n" + " | | |\n" + " | | label 2\n" + " | label 1\n" + " label 0\n", + dc.test_show_locus (richloc)); + + { + test_context dc; + dc.show_line_numbers (true); + auto out + = make_raw_html_for_locus (richloc, diagnostics::kind::error, dc); + ASSERT_STREQ + ("<table class=\"locus\">\n" + " <tbody class=\"line-span\">\n" + " <tr><td class=\"linenum\"> 1</td><td class=\"left-margin\"> </td><td class=\"source\">foo = bar.field;</td></tr>\n" + " <tr><td class=\"linenum\"> </td><td class=\"left-margin\"> </td><td class=\"annotation\">^~~ ~~~ ~~~~~</td></tr>\n" + " <tr><td class=\"linenum\"> </td><td class=\"left-margin\"> </td><td class=\"annotation\">| | |</td></tr>\n" + " <tr><td class=\"linenum\"> </td><td class=\"left-margin\"> </td><td class=\"annotation\">| | label 2</td></tr>\n" + " <tr><td class=\"linenum\"> </td><td class=\"left-margin\"> </td><td class=\"annotation\">| label 1</td></tr>\n" + " <tr><td class=\"linenum\"> </td><td class=\"left-margin\"> </td><td class=\"annotation\">label 0</td></tr>\n" + " </tbody>\n" + "</table>\n", + out.get ()); + } + } + + /* Example of boundary conditions: label 0 and 1 have just enough clearance, + but label 1 just touches label 2. */ + { + text_range_label label0 ("aaaaa"); + text_range_label label1 ("bbbb"); + text_range_label label2 ("c"); + gcc_rich_location richloc (foo, &label0, nullptr); + richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1); + richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2); + + test_context dc; + ASSERT_STREQ (" foo = bar.field;\n" + " ^~~ ~~~ ~~~~~\n" + " | | |\n" + " | | c\n" + " aaaaa bbbb\n", + dc.test_show_locus (richloc)); + } + + /* Example of out-of-order ranges (thus requiring a sort). */ + { + text_range_label label0 ("0"); + text_range_label label1 ("1"); + text_range_label label2 ("2"); + gcc_rich_location richloc (field, &label0, nullptr); + richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1); + richloc.add_range (foo, SHOW_RANGE_WITHOUT_CARET, &label2); + + test_context dc; + ASSERT_STREQ (" foo = bar.field;\n" + " ~~~ ~~~ ^~~~~\n" + " | | |\n" + " 2 1 0\n", + dc.test_show_locus (richloc)); + } + + /* Ensure we don't ICE if multiple ranges with labels are on + the same point. */ + { + text_range_label label0 ("label 0"); + text_range_label label1 ("label 1"); + text_range_label label2 ("label 2"); + gcc_rich_location richloc (bar, &label0, nullptr); + richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1); + richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label2); + + test_context dc; + ASSERT_STREQ (" foo = bar.field;\n" + " ^~~\n" + " |\n" + " label 0\n" + " label 1\n" + " label 2\n", + dc.test_show_locus (richloc)); + } + + /* Example of out-of-order ranges (thus requiring a sort), where + they overlap, and there are multiple ranges on the same point. */ + { + text_range_label label_0a ("label 0a"); + text_range_label label_1a ("label 1a"); + text_range_label label_2a ("label 2a"); + text_range_label label_0b ("label 0b"); + text_range_label label_1b ("label 1b"); + text_range_label label_2b ("label 2b"); + text_range_label label_0c ("label 0c"); + text_range_label label_1c ("label 1c"); + text_range_label label_2c ("label 2c"); + gcc_rich_location richloc (field, &label_0a, nullptr); + richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label_1a); + richloc.add_range (foo, SHOW_RANGE_WITHOUT_CARET, &label_2a); + + richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label_0b); + richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label_1b); + richloc.add_range (foo, SHOW_RANGE_WITHOUT_CARET, &label_2b); + + richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label_0c); + richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label_1c); + richloc.add_range (foo, SHOW_RANGE_WITHOUT_CARET, &label_2c); + + test_context dc; + ASSERT_STREQ (" foo = bar.field;\n" + " ~~~ ~~~ ^~~~~\n" + " | | |\n" + " | | label 0a\n" + " | | label 0b\n" + " | | label 0c\n" + " | label 1a\n" + " | label 1b\n" + " | label 1c\n" + " label 2a\n" + " label 2b\n" + " label 2c\n", + dc.test_show_locus (richloc)); + } + + /* Verify that a nullptr result from range_label::get_text is + handled gracefully. */ + { + text_range_label label (nullptr); + gcc_rich_location richloc (bar, &label, nullptr); + + test_context dc; + ASSERT_STREQ (" foo = bar.field;\n" + " ^~~\n", + dc.test_show_locus (richloc)); + } + + /* TODO: example of formatted printing (needs to be in + gcc-rich-location.cc due to Makefile.in issues). */ +} + +/* Run the various one-liner tests. */ + +static void +test_diagnostic_show_locus_one_liner (const line_table_case &case_) +{ + /* Create a tempfile and write some text to it. + ....................0000000001111111. + ....................1234567890123456. */ + const char *content = "foo = bar.field;\n"; + + source_printing_fixture f (case_, content); + + location_t line_end = linemap_position_for_column (line_table, 16); + + /* Don't attempt to run the tests if column data might be unavailable. */ + if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS) + return; + + ASSERT_STREQ (f.get_filename (), LOCATION_FILE (line_end)); + ASSERT_EQ (1, LOCATION_LINE (line_end)); + ASSERT_EQ (16, LOCATION_COLUMN (line_end)); + + test_one_liner_simple_caret (); + test_one_liner_no_column (); + test_one_liner_caret_and_range (); + test_one_liner_multiple_carets_and_ranges (); + test_one_liner_fixit_insert_before (); + test_one_liner_fixit_insert_after (); + test_one_liner_fixit_remove (); + test_one_liner_fixit_replace (); + test_one_liner_fixit_replace_non_equal_range (); + test_one_liner_fixit_replace_equal_secondary_range (); + test_one_liner_fixit_validation_adhoc_locations (); + test_one_liner_many_fixits_1 (); + test_one_liner_many_fixits_2 (); + test_one_liner_labels (); +} + +/* Version of all one-liner tests exercising multibyte awareness. + These are all called from test_diagnostic_show_locus_one_liner, + which uses source_printing_fixture_one_liner_utf8 to create + the test file; see the notes in diagnostic-show-locus-selftest.h. + + Note: all of the below asserts would be easier to read if we used UTF-8 + directly in the string constants, but it seems better not to demand the + host compiler support this, when it isn't otherwise necessary. Instead, + whenever an extended character appears in a string, we put a line break + after it so that all succeeding characters can appear visually at the + correct display column. */ + +/* Just a caret. */ + +static void +test_one_liner_simple_caret_utf8 () +{ + test_context dc; + location_t caret = linemap_position_for_column (line_table, 18); + rich_location richloc (line_table, caret); + ASSERT_STREQ (" \xf0\x9f\x98\x82" + "_foo = \xcf\x80" + "_bar.\xf0\x9f\x98\x82" + "_field\xcf\x80" + ";\n" + " ^\n", + dc.test_show_locus (richloc)); +} + +/* Caret and range. */ +static void +test_one_liner_caret_and_range_utf8 () +{ + test_context dc; + location_t caret = linemap_position_for_column (line_table, 18); + location_t start = linemap_position_for_column (line_table, 12); + location_t finish = linemap_position_for_column (line_table, 30); + location_t loc = make_location (caret, start, finish); + rich_location richloc (line_table, loc); + ASSERT_STREQ (" \xf0\x9f\x98\x82" + "_foo = \xcf\x80" + "_bar.\xf0\x9f\x98\x82" + "_field\xcf\x80" + ";\n" + " ~~~~~^~~~~~~~~~\n", + dc.test_show_locus (richloc)); +} + +/* Multiple ranges and carets. */ + +static void +test_one_liner_multiple_carets_and_ranges_utf8 () +{ + test_context dc; + location_t foo + = make_location (linemap_position_for_column (line_table, 7), + linemap_position_for_column (line_table, 1), + linemap_position_for_column (line_table, 8)); + dc.set_caret_char (0, 'A'); + + location_t bar + = make_location (linemap_position_for_column (line_table, 16), + linemap_position_for_column (line_table, 12), + linemap_position_for_column (line_table, 17)); + dc.set_caret_char (1, 'B'); + + location_t field + = make_location (linemap_position_for_column (line_table, 26), + linemap_position_for_column (line_table, 19), + linemap_position_for_column (line_table, 30)); + dc.set_caret_char (2, 'C'); + rich_location richloc (line_table, foo); + richloc.add_range (bar, SHOW_RANGE_WITH_CARET); + richloc.add_range (field, SHOW_RANGE_WITH_CARET); + ASSERT_STREQ (" \xf0\x9f\x98\x82" + "_foo = \xcf\x80" + "_bar.\xf0\x9f\x98\x82" + "_field\xcf\x80" + ";\n" + " ~~~~A~ ~~~B~ ~~~~~C~~~\n", + dc.test_show_locus (richloc)); +} + +/* Insertion fix-it hint: adding an "&" to the front of "P_bar.field". */ + +static void +test_one_liner_fixit_insert_before_utf8 () +{ + test_context dc; + location_t caret = linemap_position_for_column (line_table, 12); + rich_location richloc (line_table, caret); + richloc.add_fixit_insert_before ("&"); + ASSERT_STREQ (" \xf0\x9f\x98\x82" + "_foo = \xcf\x80" + "_bar.\xf0\x9f\x98\x82" + "_field\xcf\x80" + ";\n" + " ^\n" + " &\n", + dc.test_show_locus (richloc)); +} + +/* Insertion fix-it hint: adding a "[0]" after "SS_foo". */ + +static void +test_one_liner_fixit_insert_after_utf8 () +{ + test_context dc; + location_t start = linemap_position_for_column (line_table, 1); + location_t finish = linemap_position_for_column (line_table, 8); + location_t foo = make_location (start, start, finish); + rich_location richloc (line_table, foo); + richloc.add_fixit_insert_after ("[0]"); + ASSERT_STREQ (" \xf0\x9f\x98\x82" + "_foo = \xcf\x80" + "_bar.\xf0\x9f\x98\x82" + "_field\xcf\x80" + ";\n" + " ^~~~~~\n" + " [0]\n", + dc.test_show_locus (richloc)); +} + +/* Removal fix-it hint: removal of the ".SS_fieldP". */ + +static void +test_one_liner_fixit_remove_utf8 () +{ + test_context dc; + location_t start = linemap_position_for_column (line_table, 18); + location_t finish = linemap_position_for_column (line_table, 30); + location_t dot = make_location (start, start, finish); + rich_location richloc (line_table, dot); + richloc.add_fixit_remove (); + ASSERT_STREQ (" \xf0\x9f\x98\x82" + "_foo = \xcf\x80" + "_bar.\xf0\x9f\x98\x82" + "_field\xcf\x80" + ";\n" + " ^~~~~~~~~~\n" + " ----------\n", + dc.test_show_locus (richloc)); +} + +/* Replace fix-it hint: replacing "SS_fieldP" with "m_SSfieldP". */ + +static void +test_one_liner_fixit_replace_utf8 () +{ + test_context dc; + location_t start = linemap_position_for_column (line_table, 19); + location_t finish = linemap_position_for_column (line_table, 30); + location_t field = make_location (start, start, finish); + rich_location richloc (line_table, field); + richloc.add_fixit_replace ("m_\xf0\x9f\x98\x82_field\xcf\x80"); + ASSERT_STREQ (" \xf0\x9f\x98\x82" + "_foo = \xcf\x80" + "_bar.\xf0\x9f\x98\x82" + "_field\xcf\x80" + ";\n" + " ^~~~~~~~~\n" + " m_\xf0\x9f\x98\x82" + "_field\xcf\x80\n", + dc.test_show_locus (richloc)); +} + +/* Replace fix-it hint: replacing "SS_fieldP" with "m_SSfieldP", + but where the caret was elsewhere. */ + +static void +test_one_liner_fixit_replace_non_equal_range_utf8 () +{ + test_context dc; + location_t equals = linemap_position_for_column (line_table, 10); + location_t start = linemap_position_for_column (line_table, 19); + location_t finish = linemap_position_for_column (line_table, 30); + rich_location richloc (line_table, equals); + source_range range; + range.m_start = start; + range.m_finish = finish; + richloc.add_fixit_replace (range, "m_\xf0\x9f\x98\x82_field\xcf\x80"); + /* The replacement range is not indicated in the annotation line, so + it should be indicated via an additional underline. */ + ASSERT_STREQ (" \xf0\x9f\x98\x82" + "_foo = \xcf\x80" + "_bar.\xf0\x9f\x98\x82" + "_field\xcf\x80" + ";\n" + " ^\n" + " ---------\n" + " m_\xf0\x9f\x98\x82" + "_field\xcf\x80\n", + dc.test_show_locus (richloc)); +} + +/* Replace fix-it hint: replacing "SS_fieldP" with "m_SSfieldP", + where the caret was elsewhere, but where a secondary range + exactly covers "field". */ + +static void +test_one_liner_fixit_replace_equal_secondary_range_utf8 () +{ + test_context dc; + location_t equals = linemap_position_for_column (line_table, 10); + location_t start = linemap_position_for_column (line_table, 19); + location_t finish = linemap_position_for_column (line_table, 30); + rich_location richloc (line_table, equals); + location_t field = make_location (start, start, finish); + richloc.add_range (field); + richloc.add_fixit_replace (field, "m_\xf0\x9f\x98\x82_field\xcf\x80"); + /* The replacement range is indicated in the annotation line, + so it shouldn't be indicated via an additional underline. */ + ASSERT_STREQ (" \xf0\x9f\x98\x82" + "_foo = \xcf\x80" + "_bar.\xf0\x9f\x98\x82" + "_field\xcf\x80" + ";\n" + " ^ ~~~~~~~~~\n" + " m_\xf0\x9f\x98\x82" + "_field\xcf\x80\n", + dc.test_show_locus (richloc)); +} + +/* Verify that we can use ad-hoc locations when adding fixits to a + rich_location. */ + +static void +test_one_liner_fixit_validation_adhoc_locations_utf8 () +{ + /* Generate a range that's too long to be packed, so must + be stored as an ad-hoc location (given the defaults + of 5 bits or 7 bits or 0 bits of packed range); 150 columns > 2**7. */ + const location_t c12 = linemap_position_for_column (line_table, 12); + const location_t c162 = linemap_position_for_column (line_table, 162); + const location_t loc = make_location (c12, c12, c162); + + if (c162 > LINE_MAP_MAX_LOCATION_WITH_COLS) + return; + + ASSERT_TRUE (IS_ADHOC_LOC (loc)); + + /* Insert. */ + { + rich_location richloc (line_table, loc); + richloc.add_fixit_insert_before (loc, "test"); + /* It should not have been discarded by the validator. */ + ASSERT_EQ (1, richloc.get_num_fixit_hints ()); + + test_context dc; + ASSERT_STREQ (" \xf0\x9f\x98\x82" + "_foo = \xcf\x80" + "_bar.\xf0\x9f\x98\x82" + "_field\xcf\x80" + ";\n" + " ^~~~~~~~~~~~~~~~ " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " \n" + " test\n", + dc.test_show_locus (richloc)); + } + + /* Remove. */ + { + rich_location richloc (line_table, loc); + source_range range = source_range::from_locations (loc, c162); + richloc.add_fixit_remove (range); + /* It should not have been discarded by the validator. */ + ASSERT_EQ (1, richloc.get_num_fixit_hints ()); + + test_context dc; + ASSERT_STREQ (" \xf0\x9f\x98\x82" + "_foo = \xcf\x80" + "_bar.\xf0\x9f\x98\x82" + "_field\xcf\x80" + ";\n" + " ^~~~~~~~~~~~~~~~ " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " \n" + " -------------------------------------" + "----------" + "----------" + "----------" + "----------" + "----------" + "----------" + "----------" + "----------" + "----------" + "----------" + "----------\n", + dc.test_show_locus (richloc)); + } + + /* Replace. */ + { + rich_location richloc (line_table, loc); + source_range range = source_range::from_locations (loc, c162); + richloc.add_fixit_replace (range, "test"); + /* It should not have been discarded by the validator. */ + ASSERT_EQ (1, richloc.get_num_fixit_hints ()); + + test_context dc; + ASSERT_STREQ (" \xf0\x9f\x98\x82" + "_foo = \xcf\x80" + "_bar.\xf0\x9f\x98\x82" + "_field\xcf\x80" + ";\n" + " ^~~~~~~~~~~~~~~~ " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " \n" + " test\n", + dc.test_show_locus (richloc)); + } +} + +/* Test of consolidating insertions at the same location. */ + +static void +test_one_liner_many_fixits_1_utf8 () +{ + test_context dc; + location_t equals = linemap_position_for_column (line_table, 10); + rich_location richloc (line_table, equals); + for (int i = 0; i < 19; i++) + richloc.add_fixit_insert_before (i & 1 ? "@" : "\xcf\x80"); + ASSERT_EQ (1, richloc.get_num_fixit_hints ()); + ASSERT_STREQ (" \xf0\x9f\x98\x82" + "_foo = \xcf\x80" + "_bar.\xf0\x9f\x98\x82" + "_field\xcf\x80" + ";\n" + " ^\n" + " \xcf\x80@\xcf\x80@\xcf\x80@\xcf\x80@\xcf\x80@" + "\xcf\x80@\xcf\x80@\xcf\x80@\xcf\x80@\xcf\x80\n", + dc.test_show_locus (richloc)); +} + +/* Ensure that we can add an arbitrary number of fix-it hints to a + rich_location, even if they are not consolidated. */ + +static void +test_one_liner_many_fixits_2_utf8 () +{ + test_context dc; + location_t equals = linemap_position_for_column (line_table, 10); + rich_location richloc (line_table, equals); + const int nlocs = 19; + int locs[nlocs] = {1, 5, 7, 9, 11, 14, 16, 18, 23, 25, 27, 29, 32, + 34, 36, 38, 40, 42, 44}; + for (int i = 0; i != nlocs; ++i) + { + location_t loc = linemap_position_for_column (line_table, locs[i]); + richloc.add_fixit_insert_before (loc, i & 1 ? "@" : "\xcf\x80"); + } + + ASSERT_EQ (nlocs, richloc.get_num_fixit_hints ()); + ASSERT_STREQ (" \xf0\x9f\x98\x82" + "_foo = \xcf\x80" + "_bar.\xf0\x9f\x98\x82" + "_field\xcf\x80" + ";\n" + " ^\n" + " \xcf\x80 @ \xcf\x80 @ \xcf\x80 @ \xcf\x80 @ \xcf\x80 @" + " \xcf\x80 @ \xcf\x80 @ \xcf\x80 @ \xcf\x80 @ \xcf\x80\n", + dc.test_show_locus (richloc)); +} + +/* Test of labeling the ranges within a rich_location. */ + +static void +test_one_liner_labels_utf8 () +{ + location_t foo + = make_location (linemap_position_for_column (line_table, 1), + linemap_position_for_column (line_table, 1), + linemap_position_for_column (line_table, 8)); + location_t bar + = make_location (linemap_position_for_column (line_table, 12), + linemap_position_for_column (line_table, 12), + linemap_position_for_column (line_table, 17)); + location_t field + = make_location (linemap_position_for_column (line_table, 19), + linemap_position_for_column (line_table, 19), + linemap_position_for_column (line_table, 30)); + + /* Example where all the labels fit on one line. */ + { + /* These three labels contain multibyte characters such that their byte + lengths are respectively (12, 10, 18), but their display widths are only + (6, 5, 9). All three fit on the line when considering the display + widths, but not when considering the byte widths, so verify that we do + indeed put them all on one line. */ + text_range_label label0 + ("\xcf\x80\xcf\x80\xcf\x80\xcf\x80\xcf\x80\xcf\x80"); + text_range_label label1 + ("\xf0\x9f\x98\x82\xf0\x9f\x98\x82\xcf\x80"); + text_range_label label2 + ("\xf0\x9f\x98\x82\xcf\x80\xf0\x9f\x98\x82\xf0\x9f\x98\x82\xcf\x80" + "\xcf\x80"); + gcc_rich_location richloc (foo, &label0, nullptr); + richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1); + richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2); + + { + test_context dc; + ASSERT_STREQ (" \xf0\x9f\x98\x82" + "_foo = \xcf\x80" + "_bar.\xf0\x9f\x98\x82" + "_field\xcf\x80" + ";\n" + " ^~~~~~ ~~~~~ ~~~~~~~~~\n" + " | | |\n" + " \xcf\x80\xcf\x80\xcf\x80\xcf\x80\xcf\x80\xcf\x80" + " \xf0\x9f\x98\x82\xf0\x9f\x98\x82\xcf\x80" + " \xf0\x9f\x98\x82\xcf\x80\xf0\x9f\x98\x82" + "\xf0\x9f\x98\x82\xcf\x80\xcf\x80\n", + dc.test_show_locus (richloc)); + } + + } + + /* Example where the labels need extra lines. */ + { + text_range_label label0 ("label 0\xf0\x9f\x98\x82"); + text_range_label label1 ("label 1\xcf\x80"); + text_range_label label2 ("label 2\xcf\x80"); + gcc_rich_location richloc (foo, &label0, nullptr); + richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1); + richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2); + + test_context dc; + ASSERT_STREQ (" \xf0\x9f\x98\x82" + "_foo = \xcf\x80" + "_bar.\xf0\x9f\x98\x82" + "_field\xcf\x80" + ";\n" + " ^~~~~~ ~~~~~ ~~~~~~~~~\n" + " | | |\n" + " | | label 2\xcf\x80\n" + " | label 1\xcf\x80\n" + " label 0\xf0\x9f\x98\x82\n", + dc.test_show_locus (richloc)); + } + + /* Example of boundary conditions: label 0 and 1 have just enough clearance, + but label 1 just touches label 2. */ + { + text_range_label label0 ("aaaaa\xf0\x9f\x98\x82\xcf\x80"); + text_range_label label1 ("bb\xf0\x9f\x98\x82\xf0\x9f\x98\x82"); + text_range_label label2 ("c"); + gcc_rich_location richloc (foo, &label0, nullptr); + richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1); + richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2); + + test_context dc; + ASSERT_STREQ (" \xf0\x9f\x98\x82" + "_foo = \xcf\x80" + "_bar.\xf0\x9f\x98\x82" + "_field\xcf\x80" + ";\n" + " ^~~~~~ ~~~~~ ~~~~~~~~~\n" + " | | |\n" + " | | c\n" + " aaaaa\xf0\x9f\x98\x82\xcf\x80" + " bb\xf0\x9f\x98\x82\xf0\x9f\x98\x82\n", + dc.test_show_locus (richloc)); + } + + /* Example of escaping the source lines. */ + { + text_range_label label0 ("label 0\xf0\x9f\x98\x82"); + text_range_label label1 ("label 1\xcf\x80"); + text_range_label label2 ("label 2\xcf\x80"); + gcc_rich_location richloc (foo, &label0, nullptr); + richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1); + richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2); + richloc.set_escape_on_output (true); + + { + test_context dc; + dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_UNICODE); + ASSERT_STREQ (" <U+1F602>_foo = <U+03C0>_bar.<U+1F602>_field<U+03C0>;\n" + " ^~~~~~~~~~~~~ ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~\n" + " | | |\n" + " label 0\xf0\x9f\x98\x82" + /* ... */ " label 1\xcf\x80" + /* ...................*/ " label 2\xcf\x80\n", + dc.test_show_locus (richloc)); + } + { + test_context dc; + dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_BYTES); + ASSERT_STREQ + (" <f0><9f><98><82>_foo = <cf><80>_bar.<f0><9f><98><82>_field<cf><80>;\n" + " ^~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + " | | |\n" + " label 0\xf0\x9f\x98\x82" + /* ... */ " label 1\xcf\x80" + /* ..........................*/ " label 2\xcf\x80\n", + dc.test_show_locus (richloc)); + } + } +} + +/* Make sure that colorization codes don't interrupt a multibyte + sequence, which would corrupt it. */ +static void +test_one_liner_colorized_utf8 () +{ + test_context dc; + dc.colorize_source (true); + diagnostic_color_init (&dc, DIAGNOSTICS_COLOR_YES); + const location_t pi = linemap_position_for_column (line_table, 12); + rich_location richloc (line_table, pi); + + /* In order to avoid having the test depend on exactly how the colorization + was effected, just confirm there are two pi characters in the output. */ + const char *result = dc.test_show_locus (richloc); + const char *null_term = result + strlen (result); + const char *first_pi = strstr (result, "\xcf\x80"); + ASSERT_TRUE (first_pi && first_pi <= null_term - 2); + ASSERT_STR_CONTAINS (first_pi + 2, "\xcf\x80"); +} + +static const char * const one_liner_utf8_content + /* Display columns. + 0000000000000000000000011111111111111111111111111111112222222222222 + 1111111122222222345678900000000123456666666677777777890123444444445 */ + = "\xf0\x9f\x98\x82_foo = \xcf\x80_bar.\xf0\x9f\x98\x82_field\xcf\x80;\n"; + /* 0000000000000000000001111111111111111111222222222222222222222233333 + 1111222233334444567890122223333456789999000011112222345678999900001 + Byte columns. */ + +source_printing_fixture_one_liner_utf8:: +source_printing_fixture_one_liner_utf8 (const line_table_case &case_) +: source_printing_fixture (case_, one_liner_utf8_content) +{ +} + +/* Run the various one-liner tests. */ + +static void +test_diagnostic_show_locus_one_liner_utf8 (const line_table_case &case_) +{ + source_printing_fixture_one_liner_utf8 f (case_); + + location_t line_end = linemap_position_for_column (line_table, 31); + + /* Don't attempt to run the tests if column data might be unavailable. */ + if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS) + return; + + ASSERT_STREQ (f.get_filename (), LOCATION_FILE (line_end)); + ASSERT_EQ (1, LOCATION_LINE (line_end)); + ASSERT_EQ (31, LOCATION_COLUMN (line_end)); + + diagnostics::char_span lspan = f.m_fc.get_source_line (f.get_filename (), 1); + ASSERT_EQ (25, cpp_display_width (lspan.get_buffer (), lspan.length (), + def_policy ())); + ASSERT_EQ (25, location_compute_display_column (f.m_fc, + expand_location (line_end), + def_policy ())); + + test_one_liner_simple_caret_utf8 (); + test_one_liner_caret_and_range_utf8 (); + test_one_liner_multiple_carets_and_ranges_utf8 (); + test_one_liner_fixit_insert_before_utf8 (); + test_one_liner_fixit_insert_after_utf8 (); + test_one_liner_fixit_remove_utf8 (); + test_one_liner_fixit_replace_utf8 (); + test_one_liner_fixit_replace_non_equal_range_utf8 (); + test_one_liner_fixit_replace_equal_secondary_range_utf8 (); + test_one_liner_fixit_validation_adhoc_locations_utf8 (); + test_one_liner_many_fixits_1_utf8 (); + test_one_liner_many_fixits_2_utf8 (); + test_one_liner_labels_utf8 (); + test_one_liner_colorized_utf8 (); +} + +/* Verify that gcc_rich_location::add_location_if_nearby works. */ + +static void +test_add_location_if_nearby (const line_table_case &case_) +{ + /* Create a tempfile and write some text to it. + ...000000000111111111122222222223333333333. + ...123456789012345678901234567890123456789. */ + const char *content + = ("struct same_line { double x; double y; ;\n" /* line 1. */ + "struct different_line\n" /* line 2. */ + "{\n" /* line 3. */ + " double x;\n" /* line 4. */ + " double y;\n" /* line 5. */ + ";\n"); /* line 6. */ + temp_source_file tmp (SELFTEST_LOCATION, ".c", content, nullptr); + line_table_test ltt (case_); + + const line_map_ordinary *ord_map + = linemap_check_ordinary (linemap_add (line_table, LC_ENTER, false, + tmp.get_filename (), 0)); + + linemap_line_start (line_table, 1, 100); + + const location_t final_line_end + = linemap_position_for_line_and_column (line_table, ord_map, 6, 7); + + /* Don't attempt to run the tests if column data might be unavailable. */ + if (final_line_end > LINE_MAP_MAX_LOCATION_WITH_COLS) + return; + + /* Test of add_location_if_nearby on the same line as the + primary location. */ + { + test_context dc; + const location_t missing_close_brace_1_39 + = linemap_position_for_line_and_column (line_table, ord_map, 1, 39); + const location_t matching_open_brace_1_18 + = linemap_position_for_line_and_column (line_table, ord_map, 1, 18); + gcc_rich_location richloc (missing_close_brace_1_39); + bool added = richloc.add_location_if_nearby (dc, + matching_open_brace_1_18); + ASSERT_TRUE (added); + ASSERT_EQ (2, richloc.get_num_locations ()); + ASSERT_STREQ (" struct same_line { double x; double y; ;\n" + " ~ ^\n", + dc.test_show_locus (richloc)); + } + + /* Test of add_location_if_nearby on a different line to the + primary location. */ + { + test_context dc; + const location_t missing_close_brace_6_1 + = linemap_position_for_line_and_column (line_table, ord_map, 6, 1); + const location_t matching_open_brace_3_1 + = linemap_position_for_line_and_column (line_table, ord_map, 3, 1); + gcc_rich_location richloc (missing_close_brace_6_1); + bool added = richloc.add_location_if_nearby (dc, + matching_open_brace_3_1); + ASSERT_FALSE (added); + ASSERT_EQ (1, richloc.get_num_locations ()); + } +} + +/* Verify that we print fixits even if they only affect lines + outside those covered by the ranges in the rich_location. */ + +static void +test_diagnostic_show_locus_fixit_lines (const line_table_case &case_) +{ + /* Create a tempfile and write some text to it. + ...000000000111111111122222222223333333333. + ...123456789012345678901234567890123456789. */ + const char *content + = ("struct point { double x; double y; };\n" /* line 1. */ + "struct point origin = {x: 0.0,\n" /* line 2. */ + " y\n" /* line 3. */ + "\n" /* line 4. */ + "\n" /* line 5. */ + " : 0.0};\n"); /* line 6. */ + temp_source_file tmp (SELFTEST_LOCATION, ".c", content); + line_table_test ltt (case_); + + const line_map_ordinary *ord_map + = linemap_check_ordinary (linemap_add (line_table, LC_ENTER, false, + tmp.get_filename (), 0)); + + linemap_line_start (line_table, 1, 100); + + const location_t final_line_end + = linemap_position_for_line_and_column (line_table, ord_map, 6, 36); + + /* Don't attempt to run the tests if column data might be unavailable. */ + if (final_line_end > LINE_MAP_MAX_LOCATION_WITH_COLS) + return; + + /* A pair of tests for modernizing the initializers to C99-style. */ + + /* The one-liner case (line 2). */ + { + test_context dc; + const location_t x + = linemap_position_for_line_and_column (line_table, ord_map, 2, 24); + const location_t colon + = linemap_position_for_line_and_column (line_table, ord_map, 2, 25); + rich_location richloc (line_table, colon); + richloc.add_fixit_insert_before (x, "."); + richloc.add_fixit_replace (colon, "="); + ASSERT_STREQ (" struct point origin = {x: 0.0,\n" + " ^\n" + " .=\n", + dc.test_show_locus (richloc)); + } + + /* The multiline case. The caret for the rich_location is on line 6; + verify that insertion fixit on line 3 is still printed (and that + span starts are printed due to the gap between the span at line 3 + and that at line 6). */ + { + test_context dc; + const location_t y + = linemap_position_for_line_and_column (line_table, ord_map, 3, 24); + const location_t colon + = linemap_position_for_line_and_column (line_table, ord_map, 6, 25); + rich_location richloc (line_table, colon); + richloc.add_fixit_insert_before (y, "."); + richloc.add_fixit_replace (colon, "="); + ASSERT_STREQ ("FILENAME:3:24:\n" + " y\n" + " .\n" + "FILENAME:6:25:\n" + " : 0.0};\n" + " ^\n" + " =\n", + dc.test_show_locus (richloc)); + } + + /* As above, but verify the behavior of multiple line spans + with line-numbering enabled. */ + { + const location_t y + = linemap_position_for_line_and_column (line_table, ord_map, 3, 24); + const location_t colon + = linemap_position_for_line_and_column (line_table, ord_map, 6, 25); + rich_location richloc (line_table, colon); + richloc.add_fixit_insert_before (y, "."); + richloc.add_fixit_replace (colon, "="); + test_context dc; + dc.show_line_numbers (true); + ASSERT_STREQ (" 3 | y\n" + " | .\n" + "......\n" + " 6 | : 0.0};\n" + " | ^\n" + " | =\n", + dc.test_show_locus (richloc)); + } +} + + +/* Verify that fix-it hints are appropriately consolidated. + + If any fix-it hints in a rich_location involve locations beyond + LINE_MAP_MAX_LOCATION_WITH_COLS, then we can't reliably apply + the fix-it as a whole, so there should be none. + + Otherwise, verify that consecutive "replace" and "remove" fix-its + are merged, and that other fix-its remain separate. */ + +static void +test_fixit_consolidation (const line_table_case &case_) +{ + line_table_test ltt (case_); + + linemap_add (line_table, LC_ENTER, false, "test.c", 1); + + const location_t c10 = linemap_position_for_column (line_table, 10); + const location_t c15 = linemap_position_for_column (line_table, 15); + const location_t c16 = linemap_position_for_column (line_table, 16); + const location_t c17 = linemap_position_for_column (line_table, 17); + const location_t c20 = linemap_position_for_column (line_table, 20); + const location_t c21 = linemap_position_for_column (line_table, 21); + const location_t caret = c10; + + /* Insert + insert. */ + { + rich_location richloc (line_table, caret); + richloc.add_fixit_insert_before (c10, "foo"); + richloc.add_fixit_insert_before (c15, "bar"); + + if (c15 > LINE_MAP_MAX_LOCATION_WITH_COLS) + /* Bogus column info for 2nd fixit, so no fixits. */ + ASSERT_EQ (0, richloc.get_num_fixit_hints ()); + else + /* They should not have been merged. */ + ASSERT_EQ (2, richloc.get_num_fixit_hints ()); + } + + /* Insert + replace. */ + { + rich_location richloc (line_table, caret); + richloc.add_fixit_insert_before (c10, "foo"); + richloc.add_fixit_replace (source_range::from_locations (c15, c17), + "bar"); + + if (c17 > LINE_MAP_MAX_LOCATION_WITH_COLS) + /* Bogus column info for 2nd fixit, so no fixits. */ + ASSERT_EQ (0, richloc.get_num_fixit_hints ()); + else + /* They should not have been merged. */ + ASSERT_EQ (2, richloc.get_num_fixit_hints ()); + } + + /* Replace + non-consecutive insert. */ + { + rich_location richloc (line_table, caret); + richloc.add_fixit_replace (source_range::from_locations (c10, c15), + "bar"); + richloc.add_fixit_insert_before (c17, "foo"); + + if (c17 > LINE_MAP_MAX_LOCATION_WITH_COLS) + /* Bogus column info for 2nd fixit, so no fixits. */ + ASSERT_EQ (0, richloc.get_num_fixit_hints ()); + else + /* They should not have been merged. */ + ASSERT_EQ (2, richloc.get_num_fixit_hints ()); + } + + /* Replace + non-consecutive replace. */ + { + rich_location richloc (line_table, caret); + richloc.add_fixit_replace (source_range::from_locations (c10, c15), + "foo"); + richloc.add_fixit_replace (source_range::from_locations (c17, c20), + "bar"); + + if (c20 > LINE_MAP_MAX_LOCATION_WITH_COLS) + /* Bogus column info for 2nd fixit, so no fixits. */ + ASSERT_EQ (0, richloc.get_num_fixit_hints ()); + else + /* They should not have been merged. */ + ASSERT_EQ (2, richloc.get_num_fixit_hints ()); + } + + /* Replace + consecutive replace. */ + { + rich_location richloc (line_table, caret); + richloc.add_fixit_replace (source_range::from_locations (c10, c15), + "foo"); + richloc.add_fixit_replace (source_range::from_locations (c16, c20), + "bar"); + + if (c20 > LINE_MAP_MAX_LOCATION_WITH_COLS) + /* Bogus column info for 2nd fixit, so no fixits. */ + ASSERT_EQ (0, richloc.get_num_fixit_hints ()); + else + { + /* They should have been merged into a single "replace". */ + ASSERT_EQ (1, richloc.get_num_fixit_hints ()); + const fixit_hint *hint = richloc.get_fixit_hint (0); + ASSERT_STREQ ("foobar", hint->get_string ()); + ASSERT_EQ (c10, hint->get_start_loc ()); + ASSERT_EQ (c21, hint->get_next_loc ()); + } + } + + /* Replace + consecutive removal. */ + { + rich_location richloc (line_table, caret); + richloc.add_fixit_replace (source_range::from_locations (c10, c15), + "foo"); + richloc.add_fixit_remove (source_range::from_locations (c16, c20)); + + if (c20 > LINE_MAP_MAX_LOCATION_WITH_COLS) + /* Bogus column info for 2nd fixit, so no fixits. */ + ASSERT_EQ (0, richloc.get_num_fixit_hints ()); + else + { + /* They should have been merged into a single replace, with the + range extended to cover that of the removal. */ + ASSERT_EQ (1, richloc.get_num_fixit_hints ()); + const fixit_hint *hint = richloc.get_fixit_hint (0); + ASSERT_STREQ ("foo", hint->get_string ()); + ASSERT_EQ (c10, hint->get_start_loc ()); + ASSERT_EQ (c21, hint->get_next_loc ()); + } + } + + /* Consecutive removals. */ + { + rich_location richloc (line_table, caret); + richloc.add_fixit_remove (source_range::from_locations (c10, c15)); + richloc.add_fixit_remove (source_range::from_locations (c16, c20)); + + if (c20 > LINE_MAP_MAX_LOCATION_WITH_COLS) + /* Bogus column info for 2nd fixit, so no fixits. */ + ASSERT_EQ (0, richloc.get_num_fixit_hints ()); + else + { + /* They should have been merged into a single "replace-with-empty". */ + ASSERT_EQ (1, richloc.get_num_fixit_hints ()); + const fixit_hint *hint = richloc.get_fixit_hint (0); + ASSERT_STREQ ("", hint->get_string ()); + ASSERT_EQ (c10, hint->get_start_loc ()); + ASSERT_EQ (c21, hint->get_next_loc ()); + } + } +} + +/* Verify that the line_corrections machinery correctly prints + overlapping fixit-hints. */ + +static void +test_overlapped_fixit_printing (const line_table_case &case_) +{ + /* Create a tempfile and write some text to it. + ...000000000111111111122222222223333333333. + ...123456789012345678901234567890123456789. */ + const char *content + = (" foo *f = (foo *)ptr->field;\n"); + temp_source_file tmp (SELFTEST_LOCATION, ".C", content); + diagnostics::file_cache fc; + line_table_test ltt (case_); + + const line_map_ordinary *ord_map + = linemap_check_ordinary (linemap_add (line_table, LC_ENTER, false, + tmp.get_filename (), 0)); + + linemap_line_start (line_table, 1, 100); + + const location_t final_line_end + = linemap_position_for_line_and_column (line_table, ord_map, 6, 36); + + /* Don't attempt to run the tests if column data might be unavailable. */ + if (final_line_end > LINE_MAP_MAX_LOCATION_WITH_COLS) + return; + + /* A test for converting a C-style cast to a C++-style cast. */ + const location_t open_paren + = linemap_position_for_line_and_column (line_table, ord_map, 1, 12); + const location_t close_paren + = linemap_position_for_line_and_column (line_table, ord_map, 1, 18); + const location_t expr_start + = linemap_position_for_line_and_column (line_table, ord_map, 1, 19); + const location_t expr_finish + = linemap_position_for_line_and_column (line_table, ord_map, 1, 28); + const location_t expr = make_location (expr_start, expr_start, expr_finish); + + /* Various examples of fix-it hints that aren't themselves consolidated, + but for which the *printing* may need consolidation. */ + + /* Example where 3 fix-it hints are printed as one. */ + { + test_context dc; + rich_location richloc (line_table, expr); + richloc.add_fixit_replace (open_paren, "const_cast<"); + richloc.add_fixit_replace (close_paren, "> ("); + richloc.add_fixit_insert_after (")"); + + ASSERT_STREQ (" foo *f = (foo *)ptr->field;\n" + " ^~~~~~~~~~\n" + " -----------------\n" + " const_cast<foo *> (ptr->field)\n", + dc.test_show_locus (richloc)); + + /* Unit-test the line_corrections machinery. */ + char_display_policy policy (make_char_policy (dc, richloc)); + ASSERT_EQ (3, richloc.get_num_fixit_hints ()); + const fixit_hint *hint_0 = richloc.get_fixit_hint (0); + ASSERT_EQ (column_range (12, 12), + get_affected_range (fc, policy, hint_0, CU_BYTES)); + ASSERT_EQ (column_range (12, 12), + get_affected_range (fc, policy, hint_0, CU_DISPLAY_COLS)); + ASSERT_EQ (column_range (12, 22), get_printed_columns (fc, policy, hint_0)); + const fixit_hint *hint_1 = richloc.get_fixit_hint (1); + ASSERT_EQ (column_range (18, 18), + get_affected_range (fc, policy, hint_1, CU_BYTES)); + ASSERT_EQ (column_range (18, 18), + get_affected_range (fc, policy, hint_1, CU_DISPLAY_COLS)); + ASSERT_EQ (column_range (18, 20), get_printed_columns (fc, policy, hint_1)); + const fixit_hint *hint_2 = richloc.get_fixit_hint (2); + ASSERT_EQ (column_range (29, 28), + get_affected_range (fc, policy, hint_2, CU_BYTES)); + ASSERT_EQ (column_range (29, 28), + get_affected_range (fc, policy, hint_2, CU_DISPLAY_COLS)); + ASSERT_EQ (column_range (29, 29), get_printed_columns (fc, policy, hint_2)); + + /* Add each hint in turn to a line_corrections instance, + and verify that they are consolidated into one correction instance + as expected. */ + line_corrections lc (fc, policy, tmp.get_filename (), 1); + + /* The first replace hint by itself. */ + lc.add_hint (hint_0); + ASSERT_EQ (1, lc.m_corrections.length ()); + ASSERT_EQ (column_range (12, 12), lc.m_corrections[0]->m_affected_bytes); + ASSERT_EQ (column_range (12, 12), lc.m_corrections[0]->m_affected_columns); + ASSERT_EQ (column_range (12, 22), lc.m_corrections[0]->m_printed_columns); + ASSERT_STREQ ("const_cast<", lc.m_corrections[0]->m_text); + + /* After the second replacement hint, they are printed together + as a replacement (along with the text between them). */ + lc.add_hint (hint_1); + ASSERT_EQ (1, lc.m_corrections.length ()); + ASSERT_STREQ ("const_cast<foo *> (", lc.m_corrections[0]->m_text); + ASSERT_EQ (column_range (12, 18), lc.m_corrections[0]->m_affected_bytes); + ASSERT_EQ (column_range (12, 18), lc.m_corrections[0]->m_affected_columns); + ASSERT_EQ (column_range (12, 30), lc.m_corrections[0]->m_printed_columns); + + /* After the final insertion hint, they are all printed together + as a replacement (along with the text between them). */ + lc.add_hint (hint_2); + ASSERT_STREQ ("const_cast<foo *> (ptr->field)", + lc.m_corrections[0]->m_text); + ASSERT_EQ (1, lc.m_corrections.length ()); + ASSERT_EQ (column_range (12, 28), lc.m_corrections[0]->m_affected_bytes); + ASSERT_EQ (column_range (12, 28), lc.m_corrections[0]->m_affected_columns); + ASSERT_EQ (column_range (12, 41), lc.m_corrections[0]->m_printed_columns); + } + + /* Example where two are consolidated during printing. */ + { + test_context dc; + rich_location richloc (line_table, expr); + richloc.add_fixit_replace (open_paren, "CAST ("); + richloc.add_fixit_replace (close_paren, ") ("); + richloc.add_fixit_insert_after (")"); + + ASSERT_STREQ (" foo *f = (foo *)ptr->field;\n" + " ^~~~~~~~~~\n" + " -\n" + " CAST (-\n" + " ) ( )\n", + dc.test_show_locus (richloc)); + } + + /* Example where none are consolidated during printing. */ + { + test_context dc; + rich_location richloc (line_table, expr); + richloc.add_fixit_replace (open_paren, "CST ("); + richloc.add_fixit_replace (close_paren, ") ("); + richloc.add_fixit_insert_after (")"); + + ASSERT_STREQ (" foo *f = (foo *)ptr->field;\n" + " ^~~~~~~~~~\n" + " -\n" + " CST ( -\n" + " ) ( )\n", + dc.test_show_locus (richloc)); + } + + /* Example of deletion fix-it hints. */ + { + test_context dc; + rich_location richloc (line_table, expr); + richloc.add_fixit_insert_before (open_paren, "(bar *)"); + source_range victim = {open_paren, close_paren}; + richloc.add_fixit_remove (victim); + + /* This case is actually handled by fixit-consolidation, + rather than by line_corrections. */ + ASSERT_EQ (1, richloc.get_num_fixit_hints ()); + + ASSERT_STREQ (" foo *f = (foo *)ptr->field;\n" + " ^~~~~~~~~~\n" + " -------\n" + " (bar *)\n", + dc.test_show_locus (richloc)); + } + + /* Example of deletion fix-it hints that would overlap. */ + { + test_context dc; + rich_location richloc (line_table, expr); + richloc.add_fixit_insert_before (open_paren, "(longer *)"); + source_range victim = {expr_start, expr_finish}; + richloc.add_fixit_remove (victim); + + /* These fixits are not consolidated. */ + ASSERT_EQ (2, richloc.get_num_fixit_hints ()); + + /* But the corrections are. */ + ASSERT_STREQ (" foo *f = (foo *)ptr->field;\n" + " ^~~~~~~~~~\n" + " -----------------\n" + " (longer *)(foo *)\n", + dc.test_show_locus (richloc)); + } + + /* Example of insertion fix-it hints that would overlap. */ + { + test_context dc; + rich_location richloc (line_table, expr); + richloc.add_fixit_insert_before (open_paren, "LONGER THAN THE CAST"); + richloc.add_fixit_insert_after (close_paren, "TEST"); + + /* The first insertion is long enough that if printed naively, + it would overlap with the second. + Verify that they are printed as a single replacement. */ + ASSERT_STREQ (" foo *f = (foo *)ptr->field;\n" + " ^~~~~~~~~~\n" + " -------\n" + " LONGER THAN THE CAST(foo *)TEST\n", + dc.test_show_locus (richloc)); + } +} + +/* Multibyte-aware version of preceding tests. See comments above + test_one_liner_simple_caret_utf8() too, we use the same two multibyte + characters here. */ + +static void +test_overlapped_fixit_printing_utf8 (const line_table_case &case_) +{ + /* Create a tempfile and write some text to it. */ + + const char *content + /* Display columns. + 00000000000000000000000111111111111111111111111222222222222222223 + 12344444444555555556789012344444444555555556789012345678999999990 */ + = " f\xf0\x9f\x98\x82 *f = (f\xf0\x9f\x98\x82 *)ptr->field\xcf\x80;\n"; + /* 00000000000000000000011111111111111111111112222222222333333333333 + 12344445555666677778901234566667777888899990123456789012333344445 + Byte columns. */ + + temp_source_file tmp (SELFTEST_LOCATION, ".C", content); + line_table_test ltt (case_); + + const line_map_ordinary *ord_map + = linemap_check_ordinary (linemap_add (line_table, LC_ENTER, false, + tmp.get_filename (), 0)); + + linemap_line_start (line_table, 1, 100); + + const location_t final_line_end + = linemap_position_for_line_and_column (line_table, ord_map, 6, 50); + + /* Don't attempt to run the tests if column data might be unavailable. */ + if (final_line_end > LINE_MAP_MAX_LOCATION_WITH_COLS) + return; + + /* A test for converting a C-style cast to a C++-style cast. */ + const location_t open_paren + = linemap_position_for_line_and_column (line_table, ord_map, 1, 14); + const location_t close_paren + = linemap_position_for_line_and_column (line_table, ord_map, 1, 22); + const location_t expr_start + = linemap_position_for_line_and_column (line_table, ord_map, 1, 23); + const location_t expr_finish + = linemap_position_for_line_and_column (line_table, ord_map, 1, 34); + const location_t expr = make_location (expr_start, expr_start, expr_finish); + + /* Various examples of fix-it hints that aren't themselves consolidated, + but for which the *printing* may need consolidation. */ + + /* Example where 3 fix-it hints are printed as one. */ + { + test_context dc; + diagnostics::file_cache &fc = dc.get_file_cache (); + rich_location richloc (line_table, expr); + richloc.add_fixit_replace (open_paren, "const_cast<"); + richloc.add_fixit_replace (close_paren, "> ("); + richloc.add_fixit_insert_after (")"); + + ASSERT_STREQ (" f\xf0\x9f\x98\x82" + " *f = (f\xf0\x9f\x98\x82" + " *)ptr->field\xcf\x80" + ";\n" + " ^~~~~~~~~~~\n" + " ------------------\n" + " const_cast<f\xf0\x9f\x98\x82" + " *> (ptr->field\xcf\x80" + ")\n", + dc.test_show_locus (richloc)); + + /* Unit-test the line_corrections machinery. */ + char_display_policy policy (make_char_policy (dc, richloc)); + ASSERT_EQ (3, richloc.get_num_fixit_hints ()); + const fixit_hint *hint_0 = richloc.get_fixit_hint (0); + ASSERT_EQ (column_range (14, 14), + get_affected_range (fc, policy, hint_0, CU_BYTES)); + ASSERT_EQ (column_range (12, 12), + get_affected_range (fc, policy, hint_0, CU_DISPLAY_COLS)); + ASSERT_EQ (column_range (12, 22), get_printed_columns (fc, policy, hint_0)); + const fixit_hint *hint_1 = richloc.get_fixit_hint (1); + ASSERT_EQ (column_range (22, 22), + get_affected_range (fc, policy, hint_1, CU_BYTES)); + ASSERT_EQ (column_range (18, 18), + get_affected_range (fc, policy, hint_1, CU_DISPLAY_COLS)); + ASSERT_EQ (column_range (18, 20), get_printed_columns (fc, policy, hint_1)); + const fixit_hint *hint_2 = richloc.get_fixit_hint (2); + ASSERT_EQ (column_range (35, 34), + get_affected_range (fc, policy, hint_2, CU_BYTES)); + ASSERT_EQ (column_range (30, 29), + get_affected_range (fc, policy, hint_2, CU_DISPLAY_COLS)); + ASSERT_EQ (column_range (30, 30), get_printed_columns (fc, policy, hint_2)); + + /* Add each hint in turn to a line_corrections instance, + and verify that they are consolidated into one correction instance + as expected. */ + line_corrections lc (fc, policy, tmp.get_filename (), 1); + + /* The first replace hint by itself. */ + lc.add_hint (hint_0); + ASSERT_EQ (1, lc.m_corrections.length ()); + ASSERT_EQ (column_range (14, 14), lc.m_corrections[0]->m_affected_bytes); + ASSERT_EQ (column_range (12, 12), lc.m_corrections[0]->m_affected_columns); + ASSERT_EQ (column_range (12, 22), lc.m_corrections[0]->m_printed_columns); + ASSERT_STREQ ("const_cast<", lc.m_corrections[0]->m_text); + + /* After the second replacement hint, they are printed together + as a replacement (along with the text between them). */ + lc.add_hint (hint_1); + ASSERT_EQ (1, lc.m_corrections.length ()); + ASSERT_STREQ ("const_cast<f\xf0\x9f\x98\x82 *> (", + lc.m_corrections[0]->m_text); + ASSERT_EQ (column_range (14, 22), lc.m_corrections[0]->m_affected_bytes); + ASSERT_EQ (column_range (12, 18), lc.m_corrections[0]->m_affected_columns); + ASSERT_EQ (column_range (12, 30), lc.m_corrections[0]->m_printed_columns); + + /* After the final insertion hint, they are all printed together + as a replacement (along with the text between them). */ + lc.add_hint (hint_2); + ASSERT_STREQ ("const_cast<f\xf0\x9f\x98\x82 *> (ptr->field\xcf\x80)", + lc.m_corrections[0]->m_text); + ASSERT_EQ (1, lc.m_corrections.length ()); + ASSERT_EQ (column_range (14, 34), lc.m_corrections[0]->m_affected_bytes); + ASSERT_EQ (column_range (12, 29), lc.m_corrections[0]->m_affected_columns); + ASSERT_EQ (column_range (12, 42), lc.m_corrections[0]->m_printed_columns); + } + + /* Example where two are consolidated during printing. */ + { + test_context dc; + rich_location richloc (line_table, expr); + richloc.add_fixit_replace (open_paren, "CAST ("); + richloc.add_fixit_replace (close_paren, ") ("); + richloc.add_fixit_insert_after (")"); + + ASSERT_STREQ (" f\xf0\x9f\x98\x82" + " *f = (f\xf0\x9f\x98\x82" + " *)ptr->field\xcf\x80" + ";\n" + " ^~~~~~~~~~~\n" + " -\n" + " CAST (-\n" + " ) ( )\n", + dc.test_show_locus (richloc)); + } + + /* Example where none are consolidated during printing. */ + { + test_context dc; + rich_location richloc (line_table, expr); + richloc.add_fixit_replace (open_paren, "CST ("); + richloc.add_fixit_replace (close_paren, ") ("); + richloc.add_fixit_insert_after (")"); + + ASSERT_STREQ (" f\xf0\x9f\x98\x82" + " *f = (f\xf0\x9f\x98\x82" + " *)ptr->field\xcf\x80" + ";\n" + " ^~~~~~~~~~~\n" + " -\n" + " CST ( -\n" + " ) ( )\n", + dc.test_show_locus (richloc)); + } + + /* Example of deletion fix-it hints. */ + { + test_context dc; + rich_location richloc (line_table, expr); + richloc.add_fixit_insert_before (open_paren, "(bar\xf0\x9f\x98\x82 *)"); + source_range victim = {open_paren, close_paren}; + richloc.add_fixit_remove (victim); + + /* This case is actually handled by fixit-consolidation, + rather than by line_corrections. */ + ASSERT_EQ (1, richloc.get_num_fixit_hints ()); + + ASSERT_STREQ (" f\xf0\x9f\x98\x82" + " *f = (f\xf0\x9f\x98\x82" + " *)ptr->field\xcf\x80" + ";\n" + " ^~~~~~~~~~~\n" + " -------\n" + " (bar\xf0\x9f\x98\x82" + " *)\n", + dc.test_show_locus (richloc)); + } + + /* Example of deletion fix-it hints that would overlap. */ + { + test_context dc; + rich_location richloc (line_table, expr); + richloc.add_fixit_insert_before (open_paren, "(long\xf0\x9f\x98\x82 *)"); + source_range victim = {expr_start, expr_finish}; + richloc.add_fixit_remove (victim); + + /* These fixits are not consolidated. */ + ASSERT_EQ (2, richloc.get_num_fixit_hints ()); + + /* But the corrections are. */ + ASSERT_STREQ (" f\xf0\x9f\x98\x82" + " *f = (f\xf0\x9f\x98\x82" + " *)ptr->field\xcf\x80" + ";\n" + " ^~~~~~~~~~~\n" + " ------------------\n" + " (long\xf0\x9f\x98\x82" + " *)(f\xf0\x9f\x98\x82" + " *)\n", + dc.test_show_locus (richloc)); + } + + /* Example of insertion fix-it hints that would overlap. */ + { + test_context dc; + rich_location richloc (line_table, expr); + richloc.add_fixit_insert_before + (open_paren, "L\xf0\x9f\x98\x82NGER THAN THE CAST"); + richloc.add_fixit_insert_after (close_paren, "TEST"); + + /* The first insertion is long enough that if printed naively, + it would overlap with the second. + Verify that they are printed as a single replacement. */ + ASSERT_STREQ (" f\xf0\x9f\x98\x82" + " *f = (f\xf0\x9f\x98\x82" + " *)ptr->field\xcf\x80" + ";\n" + " ^~~~~~~~~~~\n" + " -------\n" + " L\xf0\x9f\x98\x82" + "NGER THAN THE CAST(f\xf0\x9f\x98\x82" + " *)TEST\n", + dc.test_show_locus (richloc)); + } +} + +/* Verify that the line_corrections machinery correctly prints + overlapping fixit-hints that have been added in the wrong + order. + Adapted from PR c/81405 seen on gcc.dg/init-excess-1.c*/ + +static void +test_overlapped_fixit_printing_2 (const line_table_case &case_) +{ + /* Create a tempfile and write some text to it. + ...000000000111111111122222222223333333333. + ...123456789012345678901234567890123456789. */ + const char *content + = ("int a5[][0][0] = { 1, 2 };\n"); + temp_source_file tmp (SELFTEST_LOCATION, ".c", content); + line_table_test ltt (case_); + + const line_map_ordinary *ord_map + = linemap_check_ordinary (linemap_add (line_table, LC_ENTER, false, + tmp.get_filename (), 0)); + + linemap_line_start (line_table, 1, 100); + + const location_t final_line_end + = linemap_position_for_line_and_column (line_table, ord_map, 1, 100); + + /* Don't attempt to run the tests if column data might be unavailable. */ + if (final_line_end > LINE_MAP_MAX_LOCATION_WITH_COLS) + return; + + const location_t col_1 + = linemap_position_for_line_and_column (line_table, ord_map, 1, 1); + const location_t col_20 + = linemap_position_for_line_and_column (line_table, ord_map, 1, 20); + const location_t col_21 + = linemap_position_for_line_and_column (line_table, ord_map, 1, 21); + const location_t col_23 + = linemap_position_for_line_and_column (line_table, ord_map, 1, 23); + const location_t col_25 + = linemap_position_for_line_and_column (line_table, ord_map, 1, 25); + + /* Two insertions, in the wrong order. */ + { + test_context dc; + diagnostics::file_cache &fc = dc.get_file_cache (); + + rich_location richloc (line_table, col_20); + richloc.add_fixit_insert_before (col_23, "{"); + richloc.add_fixit_insert_before (col_21, "}"); + + /* These fixits should be accepted; they can't be consolidated. */ + char_display_policy policy (make_char_policy (dc, richloc)); + ASSERT_EQ (2, richloc.get_num_fixit_hints ()); + const fixit_hint *hint_0 = richloc.get_fixit_hint (0); + ASSERT_EQ (column_range (23, 22), + get_affected_range (fc, policy, hint_0, CU_BYTES)); + ASSERT_EQ (column_range (23, 23), get_printed_columns (fc, policy, hint_0)); + const fixit_hint *hint_1 = richloc.get_fixit_hint (1); + ASSERT_EQ (column_range (21, 20), + get_affected_range (fc, policy, hint_1, CU_BYTES)); + ASSERT_EQ (column_range (21, 21), get_printed_columns (fc, policy, hint_1)); + + /* Verify that they're printed correctly. */ + ASSERT_STREQ (" int a5[][0][0] = { 1, 2 };\n" + " ^\n" + " } {\n", + dc.test_show_locus (richloc)); + } + + /* Various overlapping insertions, some occurring "out of order" + (reproducing the fix-it hints from PR c/81405). */ + { + test_context dc; + rich_location richloc (line_table, col_20); + + richloc.add_fixit_insert_before (col_20, "{{"); + richloc.add_fixit_insert_before (col_21, "}}"); + richloc.add_fixit_insert_before (col_23, "{"); + richloc.add_fixit_insert_before (col_21, "}"); + richloc.add_fixit_insert_before (col_23, "{{"); + richloc.add_fixit_insert_before (col_25, "}"); + richloc.add_fixit_insert_before (col_21, "}"); + richloc.add_fixit_insert_before (col_1, "{"); + richloc.add_fixit_insert_before (col_25, "}"); + + ASSERT_STREQ (" int a5[][0][0] = { 1, 2 };\n" + " ^\n" + " { -----\n" + " {{1}}}}, {{{2 }}\n", + dc.test_show_locus (richloc)); + } +} + +/* Insertion fix-it hint: adding a "break;" on a line by itself. */ + +static void +test_fixit_insert_containing_newline (const line_table_case &case_) +{ + /* Create a tempfile and write some text to it. + .........................0000000001111111. + .........................1234567890123456. */ + const char *old_content = (" case 'a':\n" /* line 1. */ + " x = a;\n" /* line 2. */ + " case 'b':\n" /* line 3. */ + " x = b;\n");/* line 4. */ + + temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content); + line_table_test ltt (case_); + linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 3); + + location_t case_start = linemap_position_for_column (line_table, 5); + location_t case_finish = linemap_position_for_column (line_table, 13); + location_t case_loc = make_location (case_start, case_start, case_finish); + location_t line_start = linemap_position_for_column (line_table, 1); + + if (case_finish > LINE_MAP_MAX_LOCATION_WITH_COLS) + return; + + /* Add a "break;" on a line by itself before line 3 i.e. before + column 1 of line 3. */ + { + rich_location richloc (line_table, case_loc); + richloc.add_fixit_insert_before (line_start, " break;\n"); + + /* Without line numbers. */ + { + test_context dc; + ASSERT_STREQ (" x = a;\n" + "+ break;\n" + " case 'b':\n" + " ^~~~~~~~~\n", + dc.test_show_locus (richloc)); + } + + /* With line numbers. */ + { + test_context dc; + dc.show_line_numbers (true); + ASSERT_STREQ (" 2 | x = a;\n" + " +++ |+ break;\n" + " 3 | case 'b':\n" + " | ^~~~~~~~~\n", + dc.test_show_locus (richloc)); + } + } + + /* Verify that attempts to add text with a newline fail when the + insertion point is *not* at the start of a line. */ + { + rich_location richloc (line_table, case_loc); + richloc.add_fixit_insert_before (case_start, "break;\n"); + ASSERT_TRUE (richloc.seen_impossible_fixit_p ()); + test_context dc; + ASSERT_STREQ (" case 'b':\n" + " ^~~~~~~~~\n", + dc.test_show_locus (richloc)); + } +} + +/* Insertion fix-it hint: adding a "#include <stdio.h>\n" to the top + of the file, where the fix-it is printed in a different line-span + to the primary range of the diagnostic. */ + +static void +test_fixit_insert_containing_newline_2 (const line_table_case &case_) +{ + /* Create a tempfile and write some text to it. + .........................0000000001111111. + .........................1234567890123456. */ + const char *old_content = ("test (int ch)\n" /* line 1. */ + "{\n" /* line 2. */ + " putchar (ch);\n" /* line 3. */ + "}\n"); /* line 4. */ + + temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content); + line_table_test ltt (case_); + + const line_map_ordinary *ord_map = linemap_check_ordinary + (linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 0)); + linemap_line_start (line_table, 1, 100); + + /* The primary range is the "putchar" token. */ + location_t putchar_start + = linemap_position_for_line_and_column (line_table, ord_map, 3, 2); + location_t putchar_finish + = linemap_position_for_line_and_column (line_table, ord_map, 3, 8); + location_t putchar_loc + = make_location (putchar_start, putchar_start, putchar_finish); + rich_location richloc (line_table, putchar_loc); + + /* Add a "#include <stdio.h>" on a line by itself at the top of the file. */ + location_t file_start + = linemap_position_for_line_and_column (line_table, ord_map, 1, 1); + richloc.add_fixit_insert_before (file_start, "#include <stdio.h>\n"); + + if (putchar_finish > LINE_MAP_MAX_LOCATION_WITH_COLS) + return; + + { + test_context dc; + ASSERT_STREQ ("FILENAME:1:1:\n" + "+#include <stdio.h>\n" + " test (int ch)\n" + "FILENAME:3:2:\n" + " putchar (ch);\n" + " ^~~~~~~\n", + dc.test_show_locus (richloc)); + } + + /* With line-numbering, the line spans are close enough to be + consolidated, since it makes little sense to skip line 2. */ + { + test_context dc; + dc.show_line_numbers (true); + ASSERT_STREQ (" +++ |+#include <stdio.h>\n" + " 1 | test (int ch)\n" + " 2 | {\n" + " 3 | putchar (ch);\n" + " | ^~~~~~~\n", + dc.test_show_locus (richloc)); + } +} + +/* Replacement fix-it hint containing a newline. + This will fail, as newlines are only supported when inserting at the + beginning of a line. */ + +static void +test_fixit_replace_containing_newline (const line_table_case &case_) +{ + /* Create a tempfile and write some text to it. + .........................0000000001111. + .........................1234567890123. */ + const char *old_content = "foo = bar ();\n"; + + temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content); + line_table_test ltt (case_); + linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 1); + + /* Replace the " = " with "\n = ", as if we were reformatting an + overly long line. */ + location_t start = linemap_position_for_column (line_table, 4); + location_t finish = linemap_position_for_column (line_table, 6); + location_t loc = linemap_position_for_column (line_table, 13); + rich_location richloc (line_table, loc); + source_range range = source_range::from_locations (start, finish); + richloc.add_fixit_replace (range, "\n ="); + + /* Arbitrary newlines are not yet supported within fix-it hints, so + the fix-it should not be displayed. */ + ASSERT_TRUE (richloc.seen_impossible_fixit_p ()); + + if (finish > LINE_MAP_MAX_LOCATION_WITH_COLS) + return; + + test_context dc; + ASSERT_STREQ (" foo = bar ();\n" + " ^\n", + dc.test_show_locus (richloc)); +} + +/* Fix-it hint, attempting to delete a newline. + This will fail, as we currently only support fix-it hints that + affect one line at a time. */ + +static void +test_fixit_deletion_affecting_newline (const line_table_case &case_) +{ + /* Create a tempfile and write some text to it. + ..........................0000000001111. + ..........................1234567890123. */ + const char *old_content = ("foo = bar (\n" + " );\n"); + + temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content); + line_table_test ltt (case_); + const line_map_ordinary *ord_map = linemap_check_ordinary + (linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 0)); + linemap_line_start (line_table, 1, 100); + + /* Attempt to delete the " (\n...)". */ + location_t start + = linemap_position_for_line_and_column (line_table, ord_map, 1, 10); + location_t caret + = linemap_position_for_line_and_column (line_table, ord_map, 1, 11); + location_t finish + = linemap_position_for_line_and_column (line_table, ord_map, 2, 7); + location_t loc = make_location (caret, start, finish); + rich_location richloc (line_table, loc); + richloc. add_fixit_remove (); + + /* Fix-it hints that affect more than one line are not yet supported, so + the fix-it should not be displayed. */ + ASSERT_TRUE (richloc.seen_impossible_fixit_p ()); + + if (finish > LINE_MAP_MAX_LOCATION_WITH_COLS) + return; + + test_context dc; + ASSERT_STREQ (" foo = bar (\n" + " ~^\n" + " );\n" + " ~ \n", + dc.test_show_locus (richloc)); +} + +static void +test_tab_expansion (const line_table_case &case_) +{ + /* Create a tempfile and write some text to it. This example uses a tabstop + of 8, as the column numbers attempt to indicate: + + .....................000.01111111111.22222333333 display + .....................123.90123456789.56789012345 columns */ + const char *content = " \t This: `\t' is a tab.\n"; + /* ....................000 00000011111 11111222222 byte + ....................123 45678901234 56789012345 columns */ + + const int tabstop = 8; + cpp_char_column_policy policy (tabstop, cpp_wcwidth); + const int first_non_ws_byte_col = 7; + const int right_quote_byte_col = 15; + const int last_byte_col = 25; + ASSERT_EQ (35, cpp_display_width (content, last_byte_col, policy)); + + temp_source_file tmp (SELFTEST_LOCATION, ".c", content); + line_table_test ltt (case_); + linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 1); + + /* Don't attempt to run the tests if column data might be unavailable. */ + location_t line_end = linemap_position_for_column (line_table, last_byte_col); + if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS) + return; + + /* Check that the leading whitespace with mixed tabs and spaces is expanded + into 11 spaces. Recall that print_line() also puts one space before + everything too. */ + { + test_context dc; + dc.m_tabstop = tabstop; + rich_location richloc (line_table, + linemap_position_for_column (line_table, + first_non_ws_byte_col)); + ASSERT_STREQ (" This: ` ' is a tab.\n" + " ^\n", + dc.test_show_locus (richloc)); + } + + /* Confirm the display width was tracked correctly across the internal tab + as well. */ + { + test_context dc; + dc.m_tabstop = tabstop; + rich_location richloc (line_table, + linemap_position_for_column (line_table, + right_quote_byte_col)); + ASSERT_STREQ (" This: ` ' is a tab.\n" + " ^\n", + dc.test_show_locus (richloc)); + } +} + +/* Verify that the escaping machinery can cope with a variety of different + invalid bytes. */ + +static void +test_escaping_bytes_1 (const line_table_case &case_) +{ + const char content[] = "before\0\1\2\3\v\x80\xff""after\n"; + const size_t sz = sizeof (content); + temp_source_file tmp (SELFTEST_LOCATION, ".c", content, sz); + line_table_test ltt (case_); + const line_map_ordinary *ord_map = linemap_check_ordinary + (linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 0)); + linemap_line_start (line_table, 1, 100); + + location_t finish + = linemap_position_for_line_and_column (line_table, ord_map, 1, + strlen (content)); + + if (finish > LINE_MAP_MAX_LOCATION_WITH_COLS) + return; + + /* Locations of the NUL and \v bytes. */ + location_t nul_loc + = linemap_position_for_line_and_column (line_table, ord_map, 1, 7); + location_t v_loc + = linemap_position_for_line_and_column (line_table, ord_map, 1, 11); + gcc_rich_location richloc (nul_loc); + richloc.add_range (v_loc); + + { + test_context dc; + ASSERT_STREQ (" before \1\2\3\v\x80\xff""after\n" + " ^ ~\n", + dc.test_show_locus (richloc)); + } + richloc.set_escape_on_output (true); + { + test_context dc; + dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_UNICODE); + ASSERT_STREQ + (" before<U+0000><U+0001><U+0002><U+0003><U+000B><80><ff>after\n" + " ^~~~~~~~ ~~~~~~~~\n", + dc.test_show_locus (richloc)); + } + { + test_context dc; + dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_BYTES); + ASSERT_STREQ (" before<00><01><02><03><0b><80><ff>after\n" + " ^~~~ ~~~~\n", + dc.test_show_locus (richloc)); + } +} + +/* As above, but verify that we handle the initial byte of a line + correctly. */ + +static void +test_escaping_bytes_2 (const line_table_case &case_) +{ + const char content[] = "\0after\n"; + const size_t sz = sizeof (content); + temp_source_file tmp (SELFTEST_LOCATION, ".c", content, sz); + line_table_test ltt (case_); + const line_map_ordinary *ord_map = linemap_check_ordinary + (linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 0)); + linemap_line_start (line_table, 1, 100); + + location_t finish + = linemap_position_for_line_and_column (line_table, ord_map, 1, + strlen (content)); + + if (finish > LINE_MAP_MAX_LOCATION_WITH_COLS) + return; + + /* Location of the NUL byte. */ + location_t nul_loc + = linemap_position_for_line_and_column (line_table, ord_map, 1, 1); + gcc_rich_location richloc (nul_loc); + + { + test_context dc; + ASSERT_STREQ (" after\n" + " ^\n", + dc.test_show_locus (richloc)); + } + richloc.set_escape_on_output (true); + { + test_context dc; + dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_UNICODE); + ASSERT_STREQ (" <U+0000>after\n" + " ^~~~~~~~\n", + dc.test_show_locus (richloc)); + } + { + test_context dc; + dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_BYTES); + ASSERT_STREQ (" <00>after\n" + " ^~~~\n", + dc.test_show_locus (richloc)); + } +} + +/* Verify that line numbers are correctly printed for the case of + a multiline range in which the width of the line numbers changes + (e.g. from "9" to "10"). */ + +static void +test_line_numbers_multiline_range () +{ + /* Create a tempfile and write some text to it. */ + pretty_printer pp; + for (int i = 0; i < 20; i++) + /* .........0000000001111111. + .............1234567890123456. */ + pp_printf (&pp, "this is line %i\n", i + 1); + temp_source_file tmp (SELFTEST_LOCATION, ".txt", pp_formatted_text (&pp)); + line_table_test ltt; + + const line_map_ordinary *ord_map = linemap_check_ordinary + (linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 0)); + linemap_line_start (line_table, 1, 100); + + /* Create a multi-line location, starting at the "line" of line 9, with + a caret on the "is" of line 10, finishing on the "this" line 11. */ + + location_t start + = linemap_position_for_line_and_column (line_table, ord_map, 9, 9); + location_t caret + = linemap_position_for_line_and_column (line_table, ord_map, 10, 6); + location_t finish + = linemap_position_for_line_and_column (line_table, ord_map, 11, 4); + location_t loc = make_location (caret, start, finish); + + test_context dc; + dc.show_line_numbers (true); + auto &source_printing_opts = dc.get_source_printing_options (); + source_printing_opts.min_margin_width = 0; + gcc_rich_location richloc (loc); + ASSERT_STREQ (" 9 | this is line 9\n" + " | ~~~~~~\n" + "10 | this is line 10\n" + " | ~~~~~^~~~~~~~~~\n" + "11 | this is line 11\n" + " | ~~~~ \n", + dc.test_show_locus (richloc)); +} + +/* Run all of the selftests within this file. */ + +void +source_printing_cc_tests () +{ + test_line_span (); + + test_layout_range_for_single_point (); + test_layout_range_for_single_line (); + test_layout_range_for_multiple_lines (); + + test_display_widths (); + + for_each_line_table_case (test_layout_x_offset_display_utf8); + for_each_line_table_case (test_layout_x_offset_display_tab); + + test_get_line_bytes_without_trailing_whitespace (); + + test_diagnostic_show_locus_unknown_location (); + + for_each_line_table_case (test_diagnostic_show_locus_one_liner); + for_each_line_table_case (test_diagnostic_show_locus_one_liner_utf8); + for_each_line_table_case (test_add_location_if_nearby); + for_each_line_table_case (test_diagnostic_show_locus_fixit_lines); + for_each_line_table_case (test_fixit_consolidation); + for_each_line_table_case (test_overlapped_fixit_printing); + for_each_line_table_case (test_overlapped_fixit_printing_utf8); + for_each_line_table_case (test_overlapped_fixit_printing_2); + for_each_line_table_case (test_fixit_insert_containing_newline); + for_each_line_table_case (test_fixit_insert_containing_newline_2); + for_each_line_table_case (test_fixit_replace_containing_newline); + for_each_line_table_case (test_fixit_deletion_affecting_newline); + for_each_line_table_case (test_tab_expansion); + for_each_line_table_case (test_escaping_bytes_1); + for_each_line_table_case (test_escaping_bytes_2); + + test_line_numbers_multiline_range (); +} + +} // namespace diagnostics::selftest +} // namespace diagnostics + +#endif /* #if CHECKING_P */ + +#if __GNUC__ >= 10 +# pragma GCC diagnostic pop +#endif |