diff mbox series

improve detection of incompatible redeclarations (PR 97882)

Message ID 9d612ff3-01a0-fc13-a455-923e45890490@gmail.com
State New
Headers show
Series improve detection of incompatible redeclarations (PR 97882) | expand

Commit Message

Martin Sebor Feb. 3, 2021, 11:21 p.m. UTC
The test case in the bug report shows that the C front end is
too permissive in accepting invalid redeclarations that involve
an incomplete enum and an otherwise compatible integer type as
return types of a function without a prototype, or as types of
an ordinary variable.  For example, the redeclaration below is
accepted:

   extern enum E { e0 } e;
   extern unsigned e;

In the case of a function the redeclaration can lead to a back
end ICE when one of the declarations is a function definition:

   extern enum F f ();
   extern unsigned f () { }

The attached patch tightens up the front end to reject even these
invalid redeclarations, thus avoiding the ICE.

Tested on x86_64-linux.

The bug is a P2 GCC 7-11 regression but in his comment Joseph
suggests to avoid backporting the fix to release branches due to
the potential to invalidate otherwise presumably benign code that's
accepted there, so I'm looking for approval only for GCC 11.

Martin

Comments

Joseph Myers Feb. 4, 2021, 12:15 a.m. UTC | #1
On Wed, 3 Feb 2021, Martin Sebor via Gcc-patches wrote:

> +/* Return true of T1 and T2 are matching types for the purposes of
> +   redeclaring a variable or a function without a prototype (i.e.,
> +   considering just its return type).  */

I think this comment is confusing (it suggests it's checking something 
looser than the notion of compatibility checked by comptypes, but it's 
actually checking something stricter).  But I also think it's wrong to do 
anything restricted to the return type.

For example, I think the following should be rejected, as enum E and 
unsigned int end up incompatible and quite likely ABI-incompatible.

enum E;
void f(enum E);
void f(unsigned int);
enum E { x = 1ULL << 63 };

Maybe the ICE is specific to the case of return types, but I think the 
same rules about type compatibility should apply regardless of precisely 
where the incomplete enum type appears.

I'd expect the natural fix for this PR to involve making 
comptypes_internal treat an incomplete enum as incompatible with an 
integer type.  Only if that causes too many problems should we then look 
at other approaches.
Martin Sebor Feb. 4, 2021, 4:48 p.m. UTC | #2
On 2/3/21 5:15 PM, Joseph Myers wrote:
> On Wed, 3 Feb 2021, Martin Sebor via Gcc-patches wrote:
> 
>> +/* Return true of T1 and T2 are matching types for the purposes of
>> +   redeclaring a variable or a function without a prototype (i.e.,
>> +   considering just its return type).  */
> 
> I think this comment is confusing (it suggests it's checking something
> looser than the notion of compatibility checked by comptypes, but it's
> actually checking something stricter).  But I also think it's wrong to do
> anything restricted to the return type.
> 
> For example, I think the following should be rejected, as enum E and
> unsigned int end up incompatible and quite likely ABI-incompatible.
> 
> enum E;
> void f(enum E);
> void f(unsigned int);
> enum E { x = 1ULL << 63 };
> 
> Maybe the ICE is specific to the case of return types, but I think the
> same rules about type compatibility should apply regardless of precisely
> where the incomplete enum type appears.

Ah, it's even more wide-spread than I realized.

> 
> I'd expect the natural fix for this PR to involve making
> comptypes_internal treat an incomplete enum as incompatible with an
> integer type.  Only if that causes too many problems should we then look
> at other approaches.
> 

Let me look into it then.

Martin
Martin Sebor Feb. 4, 2021, 8:07 p.m. UTC | #3
On 2/4/21 9:48 AM, Martin Sebor wrote:
> On 2/3/21 5:15 PM, Joseph Myers wrote:
>> On Wed, 3 Feb 2021, Martin Sebor via Gcc-patches wrote:
>>
>>> +/* Return true of T1 and T2 are matching types for the purposes of
>>> +   redeclaring a variable or a function without a prototype (i.e.,
>>> +   considering just its return type).  */
>>
>> I think this comment is confusing (it suggests it's checking something
>> looser than the notion of compatibility checked by comptypes, but it's
>> actually checking something stricter).  But I also think it's wrong to do
>> anything restricted to the return type.
>>
>> For example, I think the following should be rejected, as enum E and
>> unsigned int end up incompatible and quite likely ABI-incompatible.
>>
>> enum E;
>> void f(enum E);
>> void f(unsigned int);
>> enum E { x = 1ULL << 63 };
>>
>> Maybe the ICE is specific to the case of return types, but I think the
>> same rules about type compatibility should apply regardless of precisely
>> where the incomplete enum type appears.
> 
> Ah, it's even more wide-spread than I realized.
> 
>>
>> I'd expect the natural fix for this PR to involve making
>> comptypes_internal treat an incomplete enum as incompatible with an
>> integer type.  Only if that causes too many problems should we then look
>> at other approaches.
>>
> 
> Let me look into it then.

