Attached is a more fleshed out version of the built-in implemented
to run in a pass of its own.  I did this in anticipation of looking
at the CFG to help eliminate false positives due to ASAN
instrumentation (e.g., PR 91707).

The built-in now handles a decent number of C and GCC formatting
directives.

The patch introduces a convenience API to create calls to the built-in
(gimple_build_warning).  It also avoids duplicate warnings emitted as
a result of redundant calls to the built-in for the same code (e.g.,
by different passes detecting the same out-of-bounds access).

To show how to use the built-in and the APIs within GCC the patch
modifies path isolation and CCP to inject calls to it into the CFG.
A couple of new tests exercise using the built-in from user code.

The patch triggers a number of -Wnonnull instances during bootstrap
and failures in tests that exercise the warnings modified by using
the built-in.  The GCC warnings are mostly potential bugs that
I will submit patches for, but they're in general unrelated to
the built-in itself.

At this point I want to know if there is support for a) including
the built-in in GCC 10, b) the path isolation changes to make use
of it, and c) the CCP -Wnonnull changes.  If (a), I will submit
a final patch in the next few weeks.  If also (b) and/or (c)
I will also work on cleaning up the GCC warnings.

Martin

PS The patch introduces a general mechanism for processing vararg
formatting functions.  It's only used to handle the built-in but
once it's accepted I expect to replace the gimple-ssa-printf.c
parser with it.

On 8/9/19 3:26 PM, Martin Sebor wrote:
Attached is a very rough and only superficially barely tested
prototype of the __builtin_warning intrinsic we talked about
the other day.  The built-in is declared like so:

   int __builtin_warning (int loc,
                          const char *option,
                          const char *txt, ...);

If it's still present during RTL expansion the built-in calls

   bool ret = warning_at (LOC, find_opt (option), txt, ...);

and expands to the constant value of RET (which could be used
to issue __builtin_note but there may be better ways to deal
with those than that).

LOC is the location of the warning or zero for the location of
the built-in itself (when called by user code), OPTION is either
the name of the warning option (e.g., "-Wnonnull", when called
by user code) or the index of the option (e.g., OPT_Wnonnull,
when emitted by GCC into the IL), and TXT is the format string
to format the warning text.  The rest of the arguments are not
used but I expect to extract and pass them to the diagnostic
pretty printer to format the text of the warning.

Using the built-in in user code should be obvious.  To show
how it might be put to use within GCC, I added code to
gimple-ssa-isolate-paths.c to emit -Wnonnull in response to
invalid null pointer accesses.  For this demo, when compiled
with the patch applied and with -Wnull-dereference (which is
not in -Wall or -Wextra), GCC issues three warnings: two
instances of -Wnull-dereference one of which is a false positive,
and one -Wnonnull (the one I added, which is included in -Wall),
which is a true positive:

   int f (void)
   {
     char a[4] = "12";
     char *p = __builtin_strlen (a) < 3 ? a : 0;
     return *p;
   }

   int g (void)
   {
     char a[4] = "12";
     char *p = __builtin_strlen (a) > 3 ? a : 0;
     return *p;
   }

   In function ‘f’:
   warning: potential null pointer dereference [-Wnull-dereference]
     7 |   return *p;
       |          ^~
   In function ‘g’:
   warning: null pointer dereference [-Wnull-dereference]
    14 |   return *p;
       |          ^~
   warning: invalid use of a null pointer [-Wnonnull]

The -Wnull-dereference instance in f is a false positive because
the strlen result is clearly less than two.  The strlen pass folds
the strlen result to a constant but it runs after path isolation
which will have already issued the bogus warning.

Martin

PS I tried compiling GCC with the patch.  It fails in libgomp
with:

   libgomp/oacc-mem.c: In function ‘gomp_acc_remove_pointer’:
   cc1: warning: invalid use of a null pointer [-Wnonnull]

so clearly it's missing location information.  With
-Wnull-dereference enabled we get more detail:

   libgomp/oacc-mem.c: In function ‘gomp_acc_remove_pointer’:
  libgomp/oacc-mem.c:1013:31: warning: potential null pointer dereference [-Wnull-dereference]
    1013 |       for (size_t i = 0; i < t->list_count; i++)
         |                              ~^~~~~~~~~~~~
  libgomp/oacc-mem.c:1012:19: warning: potential null pointer dereference [-Wnull-dereference]
    1012 |       t->refcount = minrefs;
         |       ~~~~~~~~~~~~^~~~~~~~~
  libgomp/oacc-mem.c:1013:31: warning: potential null pointer dereference [-Wnull-dereference]
    1013 |       for (size_t i = 0; i < t->list_count; i++)
         |                              ~^~~~~~~~~~~~
  libgomp/oacc-mem.c:1012:19: warning: potential null pointer dereference [-Wnull-dereference]
    1012 |       t->refcount = minrefs;
         |       ~~~~~~~~~~~~^~~~~~~~~
   cc1: warning: invalid use of a null pointer [-Wnonnull]

I didn't spend too long examining the code but it seems like
the warnings might actually be justified.  When the first loop
terminates with t being null the subsequent dereferences are
invalid:

       if (t->refcount == minrefs)
     {
       /* This is the last reference, so pull the descriptor off the
          chain. This prevents gomp_unmap_vars via gomp_unmap_tgt from
          freeing the device memory. */
       struct target_mem_desc *tp;
       for (tp = NULL, t = acc_dev->openacc.data_environ; t != NULL;
            tp = t, t = t->prev)
         {
           if (n->tgt == t)
         {
           if (tp)
             tp->prev = t->prev;
           else
             acc_dev->openacc.data_environ = t->prev;
           break;
         }
         }
     }

       /* Set refcount to 1 to allow gomp_unmap_vars to unmap it.  */
       n->refcount = 1;
       t->refcount = minrefs;
       for (size_t i = 0; i < t->list_count; i++)


gcc/c/ChangeLog:

	* c-objc-common.c (c_tree_printer): Fall back on provided location
	when the argument has none.

gcc/testsuite/ChangeLog:

	* gcc.dg/builtin-warning-2.c: New test.
	* gcc.dg/builtin-warning.c: New test.
	* gcc.dg/tree-ssa/builtin-fprintf-warn-1.c: Remove a test.
	* gcc.dg/tree-ssa/isolate-1.c (d_type): Xfail a test.
	* gcc.dg/tree-ssa/user-printf-warn-1.c: Use -O2.  Split up tests
	to avoid path isolation from interfering.
	* gcc.dg/tree-ssa/wnull-dereference.c: Xfail a test.

gcc/ChangeLog:

	* Makefile.in (OBJS): Add gimple-ssa-builtin-warning.o.
	* builtin-types.def (DEF_FUNCTION_TYPE_VAR_3): New.
	* builtins.c (warn_string_no_nul): Set no-warning bit on argument
	of a cast.
	(expand_builtin_strcpy_args): Remove redundat check for no-warning.
	(expand_builtin_stpcpy_1): Same.
	(expand_builtin): Handle BUILT_IN_WARNING.
	(expand_builtin_warning): New function.
	(is_simple_builtin): Handle BUILT_IN_WARNING.
	* builtins.def (BUILT_IN_WARNING): New built-in.
	* doc/extend.texi (__builtin_warning): Document.
	* gimple-ssa-builtin-warning.c: New file.
	* gimple-ssa-isolate-paths.c (build_nonnull_warning): New function.
	(stmt_uses_name_in_undefined_way): Add an argument.  Build a warning
	call.
	(stmt_uses_0_or_null_in_undefined_way): Same.
	(handle_return_addr_local_phi_arg): Insert a warning call.
	(find_implicit_erroneous_behavior): Same.
	(find_explicit_erroneous_behavior): Same.
	* gimple.c (directive_to_tree, gimple_build_warning): New functions.
	(infer_nonnull_range_by_attribute): Add an optional argument.  Return
	a bitmap of nonnull arguments.
	* gimple.h (infer_nonnull_range_by_attribute): Add an optional argument.
	* passes.def (pass_builtin_warning): Add pass_builtin_warning.
	* tree-pass.h (make_pass_builtin_warning): Declare.
	* tree-ssa-ccp.c (pass_post_ipa_warn::execute): Insert a warning call
	into the CFG.
	* tree.c (build_common_builtin_nodes): Build BUILT_IN_WARNING.
	(format_directive_t::set_flags): Define member function.
	(format_directive_t::set_modifier): Same.
	(format_directive_t::set_width_or_precision): Same.
	(format_directive_t::to_tree): Same.
	(parse_format_string): New function.
	(build_builtin_warning): New function.
	* tree.h (struct format_directive_t): Define.
	(parse_format_string): Declare.
	(build_builtin_warning): Declare.

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index c82858fa93e..591a2c8bf3f 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1336,6 +1336,7 @@ OBJS = \
 	gimple-ssa-sprintf.o \
 	gimple-ssa-warn-alloca.o \
 	gimple-ssa-warn-restrict.o \
