diff mbox

PR other/69554: avoid excessive source printing for widely-separated locations

Message ID 1455051277-28489-1-git-send-email-dmalcolm@redhat.com
State New
Headers show

Commit Message

David Malcolm Feb. 9, 2016, 8:54 p.m. UTC
PR other/69554 describes a regression seen from the Fortran frontend
when issuing a diagnostic containing more than one location: if the
locations are within the same file (and hence not filtered by the
existing sanitization code), diagnostic_show_locus could print all
of the lines of the source file between the two locations, which
could be an excessive amount of output.

It's possible to see this from other frontends; for example, in the
C frontend, we emit errors like this:

 left_hand_side () + right_hand_side ()
 ~~~~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~

and the three locations could potentially be separated by e.g. large
comments, leading to lots of diagnostic spew.

The solution seems to be to split the source file up when printing,
to print just "the source lines of interest", meaning those source
lines containing at least part of an underline or caret.

The attached patch implements this, in diagnostic-show-locus.
Previously, within class layout, the fields m_first_line and m_last_line
described a single "span" of source lines to be printed
(e.g. lines 3-12).  This patch replaces those fields with a vector of
line_span instances, so that we can print e.g. lines 3-5, then line 8,
then lines 10-12.

We need to tell the user which lines he/she is seeing, so the patch
prints locus information every time we change line spans.  This is
modelled on the existing output of the Fortran frontend.  For example,
we might print:

foo.c:8:5: error: insufficiently cromulent code
foo.c:3:1:
  left_hand_side (3, /* line 3 */
  ~~~~~~~~~~~~~~~~~~
                  4,  /* line 4 */
                  ~~
                  5)  /* line 5 */
                  ~~
foo.c:8:5:
     + /* line 8 */
     ^
foo.c:10:2:
  right_hand_side (10,  /* line 10 */
  ~~~~~~~~~~~~~~~~~~~~
                   11,  /* line 11 */
                   ~~~
                   12)  /* line 12 */
                   ~~~

(note the primary caret is at line 8 column 5, so the initial message
emitted by the C frontend describes that, but the initial span doesn't
contain the primary caret, so it gets a locus line "foo.c:3:1:")

Typically the source will be printed in a single span, and so there
won't be any extra locus lines; this is all about gracefully handling
the more awkward cases.

For Fortran, the locus line gets an extra newline (and thus
restoring the gcc 5 behavior):

foo.F90:7:4:

 1000 continue ! first instance
    1
foo.F90:11:4:

 1000 continue ! second instance
    2
Error: Duplicate statement label 1000 at (1) and (2)

Given that the code to print the locus information varies slightly
for Fortran, the patch adds it as a new callback within the
diagnostic_context: "start_span", called from diagnostic_show_locus.

I added a "dg-locus" directive for detecting these locus lines from
test cases.

As far as I know, we currently have no test coverage for the Fortran
frontend's printing of caret and source code; the test suite implicity
injects -fno-diagnostics-show-caret into options, and gfortran-dg.exp
expects this and rewrites the output somewhat accordingly.

This patch adds Fortran test cases that use -fdiagnostics-show-caret,
and adds support to gfortran-dg.exp to detect this, and to disable the
output rewriting, so that the textual output for this case can be
more directly tested.  This gives us test coverage of source-printing
of multi-location diagnostics emitted by the Fortran frontend.

The patch also adds similar test coverage for the C frontend.  In
both cases (C and Fortran), the test cases exercise a variety of
situations in which the lines can be all in one line-span, or split
between two or three.

The patch adds the first use of dg-begin/end-multiline-output for
Fortran.  Given that Fortran doesn't (to my knowledge) support
multiline comments, I enclosed the directives in a
#if 0/#endif pair (which requires the test cases to be .F90, rather
than .f90).

Successfully bootstrapped&regrtested on x86_64-pc-linux-gnu.

Adds 28 new PASS results to gcc.sum and 8 new PASS results to
gfortran.sum.

OK for trunk in stage 4?  (PR 69554 is a regression)

gcc/ChangeLog:
	PR other/69554
	* diagnostic-show-locus.c (struct line_span): New struct.
	(layout::get_first_line): Delete.
	(layout::get_last_line): Delete.
	(layout::get_num_line_spans): New member function.
	(layout::get_line_span): Likewise.
	(layout::print_heading_for_line_span_index_p): Likewise.
	(layout::get_expanded_location): Likewise.
	(layout::calculate_line_spans): Likewise.
	(layout::m_first_line): Delete.
	(layout::m_last_line): Delete.
	(layout::m_line_spans): New field.
	(layout::layout): Update comment.  Replace m_first_line and
	m_last_line with m_line_spans, replacing their initialization
	with a call to calculate_line_spans.
	(diagnostic_show_locus): When printing source lines and
	annotations, rather than looping over a single span
	of lines, instead loop over each line_span within
	the layout, with an inner loop over the lines within them.
	Call the context's start_span callback when changing line spans.
	* diagnostic.c (diagnostic_initialize): Initialize start_span.
	(diagnostic_build_prefix): Break out the building of the location
	part of the string into...
	(diagnostic_get_location_text): ...this new function, rewriting
	it from nested ternary expressions to a sequence of "if"
	statements.
	(default_diagnostic_start_span_fn): New function.
	* diagnostic.h (diagnostic_start_span_fn): New typedef.
	(diagnostic_context::start_span): New field.
	(default_diagnostic_start_span_fn): New prototype.

gcc/fortran/ChangeLog:
	PR other/69554
	* error.c (gfc_diagnostic_start_span): New function.
	(gfc_diagnostics_init): Initialize global_dc's start_span.

