From a8c1bc88df1ad43ec94b6e9cbfeb819fef5b2846 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Wed, 31 Jan 2018 16:17:38 -0500
Subject: [PATCH 25/49] FIXME: Support string locations for C++ in -Wformat

-Wformat in the C++ FE doesn't work as well as it could:
(a) we're not reporting precise locations within the string literal, and
(b) we're not underlining arguments for those arguments !CAN_HAVE_LOCATION_P,
    despite having location wrapper nodes.

(a) is due to not wiring up the langhook for extracting substring
locations.  This patch uses the one in c-family, which fixes the simple case,
with other cases falling back to the current behavior:
- string concatenations aren't recorded due to the use of
  C_LEX_STRING_NO_JOIN in cp_lexer_get_preprocessor_token.  I have a patch
  for this, but it's more of a stretch.
- macros aren't handled (again, falling back to the existing behavior);
  I've added a reproducer for this (Wformat-pr68052.C), capturing the
  behavior.

(b) is FIXME

gcc/cp/ChangeLog:
	PR c++/56856
	PR c++/68052
	PR c++/71863
	* call.c (build_over_call): Build a vec of locations of the
	arguments before the call to maybe_constant_value, and pass to
	check_function_arguments.
	* cp-lang.c (LANG_HOOKS_GET_SUBSTRING_LOCATION): Define as
	c_get_substring_location.

gcc/testsuite/ChangeLog:
	PR c++/56856
	PR c++/68052
	PR c++/71863
	* g++.dg/ext/builtin4.C: Set expected location for warning
	to the correct location within the format string.
	* g++.dg/warn/Wformat-method.C: New test.
	* g++.dg/warn/Wformat-pr68052.C: New test.
	* g++.dg/warn/Wformat-pr71863.C: New test.
---
 gcc/cp/call.c                               |  5 +++-
 gcc/cp/cp-lang.c                            |  3 +++
 gcc/testsuite/g++.dg/ext/builtin4.C         |  2 +-
 gcc/testsuite/g++.dg/warn/Wformat-method.C  | 36 +++++++++++++++++++++++++++++
 gcc/testsuite/g++.dg/warn/Wformat-pr68052.C | 18 +++++++++++++++
 gcc/testsuite/g++.dg/warn/Wformat-pr71863.C | 27 ++++++++++++++++++++++
 6 files changed, 89 insertions(+), 2 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/warn/Wformat-method.C
 create mode 100644 gcc/testsuite/g++.dg/warn/Wformat-pr68052.C
 create mode 100644 gcc/testsuite/g++.dg/warn/Wformat-pr71863.C