+	gimple-ssa-builtin-warning.o \
 	gimple-streamer-in.o \
 	gimple-streamer-out.o \
 	gimple-walk.o \
diff --git a/gcc/builtin-types.def b/gcc/builtin-types.def
index e5c9e063c48..d327f2a1cd6 100644
--- a/gcc/builtin-types.def
+++ b/gcc/builtin-types.def
@@ -811,6 +811,7 @@ DEF_FUNCTION_TYPE_VAR_3 (BT_FN_SSIZE_STRING_SIZE_CONST_STRING_VAR,
 			 BT_SSIZE, BT_STRING, BT_SIZE, BT_CONST_STRING)
 DEF_FUNCTION_TYPE_VAR_3 (BT_FN_INT_FILEPTR_INT_CONST_STRING_VAR,
 			 BT_INT, BT_FILEPTR, BT_INT, BT_CONST_STRING)
+DEF_FUNCTION_TYPE_VAR_3 (BT_FN_INT_INT_CONST_STRING_CONST_STRING, BT_INT, BT_INT, BT_CONST_STRING, BT_CONST_STRING)
 
 DEF_FUNCTION_TYPE_VAR_4 (BT_FN_INT_STRING_INT_SIZE_CONST_STRING_VAR,
 			 BT_INT, BT_STRING, BT_INT, BT_SIZE, BT_CONST_STRING)
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 5d811f113c9..9e885d75451 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -73,6 +73,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "gomp-constants.h"
 #include "omp-general.h"
 #include "tree-dfa.h"
+#include "opts.h"
 
 struct target_builtins default_target_builtins;
 #if SWITCHABLE_TARGET
@@ -180,6 +181,7 @@ static tree fold_builtin_strcspn (location_t, tree, tree);
 static rtx expand_builtin_object_size (tree);
 static rtx expand_builtin_memory_chk (tree, rtx, machine_mode,
 				      enum built_in_function);
+static rtx expand_builtin_warning (tree);
 static void maybe_emit_chk_warning (tree, enum built_in_function);
 static void maybe_emit_sprintf_chk_warning (tree, enum built_in_function);
 static void maybe_emit_free_warning (tree);
@@ -562,6 +564,14 @@ warn_string_no_nul (location_t loc, const char *fn, tree arg, tree decl)
       inform (DECL_SOURCE_LOCATION (decl),
 	      "referenced argument declared here");
       TREE_NO_WARNING (arg) = 1;
+      if (TREE_CODE (arg) == NOP_EXPR)
+	{
+	  /* Also set no-warning on the operand of a cast since it's likely
+	     going to get stripped.  */
+	  tree op0 = TREE_OPERAND (arg, 0);
+	  if (!DECL_P (op0))
+	    TREE_NO_WARNING (op0) = 1;
+	}
     }
 }
 
@@ -4092,8 +4102,7 @@ expand_builtin_strcpy_args (tree exp, tree dest, tree src, rtx target)
   if (tree nonstr = unterminated_array (src))
     {
       /* NONSTR refers to the non-nul terminated constant array.  */
-      if (!TREE_NO_WARNING (exp))
-	warn_string_no_nul (EXPR_LOCATION (exp), "strcpy", src, nonstr);
+      warn_string_no_nul (EXPR_LOCATION (exp), "strcpy", src, nonstr);
       return NULL_RTX;
     }
 
@@ -4146,7 +4155,7 @@ expand_builtin_stpcpy_1 (tree exp, rtx target, machine_mode mode)
 	return expand_movstr (dst, src, target,
 			      /*retmode=*/ RETURN_END_MINUS_ONE);
 
-      if (lendata.decl && !TREE_NO_WARNING (exp))
+      if (lendata.decl)
 	warn_string_no_nul (EXPR_LOCATION (exp), "stpcpy", src, lendata.decl);
 
       lenp1 = size_binop_loc (loc, PLUS_EXPR, len, ssize_int (1));
@@ -8393,6 +8402,9 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
       mode = get_builtin_sync_mode (fcode - BUILT_IN_SPECULATION_SAFE_VALUE_1);
       return expand_speculation_safe_value (mode, exp, target, ignore);
 
+    case BUILT_IN_WARNING:
+      return expand_builtin_warning (exp);
+
     default:	/* just do library call, if unknown builtin */
       break;
     }
@@ -10530,6 +10542,14 @@ expand_builtin_memory_chk (tree exp, rtx target, machine_mode mode,
     }
 }
 
+/* Expand a call to __builtin_warning() to the constant 1.  */
+
+static rtx
+expand_builtin_warning (tree)
+{
+  return const1_rtx;
+}
+
 /* Emit warning if a buffer overflow is detected at compile time.  */
 
 static void
@@ -11181,6 +11201,8 @@ is_simple_builtin (tree decl)
       case BUILT_IN_EH_FILTER:
       case BUILT_IN_EH_POINTER:
       case BUILT_IN_EH_COPY_VALUES:
+	/* Trivially constant.  */
+      case BUILT_IN_WARNING:
 	return true;
 
       default:
