diff mbox series

v3: C/C++: more stdlib header hints (PR c/81404)

Message ID 1508366043-50281-1-git-send-email-dmalcolm@redhat.com
State New
Headers show
Series v3: C/C++: more stdlib header hints (PR c/81404) | expand

Commit Message

David Malcolm Oct. 18, 2017, 10:34 p.m. UTC
On Wed, 2017-10-18 at 20:40 +0000, Joseph Myers wrote:
> On Wed, 18 Oct 2017, David Malcolm wrote:
> 
> > +    {"WINT_MAX", {"<wchar.h>", NULL} },
> > +    {"WINT_MIN", {"<wchar.h>", NULL} }
> 
> These are in <stdint.h> / <cstdint>, not <wchar.h>.

Thanks; here's an updated version of the patch which fixes that.

OK for trunk once the prereqs are in place? (assuming bootstrap and
usual testing passes)

gcc/ChangeLog:
	PR c/81404
	* Makefile.in (C_COMMON_OBJS): Add c-family/known-headers.o.

gcc/c-family/ChangeLog:
	PR c/81404
	* known-headers.cc: New file, based on material from c/c-decl.c.
	(suggest_missing_header): Copied as-is.
	(get_stdlib_header_for_name): New, based on get_c_name_hint but
	heavily edited to add C++ support.  Add some knowledge about
	<limits.h>, <stdint.h>, and <wchar.h>.
	* known-headers.h: Likewise.

gcc/c/ChangeLog:
	PR c/81404
	* c-decl.c: Include "c-family/known-headers.h".
	(get_c_name_hint): Rename to get_stdlib_header_for_name and move
	to known-headers.cc.
	(class suggest_missing_header): Move to known-header.h.
	(lookup_name_fuzzy): Call get_c_stdlib_header_for_name rather
	than get_c_name_hint.

gcc/cp/ChangeLog:
	PR c/81404
	* name-lookup.c: Include "c-family/known-headers.h"
	(lookup_name_fuzzy): Call get_cp_stdlib_header_for_name and
	potentially return a new suggest_missing_header hint.

gcc/testsuite/ChangeLog:
	PR c/81404
	* g++.dg/spellcheck-stdlib.C: New.
	* gcc.dg/spellcheck-stdlib.c (test_INT_MAX): New.
---
 gcc/Makefile.in                          |   2 +-
 gcc/c-family/known-headers.cc            | 167 +++++++++++++++++++++++++++++++
 gcc/c-family/known-headers.h             |  41 ++++++++
 gcc/c/c-decl.c                           |  82 +--------------
 gcc/cp/name-lookup.c                     |  11 ++
 gcc/testsuite/g++.dg/spellcheck-stdlib.C |  84 ++++++++++++++++
 gcc/testsuite/gcc.dg/spellcheck-stdlib.c |   9 ++
 7 files changed, 317 insertions(+), 79 deletions(-)
 create mode 100644 gcc/c-family/known-headers.cc
 create mode 100644 gcc/c-family/known-headers.h
 create mode 100644 gcc/testsuite/g++.dg/spellcheck-stdlib.C
diff mbox series

Patch

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 2809619..9855919 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1190,7 +1190,7 @@  C_COMMON_OBJS = c-family/c-common.o c-family/c-cppbuiltin.o c-family/c-dump.o \
   c-family/c-semantics.o c-family/c-ada-spec.o \
   c-family/c-cilkplus.o \
   c-family/array-notation-common.o c-family/cilk.o c-family/c-ubsan.o \
-  c-family/c-attribs.o c-family/c-warn.o
+  c-family/c-attribs.o c-family/c-warn.o c-family/known-headers.o
 
 # Language-independent object files.
 # We put the *-match.o and insn-*.o files first so that a parallel make
