From b1212255558f0a4a398c7314f92effe4dbdcfec2 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Tue, 21 Nov 2017 00:46:24 +0000 Subject: [PATCH] C: hints for missing stdlib includes for macros and types The C frontend already "knows" about many common functions in the C standard library: test.c: In function 'test': test.c:3:3: warning: implicit declaration of function 'printf' [-Wimplicit-function-declaration] printf ("hello world\n"); ^~~~~~ test.c:3:3: warning: incompatible implicit declaration of built-in function 'printf' test.c:3:3: note: include '' or provide a declaration of 'printf' and which header file they are in. However it doesn't know about various types and macros: test.c:1:13: error: 'NULL' undeclared here (not in a function) void *ptr = NULL; ^~~~ This patch uses the name_hint/deferred_diagnostic machinery to add hints for missing C standard library headers for some of the most common type and macro names. For example, the above becomes: test.c:1:13: error: 'NULL' undeclared here (not in a function) void *ptr = NULL; ^~~~ test.c:1:13: note: 'NULL' is defined in header ''; did you forget to '#include '? gcc/c/ChangeLog: * c-decl.c (get_c_name_hint): New function. (class suggest_missing_header): New class. (lookup_name_fuzzy): Call get_c_name_hint and use it to suggest missing headers to the user. gcc/testsuite/ChangeLog: * gcc.dg/spellcheck-stdlib.c: New test case. From-SVN: r254979 --- gcc/c/ChangeLog | 7 ++ gcc/c/c-decl.c | 92 +++++++++++++++++++++++- gcc/testsuite/ChangeLog | 4 ++ gcc/testsuite/gcc.dg/spellcheck-stdlib.c | 55 ++++++++++++++ 4 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/spellcheck-stdlib.c diff --git a/gcc/c/ChangeLog b/gcc/c/ChangeLog index 6e193932cba..b60a8e70d48 100644 --- a/gcc/c/ChangeLog +++ b/gcc/c/ChangeLog @@ -1,3 +1,10 @@ +2017-11-20 David Malcolm + + * c-decl.c (get_c_name_hint): New function. + (class suggest_missing_header): New class. + (lookup_name_fuzzy): Call get_c_name_hint and use it to + suggest missing headers to the user. + 2017-11-20 David Malcolm * c-decl.c: Define INCLUDE_UNIQUE_PTR before including system.h. diff --git a/gcc/c/c-decl.c b/gcc/c/c-decl.c index 7726476a95e..ae45e9a88f8 100644 --- a/gcc/c/c-decl.c +++ b/gcc/c/c-decl.c @@ -3992,6 +3992,83 @@ 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", ""}, + + /* . */ + {"va_list", ""}, + + /* . */ + {"NULL", ""}, + {"ptrdiff_t", ""}, + {"wchar_t", ""}, + {"size_t", ""}, + + /* . */ + {"BUFSIZ", ""}, + {"EOF", ""}, + {"FILE", ""}, + {"FILENAME_MAX", ""}, + {"fpos_t", ""}, + {"stderr", ""}, + {"stdin", ""}, + {"stdout", ""} + }; + 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 (&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. @@ -4006,13 +4083,24 @@ lookup_name_in_scope (tree name, struct c_scope *scope) identifier to the C frontend. It also looks for start_typename keywords, to detect "singed" vs "signed" - typos. */ + typos. + + Use LOC for any deferred diagnostics. */ name_hint -lookup_name_fuzzy (tree name, enum lookup_name_fuzzy_kind kind, location_t) +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_c_name_hint (IDENTIFIER_POINTER (name)); + if (header_hint) + return name_hint (NULL, + new suggest_missing_header (loc, + IDENTIFIER_POINTER (name), + header_hint)); + best_match bm (name); /* Look within currently valid scopes. */ diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index 4c9569f13a9..d1911dff6c9 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,3 +1,7 @@ +2017-11-20 David Malcolm + + * gcc.dg/spellcheck-stdlib.c: New test case. + 2017-11-20 David Malcolm PR c++/72786 diff --git a/gcc/testsuite/gcc.dg/spellcheck-stdlib.c b/gcc/testsuite/gcc.dg/spellcheck-stdlib.c new file mode 100644 index 00000000000..85a21c38400 --- /dev/null +++ b/gcc/testsuite/gcc.dg/spellcheck-stdlib.c @@ -0,0 +1,55 @@ +/* Missing . */ + +void *ptr = NULL; /* { dg-error "'NULL' undeclared here" } */ +/* { dg-message "'NULL' is defined in header ''; did you forget to '#include '?" "" { target *-*-* } .-1 } */ + +ptrdiff_t pd; /* { dg-error "unknown type name 'ptrdiff_t'" } */ +/* { dg-message "'ptrdiff_t' is defined in header ''; did you forget to '#include '?" "" { target *-*-* } .-1 } */ + +wchar_t wc; /* { dg-error "unknown type name 'wchar_t'" } */ +/* { dg-message "'wchar_t' is defined in header ''; did you forget to '#include '?" "" { target *-*-* } .-1 } */ + +size_t sz; /* { dg-error "unknown type name 'size_t'" } */ +/* { dg-message "'size_t' is defined in header ''; did you forget to '#include '?" "" { target *-*-* } .-1 } */ + +/* Missing . */ + +void test_stdio_h (void) +{ + FILE *f; /* { dg-error "unknown type name 'FILE'" } */ + /* { dg-message "'FILE' is defined in header ''; did you forget to '#include '?" "" { target *-*-* } .-1 } */ + + char buf[BUFSIZ]; /* { dg-error "'BUFSIZ' undeclared" } */ + /* { dg-message "'BUFSIZ' is defined in header ''; did you forget to '#include '?" "" { target *-*-* } .-1 } */ + + char buf2[FILENAME_MAX]; /* { dg-error "'FILENAME_MAX' undeclared" } */ + /* { dg-message "'FILENAME_MAX' is defined in header ''; did you forget to '#include '?" "" { target *-*-* } .-1 } */ + + stderr; /* { dg-error "'stderr' undeclared" } */ + /* { dg-message "'stderr' is defined in header ''; did you forget to '#include '?" "" { target *-*-* } .-1 } */ + + stdin; /* { dg-error "'stdin' undeclared" } */ + /* { dg-message "'stdin' is defined in header ''; did you forget to '#include '?" "" { target *-*-* } .-1 } */ + + stdout; /* { dg-error "'stdout' undeclared" } */ + /* { dg-message "'stdout' is defined in header ''; did you forget to '#include '?" "" { target *-*-* } .-1 } */ + + EOF; /* { dg-error "'EOF' undeclared" } */ + /* { dg-message "'EOF' is defined in header ''; did you forget to '#include '?" "" { target *-*-* } .-1 } */ +} + +/* Missing . */ + +int test_errno_h (void) +{ + return errno; /* { dg-error "'errno' undeclared" } */ + /* { dg-message "'errno' is defined in header ''; did you forget to '#include '?" "" { target *-*-* } .-1 } */ +} + +/* Missing . */ + +void test_stdarg_h (void) +{ + va_list ap; /* { dg-error "unknown type name 'va_list'" } */ + /* { dg-message "'va_list' is defined in header ''; did you forget to '#include '?" "" { target *-*-* } .-1 } */ +} -- 2.30.2