diff --git a/gcc/builtins.def b/gcc/builtins.def
index d8233f5f760..d878010c147 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -1049,6 +1049,11 @@ DEF_GCC_BUILTIN (BUILT_IN_FILE, "FILE", BT_FN_CONST_STRING, ATTR_NOTHROW_LEAF_LI
 DEF_GCC_BUILTIN (BUILT_IN_FUNCTION, "FUNCTION", BT_FN_CONST_STRING, ATTR_NOTHROW_LEAF_LIST)
 DEF_GCC_BUILTIN (BUILT_IN_LINE, "LINE", BT_FN_INT, ATTR_NOTHROW_LEAF_LIST)
 
+/* __builtin_warning */
+DEF_GCC_BUILTIN (BUILT_IN_WARNING, "warning",
+		BT_FN_INT_INT_CONST_STRING_CONST_STRING,
+		ATTR_NOTHROW_NONNULL_LEAF)
+
 /* Synchronization Primitives.  */
 #include "sync-builtins.def"
 
diff --git a/gcc/c/c-objc-common.c b/gcc/c/c-objc-common.c
index e1f3b2ee436..8d44d736f96 100644
--- a/gcc/c/c-objc-common.c
+++ b/gcc/c/c-objc-common.c
@@ -220,7 +220,10 @@ c_tree_printer (pretty_printer *pp, text_info *text, const char *spec,
   if (*spec == 'K')
     {
       t = va_arg (*text->args_ptr, tree);
-      percent_K_format (text, EXPR_LOCATION (t), TREE_BLOCK (t));
+      location_t loc = EXPR_LOCATION (t);
+      if (loc == UNKNOWN_LOCATION)
+	loc = text->m_richloc->get_loc ();
+      percent_K_format (text, loc, TREE_BLOCK (t));
       return true;
     }
 
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 7e37c5cc2e6..ba779d1848a 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -11985,6 +11985,7 @@ is called and the @var{flag} argument passed to it.
 @findex __builtin_powif
 @findex __builtin_powil
 @findex __builtin_speculation_safe_value
+@findex __builtin_warning
 @findex _Exit
 @findex _exit
 @findex abort
@@ -13253,6 +13254,15 @@ Returns the size of an object pointed to by @var{ptr}.  @xref{Object Size
 Checking}, for a detailed description of the function.
 @end deftypefn
 
+@deftypefn {Built-in Function}{int} __builtin_warning (int @var{reserved}, const char * @var{option}, const char @var{fmt}, ...)
+If warning @var{option} is enabled, formats a diagnostic message and issues
+the requested warning.  The @var{fmt} string may contain the same set of
+directives as the @code{fprintf} format string.  Not all directives are
+fully supported and result in unspecified output.  Since calls to
+the built-in are subject to dead code elimination, warnings are only issued
+if the call is not eliminated.
+@end deftypefn
+
 @deftypefn {Built-in Function} double __builtin_huge_val (void)
 Returns a positive infinity, if supported by the floating-point format,
 else @code{DBL_MAX}.  This function is suitable for implementing the
diff --git a/gcc/gimple-ssa-builtin-warning.c b/gcc/gimple-ssa-builtin-warning.c
new file mode 100644
index 00000000000..5a6eb3356e7
--- /dev/null
+++ b/gcc/gimple-ssa-builtin-warning.c
@@ -0,0 +1,376 @@
+/* Pass to issue warnings injected by the __builtin_warning intrinsic.
+
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   Contributed by Martin Sebor <mse...@redhat.com>.
+
+   This file is part of GCC.
+
+   GCC is free software; you can redistribute it and/or modify it under
+   the terms of the GNU General Public License as published by the Free
+   Software Foundation; either version 3, or (at your option) any later
+   version.
+
+   GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or
+   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+   for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with GCC; see the file COPYING3.  If not see
+   <http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "domwalk.h"
+#include "tree-pass.h"
+#include "builtins.h"
+#include "ssa.h"
+#include "gimple-pretty-print.h"
+#include "gimple-ssa-warn-restrict.h"
+#include "diagnostic-core.h"
+#include "diagnostic-color.h"
+#include "fold-const.h"
+#include "gimple-iterator.h"
+#include "tree-dfa.h"
+#include "tree-ssa.h"
+#include "params.h"
+#include "tree-cfg.h"
+#include "tree-object-size.h"
+#include "calls.h"
+#include "cfgloop.h"
+#include "intl.h"
+
+#include "opts.h"
+#include "rtl.h"
+#include "expr.h"
+#include "pretty-print.h"
+#include "hash-set.h"
+
+namespace {
+
+const pass_data pass_data_builtin_warning = {
+  GIMPLE_PASS,
+  "builtin-warning",
+  OPTGROUP_NONE,
+  TV_NONE,
+  PROP_cfg, /* Properties_required.  */
+  0,	    /* properties_provided.  */
+  0,	    /* properties_destroyed.  */
+  0,	    /* properties_start */
+  0,	    /* properties_finish */
+};
+
+/* Pass to detect violations of strict aliasing requirements in calls
+   to built-in string and raw memory functions.  */
+class pass_builtin_warning : public gimple_opt_pass
+{
+ public:
+  pass_builtin_warning (gcc::context *ctxt)
+    : gimple_opt_pass (pass_data_builtin_warning, ctxt)
+    { }
+
+  opt_pass *clone () { return new pass_builtin_warning (m_ctxt); }
+
+  virtual bool gate (function *) { return true; }
+  virtual unsigned int execute (function *);
+};
+
+/* Class to walk the basic blocks of a function in dominator order.  */
+class builtin_warning_dom_walker : public dom_walker
+{
+ public:
+  builtin_warning_dom_walker () : dom_walker (CDI_DOMINATORS) {}
+
+  edge before_dom_children (basic_block) FINAL OVERRIDE;
+  bool handle_gimple_call (gimple_stmt_iterator *);
+
+ private:
+  void handle_builtin_warning (gimple *);
+};
+
+edge
+builtin_warning_dom_walker::before_dom_children (basic_block bb)
+{
+  /* Iterate over statements, looking for __builtin_warning calls.  */
+  for (gimple_stmt_iterator si = gsi_start_bb (bb); !gsi_end_p (si);
+       gsi_next (&si))
+    {
+      gimple *stmt = gsi_stmt (si);
+      if (!gimple_call_builtin_p (stmt, BUILT_IN_WARNING))
+	continue;
+
+      handle_builtin_warning (stmt);
+    }
+
+  return NULL;
+}
+
+
+struct args_to_fmt_t
+{
+  pretty_printer pp;
+  gimple *call;
+  int argno;
+};
+
+/* Formats a single directive DIR into a args_to_fmt_t object pointed to
+   by PDATA.  Returns true.  */
+
+static bool
+tree_to_format (const format_directive_t &dir, void *pdata)
+{
+  args_to_fmt_t *paf = static_cast <args_to_fmt_t *> (pdata);
+
+  if (dir.specifier == '\0')
+    {
+      pp_verbatim (&paf->pp, "%.*s", (int) dir.len, dir.beg);
+      return true;
+    }
+
+  tree arg = gimple_call_arg (paf->call, paf->argno);
+  ++paf->argno;
+  int spec = TOUPPER (dir.specifier);
+  int base = spec == 'X' ? 16 : spec == 'O' ? 8 : 10;
+
+  // format_tree (&paf->pp, base dir.beg, dir.len, arg);
+  pretty_printer *pp = &paf->pp;
+
+  const char *quotbeg = "";
+  const char *warnbeg = "";
+  const char *colend = "";
+  if (flag_diagnostics_show_color)
+    {
+      quotbeg = colorize_start (true, "quote");
+      warnbeg = colorize_start (true, "warning");
+      colend = colorize_stop (true);
+    }
+
+  bool qflag = dir.get_flag ('q');
+
+  if (TREE_CODE (arg) == INTEGER_CST)
+    {
+      /* FIXME: Handle flags.  */
+      const char *fmt;
+      switch (base)
+	{
+	case 8: fmt = "%" PRId64 "o"; break;
+	case 16: fmt = HOST_WIDE_INT_PRINT_HEX; break;
+	default: fmt = HOST_WIDE_INT_PRINT_DEC; break;
+	}
+      if (qflag)
+	pp_printf (pp, "%s%<", quotbeg);
+      pp_scalar (pp, fmt, tree_to_shwi (arg));
+      if (qflag)
+	pp_printf (pp, "%s%>", colend);
+      return true;
+    }
+
+  if (TREE_CODE (arg) == ADDR_EXPR && POINTER_TYPE_P (TREE_TYPE (arg)))
+    {
+      tree byteoff, memsize, decl;
+      tree str = string_constant (arg, &byteoff, &memsize, &decl);
+      if (str && TREE_CODE (str) == STRING_CST)
+	arg = str;
+    }
+
+  if (TREE_CODE (arg) == ADDR_EXPR)
+    arg = TREE_OPERAND (arg, 0);
+
+  if (TREE_CODE (arg) == STRING_CST)
+    {
+      const char *str = TREE_STRING_POINTER (arg);
+      /* FIXME: Handle width and precision.  */
+      if (qflag)
+	{
+	  pp_doublequote (pp);
+	  pp_printf (pp, "%s%s%s", quotbeg, str, colend);
+	  pp_doublequote (pp);
+	}
+      else
+	pp_printf (pp, "%s", str);
+    }
+  else if (TREE_CODE (arg) == SSA_NAME)
+    {
+      if (TREE_CODE (TREE_TYPE (arg)) == INTEGER_TYPE)
+	{
+	  // Print a range.
+	  wide_int min, max;
+	  value_range_kind rng = get_range_info (arg, &min, &max);
+	  if (rng == VR_RANGE)
+	    {
+	      pp_printf (pp, "%s[%" PRId64 ", %" PRId64 "]%s",
+			 quotbeg, min.to_shwi (), max.to_shwi (), colend);
+	      return true;
+	    }
+	  else if (rng == VR_ANTI_RANGE)
+	    {
+	      pp_printf (pp, "%s~[%" PRId64 ", %" PRId64 "]%s",
+			 quotbeg, min.to_shwi (), max.to_shwi (), colend);
+	      return true;
+	    }
+	}
+
+      if (tree id = SSA_NAME_IDENTIFIER (arg))
+	pp_printf (pp, "%s%qs%s", quotbeg, IDENTIFIER_POINTER (id), colend);
+      else
+	pp_printf (pp, "%s%<???%>%s", warnbeg, colend);
+    }
+  else if (DECL_P (arg))
+    {
+      tree id = DECL_NAME (arg);
+      pp_printf (pp, "%s%qs%s", quotbeg, IDENTIFIER_POINTER (id), colend);
+    }
+  else
+    pp_printf (pp, "%s%<%%%.*s%>%w", warnbeg, (int) dir.len, dir.beg, colend);
+
+  return true;
+}
+
+/* The location of a given warning.  */
+typedef std::pair<location_t, int> loc_warn_pair_t;
+
+/* Hash traits class for loc_warn_pair_t.  */
+struct loc_warn_hash_traits: typed_noop_remove<loc_warn_pair_t>
+{
+  typedef loc_warn_pair_t  value_type;
+  typedef value_type       compare_type;
+
+  static hashval_t hash (const value_type &x)
+  {
+    return x.first ^ x.second;
+  }
+
+  static bool equal (const value_type &x, const value_type &y)
+  {
+    return x.first == y.first && x.second == y.second;
+  }
+
+  static void mark_deleted (value_type &x)
+  {
+    x = value_type (UNKNOWN_LOCATION, 0);
+  }
+
+  static void mark_empty (value_type &x)
+  {
+    x = value_type (-1, -1);
+  }
+
+  static bool is_deleted (const value_type &x)
+  {
+    return x.first != UNKNOWN_LOCATION && !x.second;
+  }
+
+  static bool is_empty (const value_type &x)
+  {
+    return x.first == (location_t)-1 && x.second < 0;
+  }
+};
+
+/* Set of already issued warnings of a given kind for a given location.
+   Used to avoid duplicates.  */
+static hash_set<loc_warn_pair_t, false, loc_warn_hash_traits> issued_warnings;
+
+
+void builtin_warning_dom_walker::handle_builtin_warning (gimple *stmt)
+{
+  tree loc = gimple_call_arg (stmt, 0);
+  tree opt = gimple_call_arg (stmt, 1);
+  tree str = gimple_call_arg (stmt, 2);
+
+  tree byteoff, memsize, decl;
+
+  /* The option index.  GCC internal calls pass in an an integer
+     but user calls pass in the option name as a string.  */
+  int iopt;
+  if (TREE_CODE (opt) == INTEGER_CST)
+    iopt = tree_to_shwi (opt);
+  else
+    {
+      /* Look up the option name.  */
+      tree byteoff, memsize, decl;
+      tree optstr = string_constant (opt, &byteoff, &memsize, &decl);
+      gcc_assert (optstr);
+      const char *opt = TREE_STRING_POINTER (optstr);
+      size_t idx = find_opt (opt + 1, -1);
+      gcc_assert (idx != OPT_SPECIAL_unknown);
+      iopt = idx;
+    }
+
+  /* FIXME: Return nonzero when warning is enabled without issuing it.  */
+  if (integer_zerop (str))
+    return;
+
+  str = string_constant (str, &byteoff, &memsize, &decl);
+
+  location_t iloc = tree_to_shwi (loc);
+  if (iloc == 0)
+    iloc = gimple_location (stmt);
+
+  if (issued_warnings.add (loc_warn_pair_t (iloc, iopt)))
+    return;
+
+  struct args_to_fmt_t args_to_fmt;
+  args_to_fmt.call = stmt;
+  args_to_fmt.argno = 3;
+
+  /* FIXME: handle invalid/unknown format better.  */
+  const char *fmt = str ? TREE_STRING_POINTER (str) : "";
+  parse_format_string (fmt, tree_to_format, &args_to_fmt);
+
+  /* Always include inlining stack.  */
+  const char *text = pp_formatted_text (&args_to_fmt.pp);
+
+  auto_diagnostic_group d;
+  if (!warning_at (iloc, iopt, "%G%s", stmt, text))
+    return;
+
+  for (gimple_stmt_iterator gsi = gsi_for_stmt (stmt); ; )
+    {
+      /* For warning about function calls, include a note pointing
+	 to the declaration of the function.  */
+      gsi_next_nondebug (&gsi);
+      gimple *next_stmt = gsi_stmt (gsi);
+      if (!next_stmt || !is_gimple_call (next_stmt))
+	return;
+
+      /* Skip over any subsequent __builtin_warning calls.  */
+      if (gimple_call_builtin_p (next_stmt, BUILT_IN_WARNING))
+	continue;
+
+      if (tree fndecl = gimple_call_fndecl (next_stmt))
+	{
+	  if (DECL_IS_BUILTIN (fndecl))
+	    inform (iloc, "in a call to built-in function %qD", fndecl);
+	  else
+	    inform (DECL_SOURCE_LOCATION (fndecl),
+		    "in a call to function %qD declared here", fndecl);
+	}
+
+      break;
+    }
+}
+
+/* Execute the pass for function FUN, walking in dominator order.  */
+
+unsigned
+pass_builtin_warning::execute (function *fun)
+{
+  calculate_dominance_info (CDI_DOMINATORS);
+
+  builtin_warning_dom_walker walker;
+  walker.walk (ENTRY_BLOCK_PTR_FOR_FN (fun));
+
+  return 0;
+}
+
+}   // unnamed namespace
+
+gimple_opt_pass *
+make_pass_builtin_warning (gcc::context *ctxt)
+{
+  return new pass_builtin_warning (ctxt);
+}
diff --git a/gcc/gimple-ssa-isolate-paths.c b/gcc/gimple-ssa-isolate-paths.c
index aca5304b10a..dcde7905729 100644
--- a/gcc/gimple-ssa-isolate-paths.c
+++ b/gcc/gimple-ssa-isolate-paths.c
@@ -258,12 +258,11 @@ is_divmod_with_given_divisor (gimple *stmt, tree divisor)
    Return TRUE if USE_STMT uses NAME in a way where a 0 or NULL value results
    in undefined behavior, FALSE otherwise
 