diff --git a/gcc/c-family/known-headers.cc b/gcc/c-family/known-headers.cc
new file mode 100644
index 0000000..e01f4e8
--- /dev/null
+++ b/gcc/c-family/known-headers.cc
@@ -0,0 +1,167 @@ 
+/* Support for suggestions about missing #include directives.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "c-family/c-common.h"
+#include "c-family/known-headers.h"
+#include "gcc-rich-location.h"
+
+/* An enum for distinguishing between the C and C++ stdlibs.  */
+
+enum stdlib
+{
+  STDLIB_C,
+  STDLIB_CPLUSPLUS,
+
+  NUM_STDLIBS
+};
+
+/* A struct for associating names in a standard library with the header
+   that should be included to locate them, for each of the C and C++ stdlibs
+   (or NULL, for names that aren't in a header for a particular stdlib).  */
+
+struct stdlib_hint
+{
+  const char *name;
+  const char *header[NUM_STDLIBS];
+};
+
+/* Given non-NULL NAME, return the header name defining it within either
+   the standard library (with '<' and '>'), or NULL.
+   Only handles a subset of the most common names within the stdlibs.  */
+
+static const char *
+get_stdlib_header_for_name (const char *name, enum stdlib lib)
+{
+  gcc_assert (name);
+  gcc_assert (lib < NUM_STDLIBS);
+
+  static const stdlib_hint hints[] = {
+    /* <errno.h> and <cerrno>.  */
+    {"errno", {"<errno.h>", "<cerrno>"} },
+
+    /* <limits.h> and <climits>.  */
+    {"CHAR_BIT", {"<limits.h>", "<climits>"} },
+    {"CHAR_MAX", {"<limits.h>", "<climits>"} },
+    {"CHAR_MIN", {"<limits.h>", "<climits>"} },
+    {"INT_MAX", {"<limits.h>", "<climits>"} },
+    {"INT_MIN", {"<limits.h>", "<climits>"} },
+    {"LLONG_MAX", {"<limits.h>", "<climits>"} },
+    {"LLONG_MIN", {"<limits.h>", "<climits>"} },
+    {"LONG_MAX", {"<limits.h>", "<climits>"} },
+    {"LONG_MIN", {"<limits.h>", "<climits>"} },
+    {"MB_LEN_MAX", {"<limits.h>", "<climits>"} },
+    {"SCHAR_MAX", {"<limits.h>", "<climits>"} },
+    {"SCHAR_MIN", {"<limits.h>", "<climits>"} },
+    {"SHRT_MAX", {"<limits.h>", "<climits>"} },
+    {"SHRT_MIN", {"<limits.h>", "<climits>"} },
+    {"UCHAR_MAX", {"<limits.h>", "<climits>"} },
+    {"UINT_MAX", {"<limits.h>", "<climits>"} },
+    {"ULLONG_MAX", {"<limits.h>", "<climits>"} },
+    {"ULONG_MAX", {"<limits.h>", "<climits>"} },
+    {"USHRT_MAX", {"<limits.h>", "<climits>"} },
+
+    /* <stdarg.h> and <cstdarg>.  */
+    {"va_list", {"<stdarg.h>", "<cstdarg>"} },
+
+    /* <stddef.h> and <cstddef>.  */
+    {"NULL", {"<stddef.h>", "<cstddef>"} },
+    {"nullptr_t", {NULL, "<cstddef>"} },
+    {"offsetof", {"<stdalign.h>", "<cstddef>"} },
+    {"ptrdiff_t", {"<stddef.h>", "<cstddef>"} },
+    {"size_t", {"<stddef.h>", "<cstddef>"} },
+    {"wchar_t", {"<stddef.h>", NULL /* a keyword in C++ */} },
+
+    /* <stdio.h>.  */
+    {"BUFSIZ", {"<stdio.h>", "<cstdio>"} },
+    {"EOF", {"<stdio.h>", "<cstdio>"} },
+    {"FILE", {"<stdio.h>", "<cstdio>"} },
+    {"FILENAME_MAX", {"<stdio.h>", "<cstdio>"} },
+    {"fpos_t", {"<stdio.h>", "<cstdio>"} },
+    {"stderr", {"<stdio.h>", "<cstdio>"} },
+    {"stdin", {"<stdio.h>", "<cstdio>"} },
+    {"stdout", {"<stdio.h>", "<cstdio>"} },
+
+    /* <stdint.h>.  */
+    {"PTRDIFF_MAX", {"<stdint.h>", "<cstdint>"} },
+    {"PTRDIFF_MIN", {"<stdint.h>", "<cstdint>"} },
+    {"SIG_ATOMIC_MAX", {"<stdint.h>", "<cstdint>"} },
+    {"SIG_ATOMIC_MIN", {"<stdint.h>", "<cstdint>"} },
+    {"SIZE_MAX", {"<stdint.h>", "<cstdint>"} },
+    {"WINT_MAX", {"<stdint.h>", "<cstdint>"} },
+    {"WINT_MIN", {"<stdint.h>", "<cstdint>"} },
+
+    /* <wchar.h>.  */
+    {"WCHAR_MAX", {"<wchar.h>", "<cwchar>"} },
+    {"WCHAR_MIN", {"<wchar.h>", "<cwchar>"} }
+  };
+  const size_t num_hints = sizeof (hints) / sizeof (hints[0]);
+  for (size_t i = 0; i < num_hints; i++)
+    if (0 == strcmp (name, hints[i].name))
+      return hints[i].header[lib];
+  return NULL;
+}
+
+/* Given non-NULL NAME, return the header name defining it within the C
+   standard library (with '<' and '>'), or NULL.  */
+
+const char *
+get_c_stdlib_header_for_name (const char *name)
+{
+  return get_stdlib_header_for_name (name, STDLIB_C);
+}
+
+/* Given non-NULL NAME, return the header name defining it within the C++
+   standard library (with '<' and '>'), or NULL.  */
+
+const char *
+get_cp_stdlib_header_for_name (const char *name)
+{
+  return get_stdlib_header_for_name (name, STDLIB_CPLUSPLUS);
+}
+
+/* Implementation of class suggest_missing_header.  */
+
+/* suggest_missing_header's ctor.  */
+
+suggest_missing_header::suggest_missing_header (location_t loc,
+						const char *name,
+						const char *header_hint)
+: deferred_diagnostic (loc), m_name_str (name), m_header_hint (header_hint)
+{
+  gcc_assert (name);
+  gcc_assert (header_hint);
+}
+
+/* suggest_missing_header's dtor.  */
+
+suggest_missing_header::~suggest_missing_header ()
+{
+  if (is_suppressed_p ())
+    return;
+
+  gcc_rich_location richloc (get_location ());
+  maybe_add_include_fixit (&richloc, m_header_hint);
+  inform_at_rich_loc (&richloc,
+		      "%qs is defined in header %qs;"
+		      " did you forget to %<#include %s%>?",
+		      m_name_str, m_header_hint, m_header_hint);
+}
diff --git a/gcc/c-family/known-headers.h b/gcc/c-family/known-headers.h
new file mode 100644
index 0000000..328100f
--- /dev/null
+++ b/gcc/c-family/known-headers.h
@@ -0,0 +1,41 @@ 
+/* Support for suggestions about missing #include directives.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_KNOWN_HEADERS_H
+#define GCC_KNOWN_HEADERS_H
+
+extern const char *get_c_stdlib_header_for_name (const char *name);
+extern const char *get_cp_stdlib_header_for_name (const char *name);
+
+/* Subclass of deferred_diagnostic for suggesting to the user
+   that they have missed a #include.  */
+
+class suggest_missing_header : public deferred_diagnostic
+{
+ public:
+  suggest_missing_header (location_t loc, const char *name,
+			  const char *header_hint);
+  ~suggest_missing_header ();
+
+ private:
+  const char *m_name_str;
+  const char *m_header_hint;
+};
+
+#endif /* GCC_KNOWN_HEADERS_H */
diff --git a/gcc/c/c-decl.c b/gcc/c/c-decl.c
index ce4cb3e..93d8aab 100644
--- a/gcc/c/c-decl.c
+++ b/gcc/c/c-decl.c
@@ -54,6 +54,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "spellcheck-tree.h"
 #include "gcc-rich-location.h"
 #include "asan.h"
