RFC: Provide diagnostic hints for missing inttypes.h string constants.
Mark Wielaard
mark@klomp.org
Thu May 21 23:30:42 GMT 2020
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
More information about the Gcc-patches
mailing list