From dd26762d27630b9900f9267ac01c3a9647d0375f Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 16 Jun 2016 04:12:38 -0400
Subject: [PATCH 10/31] FIXME: WIP on fixits for -Wformat

---
 gcc/c-family/c-common.h                         |   7 +
 gcc/c-family/c-format.c                         | 209 +++++++++++++++++++++++-
 gcc/c/c-lang.c                                  |  22 +++
 gcc/langhooks-def.h                             |   4 +-
 gcc/langhooks.h                                 |   3 +
 gcc/selftest-run-tests.c                        |   5 +
 gcc/selftest.c                                  |   4 +
 gcc/testsuite/gcc.dg/format/diagnostic-ranges.c |  25 ++-
 8 files changed, 269 insertions(+), 10 deletions(-)

diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 4e6aa00..facacbe 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -1492,4 +1492,11 @@ extern bool valid_array_size_p (location_t, tree, tree);
 extern bool cilk_ignorable_spawn_rhs_op (tree);
 extern bool cilk_recognize_spawn (tree, tree *);
 
+#if CHECKING_P
+namespace selftest {
+  extern void c_format_c_tests (void);
+  extern void run_c_tests (void);
+} // namespace selftest
+#endif /* #if CHECKING_P */
+
 #endif /* ! GCC_C_COMMON_H */
diff --git a/gcc/c-family/c-format.c b/gcc/c-family/c-format.c
index 63d025b..d9bc0ba 100644
--- a/gcc/c-family/c-format.c
+++ b/gcc/c-family/c-format.c
@@ -29,6 +29,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "intl.h"
 #include "langhooks.h"
 #include "c-format.h"
+#include "selftest.h"
 
 /* Handle attributes associated with format checking.  */
 
@@ -1034,11 +1035,13 @@ static const format_flag_spec *get_flag_spec (const format_flag_spec *,
 					      int, const char *);
 
 static void check_format_types (source_range fmt_source_range,
-				format_wanted_type *);
+				format_wanted_type *,
+				const format_kind_info *fki);
 static void format_type_warning (source_range fmt_source_range,
 				 source_range *param_range,
 				 format_wanted_type *, tree,
-				 tree);
+				 tree,
+				 const format_kind_info *fki);
 
 /* Decode a format type from a string, returning the type, or
    format_type_error if not valid, in which case the caller should print an
@@ -2439,7 +2442,7 @@ check_format_info_main (format_check_results *res,
 	  source_range fmt_source_range
 	    = strloc->get_range_between_indices (offset_to_format_start,
 						 offset_to_format_end);
-	  check_format_types (fmt_source_range, first_wanted_type);
+	  check_format_types (fmt_source_range, first_wanted_type, fki);
 	}
     }
 
@@ -2464,7 +2467,8 @@ check_format_info_main (format_check_results *res,
    location of the format string.  */
 static void
 check_format_types (source_range fmt_source_range,
-		    format_wanted_type *types)
+		    format_wanted_type *types,
+		    const format_kind_info *fki)
 {
   for (; types != 0; types = types->next)
     {
@@ -2491,7 +2495,8 @@ check_format_types (source_range fmt_source_range,
       cur_param = types->param;
       if (!cur_param)
         {
-          format_type_warning (fmt_source_range, NULL, types, wanted_type, NULL);
+          format_type_warning (fmt_source_range, NULL, types, wanted_type, NULL,
+			       fki);
           continue;
         }
 
@@ -2576,7 +2581,7 @@ check_format_types (source_range fmt_source_range,
 	  else
 	    {
               format_type_warning (fmt_source_range, param_range_ptr, types,
-				   wanted_type, orig_cur_type);
+				   wanted_type, orig_cur_type, fki);
 	      break;
 	    }
 	}
@@ -2645,10 +2650,113 @@ check_format_types (source_range fmt_source_range,
 	continue;
       /* Now we have a type mismatch.  */
       format_type_warning (fmt_source_range, param_range_ptr, types,
-			   wanted_type, orig_cur_type);
+			   wanted_type, orig_cur_type, fki);
     }
 }
 