diff --git a/gcc/cp/call.c b/gcc/cp/call.c
index b1709cb..942fe94 100644
--- a/gcc/cp/call.c
+++ b/gcc/cp/call.c
@@ -8198,6 +8198,7 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
     {
       tree *fargs = (!nargs ? argarray
 			    : (tree *) alloca (nargs * sizeof (tree)));
+      auto_vec<location_t> arglocs (nargs);
       for (j = 0; j < nargs; j++)
 	{
 	  /* For -Wformat undo the implicit passing by hidden reference
@@ -8207,10 +8208,12 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
 	    fargs[j] = TREE_OPERAND (argarray[j], 0);
 	  else
 	    fargs[j] = maybe_constant_value (argarray[j]);
+	  fargs[j] = maybe_constant_value (argarray[j]);
+	  arglocs.quick_push (EXPR_LOC_OR_LOC (argarray[j], input_location));
 	}
 
       warned_p = check_function_arguments (input_location, fn, TREE_TYPE (fn),
-					   nargs, fargs, NULL);
+					   nargs, fargs, &arglocs);
     }
 
   if (DECL_INHERITED_CTOR (fn))
diff --git a/gcc/cp/cp-lang.c b/gcc/cp/cp-lang.c
index 26a1e6d..a0b0102 100644
--- a/gcc/cp/cp-lang.c
+++ b/gcc/cp/cp-lang.c
@@ -83,6 +83,9 @@ static tree cxx_enum_underlying_base_type (const_tree);
 #define LANG_HOOKS_RUN_LANG_SELFTESTS selftest::run_cp_tests
 #endif /* #if CHECKING_P */
 
+#undef LANG_HOOKS_GET_SUBSTRING_LOCATION
+#define LANG_HOOKS_GET_SUBSTRING_LOCATION c_get_substring_location
+
 /* Each front end provides its own lang hook initializer.  */
 struct lang_hooks lang_hooks = LANG_HOOKS_INITIALIZER;
 
diff --git a/gcc/testsuite/g++.dg/ext/builtin4.C b/gcc/testsuite/g++.dg/ext/builtin4.C
index 8804b53..6691434 100644
--- a/gcc/testsuite/g++.dg/ext/builtin4.C
+++ b/gcc/testsuite/g++.dg/ext/builtin4.C
@@ -6,5 +6,5 @@
 extern "C" int printf(const char*,...);
 
 void foo() {
-  printf("%d"); 		// { dg-warning "expects a matching" }
+  printf("%d"); 		// { dg-warning "12: expects a matching" }
 }
diff --git a/gcc/testsuite/g++.dg/warn/Wformat-method.C b/gcc/testsuite/g++.dg/warn/Wformat-method.C
new file mode 100644
index 0000000..18bba0b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wformat-method.C
@@ -0,0 +1,36 @@
+// { dg-options "-Wformat -fdiagnostics-show-caret" }
+
+class logger
+{
+public:
+  void log (const char *fmt, ...) __attribute__((format (printf, 2, 3)));
+};
+
+void test ()
+{
+  logger out;
+  out.log ("before %s after", 42); // { dg-warning "argument 3 has type 'int'" }
+  /* { dg-begin-multiline-output "" }
+   out.log ("before %s after", 42);
+                    ~^         ~~
+                    %d
+   { dg-end-multiline-output "" } */
+}
+
+template <typename T>
+class logger_2
+{
+public:
+  void log (const char *fmt, ...) __attribute__((format (printf, 2, 3)));
+};
+
+void test_2 ()
+{
+  logger_2<int> out;
+  out.log ("before %s after", 42); // { dg-warning "argument 3 has type 'int'" }
+  /* { dg-begin-multiline-output "" }
+   out.log ("before %s after", 42);
+                    ~^         ~~
+                    %d
+   { dg-end-multiline-output "" } */
+}
diff --git a/gcc/testsuite/g++.dg/warn/Wformat-pr68052.C b/gcc/testsuite/g++.dg/warn/Wformat-pr68052.C
new file mode 100644
index 0000000..47f5674
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wformat-pr68052.C
@@ -0,0 +1,18 @@
+// { dg-options "-Wformat -fdiagnostics-show-caret" }
+
+#define MY_PRINTF(x, ...) __builtin_printf(x, ## __VA_ARGS__)
+
+int main(void) {
+  long long ll = 0;
+  MY_PRINTF("before %ld after", ll); // { dg-warning "argument 2 has type 'long long int'" }
+  // TODO: ideally we would underline within the format string here:
+  /* { dg-begin-multiline-output "" }
+   MY_PRINTF("before %ld after", ll);
+             ^~~~~~~~~~~~~~~~~~  ~~
+     { dg-end-multiline-output "" } */
+  /* { dg-begin-multiline-output "" }
+ #define MY_PRINTF(x, ...) __builtin_printf(x, ## __VA_ARGS__)
+                                            ^
+     { dg-end-multiline-output "" } */
+    return 0;
+}
diff --git a/gcc/testsuite/g++.dg/warn/Wformat-pr71863.C b/gcc/testsuite/g++.dg/warn/Wformat-pr71863.C
new file mode 100644
index 0000000..a88fef2
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wformat-pr71863.C
@@ -0,0 +1,27 @@
+// { dg-options "-Wformat -fdiagnostics-show-caret" }
+
+void test_1 (void)
+{
+  __builtin_printf ("%s%s", 42, 43); // { dg-warning "argument 2 has type 'int'" }
+  // { dg-warning "argument 3 has type 'int'" "" { target *-*-* } .-1 }
+  /* { dg-begin-multiline-output "" }
+   __builtin_printf ("%s%s", 42, 43);
+                      ~^     ~~
+                      %d
+     { dg-end-multiline-output "" } */
+  /* { dg-begin-multiline-output "" }
+   __builtin_printf ("%s%s", 42, 43);
+                        ~^       ~~
+                        %d
+     { dg-end-multiline-output "" } */
+}
+
+void test_2 (void)
+{
+  __builtin_printf ("before %s after", 6 * 7); // { dg-warning "argument 2 has type 'int'" }
+  /* { dg-begin-multiline-output "" }
+   __builtin_printf ("before %s after", 6 * 7);
+                             ~^         ~~~~~
+                             %d
+     { dg-end-multiline-output "" } */
+}
-- 
1.8.5.3

