2267 lines
68 KiB
C
2267 lines
68 KiB
C
/* Diagnostic subroutines for printing source-code
|
|
Copyright (C) 1999-2016 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"
|
|
#include "system.h"
|
|
#include "coretypes.h"
|
|
#include "version.h"
|
|
#include "demangle.h"
|
|
#include "intl.h"
|
|
#include "backtrace.h"
|
|
#include "diagnostic.h"
|
|
#include "diagnostic-color.h"
|
|
#include "selftest.h"
|
|
|
|
#ifdef HAVE_TERMIOS_H
|
|
# include <termios.h>
|
|
#endif
|
|
|
|
#ifdef GWINSZ_IN_SYS_IOCTL
|
|
# include <sys/ioctl.h>
|
|
#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.
|
|
|
|
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::print_source_line and layout::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 (diagnostic_context *context,
|
|
diagnostic_t diagnostic_kind);
|
|
~colorizer ();
|
|
|
|
void set_range (int range_idx) { set_state (range_idx); }
|
|
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); }
|
|
|
|
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;
|
|
|
|
diagnostic_context *m_context;
|
|
diagnostic_t m_diagnostic_kind;
|
|
int m_current_state;
|
|
const char *m_caret;
|
|
const char *m_range1;
|
|
const char *m_range2;
|
|
const char *m_fixit_insert;
|
|
const char *m_fixit_delete;
|
|
const char *m_stop_color;
|
|
};
|
|
|
|
/* A point within a layout_range; similar to an expanded_location,
|
|
but after filtering on file. */
|
|
|
|
class layout_point
|
|
{
|
|
public:
|
|
layout_point (const expanded_location &exploc)
|
|
: m_line (exploc.line),
|
|
m_column (exploc.column) {}
|
|
|
|
int m_line;
|
|
int m_column;
|
|
};
|
|
|
|
/* A class for use by "class layout" below: a filtered location_range. */
|
|
|
|
class layout_range
|
|
{
|
|
public:
|
|
layout_range (const expanded_location *start_exploc,
|
|
const expanded_location *finish_exploc,
|
|
bool show_caret_p,
|
|
const expanded_location *caret_exploc);
|
|
|
|
bool contains_point (int row, int column) const;
|
|
bool intersects_line_p (int row) const;
|
|
|
|
layout_point m_start;
|
|
layout_point m_finish;
|
|
bool m_show_caret_p;
|
|
layout_point m_caret;
|
|
};
|
|
|
|
/* 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. */
|
|
|
|
struct line_bounds
|
|
{
|
|
int m_first_non_ws;
|
|
int m_last_non_ws;
|
|
};
|
|
|
|
/* 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). */
|
|
|
|
struct line_span
|
|
{
|
|
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_diff = (int)ls1->m_first_line - (int)ls2->m_first_line;
|
|
if (first_line_diff)
|
|
return first_line_diff;
|
|
return (int)ls1->m_last_line - (int)ls2->m_last_line;
|
|
}
|
|
|
|
linenum_type m_first_line;
|
|
linenum_type m_last_line;
|
|
};
|
|
|
|
/* A class to control the overall layout when printing a diagnostic.
|
|
|
|
The layout is determined within the constructor.
|
|
It is then printed by repeatedly calling the "print_source_line",
|
|
"print_annotation_line" and "print_any_fixits" methods.
|
|
|
|
We assume we have disjoint ranges. */
|
|
|
|
class layout
|
|
{
|
|
public:
|
|
layout (diagnostic_context *context,
|
|
rich_location *richloc,
|
|
diagnostic_t diagnostic_kind);
|
|
|
|
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]; }
|
|
|
|
bool print_heading_for_line_span_index_p (int line_span_idx) const;
|
|
|
|
expanded_location get_expanded_location (const line_span *) const;
|
|
|
|
bool print_source_line (int row, line_bounds *lbounds_out);
|
|
bool should_print_annotation_line_p (int row) const;
|
|
void print_annotation_line (int row, const line_bounds lbounds);
|
|
bool annotation_line_showed_range_p (int line, int start_column,
|
|
int finish_column) const;
|
|
void print_any_fixits (int row);
|
|
|
|
void show_ruler (int max_column) const;
|
|
|
|
private:
|
|
bool validate_fixit_hint_p (const fixit_hint *hint);
|
|
|
|
void calculate_line_spans ();
|
|
|
|
void print_newline ();
|
|
|
|
bool
|
|
get_state_at_point (/* Inputs. */
|
|
int row, int column,
|
|
int first_non_ws, int last_non_ws,
|
|
/* Outputs. */
|
|
point_state *out_state);
|
|
|
|
int
|
|
get_x_bound_for_row (int row, int caret_column,
|
|
int last_non_ws);
|
|
|
|
void
|
|
move_to_column (int *column, int dest_column);
|
|
|
|
private:
|
|
diagnostic_context *m_context;
|
|
pretty_printer *m_pp;
|
|
diagnostic_t m_diagnostic_kind;
|
|
expanded_location m_exploc;
|
|
colorizer m_colorizer;
|
|
bool m_colorize_source_p;
|
|
auto_vec <layout_range> m_layout_ranges;
|
|
auto_vec <const fixit_hint *> m_fixit_hints;
|
|
auto_vec <line_span> m_line_spans;
|
|
int m_x_offset;
|
|
};
|
|
|
|
/* 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 (diagnostic_context *context,
|
|
diagnostic_t diagnostic_kind) :
|
|
m_context (context),
|
|
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 (context->printer));
|
|
}
|
|
|
|
/* The destructor for "colorize". If colorization is on, print a code to
|
|
turn it off. */
|
|
|
|
colorizer::~colorizer ()
|
|
{
|
|
finish_state (m_current_state);
|
|
}
|
|
|
|
/* 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_context->printer, m_fixit_insert);
|
|
break;
|
|
|
|
case STATE_FIXIT_DELETE:
|
|
pp_string (m_context->printer, m_fixit_delete);
|
|
break;
|
|
|
|
case 0:
|
|
/* Make range 0 be the same color as the "kind" text
|
|
(error vs warning vs note). */
|
|
pp_string
|
|
(m_context->printer,
|
|
colorize_start (pp_show_color (m_context->printer),
|
|
diagnostic_get_color_for_kind (m_diagnostic_kind)));
|
|
break;
|
|
|
|
case 1:
|
|
pp_string (m_context->printer, m_range1);
|
|
break;
|
|
|
|
case 2:
|
|
pp_string (m_context->printer, m_range2);
|
|
break;
|
|
|
|
default:
|
|
/* For ranges beyond 2, alternate between color 1 and color 2. */
|
|
{
|
|
gcc_assert (state > 2);
|
|
pp_string (m_context->printer,
|
|
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_context->printer, 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_context->printer), 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 expanded_location *start_exploc,
|
|
const expanded_location *finish_exploc,
|
|
bool show_caret_p,
|
|
const expanded_location *caret_exploc)
|
|
: m_start (*start_exploc),
|
|
m_finish (*finish_exploc),
|
|
m_show_caret_p (show_caret_p),
|
|
m_caret (*caret_exploc)
|
|
{
|
|
}
|
|
|
|
/* 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. */
|
|
|
|
bool
|
|
layout_range::contains_point (int row, int column) 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_column)
|
|
/* 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_column;
|
|
}
|
|
}
|
|
|
|
/* 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_column;
|
|
}
|
|
|
|
/* Does this layout_range contain any part of line ROW? */
|
|
|
|
bool
|
|
layout_range::intersects_line_p (int 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;
|
|
}
|
|
|
|
#if CHECKING_P
|
|
|
|
/* A helper function for testing layout_range. */
|
|
|
|
static layout_range
|
|
make_range (int start_line, int start_col, int end_line, int end_col)
|
|
{
|
|
const expanded_location start_exploc
|
|
= {"test.c", start_line, start_col, NULL, false};
|
|
const expanded_location finish_exploc
|
|
= {"test.c", end_line, end_col, NULL, false};
|
|
return layout_range (&start_exploc, &finish_exploc, false,
|
|
&start_exploc);
|
|
}
|
|
|
|
/* 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 ()
|
|
{
|
|
layout_range point = make_range (7, 10, 7, 10);
|
|
|
|
/* Tests for layout_range::contains_point. */
|
|
|
|
/* Before the line. */
|
|
ASSERT_FALSE (point.contains_point (6, 1));
|
|
|
|
/* On the line, but before start. */
|
|
ASSERT_FALSE (point.contains_point (7, 9));
|
|
|
|
/* At the point. */
|
|
ASSERT_TRUE (point.contains_point (7, 10));
|
|
|
|
/* On the line, after the point. */
|
|
ASSERT_FALSE (point.contains_point (7, 11));
|
|
|
|
/* After the line. */
|
|
ASSERT_FALSE (point.contains_point (8, 1));
|
|
|
|
/* 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 ()
|
|
{
|
|
layout_range example_a = make_range (2, 22, 2, 38);
|
|
|
|
/* Tests for layout_range::contains_point. */
|
|
|
|
/* Before the line. */
|
|
ASSERT_FALSE (example_a.contains_point (1, 1));
|
|
|
|
/* On the line, but before start. */
|
|
ASSERT_FALSE (example_a.contains_point (2, 21));
|
|
|
|
/* On the line, at the start. */
|
|
ASSERT_TRUE (example_a.contains_point (2, 22));
|
|
|
|
/* On the line, within the range. */
|
|
ASSERT_TRUE (example_a.contains_point (2, 23));
|
|
|
|
/* On the line, at the end. */
|
|
ASSERT_TRUE (example_a.contains_point (2, 38));
|
|
|
|
/* On the line, after the end. */
|
|
ASSERT_FALSE (example_a.contains_point (2, 39));
|
|
|
|
/* After the line. */
|
|
ASSERT_FALSE (example_a.contains_point (2, 39));
|
|
|
|
/* 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 ()
|
|
{
|
|
layout_range example_b = make_range (3, 14, 5, 8);
|
|
|
|
/* Tests for layout_range::contains_point. */
|
|
|
|
/* Before first line. */
|
|
ASSERT_FALSE (example_b.contains_point (1, 1));
|
|
|
|
/* On the first line, but before start. */
|
|
ASSERT_FALSE (example_b.contains_point (3, 13));
|
|
|
|
/* At the start. */
|
|
ASSERT_TRUE (example_b.contains_point (3, 14));
|
|
|
|
/* On the first line, within the range. */
|
|
ASSERT_TRUE (example_b.contains_point (3, 15));
|
|
|
|
/* On an interior line.
|
|
The column number should not matter; try various boundary
|
|
values. */
|
|
ASSERT_TRUE (example_b.contains_point (4, 1));
|
|
ASSERT_TRUE (example_b.contains_point (4, 7));
|
|
ASSERT_TRUE (example_b.contains_point (4, 8));
|
|
ASSERT_TRUE (example_b.contains_point (4, 9));
|
|
ASSERT_TRUE (example_b.contains_point (4, 13));
|
|
ASSERT_TRUE (example_b.contains_point (4, 14));
|
|
ASSERT_TRUE (example_b.contains_point (4, 15));
|
|
|
|
/* On the final line, before the end. */
|
|
ASSERT_TRUE (example_b.contains_point (5, 7));
|
|
|
|
/* On the final line, at the end. */
|
|
ASSERT_TRUE (example_b.contains_point (5, 8));
|
|
|
|
/* On the final line, after the end. */
|
|
ASSERT_FALSE (example_b.contains_point (5, 9));
|
|
|
|
/* After the line. */
|
|
ASSERT_FALSE (example_b.contains_point (6, 1));
|
|
|
|
/* 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_WIDTH, determine the width
|
|
without any trailing whitespace. */
|
|
|
|
static int
|
|
get_line_width_without_trailing_whitespace (const char *line, int line_width)
|
|
{
|
|
int result = line_width;
|
|
while (result > 0)
|
|
{
|
|
char ch = line[result - 1];
|
|
if (ch == ' ' || ch == '\t')
|
|
result--;
|
|
else
|
|
break;
|
|
}
|
|
gcc_assert (result >= 0);
|
|
gcc_assert (result <= line_width);
|
|
gcc_assert (result == 0 ||
|
|
(line[result - 1] != ' '
|
|
&& line[result -1] != '\t'));
|
|
return result;
|
|
}
|
|
|
|
#if CHECKING_P
|
|
|
|
/* A helper function for testing get_line_width_without_trailing_whitespace. */
|
|
|
|
static void
|
|
assert_eq (const char *line, int expected_width)
|
|
{
|
|
int actual_value
|
|
= get_line_width_without_trailing_whitespace (line, strlen (line));
|
|
ASSERT_EQ (actual_value, expected_width);
|
|
}
|
|
|
|
/* Verify that get_line_width_without_trailing_whitespace is sane for
|
|
various inputs. It is not required to handle newlines. */
|
|
|
|
static void
|
|
test_get_line_width_without_trailing_whitespace ()
|
|
{
|
|
assert_eq ("", 0);
|
|
assert_eq (" ", 0);
|
|
assert_eq ("\t", 0);
|
|
assert_eq ("hello world", 11);
|
|
assert_eq ("hello world ", 11);
|
|
assert_eq ("hello world \t\t ", 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. */
|
|
|
|
static bool
|
|
compatible_locations_p (location_t loc_a, location_t loc_b)
|
|
{
|
|
if (IS_ADHOC_LOC (loc_a))
|
|
loc_a = get_location_from_adhoc_loc (line_table, loc_a);
|
|
if (IS_ADHOC_LOC (loc_b))
|
|
loc_b = get_location_from_adhoc_loc (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 (line_table, loc_a);
|
|
linemap_assert (map_a);
|
|
|
|
const line_map *map_b = linemap_lookup (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))
|
|
{
|
|
/* Expand each location towards the spelling location, and
|
|
recurse. */
|
|
const line_map_macro *macro_map = linemap_check_macro (map_a);
|
|
source_location loc_a_toward_spelling
|
|
= linemap_macro_map_loc_unwind_toward_spelling (line_table,
|
|
macro_map,
|
|
loc_a);
|
|
source_location loc_b_toward_spelling
|
|
= linemap_macro_map_loc_unwind_toward_spelling (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;
|
|
}
|
|
}
|
|
|
|
/* 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, to ensure that the primary caret
|
|
will fit within the max_width provided by the diagnostic_context. */
|
|
|
|
layout::layout (diagnostic_context * context,
|
|
rich_location *richloc,
|
|
diagnostic_t diagnostic_kind)
|
|
: m_context (context),
|
|
m_pp (context->printer),
|
|
m_diagnostic_kind (diagnostic_kind),
|
|
m_exploc (richloc->get_expanded_location (0)),
|
|
m_colorizer (context, diagnostic_kind),
|
|
m_colorize_source_p (context->colorize_source_p),
|
|
m_layout_ranges (richloc->get_num_locations ()),
|
|
m_fixit_hints (richloc->get_num_fixit_hints ()),
|
|
m_line_spans (1 + richloc->get_num_locations ()),
|
|
m_x_offset (0)
|
|
{
|
|
source_location primary_loc = richloc->get_range (0)->m_loc;
|
|
|
|
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);
|
|
|
|
/* Split the "range" into caret and range information. */
|
|
source_range src_range = get_range_from_loc (line_table, loc_range->m_loc);
|
|
|
|
/* Expand the various locations. */
|
|
expanded_location start
|
|
= linemap_client_expand_location_to_spelling_point (src_range.m_start);
|
|
expanded_location finish
|
|
= linemap_client_expand_location_to_spelling_point (src_range.m_finish);
|
|
expanded_location caret
|
|
= linemap_client_expand_location_to_spelling_point (loc_range->m_loc);
|
|
|
|
/* 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)
|
|
continue;
|
|
if (finish.file != m_exploc.file)
|
|
continue;
|
|
if (loc_range->m_show_caret_p)
|
|
if (caret.file != m_exploc.file)
|
|
continue;
|
|
|
|
/* Sanitize the caret location for non-primary ranges. */
|
|
if (m_layout_ranges.length () > 0)
|
|
if (loc_range->m_show_caret_p)
|
|
if (!compatible_locations_p (loc_range->m_loc, primary_loc))
|
|
/* Discard any non-primary ranges that can't be printed
|
|
sanely relative to the primary location. */
|
|
continue;
|
|
|
|
/* Everything is now known to be in the correct source file,
|
|
but it may require further sanitization. */
|
|
layout_range ri (&start, &finish, loc_range->m_show_caret_p, &caret);
|
|
|
|
/* 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, primary_loc)
|
|
|| !compatible_locations_p (src_range.m_finish, 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. */
|
|
continue;
|
|
}
|
|
|
|
/* Passed all the tests; add the range to m_layout_ranges so that
|
|
it will be printed. */
|
|
m_layout_ranges.safe_push (ri);
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* Populate m_line_spans. */
|
|
calculate_line_spans ();
|
|
|
|
/* Adjust m_x_offset.
|
|
Center the primary caret to fit in max_width; all columns
|
|
will be adjusted accordingly. */
|
|
int max_width = m_context->caret_max_width;
|
|
int line_width;
|
|
const char *line = location_get_source_line (m_exploc.file, m_exploc.line,
|
|
&line_width);
|
|
if (line && m_exploc.column <= line_width)
|
|
{
|
|
int right_margin = CARET_LINE_MARGIN;
|
|
int column = m_exploc.column;
|
|
right_margin = MIN (line_width - column, right_margin);
|
|
right_margin = max_width - right_margin;
|
|
if (line_width >= max_width && column > right_margin)
|
|
m_x_offset = column - right_margin;
|
|
gcc_assert (m_x_offset >= 0);
|
|
}
|
|
|
|
if (context->show_ruler_p)
|
|
show_ruler (m_x_offset + max_width);
|
|
}
|
|
|
|
/* 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_column;
|
|
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)
|
|
{
|
|
switch (hint->get_kind ())
|
|
{
|
|
case fixit_hint::INSERT:
|
|
{
|
|
const fixit_insert *insert = static_cast <const fixit_insert *> (hint);
|
|
location_t loc = insert->get_location ();
|
|
if (LOCATION_FILE (loc) != m_exploc.file)
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case fixit_hint::REPLACE:
|
|
{
|
|
const fixit_replace *replace
|
|
= static_cast <const fixit_replace *> (hint);
|
|
source_range src_range = replace->get_range ();
|
|
if (LOCATION_FILE (src_range.m_start) != m_exploc.file)
|
|
return false;
|
|
if (LOCATION_FILE (src_range.m_finish) != m_exploc.file)
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
gcc_unreachable ();
|
|
}
|
|
|
|
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);
|
|
switch (hint->get_kind ())
|
|
{
|
|
case fixit_hint::INSERT:
|
|
{
|
|
const fixit_insert *insert = static_cast <const fixit_insert *> (hint);
|
|
location_t loc = insert->get_location ();
|
|
int line = LOCATION_LINE (loc);
|
|
return line_span (line, line);
|
|
}
|
|
break;
|
|
|
|
case fixit_hint::REPLACE:
|
|
{
|
|
const fixit_replace *replace
|
|
= static_cast <const fixit_replace *> (hint);
|
|
source_range src_range = replace->get_range ();
|
|
return line_span (LOCATION_LINE (src_range.m_start),
|
|
LOCATION_LINE (src_range.m_finish));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
gcc_unreachable ();
|
|
}
|
|
}
|
|
|
|
/* 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.
|
|
|
|
For example, if the primary caret location is on line 7, with ranges
|
|
covering lines 5-6 and lines 9-12:
|
|
|
|
004
|
|
005 |RANGE 0
|
|
006 |RANGE 0
|
|
007 |PRIMARY CARET
|
|
008
|
|
009 |RANGE 1
|
|
010 |RANGE 1
|
|
011 |RANGE 1
|
|
012 |RANGE 1
|
|
013
|
|
|
|
then we want two spans: lines 5-7 and lines 9-12. */
|
|
|
|
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);
|
|
if (next->m_first_line <= current->m_last_line + 1)
|
|
{
|
|
/* 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);
|
|
}
|
|
}
|
|
|
|
/* Attempt to print line ROW of source code, potentially colorized at any
|
|
ranges.
|
|
Return true if the line was printed, populating *LBOUNDS_OUT.
|
|
Return false if the source line could not be read, leaving *LBOUNDS_OUT
|
|
untouched. */
|
|
|
|
bool
|
|
layout::print_source_line (int row, line_bounds *lbounds_out)
|
|
{
|
|
int line_width;
|
|
const char *line = location_get_source_line (m_exploc.file, row,
|
|
&line_width);
|
|
if (!line)
|
|
return false;
|
|
|
|
m_colorizer.set_normal_text ();
|
|
|
|
/* We will stop printing the source line at any trailing
|
|
whitespace. */
|
|
line_width = get_line_width_without_trailing_whitespace (line,
|
|
line_width);
|
|
line += m_x_offset;
|
|
|
|
pp_space (m_pp);
|
|
int first_non_ws = INT_MAX;
|
|
int last_non_ws = 0;
|
|
int column;
|
|
for (column = 1 + m_x_offset; column <= line_width; column++)
|
|
{
|
|
/* 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_colorize_source_p)
|
|
{
|
|
bool in_range_p;
|
|
point_state state;
|
|
in_range_p = get_state_at_point (row, column,
|
|
0, INT_MAX,
|
|
&state);
|
|
if (in_range_p)
|
|
m_colorizer.set_range (state.range_idx);
|
|
else
|
|
m_colorizer.set_normal_text ();
|
|
}
|
|
char c = *line == '\t' ? ' ' : *line;
|
|
if (c == '\0')
|
|
c = ' ';
|
|
if (c != ' ')
|
|
{
|
|
last_non_ws = column;
|
|
if (first_non_ws == INT_MAX)
|
|
first_non_ws = column;
|
|
}
|
|
pp_character (m_pp, c);
|
|
line++;
|
|
}
|
|
print_newline ();
|
|
|
|
lbounds_out->m_first_non_ws = first_non_ws;
|
|
lbounds_out->m_last_non_ws = last_non_ws;
|
|
return true;
|
|
}
|
|
|
|
/* 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 (int row) const
|
|
{
|
|
layout_range *range;
|
|
int i;
|
|
FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
|
|
if (range->intersects_line_p (row))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* Print a line consisting of the caret/underlines for the given
|
|
source line. */
|
|
|
|
void
|
|
layout::print_annotation_line (int row, const line_bounds lbounds)
|
|
{
|
|
int x_bound = get_x_bound_for_row (row, m_exploc.column,
|
|
lbounds.m_last_non_ws);
|
|
|
|
pp_space (m_pp);
|
|
for (int column = 1 + m_x_offset; column < x_bound; column++)
|
|
{
|
|
bool in_range_p;
|
|
point_state state;
|
|
in_range_p = get_state_at_point (row, column,
|
|
lbounds.m_first_non_ws,
|
|
lbounds.m_last_non_ws,
|
|
&state);
|
|
if (in_range_p)
|
|
{
|
|
/* Within a range. Draw either the caret or an underline. */
|
|
m_colorizer.set_range (state.range_idx);
|
|
if (state.draw_caret_p)
|
|
{
|
|
/* Draw the caret. */
|
|
char caret_char;
|
|
if (state.range_idx < rich_location::STATICALLY_ALLOCATED_RANGES)
|
|
caret_char = m_context->caret_chars[state.range_idx];
|
|
else
|
|
caret_char = '^';
|
|
pp_character (m_pp, caret_char);
|
|
}
|
|
else
|
|
pp_character (m_pp, '~');
|
|
}
|
|
else
|
|
{
|
|
/* Not in a range. */
|
|
m_colorizer.set_normal_text ();
|
|
pp_character (m_pp, ' ');
|
|
}
|
|
}
|
|
print_newline ();
|
|
}
|
|
|
|
/* Subroutine of layout::print_any_fixits.
|
|
|
|
Determine if the annotation line printed for LINE contained
|
|
the exact range from START_COLUMN to FINISH_COLUMN. */
|
|
|
|
bool
|
|
layout::annotation_line_showed_range_p (int 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_column == start_column
|
|
&& range->m_finish.m_line == line
|
|
&& range->m_finish.m_column == finish_column)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* 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. */
|
|
|
|
void
|
|
layout::print_any_fixits (int row)
|
|
{
|
|
int column = 0;
|
|
for (unsigned int i = 0; i < m_fixit_hints.length (); i++)
|
|
{
|
|
const fixit_hint *hint = m_fixit_hints[i];
|
|
if (hint->affects_line_p (m_exploc.file, row))
|
|
{
|
|
/* For now we assume each fixit hint can only touch one line. */
|
|
switch (hint->get_kind ())
|
|
{
|
|
case fixit_hint::INSERT:
|
|
{
|
|
const fixit_insert *insert
|
|
= static_cast <const fixit_insert *> (hint);
|
|
/* This assumes the insertion just affects one line. */
|
|
int start_column
|
|
= LOCATION_COLUMN (insert->get_location ());
|
|
move_to_column (&column, start_column);
|
|
m_colorizer.set_fixit_insert ();
|
|
pp_string (m_pp, insert->get_string ());
|
|
m_colorizer.set_normal_text ();
|
|
column += insert->get_length ();
|
|
}
|
|
break;
|
|
|
|
case fixit_hint::REPLACE:
|
|
{
|
|
const fixit_replace *replace
|
|
= static_cast <const fixit_replace *> (hint);
|
|
source_range src_range = replace->get_range ();
|
|
int line = LOCATION_LINE (src_range.m_start);
|
|
int start_column = LOCATION_COLUMN (src_range.m_start);
|
|
int finish_column = LOCATION_COLUMN (src_range.m_finish);
|
|
|
|
/* 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. */
|
|
if (!annotation_line_showed_range_p (line, start_column,
|
|
finish_column)
|
|
|| replace->get_length () == 0)
|
|
{
|
|
move_to_column (&column, start_column);
|
|
m_colorizer.set_fixit_delete ();
|
|
for (; column <= finish_column; column++)
|
|
pp_character (m_pp, '-');
|
|
m_colorizer.set_normal_text ();
|
|
}
|
|
/* 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 (replace->get_length () > 0)
|
|
{
|
|
move_to_column (&column, start_column);
|
|
m_colorizer.set_fixit_insert ();
|
|
pp_string (m_pp, replace->get_string ());
|
|
m_colorizer.set_normal_text ();
|
|
column += replace->get_length ();
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
gcc_unreachable ();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Add a trailing newline, if necessary. */
|
|
move_to_column (&column, 0);
|
|
}
|
|
|
|
/* Disable any colorization and emit a newline. */
|
|
|
|
void
|
|
layout::print_newline ()
|
|
{
|
|
m_colorizer.set_normal_text ();
|
|
pp_newline (m_pp);
|
|
}
|
|
|
|
/* 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). */
|
|
|
|
bool
|
|
layout::get_state_at_point (/* Inputs. */
|
|
int row, int column,
|
|
int first_non_ws, int last_non_ws,
|
|
/* Outputs. */
|
|
point_state *out_state)
|
|
{
|
|
layout_range *range;
|
|
int i;
|
|
FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
|
|
{
|
|
if (range->contains_point (row, column))
|
|
{
|
|
out_state->range_idx = i;
|
|
|
|
/* Are we at the range's caret? is it visible? */
|
|
out_state->draw_caret_p = false;
|
|
if (range->m_show_caret_p
|
|
&& row == range->m_caret.m_line
|
|
&& column == range->m_caret.m_column)
|
|
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 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 column of range 0's caret.
|
|
LAST_NON_WS_COLUMN is the last column containing a non-whitespace
|
|
character of source (as determined when printing the source line). */
|
|
|
|
int
|
|
layout::get_x_bound_for_row (int row, int caret_column,
|
|
int last_non_ws_column)
|
|
{
|
|
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. */
|
|
if (result <= range->m_finish.m_column)
|
|
result = range->m_finish.m_column + 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. */
|
|
|
|
void
|
|
layout::move_to_column (int *column, int dest_column)
|
|
{
|
|
/* Start a new line if we need to. */
|
|
if (*column > dest_column)
|
|
{
|
|
print_newline ();
|
|
*column = 0;
|
|
}
|
|
|
|
while (*column < dest_column)
|
|
{
|
|
pp_space (m_pp);
|
|
(*column)++;
|
|
}
|
|
}
|
|
|
|
/* For debugging layout issues, render a ruler giving column numbers
|
|
(after the 1-column indent). */
|
|
|
|
void
|
|
layout::show_ruler (int max_column) const
|
|
{
|
|
/* Hundreds. */
|
|
if (max_column > 99)
|
|
{
|
|
pp_space (m_pp);
|
|
for (int column = 1 + m_x_offset; column <= max_column; column++)
|
|
if (0 == column % 10)
|
|
pp_character (m_pp, '0' + (column / 100) % 10);
|
|
else
|
|
pp_space (m_pp);
|
|
pp_newline (m_pp);
|
|
}
|
|
|
|
/* Tens. */
|
|
pp_space (m_pp);
|
|
for (int column = 1 + m_x_offset; column <= max_column; column++)
|
|
if (0 == column % 10)
|
|
pp_character (m_pp, '0' + (column / 10) % 10);
|
|
else
|
|
pp_space (m_pp);
|
|
pp_newline (m_pp);
|
|
|
|
/* Units. */
|
|
pp_space (m_pp);
|
|
for (int column = 1 + m_x_offset; column <= max_column; column++)
|
|
pp_character (m_pp, '0' + (column % 10));
|
|
pp_newline (m_pp);
|
|
}
|
|
|
|
} /* End of anonymous namespace. */
|
|
|
|
/* Print the physical source code corresponding to the location of
|
|
this diagnostic, with additional annotations. */
|
|
|
|
void
|
|
diagnostic_show_locus (diagnostic_context * context,
|
|
rich_location *richloc,
|
|
diagnostic_t diagnostic_kind)
|
|
{
|
|
pp_newline (context->printer);
|
|
|
|
location_t loc = richloc->get_loc ();
|
|
/* Do nothing if source-printing has been disabled. */
|
|
if (!context->show_caret)
|
|
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. */
|
|
if (loc == context->last_location
|
|
&& richloc->get_num_fixit_hints () == 0)
|
|
return;
|
|
|
|
context->last_location = loc;
|
|
|
|
const char *saved_prefix = pp_get_prefix (context->printer);
|
|
pp_set_prefix (context->printer, NULL);
|
|
|
|
layout layout (context, richloc, diagnostic_kind);
|
|
for (int line_span_idx = 0; line_span_idx < layout.get_num_line_spans ();
|
|
line_span_idx++)
|
|
{
|
|
const line_span *line_span = layout.get_line_span (line_span_idx);
|
|
if (layout.print_heading_for_line_span_index_p (line_span_idx))
|
|
{
|
|
expanded_location exploc = layout.get_expanded_location (line_span);
|
|
context->start_span (context, exploc);
|
|
}
|
|
int last_line = line_span->get_last_line ();
|
|
for (int row = line_span->get_first_line (); row <= last_line; row++)
|
|
{
|
|
/* Print 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. */
|
|
line_bounds lbounds;
|
|
if (layout.print_source_line (row, &lbounds))
|
|
{
|
|
if (layout.should_print_annotation_line_p (row))
|
|
layout.print_annotation_line (row, lbounds);
|
|
layout.print_any_fixits (row);
|
|
}
|
|
}
|
|
}
|
|
|
|
pp_set_prefix (context->printer, saved_prefix);
|
|
}
|
|
|
|
#if CHECKING_P
|
|
|
|
namespace selftest {
|
|
|
|
/* Selftests for diagnostic_show_locus. */
|
|
|
|
/* Convenience subclass of diagnostic_context for testing
|
|
diagnostic_show_locus. */
|
|
|
|
class test_diagnostic_context : public diagnostic_context
|
|
{
|
|
public:
|
|
test_diagnostic_context ()
|
|
{
|
|
diagnostic_initialize (this, 0);
|
|
show_caret = true;
|
|
show_column = true;
|
|
start_span = start_span_cb;
|
|
}
|
|
~test_diagnostic_context ()
|
|
{
|
|
diagnostic_finish (this);
|
|
}
|
|
|
|
/* Implementation of diagnostic_start_span_fn, hiding the
|
|
real filename (to avoid printing the names of tempfiles). */
|
|
static void
|
|
start_span_cb (diagnostic_context *context, expanded_location exploc)
|
|
{
|
|
exploc.file = "FILENAME";
|
|
default_diagnostic_start_span_fn (context, exploc);
|
|
}
|
|
};
|
|
|
|
/* Verify that diagnostic_show_locus works sanely on UNKNOWN_LOCATION. */
|
|
|
|
static void
|
|
test_diagnostic_show_locus_unknown_location ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
rich_location richloc (line_table, UNKNOWN_LOCATION);
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("\n", pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* 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_diagnostic_context dc;
|
|
location_t caret = linemap_position_for_column (line_table, 10);
|
|
rich_location richloc (line_table, caret);
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("\n"
|
|
" foo = bar.field;\n"
|
|
" ^\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Caret and range. */
|
|
|
|
static void
|
|
test_one_liner_caret_and_range ()
|
|
{
|
|
test_diagnostic_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);
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("\n"
|
|
" foo = bar.field;\n"
|
|
" ~~~^~~~~~\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Multiple ranges and carets. */
|
|
|
|
static void
|
|
test_one_liner_multiple_carets_and_ranges ()
|
|
{
|
|
test_diagnostic_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.caret_chars[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.caret_chars[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.caret_chars[2] = 'C';
|
|
|
|
rich_location richloc (line_table, foo);
|
|
richloc.add_range (bar, true);
|
|
richloc.add_range (field, true);
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("\n"
|
|
" foo = bar.field;\n"
|
|
" ~A~ ~B~ ~~C~~\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Insertion fix-it hint: adding an "&" to the front of "bar.field". */
|
|
|
|
static void
|
|
test_one_liner_fixit_insert_before ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t caret = linemap_position_for_column (line_table, 7);
|
|
rich_location richloc (line_table, caret);
|
|
richloc.add_fixit_insert_before ("&");
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("\n"
|
|
" foo = bar.field;\n"
|
|
" ^\n"
|
|
" &\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Insertion fix-it hint: adding a "[0]" after "foo". */
|
|
|
|
static void
|
|
test_one_liner_fixit_insert_after ()
|
|
{
|
|
test_diagnostic_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]");
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("\n"
|
|
" foo = bar.field;\n"
|
|
" ^~~\n"
|
|
" [0]\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Removal fix-it hint: removal of the ".field". */
|
|
|
|
static void
|
|
test_one_liner_fixit_remove ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
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 ();
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("\n"
|
|
" foo = bar.field;\n"
|
|
" ^~~~~~\n"
|
|
" ------\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Replace fix-it hint: replacing "field" with "m_field". */
|
|
|
|
static void
|
|
test_one_liner_fixit_replace ()
|
|
{
|
|
test_diagnostic_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");
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("\n"
|
|
" foo = bar.field;\n"
|
|
" ^~~~~\n"
|
|
" m_field\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* 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_diagnostic_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");
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
/* The replacement range is not indicated in the annotation line, so
|
|
it should be indicated via an additional underline. */
|
|
ASSERT_STREQ ("\n"
|
|
" foo = bar.field;\n"
|
|
" ^\n"
|
|
" -----\n"
|
|
" m_field\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* 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_diagnostic_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, false);
|
|
richloc.add_fixit_replace (field, "m_field");
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
/* The replacement range is indicated in the annotation line,
|
|
so it shouldn't be indicated via an additional underline. */
|
|
ASSERT_STREQ ("\n"
|
|
" foo = bar.field;\n"
|
|
" ^ ~~~~~\n"
|
|
" m_field\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* 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 bits or 0 bits of packed range); 41 columns > 2**5. */
|
|
const location_t c7 = linemap_position_for_column (line_table, 7);
|
|
const location_t c47 = linemap_position_for_column (line_table, 47);
|
|
const location_t loc = make_location (c7, c7, c47);
|
|
|
|
if (c47 > 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_diagnostic_context dc;
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("\n"
|
|
" foo = bar.field;\n"
|
|
" ^~~~~~~~~~ \n"
|
|
" test\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Remove. */
|
|
{
|
|
rich_location richloc (line_table, loc);
|
|
source_range range = source_range::from_locations (loc, c47);
|
|
richloc.add_fixit_remove (range);
|
|
/* It should not have been discarded by the validator. */
|
|
ASSERT_EQ (1, richloc.get_num_fixit_hints ());
|
|
|
|
test_diagnostic_context dc;
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("\n"
|
|
" foo = bar.field;\n"
|
|
" ^~~~~~~~~~ \n"
|
|
" -----------------------------------------\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Replace. */
|
|
{
|
|
rich_location richloc (line_table, loc);
|
|
source_range range = source_range::from_locations (loc, c47);
|
|
richloc.add_fixit_replace (range, "test");
|
|
/* It should not have been discarded by the validator. */
|
|
ASSERT_EQ (1, richloc.get_num_fixit_hints ());
|
|
|
|
test_diagnostic_context dc;
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("\n"
|
|
" foo = bar.field;\n"
|
|
" ^~~~~~~~~~ \n"
|
|
" test\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
}
|
|
|
|
/* Ensure that we can add an arbitrary number of fix-it hints to a
|
|
rich_location. */
|
|
|
|
static void
|
|
test_one_liner_many_fixits ()
|
|
{
|
|
test_diagnostic_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 (19, richloc.get_num_fixit_hints ());
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("\n"
|
|
" foo = bar.field;\n"
|
|
" ^\n"
|
|
" a\n"
|
|
" a\n"
|
|
" a\n"
|
|
" a\n"
|
|
" a\n"
|
|
" a\n"
|
|
" a\n"
|
|
" a\n"
|
|
" a\n"
|
|
" a\n"
|
|
" a\n"
|
|
" a\n"
|
|
" a\n"
|
|
" a\n"
|
|
" a\n"
|
|
" a\n"
|
|
" a\n"
|
|
" a\n"
|
|
" a\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* 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";
|
|
temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
|
|
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, 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 (tmp.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_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 ();
|
|
}
|
|
|
|
/* 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_diagnostic_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, "=");
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("\n"
|
|
" struct point origin = {x: 0.0,\n"
|
|
" ^\n"
|
|
" .=\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* 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_diagnostic_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, "=");
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("\n"
|
|
"FILENAME:3:24:\n"
|
|
" y\n"
|
|
" .\n"
|
|
"FILENAME:6:25:\n"
|
|
" : 0.0};\n"
|
|
" ^\n"
|
|
" =\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
}
|
|
|
|
|
|
/* 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 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_EQ (fixit_hint::REPLACE, hint->get_kind ());
|
|
const fixit_replace *replace = (const fixit_replace *)hint;
|
|
ASSERT_STREQ ("foobar", replace->get_string ());
|
|
ASSERT_EQ (c10, replace->get_range ().m_start);
|
|
ASSERT_EQ (c20, replace->get_range ().m_finish);
|
|
}
|
|
}
|
|
|
|
/* 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_EQ (fixit_hint::REPLACE, hint->get_kind ());
|
|
const fixit_replace *replace = (const fixit_replace *)hint;
|
|
ASSERT_STREQ ("foo", replace->get_string ());
|
|
ASSERT_EQ (c10, replace->get_range ().m_start);
|
|
ASSERT_EQ (c20, replace->get_range ().m_finish);
|
|
}
|
|
}
|
|
|
|
/* 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_EQ (fixit_hint::REPLACE, hint->get_kind ());
|
|
const fixit_replace *replace = (const fixit_replace *)hint;
|
|
ASSERT_STREQ ("", replace->get_string ());
|
|
ASSERT_EQ (c10, replace->get_range ().m_start);
|
|
ASSERT_EQ (c20, replace->get_range ().m_finish);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Insertion fix-it hint: adding a "break;" on a line by itself.
|
|
This will fail, as newlines aren't yet supported. */
|
|
|
|
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);
|
|
|
|
/* Add a "break;" on a line by itself before line 3 i.e. before
|
|
column 1 of line 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);
|
|
rich_location richloc (line_table, case_loc);
|
|
location_t line_start = linemap_position_for_column (line_table, 1);
|
|
richloc.add_fixit_insert_before (line_start, " break;\n");
|
|
|
|
/* 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 (case_finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
|
|
return;
|
|
|
|
test_diagnostic_context dc;
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("\n"
|
|
" case 'b':\n"
|
|
" ^~~~~~~~~\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Replacement fix-it hint containing a newline.
|
|
This will fail, as newlines aren't yet supported. */
|
|
|
|
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 =");
|
|
|
|
/* 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_diagnostic_context dc;
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("\n"
|
|
" foo = bar ();\n"
|
|
" ^\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Run all of the selftests within this file. */
|
|
|
|
void
|
|
diagnostic_show_locus_c_tests ()
|
|
{
|
|
test_layout_range_for_single_point ();
|
|
test_layout_range_for_single_line ();
|
|
test_layout_range_for_multiple_lines ();
|
|
|
|
test_get_line_width_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_fixit_lines);
|
|
for_each_line_table_case (test_fixit_consolidation);
|
|
for_each_line_table_case (test_fixit_insert_containing_newline);
|
|
for_each_line_table_case (test_fixit_replace_containing_newline);
|
|
}
|
|
|
|
} // namespace selftest
|
|
|
|
#endif /* #if CHECKING_P */
|