gcc/testsuite/ChangeLog:
	PR other/69554
	* gcc.dg/pr69554-1.c: New test.
	* gfortran.dg/pr69554-1.F90: New test.
	* gfortran.dg/pr69554-2.F90: New test.
	* lib/gcc-dg.exp (proc dg-locus): New function.
	* lib/gfortran-dg.exp (proc gfortran-dg-test): Update comment to
	distinguish between the caret-printing and non-caret-printing
	cases.  If caret-printing has been explicitly enabled, bail out
	without attempting to fix up the output.
---
 gcc/diagnostic-show-locus.c             | 226 ++++++++++++++++++++++++++++----
 gcc/diagnostic.c                        |  62 ++++++---
 gcc/diagnostic.h                        |  11 ++
 gcc/fortran/error.c                     |  15 +++
 gcc/testsuite/gcc.dg/pr69554-1.c        | 152 +++++++++++++++++++++
 gcc/testsuite/gfortran.dg/pr69554-1.F90 |  28 ++++
 gcc/testsuite/gfortran.dg/pr69554-2.F90 |  21 +++
 gcc/testsuite/lib/gcc-dg.exp            |  27 ++++
 gcc/testsuite/lib/gfortran-dg.exp       |  19 ++-
 9 files changed, 520 insertions(+), 41 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/pr69554-1.c
 create mode 100644 gcc/testsuite/gfortran.dg/pr69554-1.F90
 create mode 100644 gcc/testsuite/gfortran.dg/pr69554-2.F90

Comments

Jeff Law Feb. 12, 2016, 6:25 p.m. UTC | #1
On 02/09/2016 01:54 PM, David Malcolm wrote:

> gcc/ChangeLog:
> 	PR other/69554
> 	* diagnostic-show-locus.c (struct line_span): New struct.
> 	(layout::get_first_line): Delete.
> 	(layout::get_last_line): Delete.
> 	(layout::get_num_line_spans): New member function.
> 	(layout::get_line_span): Likewise.
> 	(layout::print_heading_for_line_span_index_p): Likewise.
> 	(layout::get_expanded_location): Likewise.
> 	(layout::calculate_line_spans): Likewise.
> 	(layout::m_first_line): Delete.
> 	(layout::m_last_line): Delete.
> 	(layout::m_line_spans): New field.
> 	(layout::layout): Update comment.  Replace m_first_line and
> 	m_last_line with m_line_spans, replacing their initialization
> 	with a call to calculate_line_spans.
> 	(diagnostic_show_locus): When printing source lines and
> 	annotations, rather than looping over a single span
> 	of lines, instead loop over each line_span within
> 	the layout, with an inner loop over the lines within them.
> 	Call the context's start_span callback when changing line spans.
> 	* diagnostic.c (diagnostic_initialize): Initialize start_span.
> 	(diagnostic_build_prefix): Break out the building of the location
> 	part of the string into...
> 	(diagnostic_get_location_text): ...this new function, rewriting
> 	it from nested ternary expressions to a sequence of "if"
> 	statements.
> 	(default_diagnostic_start_span_fn): New function.
> 	* diagnostic.h (diagnostic_start_span_fn): New typedef.
> 	(diagnostic_context::start_span): New field.
> 	(default_diagnostic_start_span_fn): New prototype.
>
> gcc/fortran/ChangeLog:
> 	PR other/69554
> 	* error.c (gfc_diagnostic_start_span): New function.
> 	(gfc_diagnostics_init): Initialize global_dc's start_span.
>
> gcc/testsuite/ChangeLog:
> 	PR other/69554
> 	* gcc.dg/pr69554-1.c: New test.
> 	* gfortran.dg/pr69554-1.F90: New test.
> 	* gfortran.dg/pr69554-2.F90: New test.
> 	* lib/gcc-dg.exp (proc dg-locus): New function.
> 	* lib/gfortran-dg.exp (proc gfortran-dg-test): Update comment to
> 	distinguish between the caret-printing and non-caret-printing
> 	cases.  If caret-printing has been explicitly enabled, bail out
> 	without attempting to fix up the output.
> ---
>   gcc/diagnostic-show-locus.c             | 226 ++++++++++++++++++++++++++++----
>   gcc/diagnostic.c                        |  62 ++++++---
>   gcc/diagnostic.h                        |  11 ++
>   gcc/fortran/error.c                     |  15 +++
>   gcc/testsuite/gcc.dg/pr69554-1.c        | 152 +++++++++++++++++++++
>   gcc/testsuite/gfortran.dg/pr69554-1.F90 |  28 ++++
>   gcc/testsuite/gfortran.dg/pr69554-2.F90 |  21 +++
>   gcc/testsuite/lib/gcc-dg.exp            |  27 ++++
>   gcc/testsuite/lib/gfortran-dg.exp       |  19 ++-
>   9 files changed, 520 insertions(+), 41 deletions(-)
>   create mode 100644 gcc/testsuite/gcc.dg/pr69554-1.c
>   create mode 100644 gcc/testsuite/gfortran.dg/pr69554-1.F90
>   create mode 100644 gcc/testsuite/gfortran.dg/pr69554-2.F90
>
> diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show-locus.c
> index d9b6750..698f42e 100644
> --- a/gcc/diagnostic-show-locus.c
> +++ b/gcc/diagnostic-show-locus.c