+#include "c-family/known-headers.h"
 
 /* In grokdeclarator, distinguish syntactic contexts of declarators.  */
 enum decl_context
@@ -3983,83 +3984,6 @@  lookup_name_in_scope (tree name, struct c_scope *scope)
   return NULL_TREE;
 }
 
-/* Subroutine of lookup_name_fuzzy for handling unrecognized names
-   for some of the most common names within the C standard library.
-   Given non-NULL NAME, return the header name defining it within the C
-   standard library (with '<' and '>'), or NULL.  */
-
-static const char *
-get_c_name_hint (const char *name)
-{
-  struct std_name_hint
-  {
-    const char *name;
-    const char *header;
-  };
-  static const std_name_hint hints[] = {
-    /* <errno.h>.  */
-    {"errno", "<errno.h>"},
-
-    /* <stdarg.h>.  */
-    {"va_list", "<stdarg.h>"},
-
-    /* <stddef.h>.  */
-    {"NULL", "<stddef.h>"},
-    {"ptrdiff_t", "<stddef.h>"},
-    {"wchar_t", "<stddef.h>"},
-    {"size_t", "<stddef.h>"},
-
-    /* <stdio.h>.  */
-    {"BUFSIZ", "<stdio.h>"},
-    {"EOF", "<stdio.h>"},
-    {"FILE", "<stdio.h>"},
-    {"FILENAME_MAX", "<stdio.h>"},
-    {"fpos_t", "<stdio.h>"},
-    {"stderr", "<stdio.h>"},
-    {"stdin", "<stdio.h>"},
-    {"stdout", "<stdio.h>"}
-  };
-  const size_t num_hints = sizeof (hints) / sizeof (hints[0]);
-  for (size_t i = 0; i < num_hints; i++)
-    {
-      if (0 == strcmp (name, hints[i].name))
-	return hints[i].header;
-    }
-  return NULL;
-}
-
-/* Subclass of deferred_diagnostic for suggesting to the user
-   that they have missed a #include.  */
-
-class suggest_missing_header : public deferred_diagnostic
-{
- public:
-  suggest_missing_header (location_t loc, const char *name,
-			  const char *header_hint)
-  : deferred_diagnostic (loc), m_name_str (name), m_header_hint (header_hint)
-  {
-    gcc_assert (name);
-    gcc_assert (header_hint);
-  }
-
-  ~suggest_missing_header ()
-  {
-    if (is_suppressed_p ())
-      return;
-
-    gcc_rich_location richloc (get_location ());
-    maybe_add_include_fixit (&richloc, m_header_hint);
-    inform_at_rich_loc (&richloc,
-			"%qs is defined in header %qs;"
-			" did you forget to %<#include %s%>?",
-			m_name_str, m_header_hint, m_header_hint);
-  }
-
- private:
-  const char *m_name_str;
-  const char *m_header_hint;
-};
-
 /* Look for the closest match for NAME within the currently valid
    scopes.
 
@@ -4085,7 +4009,9 @@  lookup_name_fuzzy (tree name, enum lookup_name_fuzzy_kind kind, location_t loc)
 
   /* First, try some well-known names in the C standard library, in case
      the user forgot a #include.  */