-   LOC is used for issuing diagnostics.  This case represents potential
-   undefined behavior exposed by path splitting and that's reflected in
-   the diagnostic.  */
+   This case represents potential undefined behavior exposed by path splitting.
+   *PWARN is set to the diagnostic to issue.  */
 
-bool
-stmt_uses_name_in_undefined_way (gimple *use_stmt, tree name, location_t loc)
+static bool
+stmt_uses_name_in_undefined_way (gimple *use_stmt, tree name, gimple **pwarn)
 {
   /* If we are working with a non pointer type, then see
      if this use is a DIV/MOD operation using NAME as the
@@ -277,27 +276,21 @@ stmt_uses_name_in_undefined_way (gimple *use_stmt, tree name, location_t loc)
 
   /* NAME is a pointer, so see if it's used in a context where it must
      be non-NULL.  */
-  bool by_dereference
-    = infer_nonnull_range_by_dereference (use_stmt, name);
-
-  if (by_dereference
-      || infer_nonnull_range_by_attribute (use_stmt, name))
+  if (infer_nonnull_range_by_dereference (use_stmt, name))
     {
+      *pwarn = gimple_build_warning (use_stmt,
+				     OPT_Wnull_dereference,
+				     "potential null pointer dereference");
+      return flag_isolate_erroneous_paths_dereference;
+    }
 
-      if (by_dereference)
-	{
-	  warning_at (loc, OPT_Wnull_dereference,
-		      "potential null pointer dereference");
-	  if (!flag_isolate_erroneous_paths_dereference)
-	    return false;
-	}
-      else
-	{
-	  if (!flag_isolate_erroneous_paths_attribute)
-	    return false;
-	}
-      return true;
+  bitmap nonnullargs = NULL;
+  if (infer_nonnull_range_by_attribute (use_stmt, name, &nonnullargs))
+    {
+      *pwarn = gimple_build_nonnull_warning (use_stmt, nonnullargs);
+      return flag_isolate_erroneous_paths_attribute;
     }
+
   return false;
 }
 
@@ -307,7 +300,7 @@ stmt_uses_name_in_undefined_way (gimple *use_stmt, tree name, location_t loc)
    These cases are explicit in the IL.  */
 
 bool
-stmt_uses_0_or_null_in_undefined_way (gimple *stmt)
+stmt_uses_0_or_null_in_undefined_way (gimple *stmt, gimple **pwarn)
 {
   if (!cfun->can_throw_non_call_exceptions
       && is_divmod_with_given_divisor (stmt, integer_zero_node))
@@ -318,26 +311,20 @@ stmt_uses_0_or_null_in_undefined_way (gimple *stmt)
      pointer dereferences and other uses where a non-NULL
      value is required.  */
 
-  bool by_dereference
-    = infer_nonnull_range_by_dereference (stmt, null_pointer_node);
-  if (by_dereference
-      || infer_nonnull_range_by_attribute (stmt, null_pointer_node))
+  if (infer_nonnull_range_by_dereference (stmt, null_pointer_node))
     {
-      if (by_dereference)
-	{
-	  location_t loc = gimple_location (stmt);
-	  warning_at (loc, OPT_Wnull_dereference,
-		      "null pointer dereference");
-	  if (!flag_isolate_erroneous_paths_dereference)
-	    return false;
-	}
-      else
-	{
-	  if (!flag_isolate_erroneous_paths_attribute)
-	    return false;
-	}
-      return true;
+      *pwarn = gimple_build_warning (stmt, OPT_Wnull_dereference,
+				     "null pointer dereference");
+      return flag_isolate_erroneous_paths_dereference;
     }
+
+  bitmap nonnullargs = NULL;
+  if (infer_nonnull_range_by_attribute (stmt, null_pointer_node, &nonnullargs))
+    {
+      *pwarn = gimple_build_nonnull_warning (stmt, nonnullargs);
+      return flag_isolate_erroneous_paths_attribute;
+    }
+
   return false;
 }
 
@@ -644,8 +631,7 @@ handle_return_addr_local_phi_arg (basic_block bb, basic_block duplicate,
 	      || flag_isolate_erroneous_paths_attribute)
 	  && gimple_bb (use_stmt) == bb)
 	{
-	  duplicate = isolate_path (bb, duplicate, e,
-				    use_stmt, lhs, true);
+	  duplicate = isolate_path (bb, duplicate, e, use_stmt, lhs, true);
 
 	  /* Let caller know the path has been isolated.  */
 	  *isolated = true;
@@ -760,16 +746,32 @@ find_implicit_erroneous_behavior (void)
 		    ? gimple_location (use_stmt)
 		    : phi_arg_loc;
 
-		  if (stmt_uses_name_in_undefined_way (use_stmt, lhs, loc))
+		  gimple *warn_stmt = NULL;
+		  bool ub = stmt_uses_name_in_undefined_way (use_stmt, lhs,
+							     &warn_stmt);
+		  if (ub)
 		    {
-		      duplicate = isolate_path (bb, duplicate, e,
-						use_stmt, lhs, false);
+		      duplicate = isolate_path (bb, duplicate, e, use_stmt,
+						lhs, false);
 
 		      /* When we remove an incoming edge, we need to
 			 reprocess the Ith element.  */
 		      next_i = i;
 		      cfg_altered = true;
 		    }
+
+		  if (warn_stmt)
+		    {
+		      /* Insert a __builtin_warning() call into before
+			 the undefined statement.  This may result in
+			 consecutive warning calls for statements that
+			 have already been removed by prior isolate_path
+			 calls.  */
+		      gimple_set_location (warn_stmt, loc);
+		      gimple_stmt_iterator si = gsi_for_stmt (use_stmt);
+		      gsi_insert_before (&si, warn_stmt, GSI_NEW_STMT);
+		      cfg_altered = true;
+		    }
 		}
 	    }
 	}
