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