Extend -Warray-bounds to detect out-of-bounds accesses to array parameters.

gcc/ChangeLog:

	PR middle-end/82608
	PR middle-end/94195
	PR c/50584
	PR middle-end/84051
	* gimple-array-bounds.cc (get_base_decl): New function.
	(get_ref_size): New function.
	(trailing_array): New function.
	(array_bounds_checker::check_array_ref): Call them.  Handle arrays
	declared in function parameters.
	(array_bounds_checker::check_mem_ref):  Same.  Handle references to
	dynamically allocated arrays.

gcc/testsuite/ChangeLog:

	PR middle-end/82608
	PR middle-end/94195
	PR c/50584
	PR middle-end/84051
	* c-c++-common/Warray-bounds.c: Adjust.
	* gcc.dg/Wbuiltin-declaration-mismatch-9.c: Adjust.
	* gcc.dg/Warray-bounds-63.c: New test.
	* gcc.dg/Warray-bounds-64.c: New test.
	* gcc.dg/Warray-bounds-65.c: New test.
	* gcc.dg/Warray-bounds-66.c: New test.
	* gcc.dg/Warray-bounds-67.c: New test.
This commit is contained in:
Martin Sebor 2020-09-19 17:47:29 -06:00
parent baad4c48a8
commit 3f9a497d1b
8 changed files with 944 additions and 80 deletions

View File

