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);