From eaa8704f1eb15ea460fa49dd4d23214f694534d2 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 22 Mar 2018 10:47:14 -0400
Subject: [PATCH 16/42] C: Ensure that we print include hints in
 -Wimplicit-function-declaration

I noticed when testing the previous patch on the C frontend that
the fix-it hints weren't always showing up:
-Wimplicit-function-declaration wasn't always providing a fix-it
hint, but "incompatible implicit declaration of built-in function"
was, *if* it warned.

Consider e.g.:

  $ cat t.c
  void test (const char *a, const char *b)
  {
    if (strcmp (a, b))
      printf ("a: %s, b: %b\n", a, b);
  }

  $ gcc -c t.c
  t.c: In function 'test':
  t.c:3:7: warning: implicit declaration of function 'strcmp' [-Wimplicit-function-declaration]
     if (strcmp (a, b))
         ^~~~~~
  t.c:4:5: warning: implicit declaration of function 'printf' [-Wimplicit-function-declaration]
       printf ("a: %s, b: %b\n", a, b);
       ^~~~~~
  t.c:4:5: warning: incompatible implicit declaration of built-in function 'printf'
  t.c:4:5: note: include '<stdio.h>' or provide a declaration of 'printf'
  t.c:1:1:
  +#include <stdio.h>
   void test (const char *a, const char *b)
  t.c:4:5:
       printf ("a: %s, b: %b\n", a, b);
       ^~~~~~

Note how "printf" gets a fix-it hint, but strcmp doesn't: we aren't
suggesting <string.h>.

When implicit_decl_warning complains, but OLDDECL is non-NULL, it
wasn't suggesting missing #includes.  If implicitly_declare later
complains about the implicit decl being incompatible, then we get a
fix-it hint, but if implicitly_declare *doesn't* complain (as in
"strcmp" above), then there's no suggestion about which header to use.

This patch tweaks implicit_decl_warning so that it attempts to provide
a fix-it hint for this case.  They're idempotent, so it leads to minimal
extra output for the case where implicitly_declare complains a second
time (one extra "note"), for the <stdio.h>/sprintf case below:

  t.c: In function 'test':
  t.c:3:7: warning: implicit declaration of function 'strcmp' [-Wimplicit-function-declaration]
     if (strcmp (a, b))
         ^~~~~~
  t.c:3:7: note: 'strcmp' is defined in header '<string.h>'; did you forget to '#include <string.h>'?
  t.c:1:1:
  +#include <string.h>
   void test (const char *a, const char *b)
  t.c:3:7:
     if (strcmp (a, b))
         ^~~~~~
  t.c:4:5: warning: implicit declaration of function 'printf' [-Wimplicit-function-declaration]
       printf ("a: %s, b: %b\n", a, b);
       ^~~~~~
  t.c:4:5: note: 'printf' is defined in header '<stdio.h>'; did you forget to '#include <stdio.h>'?
  t.c:1:1:
  +#include <stdio.h>
   void test (const char *a, const char *b)
  t.c:4:5:
       printf ("a: %s, b: %b\n", a, b);
       ^~~~~~
  t.c:4:5: warning: incompatible implicit declaration of built-in function 'printf'
  t.c:4:5: note: include '<stdio.h>' or provide a declaration of 'printf'

gcc/c/ChangeLog:
	PR c++/84269
	* c-decl.c (implicit_decl_warning): When OLDDECL is non-NULL,
	potentially provide a missing header hint.

gcc/testsuite/ChangeLog:
	PR c++/84269
	* gcc.dg/spellcheck-stdlib-2.c: New test.
	* gcc.dg/spellcheck-stdlib-3.c: New test.
	* gcc.dg/spellcheck-stdlib.c: Add tests for <stdio.h>, <string.h>,
	and <stdlib.h>.

FIXME: C fixes: stdlib.h
---
 gcc/c/c-decl.c                             | 14 ++++++++-
 gcc/testsuite/gcc.dg/spellcheck-stdlib-2.c | 38 +++++++++++++++++++++++
 gcc/testsuite/gcc.dg/spellcheck-stdlib-3.c | 16 ++++++++++
 gcc/testsuite/gcc.dg/spellcheck-stdlib.c   | 50 ++++++++++++++++++++++++++++++
 4 files changed, 117 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/gcc.dg/spellcheck-stdlib-2.c
 create mode 100644 gcc/testsuite/gcc.dg/spellcheck-stdlib-3.c