+/* FIXME.  */
+
+static tree
+deref_n_times (tree type, int n)
+{
+  gcc_assert (type);
+
+  for (int i = n; i > 0; i--)
+    {
+      if (TREE_CODE (type) != POINTER_TYPE)
+	return NULL_TREE;
+      type = TREE_TYPE (type);
+    }
+  /* Strip off any "const" etc.  */
+  return build_qualified_type (type, 0);
+}
+
+/* FIXME.  */
+#if 0
+static const format_length_info printf_length_specs[] =
+{
+  { "h", FMT_LEN_h, STD_C89, "hh", FMT_LEN_hh, STD_C99, 0 },
+  { "l", FMT_LEN_l, STD_C89, "ll", FMT_LEN_ll, STD_C9L, 0 },
+  { "q", FMT_LEN_ll, STD_EXT, NO_FMT, 0 },
+  { "L", FMT_LEN_L, STD_C89, NO_FMT, 0 },
+  { "z", FMT_LEN_z, STD_C99, NO_FMT, 0 },
+  { "Z", FMT_LEN_z, STD_EXT, NO_FMT, 0 },
+  { "t", FMT_LEN_t, STD_C99, NO_FMT, 0 },
+  { "j", FMT_LEN_j, STD_C99, NO_FMT, 0 },
+  { "H", FMT_LEN_H, STD_EXT, NO_FMT, 0 },
+  { "D", FMT_LEN_D, STD_EXT, "DD", FMT_LEN_DD, STD_EXT, 0 },
+  { NO_FMT, NO_FMT, 0 }
+};
+#endif
+static const char *
+get_modifier_for_format_len (const format_length_info *fli,
+			     enum format_lengths format_len)
+{
+  for (; fli->name; fli++)
+    {
+      if (fli->index == format_len)
+	return fli->name;
+      if (fli->double_index == format_len)
+	return fli->double_name;
+    }
+  return NULL;
+}
+
+/* FIXME.  */
+
+static char *
+get_format_for_type (const format_kind_info *fki, tree arg_type)
+{
+  gcc_assert (arg_type);
+
+  const format_char_info *spec;
+  for (spec = &fki->conversion_specs[0];
+       spec->format_chars;
+       spec++)
+    {
+      tree effective_arg_type = deref_n_times (arg_type,
+					       spec->pointer_count);
+      if (!effective_arg_type)
+	continue;
+      for (int i = 0; i < FMT_LEN_MAX; i++)
+	{
+	  const format_type_detail *ftd = &spec->types[i];
+	  if (!ftd->type)
+	    continue;
+	  if (TYPE_CANONICAL (*ftd->type)
+	      == TYPE_CANONICAL (effective_arg_type))
+	    {
+	      /* FIXME: need a % prefix, and maybe other prefix. */
+	      const char *len_modifier
+		= get_modifier_for_format_len (fki->length_char_specs,
+					       (enum format_lengths)i);
+	      if (!len_modifier)
+		len_modifier = "";
+#if 0
+static const format_char_info print_char_table[] =
+{
+  /* C89 conversion specifiers.  */
+  { "di",  0, STD_C89, { T89_I,   T99_SC,  T89_S,   T89_L,   T9L_LL,  TEX_LL,  T99_SST, T99_PD,  T99_IM,  BADLEN,  BADLEN,  BADLEN  }, "-wp0 +'I",  "i",  NULL },
+
+#define T_I	&integer_type_node
+#define T89_I	{ STD_C89, NULL, T_I }
+
+	      // TODO: handle length prefixes like:
+static const format_length_info printf_length_specs[] =
+{
+  { "h", FMT_LEN_h, STD_C89, "hh", FMT_LEN_hh, STD_C99, 0 },
+  { "l", FMT_LEN_l, STD_C89, "ll", FMT_LEN_ll, STD_C9L, 0 },
+#endif
+  //fki->length_char_specs[i].name,
+
+	      return xasprintf ("%%%s%c",
+				len_modifier,
+				spec->format_chars[0]);
+	    }
+	}
+   }
+  return NULL;
+}
 
 /* Give a warning at FMT_SOURCE_RANGE about a format argument of different type
    from that expected.  If non-NULL, PARAM_RANGE is the source range of the
@@ -2662,7 +2770,8 @@ static void
 format_type_warning (source_range fmt_source_range,
 		     source_range *param_range,
 		     format_wanted_type *type,
-		     tree wanted_type, tree arg_type)
+		     tree wanted_type, tree arg_type,
+		     const format_kind_info *fki)
 {
   int kind = type->kind;
   const char *wanted_type_name = type->wanted_type_name;
@@ -2715,6 +2824,13 @@ format_type_warning (source_range fmt_source_range,
 					    param_range->m_finish);
       richloc.add_range (param_loc, false);
     }
+  if (arg_type)
+    {
+      char *format_for_type = get_format_for_type (fki, arg_type);
+      if (format_for_type)
+	richloc.add_fixit_replace (fmt_source_range, format_for_type);
+      free (format_for_type);
+    }
 
   if (wanted_type_name)
     {
@@ -3270,3 +3386,80 @@ handle_format_attribute (tree *node, tree ARG_UNUSED (name), tree args,
 
   return NULL_TREE;
 }
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Selftests of location handling.  */
+
+/* Get the format_kind_info with the given name.  */
+
+static const format_kind_info *
+get_info (const char *name)
+{
+  int idx = decode_format_type (name);
+  const format_kind_info *fki = &format_types[idx];
+  ASSERT_STREQ (fki->name, name);
+  return fki;
+}
+
+/* Verify that get_format_for_type (FKI, TYPE) is EXPECTED_FORMAT.  */
+
+static void
+assert_format_for_type_streq (const location &loc, const format_kind_info *fki,
+			      const char *expected_format, tree type)
+{
+  gcc_assert (fki);
+  gcc_assert (expected_format);
+  gcc_assert (type);
+
+  char *actual_format = get_format_for_type (fki, type);
+  ASSERT_STREQ_AT (loc, expected_format, actual_format);
+  free (actual_format);
+}
+
+/* Selftest for get_format_for_type.  */
+
+static void
+test_get_format_for_type ()
+{
+#define ASSERT_FORMAT_FOR_TYPE_STREQ(EXPECTED_FORMAT, TYPE) \
+  assert_format_for_type_streq (SELFTEST_LOCATION, (fki), (EXPECTED_FORMAT), (TYPE))
+
+  const format_kind_info *fki = get_info ("gnu_printf");
+  ASSERT_NE (fki, NULL);
+
+  //ASSERT_FORMAT_FOR_TYPE_STREQ ("%d", float_type_node);
+  ASSERT_FORMAT_FOR_TYPE_STREQ ("%f", double_type_node);
+  ASSERT_FORMAT_FOR_TYPE_STREQ ("%Lf", long_double_type_node);
+  //ASSERT_FORMAT_FOR_TYPE_STREQ ("%c", char_type_node);
+  //ASSERT_FORMAT_FOR_TYPE_STREQ ("%c", short_integer_type_node);
+  ASSERT_FORMAT_FOR_TYPE_STREQ ("%d", integer_type_node);
+  ASSERT_FORMAT_FOR_TYPE_STREQ ("%o", unsigned_type_node);
+  ASSERT_FORMAT_FOR_TYPE_STREQ ("%ld", long_integer_type_node);
+  ASSERT_FORMAT_FOR_TYPE_STREQ ("%lo", long_unsigned_type_node);
+  ASSERT_FORMAT_FOR_TYPE_STREQ ("%lld", long_long_integer_type_node);
+  ASSERT_FORMAT_FOR_TYPE_STREQ ("%llo", long_long_unsigned_type_node);
+  ASSERT_FORMAT_FOR_TYPE_STREQ ("%s", build_pointer_type (char_type_node));
+
+  // TODO: other format kinds
+
+  fki = get_info ("gnu_scanf");
+  ASSERT_NE (fki, NULL);
+  ASSERT_FORMAT_FOR_TYPE_STREQ ("%d", build_pointer_type (integer_type_node));
+
+#undef ASSERT_FORMAT_FOR_TYPE_STREQ
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+c_format_c_tests ()
+{
+  test_get_format_for_type ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/c/c-lang.c b/gcc/c/c-lang.c
index 89954b7..b26be6a 100644
--- a/gcc/c/c-lang.c
+++ b/gcc/c/c-lang.c
@@ -38,7 +38,29 @@ enum c_language_kind c_language = clk_c;
 #undef LANG_HOOKS_INIT_TS
 #define LANG_HOOKS_INIT_TS c_common_init_ts
 
+#if CHECKING_P
+#undef LANG_HOOKS_RUN_LANG_SELFTESTS
+#define LANG_HOOKS_RUN_LANG_SELFTESTS selftest::run_c_tests
+#endif /* #if CHECKING_P */
+
 /* Each front end provides its own lang hook initializer.  */
 struct lang_hooks lang_hooks = LANG_HOOKS_INITIALIZER;
 
+#if CHECKING_P
+
+namespace selftest {
+
+/* Implementation of LANG_HOOKS_RUN_LANG_SELFTESTS for the C frontend.  */
+
+void
+run_c_tests (void)
+{
+  c_format_c_tests ();
+}
+
+} // namespace selftest
+
+#endif /* #if CHECKING_P */
+
+
 #include "gtype-c.h"
diff --git a/gcc/langhooks-def.h b/gcc/langhooks-def.h
index 034b3b7..c17f998 100644
--- a/gcc/langhooks-def.h
+++ b/gcc/langhooks-def.h
@@ -120,6 +120,7 @@ extern bool lhd_omp_mappable_type (tree);
 #define LANG_HOOKS_BLOCK_MAY_FALLTHRU	hook_bool_const_tree_true
 #define LANG_HOOKS_EH_USE_CXA_END_CLEANUP	false
 #define LANG_HOOKS_DEEP_UNSHARING	false
+#define LANG_HOOKS_RUN_LANG_SELFTESTS   lhd_do_nothing
 
 /* Attribute hooks.  */
 #define LANG_HOOKS_ATTRIBUTE_TABLE		NULL
@@ -319,7 +320,8 @@ extern void lhd_end_section (void);
   LANG_HOOKS_EH_PROTECT_CLEANUP_ACTIONS, \
   LANG_HOOKS_BLOCK_MAY_FALLTHRU, \
   LANG_HOOKS_EH_USE_CXA_END_CLEANUP, \
-  LANG_HOOKS_DEEP_UNSHARING \
+  LANG_HOOKS_DEEP_UNSHARING, \
+  LANG_HOOKS_RUN_LANG_SELFTESTS \
 }
 
 #endif /* GCC_LANG_HOOKS_DEF_H */
diff --git a/gcc/langhooks.h b/gcc/langhooks.h
index 0593424..150ddce 100644
--- a/gcc/langhooks.h
+++ b/gcc/langhooks.h
@@ -505,6 +505,9 @@ struct lang_hooks
      gimplification.  */
   bool deep_unsharing;
 
+  /* FIXME.  */
+  void (*run_lang_selftests) (void);
+
   /* Whenever you add entries here, make sure you adjust langhooks-def.h
      and langhooks.c accordingly.  */
 };
diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c
index 934e700..58ecaa7 100644
--- a/gcc/selftest-run-tests.c
+++ b/gcc/selftest-run-tests.c
@@ -21,6 +21,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "system.h"
 #include "coretypes.h"
 #include "selftest.h"
+#include "tree.h"
+#include "langhooks.h"
 
 /* This function needed to be split out from selftest.c as it references
    tests from the whole source tree, and so is within
@@ -66,6 +68,9 @@ selftest::run_tests ()
   /* This one relies on most of the above.  */
   function_tests_c_tests ();
 
+  /* Run any lang-specific selftests.  */
+  lang_hooks.run_lang_selftests ();
+
   /* Finished running tests.  */
   long finish_time = get_run_time ();
   long elapsed_time = finish_time - start_time;
diff --git a/gcc/selftest.c b/gcc/selftest.c
index ed6e517..4f5ea29 100644
--- a/gcc/selftest.c
+++ b/gcc/selftest.c
@@ -67,6 +67,10 @@ selftest::assert_streq (const location &loc,
 			const char *desc_expected, const char *desc_actual,
 			const char *val_expected, const char *val_actual)
 {
+  if (val_actual == NULL)
+    ::selftest::fail_formatted
+	(loc, "ASSERT_STREQ (%s, %s) expected=\"%s\" actual=NULL",
+	 desc_expected, desc_actual, val_expected);
   if (0 == strcmp (val_expected, val_actual))
     ::selftest::pass (loc, "ASSERT_STREQ");
   else
diff --git a/gcc/testsuite/gcc.dg/format/diagnostic-ranges.c b/gcc/testsuite/gcc.dg/format/diagnostic-ranges.c
index c3924ba..5153c3c 100644
--- a/gcc/testsuite/gcc.dg/format/diagnostic-ranges.c
+++ b/gcc/testsuite/gcc.dg/format/diagnostic-ranges.c
@@ -7,11 +7,29 @@ extern int printf (__const char *__restrict __format, ...);
 void test_mismatching_types (const char *msg)
 {
   printf("hello %i", msg);  /* { dg-warning "format '%i' expects argument of type 'int', but argument 2 has type 'const char \\*' " } */
-
 /* TODO: ideally would also underline "msg".  */
 /* { dg-begin-multiline-output "" }
    printf("hello %i", msg);
                  ^~
+                 %s
+   { dg-end-multiline-output "" } */
+
+
+  printf("hello %s", 42);  /* { dg-warning "format '%s' expects argument of type 'char \\*', but argument 2 has type 'int'" } */
+/* TODO: ideally would also underline "42".  */
+/* { dg-begin-multiline-output "" }
+   printf("hello %s", 42);
+                 ^~
+                 %d
+   { dg-end-multiline-output "" } */
+
+
+  printf("hello %i", (long)0);  /* { dg-warning "format '%i' expects argument of type 'int', but argument 2 has type 'long int' " } */
+/* TODO: ideally would also underline the argument.  */
+/* { dg-begin-multiline-output "" }
+   printf("hello %i", (long)0);
+                 ^~
+                 %ld
    { dg-end-multiline-output "" } */
 }
 
@@ -23,6 +41,7 @@ void test_multiple_arguments (void)
 /* { dg-begin-multiline-output "" }
    printf ("arg0: %i  arg1: %s arg 2: %i",
                             ^~
+                            %d
    { dg-end-multiline-output "" } */
 }
 
@@ -50,6 +69,7 @@ void test_hex (const char *msg)
 /* { dg-begin-multiline-output "" }
    printf("hello \x25\x69", msg);
                  ^~~~~~~~
+                 %s
    { dg-end-multiline-output "" } */
 }
 
@@ -63,6 +83,7 @@ void test_oct (const char *msg)
 /* { dg-begin-multiline-output "" }
    printf("hello \045\151", msg);
                  ^~~~~~~~
+                 %s
    { dg-end-multiline-output "" } */
 }
 
@@ -77,6 +98,7 @@ void test_multiple (const char *msg)
 /* { dg-begin-multiline-output "" }
    printf("prefix"  "\x25"  "\151"  "suffix",
                      ^~~~~~~~~~~~
+                     %s
   { dg-end-multiline-output "" } */
 }
 
@@ -87,6 +109,7 @@ void test_u8 (const char *msg)
 /* { dg-begin-multiline-output "" }
    printf(u8"hello %i", msg);
                    ^~
+                   %s
    { dg-end-multiline-output "" } */
 }
 
-- 
1.8.5.3

