This is on top of the stdbool.h and stdint.h patches. This adds a flag to c_parser so we know when we were trying to constract a string literal. If there is a parse error and we were constructing a string literal, and the next token is an unknown identifier name, and we know there is a standard header that defines that name as a string literal, then add a missing header hint to the error messsage.
I haven't checked yet if we can do something similar for the C++ parser. And the testcase needs to be extended a bit. But I hope the direction is OK. For the following source: const char *hex64_fmt = PRIx64; const char *msg_fmt = "Provide %" PRIx64 "\n"; void foo (uint32_t v) { printf ("%" PRIu32, v); } We will get the following: $ /opt/local/gcc/bin/gcc -c t.c t.c:4:26: error: ‘PRIx64’ undeclared here (not in a function) 4 | const char *hex64_fmt = PRIx64; | ^~~~~~ t.c:3:1: note: ‘PRIx64’ is defined in header ‘<inttypes.h>’; did you forget to ‘#include <inttypes.h>’? 2 | #include <stdint.h> +++ |+#include <inttypes.h> 3 | t.c:5:35: error: expected ‘,’ or ‘;’ before ‘PRIx64’ 5 | const char *msg_fmt = "Provide %" PRIx64 "\n"; | ^~~~~~ t.c:5:35: note: ‘PRIx64’ is defined in header ‘<inttypes.h>’; did you forget to ‘#include <inttypes.h>’? t.c: In function ‘foo’: t.c:9:14: error: expected ‘)’ before ‘PRIu32’ 9 | printf ("%" PRIu32, v); | ^~~~~~~ | ) t.c:9:15: note: ‘PRIu32’ is defined in header ‘<inttypes.h>’; did you forget to ‘#include <inttypes.h>’? 9 | printf ("%" PRIu32, v); | ^~~~~~ --- gcc/c-family/known-headers.cc | 88 ++++++++++++++++++++++ gcc/c-family/known-headers.h | 2 + gcc/c/c-parser.c | 29 +++++++ gcc/testsuite/gcc.dg/spellcheck-inttypes.c | 32 ++++++++ 4 files changed, 151 insertions(+) create mode 100644 gcc/testsuite/gcc.dg/spellcheck-inttypes.c diff --git a/gcc/c-family/known-headers.cc b/gcc/c-family/known-headers.cc index 1e2bf49c439a..b9772af21863 100644 --- a/gcc/c-family/known-headers.cc +++ b/gcc/c-family/known-headers.cc @@ -46,6 +46,71 @@ struct stdlib_hint const char *header[NUM_STDLIBS]; }; +/* These can be used as string macros or as identifiers. Must all be + string-literals. Used in get_stdlib_header_for_name and + get_c_stdlib_header_for_string_macro_name. */ +static const stdlib_hint c99_cxx11_macro_hints[] = { + /* <inttypes.h> and <cinttypes>. */ + {"PRId8", {"<inttypes.h>", "<cinttypes>"} }, + {"PRId16", {"<inttypes.h>", "<cinttypes>"} }, + {"PRId32", {"<inttypes.h>", "<cinttypes>"} }, + {"PRId64", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIi8", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIi16", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIi32", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIi64", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIo8", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIo16", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIo32", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIo64", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIu8", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIu16", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIu32", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIu64", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIx8", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIx16", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIx32", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIx64", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIX8", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIX16", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIX32", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIX64", {"<inttypes.h>", "<cinttypes>"} }, + + {"PRIdPTR", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIiPTR", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIoPTR", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIuPTR", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIxPTR", {"<inttypes.h>", "<cinttypes>"} }, + {"PRIXPTR", {"<inttypes.h>", "<cinttypes>"} }, + + {"SCNd8", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNd16", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNd32", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNd64", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNi8", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNi16", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNi32", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNi64", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNo8", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNo16", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNo32", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNo64", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNu8", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNu16", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNu32", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNu64", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNx8", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNx16", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNx32", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNx64", {"<inttypes.h>", "<cinttypes>"} }, + + {"SCNdPTR", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNiPTR", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNoPTR", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNuPTR", {"<inttypes.h>", "<cinttypes>"} }, + {"SCNxPTR", {"<inttypes.h>", "<cinttypes>"} } +}; + /* 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. */ @@ -196,6 +261,14 @@ get_stdlib_header_for_name (const char *name, enum stdlib lib) if (strcmp (name, c99_cxx11_hints[i].name) == 0) return c99_cxx11_hints[i].header[lib]; + const size_t num_c99_cxx11_macro_hints + = sizeof (c99_cxx11_macro_hints) / sizeof (c99_cxx11_macro_hints[0]); + if ((lib == STDLIB_C && flag_isoc99) + || (lib == STDLIB_CPLUSPLUS && cxx_dialect >= cxx11 )) + for (size_t i = 0; i < num_c99_cxx11_macro_hints; i++) + if (strcmp (name, c99_cxx11_macro_hints[i].name) == 0) + return c99_cxx11_macro_hints[i].header[lib]; + return NULL; } @@ -217,6 +290,21 @@ get_cp_stdlib_header_for_name (const char *name) return get_stdlib_header_for_name (name, STDLIB_CPLUSPLUS); } +/* Given non-NULL NAME, return the header name defining a string macro + within the C standard library (with '<' and '>'), or NULL. */ +const char * +get_c_stdlib_header_for_string_macro_name (const char *name) +{ + const size_t num_c99_cxx11_macro_hints + = sizeof (c99_cxx11_macro_hints) / sizeof (c99_cxx11_macro_hints[0]); + if (flag_isoc99) + for (size_t i = 0; i < num_c99_cxx11_macro_hints; i++) + if (strcmp (name, c99_cxx11_macro_hints[i].name) == 0) + return c99_cxx11_macro_hints[i].header[STDLIB_C]; + + return NULL; +} + /* Implementation of class suggest_missing_header. */ /* suggest_missing_header's ctor. */ diff --git a/gcc/c-family/known-headers.h b/gcc/c-family/known-headers.h index 4ec4e23fc88d..a69bbbf28e76 100644 --- a/gcc/c-family/known-headers.h +++ b/gcc/c-family/known-headers.h @@ -23,6 +23,8 @@ along with GCC; see the file COPYING3. If not see extern const char *get_c_stdlib_header_for_name (const char *name); extern const char *get_cp_stdlib_header_for_name (const char *name); +extern const char *get_c_stdlib_header_for_string_macro_name (const char *n); + /* Subclass of deferred_diagnostic for suggesting to the user that they have missed a #include. */ diff --git a/gcc/c/c-parser.c b/gcc/c/c-parser.c index 5d11e7e73c16..6b2ae5688a72 100644 --- a/gcc/c/c-parser.c +++ b/gcc/c/c-parser.c @@ -69,6 +69,7 @@ along with GCC; see the file COPYING3. If not see #include "c-family/name-hint.h" #include "tree-iterator.h" #include "memmodel.h" +#include "c-family/known-headers.h" /* We need to walk over decls with incomplete struct/union/enum types after parsing the whole translation unit. @@ -223,6 +224,13 @@ struct GTY(()) c_parser { keywords are valid. */ BOOL_BITFIELD objc_property_attr_context : 1; + /* Whether we have just seen/constructed a string-literal. Set when + returning a string-literal from c_parser_string_literal. Reset + in consume_token. Useful when we get a parse error and see an + unknown token, which could have been a string-literal constant + macro. */ + BOOL_BITFIELD seen_string_literal : 1; + /* Location of the last consumed token. */ location_t last_token_location; }; @@ -853,6 +861,7 @@ c_parser_consume_token (c_parser *parser) } } parser->tokens_avail--; + parser->seen_string_literal = false; } /* Expect the current token to be a #pragma. Consume it and remember @@ -966,6 +975,25 @@ c_parser_error_richloc (c_parser *parser, const char *gmsgid, } } + /* If we were parsing a string-literal and there is an unknown name + token right after, then check to see if that could also have been + a literal string by checking the name against a list of known + standard string literal constants defined in header files. If + there is one, then add that as an hint to the error message. */ + auto_diagnostic_group d; + name_hint h; + if (parser->seen_string_literal && token->type == CPP_NAME) + { + tree name = token->value; + const char *header_hint + = get_c_stdlib_header_for_name (IDENTIFIER_POINTER (name)); + if (header_hint != NULL) + h = name_hint (NULL, + new suggest_missing_header (token->location, + IDENTIFIER_POINTER (name), + header_hint)); + } + c_parse_error (gmsgid, /* Because c_parse_error does not understand CPP_KEYWORD, keywords are treated like @@ -7539,6 +7567,7 @@ c_parser_string_literal (c_parser *parser, bool translate, bool wide_ok) ret.original_code = STRING_CST; ret.original_type = NULL_TREE; set_c_expr_source_range (&ret, get_range_from_loc (line_table, loc)); + parser->seen_string_literal = true; return ret; } diff --git a/gcc/testsuite/gcc.dg/spellcheck-inttypes.c b/gcc/testsuite/gcc.dg/spellcheck-inttypes.c new file mode 100644 index 000000000000..91c082d606d9 --- /dev/null +++ b/gcc/testsuite/gcc.dg/spellcheck-inttypes.c @@ -0,0 +1,32 @@ +/* { dg-options "-std=c99" } */ +#include <stdio.h> +#include <stdint.h> +/* Missing <inttypes.h>. */ + +int8_t i8; +int16_t i16; +int32_t i32; +int64_t i64; + +intptr_t ip; +uintptr_t up; + +/* As an identifier. */ +const char *hex64_fmt = PRIx64; /* { dg-error "'PRIx64' undeclared" "undeclared identifier" { target *-*-* } } */ +/* { dg-message "'PRIx64' is defined in header '<inttypes.h>'; did you forget to '#include <inttypes.h>'?" "replacement note" { target *-*-* } .-1 } */ + +/* As a part of a string-literal. */ +const char *msg_fmt = "Provide %" PRIx32 "\n"; /* { dg-error "expected" "expected string-literal" { target *-*-* } } */ +/* { dg-message "'PRIx32' is defined in header '<inttypes.h>'; did you forget to '#include <inttypes.h>'?" "replacement note" { target *-*-* } .-1 } */ + +void test_printf (void) +{ + printf ("%" PRId8 "\n", i8); /* { dg-error "expected" } */ +/* { dg-message "'PRId8' is defined in header '<inttypes.h>'; did you forget to '#include <inttypes.h>'?" "replacement note" { target *-*-* } .-1 } */ +} + +void test_scanf (void) +{ + scanf ("%" SCNd8 "\n", &i8); /* { dg-error "expected" } */ +/* { dg-message "'SCNd8' is defined in header '<inttypes.h>'; did you forget to '#include <inttypes.h>'?" "replacement note" { target *-*-* } .-1 } */ +} -- 2.20.1