@ -36,6 +36,8 @@ along with GCC; see the file COPYING3. If not see
#include "vr-values.h"
#include "domwalk.h"
#include "tree-cfg.h"
#include "attribs.h"
#include "builtins.h"
// This purposely returns a value_range, not a value_range_equiv, to
// break the dependency on equivalences for this pass.
@ -46,19 +48,137 @@ array_bounds_checker::get_value_range (const_tree op)
return ranges->get_value_range (op);
}
/* Try to determine the DECL that REF refers to. Return the DECL or
the expression closest to it. Used in informational notes pointing
to referenced objects or function parameters. */
static tree
get_base_decl (tree ref)
{
tree base = get_base_address (ref);
if (DECL_P (base))
return base;
if (TREE_CODE (base) == MEM_REF)
base = TREE_OPERAND (base, 0);
if (TREE_CODE (base) != SSA_NAME)
return base;
do
{
gimple *def = SSA_NAME_DEF_STMT (base);
if (gimple_assign_single_p (def))
{
base = gimple_assign_rhs1 (def);
if (TREE_CODE (base) != ASSERT_EXPR)
return base;
base = TREE_OPERAND (base, 0);
if (TREE_CODE (base) != SSA_NAME)
return base;
continue;
}
if (!gimple_nop_p (def))
return base;
break;
} while (true);
tree var = SSA_NAME_VAR (base);
if (TREE_CODE (var) != PARM_DECL)
return base;
return var;
}
/* Return the constant byte size of the object or type referenced by
the MEM_REF ARG. On success, set *PREF to the DECL or expression
ARG refers to. Otherwise return null. */
static tree
get_ref_size (tree arg, tree *pref)
{
if (TREE_CODE (arg) != MEM_REF)
return NULL_TREE;
arg = TREE_OPERAND (arg, 0);
tree type = TREE_TYPE (arg);
if (!POINTER_TYPE_P (type))
return NULL_TREE;
type = TREE_TYPE (type);
if (TREE_CODE (type) != ARRAY_TYPE)
return NULL_TREE;
tree nbytes = TYPE_SIZE_UNIT (type);
if (!nbytes || TREE_CODE (nbytes) != INTEGER_CST)
return NULL_TREE;
*pref = get_base_decl (arg);
return nbytes;
}
/* Return true if REF is (likely) an ARRAY_REF to a trailing array member
of a struct. It refines array_at_struct_end_p by detecting a pointer
to an array and an array parameter declared using the [N] syntax (as
opposed to a pointer) and returning false. Set *PREF to the decl or
expression REF refers to. */
static bool
trailing_array (tree arg, tree *pref)
{
tree ref = arg;
tree base = get_base_decl (arg);
while (TREE_CODE (ref) == ARRAY_REF || TREE_CODE (ref) == MEM_REF)
ref = TREE_OPERAND (ref, 0);
if (TREE_CODE (ref) == COMPONENT_REF)
{
*pref = TREE_OPERAND (ref, 1);
tree type = TREE_TYPE (*pref);
if (TREE_CODE (type) == ARRAY_TYPE)
{
/* A multidimensional trailing array is not considered special
no matter what its major bound is. */
type = TREE_TYPE (type);
if (TREE_CODE (type) == ARRAY_TYPE)
return false;
}
}
else
*pref = base;
tree basetype = TREE_TYPE (base);
if (TREE_CODE (base) == PARM_DECL
&& POINTER_TYPE_P (basetype))
{
tree ptype = TREE_TYPE (basetype);
if (TREE_CODE (ptype) == ARRAY_TYPE)
return false;
}
return array_at_struct_end_p (arg);
}
/* Checks one ARRAY_REF in REF, located at LOCUS. Ignores flexible
arrays and "struct" hacks. If VRP can determine that the array
subscript is a constant, check if it is outside valid range. If
the array subscript is a RANGE, warn if it is non-overlapping with
valid range. IGNORE_OFF_BY_ONE is true if the ARRAY_REF is inside
a ADDR_EXPR. Returns true if a warning has been issued. */
a ADDR_EXPR. Return true if a warning has been issued or if
no-warning is set. */
bool
array_bounds_checker::check_array_ref (location_t location, tree ref,
bool ignore_off_by_one)
{
if (TREE_NO_WARNING (ref))
return false;
/* Return true to have the caller prevent warnings for enclosing
refs. */
return true;
tree low_sub = TREE_OPERAND (ref, 1);
tree up_sub = low_sub;
@ -74,8 +194,7 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
if (!up_bound
|| TREE_CODE (up_bound) != INTEGER_CST
|| (warn_array_bounds < 2
&& array_at_struct_end_p (ref)))
|| (warn_array_bounds < 2 && trailing_array (ref, &decl)))
{
/* Accesses to trailing arrays via pointers may access storage
beyond the types array bounds. For such arrays, or for flexible
@ -116,7 +235,14 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
poly_int64 off;
if (tree base = get_addr_base_and_unit_offset (arg, &off))
{
if (!compref && DECL_P (base))
if (TREE_CODE (base) == MEM_REF)
{
/* Try to determine the size from a pointer to
an array if BASE is one. */
if (tree size = get_ref_size (base, &decl))
maxbound = size;
}
else if (!compref && DECL_P (base))
if (tree basesize = DECL_SIZE_UNIT (base))
if (TREE_CODE (basesize) == INTEGER_CST)
{
@ -217,7 +343,13 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
fprintf (dump_file, "\n");
}
ref = decl ? decl : TREE_OPERAND (ref, 0);
/* Avoid more warnings when checking more significant subscripts
of the same expression. */
ref = TREE_OPERAND (ref, 0);
TREE_NO_WARNING (ref) = 1;
if (decl)
ref = decl;
tree rec = NULL_TREE;
if (TREE_CODE (ref) == COMPONENT_REF)
@ -235,8 +367,6 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
inform (DECL_SOURCE_LOCATION (ref), "while referencing %qD", ref);
if (rec && DECL_P (rec))
inform (DECL_SOURCE_LOCATION (rec), "defined here %qD", rec);
TREE_NO_WARNING (ref) = 1;
}
return warned;
@ -266,8 +396,8 @@ array_bounds_checker::check_mem_ref (location_t location, tree ref,
const offset_int maxobjsize = tree_to_shwi (max_object_size ());
/* The array or string constant bounds in bytes. Initially set
to [-MAXOBJSIZE - 1, MAXOBJSIZE] until a tighter bound is
/* The zero-based array or string constant bounds in bytes. Initially
set to [-MAXOBJSIZE - 1, MAXOBJSIZE] until a tighter bound is
determined. */
offset_int arrbounds[2] = { -maxobjsize - 1, maxobjsize };
@ -275,7 +405,7 @@ array_bounds_checker::check_mem_ref (location_t location, tree ref,
to be valid, not only does the final offset/subscript must be
in bounds but all intermediate offsets should be as well.
GCC may be able to deal gracefully with such out-of-bounds
offsets so the checking is only enbaled at -Warray-bounds=2
offsets so the checking is only enabled at -Warray-bounds=2
where it may help detect bugs in uses of the intermediate
offsets that could otherwise not be detectable. */
offset_int ioff = wi::to_offset (fold_convert (ptrdiff_type_node, cstoff));
@ -284,7 +414,10 @@ array_bounds_checker::check_mem_ref (location_t location, tree ref,
/* The range of the byte offset into the reference. */
offset_int offrange[2] = { 0, 0 };
const value_range *vr = NULL;
/* The statement used to allocate the array or null. */
gimple *alloc_stmt = NULL;
/* For an allocation statement, the low bound of the size range. */
offset_int minbound = 0;
/* Determine the offsets and increment OFFRANGE for the bounds of each.
The loop computes the range of the final offset for expressions such
@ -294,6 +427,35 @@ array_bounds_checker::check_mem_ref (location_t location, tree ref,
for (unsigned n = 0; TREE_CODE (arg) == SSA_NAME && n < limit; ++n)
{
gimple *def = SSA_NAME_DEF_STMT (arg);
if (is_gimple_call (def))
{
/* Determine the byte size of the array from an allocation call. */
wide_int sizrng[2];
if (gimple_call_alloc_size (def, sizrng))
{
arrbounds[0] = 0;
arrbounds[1] = offset_int::from (sizrng[1], UNSIGNED);
minbound = offset_int::from (sizrng[0], UNSIGNED);
alloc_stmt = def;
}
break;
}
if (gimple_nop_p (def))
{
/* For a function argument try to determine the byte size
of the array from the current function declaratation
(e.g., attribute access or related). */
wide_int wr[2];
tree ref = gimple_parm_array_size (arg, wr);
if (!ref)
break;
arrbounds[0] = offset_int::from (wr[0], UNSIGNED);
arrbounds[1] = offset_int::from (wr[1], UNSIGNED);
arg = ref;
break;
}
if (!is_gimple_assign (def))
break;
@ -316,7 +478,7 @@ array_bounds_checker::check_mem_ref (location_t location, tree ref,
if (TREE_CODE (varoff) != SSA_NAME)
break;
vr = get_value_range (varoff);
const value_range* const vr = get_value_range (varoff);
if (!vr || vr->undefined_p () || vr->varying_p ())
break;
@ -366,79 +528,104 @@ array_bounds_checker::check_mem_ref (location_t location, tree ref,
offrange[1] = arrbounds[1];
}
if (TREE_CODE (arg) == ADDR_EXPR)
tree reftype = NULL_TREE;
offset_int eltsize = -1;
if (arrbounds[0] >= 0)
{
/* The byte size of the array has already been determined above
based on a pointer ARG. Set ELTSIZE to the size of the type
it points to and REFTYPE to the array with the size, rounded
down as necessary. */
reftype = TREE_TYPE (TREE_TYPE (arg));
if (TREE_CODE (reftype) == ARRAY_TYPE)
reftype = TREE_TYPE (reftype);
if (tree refsize = TYPE_SIZE_UNIT (reftype))
if (TREE_CODE (refsize) == INTEGER_CST)
eltsize = wi::to_offset (refsize);
if (eltsize < 0)
return false;
offset_int nelts = arrbounds[1] / eltsize;
reftype = build_array_type_nelts (reftype, nelts.to_uhwi ());
}
else if (TREE_CODE (arg) == ADDR_EXPR)
{
arg = TREE_OPERAND (arg, 0);
if (TREE_CODE (arg) != STRING_CST
&& TREE_CODE (arg) != PARM_DECL
&& TREE_CODE (arg) != VAR_DECL)
return false;
}
else
return false;
/* The type of the object being referred to. It can be an array,
string literal, or a non-array type when the MEM_REF represents
a reference/subscript via a pointer to an object that is not
an element of an array. Incomplete types are excluded as well
because their size is not known. */
tree reftype = TREE_TYPE (arg);
if (POINTER_TYPE_P (reftype)
|| !COMPLETE_TYPE_P (reftype)
|| TREE_CODE (TYPE_SIZE_UNIT (reftype)) != INTEGER_CST)
return false;
/* The type of the object being referred to. It can be an array,
string literal, or a non-array type when the MEM_REF represents
a reference/subscript via a pointer to an object that is not
an element of an array. Incomplete types are excluded as well
because their size is not known. */
reftype = TREE_TYPE (arg);
if (POINTER_TYPE_P (reftype)
|| !COMPLETE_TYPE_P (reftype)
|| TREE_CODE (TYPE_SIZE_UNIT (reftype)) != INTEGER_CST)
return false;
/* Except in declared objects, references to trailing array members
of structs and union objects are excluded because MEM_REF doesn't
make it possible to identify the member where the reference
originated. */
if (RECORD_OR_UNION_TYPE_P (reftype)
&& (!VAR_P (arg)
|| (DECL_EXTERNAL (arg) && array_at_struct_end_p (ref))))
return false;
/* Except in declared objects, references to trailing array members
of structs and union objects are excluded because MEM_REF doesn't
make it possible to identify the member where the reference
originated. */
if (RECORD_OR_UNION_TYPE_P (reftype)
&& (!VAR_P (arg)
|| (DECL_EXTERNAL (arg) && array_at_struct_end_p (ref))))
return false;
arrbounds[0] = 0;
/* FIXME: Should this be 1 for Fortran? */
arrbounds[0] = 0;
offset_int eltsize;
if (TREE_CODE (reftype) == ARRAY_TYPE)
{
eltsize = wi::to_offset (TYPE_SIZE_UNIT (TREE_TYPE (reftype)));
if (tree dom = TYPE_DOMAIN (reftype))
if (TREE_CODE (reftype) == ARRAY_TYPE)
{
tree bnds[] = { TYPE_MIN_VALUE (dom), TYPE_MAX_VALUE (dom) };
if (TREE_CODE (arg) == COMPONENT_REF)
/* Set to the size of the array element (and adjust below). */
eltsize = wi::to_offset (TYPE_SIZE_UNIT (TREE_TYPE (reftype)));
/* Use log2 of size to convert the array byte size in to its
upper bound in elements. */
const offset_int eltsizelog2 = wi::floor_log2 (eltsize);
if (tree dom = TYPE_DOMAIN (reftype))
{
offset_int size = maxobjsize;
if (tree fldsize = component_ref_size (arg))
size = wi::to_offset (fldsize);
arrbounds[1] = wi::lrshift (size, wi::floor_log2 (eltsize));
tree bnds[] = { TYPE_MIN_VALUE (dom), TYPE_MAX_VALUE (dom) };
if (TREE_CODE (arg) == COMPONENT_REF)
{
offset_int size = maxobjsize;
if (tree fldsize = component_ref_size (arg))
size = wi::to_offset (fldsize);
arrbounds[1] = wi::lrshift (size, eltsizelog2);
}
else if (array_at_struct_end_p (arg) || !bnds[0] || !bnds[1])
arrbounds[1] = wi::lrshift (maxobjsize, eltsizelog2);
else
arrbounds[1] = (wi::to_offset (bnds[1]) - wi::to_offset (bnds[0])
+ 1) * eltsize;
}
else if (array_at_struct_end_p (arg) || !bnds[0] || !bnds[1])
arrbounds[1] = wi::lrshift (maxobjsize, wi::floor_log2 (eltsize));
else
arrbounds[1] = (wi::to_offset (bnds[1]) - wi::to_offset (bnds[0])
+ 1) * eltsize;
arrbounds[1] = wi::lrshift (maxobjsize, eltsizelog2);
/* Determine a tighter bound of the non-array element type. */
tree eltype = TREE_TYPE (reftype);
while (TREE_CODE (eltype) == ARRAY_TYPE)
eltype = TREE_TYPE (eltype);
eltsize = wi::to_offset (TYPE_SIZE_UNIT (eltype));
}
else
arrbounds[1] = wi::lrshift (maxobjsize, wi::floor_log2 (eltsize));
{
eltsize = 1;
tree size = TYPE_SIZE_UNIT (reftype);
if (VAR_P (arg))
if (tree initsize = DECL_SIZE_UNIT (arg))
if (tree_int_cst_lt (size, initsize))
size = initsize;
/* Determine a tighter bound of the non-array element type. */
tree eltype = TREE_TYPE (reftype);
while (TREE_CODE (eltype) == ARRAY_TYPE)
eltype = TREE_TYPE (eltype);
eltsize = wi::to_offset (TYPE_SIZE_UNIT (eltype));
arrbounds[1] = wi::to_offset (size);
}
}
else
{
eltsize = 1;
tree size = TYPE_SIZE_UNIT (reftype);
if (VAR_P (arg))
if (tree initsize = DECL_SIZE_UNIT (arg))
if (tree_int_cst_lt (size, initsize))
size = initsize;
arrbounds[1] = wi::to_offset (size);
}
return false;
offrange[0] += ioff;
offrange[1] += ioff;
@ -448,11 +635,25 @@ array_bounds_checker::check_mem_ref (location_t location, tree ref,
of an array) but always use the stricter bound in diagnostics. */
offset_int ubound = arrbounds[1];
if (ignore_off_by_one)
ubound += 1;
ubound += eltsize;
if (arrbounds[0] == arrbounds[1]
|| offrange[0] >= ubound
|| offrange[1] < arrbounds[0])
bool warned = false;
/* Set if the lower bound of the subscript is out of bounds. */
const bool lboob = (arrbounds[0] == arrbounds[1]
|| offrange[0] >= ubound
|| offrange[1] < arrbounds[0]);
/* Set if only the upper bound of the subscript is out of bounds.
This can happen when using a bigger type to index into an array
of a smaller type, as is common with unsigned char. */
tree axstype = TREE_TYPE (ref);
offset_int axssize = 0;
if (TREE_CODE (axstype) != UNION_TYPE)
if (tree access_size = TYPE_SIZE_UNIT (axstype))
if (TREE_CODE (access_size) == INTEGER_CST)
axssize = wi::to_offset (access_size);
const bool uboob = !lboob && offrange[0] + axssize > ubound;
if (lboob || uboob)
{
/* Treat a reference to a non-array object as one to an array
of a single element. */
@ -471,8 +672,10 @@ array_bounds_checker::check_mem_ref (location_t location, tree ref,
offrange[0] = offrange[0] / wi::to_offset (size);
offrange[1] = offrange[1] / wi::to_offset (size);
}
}
bool warned;
if (lboob)
{
if (offrange[0] == offrange[1])
warned = warning_at (location, OPT_Warray_bounds,
"array subscript %wi is outside array bounds "
@ -484,12 +687,66 @@ array_bounds_checker::check_mem_ref (location_t location, tree ref,
"array bounds of %qT",
offrange[0].to_shwi (),
offrange[1].to_shwi (), reftype);
if (warned && DECL_P (arg))
inform (DECL_SOURCE_LOCATION (arg), "while referencing %qD", arg);
}
else if (uboob && !ignore_off_by_one)
{
tree backtype = reftype;
if (alloc_stmt)
/* If the memory was dynamically allocated refer to it as if
it were an untyped array of bytes. */
backtype = build_array_type_nelts (unsigned_char_type_node,
arrbounds[1].to_uhwi ());
if (warned)
TREE_NO_WARNING (ref) = 1;
return warned;
warned = warning_at (location, OPT_Warray_bounds,
"array subscript %<%T[%wi]%> is partly "
"outside array bounds of %qT",
axstype, offrange[0].to_shwi (), backtype);
}
if (warned)
{
if (DECL_P (arg))
inform (DECL_SOURCE_LOCATION (arg), "while referencing %qD", arg);
else if (alloc_stmt)
{
location_t loc = gimple_location (alloc_stmt);
if (gimple_call_builtin_p (alloc_stmt, BUILT_IN_ALLOCA_WITH_ALIGN))
{
if (minbound == arrbounds[1])
inform (loc, "referencing a variable length array "
"of size %wu", minbound.to_uhwi ());
else
inform (loc, "referencing a variable length array "
"of size between %wu and %wu",
minbound.to_uhwi (), arrbounds[1].to_uhwi ());
}
else if (tree fndecl = gimple_call_fndecl (alloc_stmt))
{
if (minbound == arrbounds[1])
inform (loc, "referencing an object of size %wu "
"allocated by %qD",
minbound.to_uhwi (), fndecl);
else
inform (loc, "referencing an object of size between "
"%wu and %wu allocated by %qD",
minbound.to_uhwi (), arrbounds[1].to_uhwi (), fndecl);
}
else
{
tree fntype = gimple_call_fntype (alloc_stmt);
if (minbound == arrbounds[1])
inform (loc, "referencing an object of size %wu "
"allocated by %qT",
minbound.to_uhwi (), fntype);
else
inform (loc, "referencing an object of size between "
"%wu and %wu allocated by %qT",
minbound.to_uhwi (), arrbounds[1].to_uhwi (), fntype);
}
}
TREE_NO_WARNING (ref) = 1;
return true;
}
if (warn_array_bounds < 2)

View File

@ -2,7 +2,7 @@
large index
{ dg-do compile }
{ dg-require-effective-target alloca }
{ dg-options "-O2 -Warray-bounds -ftrack-macro-expansion=0" } */
{ dg-options "-O2 -Warray-bounds -Wno-stringop-overread -ftrack-macro-expansion=0" } */
#include "../gcc.dg/range.h"

View File

@ -0,0 +1,53 @@
/* PR middle-end/94195 - missing warning reading a smaller object via
an lvalue of a larger type
{ dg-do compile }
{ dg-options "-O2 -Wall" } */
typedef __INT16_TYPE__ int16_t;
typedef __SIZE_TYPE__ size_t;
void* alloca (size_t);
void sink (void*);
void byte_store_to_decl (void)
{
struct S6 { char a[6]; } s; // { dg-message "referencing 's'" }
char *p = (char*)&s;
p[0] = 0; p[1] = 1; p[2] = 2; p[3] = 3; p[4] = 4; p[5] = 5;
p[6] = 6; // { dg-warning "array subscript 6 is outside array bounds of 'struct S6\\\[1]" }
sink (&s);
}
void word_store_to_decl (void)
{
struct S6 { char a[6]; } s; // { dg-message "referencing 's'" }
char *p = (char*)&s;
int16_t *q = (int16_t*)(p + 1);
q[0] = 0; q[1] = 1;
q[2] = 2; // { dg-warning "array subscript 'int16_t {aka short int}\\\[2]' is partly outside array bounds of 'struct S6\\\[1]'" }
sink (&s);
}
void word_store_to_alloc (void)
{
struct S6 { char a[6]; } *p;
p = alloca (sizeof *p); // { dg-message "referencing an object of size 6 allocated by 'alloca'" }
int16_t *q = (int16_t*)((char*)p + 1);
q[0] = 0; q[1] = 1;
q[2] = 2; // { dg-warning "array subscript 'int16_t {aka short int}\\\[2]' is partly outside array bounds of 'unsigned char\\\[6]'" }
sink (p);
}

View File

@ -0,0 +1,60 @@
/* PR c/50584 - No warning for passing small array to C99 static array
declarator
Verify that out-of-bounds accesses to array arguments are diagnosed,
both to ordinary array parameters with constant bounds and to array
parameters declared static. This is the converse of what PR 50584
asks for.
{ dg-do compile }
{ dg-options "-O2 -Wall -Warray-parameter -Wno-vla-paramater" } */
#define NOIPA __attribute__ ((noipa))
void sink (void*, ...);
#define T(...) sink (0, __VA_ARGS__)
NOIPA void fca1 (char a[1])
{
T (a[0]);
T (a[1]); // { dg-warning "-Warray-bounds" }
}
NOIPA void fcas1 (char a[static 1])
{
T (a[0]);
T (a[1]); // { dg-warning "-Warray-bounds" }
}
NOIPA void fca2 (char a[2])
{
T (a[0]); T (a[1]);
T (a[2]); // { dg-warning "-Warray-bounds" }
}
NOIPA void fcas2 (char a[static 2])
{
T (a[0]); T (a[1]);
T (a[2]); // { dg-warning "-Warray-bounds" }
}
NOIPA void fca3 (char a[3])
{
T (a[0]); T (a[1]); T (a[2]);
T (a[3]); // { dg-warning "-Warray-bounds" }
}
NOIPA void fcas3 (char a[static 3])
{
T (a[0]); T (a[1]); T (a[2]);
T (a[3]); // { dg-warning "-Warray-bounds" }
}
NOIPA void fca1_1 (char a[1][1])
{
T (a[0][0]);
T (a[0][1]); // { dg-warning "-Warray-bounds" }
}

View File

@ -0,0 +1,202 @@
/* PR middle-end/84051 - missing -Warray-bounds on an out-of-bounds access
via an array pointer
{ dg-do compile }
{ dg-options "-O2 -Wall -ftrack-macro-expansion=0" } */
void sink (void*, ...);
#define T(x) sink (0, x)
void
test_note (int (*pia3)[3]) // { dg-message "while referencing 'pia3'" }
{
int i = 0;
T ((*pia3)[i++]);
T ((*pia3)[i++]);
T ((*pia3)[i++]);
T ((*pia3)[i++]); // { dg-warning "array subscript 3 is (above|outside) array bounds of 'int\\\[3]'" }
T ((*pia3)[i++]); // { dg-warning "array subscript 4 is (above|outside) array bounds of 'int\\\[3]'" }
{
/* Regrettably, the following isn't diagnosed because it's represented
the same as the possibly valid access below:
MEM[(int *)a_1(D) + 36B] = 0; */
int *p0 = pia3[0];
T (p0[3]); // { dg-warning "array subscript 3 is (above|outside) array bounds of 'int\\\[3]'" "pr?????" { xfail *-*-* } }
int *p1 = pia3[3];
T (p1[0]); // okay
}
}
void test_a1_cst (_Bool (*pba0)[0], char (*pca1)[1],
short (*psa2)[2], int (*pia3)[3])
{
T ((*pba0)[-1]); // { dg-warning "array subscript -1 is (above|outside) array bounds of '_Bool\\\[0]'" }
T ((*pba0)[0]); // { dg-warning "array subscript 0 is (above|outside) array bounds of '_Bool\\\[0]'" }
T ((*pba0)[1]); // { dg-warning "array subscript 1 is (above|outside) array bounds of '_Bool\\\[0]'" }
T ((*pba0)[2]); // { dg-warning "array subscript 2 is (above|outside) array bounds of '_Bool\\\[0]'" }
T ((*pba0)[12]); // { dg-warning "array subscript 12 is (above|outside) array bounds of '_Bool\\\[0]'" }
T ((*pca1)[-1]); // { dg-warning "array subscript -1 is (below|outside) array bounds of 'char\\\[1]'" }
T ((*pca1)[0]);
T ((*pca1)[1]); // { dg-warning "array subscript 1 is (above|outside) array bounds of 'char\\\[1]'" }
T ((*pca1)[2]); // { dg-warning "array subscript 2 is (above|outside) array bounds of 'char\\\[1]'" }
T ((*pca1)[123]); // { dg-warning "array subscript 123 is (above|outside) array bounds of 'char\\\[1]'" }
T ((*psa2)[-1]); // { dg-warning "array subscript -1 is (below|outside) array bounds of 'short int\\\[2]'" }
T ((*psa2)[0]);
T ((*psa2)[1]);
T ((*psa2)[2]); // { dg-warning "array subscript 2 is (above|outside) array bounds of 'short int\\\[2]'" }
T ((*psa2)[1234]); // { dg-warning "array subscript 1234 is (above|outside) array bounds of 'short int\\\[2]'" }
T ((*pia3)[-1]); // { dg-warning "array subscript -1 is (below|outside) array bounds of 'int\\\[3]'" }
T ((*pia3)[0]);
T ((*pia3)[1]);
T ((*pia3)[2]);
T ((*pia3)[3]); // { dg-warning "array subscript 3 is (above|outside) array bounds of 'int\\\[3]'" }
T ((*pia3)[12345]); // { dg-warning "array subscript 12345 is (above|outside) array bounds of 'int\\\[3]'" }
}
void test_a2_cst (_Bool (*pba0_1)[0][1], char (*pca1_2)[1][2],
short (*psa2_3)[2][3], int (*pia3_4)[3][4])
{
T ((*pba0_1)[-1][-1]); // { dg-warning "array subscript -1 is (below|outside) array bounds of '_Bool\\\[1]'" }
T ((*pba0_1)[-1][0]); // { dg-warning "array subscript -1 is (above|outside) array bounds of '_Bool\\\[0]\\\[1]'" }
T ((*pba0_1)[0][-1]); // { dg-warning "array subscript -1 is (below|outside) array bounds of '_Bool\\\[1]'" }
T ((*pba0_1)[0][0]); // { dg-warning "array subscript 0 is (above|outside) array bounds of '_Bool\\\[0]\\\[1]'" }
T ((*pba0_1)[0][1]); // { dg-warning "array subscript 1 is (above|outside) array bounds of '_Bool\\\[1]'" }
T ((*pba0_1)[0][2]); // { dg-warning "array subscript 2 is (above|outside) array bounds of '_Bool\\\[1]'" }
T ((*pba0_1)[0][12]); // { dg-warning "array subscript 12 is (above|outside) array bounds of '_Bool\\\[1]'" }
T ((*pba0_1)[1][-1]); // { dg-warning "array subscript -1 is (below|outside) array bounds of '_Bool\\\[1]'" }
T ((*pba0_1)[1][0]); // { dg-warning "array subscript 1 is (above|outside) array bounds of '_Bool\\\[0]\\\[1]'" }
T ((*pba0_1)[1][1]); // { dg-warning "array subscript 1 is (above|outside) array bounds of '_Bool\\\[1]'" }
T ((*pba0_1)[1][2]); // { dg-warning "array subscript 2 is (above|outside) array bounds of '_Bool\\\[1]'" }
T ((*pba0_1)[1][12]); // { dg-warning "array subscript 12 is (above|outside) array bounds of '_Bool\\\[1]'" }
T ((*pca1_2)[0][0]);
T ((*pca1_2)[0][1]);
T ((*pca1_2)[0][2]); // { dg-warning "array subscript 2 is (above|outside) array bounds of 'char\\\[2]'" }
T ((*pca1_2)[1][0]); // { dg-warning "array subscript 1 is (above|outside) array bounds of 'char\\\[1]\\\[2]'" }
T ((*pca1_2)[1][1]); // { dg-warning "array subscript 1 is (above|outside) array bounds of 'char\\\[1]\\\[2]'" }
T ((*pca1_2)[1][2]); // { dg-warning "array subscript 2 is (above|outside) array bounds of 'char\\\[2]'" }
T ((*psa2_3)[0][0]);
T ((*psa2_3)[0][1]);
T ((*psa2_3)[0][2]);
T ((*psa2_3)[0][3]); // { dg-warning "array subscript 3 is (above|outside) array bounds of 'short int\\\[3]'" }
T ((*psa2_3)[1][0]);
T ((*psa2_3)[1][1]);
T ((*psa2_3)[1][2]);
T ((*psa2_3)[1][3]); // { dg-warning "array subscript 3 is (above|outside) array bounds of 'short int\\\[3]'" }
T ((*psa2_3)[2][0]); // { dg-warning "array subscript 2 is (above|outside) array bounds of 'short int\\\[2]\\\[3]'" }
T ((*psa2_3)[2][1]); // { dg-warning "array subscript 2 is (above|outside) array bounds of 'short int\\\[2]\\\[3]'" }
T ((*psa2_3)[2][2]); // { dg-warning "array subscript 2 is (above|outside) array bounds of 'short int\\\[2]\\\[3]'" }
T ((*psa2_3)[2][3]); // { dg-warning "array subscript 3 is (above|outside) array bounds of 'short int\\\[3]'" }
T ((*pia3_4)[0][0]);
T ((*pia3_4)[0][1]);
T ((*pia3_4)[0][2]);
T ((*pia3_4)[0][3]);
T ((*pia3_4)[0][4]); // { dg-warning "array subscript 4 is (above|outside) array bounds of 'int\\\[4]'" }
T ((*pia3_4)[1][0]);
T ((*pia3_4)[1][1]);
T ((*pia3_4)[1][2]);
T ((*pia3_4)[1][3]);
T ((*pia3_4)[1][4]); // { dg-warning "array subscript 4 is (above|outside) array bounds of 'int\\\[4]'" }
T ((*pia3_4)[2][0]);
T ((*pia3_4)[2][1]);
T ((*pia3_4)[2][2]);
T ((*pia3_4)[2][3]);
T ((*pia3_4)[2][4]); // { dg-warning "array subscript 4 is (above|outside) array bounds of 'int\\\[4]'" }
T ((*pia3_4)[3][0]); // { dg-warning "array subscript 3 is (above|outside) array bounds of 'int\\\[3]\\\[4]'" }
T ((*pia3_4)[3][1]); // { dg-warning "array subscript 3 is (above|outside) array bounds of 'int\\\[3]\\\[4]'" }
T ((*pia3_4)[3][2]); // { dg-warning "array subscript 3 is (above|outside) array bounds of 'int\\\[3]\\\[4]'" }
T ((*pia3_4)[3][3]); // { dg-warning "array subscript 3 is (above|outside) array bounds of 'int\\\[3]\\\[4]'" }
T ((*pia3_4)[3][4]); // { dg-warning "array subscript 4 is (above|outside) array bounds of 'int\\\[4]'" }
}
typedef int IA4[4];
typedef IA4 IA3_4[3];
void test_a2_var (IA3_4 *pia3_4)
{
{
IA4 *pia4 = &(*pia3_4)[0];
T ((*pia4)[-1]); // { dg-warning "array subscript -1 is (below|outside) array bounds of 'IA4'" }
T ((*pia4)[0]);
T ((*pia4)[1]);
T ((*pia4)[2]);
T ((*pia4)[3]);
T ((*pia4)[4]); // { dg-warning "array subscript 4 is (above|outside) array bounds of 'IA4'" }
}
{
IA4 *pia4 = &(*pia3_4)[1];
T ((*pia4)[-1]); // { dg-warning "array subscript -1 is (below|outside) array bounds of 'IA4'" }
T ((*pia4)[0]);
T ((*pia4)[1]);
T ((*pia4)[2]);
T ((*pia4)[3]);
T ((*pia4)[4]); // { dg-warning "array subscript 4 is (above|outside) array bounds of 'IA4'" }
}
{
IA4 *pia4 = &(*pia3_4)[2];
T ((*pia4)[-1]); // { dg-warning "array subscript -1 is (below|outside) array bounds of 'IA4'" }
T ((*pia4)[0]);
T ((*pia4)[1]);
T ((*pia4)[2]);
T ((*pia4)[3]);
T ((*pia4)[4]); // { dg-warning "array subscript 4 is (above|outside) array bounds of 'IA4'" }
}
{
IA4 *pia4 = &(*pia3_4)[3];
T ((*pia4)[-1]); // { dg-warning "\\\[-Warray-bounds" }
/* The following aren't diagnosed unless N itself is out of bounds
because thanks to the MEM_REF they're indistinguishable from
possibly valid accesses:
MEM[(int[4] *)pia3_4_2(D) + 48B][N]; */
T ((*pia4)[0]); // { dg-warning "\\\[-Warray-bounds" "pr?????" { xfail *-*-* } }
T ((*pia4)[1]); // { dg-warning "\\\[-Warray-bounds" "pr?????" { xfail *-*-* } }
T ((*pia4)[2]); // { dg-warning "\\\[-Warray-bounds" "pr?????" { xfail *-*-* } }
T ((*pia4)[3]); // { dg-warning "\\\[-Warray-bounds" "pr?????" { xfail *-*-* } }
T ((*pia4)[4]); // { dg-warning "\\\[-Warray-bounds" }
}
}
struct S { IA3_4 *pia3_4; };
typedef struct S S5[5];
typedef S5 S5_7[7];
void test_s5_7 (S5_7 *ps5_7)
{
{
S5 *ps5 = &(*ps5_7)[0];
T ((*ps5)[0]);
T ((*(*ps5)[0].pia3_4)[0][0]);
T ((*(*ps5)[0].pia3_4)[2][3]);
T ((*(*ps5)[0].pia3_4)[2][4]); // { dg-warning "array subscript 4 is above array bounds of 'IA4'" }
T ((*(*ps5)[1].pia3_4)[2][3]);
T ((*(*ps5)[5].pia3_4)[2][3]); // { dg-warning "array subscript 5 is above array bounds of 'S5'" }
}
}

View File

@ -0,0 +1,256 @@
/* PR middle-end/82608 - missing -Warray-bounds on an out-of-bounds VLA index
{ dg-do compile }
{ dg-options "-O2 -Wall -Wno-uninitialized -ftrack-macro-expansion=0" } */
#include "range.h"
typedef __INT16_TYPE__ int16_t;
#define alloca(n) __builtin_alloca (n)
void* calloc (size_t, size_t);
void* malloc (size_t);
void sink (void*, ...);
#define sink(...) sink (0, __VA_ARGS__)
#define T(x) (sink (x))
__attribute__ ((alloc_size (1))) void* alloc (size_t);
void test_alloca_cst (void)
{
{
char *p = alloca (1);
sink (p);
T (p[0]);
T (p[1]); // { dg-warning "subscript 1 is outside array bounds of 'char\\\[1\\\]'" }
}
{
char *p = alloca (2);
sink (p);
T (p[0]), T (p[1]);
T (p[2]); // { dg-warning "subscript 2 is outside array bounds of 'char\\\[2\\\]'" }
}
{
char *p = alloca (3);
sink (p);
T (p[0]), T (p[1]), T (p[2]);
T (p[3]); // { dg-warning "subscript 3 is outside array bounds of 'char\\\[3\\\]'" }
}
}
void test_alloca_char_range (int i, unsigned n, size_t sz)
{
{
// Be sure to exercise signed as well as unsigned arguments.
char *p = alloca (i);
sink (p);
T (p[0]), T (p[1]), T (p[12345]);
T (p[-1]); // { dg-warning "subscript -1 is outside array bounds of 'char\\\[" }
}
{
char *p = alloca (n);
sink (p);
T (p[0]), T (p[1]), T (p[12345]);
T (p[-1]); // { dg-warning "subscript -1 is outside array bounds of 'char\\\[" }
}
{
char *p = alloca (sz);
sink (p);
T (p[0]), T (p[1]), T (p[23456]);
T (p[-1]); // { dg-warning "subscript -1 is outside array bounds of 'char\\\[" }
}
{
char *p = alloca (UR (0, 1));
sink (p);
T (p[0]);
T (p[1]); // { dg-warning "subscript 1 is outside array bounds of 'char\\\[1\\\]'" }
}
{
char *p = alloca (UR (0, 2));
sink (p);
sink (p[0], p[1]);
sink (p[2]); // { dg-warning "subscript 2 is outside array bounds of 'char\\\[2\\\]'" }
}
{
char *p = alloca (UR (0, 3));
sink (p);
T (p[0]), T (p[1]), T (p[2]);
T (p[3]); // { dg-warning "subscript 3 is outside array bounds of 'char\\\[3\\\]'" }
}
{
char *p = alloca (UR (1, 3));
sink (p);
T (p[0]), T (p[1]), T (p[2]);
T (p[3]); // { dg-warning "subscript 3 is outside array bounds of 'char\\\[3\\\]'" }
}
{
char *p = alloca (UR (2, 3));
sink (p);
T (p[0]), T (p[1]), T (p[2]);
T (p[3]); // { dg-warning "subscript 3 is outside array bounds of 'char\\\[3\\\]'" }
}
}
void test_alloca_int16_range (unsigned n)
{
int16_t *p;
{
p = alloca (n); // { dg-message "allocated by " }
sink (p);
T (p[0]), T (p[1]), T (p[12345]);
T (p[-1]); // { dg-warning "subscript -1 is outside array bounds of 'int16_t\\\[" }
}
{
p = alloca (UR (0, 1)); // { dg-message "object of size between 0 and 1 allocated by '__builtin_alloca'" }
sink (p);
T (p[0]); // { dg-warning "subscript 'int16_t {aka short int}\\\[0\\\]' is partly outside array bounds of 'unsigned char\\\[1]'" }
T (p[1]); // { dg-warning "subscript 1 is outside array bounds of 'int16_t\\\[0]'" }
}
{
p = alloca (UR (0, 2)); // { dg-message "object of size between 0 and 2 allocated by '__builtin_alloca'" }
sink (p);
sink (p[0]);
sink (p[1]); // { dg-warning "subscript 1 is outside array bounds of 'int16_t\\\[1]'" }
sink (p[2]); // { dg-warning "subscript 2 is outside array bounds of 'int16_t\\\[1\\\]'" }
}
{
p = alloca (UR (0, 3)); // { dg-message "object of size between 0 and 3 allocated by '__builtin_alloca'" }
sink (p);
T (p[0]);
T (p[1]); // { dg-warning "subscript 'int16_t {aka short int}\\\[1\\\]' is partly outside array bounds of 'unsigned char\\\[3]'" }
T (p[2]); // { dg-warning "subscript 2 is outside array bounds of 'int16_t\\\[1\\\]'" }
T (p[3]); // { dg-warning "subscript 3 is outside array bounds of 'int16_t\\\[1\\\]'" }
}
{
p = alloca (UR (1, 3)); // { dg-message "object of size between 1 and 3 allocated by '__builtin_alloca'" }
sink (p);
T (p[0]);
T (p[1]); // { dg-warning "subscript 'int16_t {aka short int}\\\[1\\\]' is partly outside array bounds of 'unsigned char\\\[3]'" }
T (p[2]); // { dg-warning "subscript 2 is outside array bounds of 'int16_t\\\[1\\\]'" }
T (p[3]); // { dg-warning "subscript 3 is outside array bounds of 'int16_t\\\[1\\\]'" }
}
{
p = alloca (UR (2, 3)); // { dg-message "object of size between 2 and 3 allocated by '__builtin_alloca'" }
sink (p);
T (p[0]);
T (p[1]); // { dg-warning "subscript 'int16_t {aka short int}\\\[1\\\]' is partly outside array bounds of 'unsigned char\\\[3]'" }
T (p[2]); // { dg-warning "subscript 2 is outside array bounds of 'int16_t\\\[1\\\]'" }
T (p[3]); // { dg-warning "subscript 3 is outside array bounds of 'int16_t\\\[1\\\]'" }
}
{
p = alloca (UR (3, 4)); // { dg-message "object of size between 3 and 4 allocated by '__builtin_alloca'" }
sink (p);
T (p[0]);
T (p[1]);
T (p[2]); // { dg-warning "subscript 2 is outside array bounds of 'int16_t\\\[2\\\]'" }
T (p[3]); // { dg-warning "subscript 3 is outside array bounds of 'int16_t\\\[2\\\]'" }
}
}
void test_vla_cst (void)
{
int n = 1;
{
char a[n];
sink (a);
T (a[0]);
T (a[1]); // { dg-warning "subscript 1 is (above|outside) array bounds " }
}
{
n = 2;
char a[n];
sink (a);
T (a[0]), T (a[1]);
T (a[2]); // { dg-warning "subscript 2 is (above|outside) array bounds " }
}
{
n = 3;
char a[n], *p = a;
sink (p);
T (p[0]), T (p[1]), T (p[2]);
T (p[3]); // { dg-warning "subscript 3 is (above|outside) array bounds " }
}
}
void test_vla_char_range (int i, unsigned n, size_t sz)
{
{
char a[i];
sink (a);
T (a[0]), T (a[1]), T (a[12345]);
T (a[-1]); // { dg-warning "subscript -1 is (below|outside) array bounds of 'char\\\[" }
}
{
char a[n];
sink (a);
T (a[0]), T (a[1]), T (a[12345]);
T (a[-1]); // { dg-warning "subscript -1 is (below|outside) array bounds of 'char\\\[" }
}
{
char a[sz];
sink (a);
T (a[0]), T (a[1]), T (a[23456]);
T (a[-1]); // { dg-warning "subscript -1 is (below|outside) array bounds of 'char\\\[" }
}
{
char a[UR (0, 1)];
sink (a);
T (a[0]);
T (a[1]); // { dg-warning "subscript 1 is outside array bounds of 'char\\\[1\\\]'" "pr82608" { xfail *-*-* } }
}
{
char a[UR (0, 2)];
sink (a);
sink (a[0], a[1]);
sink (a[2]); // { dg-warning "subscript 2 is outside array bounds of 'char\\\[2\\\]'" "pr82608" { xfail *-*-* } }
}
{
char a[UR (0, 3)];
sink (a);
T (a[0]), T (a[1]), T (a[2]);
T (a[3]); // { dg-warning "subscript 3 is outside array bounds of 'char\\\[3\\\]'" "pr82608" { xfail *-*-* } }
}
{
char a[UR (1, 3)];
sink (a);
T (a[0]), T (a[1]), T (a[2]);
T (a[3]); // { dg-warning "subscript 3 is outside array bounds of 'char\\\[3\\\]'" "pr82608" { xfail *-*-* } }
}
{
char a[UR (2, 3)];
sink (a);
T (a[0]), T (a[1]), T (a[2]);
T (a[3]); // { dg-warning "subscript 3 is outside array bounds of 'char\\\[3\\\]'" "pr82608" { xfail *-*-* } }
}
}

View File

@ -0,0 +1,36 @@
/* Verify warnings fpr accesses to trailing one-element array members
of a struct that's a member of either a struct or a union. Both
are obviously undefined but GCC relies on these hacks so the test
verifies that -Warray-bounds doesn't trigger for it.
{ do-do compile }
{ dg-options "-O2 -Wall" } */
typedef union tree_node *tree;
struct tree_exp { int i; tree operands[1]; };
union tree_node
{
struct tree_exp exp;
};
tree test_nowarn (tree t)
{
return t->exp.operands[3]; // { dg-bogus "\\\[-Warray-bounds" }
}
typedef struct shrub_node *shrub;
struct shrub_exp { int i; shrub operands[1]; };
struct shrub_node
{
struct shrub_exp exp;
};
shrub test_warn (shrub s)
{
return s->exp.operands[3]; // { dg-warning "\\\[-Warray-bounds" "pr96346" { xfail *-*-* } }
}

View File

@ -11,5 +11,5 @@ void a (void)
);
}
/* The scanf call may also trigger:
{ dg-prune-output "-Wstringop-overflow" } */
/* The invalid scanf call may also trigger:
{ dg-prune-output "accessing 4 bytes in a region of size 1" } */