@@ -858,8 +860,14 @@ find_explicit_erroneous_behavior (void)
 	{
 	  gimple *stmt = gsi_stmt (si);
 
-	  if (stmt_uses_0_or_null_in_undefined_way (stmt))
+	  gimple *warn_stmt = NULL;
+	  if (stmt_uses_0_or_null_in_undefined_way (stmt, &warn_stmt))
 	    {
+	      if (warn_stmt)
+		{
+		  gsi_insert_before (&si, warn_stmt, GSI_NEW_STMT);
+		  gsi_next (&si);
+		}
 	      insert_trap (&si, null_pointer_node);
 	      bb = gimple_bb (gsi_stmt (si));
 
@@ -870,6 +878,14 @@ find_explicit_erroneous_behavior (void)
 	      break;
 	    }
 
+	  if (warn_stmt)
+	    {
+	      gsi_insert_before (&si, warn_stmt, GSI_NEW_STMT);
+	      bb = gimple_bb (gsi_stmt (si));
+	      gsi_next (&si);
+	      cfg_altered = true;
+	    }
+
 	  /* Look for a return statement that returns the address
 	     of a local variable or the result of alloca.  */
 	  if (greturn *return_stmt = dyn_cast <greturn *> (stmt))
diff --git a/gcc/gimple.c b/gcc/gimple.c
index a874c29454c..0ace55fa2fb 100644
--- a/gcc/gimple.c
+++ b/gcc/gimple.c
@@ -28,6 +28,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "ssa.h"
 #include "cgraph.h"
 #include "diagnostic.h"
+#include "tree-diagnostic.h"
 #include "alias.h"
 #include "fold-const.h"
 #include "calls.h"
@@ -45,7 +46,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "attribs.h"
 #include "asan.h"
 #include "langhooks.h"
-
+#include "intl.h"
 
 /* All the tuples have their operand vector (if present) at the very bottom
    of the structure.  Therefore, the offset required to find the
@@ -414,6 +415,114 @@ gimple_build_call_from_tree (tree t, tree fnptrtype)
   return call;
 }
 
+/* Data passed by gimple_build_warning to directive_to_tree.  */
+struct va_to_auto_vec_t
+{
+  auto_vec<tree> args;   /* Argument list converted to tree nodes. */
+  va_list va;            /* Warning function argument list.  */
+};
+
+/* Utility function to convert a format directive DIR to an argument
+   to a call to __builtin_warning().  Called by parse_format_string.
+   Returns true to advance to the next directive.  */
+
+static bool
+directive_to_tree (const format_directive_t &dir, void *pdata)
+{
+  if (dir.specifier)
+    {
+      va_to_auto_vec_t *pvv = static_cast <va_to_auto_vec_t *> (pdata);
+      tree arg = dir.to_tree (pvv->va);
+      pvv->args.safe_push (arg);
+    }
+  return true;
+}
+
+/* Builds a call to __builtin_warning (int, const char*, const char*, ...)
+   with the second argument being the internal option number cast to a pointer.
+   Returns the call.  */
+
+gcall *
+gimple_build_warning (gimple *stmt, int opt, const char *fmt, va_list va)
+{
+  gcc_assert (stmt && fmt);
+
+  tree warnfn = builtin_decl_explicit (BUILT_IN_WARNING);
+  location_t loc = gimple_nonartificial_location (stmt);
+  if (loc == UNKNOWN_LOCATION && is_gimple_assign (stmt))
+    {
+      /* If the statement doesn't have a location try to get it
+	 from the LHS.  */
+      tree lhs = gimple_assign_lhs (stmt);
+      if (EXPR_HAS_LOCATION (lhs))
+	loc = tree_nonartificial_location (lhs);
+    }
+  loc = expansion_point_location_if_in_system_header (loc);
+
+  tree locarg = build_int_cst (integer_type_node, loc);
+  tree optarg = build_int_cst (ptr_type_node, opt);
+  tree fmtarg = build_string_literal (strlen (fmt), fmt);
+
+  va_to_auto_vec_t vv;
+  vv.args.safe_push (locarg);
+  vv.args.safe_push (optarg);
+  vv.args.safe_push (fmtarg);
+  va_copy (vv.va, va);
+
+  parse_format_string (fmt, directive_to_tree, &vv);
+
+  unsigned nargs = vv.args.length ();
+  gcall *call = gimple_build_call_1 (warnfn, nargs);
+
+  for (unsigned i = 0; i < nargs; i++)
+    gimple_call_set_arg (call, i, vv.args[i]);
+
+  return call;
+}
+
+/* Builds a call to __builtin_warning().  Returns the call.  */
+
+gcall *
+gimple_build_warning (gimple *stmt, int opt, const char *fmt, ...)
+{
+  va_list va;
+  va_start (va, fmt);
+  gcall *call = gimple_build_warning (stmt, opt, fmt, va);
+  va_end (va);
+  return call;
+}
+
+/* Formats a -Wnonnull message for call STMT referencing call arguments
+   in NONNULLARGS and builds and returns a __builtin_warning  call with
+   that message.  */
+
+gcall *
+gimple_build_nonnull_warning (gimple *stmt, bitmap nonnullargs)
+{
+  if (nonnullargs == NULL)
+    return gimple_build_warning (stmt, OPT_Wnonnull,
+				 "operand null where non-null expected");
+
+  char argliststr[128];
+  *argliststr = 0;
+
+  unsigned nnull = 0;
+  char *ps = argliststr;
+  for (unsigned i = 0; i < gimple_call_num_args (stmt); i++)
+    if (bitmap_bit_p (nonnullargs, i))
+      {
+	if ((ptrdiff_t) sizeof argliststr < ps - argliststr + 11)
+	  break;
+	ps += sprintf (ps, "%s%u", nnull ? ", " : "", i + 1);
+	++nnull;
+      }
+
+  return gimple_build_warning (stmt, OPT_Wnonnull,
+			       nnull == 1
+			       ? G_("argument %s null where non-null expected")
+			       : G_("arguments %s null where non-null expected"),
+			       argliststr);
+}
 
 /* Build a GIMPLE_ASSIGN statement.
 
@@ -2955,10 +3064,11 @@ infer_nonnull_range_by_dereference (gimple *stmt, tree op)
   return false;
 }
 
-/* Return true if OP can be inferred to be a non-NULL after STMT
+/* Return true if OP can be inferred to be non-NULL after STMT
    executes by using attributes.  */
 bool
-infer_nonnull_range_by_attribute (gimple *stmt, tree op)
+infer_nonnull_range_by_attribute (gimple *stmt, tree op,
+				  bitmap *pargs /* = NULL */)
 {
   /* We can only assume that a pointer dereference will yield
      non-NULL if -fdelete-null-pointer-checks is enabled.  */
@@ -2970,41 +3080,45 @@ infer_nonnull_range_by_attribute (gimple *stmt, tree op)
   if (is_gimple_call (stmt) && !gimple_call_internal_p (stmt))
     {
       tree fntype = gimple_call_fntype (stmt);
-      tree attrs = TYPE_ATTRIBUTES (fntype);
-      for (; attrs; attrs = TREE_CHAIN (attrs))
-	{
-	  attrs = lookup_attribute ("nonnull", attrs);
+      /* Get the set of nonnull parameters.  A null result means none
+	 is declared nonnull.  */
+      bitmap nonnullargs = get_nonnull_args (fntype);
 
-	  /* If "nonnull" wasn't specified, we know nothing about
-	     the argument.  */
-	  if (attrs == NULL_TREE)
-	    return false;
+      /* If "nonnull" wasn't specified, we know nothing about
+	 the argument.  */
+      if (nonnullargs == NULL)
+	return false;
 
-	  /* If "nonnull" applies to all the arguments, then ARG
-	     is non-null if it's in the argument list.  */
-	  if (TREE_VALUE (attrs) == NULL_TREE)
-	    {
-	      for (unsigned int i = 0; i < gimple_call_num_args (stmt); i++)
-		{
-		  if (POINTER_TYPE_P (TREE_TYPE (gimple_call_arg (stmt, i)))
-		      && operand_equal_p (op, gimple_call_arg (stmt, i), 0))
-		    return true;
-		}
-	      return false;
-	    }
+      /* An empty bitmap means all parameters are nonnull.  */
+      bool allnonnull = bitmap_empty_p (nonnullargs);
+
+      /* Number of null arguments to nonnull parameters.  */
+      unsigned nnull = 0;
+      for (unsigned i = 0; i < gimple_call_num_args (stmt); i++)
+	{
+	  if (!allnonnull && !bitmap_bit_p (nonnullargs, i))
+	    continue;
 
-	  /* Now see if op appears in the nonnull list.  */
-	  for (tree t = TREE_VALUE (attrs); t; t = TREE_CHAIN (t))
+	  tree arg = gimple_call_arg (stmt, i);
+	  if (POINTER_TYPE_P (TREE_TYPE (arg))
+	      && operand_equal_p (op, arg, 0))
 	    {
-	      unsigned int idx = TREE_INT_CST_LOW (TREE_VALUE (t)) - 1;
-	      if (idx < gimple_call_num_args (stmt))
-		{
-		  tree arg = gimple_call_arg (stmt, idx);
-		  if (operand_equal_p (op, arg, 0))
-		    return true;
-		}
+	      ++nnull;
+	      if (pargs == NULL)
+		break;
+
+	      bitmap_set_bit (nonnullargs, i);
 	    }
+	  else
+	    bitmap_clear_bit (nonnullargs, i);
 	}
+
+      if (nnull && pargs)
+	*pargs = nonnullargs;
+      else
+	BITMAP_FREE (nonnullargs);
+
+      return nnull != 0;
     }
 
   /* If this function is marked as returning non-null, then we can
diff --git a/gcc/gimple.h b/gcc/gimple.h
index cf1f8da5ae2..75fa628238d 100644
--- a/gcc/gimple.h
+++ b/gcc/gimple.h
@@ -1456,6 +1456,11 @@ gcall *gimple_build_call_valist (tree, unsigned, va_list);
 gcall *gimple_build_call_internal (enum internal_fn, unsigned, ...);
 gcall *gimple_build_call_internal_vec (enum internal_fn, vec<tree> );
 gcall *gimple_build_call_from_tree (tree, tree);
+
+gcall *gimple_build_warning (gimple *, int, const char *, va_list);
+gcall *gimple_build_warning (gimple *, int, const char *, ...);
+gcall *gimple_build_nonnull_warning (gimple *, bitmap);
+
 gassign *gimple_build_assign (tree, tree CXX_MEM_STAT_INFO);
 gassign *gimple_build_assign (tree, enum tree_code,
 			      tree, tree, tree CXX_MEM_STAT_INFO);
@@ -1560,7 +1565,7 @@ extern bool nonfreeing_call_p (gimple *);
 extern bool nonbarrier_call_p (gimple *);
 extern bool infer_nonnull_range (gimple *, tree);
 extern bool infer_nonnull_range_by_dereference (gimple *, tree);
-extern bool infer_nonnull_range_by_attribute (gimple *, tree);
+extern bool infer_nonnull_range_by_attribute (gimple *, tree, bitmap * = NULL);
 extern void sort_case_labels (vec<tree>);
 extern void preprocess_case_label_vec_for_gimple (vec<tree>, tree, tree *);
 extern void gimple_seq_set_location (gimple_seq, location_t);
diff --git a/gcc/passes.def b/gcc/passes.def
index 8999ceec636..06f0db7a381 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -396,6 +396,7 @@ along with GCC; see the file COPYING3.  If not see
   NEXT_PASS (pass_nrv);
   NEXT_PASS (pass_cleanup_cfg_post_optimizing);
   NEXT_PASS (pass_warn_function_noreturn);
+  NEXT_PASS (pass_builtin_warning);
   NEXT_PASS (pass_gen_hsail);
 
   NEXT_PASS (pass_expand);
diff --git a/gcc/testsuite/gcc.dg/builtin-warning-2.c b/gcc/testsuite/gcc.dg/builtin-warning-2.c
new file mode 100644
index 00000000000..3210a781866
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/builtin-warning-2.c
@@ -0,0 +1,40 @@
+/* Test exercising the __builtin_warning intrinsic function with
+   optimization.
+   { dg-do compile }
+   { dg-options "-O -ftrack-macro-expansion=0" } */
+
+#define W(opt, ...)   __builtin_warning (0, opt, __VA_ARGS__)
+
+void sink (void*, ...);
+
+void nowarn_dead_conditional (void)
+{
+  int i = 7;
+  i < 0 ? W ("-Warray-bounds", "i is negative") : (void)0;
+}
+
+void nowarn_dead_block (void)
+{
+  int i = 7;
+  if (i < 0)
+    W ("-Warray-bounds", "i is negative");
+}
+
+void warn_conditional (void)
+{
+  int i = 7;
+  (i < 0
+   ? W ("-Warray-bounds", "i is negative")
+   : (i > 3
+      ? W ("-Warray-bounds", "i: %i > 3", i)    // { dg-warning "i: '7' > 3" }
+      : W ("-Warray-bounds", "i: %i <= 3", i)));
+}
+
+void warn_if_else (void)
+{
+  int i = 5;
+  if (i < 0)
+    W ("-Warray-bounds", "i is negative");
+  else if (i > 2)
+    W ("-Warray-bounds", "i: %i > 2", i);       // { dg-warning "i: '5' > 2" }
+}
diff --git a/gcc/testsuite/gcc.dg/builtin-warning.c b/gcc/testsuite/gcc.dg/builtin-warning.c
new file mode 100644
index 00000000000..b7518ea588c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/builtin-warning.c
@@ -0,0 +1,75 @@
+/* Test exercising the __builtin_warning intrinsic function without
+   optimization.
+   { dg-do compile }
+   { dg-options "-ftrack-macro-expansion=0" } */
+
+#define W(opt, ...)   __builtin_warning (0, opt, __VA_ARGS__)
+
+#define WSCN "-Wshift-count-negative"
+#define WSO  "-Wshift-overflow"
+
+extern const char garr[];
+const char gstr[] = "global string";
+
+void sink (void*, ...);
+
+void test_noarg (void)
+{
+  W (WSO, 0);
+  W (WSO, "");                    // { dg-warning "\\\[-Wshift-overflow]" }
+  W (WSCN, "");                   // { dg-warning "\\\[-Wshift-count-negative]" }
+  W (WSO, garr);                  // { dg-warning "" }
+  W (WSO, gstr);                  // { dg-warning "global string" }
+}
+
+
+void test_string_arg (void)
+{
+  W (WSO, "s=%s", "literal");     // { dg-warning "s=literal" }
+  W (WSO, "s=%qs", "literal");    // { dg-warning "s=\"literal\"" }
+  W (WSO, "s=%s", garr);          // { dg-warning "s='garr'" }
+  W (WSO, "s=%s", gstr);          // { dg-warning "s=global string" }
+  W (WSO, "s=%qs", gstr);         // { dg-warning "s=\"global string\"" }
+
+  /* Verify that when the string cannot be determined the warning refers
+     to the variable by name.  */
+  char lstr[8];
+  sink (lstr);
+  W (WSO, "name=%s", lstr);       // { dg-warning "name='lstr'" }
+
+  char *lptr;
+  sink (&lptr);
+  W (WSO, "var=%s", lptr);        // { dg-warning "var='lptr" }
+}
+
+
+void test_int_arg (void)
+{
+  W (WSCN, "var=%i", 123);        // { dg-warning "var=123" }
+  W (WSCN, "var=%qi", 456);       // { dg-warning "var='456'" }
+  int ivar;
+  sink (&ivar);
+  W (WSCN, "var=%i", ivar);       // { dg-warning "var='ivar" }
+  W (WSCN, "i=%i", ivar + 1);     // { dg-warning "i='\\\?\\\?\\\?'" }
+}
+
+
+void test_dead_constant_conditional (void)
+{
+  enum { i = 7 };
+  i < 0 ? W ("-Warray-bounds", "i is negative") : (void)0;
+}
+
+void test_dead_block (void)
+{
+  enum { i = 7 };
+  if (i < 0)
+    W ("-Warray-bounds", "i is negative");
+}
+
+const char str[] = "12345";
+
+void test_const_strlen (void)
+{
+  __builtin_strlen (str) == 5 || W ("-Warray-bounds", "string length");
+}
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-fprintf-warn-1.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-fprintf-warn-1.c
index 690404ade99..ad166736fbf 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-fprintf-warn-1.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-fprintf-warn-1.c
@@ -73,11 +73,10 @@ void test_fprintf_c_const (int width)
 
 /* Exercise the "%s" directive with constant arguments.  */
 
-void test_fprintf_s_const (int width)
+void test_fprintf_s_const (int width, int i)
 {
   const char *nulptr = 0;
 
-  T ("%s", nulptr);               /* { dg-warning "\\\[-Wformat|-Wnonnull" } */
   T ("%.0s", nulptr);             /* { dg-warning ".%.0s. directive argument is null" } */
 
   /* Verify no warning is issued for unreachable code.  */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/isolate-1.c b/gcc/testsuite/gcc.dg/tree-ssa/isolate-1.c
index 486c307cbdc..1048ae28d53 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/isolate-1.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/isolate-1.c
@@ -39,7 +39,9 @@ d_type (struct d_info *di)
    struct demangle_component *ret;
    ret = d_make_empty (di);
    ret->type = 42;		/* { dg-warning "null pointer dereference" } */
-   ret->zzz = -1;		/* { dg-warning "null pointer dereference" } */
+   /* The statement below is eliminated as a resulf of the trap inserted
+      for the derefernce above.  */
+   ret->zzz = -1;		/* { dg-warning "null pointer dereference" "removed" { xfail *-*-* } } */
    return ret;
 }
 
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/user-printf-warn-1.c b/gcc/testsuite/gcc.dg/tree-ssa/user-printf-warn-1.c
index 1896e1c6489..164ac21d8f9 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/user-printf-warn-1.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/user-printf-warn-1.c
@@ -3,7 +3,7 @@
    Test to verify that the applicable subset of -Wformat-overflow warnings
    are issued for user-defined function declared attribute format printf.
    { dg-do compile }
-   { dg-options "-O -Wformat -Wformat-overflow=1 -ftrack-macro-expansion=0" }
+   { dg-options "-O2 -Wformat -Wformat-overflow=1 -ftrack-macro-expansion=0" }
    { dg-require-effective-target int32plus } */
 
 /* When debugging, define LINE to the line number of the test case to exercise
@@ -48,9 +48,7 @@ const char arr_no_nul[] = { 'a', 'b' };
   (((!LINE || LINE == __LINE__)					\
     ? user_print : dummy_print) (0, __VA_ARGS__))
 
-/* Exercise the "%c" directive with constant arguments.  */
-
-void test_user_print_format_string (void)
+void test_user_print (void)
 {
   char *null = 0;
   /* Verify that no warning is issued for a null format string unless
@@ -58,13 +56,22 @@ void test_user_print_format_string (void)
   user_print (0, null);
   user_print_nonnull ("x", "y");
   user_print_nonnull ("x", null);   /* { dg-warning "\\\[-Wnonnull]" } */
+}
+
+void test_user_print_format (void)
+{
+  char *null = 0;
   user_print_nonnull_fmt (null, "x");
   user_print_nonnull_fmt (0, null); /* { dg-warning "\\\[-Wnonnull]" } */
+}
+
+void test_user_print_other (void)
+{
+  char *null = 0;
   user_print_nonnull_other (null, "x", "y");
   user_print_nonnull_other (null, "x", null);  /* { dg-warning "\\\[-Wnonnull]" } */
 }
 
-
 /* Exercise the "%c" directive with constant arguments.  */
 
 void test_user_print_c_const (int width)
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/wnull-dereference.c b/gcc/testsuite/gcc.dg/tree-ssa/wnull-dereference.c
index db36acc15b7..18dac52ab45 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/wnull-dereference.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/wnull-dereference.c
@@ -38,7 +38,9 @@ void test3 (struct t *s)
   if (s != NULL || s->bar > 2)  /* { dg-warning "null" } */
     return;
 
-  s->bar = 3;  /* { dg-warning "null" } */
+  /* The following statement is removed as a result of the call to
+     __builtin_trap() injected for the null pointer derefernce above.  */
+  s->bar = 3;  /* { dg-warning "null" "removed" { xfail *-*-* } } */
 }
 
 int test4 (struct t *s)
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index 85b1c828f3a..686a3d23dde 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -478,6 +478,7 @@ extern gimple_opt_pass *make_pass_gen_hsail (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_warn_nonnull_compare (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_sprintf_length (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_walloca (gcc::context *ctxt);
+extern gimple_opt_pass *make_pass_builtin_warning (gcc::context *ctxt);
 
 /* IPA Passes */
 extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ctxt);
diff --git a/gcc/tree-ssa-ccp.c b/gcc/tree-ssa-ccp.c
index a8d0738fbb0..e3e0b2e8550 100644
--- a/gcc/tree-ssa-ccp.c
+++ b/gcc/tree-ssa-ccp.c
@@ -3482,6 +3482,11 @@ public:
 unsigned int
 pass_post_ipa_warn::execute (function *fun)
 {
+  if (!warn_nonnull)
+    return 0;
+
+  bool cfg_altered = false;
+
   basic_block bb;
 
   FOR_EACH_BB_FN (bb, fun)
@@ -3493,46 +3498,26 @@ pass_post_ipa_warn::execute (function *fun)
 	  if (!is_gimple_call (stmt) || gimple_no_warning_p (stmt))
 	    continue;
 
-	  if (warn_nonnull)
+	  bitmap nonnullargs = NULL;
+	  if (infer_nonnull_range_by_attribute (stmt, null_pointer_node,
+						&nonnullargs))
 	    {
-	      bitmap nonnullargs
-		= get_nonnull_args (gimple_call_fntype (stmt));
-	      if (nonnullargs)
-		{
-		  for (unsigned i = 0; i < gimple_call_num_args (stmt); i++)
-		    {
-		      tree arg = gimple_call_arg (stmt, i);
-		      if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE)
-			continue;
-		      if (!integer_zerop (arg))
-			continue;
-		      if (!bitmap_empty_p (nonnullargs)
-			  && !bitmap_bit_p (nonnullargs, i))
-			continue;
-
-		      location_t loc = gimple_location (stmt);
-		      auto_diagnostic_group d;
-		      if (warning_at (loc, OPT_Wnonnull,
-				      "%Gargument %u null where non-null "
-				      "expected", stmt, i + 1))
-			{
-			  tree fndecl = gimple_call_fndecl (stmt);
-			  if (fndecl && DECL_IS_BUILTIN (fndecl))
-			    inform (loc, "in a call to built-in function %qD",
-				    fndecl);
-			  else if (fndecl)
-			    inform (DECL_SOURCE_LOCATION (fndecl),
-				    "in a call to function %qD declared here",
-				    fndecl);
-
-			}
-		    }
-		  BITMAP_FREE (nonnullargs);
-		}
+	      cfg_altered = true;
+	      gimple *warn_stmt
+		= gimple_build_nonnull_warning (stmt, nonnullargs);
+	      gsi_insert_before (&gsi, warn_stmt, GSI_NEW_STMT);
+	      BITMAP_FREE (nonnullargs);
+	      gsi_next (&gsi);
+	      if (gsi_end_p (gsi))
+		break;
 	    }
 	}
     }
-  return 0;
+
+  if (cfg_altered)
+      return TODO_cleanup_cfg | TODO_update_ssa;
+
+    return 0;
 }
 
 } // anon namespace