> @@ -437,8 +477,7 @@ layout::layout (diagnostic_context * context,
>     m_colorizer (context, diagnostic),
>     m_colorize_source_p (context->colorize_source_p),
>     m_layout_ranges (rich_location::MAX_RANGES),
> -  m_first_line (m_exploc.line),
> -  m_last_line  (m_exploc.line),
> +  m_line_spans (1 + rich_location::MAX_RANGES),
>     m_x_offset (0)
Umm, does that allocate 1 + rich_location::MAX_RANGES linespans?



> +  auto_vec<line_span> tmp_spans (1 + rich_location::MAX_RANGES);

Similarly.

Jeff
David Malcolm Feb. 12, 2016, 7:06 p.m. UTC | #2
On Fri, 2016-02-12 at 11:25 -0700, Jeff Law wrote:
> On 02/09/2016 01:54 PM, David Malcolm wrote:
> 
> > gcc/ChangeLog:
> > 	PR other/69554
> > 	* diagnostic-show-locus.c (struct line_span): New struct.
> > 	(layout::get_first_line): Delete.
> > 	(layout::get_last_line): Delete.
> > 	(layout::get_num_line_spans): New member function.
> > 	(layout::get_line_span): Likewise.
> > 	(layout::print_heading_for_line_span_index_p): Likewise.
> > 	(layout::get_expanded_location): Likewise.
> > 	(layout::calculate_line_spans): Likewise.
> > 	(layout::m_first_line): Delete.
> > 	(layout::m_last_line): Delete.
> > 	(layout::m_line_spans): New field.
> > 	(layout::layout): Update comment.  Replace m_first_line and
> > 	m_last_line with m_line_spans, replacing their initialization
> > 	with a call to calculate_line_spans.
> > 	(diagnostic_show_locus): When printing source lines and
> > 	annotations, rather than looping over a single span
> > 	of lines, instead loop over each line_span within
> > 	the layout, with an inner loop over the lines within them.
> > 	Call the context's start_span callback when changing line
> > spans.
> > 	* diagnostic.c (diagnostic_initialize): Initialize start_span.
> > 	(diagnostic_build_prefix): Break out the building of the
> > location
> > 	part of the string into...
> > 	(diagnostic_get_location_text): ...this new function, rewriting
> > 	it from nested ternary expressions to a sequence of "if"
> > 	statements.
> > 	(default_diagnostic_start_span_fn): New function.
> > 	* diagnostic.h (diagnostic_start_span_fn): New typedef.
> > 	(diagnostic_context::start_span): New field.
> > 	(default_diagnostic_start_span_fn): New prototype.
> > 
> > gcc/fortran/ChangeLog:
> > 	PR other/69554
> > 	* error.c (gfc_diagnostic_start_span): New function.
> > 	(gfc_diagnostics_init): Initialize global_dc's start_span.
> > 
> > gcc/testsuite/ChangeLog:
> > 	PR other/69554
> > 	* gcc.dg/pr69554-1.c: New test.
> > 	* gfortran.dg/pr69554-1.F90: New test.
> > 	* gfortran.dg/pr69554-2.F90: New test.
> > 	* lib/gcc-dg.exp (proc dg-locus): New function.
> > 	* lib/gfortran-dg.exp (proc gfortran-dg-test): Update comment
> > to
> > 	distinguish between the caret-printing and non-caret-printing
> > 	cases.  If caret-printing has been explicitly enabled, bail out
> > 	without attempting to fix up the output.
> > ---
> >   gcc/diagnostic-show-locus.c             | 226
> > ++++++++++++++++++++++++++++----
> >   gcc/diagnostic.c                        |  62 ++++++---
> >   gcc/diagnostic.h                        |  11 ++
> >   gcc/fortran/error.c                     |  15 +++
> >   gcc/testsuite/gcc.dg/pr69554-1.c        | 152
> > +++++++++++++++++++++
> >   gcc/testsuite/gfortran.dg/pr69554-1.F90 |  28 ++++
> >   gcc/testsuite/gfortran.dg/pr69554-2.F90 |  21 +++
> >   gcc/testsuite/lib/gcc-dg.exp            |  27 ++++
> >   gcc/testsuite/lib/gfortran-dg.exp       |  19 ++-
> >   9 files changed, 520 insertions(+), 41 deletions(-)
> >   create mode 100644 gcc/testsuite/gcc.dg/pr69554-1.c
> >   create mode 100644 gcc/testsuite/gfortran.dg/pr69554-1.F90
> >   create mode 100644 gcc/testsuite/gfortran.dg/pr69554-2.F90
> > 
> > diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show
> > -locus.c
> > index d9b6750..698f42e 100644
> > --- a/gcc/diagnostic-show-locus.c
> > +++ b/gcc/diagnostic-show-locus.c
> 
> > @@ -437,8 +477,7 @@ layout::layout (diagnostic_context * context,
> >     m_colorizer (context, diagnostic),
> >     m_colorize_source_p (context->colorize_source_p),
> >     m_layout_ranges (rich_location::MAX_RANGES),
> > -  m_first_line (m_exploc.line),
> > -  m_last_line  (m_exploc.line),
> > +  m_line_spans (1 + rich_location::MAX_RANGES),
> >     m_x_offset (0)
> Umm, does that allocate 1 + rich_location::MAX_RANGES linespans?
> 
> 
> 
> > +  auto_vec<line_span> tmp_spans (1 + rich_location::MAX_RANGES);
> 
> Similarly.

Yes, it's preallocating space in the vecs.  rich_location::MAX_RANGES
is 3, so there can be at most 4 line spans to consider.

A line_span is two linenum_type i.e. a pair of unsigned int, so
assuming sizeof(unsigned int) == 4 we have 4 * 2 * 4 = 32 bytes.

My thinking here was to preallocate these two vecs with the maximum
size they can be, to avoid growing the vec by one each time, thus
avoiding "exponential" reallocing and copying.

Typically there will be just one or two elements, rather than 4 in this
vec, but given the tiny sizes it seemed easiest to preallocate the
maximum possible size (granted, N is so small that O(N^2) isn't going
to get that big).

This pair of allocations is done once per call to diagnostic_show_locus
i.e. per diagnostic that ends up printing source code.

I believe vl_embed isn't usable here (though I find vec.h to be clear
as mud); would a simple array plus length be better?

Dave
Jeff Law Feb. 12, 2016, 7:07 p.m. UTC | #3
On 02/12/2016 12:06 PM, David Malcolm wrote:
> On Fri, 2016-02-12 at 11:25 -0700, Jeff Law wrote:
>> On 02/09/2016 01:54 PM, David Malcolm wrote:
>>
>>> gcc/ChangeLog:
>>> 	PR other/69554
>>> 	* diagnostic-show-locus.c (struct line_span): New struct.
>>> 	(layout::get_first_line): Delete.
>>> 	(layout::get_last_line): Delete.
>>> 	(layout::get_num_line_spans): New member function.
>>> 	(layout::get_line_span): Likewise.
>>> 	(layout::print_heading_for_line_span_index_p): Likewise.
>>> 	(layout::get_expanded_location): Likewise.
>>> 	(layout::calculate_line_spans): Likewise.
>>> 	(layout::m_first_line): Delete.
>>> 	(layout::m_last_line): Delete.
>>> 	(layout::m_line_spans): New field.
>>> 	(layout::layout): Update comment.  Replace m_first_line and
>>> 	m_last_line with m_line_spans, replacing their initialization
>>> 	with a call to calculate_line_spans.
>>> 	(diagnostic_show_locus): When printing source lines and
>>> 	annotations, rather than looping over a single span
>>> 	of lines, instead loop over each line_span within
>>> 	the layout, with an inner loop over the lines within them.
>>> 	Call the context's start_span callback when changing line
>>> spans.
>>> 	* diagnostic.c (diagnostic_initialize): Initialize start_span.
>>> 	(diagnostic_build_prefix): Break out the building of the
>>> location
>>> 	part of the string into...
>>> 	(diagnostic_get_location_text): ...this new function, rewriting
>>> 	it from nested ternary expressions to a sequence of "if"
>>> 	statements.
>>> 	(default_diagnostic_start_span_fn): New function.
>>> 	* diagnostic.h (diagnostic_start_span_fn): New typedef.
>>> 	(diagnostic_context::start_span): New field.
>>> 	(default_diagnostic_start_span_fn): New prototype.
>>>
>>> gcc/fortran/ChangeLog:
>>> 	PR other/69554
>>> 	* error.c (gfc_diagnostic_start_span): New function.
>>> 	(gfc_diagnostics_init): Initialize global_dc's start_span.
>>>
>>> gcc/testsuite/ChangeLog:
>>> 	PR other/69554
>>> 	* gcc.dg/pr69554-1.c: New test.
>>> 	* gfortran.dg/pr69554-1.F90: New test.
>>> 	* gfortran.dg/pr69554-2.F90: New test.
>>> 	* lib/gcc-dg.exp (proc dg-locus): New function.
>>> 	* lib/gfortran-dg.exp (proc gfortran-dg-test): Update comment
>>> to
>>> 	distinguish between the caret-printing and non-caret-printing
>>> 	cases.  If caret-printing has been explicitly enabled, bail out
>>> 	without attempting to fix up the output.
>>> ---
>>>    gcc/diagnostic-show-locus.c             | 226
>>> ++++++++++++++++++++++++++++----
>>>    gcc/diagnostic.c                        |  62 ++++++---
>>>    gcc/diagnostic.h                        |  11 ++
>>>    gcc/fortran/error.c                     |  15 +++
>>>    gcc/testsuite/gcc.dg/pr69554-1.c        | 152
>>> +++++++++++++++++++++
>>>    gcc/testsuite/gfortran.dg/pr69554-1.F90 |  28 ++++
>>>    gcc/testsuite/gfortran.dg/pr69554-2.F90 |  21 +++
>>>    gcc/testsuite/lib/gcc-dg.exp            |  27 ++++
>>>    gcc/testsuite/lib/gfortran-dg.exp       |  19 ++-
>>>    9 files changed, 520 insertions(+), 41 deletions(-)
>>>    create mode 100644 gcc/testsuite/gcc.dg/pr69554-1.c
>>>    create mode 100644 gcc/testsuite/gfortran.dg/pr69554-1.F90
>>>    create mode 100644 gcc/testsuite/gfortran.dg/pr69554-2.F90
>>>
>>> diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show
>>> -locus.c
>>> index d9b6750..698f42e 100644
>>> --- a/gcc/diagnostic-show-locus.c
>>> +++ b/gcc/diagnostic-show-locus.c
>>
>>> @@ -437,8 +477,7 @@ layout::layout (diagnostic_context * context,
>>>      m_colorizer (context, diagnostic),
>>>      m_colorize_source_p (context->colorize_source_p),
>>>      m_layout_ranges (rich_location::MAX_RANGES),
>>> -  m_first_line (m_exploc.line),
>>> -  m_last_line  (m_exploc.line),
>>> +  m_line_spans (1 + rich_location::MAX_RANGES),
>>>      m_x_offset (0)
>> Umm, does that allocate 1 + rich_location::MAX_RANGES linespans?
>>
>>
>>
>>> +  auto_vec<line_span> tmp_spans (1 + rich_location::MAX_RANGES);
>>
>> Similarly.
>
> Yes, it's preallocating space in the vecs.  rich_location::MAX_RANGES
> is 3, so there can be at most 4 line spans to consider.
Ah, nevermind then.  If it's just 4, then I'm not worried.

OK for the trunk.
jeff
diff mbox

Patch

diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show-locus.c
index d9b6750..698f42e 100644
--- a/gcc/diagnostic-show-locus.c
+++ b/gcc/diagnostic-show-locus.c
@@ -137,6 +137,40 @@  struct line_bounds
   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.
@@ -151,14 +185,20 @@  class layout
   layout (diagnostic_context *context,
 	  const diagnostic_info *diagnostic);
 
-  int get_first_line () const { return m_first_line; }
-  int get_last_line () const { return m_last_line; }
+  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);
   void print_annotation_line (int row, const line_bounds lbounds);
   void print_any_fixits (int row, const rich_location *richloc);
 
  private:
+  void calculate_line_spans ();
+
   void print_newline ();
 
   bool
@@ -183,8 +223,7 @@  class layout
   colorizer m_colorizer;
   bool m_colorize_source_p;
   auto_vec <layout_range> m_layout_ranges;
-  int m_first_line;
-  int m_last_line;
+  auto_vec <line_span> m_line_spans;
   int m_x_offset;
 };
 
