diff mbox

PR c/68473: sanitize source range-printing within certain macro expansions

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

Commit Message

David Malcolm Nov. 23, 2015, 5:52 p.m. UTC
This patch fixes PR c/68473 by bulletproofing the new
diagnostic_show_locus implementation against ranges that finish before
they start (which can happen when using the C preprocessor), falling
back to simply printing a caret.

Successfully bootstrapped&regrtested on x86_64-pc-linux-gnu; adds 7 new
PASS results to gcc.sum.

OK for trunk?

gcc/ChangeLog:
	PR c/68473
	* diagnostic-show-locus.c (layout::layout): Make loc_range const.
	Sanitize the layout_range against ranges that finish before they
	start.

gcc/testsuite/ChangeLog:
	PR c/68473
	* gcc.dg/plugin/diagnostic-test-expressions-1.c (fminl): New decl.
	(TEST_EQ): New macro.
	(test_macro): New function.
	* gcc.target/i386/pr68473-1.c: New test case.
---
 gcc/diagnostic-show-locus.c                        | 34 ++++++++++++++++++----
 .../gcc.dg/plugin/diagnostic-test-expressions-1.c  | 17 +++++++++++
 gcc/testsuite/gcc.target/i386/pr68473-1.c          | 24 +++++++++++++++
 3 files changed, 69 insertions(+), 6 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/i386/pr68473-1.c

Comments

Bernd Schmidt Nov. 23, 2015, 5:59 p.m. UTC | #1
On 11/23/2015 06:52 PM, David Malcolm wrote:
> This patch fixes PR c/68473 by bulletproofing the new
> diagnostic_show_locus implementation against ranges that finish before
> they start (which can happen when using the C preprocessor), falling
> back to simply printing a caret.

Hmm, wouldn't it be better to avoid such a situation? Can you describe a 
bit more how exactly the macro expansion caused such a situation?


Bernd
David Malcolm Nov. 23, 2015, 6:26 p.m. UTC | #2
On Mon, 2015-11-23 at 18:59 +0100, Bernd Schmidt wrote:
> On 11/23/2015 06:52 PM, David Malcolm wrote:
> > This patch fixes PR c/68473 by bulletproofing the new
> > diagnostic_show_locus implementation against ranges that finish before
> > they start (which can happen when using the C preprocessor), falling
> > back to simply printing a caret.
> 
> Hmm, wouldn't it be better to avoid such a situation? Can you describe a 
> bit more how exactly the macro expansion caused such a situation?

The issue is here:

     1	/* { dg-options "-fdiagnostics-show-caret -mno-fp-ret-in-387" } */
     2	
     3	extern long double fminl (long double __x, long double __y);
     4	
     5	#define TEST_EQ(FUNC) do { \
     6	  if ((long)FUNC##l(xl,xl) != (long)xl) \
     7	    return; \
     8	  } while (0)
     9	
    10	void
    11	foo (long double xl)
    12	{
    13	  TEST_EQ (fmin); /* { dg-error "x87 register return with x87 disabled" } */
    14	}


    16	/* { dg-begin-multiline-output "" }
    17	   TEST_EQ (fmin);
    18	            ^
    19	   { dg-end-multiline-output "" } */
    20	
    21	/* { dg-begin-multiline-output "" }
    22	   if ((long)FUNC##l(xl,xl) != (long)xl) \
    23	             ^~~~
    24	   { dg-end-multiline-output "" } */

An error is emitted whilst expanding the macro at line 13, at
input_location.

This is at the expansion of this function call:

   fminl (xl, xl)

Normally we'd emit a source range like this for a function call:

   fminl (xl, xl)
   ^~~~~~~~~~~~~~

However, once we fully resolve locations, the "fmin" part of "fminl"
appears at line 13 here:

    13	  TEST_EQ (fmin);
                   ^~~~