diff --git a/gcc/tree.c b/gcc/tree.c
index e845fc7a00e..ad89e70aae8 100644
--- a/gcc/tree.c
+++ b/gcc/tree.c
@@ -32,12 +32,13 @@ along with GCC; see the file COPYING3.  If not see
 #include "coretypes.h"
 #include "backend.h"
 #include "target.h"
+#include "diagnostic-core.h"
+#include "diagnostic.h"
 #include "tree.h"
 #include "gimple.h"
 #include "tree-pass.h"
 #include "ssa.h"
 #include "cgraph.h"
-#include "diagnostic.h"
 #include "flags.h"
 #include "alias.h"
 #include "fold-const.h"
@@ -10793,6 +10794,16 @@ build_common_builtin_nodes (void)
 			    "memset", ECF_NOTHROW | ECF_LEAF | ECF_RET1);
     }
 
+  if (!builtin_decl_explicit_p (BUILT_IN_WARNING))
+    {
+      ftype = build_function_type_list (integer_type_node,
+					integer_type_node, integer_type_node,
+					const_ptr_type_node, const_ptr_type_node,
+					NULL_TREE);
+      local_define_builtin ("__builtin_warning", ftype, BUILT_IN_WARNING,
+			    "__builtin_warning", ECF_NOTHROW | ECF_LEAF);
+    }
+
   /* If we're checking the stack, `alloca' can throw.  */
   const int alloca_flags
     = ECF_MALLOC | ECF_LEAF | (flag_stack_check ? 0 : ECF_NOTHROW);