Okay, that's much simpler.  Thanks for nudging me in the right
direction!  How's the attached patch? Retested on x86_64-linux.

Martin
Joseph Myers Feb. 4, 2021, 9:13 p.m. UTC | #4
On Thu, 4 Feb 2021, Martin Sebor via Gcc-patches wrote:

> Okay, that's much simpler.  Thanks for nudging me in the right
> direction!  How's the attached patch? Retested on x86_64-linux.

OK, minus the addition of gcc/testsuite/c-c++-common/array-lit.s which 
looks like a mistake.
diff mbox series

Patch

PR c/97882 - Segmentation Fault on improper redeclaration of function

gcc/c/ChangeLog:

	PR c/97882
	* c-decl.c (matching_types_p): New function.
	(diagnose_mismatched_decls): Call it.  Add detail to warning message.
	(start_function): Call matching_types_p instead of comptypes.

gcc/testsuite/ChangeLog:

	PR c/97882
	* gcc.dg/decl-8.c: Adjust text of expected diagnostic.
	* gcc.dg/label-decl-4.c: Same.
	* gcc.dg/mismatch-decl-1.c: Same.
	* gcc.dg/old-style-then-proto-1.c: Same.
	* gcc.dg/parm-mismatch-1.c: Same.
	* gcc.dg/pr35445.c: Same.
	* gcc.dg/redecl-11.c: Same.
	* gcc.dg/redecl-12.c: Same.
	* gcc.dg/redecl-13.c: Same.
	* gcc.dg/redecl-15.c: Same.
	* gcc.dg/tls/thr-init-1.c: Same.
	* objc.dg/tls/diag-3.m: Same.
	* c-c++-common/array-lit.s: New test.
	* gcc.dg/pr97882.c: New test.
	* gcc.dg/qual-return-7.c: New test.
	* gcc.dg/qual-return-8.c: New test.

diff --git a/gcc/c/c-decl.c b/gcc/c/c-decl.c
index be95643fcf9..d8102d71766 100644
--- a/gcc/c/c-decl.c
+++ b/gcc/c/c-decl.c
@@ -1910,15 +1910,79 @@  validate_proto_after_old_defn (tree newdecl, tree newtype, tree oldtype)
 static void
 locate_old_decl (tree decl)
 {
-  if (TREE_CODE (decl) == FUNCTION_DECL && fndecl_built_in_p (decl)
+  if (TREE_CODE (decl) == FUNCTION_DECL
+      && fndecl_built_in_p (decl)
       && !C_DECL_DECLARED_BUILTIN (decl))
     ;
   else if (DECL_INITIAL (decl))
-    inform (input_location, "previous definition of %q+D was here", decl);
+    inform (input_location,
+	    "previous definition of %q+D with type %qT",
+	    decl, TREE_TYPE (decl));
   else if (C_DECL_IMPLICIT (decl))
-    inform (input_location, "previous implicit declaration of %q+D was here", decl);
+    inform (input_location,
+	    "previous implicit declaration of %q+D with type %qT",
+	    decl, TREE_TYPE (decl));
   else
-    inform (input_location, "previous declaration of %q+D was here", decl);
+    inform (input_location,
+	    "previous declaration of %q+D with type %qT",
+	    decl, TREE_TYPE (decl));
+}
+
+/* Return true of T1 and T2 are matching types for the purposes of
+   redeclaring a variable or a function without a prototype (i.e.,
+   considering just its return type).  */
+
+static bool
+matching_types_p (tree_code which, tree t0, tree t1)
+{
+  bool types_differ = false;
+  if (!comptypes_check_different_types (t0, t1, &types_differ))
+    return false;
+
+  if (!types_differ)
+    return true;
+
+  if (which == FUNCTION_DECL)
+    {
+      /* When WHICH denotes a function T1 and T2 may be either function
+	 types or return types.  */
+      if (TREE_CODE (t0) == FUNCTION_TYPE)
+	{
+	  if (prototype_p (t0))
+	    return true;
+
+	  t0 = TREE_TYPE (t0);
+	  t1 = TREE_TYPE (t1);
+	}
+
+      if (flag_isoc11)
+	{
+	  t0 = TYPE_MAIN_VARIANT (t0);
+	  t1 = TYPE_MAIN_VARIANT (t1);
+	}
+    }
+  else if (which != VAR_DECL)
+    return true;
+
+  /* Check the return type by itself and detect mismatches in non-pointer
+     types only.  Pointers to arrays may be different when thee latter
+     specifies a bound that the former doesn't.  */
+
+  while (TREE_CODE (t0) == ARRAY_TYPE || POINTER_TYPE_P (t0))
+    t0 = TREE_TYPE (t0);
+  while (TREE_CODE (t1) == ARRAY_TYPE || POINTER_TYPE_P (t1))
+    t1 = TREE_TYPE (t1);
+
+  if (TREE_CODE (t0) == FUNCTION_TYPE)
+    /* For pointers to functions be sure to apply the rules above.  */
+    return matching_types_p (FUNCTION_DECL, t0, t1);
+
+  types_differ = false;
+  if (!comptypes_check_different_types (t0, t1, &types_differ)
+      || types_differ)
+    return false;
+
+  return true;
 }
 
 /* Subroutine of duplicate_decls.  Compare NEWDECL to OLDDECL.
@@ -1983,8 +2047,7 @@  diagnose_mismatched_decls (tree newdecl, tree olddecl,
   bool pedwarned = false;
   bool warned = false;
   auto_diagnostic_group d;
-
-  if (!comptypes (oldtype, newtype))
+  if (!matching_types_p (TREE_CODE (olddecl), oldtype, newtype))
     {
       if (TREE_CODE (olddecl) == FUNCTION_DECL
 	  && fndecl_built_in_p (olddecl, BUILT_IN_NORMAL)
@@ -2083,7 +2146,8 @@  diagnose_mismatched_decls (tree newdecl, tree olddecl,
 	       && C_DECL_IMPLICIT (olddecl) && !DECL_INITIAL (olddecl))
 	{
 	  pedwarned = pedwarn (input_location, 0,
-			       "conflicting types for %q+D", newdecl);
+			       "conflicting types for %q+D; have %qT",
+			       newdecl, newtype);
 	  /* Make sure we keep void as the return type.  */
 	  TREE_TYPE (olddecl) = *oldtypep = oldtype = newtype;
 	}
