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 --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 } */ +} -- 1.8.5.3