@@ -15294,6 +15305,223 @@ max_object_size (void)
   return TYPE_MAX_VALUE (ptrdiff_type_node);
 }
 
+/* Extracts any optional flags from the directive at FMT and stores
+   them in *THIS.  Returns the next character after the extracted flags.  */
+
+const char*
+format_directive_t::set_flags (const char *fmt)
+{
+  /* FIXME: Handle execution character sets.  */
+
+  for (; *fmt; ++fmt)
+    {
+      switch (*fmt)
+	{
+	case '\'':
+	case ' ':
+	case '0':
+	case '#':
+	case '+':
+	case '-':
+	case 'q':
+	  set_flag (*fmt);
+	  break;
+
+	default:
+	  return fmt;
+	}
+    }
+
+  return fmt;
+}
+
+/* Extracts the optional length modifier from the directive at FMT and
+   sets it in *THIS.  Returns the next character after the extracted
+   modifier.  */
+
+const char*
+format_directive_t::set_modifier (const char *fmt)
+{
+  /* FIXME: Handle execution character sets.  */
+
+  switch (*fmt)
+    {
+    case 'h':
+      if (*fmt == 'h')
+	++fmt;
+      /* Fallthrough.  */
+    case 'l':
+      if (*fmt == 'l')
+	++fmt;
+      /* Fallthrough.  */
+    case 'q':
+    case 'j':
+    case 't':
+    case 'z':
+    case 'L':
+      ++fmt;
+    }
+
+  return fmt;
+}
+
+/* Extracts the width and/or precision from the directive at FMT and
+   stores the result in consecutive elements of the array X.  Returns
+   the next character of FMT.  */
+
+const char*
+format_directive_t::set_width_or_precision (HOST_WIDE_INT x[2], const char *fmt)
+{
+  /* FIXME: Handle execution character sets.  */
+
+  if (*fmt == '*')
+    {
+      /* FIXME: Handle asterisk.  */
+      x[0] = x[1] = 0;
+      return ++fmt;
+    }
+
+  if (ISDIGIT (*fmt))
+    {
+      /* FIXME: Handle invalid input.  */
+      char *end;
+      x[0] = strtoul (fmt, &end, 10);
+      x[1] = x[0];
+      return end;
+    }
+
+  x[0] = x[1] = 0;
+  return fmt;
+}
+
+/* Converts the format argument described by VA to a tree node.  */
+
+tree
+format_directive_t::to_tree (va_list va) const
+{
+  tree arg = NULL_TREE;
+
+  switch (specifier)
+    {
+    case 'd':
+    case 'i':
+      {
+	int iarg = va_arg (va, int);
+	arg = build_int_cst (integer_type_node, iarg);
+	break;
+      }
+    case 's':
+      {
+	const char *s = va_arg (va, const char *);
+	arg = build_string_literal (strlen (s), s);
+	break;
+      }
+    case '\0':
+      arg = build_string_literal (len, beg);
+      break;
+
+    default:
+      gcc_assert (!"specifier not handled");
+    }
+
+  return arg;
+}
+
+/* Parse the format string FMT, calling the PROCESS callback function
+   for each formatting directive with PDATA as an argument.  Returns
+   true on success.  */
+
+bool
+parse_format_string (const char *fmt,
+		     bool (*process)(const format_directive_t &, void *),
+		     void *pdata)
+{
+  for (const char *pf = fmt; *pf; )
+    {
+      format_directive_t dir = { };
+
+      const char *pcnt = strchr (pf, '%');
+      if (!pcnt)
+	{
+	  dir.beg = pf;
+	  dir.len = strlen (pf);
+	  process (dir, pdata);
+	  break;
+	}
+
+      if (*pf == '%')
+	{
+	  pf = dir.set_flags (pf + 1);
+	  pf = dir.set_width (pf);
+	  if (*pf == '.')
+	    pf = dir.set_precision (pf + 1);
+	  pf = dir.set_modifier (pf);
+
+	  dir.beg = pcnt + 1;
+	  dir.len = pf - pcnt;
+	  dir.specifier = *pf++;
+	}
+      else
+	{
+	  dir.beg = pf;
+	  dir.len = pcnt - pf;
+	  pf = pcnt;
+	}
+
+      process (dir, pdata);
+    }
+
+  return true;
+}
+
+/* Describes an argument passed to directive_to_tree below.  */
+struct va_to_auto_vec_t
+{
+  auto_vec<tree> args;   /* Vector of call arguments.  */
+  va_list va;            /* va_list passed to build_builtin_warning.  */
+};
+
+/* Callback to parse_format_string.  Converts the directive DIR to a tree
+   node and stores the result in the va_to_auto_vec_t object pointed to by
+   PDATA.  */
+
+static bool
+directive_to_tree (const format_directive_t &dir, void *pdata)
+{
+  if (dir.specifier)
+    {
+      va_to_auto_vec_t *pvv = static_cast <va_to_auto_vec_t *> (pdata);
+      tree arg = dir.to_tree (pvv->va);
+      pvv->args.safe_push (arg);
+    }
+  return true;
+}
+
+/* Builds a call expression to __builtin_warning at LOCation and with
+   the format string FMT and optional arguments.  Returns the call.  */ 
+
+tree
+build_builtin_warning (location_t loc, int opt, const char *fmt, ...)
+{
+  tree locarg = build_int_cst (integer_type_node, loc);
+  tree optarg = build_int_cst (integer_type_node, opt);
+  tree fmtarg = build_string_literal (strlen (fmt), fmt);
+
+  va_to_auto_vec_t vv;
+  vv.args.safe_push (locarg);
+  vv.args.safe_push (optarg);
+  vv.args.safe_push (fmtarg);
+
+  va_start (vv.va, fmt);
+  parse_format_string (fmt, directive_to_tree, &vv);
+  va_end (vv.va);
+
+  const tree *args = vv.args.begin ();
+  int nargs = vv.args.length ();
+  tree warnfn = builtin_decl_explicit (BUILT_IN_WARNING);
+  return build_call_array (integer_type_node, warnfn, nargs, args);
+}
+
 #if CHECKING_P
 
 namespace selftest {
diff --git a/gcc/tree.h b/gcc/tree.h
index c825109b5f7..8f6f452dabe 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -5206,6 +5206,107 @@ extern tree build_nonstandard_integer_type (unsigned HOST_WIDE_INT, int);
 extern tree build_nonstandard_boolean_type (unsigned HOST_WIDE_INT);
 extern tree build_range_type (tree, tree, tree);
 extern tree build_nonshared_range_type (tree, tree, tree);
+
+/* Description of a format directive.  A directive is either a plain
+   string or a conversion specification that starts with '%'.  */
+
+struct format_directive_t
+{
+  enum format_lengths
+    {
+     FMT_LEN_none,
+     FMT_LEN_hh,    // char argument
+     FMT_LEN_h,     // short
+     FMT_LEN_l,     // long
+     FMT_LEN_ll,    // long long
+     FMT_LEN_L,     // long double (and GNU long long)
+     FMT_LEN_z,     // size_t
+     FMT_LEN_t,     // ptrdiff_t
+     FMT_LEN_j      // intmax_t
+    };
+
+  /* The 1-based directive number (for debugging).  */
+  unsigned dirno;
+
+  /* The first character of the directive and its length.  */
+  const char *beg;
+  size_t len;
+
+  /* A bitmap of flags, one for each character.  */
+  unsigned flags[256 / sizeof (int)];
+
+  /* The range of values of the specified width, or -1 if not specified.  */
+  HOST_WIDE_INT width[2];
+  /* The range of values of the specified precision, or -1 if not
+     specified.  */
+  HOST_WIDE_INT prec[2];
+
+  /* Length modifier.  */
+  format_lengths modifier;
+
+  /* Format specifier character.  */
+  char specifier;
+
+  /* The argument of the directive or null when the directive doesn't
+     take one or when none is available (such as for vararg functions).  */
+  tree arg;
+
+  /* Return True when a the format flag CHR has been used.  */
+  bool get_flag (char chr) const
+  {
+    unsigned char c = chr & 0xff;
+    return (flags[c / (CHAR_BIT * sizeof *flags)]
+	    & (1U << (c % (CHAR_BIT * sizeof *flags))));
+  }
+
+  /* Make a record of the format flag CHR having been used.  */
+  void set_flag (char chr)
+  {
+    unsigned char c = chr & 0xff;
+    flags[c / (CHAR_BIT * sizeof *flags)]
+      |= (1U << (c % (CHAR_BIT * sizeof *flags)));
+  }
+
+  /* Reset the format flag CHR.  */
+  void clear_flag (char chr)
+  {
+    unsigned char c = chr & 0xff;
+    flags[c / (CHAR_BIT * sizeof *flags)]
+      &= ~(1U << (c % (CHAR_BIT * sizeof *flags)));
+  }
+
+  /* Extracts flags from the argument and set them in *this.  Returns
+     the next character of the directive.  */
+  const char* set_flags (const char *);
+
+  /* Extracts length modifier from the argument and set it in *this.
+     Returns the next character of the directive.  */
+  const char* set_modifier (const char *);
+
+  const char* set_width (const char *fmt)
+  {
+    return set_width_or_precision (width, fmt);
+  }
+
+  const char* set_precision (const char *fmt)
+  {
+    return set_width_or_precision (width, fmt);
+  }
+
+  tree to_tree (va_list) const;
+
+private:
+  static const char* set_width_or_precision (HOST_WIDE_INT x[2], const char *);
+
+};
+
+extern bool parse_format_string (const char *,
+				 bool (*)(const format_directive_t &, void *),
+				 void *);
+
+extern tree build_builtin_warning (location_t, int, const char*, ...)
+  /* ATTRIBUTE_GCC_DIAG (3, 4) */ ATTRIBUTE_NONNULL (3) ATTRIBUTE_RETURNS_NONNULL;
+
 extern bool subrange_type_for_debug_p (const_tree, tree *, tree *);
 extern HOST_WIDE_INT int_cst_value (const_tree);
 extern tree tree_block (tree);

Reply via email to