From adc577c558a4b2e3b604511927cce17039d57c11 Mon Sep 17 00:00:00 2001 From: Aldy Hernandez Date: Tue, 18 Oct 2016 21:40:58 +0000 Subject: [PATCH] Makefile.in (OBJS): Add gimple-ssa-warn-alloca.o. * Makefile.in (OBJS): Add gimple-ssa-warn-alloca.o. * passes.def: Add two instances of pass_walloca. * tree-pass.h (make_pass_walloca): New. * gimple-ssa-warn-walloca.c: New file. * doc/invoke.texi: Document -Walloca, -Walloca-larger-than=, and -Wvla-larger-than= options. From-SVN: r241318 --- gcc/ChangeLog | 9 + gcc/Makefile.in | 1 + gcc/c-family/ChangeLog | 6 + gcc/c-family/c-opts.c | 10 + gcc/c-family/c.opt | 16 + gcc/doc/invoke.texi | 90 ++++- gcc/gimple-ssa-warn-alloca.c | 524 ++++++++++++++++++++++++++++++ gcc/passes.def | 2 + gcc/testsuite/gcc.dg/Walloca-1.c | 63 ++++ gcc/testsuite/gcc.dg/Walloca-10.c | 25 ++ gcc/testsuite/gcc.dg/Walloca-2.c | 40 +++ gcc/testsuite/gcc.dg/Walloca-3.c | 33 ++ gcc/testsuite/gcc.dg/Walloca-4.c | 16 + gcc/testsuite/gcc.dg/Walloca-5.c | 32 ++ gcc/testsuite/gcc.dg/Walloca-6.c | 11 + gcc/testsuite/gcc.dg/Walloca-7.c | 17 + gcc/testsuite/gcc.dg/Walloca-8.c | 12 + gcc/testsuite/gcc.dg/Walloca-9.c | 19 ++ gcc/testsuite/gcc.dg/Wvla-1.c | 24 ++ gcc/testsuite/gcc.dg/Wvla-2.c | 70 ++++ gcc/testsuite/gcc.dg/Wvla-3.c | 12 + gcc/tree-pass.h | 1 + 22 files changed, 1030 insertions(+), 3 deletions(-) create mode 100644 gcc/gimple-ssa-warn-alloca.c create mode 100644 gcc/testsuite/gcc.dg/Walloca-1.c create mode 100644 gcc/testsuite/gcc.dg/Walloca-10.c create mode 100644 gcc/testsuite/gcc.dg/Walloca-2.c create mode 100644 gcc/testsuite/gcc.dg/Walloca-3.c create mode 100644 gcc/testsuite/gcc.dg/Walloca-4.c create mode 100644 gcc/testsuite/gcc.dg/Walloca-5.c create mode 100644 gcc/testsuite/gcc.dg/Walloca-6.c create mode 100644 gcc/testsuite/gcc.dg/Walloca-7.c create mode 100644 gcc/testsuite/gcc.dg/Walloca-8.c create mode 100644 gcc/testsuite/gcc.dg/Walloca-9.c create mode 100644 gcc/testsuite/gcc.dg/Wvla-1.c create mode 100644 gcc/testsuite/gcc.dg/Wvla-2.c create mode 100644 gcc/testsuite/gcc.dg/Wvla-3.c diff --git a/gcc/ChangeLog b/gcc/ChangeLog index 6002ddee5bf..2431b30627e 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,3 +1,12 @@ +2016-10-18 Aldy Hernandez + + * Makefile.in (OBJS): Add gimple-ssa-warn-alloca.o. + * passes.def: Add two instances of pass_walloca. + * tree-pass.h (make_pass_walloca): New. + * gimple-ssa-warn-alloca.c: New file. + * doc/invoke.texi: Document -Walloca, -Walloca-larger-than=, and + -Wvla-larger-than= options. + 2016-10-18 Thomas Schwinge * cfg.c (clear_bb_flags): Use FOR_ALL_BB_FN. diff --git a/gcc/Makefile.in b/gcc/Makefile.in index c512cd70970..c08da80be27 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1298,6 +1298,7 @@ OBJS = \ gimple-ssa-split-paths.o \ gimple-ssa-strength-reduction.o \ gimple-ssa-sprintf.o \ + gimple-ssa-warn-alloca.o \ gimple-streamer-in.o \ gimple-streamer-out.o \ gimple-walk.o \ diff --git a/gcc/c-family/ChangeLog b/gcc/c-family/ChangeLog index d5e90905fe8..f3055930065 100644 --- a/gcc/c-family/ChangeLog +++ b/gcc/c-family/ChangeLog @@ -1,3 +1,9 @@ +2016-06-16 Aldy Hernandez + + * c.opt (Walloca): New. + (Walloca-larger-than=): New. + (Wvla-larger-than=): New. + 2016-10-17 Marek Polacek * c-warn.c (find_array_ref_with_const_idx_r): Remove parameter names. diff --git a/gcc/c-family/c-opts.c b/gcc/c-family/c-opts.c index a0c0c624f96..c39930708d6 100644 --- a/gcc/c-family/c-opts.c +++ b/gcc/c-family/c-opts.c @@ -380,6 +380,16 @@ c_common_handle_option (size_t scode, const char *arg, int value, cpp_opts->warn_num_sign_change = value; break; + case OPT_Walloca_larger_than_: + if (!value) + inform (loc, "-Walloca-larger-than=0 is meaningless"); + break; + + case OPT_Wvla_larger_than_: + if (!value) + inform (loc, "-Wvla-larger-than=0 is meaningless"); + break; + case OPT_Wunknown_pragmas: /* Set to greater than 1, so that even unknown pragmas in system headers will be warned about. */ diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 82c992c1f5a..458d453cdd8 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -295,6 +295,16 @@ Wall C ObjC C++ ObjC++ Warning Enable most warning messages. +Walloca +C ObjC C++ ObjC++ Var(warn_alloca) Warning +Warn on any use of alloca. + +Walloca-larger-than= +C ObjC C++ ObjC++ Var(warn_alloca_limit) Warning Joined RejectNegative UInteger +-Walloca-larger-than= Warn on unbounded uses of +alloca, and on bounded uses of alloca whose bound can be larger than + bytes. + Warray-bounds LangEnabledBy(C ObjC C++ ObjC++,Wall) ; in common.opt @@ -1030,6 +1040,12 @@ Wvla C ObjC C++ ObjC++ Var(warn_vla) Init(-1) Warning Warn if a variable length array is used. +Wvla-larger-than= +C ObjC C++ ObjC++ Var(warn_vla_limit) Warning Joined RejectNegative UInteger +-Wvla-larger-than= Warn on unbounded uses of variable-length arrays, and +on bounded uses of variable-length arrays whose bound can be +larger than bytes. + Wvolatile-register-var C ObjC C++ ObjC++ Var(warn_volatile_register_var) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall) Warn when a register variable is declared volatile. diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 795ad1b63a9..556ad36be0d 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -255,6 +255,7 @@ Objective-C and Objective-C++ Dialects}. @gccoptlist{-fsyntax-only -fmax-errors=@var{n} -Wpedantic @gol -pedantic-errors @gol -w -Wextra -Wall -Waddress -Waggregate-return @gol +-Walloca -Walloca-larger-than=@var{n} @gol -Wno-aggressive-loop-optimizations -Warray-bounds -Warray-bounds=@var{n} @gol -Wno-attributes -Wbool-compare -Wbool-operation @gol -Wno-builtin-macro-redefined -Wc90-c99-compat -Wc99-c11-compat @gol @@ -313,7 +314,7 @@ Objective-C and Objective-C++ Dialects}. -Wunused-const-variable -Wunused-const-variable=@var{n} @gol -Wunused-but-set-parameter -Wunused-but-set-variable @gol -Wuseless-cast -Wvariadic-macros -Wvector-operation-performance @gol --Wvla -Wvolatile-register-var -Wwrite-strings @gol +-Wvla -Wvla-larger-than=@var{n} -Wvolatile-register-var -Wwrite-strings @gol -Wzero-as-null-pointer-constant -Whsa} @item C and Objective-C-only Warning Options @@ -4976,6 +4977,73 @@ annotations. Warn about overriding virtual functions that are not marked with the override keyword. +@item -Walloca +@opindex Wno-alloca +@opindex Walloca +This option warns on all uses of @code{alloca} in the source. + +@item -Walloca-larger-than=@var{n} +This option warns on calls to @code{alloca} that are not bounded by a +controlling predicate limiting its size to @var{n} bytes, or calls to +@code{alloca} where the bound is unknown. + +For example, a bounded case of @code{alloca} could be: + +@smallexample +void func (size_t n) +@{ + void *p; + if (n <= 1000) + p = alloca (n); + else + p = malloc (n); + f (p); +@} +@end smallexample + +In the above example, passing @code{-Walloca=1000} would not issue a +warning because the call to @code{alloca} is known to be at most 1000 +bytes. However, if @code{-Walloca=500} was passed, the compiler would +have emitted a warning. + +Unbounded uses, on the other hand, are uses of @code{alloca} with no +controlling predicate verifying its size. For example: + +@smallexample +void func () +@{ + void *p = alloca (n); + f (p); +@} +@end smallexample + +If @code{-Walloca=500} was passed, the above would trigger a warning, +but this time because of the lack of bounds checking. + +Note, that even seemingly correct code involving signed integers could +cause a warning: + +@smallexample +void func (signed int n) +@{ + if (n < 500) + @{ + p = alloca (n); + f (p); + @} +@} +@end smallexample + +In the above example, @var{n} could be negative, causing a larger than +expected argument to be implicitly casted into the @code{alloca} call. + +This option also warns when @code{alloca} is used in a loop. + +This warning is not enabled by @option{-Wall}, and is only active when +@option{-ftree-vrp} is active (default for @option{-O2} and above). + +See also @option{-Wvla-larger-than=@var{n}}. + @item -Warray-bounds @itemx -Warray-bounds=@var{n} @opindex Wno-array-bounds @@ -6172,9 +6240,25 @@ moving from a moved-from object, this warning can be disabled. @item -Wvla @opindex Wvla @opindex Wno-vla -Warn if variable length array is used in the code. +Warn if a variable-length array is used in the code. @option{-Wno-vla} prevents the @option{-Wpedantic} warning of -the variable length array. +the variable-length array. + +@item -Wvla-larger-than=@var{n} +If this option is used, the compiler will warn on uses of +variable-length arrays where the size is either unbounded, or bounded +by an argument that can be larger than @var{n} bytes. This is similar +to how @option{-Walloca-larger-than=@var{n}} works, but with +variable-length arrays. + +Note that GCC may optimize small variable-length arrays of a known +value into plain arrays, so this warning may not get triggered for +such arrays. + +This warning is not enabled by @option{-Wall}, and is only active when +@option{-ftree-vrp} is active (default for @option{-O2} and above). + +See also @option{-Walloca-larger-than=@var{n}}. @item -Wvolatile-register-var @opindex Wvolatile-register-var diff --git a/gcc/gimple-ssa-warn-alloca.c b/gcc/gimple-ssa-warn-alloca.c new file mode 100644 index 00000000000..e75f2fa76fc --- /dev/null +++ b/gcc/gimple-ssa-warn-alloca.c @@ -0,0 +1,524 @@ +/* Warn on problematic uses of alloca and variable length arrays. + Copyright (C) 2016 Free Software Foundation, Inc. + Contributed by Aldy Hernandez . + +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 +. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "backend.h" +#include "tree.h" +#include "gimple.h" +#include "tree-pass.h" +#include "ssa.h" +#include "gimple-pretty-print.h" +#include "diagnostic-core.h" +#include "fold-const.h" +#include "gimple-iterator.h" +#include "tree-ssa.h" +#include "params.h" +#include "tree-cfg.h" +#include "calls.h" +#include "cfgloop.h" +#include "intl.h" + +const pass_data pass_data_walloca = { + GIMPLE_PASS, + "walloca", + OPTGROUP_NONE, + TV_NONE, + PROP_cfg, // properties_required + 0, // properties_provided + 0, // properties_destroyed + 0, // properties_start + 0, // properties_finish +}; + +class pass_walloca : public gimple_opt_pass +{ +public: + pass_walloca (gcc::context *ctxt) + : gimple_opt_pass(pass_data_walloca, ctxt), first_time_p (false) + {} + opt_pass *clone () { return new pass_walloca (m_ctxt); } + void set_pass_param (unsigned int n, bool param) + { + gcc_assert (n == 0); + first_time_p = param; + } + virtual bool gate (function *); + virtual unsigned int execute (function *); + + private: + // Set to TRUE the first time we run this pass on a function. + bool first_time_p; +}; + +bool +pass_walloca::gate (function *fun ATTRIBUTE_UNUSED) +{ + // The first time this pass is called, it is called before + // optimizations have been run and range information is unavailable, + // so we can only perform strict alloca checking. + if (first_time_p) + return warn_alloca != 0; + + return warn_alloca_limit > 0 || warn_vla_limit > 0; +} + +// Possible problematic uses of alloca. +enum alloca_type { + // Alloca argument is within known bounds that are appropriate. + ALLOCA_OK, + + // Alloca argument is KNOWN to have a value that is too large. + ALLOCA_BOUND_DEFINITELY_LARGE, + + // Alloca argument may be too large. + ALLOCA_BOUND_MAYBE_LARGE, + + // Alloca argument is bounded but of an indeterminate size. + ALLOCA_BOUND_UNKNOWN, + + // Alloca argument was casted from a signed integer. + ALLOCA_CAST_FROM_SIGNED, + + // Alloca appears in a loop. + ALLOCA_IN_LOOP, + + // Alloca argument is 0. + ALLOCA_ARG_IS_ZERO, + + // Alloca call is unbounded. That is, there is no controlling + // predicate for its argument. + ALLOCA_UNBOUNDED +}; + +// Type of an alloca call with its corresponding limit, if applicable. +struct alloca_type_and_limit { + enum alloca_type type; + // For ALLOCA_BOUND_MAYBE_LARGE and ALLOCA_BOUND_DEFINITELY_LARGE + // types, this field indicates the assumed limit if known or + // integer_zero_node if unknown. For any other alloca types, this + // field is undefined. + wide_int limit; + alloca_type_and_limit (); + alloca_type_and_limit (enum alloca_type type, + wide_int i) : type(type), limit(i) { } + alloca_type_and_limit (enum alloca_type type) : type(type) { } +}; + +// NOTE: When we get better range info, this entire function becomes +// irrelevant, as it should be possible to get range info for an SSA +// name at any point in the program. +// +// We have a few heuristics up our sleeve to determine if a call to +// alloca() is within bounds. Try them out and return the type of +// alloca call with its assumed limit (if applicable). +// +// Given a known argument (ARG) to alloca() and an EDGE (E) +// calculating said argument, verify that the last statement in the BB +// in E->SRC is a gate comparing ARG to an acceptable bound for +// alloca(). See examples below. +// +// If set, ARG_CASTED is the possible unsigned argument to which ARG +// was casted to. This is to handle cases where the controlling +// predicate is looking at a casted value, not the argument itself. +// arg_casted = (size_t) arg; +// if (arg_casted < N) +// goto bb3; +// else +// goto bb5; +// +// MAX_SIZE is WARN_ALLOCA= adjusted for VLAs. It is the maximum size +// in bytes we allow for arg. + +static struct alloca_type_and_limit +alloca_call_type_by_arg (tree arg, tree arg_casted, edge e, unsigned max_size) +{ + basic_block bb = e->src; + gimple_stmt_iterator gsi = gsi_last_bb (bb); + gimple *last = gsi_stmt (gsi); + if (!last || gimple_code (last) != GIMPLE_COND) + return alloca_type_and_limit (ALLOCA_UNBOUNDED); + + enum tree_code cond_code = gimple_cond_code (last); + if (e->flags & EDGE_TRUE_VALUE) + ; + else if (e->flags & EDGE_FALSE_VALUE) + cond_code = invert_tree_comparison (cond_code, false); + else + return alloca_type_and_limit (ALLOCA_UNBOUNDED); + + // Check for: + // if (ARG .COND. N) + // goto ; + // else + // goto ; + // : + // alloca(ARG); + if ((cond_code == LE_EXPR + || cond_code == LT_EXPR + || cond_code == GT_EXPR + || cond_code == GE_EXPR) + && (gimple_cond_lhs (last) == arg + || gimple_cond_lhs (last) == arg_casted)) + { + if (TREE_CODE (gimple_cond_rhs (last)) == INTEGER_CST) + { + tree rhs = gimple_cond_rhs (last); + int tst = wi::cmpu (wi::to_widest (rhs), max_size); + if ((cond_code == LT_EXPR && tst == -1) + || (cond_code == LE_EXPR && (tst == -1 || tst == 0))) + return alloca_type_and_limit (ALLOCA_OK); + else + { + // Let's not get too specific as to how large the limit + // may be. Someone's clearly an idiot when things + // degrade into "if (N > Y) alloca(N)". + if (cond_code == GT_EXPR || cond_code == GE_EXPR) + rhs = integer_zero_node; + return alloca_type_and_limit (ALLOCA_BOUND_MAYBE_LARGE, rhs); + } + } + else + return alloca_type_and_limit (ALLOCA_BOUND_UNKNOWN); + } + + // Similarly, but check for a comparison with an unknown LIMIT. + // if (LIMIT .COND. ARG) + // alloca(arg); + // + // Where LIMIT has a bound of unknown range. + // + // Note: All conditions of the form (ARG .COND. XXXX) where covered + // by the previous check above, so we only need to look for (LIMIT + // .COND. ARG) here. + tree limit = gimple_cond_lhs (last); + if ((gimple_cond_rhs (last) == arg + || gimple_cond_rhs (last) == arg_casted) + && TREE_CODE (limit) == SSA_NAME) + { + wide_int min, max; + value_range_type range_type = get_range_info (limit, &min, &max); + + if (range_type == VR_UNDEFINED || range_type == VR_VARYING) + return alloca_type_and_limit (ALLOCA_BOUND_UNKNOWN); + + // ?? It looks like the above `if' is unnecessary, as we never + // get any VR_RANGE or VR_ANTI_RANGE here. If we had a range + // for LIMIT, I suppose we would have taken care of it in + // alloca_call_type(), or handled above where we handle (ARG .COND. N). + // + // If this ever triggers, we should probably figure out why and + // handle it, though it is likely to be just an ALLOCA_UNBOUNDED. + return alloca_type_and_limit (ALLOCA_UNBOUNDED); + } + + return alloca_type_and_limit (ALLOCA_UNBOUNDED); +} + +// Return TRUE if SSA's definition is a cast from a signed type. +// If so, set *INVALID_CASTED_TYPE to the signed type. + +static bool +cast_from_signed_p (tree ssa, tree *invalid_casted_type) +{ + gimple *def = SSA_NAME_DEF_STMT (ssa); + if (def + && !gimple_nop_p (def) + && gimple_assign_cast_p (def) + && !TYPE_UNSIGNED (TREE_TYPE (gimple_assign_rhs1 (def)))) + { + *invalid_casted_type = TREE_TYPE (gimple_assign_rhs1 (def)); + return true; + } + return false; +} + +// Return TRUE if X has a maximum range of MAX, basically covering the +// entire domain, in which case it's no range at all. + +static bool +is_max (tree x, wide_int max) +{ + return wi::max_value (TREE_TYPE (x)) == max; +} + +// Analyze the alloca call in STMT and return the alloca type with its +// corresponding limit (if applicable). IS_VLA is set if the alloca +// call is really a BUILT_IN_ALLOCA_WITH_ALIGN, signifying a VLA. +// +// If the alloca call may be too large because of a cast from a signed +// type to an unsigned type, set *INVALID_CASTED_TYPE to the +// problematic signed type. + +static struct alloca_type_and_limit +alloca_call_type (gimple *stmt, bool is_vla, tree *invalid_casted_type) +{ + gcc_assert (gimple_alloca_call_p (stmt)); + tree len = gimple_call_arg (stmt, 0); + tree len_casted = NULL; + wide_int min, max; + struct alloca_type_and_limit ret = alloca_type_and_limit (ALLOCA_UNBOUNDED); + + gcc_assert (!is_vla || warn_vla_limit > 0); + gcc_assert (is_vla || warn_alloca_limit > 0); + + // Adjust warn_alloca_max_size for VLAs, by taking the underlying + // type into account. + unsigned HOST_WIDE_INT max_size; + if (is_vla) + max_size = (unsigned HOST_WIDE_INT) warn_vla_limit; + else + max_size = (unsigned HOST_WIDE_INT) warn_alloca_limit; + + // Check for the obviously bounded case. + if (TREE_CODE (len) == INTEGER_CST) + { + if (tree_to_uhwi (len) > max_size) + return alloca_type_and_limit (ALLOCA_BOUND_DEFINITELY_LARGE, len); + if (integer_zerop (len)) + return alloca_type_and_limit (ALLOCA_ARG_IS_ZERO); + ret = alloca_type_and_limit (ALLOCA_OK); + } + // Check the range info if available. + else if (value_range_type range_type = get_range_info (len, &min, &max)) + { + if (range_type == VR_RANGE) + { + if (wi::leu_p (max, max_size)) + ret = alloca_type_and_limit (ALLOCA_OK); + else + { + // A cast may have created a range we don't care + // about. For instance, a cast from 16-bit to + // 32-bit creates a range of 0..65535, even if there + // is not really a determinable range in the + // underlying code. In this case, look through the + // cast at the original argument, and fall through + // to look at other alternatives. + // + // We only look at through the cast when its from + // unsigned to unsigned, otherwise we may risk + // looking at SIGNED_INT < N, which is clearly not + // what we want. In this case, we'd be interested + // in a VR_RANGE of [0..N]. + // + // Note: None of this is perfect, and should all go + // away with better range information. But it gets + // most of the cases. + gimple *def = SSA_NAME_DEF_STMT (len); + if (gimple_assign_cast_p (def) + && TYPE_UNSIGNED (TREE_TYPE (gimple_assign_rhs1 (def)))) + + { + len_casted = gimple_assign_rhs1 (def); + range_type = get_range_info (len_casted, &min, &max); + } + // An unknown range or a range of the entire domain is + // really no range at all. + if (range_type == VR_VARYING + || (!len_casted && is_max (len, max)) + || (len_casted && is_max (len_casted, max))) + { + // Fall through. + } + else if (range_type != VR_VARYING) + return + alloca_type_and_limit (ALLOCA_BOUND_MAYBE_LARGE, max); + } + } + else if (range_type == VR_ANTI_RANGE) + { + // There may be some wrapping around going on. Catch it + // with this heuristic. Hopefully, this VR_ANTI_RANGE + // nonsense will go away, and we won't have to catch the + // sign conversion problems with this crap. + if (cast_from_signed_p (len, invalid_casted_type)) + return alloca_type_and_limit (ALLOCA_CAST_FROM_SIGNED); + } + // No easily determined range and try other things. + } + + // If we couldn't find anything, try a few heuristics for things we + // can easily determine. Check these misc cases but only accept + // them if all predecessors have a known bound. + basic_block bb = gimple_bb (stmt); + if (ret.type == ALLOCA_UNBOUNDED) + { + ret.type = ALLOCA_OK; + for (unsigned ix = 0; ix < EDGE_COUNT (bb->preds); ix++) + { + gcc_assert (!len_casted || TYPE_UNSIGNED (TREE_TYPE (len_casted))); + ret = alloca_call_type_by_arg (len, len_casted, + EDGE_PRED (bb, ix), max_size); + if (ret.type != ALLOCA_OK) + return ret; + } + } + + return ret; +} + +// Return TRUE if the alloca call in STMT is in a loop, otherwise +// return FALSE. As an exception, ignore alloca calls for VLAs that +// occur in a loop since those will be cleaned up when they go out of +// scope. + +static bool +in_loop_p (bool is_vla, gimple *stmt) +{ + basic_block bb = gimple_bb (stmt); + if (bb->loop_father + && bb->loop_father->header != ENTRY_BLOCK_PTR_FOR_FN (cfun)) + { + // Do not warn on VLAs occurring in a loop, since VLAs are + // guaranteed to be cleaned up when they go out of scope. + // That is, there is a corresponding __builtin_stack_restore + // at the end of the scope in which the VLA occurs. + tree fndecl = gimple_call_fn (stmt); + while (TREE_CODE (fndecl) == ADDR_EXPR) + fndecl = TREE_OPERAND (fndecl, 0); + if (DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL + && is_vla + && DECL_FUNCTION_CODE (fndecl) == BUILT_IN_ALLOCA_WITH_ALIGN) + return false; + + return true; + } + return false; +} + +unsigned int +pass_walloca::execute (function *fun) +{ + basic_block bb; + FOR_EACH_BB_FN (bb, fun) + { + for (gimple_stmt_iterator si = gsi_start_bb (bb); !gsi_end_p (si); + gsi_next (&si)) + { + gimple *stmt = gsi_stmt (si); + location_t loc = gimple_location (stmt); + + if (!gimple_alloca_call_p (stmt)) + continue; + gcc_assert (gimple_call_num_args (stmt) >= 1); + + bool is_vla = gimple_alloca_call_p (stmt) + && gimple_call_alloca_for_var_p (as_a (stmt)); + + // Strict mode whining for VLAs is handled by the front-end, + // so we can safely ignore this case. Also, ignore VLAs if + // the user doesn't care about them. + if (is_vla + && (warn_vla > 0 || !warn_vla_limit)) + continue; + + if (!is_vla && (warn_alloca || !warn_alloca_limit)) + { + if (warn_alloca) + warning_at (loc, OPT_Walloca, G_("use of %")); + continue; + } + + tree invalid_casted_type = NULL; + struct alloca_type_and_limit t + = alloca_call_type (stmt, is_vla, &invalid_casted_type); + + // Even if we think the alloca call is OK, make sure it's + // not in a loop. + if (t.type == ALLOCA_OK && in_loop_p (is_vla, stmt)) + t = alloca_type_and_limit (ALLOCA_IN_LOOP); + + enum opt_code wcode + = is_vla ? OPT_Wvla_larger_than_ : OPT_Walloca_larger_than_; + char buff[WIDE_INT_MAX_PRECISION / 4 + 4]; + switch (t.type) + { + case ALLOCA_OK: + break; + case ALLOCA_BOUND_MAYBE_LARGE: + if (warning_at (loc, wcode, + is_vla ? G_("argument to variable-length array " + "may be too large") + : G_("argument to % may be too large")) + && t.limit != integer_zero_node) + { + print_decu (t.limit, buff); + inform (loc, G_("limit is %u bytes, but argument " + "may be as large as %s"), + is_vla ? warn_vla_limit : warn_alloca_limit, buff); + } + break; + case ALLOCA_BOUND_DEFINITELY_LARGE: + if (warning_at (loc, wcode, + is_vla ? G_("argument to variable-length array " + "is too large") + : G_("argument to % is too large")) + && t.limit != integer_zero_node) + { + print_decu (t.limit, buff); + inform (loc, G_("limit is %u bytes, but argument is %s"), + is_vla ? warn_vla_limit : warn_alloca_limit, buff); + } + break; + case ALLOCA_BOUND_UNKNOWN: + warning_at (loc, wcode, + is_vla ? G_("variable-length array bound is unknown") + : G_("% bound is unknown")); + break; + case ALLOCA_UNBOUNDED: + warning_at (loc, wcode, + is_vla ? G_("unbounded use of variable-length array") + : G_("unbounded use of %")); + break; + case ALLOCA_IN_LOOP: + gcc_assert (!is_vla); + warning_at (loc, wcode, G_("use of % within a loop")); + break; + case ALLOCA_CAST_FROM_SIGNED: + gcc_assert (invalid_casted_type != NULL_TREE); + warning_at (loc, wcode, + is_vla ? G_("argument to variable-length array " + "may be too large due to " + "conversion from %qT to %qT") + : G_("argument to % may be too large " + "due to conversion from %qT to %qT"), + invalid_casted_type, size_type_node); + break; + case ALLOCA_ARG_IS_ZERO: + warning_at (loc, wcode, + is_vla ? G_("argument to variable-length array " + "is zero") + : G_("argument to % is zero")); + break; + default: + gcc_unreachable (); + } + } + } + return 0; +} + +gimple_opt_pass * +make_pass_walloca (gcc::context *ctxt) +{ + return new pass_walloca (ctxt); +} diff --git a/gcc/passes.def b/gcc/passes.def index 1375254e971..51e9aa7eba4 100644 --- a/gcc/passes.def +++ b/gcc/passes.def @@ -44,6 +44,7 @@ along with GCC; see the file COPYING3. If not see NEXT_PASS (pass_expand_omp); NEXT_PASS (pass_build_cgraph_edges); NEXT_PASS (pass_sprintf_length, false); + NEXT_PASS (pass_walloca, /*strict_mode_p=*/true); TERMINATE_PASS_LIST (all_lowering_passes) /* Interprocedural optimization passes. */ @@ -251,6 +252,7 @@ along with GCC; see the file COPYING3. If not see NEXT_PASS (pass_laddress); NEXT_PASS (pass_lim); NEXT_PASS (pass_split_crit_edges); + NEXT_PASS (pass_walloca, false); NEXT_PASS (pass_pre); NEXT_PASS (pass_sink_code); NEXT_PASS (pass_sancov); diff --git a/gcc/testsuite/gcc.dg/Walloca-1.c b/gcc/testsuite/gcc.dg/Walloca-1.c new file mode 100644 index 00000000000..34a20c3e6a9 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Walloca-1.c @@ -0,0 +1,63 @@ +/* { dg-do compile } */ +/* { dg-options "-Walloca-larger-than=2000 -O2" } */ + +#define alloca __builtin_alloca + +typedef __SIZE_TYPE__ size_t; +extern size_t strlen(const char *); + +extern void useit (char *); + +int num; + +void foo1 (size_t len, size_t len2, size_t len3) +{ + int i; + + for (i=0; i < 123; ++i) + { + char *s = alloca (566); /* { dg-warning "'alloca' within a loop" } */ + useit (s); + } + + char *s = alloca (123); + useit (s); // OK, constant argument to alloca + + s = alloca (num); // { dg-warning "large due to conversion" } + useit (s); + + s = alloca(90000); /* { dg-warning "is too large" } */ + useit (s); + + if (len < 2000) + { + s = alloca(len); // OK, bounded + useit (s); + } + + if (len + len2 < 2000) // OK, bounded + { + s = alloca(len + len2); + useit (s); + } + + if (len3 <= 2001) + { + s = alloca(len3); /* { dg-warning "may be too large" } */ + useit(s); + } +} + +void foo2 (__SIZE_TYPE__ len) +{ + // Test that a direct call to __builtin_alloca_with_align is not confused + // with a VLA. + void *p = __builtin_alloca_with_align (len, 8); // { dg-warning "unbounded use of 'alloca'" } + useit (p); +} + +void foo3 (unsigned char a) +{ + if (a == 0) + useit (__builtin_alloca (a)); // { dg-warning "argument to 'alloca' is zero" } +} diff --git a/gcc/testsuite/gcc.dg/Walloca-10.c b/gcc/testsuite/gcc.dg/Walloca-10.c new file mode 100644 index 00000000000..69549fcc0d5 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Walloca-10.c @@ -0,0 +1,25 @@ +/* { dg-do compile } */ +/* { dg-options "-Walloca-larger-than=2000 -O2" } */ + +// Test when the conditionals are incorrectly reversed. + +void f (void *); +void foo (__SIZE_TYPE__ len) +{ + void *p; + if (len < 500) + p = __builtin_malloc (len); + else + p = __builtin_alloca (len); // { dg-warning "argument to .alloca. may be too large" } + f (p); +} + +void bar (__SIZE_TYPE__ len) +{ + void *p; + if (len > 500) + p = __builtin_alloca (len); // { dg-warning "argument to .alloca. may be too large" } + else + p = __builtin_malloc (len); + f (p); +} diff --git a/gcc/testsuite/gcc.dg/Walloca-2.c b/gcc/testsuite/gcc.dg/Walloca-2.c new file mode 100644 index 00000000000..284b34e323f --- /dev/null +++ b/gcc/testsuite/gcc.dg/Walloca-2.c @@ -0,0 +1,40 @@ +/* { dg-do compile } */ +/* { dg-options "-Walloca-larger-than=2000 -O2" } */ + +void f (void *); + +void +g1 (int n) +{ + void *p; + if (n > 0 && n < 2000) + p = __builtin_alloca (n); + else + p = __builtin_malloc (n); + f (p); +} + +void +g2 (int n) +{ + void *p; + if (n < 2000) + p = __builtin_alloca (n); // { dg-warning "large due to conversion" } + else + p = __builtin_malloc (n); + f (p); +} + +void +g3 (int n) +{ + void *p; + if (n > 0 && n < 3000) + { + p = __builtin_alloca (n); // { dg-warning "'alloca' may be too large" } + // { dg-message "note:.*argument may be as large as 2999" "note" { target *-*-* } 34 } + } + else + p = __builtin_malloc (n); + f (p); +} diff --git a/gcc/testsuite/gcc.dg/Walloca-3.c b/gcc/testsuite/gcc.dg/Walloca-3.c new file mode 100644 index 00000000000..5345197eba8 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Walloca-3.c @@ -0,0 +1,33 @@ +/* { dg-do compile } */ +/* { dg-options "-Walloca-larger-than=2000 -O2" } */ + +void f (void *); + +__SIZE_TYPE__ LIMIT; + +// Warn when there is an alloca bound, but it is an unknown bound. + +void +g1 (__SIZE_TYPE__ n) +{ + void *p; + if (n < LIMIT) + p = __builtin_alloca (n); // { dg-warning "'alloca' bound is unknown" } + else + p = __builtin_malloc (n); + f (p); +} + +// Similar to the above, but do not get confused by the upcast. + +unsigned short SHORT_LIMIT; +void +g2 (unsigned short n) +{ + void *p; + if (n < SHORT_LIMIT) + p = __builtin_alloca (n); // { dg-warning "'alloca' bound is unknown" } + else + p = __builtin_malloc (n); + f (p); +} diff --git a/gcc/testsuite/gcc.dg/Walloca-4.c b/gcc/testsuite/gcc.dg/Walloca-4.c new file mode 100644 index 00000000000..a559534acaa --- /dev/null +++ b/gcc/testsuite/gcc.dg/Walloca-4.c @@ -0,0 +1,16 @@ +/* { dg-do compile } */ +/* { dg-options "-Walloca-larger-than=5000 -O2" } */ + + char * + _i18n_number_rewrite (char *w, char *rear_ptr) +{ + + char *src; + _Bool + use_alloca = (((rear_ptr - w) * sizeof (char)) < 4096U); + if (use_alloca) + src = (char *) __builtin_alloca ((rear_ptr - w) * sizeof (char)); + else + src = (char *) __builtin_malloc ((rear_ptr - w) * sizeof (char)); + return src; +} diff --git a/gcc/testsuite/gcc.dg/Walloca-5.c b/gcc/testsuite/gcc.dg/Walloca-5.c new file mode 100644 index 00000000000..5ed1171e0d3 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Walloca-5.c @@ -0,0 +1,32 @@ +/* { dg-do compile } */ +/* { dg-options "-Walloca-larger-than=123 -O2" } */ +/* { dg-xfail-if "Currently broken but Andrew's work should fix this" { *-*-* } } */ + +/* The argument to alloca ends up having a range of 0..MAXINT(32bits), + so we think we have a range because of the upcast. Consequently, + we warn with "alloca may be too large", but we should technically + warn with "unbounded use of alloca". + + We currently drill through casts to figure this stuff out, but we + get confused because it's not just a cast. It's a cast, plus a + multiply. + + : + # RANGE [0, 4294967295] NONZERO 4294967295 + _1 = (long unsigned int) something_4(D); + # RANGE [0, 34359738360] NONZERO 34359738360 + _2 = _1 * 8; + _3 = __builtin_alloca (_2); + + I don't know whether it's even worth such fine-grained warnings. + Perhaps we should generically warn everywhere with "alloca may be + too large". + + I'm hoping that this particular case will be easier to diagnose with + Andrew's work. */ + +void useit(void *); +void foobar(unsigned int something) +{ + useit(__builtin_alloca (something * sizeof (const char *))); // { dg-warning "unbounded use of alloca" "" { xfail *-*-* } } +} diff --git a/gcc/testsuite/gcc.dg/Walloca-6.c b/gcc/testsuite/gcc.dg/Walloca-6.c new file mode 100644 index 00000000000..b4d8d41b062 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Walloca-6.c @@ -0,0 +1,11 @@ +/* { dg-do compile } */ +/* { dg-options "-Walloca-larger-than=256 -O2" } */ +/* { dg-xfail-if "Currently broken but Andrew's work should fix this" { *-*-* } } */ + +void f (void*); +void g (__SIZE_TYPE__ n) +{ + // No warning on this case. Range is easily determinable. + if (n > 0 && n < 256) + f (__builtin_alloca (n)); +} diff --git a/gcc/testsuite/gcc.dg/Walloca-7.c b/gcc/testsuite/gcc.dg/Walloca-7.c new file mode 100644 index 00000000000..d6581a5b20b --- /dev/null +++ b/gcc/testsuite/gcc.dg/Walloca-7.c @@ -0,0 +1,17 @@ +/* { dg-do compile } */ +/* { dg-options "-Walloca -O0" } */ + +extern void f(void *); + +void foo(void) +{ + // Test that strict -Walloca works even without optimization. + f (__builtin_alloca(500)); // { dg-warning "use of 'alloca'" } +} + +void bar(void) +{ + // Test that we warn on alloca() calls, not just __builtin_alloca calls. + extern void *alloca(__SIZE_TYPE__); + f (alloca (123)); // { dg-warning "use of 'alloca'" } +} diff --git a/gcc/testsuite/gcc.dg/Walloca-8.c b/gcc/testsuite/gcc.dg/Walloca-8.c new file mode 100644 index 00000000000..a4a120485cd --- /dev/null +++ b/gcc/testsuite/gcc.dg/Walloca-8.c @@ -0,0 +1,12 @@ +/* { dg-do compile } */ +/* { dg-options "-Walloca-larger-than=2000 -O2" } */ + +void *p; +void +foo (__SIZE_TYPE__ len) +{ + if (len < 2000 / sizeof (void *)) + p = __builtin_alloca (len * sizeof (void *)); + else + p = __builtin_malloc (len * sizeof (void *)); +} diff --git a/gcc/testsuite/gcc.dg/Walloca-9.c b/gcc/testsuite/gcc.dg/Walloca-9.c new file mode 100644 index 00000000000..c67d9d63255 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Walloca-9.c @@ -0,0 +1,19 @@ +/* { dg-do compile } */ +/* { dg-options "-Walloca-larger-than=5000 -O2" } */ + +extern void useit(char *); + +int +foobar (unsigned short length) +{ + char *pbuf; + __SIZE_TYPE__ size = (__SIZE_TYPE__) length; + + if (size < 4032) + pbuf = (char *) __builtin_alloca(size); + else + pbuf = (char *) __builtin_malloc (size); + + useit(pbuf); + return 0; +} diff --git a/gcc/testsuite/gcc.dg/Wvla-1.c b/gcc/testsuite/gcc.dg/Wvla-1.c new file mode 100644 index 00000000000..384c930c995 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wvla-1.c @@ -0,0 +1,24 @@ +/* { dg-do compile } */ +/* { dg-options "-Wvla-larger-than=100 -O2" } */ + +typedef __SIZE_TYPE__ size_t; + +extern void useit (char *); + +int num; + +void test_vlas (size_t num) +{ + char str2[num]; /* { dg-warning "unbounded use" } */ + useit(str2); + + num = 98; + for (int i=0; i < 1234; ++i) { + char str[num]; // OK, VLA in a loop, but it is a + // known size *AND* the compiler takes + // care of cleaning up between + // iterations with + // __builtin_stack_restore. + useit(str); + } +} diff --git a/gcc/testsuite/gcc.dg/Wvla-2.c b/gcc/testsuite/gcc.dg/Wvla-2.c new file mode 100644 index 00000000000..96814dcaac0 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wvla-2.c @@ -0,0 +1,70 @@ +/* { dg-do compile } */ +/* { dg-require-effective-target stdint_types } */ +/* { dg-options "-O2 -Wvla-larger-than=40" } */ + +#include + +void f0 (void *); +void +f1 (__SIZE_TYPE__ a) +{ + if (a <= 10) + { + // 10 * 4 bytes = 40: OK! + uint32_t x[a]; + f0 (x); + } +} + +void +f2 (__SIZE_TYPE__ a) +{ + if (a <= 11) + { + // 11 * 4 bytes = 44: Not OK. + uint32_t x[a]; // { dg-warning "array may be too large" } + // { dg-message "note:.*argument may be as large as 44" "note" { target *-*-* } 25 } + f0 (x); + } +} + +void +f3 (__SIZE_TYPE__ a, __SIZE_TYPE__ b) +{ + if (a <= 5 && b <= 3) + { + // 5 * 3 * 4 bytes = 60: Not OK. + uint32_t x[a][b]; // { dg-warning "array may be too large" } + f0 (x); + } +} + +void +f4 (__SIZE_TYPE__ a, __SIZE_TYPE__ b) +{ + if (a <= 5 && b <= 2) + { + // 5 * 2 * 4 bytes = 40 bytes: OK! + uint32_t x[a][b]; + f0 (x); + } +} + +void +f5 (__SIZE_TYPE__ len) +{ + // Test that a direct call to __builtin_alloca_with_align is not + // confused with a VLA. + void *p = __builtin_alloca_with_align (len, 8); + f0 (p); +} + +void +f6 (unsigned stuff) +{ + int n = 7000; + do { + char a[n]; // { dg-warning "variable-length array is too large" } + f0 (a); + } while (stuff--); +} diff --git a/gcc/testsuite/gcc.dg/Wvla-3.c b/gcc/testsuite/gcc.dg/Wvla-3.c new file mode 100644 index 00000000000..5124476d71f --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wvla-3.c @@ -0,0 +1,12 @@ +/* { dg-do compile } */ +/* { dg-options "-Walloca -O2" } */ + +// Make sure we don't warn on VLA with -Walloca. + +void f (void*); + +void h1 (unsigned n) +{ + int a [n]; + f (a); +} diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h index 5903fde0bb5..dda693b213e 100644 --- a/gcc/tree-pass.h +++ b/gcc/tree-pass.h @@ -472,6 +472,7 @@ extern simple_ipa_opt_pass *make_pass_ipa_oacc_kernels (gcc::context *ctxt); 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); /* IPA Passes */ extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ctxt); -- 2.30.2