diff --git a/gcc/c/c-decl.c b/gcc/c/c-decl.c
index 160ce35..4ea4713 100644
--- a/gcc/c/c-decl.c
+++ b/gcc/c/c-decl.c
@@ -3145,7 +3145,19 @@ implicit_decl_warning (location_t loc, tree id, tree olddecl)
   bool warned;
   auto_diagnostic_group d;
   name_hint hint;
-  if (!olddecl)
+  if (olddecl)
+    {
+      /* It might be due to a missing header.
+	 Provide a hint, without looking for misspellings.  */
+      const char *header_hint
+	= get_c_stdlib_header_for_name (IDENTIFIER_POINTER (id));
+      if (header_hint)
+	hint = name_hint (NULL,
+			  new suggest_missing_header (loc,
+						      IDENTIFIER_POINTER (id),
+						      header_hint));
+    }
+  else
     hint = lookup_name_fuzzy (id, FUZZY_LOOKUP_FUNCTION_NAME, loc);
 
   if (flag_isoc99)
diff --git a/gcc/testsuite/gcc.dg/spellcheck-stdlib-2.c b/gcc/testsuite/gcc.dg/spellcheck-stdlib-2.c
new file mode 100644
index 0000000..5086374
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/spellcheck-stdlib-2.c
@@ -0,0 +1,38 @@
+/* Test coverage for header suggestions for common names in the stdlib
+   for which -Wimplicit-function-declaration only emits one message.  */
+
+/* { dg-options "-Wimplicit-function-declaration" } */
+
+/* Missing <stdio.h>.  */
+
+void test_stdio_h (void)
+{
+  fopen ("test.txt"); /* { dg-warning "implicit declaration" } */
+  /* { dg-message "'#include <stdio.h>'" "" { target *-*-* } .-1 } */
+
+  getchar ();  /* { dg-warning "implicit declaration" } */
+  /* { dg-message "'#include <stdio.h>'" "" { target *-*-* } .-1 } */
+}
+
+/* Missing <string.h>.  */
+
+void test_string_h (char *dest, char *src)
+{
+  char buf[16];
+  memcmp(dest, src, 4); /* { dg-warning "implicit declaration" } */
+  /* { dg-message "'#include <string.h>'" "" { target *-*-* } .-1 } */
+  strcmp(dest, "test"); /* { dg-warning "implicit declaration" } */
+  /* { dg-message "'#include <string.h>'" "" { target *-*-* } .-1 } */
+  strncmp(dest, "test", 3); /* { dg-warning "implicit declaration" } */
+  /* { dg-message "'#include <string.h>'" "" { target *-*-* } .-1 } */
+  snprintf (buf, 16, "test\n");  /* { dg-warning "implicit declaration" } */
+  /* { dg-message "include '<stdio.h>'" "" { target *-*-* } .-1 } */
+}
+
+/* Missing <assert.h>.  */
+
+void test (int a, int b)
+{
+  assert (a == b); /* { dg-warning "implicit declaration" } */
+  // { dg-message "'#include <assert.h>'" "" { target *-*-* } .-1 }
+}
diff --git a/gcc/testsuite/gcc.dg/spellcheck-stdlib-3.c b/gcc/testsuite/gcc.dg/spellcheck-stdlib-3.c
new file mode 100644
index 0000000..73ec9f7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/spellcheck-stdlib-3.c
@@ -0,0 +1,16 @@
+/* Test coverage for header suggestions for common names in the stdlib
+   for which -Wimplicit-function-declaration leads to two messages
+   being emitted.  */
+
+/* { dg-options "-Wimplicit-function-declaration" } */
+
+/* Missing <stdio.h>.  */
+
+void test_stdio_h (void)
+{
+  char buf[16];
+  sprintf (buf, "test\n");  /* { dg-warning "implicit declaration of function 'sprintf'" } */
+  /* { dg-message "'sprintf' is defined in header '<stdio.h>'; did you forget to '#include <stdio.h>'." "" { target *-*-* } .-1 } */
+  /* { dg-warning "incompatible implicit declaration of built-in function 'sprintf'" "" { target *-*-* } .-2 } */
+  /* { dg-message "include '<stdio.h>' or provide a declaration of 'sprintf'" "" { target *-*-* } .-3 } */
+}
diff --git a/gcc/testsuite/gcc.dg/spellcheck-stdlib.c b/gcc/testsuite/gcc.dg/spellcheck-stdlib.c
index 7474c9a..e36beac 100644
--- a/gcc/testsuite/gcc.dg/spellcheck-stdlib.c
+++ b/gcc/testsuite/gcc.dg/spellcheck-stdlib.c
@@ -36,6 +36,12 @@ void test_stdio_h (void)
 
   EOF; /* { dg-error "'EOF' undeclared" } */
   /* { dg-message "'EOF' is defined in header '<stdio.h>'; did you forget to '#include <stdio.h>'?" "" { target *-*-* } .-1 } */