-  const char *header_hint = get_c_name_hint (IDENTIFIER_POINTER (name));
+  const char *header_hint
+    = get_c_stdlib_header_for_name (IDENTIFIER_POINTER (name));
+
   if (header_hint)
     return name_hint (NULL,
 		      new suggest_missing_header (loc,
diff --git a/gcc/cp/name-lookup.c b/gcc/cp/name-lookup.c
index dec93ad..5de320f 100644
--- a/gcc/cp/name-lookup.c
+++ b/gcc/cp/name-lookup.c
@@ -32,6 +32,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "gcc-rich-location.h"
 #include "spellcheck-tree.h"
 #include "parser.h"
+#include "c-family/known-headers.h"
 
 static cxx_binding *cxx_binding_make (tree value, tree type);
 static cp_binding_level *innermost_nonclass_level (void);
@@ -5672,6 +5673,16 @@  lookup_name_fuzzy (tree name, enum lookup_name_fuzzy_kind kind, location_t loc)
 {
   gcc_assert (TREE_CODE (name) == IDENTIFIER_NODE);
 
+  /* First, try some well-known names in the C++ standard library, in case
+     the user forgot a #include.  */
+  const char *header_hint
+    = get_cp_stdlib_header_for_name (IDENTIFIER_POINTER (name));
+  if (header_hint)
+    return name_hint (NULL,
+		      new suggest_missing_header (loc,
+						  IDENTIFIER_POINTER (name),
+						  header_hint));
+
   best_match <tree, const char *> bm (name);
 
   cp_binding_level *lvl;
diff --git a/gcc/testsuite/g++.dg/spellcheck-stdlib.C b/gcc/testsuite/g++.dg/spellcheck-stdlib.C
new file mode 100644
index 0000000..6e6ab1d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/spellcheck-stdlib.C
@@ -0,0 +1,84 @@ 
+/* Missing <cstddef>.  */
+
+void *ptr = NULL; // { dg-error "'NULL' was not declared" }
+// { dg-message "'NULL' is defined in header '<cstddef>'; did you forget to '#include <cstddef>'?" "" { target *-*-* } .-1 }
+
+ptrdiff_t pd; // { dg-error "'ptrdiff_t' does not name a type" }
+// { dg-message "'ptrdiff_t' is defined in header '<cstddef>'; did you forget to '#include <cstddef>'?" "" { target *-*-* } .-1 }
+
+size_t sz; // { dg-error "'size_t' does not name a type" }
+// { dg-message "'size_t' is defined in header '<cstddef>'; did you forget to '#include <cstddef>'?" "" { target *-*-* } .-1 }
+
+/* Missing <cstdio>.  */
+
+void test_cstdio (void)
+{
+  FILE *f; // { dg-error "'FILE' was not declared in this scope" }
+  // { dg-message "'FILE' is defined in header '<cstdio>'; did you forget to '#include <cstdio>'?" "" { target *-*-* } .-1 }
+  // { dg-error "'f' was not declared in this scope" "" { target *-*-* } .-2 }
+  // { dg-bogus "suggested alternative: 'if'" "PR c++/80567" { xfail *-*-* } .-3 }
+
+  char buf[BUFSIZ]; // { dg-error "'BUFSIZ' was not declared" }
+  // { dg-message "'BUFSIZ' is defined in header '<cstdio>'; did you forget to '#include <cstdio>'?" "" { target *-*-* } .-1 }
+
+  char buf2[FILENAME_MAX]; // { dg-error "'FILENAME_MAX' was not declared" }
+  // { dg-message "'FILENAME_MAX' is defined in header '<cstdio>'; did you forget to '#include <cstdio>'?" "" { target *-*-* } .-1 }
+
+  stderr; // { dg-error "'stderr' was not declared" }
+  // { dg-message "'stderr' is defined in header '<cstdio>'; did you forget to '#include <cstdio>'?" "" { target *-*-* } .-1 }
+
+  stdin; // { dg-error "'stdin' was not declared" }
+  // { dg-message "'stdin' is defined in header '<cstdio>'; did you forget to '#include <cstdio>'?" "" { target *-*-* } .-1 }
+
+  stdout; // { dg-error "'stdout' was not declared" }
+  // { dg-message "'stdout' is defined in header '<cstdio>'; did you forget to '#include <cstdio>'?" "" { target *-*-* } .-1 }
+
+  EOF; // { dg-error "'EOF' was not declared" }
+  // { dg-message "'EOF' is defined in header '<cstdio>'; did you forget to '#include <cstdio>'?" "" { target *-*-* } .-1 }
+}
+
+/* Missing <cerrno>.  */
+
+int test_cerrno (void)
+{
+  return errno; // { dg-error "'errno' was not declared" }
+  // { dg-message "'errno' is defined in header '<cerrno>'; did you forget to '#include <cerrno>'?" "" { target *-*-* } .-1 }
+}
+
+/* Missing <cstdarg>.  */
+
+void test_cstdarg (void)
+{
+  va_list ap; // { dg-error "'va_list'" }
+  // { dg-message "'va_list' is defined in header '<cstdarg>'; did you forget to '#include <cstdarg>'?" "" { target *-*-* } .-1 }
+}
+
+/* Missing <climits>.  */
+int test_INT_MAX (void)
+{
+  return INT_MAX; // { dg-line INT_MAX_line }
+  // { dg-error "'INT_MAX' was not declared" "" { target *-*-* } INT_MAX_line }
+  // { dg-bogus "__INT_MAX__" "" { target *-*-* } INT_MAX_line }
+  // { dg-message "'INT_MAX' is defined in header '<climits>'; did you forget to '#include <climits>'?" "" { target *-*-* } INT_MAX_line }
+}
+
+/* Verify that we don't offer suggestions to stdlib globals names when
+   there's an explicit namespace.  */
+
+namespace some_ns {}
+
+int not_within_namespace (void)
+{
+  return some_ns::stdout; // { dg-error "'stdout' is not a member of 'some_ns'" }
+  // { dg-bogus "is defined in header" "" { target *-*-* } .-1 }
+}
+
+/* Similarly for when there's an explicit class scope.  */
+
+class some_class {};
+
+int not_within_class (void)
+{
+  return some_class::stdout; // { dg-error "'stdout' is not a member of 'some_class'" }
+  // { dg-bogus "is defined in header" "" { target *-*-* } .-1 }
+}
diff --git a/gcc/testsuite/gcc.dg/spellcheck-stdlib.c b/gcc/testsuite/gcc.dg/spellcheck-stdlib.c
index 85a21c3..7474c9a 100644
--- a/gcc/testsuite/gcc.dg/spellcheck-stdlib.c
+++ b/gcc/testsuite/gcc.dg/spellcheck-stdlib.c
@@ -53,3 +53,12 @@  void test_stdarg_h (void)
   va_list ap; /* { dg-error "unknown type name 'va_list'" } */
   /* { dg-message "'va_list' is defined in header '<stdarg.h>'; did you forget to '#include <stdarg.h>'?" "" { target *-*-* } .-1 } */
 }
+
+/* Missing <limits.h>.  */
+int test_INT_MAX (void)
+{
+  return INT_MAX; /* { dg-line INT_MAX_line } */
+  /* { dg-error "'INT_MAX' undeclared" "" { target *-*-* } INT_MAX_line } */
+  /* { 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 } */
+}