@@ -424,7 +463,8 @@  get_line_width_without_trailing_whitespace (const char *line, int line_width)
 
    Filter the ranges from the rich_location to those that we can
    sanely print, populating m_layout_ranges.
-   Determine the range of lines that we will print.
+   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.  */
 
@@ -437,8 +477,7 @@  layout::layout (diagnostic_context * context,
   m_colorizer (context, diagnostic),
   m_colorize_source_p (context->colorize_source_p),
   m_layout_ranges (rich_location::MAX_RANGES),
-  m_first_line (m_exploc.line),
-  m_last_line  (m_exploc.line),
+  m_line_spans (1 + rich_location::MAX_RANGES),
   m_x_offset (0)
 {
   rich_location *richloc = diagnostic->richloc;
@@ -484,14 +523,11 @@  layout::layout (diagnostic_context * context,
       /* Passed all the tests; add the range to m_layout_ranges so that
 	 it will be printed.  */
       m_layout_ranges.safe_push (ri);
-
-      /* Update m_first_line/m_last_line if necessary.  */
-      if (ri.m_start.m_line < m_first_line)
-	m_first_line = ri.m_start.m_line;
-      if (ri.m_finish.m_line > m_last_line)
-	m_last_line = ri.m_finish.m_line;
     }
 
+  /* 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.  */
@@ -511,6 +547,142 @@  layout::layout (diagnostic_context * context,
     }
 }
 
+/* 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;
+	}
+    }
+
+  /* It should not be possible to have a line span that didn't
+     contain any of the layout_range instances.  */
+  gcc_unreachable ();
+  return m_exploc;
+}
+
+/* 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 + rich_location::MAX_RANGES);
+  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));
+    }
+
+  /* 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.
@@ -825,17 +997,27 @@  diagnostic_show_locus (diagnostic_context * context,
   pp_set_prefix (context->printer, NULL);
 
   layout layout (context, diagnostic);
-  int last_line = layout.get_last_line ();
-  for (int row = layout.get_first_line (); row <= last_line; row++)
+  for (int line_span_idx = 0; line_span_idx < layout.get_num_line_spans ();
+       line_span_idx++)
     {
-      /* 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))
+      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++)
 	{
-	  layout.print_annotation_line (row, lbounds);
-	  layout.print_any_fixits (row, diagnostic->richloc);
+	  /* 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))
+	    {
+	      layout.print_annotation_line (row, lbounds);
+	      layout.print_any_fixits (row, diagnostic->richloc);
+	    }
 	}
     }
 
diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
index f661b57..322f2d9 100644
--- a/gcc/diagnostic.c
+++ b/gcc/diagnostic.c
@@ -158,6 +158,7 @@  diagnostic_initialize (diagnostic_context *context, int n_opts)
   context->max_errors = 0;
   context->internal_error = NULL;
   diagnostic_starter (context) = default_diagnostic_starter;
+  context->start_span = default_diagnostic_start_span_fn;
   diagnostic_finalizer (context) = default_diagnostic_finalizer;
   context->option_enabled = NULL;
   context->option_state = NULL;
@@ -274,8 +275,34 @@  diagnostic_get_color_for_kind (diagnostic_t kind)
   return diagnostic_kind_color[kind];
 }
 
-/* Return a malloc'd string describing a location.  The caller is
-   responsible for freeing the memory.  */
+/* Return a malloc'd string describing a location e.g. "foo.c:42:10".
+   The caller is responsible for freeing the memory.  */
+
+static char *
+diagnostic_get_location_text (diagnostic_context *context,
+			      expanded_location s)
+{
+  pretty_printer *pp = context->printer;
+  const char *locus_cs = colorize_start (pp_show_color (pp), "locus");
+  const char *locus_ce = colorize_stop (pp_show_color (pp));
+
+  if (s.file == NULL)
+    return build_message_string ("%s%s:%s", locus_cs, progname, locus_ce);
+
+  if (!strcmp (s.file, N_("<built-in>")))
+    return build_message_string ("%s%s:%s", locus_cs, s.file, locus_ce);
+
+  if (context->show_column)
+    return build_message_string ("%s%s:%d:%d:%s", locus_cs, s.file, s.line,
+				 s.column, locus_ce);
+  else
+    return build_message_string ("%s%s:%d:%s", locus_cs, s.file, s.line,
+				 locus_ce);
+}
+
+/* Return a malloc'd string describing a location and the severity of the
+   diagnostic, e.g. "foo.c:42:10: error: ".  The caller is responsible for
+   freeing the memory.  */
 char *
 diagnostic_build_prefix (diagnostic_context *context,
 			 const diagnostic_info *diagnostic)