+
+  printf ("test\n"); /* { dg-warning "incompatible implicit declaration" } */
+  /* { dg-message "include '<stdio.h>'" "" { target *-*-* } .-1 } */
+  
+  sprintf (buf, "test\n");  /* { dg-warning "incompatible implicit declaration" } */
+  /* { dg-message "include '<stdio.h>'" "" { target *-*-* } .-1 } */
 }
 
 /* Missing <errno.h>.  */
@@ -62,3 +68,47 @@ int test_INT_MAX (void)
   /* { dg-bogus "__INT_MAX__" "" { target *-*-* } INT_MAX_line } */
   /* { dg-message "'INT_MAX' is defined in header '<limits.h>'; did you forget to '#include <limits.h>'?" "" { target *-*-* } INT_MAX_line } */
 }
+
+/* Missing <string.h>.  */
+
+void test_string_h (char *dest, char *src)
+{
+  memchr(dest, 'a', 4); /* { dg-warning "incompatible implicit declaration" } */
+  /* { dg-message "include '<string.h>'" "" { target *-*-* } .-1 } */
+  memcpy(dest, src, 4); /* { dg-warning "incompatible implicit declaration" } */
+  /* { dg-message "include '<string.h>'" "" { target *-*-* } .-1 } */
+  memmove(dest, src, 4); /* { dg-warning "incompatible implicit declaration" } */
+  /* { dg-message "include '<string.h>'" "" { target *-*-* } .-1 } */
+  memset(dest, 'a', 4); /* { dg-warning "incompatible implicit declaration" } */
+  /* { dg-message "include '<string.h>'" "" { target *-*-* } .-1 } */
+  strcat(dest, "test"); /* { dg-warning "incompatible implicit declaration" } */
+  /* { dg-message "include '<string.h>'" "" { target *-*-* } .-1 } */
+  strchr("test", 'e'); /* { dg-warning "incompatible implicit declaration" } */
+  /* { dg-message "include '<string.h>'" "" { target *-*-* } .-1 } */
+  strcpy(dest, "test"); /* { dg-warning "incompatible implicit declaration" } */
+  /* { dg-message "include '<string.h>'" "" { target *-*-* } .-1 } */
+  strlen("test"); /* { dg-warning "incompatible implicit declaration" } */
+  /* { dg-message "include '<string.h>'" "" { target *-*-* } .-1 } */
+  strncat(dest, "test", 3); /* { dg-warning "incompatible implicit declaration" } */
+  /* { dg-message "include '<string.h>'" "" { target *-*-* } .-1 } */
+  strncpy(dest, "test", 3); /* { dg-warning "incompatible implicit declaration" } */
+  /* { dg-message "include '<string.h>'" "" { target *-*-* } .-1 } */
+  strrchr("test", 'e'); /* { dg-warning "incompatible implicit declaration" } */
+  /* { dg-message "include '<string.h>'" "" { target *-*-* } .-1 } */
+  strspn(dest, "test"); /* { dg-warning "incompatible implicit declaration" } */
+  /* { dg-message "include '<string.h>'" "" { target *-*-* } .-1 } */
+  strstr(dest, "test"); /* { dg-warning "incompatible implicit declaration" } */
+  /* { dg-message "include '<string.h>'" "" { target *-*-* } .-1 } */
+}
+
+/* Missing <stdlib.h>.  */
+
+void test_stdlib_h (void *q)
+{
+  void *ptr = malloc (64); /* { dg-warning "incompatible implicit declaration" } */
+  /* { dg-message "include '<stdlib.h>'" "" { target *-*-* } .-1 } */
+  free (ptr); /* { dg-warning "incompatible implicit declaration" } */
+  /* { dg-message "include '<stdlib.h>'" "" { target *-*-* } .-1 } */
+  q = realloc (q, 1024); /* { dg-warning "incompatible implicit declaration" } */
+  /* { dg-message "include '<stdlib.h>'" "" { target *-*-* } .-1 } */
+}
-- 
1.8.5.3