giving the location of the caret, and start of the range, whereas the
rest of the the call is spelled here:

     6	  if ((long)FUNC##l(xl,xl) != (long)xl) \
                           ~~~~~~~

where the close paren gives the end of the range.

It would be wrong to try to print the whole range (anything might be
between lines 6 and 13).

In theory we could attempt to try to handle this kind of thing by
looking at the macro expansions, and to print something like:

    13	  TEST_EQ (fmin);
                   ^~~~
     6	  if ((long)FUNC##l(xl,xl) != (long)xl) \
                          ~~~~~~~~

or whatnot, but that strikes me as error-prone at this stage.


The patch instead detects such a situation, and tries to handle things
gracefully by falling back to simply printing a caret, without any
underlines:

pr68473-1.c: In function ‘foo’:
pr68473-1.c:13:12: error: x87 register return with x87 disabled
   TEST_EQ (fmin);
            ^

pr68473-1.c:6:13: note: in definition of macro ‘TEST_EQ’
   if ((long)FUNC##l(xl,xl) != (long)xl) \
             ^~~~


Dave
Bernd Schmidt Nov. 24, 2015, 12:43 p.m. UTC | #3
On 11/23/2015 07:26 PM, David Malcolm wrote:
>
> In theory we could attempt to try to handle this kind of thing by
> looking at the macro expansions, and to print something like:
>
>      13	  TEST_EQ (fmin);
>                     ^~~~
>       6	  if ((long)FUNC##l(xl,xl) != (long)xl) \
>                            ~~~~~~~~
>
> or whatnot, but that strikes me as error-prone at this stage.

Could I ask you to spend some time looking at what would be involved at 
fixing it this way? In the end (assuming it doesn't prove to be a simple 
fix) we will probably go with your original patch for gcc-6, but it 
really goes against the grain to paper over a bug like this.


Bernd
Martin Sebor Dec. 10, 2015, 4:24 p.m. UTC | #4
On 11/23/2015 10:52 AM, David Malcolm wrote:
> This patch fixes PR c/68473 by bulletproofing the new
> diagnostic_show_locus implementation against ranges that finish before
> they start (which can happen when using the C preprocessor), falling
> back to simply printing a caret.
>
> Successfully bootstrapped&regrtested on x86_64-pc-linux-gnu; adds 7 new
> PASS results to gcc.sum.

I just ran into the same ICE with a different (simpler) example
in bug 68839 and confirm that the patch fixes that one as well.

Martin
David Malcolm Dec. 11, 2015, 6:45 p.m. UTC | #5
On Tue, 2015-11-24 at 13:43 +0100, Bernd Schmidt wrote:
> On 11/23/2015 07:26 PM, David Malcolm wrote:
> >
> > In theory we could attempt to try to handle this kind of thing by
> > looking at the macro expansions, and to print something like:
> >
> >      13	  TEST_EQ (fmin);
> >                     ^~~~
> >       6	  if ((long)FUNC##l(xl,xl) != (long)xl) \
> >                            ~~~~~~~~
> >
> > or whatnot, but that strikes me as error-prone at this stage.
> 
> Could I ask you to spend some time looking at what would be involved at 
> fixing it this way? In the end (assuming it doesn't prove to be a simple 
> fix) we will probably go with your original patch for gcc-6, but it 
> really goes against the grain to paper over a bug like this.

I've been looking into this.

The first issue we run into is that, currently, when we pass location_t
values into rich_location, it expands them immediately and stores the
result, which is thus throwing away any information on virtual macro
locations.

The first patch in the attached kit fixes that, storing location_t
values (aka source_location) within rich_location, delaying their
expansion until inside diagnostic_show_locus.  Doing so also
simplifies the code.

In writing the above patch I noticed now muddled the terminology has
become (range vs location), so the second patch in the kit rewords
things; I believe it makes the code much clearer.

The third patch in the kit is the earlier workaround for the bug; as
before it degrades diagnostic-printing to just print a caret for the
awkward cases, rather than attempting to print a range.  This should
mean that we fallback to the gcc 5 behavior for these cases.  I've
also added some new test cases based on duplicates filed against the
bug; they show a variety of interesting patterns that would have to
be handled if we were to use the 1st patch to fix things "properly".
I've added some TODO comments to the testcases to describe my
thoughts on how a proper fix might print them.

I've successfully bootstrapped&regrtested the combination of the patch
kit on x86_64-pc-linux-gnu; adds 17 PASS results to gcc.sum.

OK for trunk for gcc 6? (as 3 separate commits, assuming they
individually bootstrap&regrtest)

Thanks
Dave
Bernd Schmidt Dec. 14, 2015, 12:39 p.m. UTC | #6
On 12/11/2015 07:45 PM, David Malcolm wrote:
> The third patch in the kit is the earlier workaround for the bug; as
> before it degrades diagnostic-printing to just print a caret for the
> awkward cases, rather than attempting to print a range.

I'm a little confused now, do the first two patches actually help or are 
they just cleanups? If they have no immediate effect, let's postpone 
them to stage 1 and just do your initial workaround for now.


Bernd
diff mbox

Patch

diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show-locus.c
index 9e51b95..968b3cb 100644
--- a/gcc/diagnostic-show-locus.c
+++ b/gcc/diagnostic-show-locus.c
@@ -444,7 +444,7 @@  layout::layout (diagnostic_context * context,
     {
       /* This diagnostic printer can only cope with "sufficiently sane" ranges.
 	 Ignore any ranges that are awkward to handle.  */
-      location_range *loc_range = richloc->get_range (idx);
+      const location_range *loc_range = richloc->get_range (idx);
 
       /* If any part of the range isn't in the same file as the primary
 	 location of this diagnostic, ignore the range.  */
@@ -456,16 +456,38 @@  layout::layout (diagnostic_context * context,
 	if (loc_range->m_caret.file != m_exploc.file)
 	  continue;
 
+      /* Everything is now known to be in the correct source file,
+	 but it may require further sanitization.  */
+      layout_range ri (loc_range);
+
+      /* 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).  */
+      if (loc_range->m_start.line > loc_range->m_finish.line)
+	{
+	  /* 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.  */
-      layout_range ri (loc_range);
       m_layout_ranges.safe_push (ri);
 
       /* Update m_first_line/m_last_line if necessary.  */
-      if (loc_range->m_start.line < m_first_line)
-	m_first_line = loc_range->m_start.line;
-      if (loc_range->m_finish.line > m_last_line)
-	m_last_line = loc_range->m_finish.line;
+      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;
     }
 
   /* Adjust m_x_offset.
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-expressions-1.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-expressions-1.c
index 0d8c7c5..3e38035 100644
--- a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-expressions-1.c
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-expressions-1.c
@@ -541,3 +541,20 @@  void test_quadratic (double a, double b, double c)
    { dg-end-multiline-output "" } */
 
 }
+
+/* Reproducer for PR c/68473.  */
+
+extern long double fminl (long double __x, long double __y);
+#define TEST_EQ(FUNC) FUNC##l(xl,xl)
+void test_macro (long double xl)
+{
+  __emit_expression_range (0, TEST_EQ (fmin) ); /* { dg-warning "range" } */
+/* { dg-begin-multiline-output "" }
+   __emit_expression_range (0, TEST_EQ (fmin) );
+                                        ^
+   { dg-end-multiline-output "" } */
+/* { dg-begin-multiline-output "" }
+ #define TEST_EQ(FUNC) FUNC##l(xl,xl)
+                       ^~~~
+   { dg-end-multiline-output "" } */
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr68473-1.c b/gcc/testsuite/gcc.target/i386/pr68473-1.c
new file mode 100644
index 0000000..ffffaa7
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr68473-1.c
@@ -0,0 +1,24 @@ 
+/* { dg-options "-fdiagnostics-show-caret -mno-fp-ret-in-387" } */
+
+extern long double fminl (long double __x, long double __y);
+
+#define TEST_EQ(FUNC) do { \
+  if ((long)FUNC##l(xl,xl) != (long)xl) \
+    return; \
+  } while (0)
+
+void
+foo (long double xl)
+{
+  TEST_EQ (fmin); /* { dg-error "x87 register return with x87 disabled" } */
+}
+
+/* { dg-begin-multiline-output "" }
+   TEST_EQ (fmin);
+            ^
+   { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+   if ((long)FUNC##l(xl,xl) != (long)xl) \
+             ^~~~
+   { dg-end-multiline-output "" } */