[PATCH] v2: C/C++: more stdlib header hints (PR c/81404)
David Malcolm
dmalcolm@redhat.com
Wed Oct 18 20:31:00 GMT 2017
On Tue, 2017-10-17 at 20:05 +0000, Joseph Myers wrote:
> On Tue, 17 Oct 2017, David Malcolm wrote:
>
> > It also adds generalizes some of the code for this (and for the
> > "std::"
> > namespace hints in the C++ frontend), moving it to a new
> > c-family/known-headers.cc and .h, and introducing a class
> > known_headers.
> > This currently just works by scanning a hardcoded array of known
> > name/header associations, but perhaps in the future could be turned
> > into some kind of symbol database so that the compiler could record
> > API
> > uses and use that to offer suggestions e.g.
> >
> > foo.cc: error: 'myapi::foo' was not declared in this scope
> > foo.cc: note: 'myapi::foo" was declared in header 'myapi/private.h'
> > (included via 'myapi/public.h') when compiling 'bar.cc'; did you
> > forget to
> > '#include "myapi/public.h"'?
> >
> > or somesuch.
> >
> > In any case, moving this to a class gives an easier way to locate
> > the
> > hardcoded knowledge about the stdlib.
> >
> > The patch also adds similar code to the C++ frontend covering
> > unqualified names in the standard library, so that rather than just
>
> I'd tend to expect hardcoded standard library knowledge, where it
> relates
> to symbols present for both C and C++, to be in a common c-family
> file
> (e.g. listing both C and C++ headers for each symbol, with the
> possibility
> of some symbols only having a header listed for one C and C++; most
> C
> symbols would have <foo.h> and <cfoo> listed, but some might be
> different,
> e.g. wchar_t being a keyword in C++ or clog being completely
> different in
> the two libraries). That reduces the chance of a symbol being
> gratuitously listed for one language only when such hints make sense
> for
> it in both languages.
Here's an updated version of the patch, which moves the data to
c-family/known-headers.cc and unifies the C and C++ data into one array.
Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
OK for trunk once the prereqs are in place?
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 --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..1ac42f1
--- /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>"} },
+
+ /* <wchar.h>. */
+ {"WCHAR_MAX", {"<wchar.h>", "<cwchar>"} },
+ {"WCHAR_MIN", {"<wchar.h>", "<cwchar>"} },
+ {"WINT_MAX", {"<wchar.h>", NULL} },
+ {"WINT_MIN", {"<wchar.h>", NULL} }
+ };
+ 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 } */
+}
--
1.8.5.3
More information about the Gcc-patches
mailing list