@@ -290,7 +317,6 @@  diagnostic_build_prefix (diagnostic_context *context,
 
   const char *text = _(diagnostic_kind_text[diagnostic->kind]);
   const char *text_cs = "", *text_ce = "";
-  const char *locus_cs, *locus_ce;
   pretty_printer *pp = context->printer;
 
   if (diagnostic_kind_color[diagnostic->kind])
@@ -299,22 +325,14 @@  diagnostic_build_prefix (diagnostic_context *context,
 				diagnostic_kind_color[diagnostic->kind]);
       text_ce = colorize_stop (pp_show_color (pp));
     }
-  locus_cs = colorize_start (pp_show_color (pp), "locus");
-  locus_ce = colorize_stop (pp_show_color (pp));
 
   expanded_location s = diagnostic_expand_location (diagnostic);
-  return
-    (s.file == NULL
-     ? build_message_string ("%s%s:%s %s%s%s", locus_cs, progname, locus_ce,
-			     text_cs, text, text_ce)
-     : !strcmp (s.file, N_("<built-in>"))
-     ? build_message_string ("%s%s:%s %s%s%s", locus_cs, s.file, locus_ce,
-			     text_cs, text, text_ce)
-     : context->show_column
-     ? build_message_string ("%s%s:%d:%d:%s %s%s%s", locus_cs, s.file, s.line,
-			     s.column, locus_ce, text_cs, text, text_ce)
-     : build_message_string ("%s%s:%d:%s %s%s%s", locus_cs, s.file, s.line,
-			     locus_ce, text_cs, text, text_ce));
+  char *location_text = diagnostic_get_location_text (context, s);
+
+  char *result = build_message_string ("%s %s%s%s", location_text,
+				       text_cs, text, text_ce);
+  free (location_text);
+  return result;
 }
 
 /* Functions at which to stop the backtrace print.  It's not
@@ -541,6 +559,16 @@  default_diagnostic_starter (diagnostic_context *context,
 }
 
 void
+default_diagnostic_start_span_fn (diagnostic_context *context,
+				  expanded_location exploc)
+{
+  pp_set_prefix (context->printer,
+		 diagnostic_get_location_text (context, exploc));
+  pp_string (context->printer, "");
+  pp_newline (context->printer);
+}
+
+void
 default_diagnostic_finalizer (diagnostic_context *context,
 			      diagnostic_info *diagnostic)
 {
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 7cc5cff..017ddca 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -56,6 +56,10 @@  struct diagnostic_classification_change_t
 /*  Forward declarations.  */
 typedef void (*diagnostic_starter_fn) (diagnostic_context *,
 				       diagnostic_info *);