@@ -2119,7 +2183,7 @@  diagnose_mismatched_decls (tree newdecl, tree olddecl,
 		error ("conflicting type qualifiers for %q+D", newdecl);
 	    }
 	  else
-	    error ("conflicting types for %q+D", newdecl);
+	    error ("conflicting types for %q+D; have %qT", newdecl, newtype);
 	  diagnose_arglist_conflict (newdecl, olddecl, newtype, oldtype);
 	  locate_old_decl (olddecl);
 	  return false;
@@ -9457,27 +9521,29 @@  start_function (struct c_declspecs *declspecs, struct c_declarator *declarator,
   current_function_prototype_locus = UNKNOWN_LOCATION;
   current_function_prototype_built_in = false;
   current_function_prototype_arg_types = NULL_TREE;
-  if (!prototype_p (TREE_TYPE (decl1)))
+  tree newtype = TREE_TYPE (decl1);
+  tree oldtype = old_decl ? TREE_TYPE (old_decl) : newtype;
+  if (!prototype_p (newtype))
     {
+      tree oldrt = TREE_TYPE (oldtype);
+      tree newrt = TREE_TYPE (newtype);
       if (old_decl != NULL_TREE
-	  && TREE_CODE (TREE_TYPE (old_decl)) == FUNCTION_TYPE
-	  && comptypes (TREE_TYPE (TREE_TYPE (decl1)),
-			TREE_TYPE (TREE_TYPE (old_decl))))
+	  && TREE_CODE (oldtype) == FUNCTION_TYPE
+	  && matching_types_p (FUNCTION_DECL, oldrt, newrt))
 	{
-	  if (stdarg_p (TREE_TYPE (old_decl)))
+	  if (stdarg_p (oldtype))
 	    {
 	      auto_diagnostic_group d;
 	      warning_at (loc, 0, "%q+D defined as variadic function "
 			  "without prototype", decl1);
 	      locate_old_decl (old_decl);
 	    }
-	  TREE_TYPE (decl1) = composite_type (TREE_TYPE (old_decl),
-					      TREE_TYPE (decl1));
+	  TREE_TYPE (decl1) = composite_type (oldtype, newtype);
 	  current_function_prototype_locus = DECL_SOURCE_LOCATION (old_decl);
 	  current_function_prototype_built_in
 	    = C_DECL_BUILTIN_PROTOTYPE (old_decl);
 	  current_function_prototype_arg_types
-	    = TYPE_ARG_TYPES (TREE_TYPE (decl1));
+	    = TYPE_ARG_TYPES (newtype);
 	}
       if (TREE_PUBLIC (decl1))
 	{
diff --git a/gcc/testsuite/c-c++-common/array-lit.s b/gcc/testsuite/c-c++-common/array-lit.s
new file mode 100644
index 00000000000..476b5cf2509
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/array-lit.s
@@ -0,0 +1 @@ 
+	.file	"array-lit.c"
diff --git a/gcc/testsuite/gcc.dg/comp-return-1.s b/gcc/testsuite/gcc.dg/comp-return-1.s
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/gcc/testsuite/gcc.dg/decl-8.c b/gcc/testsuite/gcc.dg/decl-8.c
index 485065b7aa1..608ff97d7fc 100644
--- a/gcc/testsuite/gcc.dg/decl-8.c
+++ b/gcc/testsuite/gcc.dg/decl-8.c
@@ -3,8 +3,8 @@ 
 /* { dg-do compile } */
 /* { dg-options "-std=gnu89 -pedantic-errors" } */
 
-typedef int I; /* { dg-message "note: previous declaration of 'I' was here" } */
+typedef int I; /* { dg-message "note: previous declaration of 'I'" "note" } */
 typedef int I; /* { dg-error "redefinition of typedef 'I'" } */
 
-typedef int I1; /* { dg-message "note: previous declaration of 'I1' was here" } */
+typedef int I1; /* { dg-message "note: previous declaration of 'I1'" "note" } */
 typedef long I1; /* { dg-error "conflicting types for 'I1'" } */
diff --git a/gcc/testsuite/gcc.dg/label-decl-4.c b/gcc/testsuite/gcc.dg/label-decl-4.c
index 5661e019d1e..82f1af050b0 100644
--- a/gcc/testsuite/gcc.dg/label-decl-4.c
+++ b/gcc/testsuite/gcc.dg/label-decl-4.c
@@ -7,8 +7,8 @@  void
 f (void)
 {
   __label__ a, b, a; /* { dg-error "duplicate label declaration 'a'" } */
-  /* { dg-message "note: previous declaration of 'a' was here" "previous" { target *-*-* } .-1 } */
-  __label__ c; /* { dg-message "note: previous declaration of 'c' was here" } */
+  /* { dg-message "note: previous declaration of 'a'" "previous" { target *-*-* } .-1 } */
+  __label__ c; /* { dg-message "note: previous declaration of 'c'" "note" } */
   __label__ c; /* { dg-error "duplicate label declaration 'c'" } */
   return;
 }
diff --git a/gcc/testsuite/gcc.dg/mismatch-decl-1.c b/gcc/testsuite/gcc.dg/mismatch-decl-1.c
index da4db0a57f0..b6dd5431a48 100644
--- a/gcc/testsuite/gcc.dg/mismatch-decl-1.c
+++ b/gcc/testsuite/gcc.dg/mismatch-decl-1.c
@@ -4,12 +4,12 @@ 
 
 /* The bug this is testing is that if a new decl conflicts with an
    explicit decl, you don't get the "changes type of builtin" message,
-   but if there was *also* a builtin, you *also* don't get the
-   "previous declaration was here" message, leaving you with no clue
-   where the previous declaration came from.  */
+   but if there was *also* a builtin, you *also* don't get the "previous
+   declaration" message, leaving you with no clue where the previous
+   declaration came from.  */
 
-extern char foo(int,int); /* { dg-message "previous declaration of 'foo' was here" } */
-extern char *index(const char *,int); /* { dg-message "previous declaration of 'index' was here" } */
+extern char foo(int,int); /* { dg-message "previous declaration of 'foo'" "note" } */
+extern char *index(const char *,int); /* { dg-message "previous declaration of 'index'" "note" } */
 
 /* This changes the type of "index", which is both a builtin and an
    explicit decl.  */
diff --git a/gcc/testsuite/gcc.dg/old-style-prom-1.s b/gcc/testsuite/gcc.dg/old-style-prom-1.s
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/gcc/testsuite/gcc.dg/old-style-then-proto-1.c b/gcc/testsuite/gcc.dg/old-style-then-proto-1.c
index 7d76287b0d1..e3e61861673 100644
--- a/gcc/testsuite/gcc.dg/old-style-then-proto-1.c
+++ b/gcc/testsuite/gcc.dg/old-style-then-proto-1.c
@@ -7,38 +7,38 @@ 
 void f1() {}
 void f1(void); /* { dg-warning "prototype for 'f1' follows non-prototype definition" } */
 
-void f2() {} /* { dg-message "note: previous definition of 'f2' was here" } */
+void f2() {} /* { dg-message "note: previous definition of 'f2'" "note" } */
 void f2(int); /* { dg-error "prototype for 'f2' declares more arguments than previous old-style definition" } */
 
-void f3(a) int a; {} /* { dg-message "note: previous definition of 'f3' was here" } */
+void f3(a) int a; {} /* { dg-message "note: previous definition of 'f3'" "note" } */
 void f3(void); /* { dg-error "prototype for 'f3' declares fewer arguments than previous old-style definition" } */
 
 void f4(a) int a; {}
 void f4(int); /* { dg-warning "prototype for 'f4' follows non-prototype definition" } */
 
-void f5(a) int a; {} /* { dg-message "note: previous definition of 'f5' was here" } */
+void f5(a) int a; {} /* { dg-message "note: previous definition of 'f5'" "note" } */
 void f5(int, int); /* { dg-error "prototype for 'f5' declares more arguments than previous old-style definition" } */
 
-void f6(a) int a; {} /* { dg-message "note: previous definition of 'f6' was here" } */
+void f6(a) int a; {} /* { dg-message "note: previous definition of 'f6'" "note" } */
 void f6(int, ...); /* { dg-error "conflicting types for 'f6'" } */
 
-void f7(a, b) int a, b; {} /* { dg-message "note: previous definition of 'f7' was here" } */
+void f7(a, b) int a, b; {} /* { dg-message "note: previous definition of 'f7'" "note" } */
 void f7(int); /* { dg-error "prototype for 'f7' declares fewer arguments than previous old-style definition" } */
 
-void f8(a, b) int a, b; {} /* { dg-message "note: previous definition of 'f8' was here" } */
+void f8(a, b) int a, b; {} /* { dg-message "note: previous definition of 'f8'" "note" } */
 void f8(int, ...); /* { dg-error "conflicting types for 'f8'" } */
 
 void f9(a, b) int a, b; {}
 void f9(int, int); /* { dg-warning "prototype for 'f9' follows non-prototype definition" } */
 
-void f10(a, b) int a, b; {} /* { dg-message "note: previous definition of 'f10' was here" } */
+void f10(a, b) int a, b; {} /* { dg-message "note: previous definition of 'f10'" "note" } */
 void f10(int, long); /* { dg-error "prototype for 'f10' declares argument 2 with incompatible type" } */
 
-void f11(a, b) int a, b; {} /* { dg-message "note: previous definition of 'f11' was here" } */
+void f11(a, b) int a, b; {} /* { dg-message "note: previous definition of 'f11'" "note" } */
 void f11(long, int); /* { dg-error "prototype for 'f11' declares argument 1 with incompatible type" } */
 
 void f12(a, b) const int a; volatile int b; {}
 void f12(volatile int, const int); /* { dg-warning "prototype for 'f12' follows non-prototype definition" } */
 
-void f13(a) const int a[2][2]; {} /* { dg-message "note: previous definition of 'f13' was here" } */
+void f13(a) const int a[2][2]; {} /* { dg-message "note: previous definition of 'f13'" "note" } */
 void f13(volatile int [2][2]); /* { dg-error "prototype for 'f13' declares argument 1 with incompatible type" } */
diff --git a/gcc/testsuite/gcc.dg/parm-mismatch-1.c b/gcc/testsuite/gcc.dg/parm-mismatch-1.c
index 058f2e856f5..d7621bcd6b2 100644
--- a/gcc/testsuite/gcc.dg/parm-mismatch-1.c
+++ b/gcc/testsuite/gcc.dg/parm-mismatch-1.c
@@ -4,15 +4,15 @@ 
 /* { dg-do compile } */
 /* { dg-options "" } */
 
-void f0(); /* { dg-message "note: previous declaration of 'f0' was here" } */
+void f0(); /* { dg-message "note: previous declaration of 'f0'" "note" } */
 void f0(int, ...); /* { dg-error "conflicting types for 'f0'" } */
 /* { dg-message "note: a parameter list with an ellipsis cannot match an empty parameter name list declaration" "note" { target *-*-* } .-1 } */
-void f1(int, ...); /* { dg-message "note: previous declaration of 'f1' was here" } */
+void f1(int, ...); /* { dg-message "note: previous declaration of 'f1'" "note" } */
 void f1(); /* { dg-error "conflicting types for 'f1'" } */
 /* { dg-message "note: a parameter list with an ellipsis cannot match an empty parameter name list declaration" "note" { target *-*-* } .-1 } */
-void f2(); /* { dg-message "note: previous declaration of 'f2' was here" } */
+void f2(); /* { dg-message "note: previous declaration of 'f2'" "note" } */
 void f2(char); /* { dg-error "conflicting types for 'f2'" } */
 /* { dg-message "note: an argument type that has a default promotion cannot match an empty parameter name list declaration" "note" { target *-*-* } .-1 } */
-void f3(char); /* { dg-message "note: previous declaration of 'f3' was here" } */
+void f3(char); /* { dg-message "note: previous declaration of 'f3'" "note" } */
 void f3(); /* { dg-error "conflicting types for 'f3'" } */
 /* { dg-message "note: an argument type that has a default promotion cannot match an empty parameter name list declaration" "note" { target *-*-* } .-1 } */
diff --git a/gcc/testsuite/gcc.dg/pr35445.c b/gcc/testsuite/gcc.dg/pr35445.c
index 56ca6e2580a..30c29f4c3bc 100644
--- a/gcc/testsuite/gcc.dg/pr35445.c
+++ b/gcc/testsuite/gcc.dg/pr35445.c
@@ -2,5 +2,5 @@ 
 /* { dg-do compile } */
 
 extern int i;
-extern int i; /* { dg-message "was here" } */
+extern int i; /* { dg-message "previous declaration of 'i'" } */
 int i[] = { 0 }; /* { dg-error "conflicting types" } */
diff --git a/gcc/testsuite/gcc.dg/pr97882.c b/gcc/testsuite/gcc.dg/pr97882.c
new file mode 100644
index 00000000000..8c08a664ef7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pr97882.c
@@ -0,0 +1,122 @@ 
+/* PR c/97882 - Segmentation Fault on improper redeclaration of function
+   { dg-do compile }
+   { dg-options "" } */
+
+// Check pointer declaration incompatibiliies.
+
+extern enum E e_u;      // { dg-message "note: previous declaration of 'e_u' with type 'enum E'" "note" }
+unsigned e_u;           // { dg-error "conflicting types for 'e_u'; have 'unsigned int'" }
+
+
+extern enum E *p;       // { dg-message "note: previous declaration of 'p' with type 'enum E \\*'" "note" }
+unsigned *p;            // { dg-error "conflicting types for 'p'; have 'unsigned int \\*'" }
+
+extern enum E **p2;     // { dg-message "note: previous declaration of 'p2' with type 'enum E \\*\\*'" "note" }
+unsigned **p2;          // { dg-error "conflicting types for 'p2'; have 'unsigned int \\*\\*'" }
+
+extern enum E ***p3;    // { dg-message "note: previous declaration of 'p3' with type 'enum E \\*\\*\\*'" "note" }
+unsigned ***p3;         // { dg-error "conflicting types for 'p3'; have 'unsigned int \\*\\*\\*'" }
+
+extern enum F *q;       // { dg-message "note: previous declaration of 'q' with type 'enum F \\*'" "note" }
+int *q;                 // { dg-error "conflicting types for 'q'; have 'int \\*'" }
+
+extern enum E* r[];     // { dg-message "note: previous declaration of 'r' with type 'enum E \\*\\\[]'" "note" }
+extern unsigned *r[1];  // { dg-error "conflicting types for 'r'; have 'unsigned int \\*\\\[1]'" }
+
+extern enum E **r2[];   // { dg-message "note: previous declaration of 'r2' with type 'enum E \\*\\*\\\[]'" "note" }
+extern unsigned **r2[2];// { dg-error "conflicting types for 'r2'; have 'unsigned int \\*\\*\\\[2]'" }
+
+
+typedef enum E* EPAx[];
+typedef unsigned* UPAx[];
+
+extern EPAx* peax;      //  { dg-message "note: previous declaration of 'peax' with type 'enum E \\* \\(\\*\\)\\\[]'" "note" }
+extern UPAx* peax;      // { dg-error "conflicting types for 'peax'; have 'unsigned int \\* \\(\\*\\)\\\[]'" }
+
+/* Check incompatibilities in the return type in a redeclaration
+   of a function without a prototye.  */
+
+/* Verify the following isn't rejected.  */
+void f_v ();
+void f_v (void);
+
+enum E fE_u ();        // { dg-message "previous declaration of 'fE_u' with type 'enum E\\(\\)'" "note" }
+unsigned fE_u ();      // { dg-error "conflicting types for 'fE_u'; have 'unsigned int\\(\\)'" }
+
+enum E* fpE_u ();      // { dg-message "previous declaration of 'fpE_u' with type 'enum E \\*\\(\\)'" "note" }
+unsigned* fpE_u ();    // { dg-error "conflicting types for 'fpE_u'; have 'unsigned int \\*\\(\\)'" }
+
+enum E** fppE_u ();     // { dg-message "previous declaration of 'fppE_u' with type 'enum E \\*\\*\\(\\)'" "note" }
+unsigned** fppE_u ();   // { dg-error "conflicting types for 'fppE_u'; have 'unsigned int \\*\\*\\(\\)'" }
+
+enum E** fppE_u ();     // { dg-message "previous declaration of 'fppE_u' with type 'enum E \\*\\*\\(\\)'" "note" }
+unsigned** fppE_u ();   // { dg-error "conflicting types for 'fppE_u'; have 'unsigned int \\*\\*\\(\\)'" }
+
+enum E gE_u ();        // { dg-message "previous declaration of 'gE_u' with type 'enum E\\(\\)'" "note" }
+unsigned gE_u ()       // { dg-error "conflicting types for 'gE_u'; have 'unsigned int\\(\\)'" }
+{ return 0; }
+
+enum E** gppE_u ();    // { dg-message "previous declaration of 'gppE_u' with type 'enum E \\*\\*\\(\\)'" "note" }
+unsigned** gppE_u ()   // { dg-error "conflicting types for 'gppE_u'; have 'unsigned int \\*\\*\\(\\)'" }
+{ return 0; }
+
+unsigned fu_E ();      // { dg-message "previous declaration of 'fu_E' with type 'unsigned int\\(\\)'" "note" }
+enum E fu_E ();        // { dg-error "conflicting types for 'fu_E'; have 'enum E\\(\\)'" }
+
+unsigned gu_E ();      // { dg-message "previous declaration of 'gu_E' with type 'unsigned int\\(\\)'" "note" }
+enum E gu_E () { }     // { dg-error "conflicting types for 'gu_E'" }
+                       // { dg-error "incomplete type" "return type" { target *-*-* } .-1 }
+
+typedef enum E FE_ ();
+typedef unsigned Fuv (void);
+
+FE_* fpF_u ();         // // { dg-message "previous declaration of 'fpF_u' with type 'enum E \\(\\*\\(\\)\\)\\(\\)'" "note" }
+Fuv* fpF_u ();         // { dg-error "conflicting types for 'fpF_u'; have 'unsigned int \\(\\*\\(\\)\\)\\(void\\)'" }
+
+
+typedef void Fv_ ();
+typedef void Fvv (void);
+
+/* Verify the following isn't rejected.  */
+Fv_* f ();
+Fvv* f ();
+
+
+/* Check incompatibilities in the return type in a redeclaration
+   of a nested function without a prototye.  */
+
+void f1 (void)
+{
+  enum G f11 ();        // { dg-message "note: previous declaration of 'f11' with type 'enum G\\(\\)'" "note" }
+  unsigned f11 () { }   // { dg-error "conflicting types for 'f11'; have 'unsigned int\\(\\)'" }
+}
+
+
+void f2 (void)
+{
+  const enum G f21 ();  // { dg-message "note: previous declaration of 'f21' with type 'enum G\\(\\)'" "note" }
+  unsigned f21 () { }   // { dg-error "conflicting types for 'f21'; have 'unsigned int\\(\\)'" }
+}
+
+
+void f3 (void)
+{
+  enum G f31 ();        // { dg-message "note: previous declaration of 'f31' with type 'enum G\\(\\)'" "note" }
+  const unsigned f31 () { }   // { dg-error "conflicting types for 'f31'; have 'unsigned int\\(\\)'" }
+}
+
+
+void f4 (void)
+{
+  auto enum G f31 ();         // { dg-message "note: previous declaration of 'f31' with type 'enum G\\(\\)'" "note" }
+  const unsigned f31 () { }   // { dg-error "conflicting types for 'f31'; have 'unsigned int\\(\\)'" }
+}
+
+
+void f5 (void)
+{
+  enum G* f51 ();       // { dg-message "note: previous declaration of 'f51' with type 'enum G \\*\\(\\)'" "note" }
+  int* f51 () { }       // { dg-error "conflicting types for 'f51'; have 'int \\*\\(\\)'" }
+}
+
+// { dg-prune-output "nested function '\[^\n\r ]+' declared but never defined" }
diff --git a/gcc/testsuite/gcc.dg/pr97882.s b/gcc/testsuite/gcc.dg/pr97882.s
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/gcc/testsuite/gcc.dg/qual-return-7.c b/gcc/testsuite/gcc.dg/qual-return-7.c
new file mode 100644
index 00000000000..96f7f16305d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/qual-return-7.c
@@ -0,0 +1,18 @@ 
+/* Same as qual-return-3.c but with nested functions.
+   { dg-do compile }
+   { dg-options "-std=gnu99" } */
+
+void test_local (void)
+{
+  auto int foo ();                /* { dg-message "note: previous declaration" "different qualifiers" } */
+
+  const int foo () { return 0; }  /* { dg-error "conflicting types" "different qualifiers" } */
+
+  auto void bar (void);
+  volatile void bar () { }        /* { dg-warning "qualified|volatile" "different qualifiers" } */
+
+  auto volatile void baz (void);
+  void baz () { }                 /* { dg-warning "not compatible" "different qualifiers" } */
+}
+
+/* { dg-prune-output "nested function 'foo' declared but never defined" } */
diff --git a/gcc/testsuite/gcc.dg/qual-return-8.c b/gcc/testsuite/gcc.dg/qual-return-8.c
new file mode 100644
index 00000000000..de1e7cb7b64
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/qual-return-8.c
@@ -0,0 +1,28 @@ 
+/* Same as qual-return-7.c but in C11 mode.
+   { dg-do compile }
+   { dg-options "-std=gnu11" } */
+
+void test_local (void)
+{
+#if 0
+  /* _Atomic is not considered a qualifier and so is not ignored
+     on a return type.  As a result, the redeclaration below isn't
+     valid.  See also qual-return-5.c.  */
+  auto int fi_ai ();
+  _Atomic int fi_ai () { return 0; }
+#endif
+
+  auto int fi_ci ();
+  const int fi_ci () { return 0; }
+
+  auto enum E fe_ce ();
+
+  enum E { e };
+  const enum E fe_ce () { return 0; }
+
+  auto void fv_vv (void);
+  volatile void fv_vv () { }
+
+  auto volatile void fvv_v (void);
+  void fvv_v () { }
+}
diff --git a/gcc/testsuite/gcc.dg/redecl-11.c b/gcc/testsuite/gcc.dg/redecl-11.c
index 5540e40503c..3c6f64fca51 100644
--- a/gcc/testsuite/gcc.dg/redecl-11.c
+++ b/gcc/testsuite/gcc.dg/redecl-11.c
@@ -5,5 +5,5 @@ 
 /* { dg-options "" } */
 
 int f(int (*)[]);
-void g() { int f(int (*)[2]); } /* { dg-message "note: previous declaration of 'f' was here" } */
+void g() { int f(int (*)[2]); } /* { dg-message "note: previous declaration of 'f'" "note" } */
 int f(int (*)[3]); /* { dg-error "conflicting types for 'f'" } */
diff --git a/gcc/testsuite/gcc.dg/redecl-12.c b/gcc/testsuite/gcc.dg/redecl-12.c
index 711b8a3fcae..9922cf49fc5 100644
--- a/gcc/testsuite/gcc.dg/redecl-12.c
+++ b/gcc/testsuite/gcc.dg/redecl-12.c
@@ -5,5 +5,5 @@ 
 /* { dg-options "" } */
 
 extern int a[];
-void f(void) { extern int a[]; extern int a[10]; } /* { dg-message "note: previous declaration of 'a' was here" } */
+void f(void) { extern int a[]; extern int a[10]; } /* { dg-message "note: previous declaration of 'a'" "note" } */
 extern int a[5]; /* { dg-error "conflicting types for 'a'" } */
diff --git a/gcc/testsuite/gcc.dg/redecl-13.c b/gcc/testsuite/gcc.dg/redecl-13.c
index 3f05d0fb168..556a3cd69b6 100644
--- a/gcc/testsuite/gcc.dg/redecl-13.c
+++ b/gcc/testsuite/gcc.dg/redecl-13.c
@@ -5,5 +5,5 @@ 
 /* { dg-options "" } */
 
 extern int a[];
-void f(void) { extern int a[10]; } /* { dg-message "note: previous declaration of 'a' was here" } */
+void f(void) { extern int a[10]; } /* { dg-message "note: previous declaration of 'a'" "note" } */
 extern int a[5]; /* { dg-error "conflicting types for 'a'" } */
diff --git a/gcc/testsuite/gcc.dg/redecl-15.c b/gcc/testsuite/gcc.dg/redecl-15.c
index ff484c99542..06d6523eea6 100644
--- a/gcc/testsuite/gcc.dg/redecl-15.c
+++ b/gcc/testsuite/gcc.dg/redecl-15.c
@@ -7,7 +7,7 @@ 
 void
 f (void)
 {
-  g(); /* { dg-message "note: previous implicit declaration of 'g' was here" } */
+  g(); /* { dg-message "note: previous implicit declaration of 'g'" } */
   {
     void g(); /* { dg-warning "conflicting types for 'g'" } */
   }
diff --git a/gcc/testsuite/gcc.dg/tls/thr-init-1.c b/gcc/testsuite/gcc.dg/tls/thr-init-1.c
index a9b60612be2..af51484b491 100644
--- a/gcc/testsuite/gcc.dg/tls/thr-init-1.c
+++ b/gcc/testsuite/gcc.dg/tls/thr-init-1.c
@@ -6,4 +6,4 @@  static __thread int fstat = 1 ; /* { dg-line fstat_prev } */
 static __thread int fstat ;
 static __thread int fstat = 2;
 /* { dg-error "redefinition of 'fstat'" "" { target *-*-* } .-1 } */
-/* { dg-message "note: previous definition of 'fstat' was here" "" { target *-*-* } fstat_prev } */
+/* { dg-message "note: previous definition of 'fstat'" "note" { target *-*-* } fstat_prev } */
diff --git a/gcc/testsuite/objc.dg/tls/diag-3.m b/gcc/testsuite/objc.dg/tls/diag-3.m
index c71f66fb8f5..4f58802d654 100644
--- a/gcc/testsuite/objc.dg/tls/diag-3.m
+++ b/gcc/testsuite/objc.dg/tls/diag-3.m
@@ -1,10 +1,10 @@ 
-/* Report invalid extern and __thread combinations. */
+\/* Report invalid extern and __thread combinations. */
 /* { dg-require-effective-target tls } */
 
-extern int j;		/* { dg-message "previous declaration of 'j' was here" } */
+extern int j;		/* { dg-message "previous declaration of 'j'" } */
 __thread int j;		/* { dg-error "follows non-thread-local" } */
 
-extern __thread int i;	/* { dg-message "previous declaration of 'i' was here" } */
+extern __thread int i;	/* { dg-message "previous declaration of 'i'" } */
 int i;			/* { dg-error "follows thread-local" } */
 
 extern __thread int k;	/* This is fine. */