+
+typedef void (*diagnostic_start_span_fn) (diagnostic_context *,
+					  expanded_location);
+
 typedef diagnostic_starter_fn diagnostic_finalizer_fn;
 
 /* This data structure bundles altogether any information relevant to
@@ -148,6 +152,11 @@  struct diagnostic_context
   */
   diagnostic_starter_fn begin_diagnostic;
 
+  /* This function is called by diagnostic_show_locus in between
+     disjoint spans of source code, so that the context can print
+     something to indicate that a new span of source code has begun.  */
+  diagnostic_start_span_fn start_span;
+
   /* This function is called after the diagnostic message is printed.  */
   diagnostic_finalizer_fn end_diagnostic;
 
@@ -296,6 +305,8 @@  extern void diagnostic_append_note (diagnostic_context *, location_t,
 #endif
 extern char *diagnostic_build_prefix (diagnostic_context *, const diagnostic_info *);
 void default_diagnostic_starter (diagnostic_context *, diagnostic_info *);
+void default_diagnostic_start_span_fn (diagnostic_context *,
+				       expanded_location);
 void default_diagnostic_finalizer (diagnostic_context *, diagnostic_info *);
 void diagnostic_set_caret_max_width (diagnostic_context *context, int value);
 void diagnostic_action_after_output (diagnostic_context *, diagnostic_t);
diff --git a/gcc/fortran/error.c b/gcc/fortran/error.c
index e7f4ba7..003702b 100644
--- a/gcc/fortran/error.c
+++ b/gcc/fortran/error.c
@@ -1103,6 +1103,20 @@  gfc_diagnostic_starter (diagnostic_context *context,
 }
 
 static void
+gfc_diagnostic_start_span (diagnostic_context *context,
+			   expanded_location exploc)
+{
+  char *locus_prefix;
+  locus_prefix = gfc_diagnostic_build_locus_prefix (context, exploc);
+  pp_verbatim (context->printer, locus_prefix);
+  free (locus_prefix);
+  pp_newline (context->printer);
+  /* Fortran uses an empty line between locus and caret line.  */
+  pp_newline (context->printer);
+}
+
+
+static void
 gfc_diagnostic_finalizer (diagnostic_context *context,
 			  diagnostic_info *diagnostic ATTRIBUTE_UNUSED)
 {
@@ -1426,6 +1440,7 @@  void
 gfc_diagnostics_init (void)
 {
   diagnostic_starter (global_dc) = gfc_diagnostic_starter;
+  global_dc->start_span = gfc_diagnostic_start_span;
   diagnostic_finalizer (global_dc) = gfc_diagnostic_finalizer;
   diagnostic_format_decoder (global_dc) = gfc_format_decoder;
   global_dc->caret_chars[0] = '1';
diff --git a/gcc/testsuite/gcc.dg/pr69554-1.c b/gcc/testsuite/gcc.dg/pr69554-1.c
new file mode 100644
index 0000000..07ad0db
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pr69554-1.c
@@ -0,0 +1,152 @@ 
+/* { dg-options "-fdiagnostics-show-caret" } */
+
+/* Various versions of the same C error, with a variety of line spacing,
+   and of columns, to exercise the line-span handling in
+   diagnostic-show-locus.c (PR other/69554).  */
+
+/* All on one line.  */
+
+int test_1 (const char *p, const char *q)
+{
+  return (p + 1) + (q + 1); /* { dg-error "invalid operands" } */
+/* { dg-begin-multiline-output "" }
+   return (p + 1) + (q + 1);
+          ~~~~~~~ ^ ~~~~~~~
+   { dg-end-multiline-output "" } */
+}
+
+/* On separate lines, but without intervening lines.
+   This can be printed as a single span of lines.  */
+
+int test_2 (const char *p, const char *q)
+{
+  return (p + 1)
+           +  /* { dg-error "invalid operands" } */
+            (q + 1);
+/* { dg-begin-multiline-output "" }
+   return (p + 1)
+          ~~~~~~~
+            +
+            ^
+             (q + 1);
+             ~~~~~~~
+   { dg-end-multiline-output "" } */
+}
+
+/* On separate lines, with an intervening line between lines 1 and 2.
+   This is printed as 2 "spans" of lines, broken up by the intervening
+   line.  */
+
+int test_3 (const char *p, const char *q)
+{
+  return (p + 1) /* { dg-locus "10" } */
+
+           +  /* { dg-error "invalid operands" } */
+             (q + 1);
+/* { dg-locus "12" "" { target *-*-* } "44" } */
+/* { dg-begin-multiline-output "" }
+   return (p + 1)
+          ~~~~~~~
+   { dg-end-multiline-output "" } */
+/* { dg-begin-multiline-output "" }
+            +
+            ^
+              (q + 1);
+              ~~~~~~~
+   { dg-end-multiline-output "" } */
+}
+
+/* As above, but the intervening line is between lines 2 and 3,
+   so that the 2 spans are grouped the other way.  */
+
+int test_4 (const char *p, const char *q)
+{
+  return (p + 1)
+           +  /* { dg-error "invalid operands" } */
+
+             (q + 1); /* { dg-locus "14" } */
+/* { dg-begin-multiline-output "" }
+   return (p + 1)
+          ~~~~~~~
+            +
+            ^
+   { dg-end-multiline-output "" } */
+/* { dg-begin-multiline-output "" }
+              (q + 1);
+              ~~~~~~~
+   { dg-end-multiline-output "" } */
+}
+
+/* On separate lines, with intervening lines.
+   This is printed as 3 "spans" of lines, each span being an
+   individual line.  */
+
+int test_5 (const char *p, const char *q)
+{
+  return (p + 1) /* { dg-locus "10" } */
+
+           +  /* { dg-error "invalid operands" } */
+
+             (q + 1); /* { dg-locus "14" } */
+/* { dg-locus "12" "" { target *-*-* } "88" } */
+/* { dg-begin-multiline-output "" }
+   return (p + 1)
+          ~~~~~~~
+   { dg-end-multiline-output "" } */
+/* { dg-begin-multiline-output "" }
+            +
+            ^
+   { dg-end-multiline-output "" } */
+/* { dg-begin-multiline-output "" }
+              (q + 1);
+              ~~~~~~~
+   { dg-end-multiline-output "" } */
+}
+
+/* On separate lines, with numerous intervening lines.
+   This is printed as 3 "spans" of lines, each span being an
+   individual line.  */
+
+int test_6 (const char *p, const char *q)
+{
+  return (p + 1) /* { dg-locus "10" } */
+	  /* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+	     Maecenas nisl sapien, rutrum non euismod et, rutrum ac felis.
+	     Morbi nec nisi ipsum. Quisque pulvinar ante nec urna rhoncus,
+	     a cursus nisi commodo. Praesent euismod neque lectus, at
+	     dapibus ipsum gravida in. Pellentesque tempor massa eu viverra
+	     feugiat. Proin eleifend pulvinar urna, ut dapibus metus vehicula
+	     ac. Suspendisse rutrum finibus quam, ac dignissim diam blandit
+	     maximus. In blandit viverra pulvinar. Praesent vel tellus
+	     elementum, placerat lacus quis, ornare lectus. Donec ac
+	     eleifend nulla, sit amet condimentum risus. Vestibulum aliquam
+	     maximus ante non pellentesque. Praesent mollis ante in risus
+	     feugiat hendrerit. Praesent feugiat maximus urna nec blandit. */
+           +  /* { dg-error "invalid operands" } */
+	  /* Vestibulum ac nunc eget enim tempor tristique. Suspendisse
+	     potenti. Nam et sollicitudin enim. Morbi sed tincidunt lectus.
+	     Sed facilisis velit at ante maximus feugiat. Sed vestibulum mi
+	     id leo tempor, sed ullamcorper sapien efficitur. Vestibulum purus
+	     lacus, dignissim non magna at, tincidunt luctus nisl. Cum sociis
+	     natoque penatibus et magnis dis parturient montes, nascetur
+	     ridiculus mus. Donec elit elit, laoreet a dolor quis, eleifend
+	     dapibus metus. Proin lectus turpis, eleifend nec pharetra eu,
+	     fermentum in lacus. Morbi sit amet mauris orci. Nam sagittis,
+	     nibh vel fermentum dictum, purus ex hendrerit odio, feugiat
+	     fringilla sapien elit vitae nisl. Fusce mattis commodo risus
+	     nec convallis. */
+             (q + 1); /* { dg-locus "14" } */
+/* { dg-locus "12" "" { target *-*-* } "125" } */
+/* { dg-begin-multiline-output "" }
+   return (p + 1)
+          ~~~~~~~
+   { dg-end-multiline-output "" } */
+/* { dg-begin-multiline-output "" }
+            +
+            ^
+   { dg-end-multiline-output "" } */
+/* { dg-begin-multiline-output "" }
+              (q + 1);
+              ~~~~~~~
+   { dg-end-multiline-output "" } */
+}
diff --git a/gcc/testsuite/gfortran.dg/pr69554-1.F90 b/gcc/testsuite/gfortran.dg/pr69554-1.F90
new file mode 100644
index 0000000..38a3c88
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/pr69554-1.F90
@@ -0,0 +1,28 @@ 
+! { dg-do compile }
+! { dg-options "-fdiagnostics-show-caret" }
+! { dg-allow-blank-lines-in-output 1 }
+
+program main
+  goto 1000
+1000 continue ! first instance
+  a = a
+  a = a
+  a = a
+1000 continue ! second instance
+end
+
+#if 0
+! { dg-locus "4" "" { target *-*-* } "7" }
+! { dg-begin-multiline-output "" }
+
+ 1000 continue ! first instance
+    1
+! { dg-end-multiline-output "" }
+! { dg-locus "4" "" { target *-*-* } "11" }
+! { dg-begin-multiline-output "" }
+
+ 1000 continue ! second instance
+    2
+Error: Duplicate statement label 1000 at (1) and (2)
+! { dg-end-multiline-output "" }
+#endif
diff --git a/gcc/testsuite/gfortran.dg/pr69554-2.F90 b/gcc/testsuite/gfortran.dg/pr69554-2.F90
new file mode 100644
index 0000000..0a25e58
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/pr69554-2.F90
@@ -0,0 +1,21 @@ 
+! { dg-do compile }
+! { dg-options "-fdiagnostics-show-caret" }
+! { dg-allow-blank-lines-in-output 1 }
+
+program main
+  goto 1000
+1000 continue ! first instance
+1000 continue ! second instance
+end
+
+#if 0
+! { dg-locus "4" "" { target *-*-* } "7" }
+! { dg-begin-multiline-output "" }
+
+ 1000 continue ! first instance
+    1
+ 1000 continue ! second instance
+    2
+Error: Duplicate statement label 1000 at (1) and (2)
+! { dg-end-multiline-output "" }
+#endif
diff --git a/gcc/testsuite/lib/gcc-dg.exp b/gcc/testsuite/lib/gcc-dg.exp
index 3dd8564..b732b54 100644
--- a/gcc/testsuite/lib/gcc-dg.exp
+++ b/gcc/testsuite/lib/gcc-dg.exp
@@ -988,6 +988,33 @@  proc dg-message { args } {
     process-message saved-dg-warning "" $args
 }
 
+# Look for a location marker of the form
+#   file:line:column:
+# with no extra text (e.g. a line-span separator).
+
+proc dg-locus { args } {
+    upvar dg-messages dg-messages
+
+    # Process the dg- directive, including adding the regular expression
+    # to the new message entry in dg-messages.
+    set msgcnt [llength ${dg-messages}]
+    eval saved-dg-warning $args
+
+    # If the target expression wasn't satisfied there is no new message.
+    if { [llength ${dg-messages}] == $msgcnt } {
+	return;
+    }
+
+    # Get the entry for the new message.  Prepend the message prefix to
+    # the regular expression and make it match a single line.
+    set newentry [lindex ${dg-messages} end]
+    set expmsg [lindex $newentry 2]
+
+    set newentry [lreplace $newentry 2 2 $expmsg]
+    set dg-messages [lreplace ${dg-messages} end end $newentry]
+    verbose "process-message:\n${dg-messages}" 2
+}
+
 # Check the existence of a gdb in the path, and return true if there
 # is one.
 #
diff --git a/gcc/testsuite/lib/gfortran-dg.exp b/gcc/testsuite/lib/gfortran-dg.exp
index 52bb341..6b7f98b 100644
--- a/gcc/testsuite/lib/gfortran-dg.exp
+++ b/gcc/testsuite/lib/gfortran-dg.exp
@@ -26,7 +26,15 @@  proc gfortran-dg-test { prog do_what extra_tool_flags } {
     set comp_output [lindex $result 0]
     set output_file [lindex $result 1]
 
-    # gfortran error messages look like this:
+    # gcc's default is to print the caret and source code, but
+    # most test cases implicitly use the flag -fno-diagnostics-show-caret
+    # to disable caret (and source code) printing.
+    #
+    # However, a few test cases override this back to the default by
+    # explicily supplying "-fdiagnostics-show-caret", so that we can have
+    # test coverage for caret/source code printing.
+    #
+    # gfortran error messages with caret-printing look like this:
     #     [name]:[locus]:
     #
     #        some code
@@ -49,7 +57,14 @@  proc gfortran-dg-test { prog do_what extra_tool_flags } {
     #              1       2
     #     Error: Some error at (1) and (2)
     #
-    # or
+    # If this is such a test case, skip the rest of this function, so
+    # that the test case can explicitly verify the output that it expects.
+    if {[string first "-fdiagnostics-show-caret" $extra_tool_flags] >= 0} {
+	return [list $comp_output $output_file]
+    }
+
+    # Otherwise, caret-printing is disabled.
+    # gfortran errors with caret-printing disabled look like this:
     #     [name]:[locus]: Error: Some error
     # or
     #     [name]:[locus]: Error: (1)