--- /dev/null
+/* PR tree-optimization/97027 - missing warning on buffer overflow storing
+ a larger scalar into a smaller array
+ Verify overflow by aggregate stores.
+ { dg-do compile }
+ { dg-options "-O2" } */
+
+#define A(N) (A ## N)
+#define Ac1 (AC1){ 0 }
+#define Ac2 (AC2){ 0, 1 }
+#define Ac4 (AC4){ 0, 1, 2, 3 }
+#define Ac8 (AC8){ 0, 1, 2, 3, 4, 5, 6, 7 }
+#define Ac16 (AC16){ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }
+
+typedef struct AC1 { char a[1]; } AC1;
+typedef struct AC2 { char a[2]; } AC2;
+typedef struct AC3 { char a[3]; } AC3;
+typedef struct AC4 { char a[4]; } AC4;
+typedef struct AC5 { char a[5]; } AC5;
+typedef struct AC8 { char a[8]; } AC8;
+typedef struct AC16 { char a[16]; } AC16;
+
+extern char a1[1], a2[2], a3[3], a4[4], a5[5], a6[6], a7[7], a8[8], a15[15];
+
+extern AC1 ac1;
+extern AC2 ac2;
+extern AC4 ac4;
+extern AC8 ac8;
+extern AC16 ac16;
+
+extern AC1 fac1 (void);
+extern AC2 fac2 (void);
+extern AC4 fac4 (void);
+extern AC8 fac8 (void);
+extern AC16 fac16 (void);
+
+void nowarn (void)
+{
+ *(AC1*)a1 = Ac1;
+ *(AC2*)a2 = Ac2;
+ *(AC4*)a4 = Ac4;
+ *(AC4*)a5 = Ac4;
+ *(AC4*)a6 = Ac4;
+ *(AC4*)a7 = Ac4;
+ *(AC8*)a8 = Ac8;
+ *(AC8*)a15 = Ac8;
+}
+
+void warn_comp_lit_zero (void)
+{
+ *(AC2*)a1 = (AC2){ }; // { dg-warning "writing 2 bytes into a region of size 1" }
+ *(AC4*)a2 = (AC4){ }; // { dg-warning "writing 4 bytes into a region of size 2" }
+ *(AC4*)a3 = (AC4){ }; // { dg-warning "writing 4 bytes into a region of size 3" }
+ *(AC8*)a4 = (AC8){ }; // { dg-warning "writing 8 bytes into a region of size 4" }
+ *(AC8*)a7 = (AC8){ }; // { dg-warning "writing 8 bytes into a region of size 7" }
+ *(AC16*)a15 = (AC16){ };// { dg-warning "writing 16 bytes into a region of size 15" }
+}
+
+void warn_comp_lit (void)
+{
+ *(AC2*)a1 = Ac2; // { dg-warning "writing 2 bytes into a region of size 1" "pr??????" { xfail *-*-* } }
+ *(AC4*)a2 = Ac4; // { dg-warning "writing 4 bytes into a region of size 2" "pr??????" { xfail *-*-* } }
+ *(AC4*)a3 = Ac4; // { dg-warning "writing 4 bytes into a region of size 3" "pr??????" { xfail *-*-* } }
+ *(AC8*)a4 = Ac8; // { dg-warning "writing 8 bytes into a region of size 4" "pr??????" { xfail *-*-* } }
+ *(AC8*)a7 = Ac8; // { dg-warning "writing 8 bytes into a region of size 7" "pr??????" { xfail *-*-* } }
+ *(AC16*)a15 = Ac16; // { dg-warning "writing 16 bytes into a region of size 15" "pr??????" { xfail *-*-* } }
+}
+
+void warn_aggr_decl (void)
+{
+ *(AC2*)a1 = ac2; // { dg-warning "writing 2 bytes into a region of size 1" }
+ *(AC4*)a2 = ac4; // { dg-warning "writing 4 bytes into a region of size 2" }
+ *(AC4*)a3 = ac4; // { dg-warning "writing 4 bytes into a region of size 3" }
+ *(AC8*)a4 = ac8; // { dg-warning "writing 8 bytes into a region of size 4" }
+ *(AC8*)a7 = ac8; // { dg-warning "writing 8 bytes into a region of size 7" }
+ *(AC16*)a15 = ac16; // { dg-warning "writing 16 bytes into a region of size 15" }
+}
+
+void warn_aggr_parm (AC2 pc2, AC4 pc4, AC8 pc8, AC16 pc16)
+{
+ *(AC2*)a1 = pc2; // { dg-warning "writing 2 bytes into a region of size 1" }
+ *(AC4*)a2 = pc4; // { dg-warning "writing 4 bytes into a region of size 2" }
+ *(AC4*)a3 = pc4; // { dg-warning "writing 4 bytes into a region of size 3" }
+ *(AC8*)a4 = pc8; // { dg-warning "writing 8 bytes into a region of size 4" }
+ *(AC8*)a7 = pc8; // { dg-warning "writing 8 bytes into a region of size 7" }
+ *(AC16*)a15 = pc16; // { dg-warning "writing 16 bytes into a region of size 15" }
+}
+
+void warn_aggr_func (void)
+{
+ *(AC2*)a1 = fac2 (); // { dg-warning "writing 2 bytes into a region of size 1" }
+ *(AC4*)a2 = fac4 (); // { dg-warning "writing 4 bytes into a region of size 2" }
+ *(AC4*)a3 = fac4 (); // { dg-warning "writing 4 bytes into a region of size 3" }
+ *(AC8*)a4 = fac8 (); // { dg-warning "writing 8 bytes into a region of size 4" }
+ *(AC8*)a7 = fac8 (); // { dg-warning "writing 8 bytes into a region of size 7" }
+ *(AC16*)a15 = fac16 ();// { dg-warning "writing 16 bytes into a region of size 15" }
+
+ extern AC2 fac2_x ();
+
+ *(AC2*)a1 = fac2_x (); // { dg-warning "writing 2 bytes into a region of size 1" }
+
+ extern AC2 fac2_p (char*);
+
+ *(AC2*)a1 = fac2_p (0); // { dg-warning "writing 2 bytes into a region of size 1" }
+}
static int get_stridx_plus_constant (strinfo *, unsigned HOST_WIDE_INT, tree);
static void handle_builtin_stxncpy_strncat (bool, gimple_stmt_iterator *);
+static bool handle_assign (gimple_stmt_iterator *, tree, bool *,
+ pointer_query &);
/* Sets MINMAX to either the constant value or the range VAL is in
and returns either the constant value or VAL on success or null
/* Diagnose buffer overflow by a STMT writing LEN + PLUS_ONE bytes,
either into a region allocated for the object SI when non-null,
or into an object designated by the LHS of STMT otherwise.
+ For a call STMT, when CALL_LHS is set use its left hand side
+ as the destination, otherwise use argument zero.
When nonnull uses RVALS to determine range information.
RAWMEM may be set by memcpy and other raw memory functions
to allow accesses across subobject boundaries. */
static void
-maybe_warn_overflow (gimple *stmt, tree len, pointer_query &ptr_qry,
+maybe_warn_overflow (gimple *stmt, bool call_lhs, tree len,
+ pointer_query &ptr_qry,
strinfo *si = NULL, bool plus_one = false,
bool rawmem = false)
{
/* The DECL of the function performing the write if it is done
by one. */
tree writefn = NULL_TREE;
- /* The destination expression involved in the store STMT. */
+ /* The destination expression involved in the store or call STMT. */
tree dest = NULL_TREE;
if (is_gimple_assign (stmt))
dest = gimple_assign_lhs (stmt);
else if (is_gimple_call (stmt))
{
- dest = gimple_call_arg (stmt, 0);
+ if (call_lhs)
+ dest = gimple_call_lhs (stmt);
+ else
+ {
+ gcc_assert (gimple_call_builtin_p (stmt, BUILT_IN_NORMAL));
+ dest = gimple_call_arg (stmt, 0);
+ }
+
+ if (!dest)
+ return;
writefn = gimple_call_fndecl (stmt);
}
else
/* Convenience wrapper for the above. */
static inline void
-maybe_warn_overflow (gimple *stmt, unsigned HOST_WIDE_INT len,
+maybe_warn_overflow (gimple *stmt, bool call_lhs, unsigned HOST_WIDE_INT len,
pointer_query &ptr_qry, strinfo *si = NULL,
bool plus_one = false, bool rawmem = false)
{
- maybe_warn_overflow (stmt, build_int_cst (size_type_node, len), ptr_qry,
- si, plus_one, rawmem);
+ tree tlen = build_int_cst (size_type_node, len);
+ maybe_warn_overflow (stmt, call_lhs, tlen, ptr_qry, si, plus_one, rawmem);
}
/* Handle a strlen call. If strlen of the argument is known, replace
else if (idx < 0)
srclen = build_int_cst (size_type_node, ~idx);
- maybe_warn_overflow (stmt, srclen, ptr_qry, olddsi, true);
+ maybe_warn_overflow (stmt, false, srclen, ptr_qry, olddsi, true);
if (olddsi != NULL)
adjust_last_stmt (olddsi, stmt, false, ptr_qry);
if (olddsi != NULL
&& !integer_zerop (len))
{
- maybe_warn_overflow (stmt, len, ptr_qry, olddsi, false, true);
+ maybe_warn_overflow (stmt, false, len, ptr_qry, olddsi, false, true);
adjust_last_stmt (olddsi, stmt, false, ptr_qry);
}
tree memset_size = gimple_call_arg (memset_stmt, 2);
/* Check for overflow. */
- maybe_warn_overflow (memset_stmt, memset_size, ptr_qry, NULL, false, true);
+ maybe_warn_overflow (memset_stmt, false, memset_size, ptr_qry, NULL,
+ false, true);
/* Bail when there is no statement associated with the destination
(the statement may be null even when SI1->ALLOC is not). */
}
}
+/* Set LENRANGE to the number of nonzero bytes for a store of TYPE and
+ clear all flags. Return true on success and false on failure. */
+
+static bool
+nonzero_bytes_for_type (tree type, unsigned lenrange[3],
+ bool *nulterm, bool *allnul, bool *allnonnul)
+{
+ /* Use the size of the type of the expression as the size of the store,
+ and set the upper bound of the length range to that of the size.
+ Nothing is known about the contents so clear all flags. */
+ tree typesize = TYPE_SIZE_UNIT (type);
+ if (!type)
+ return false;
+
+ if (!tree_fits_uhwi_p (typesize))
+ return false;
+
+ unsigned HOST_WIDE_INT sz = tree_to_uhwi (typesize);
+ if (sz > UINT_MAX)
+ return false;
+
+ lenrange[2] = sz;
+ lenrange[1] = lenrange[2] ? lenrange[2] - 1 : 0;
+ lenrange[0] = 0;
+ *nulterm = false;
+ *allnul = false;
+ *allnonnul = false;
+ return true;
+}
+
static bool
count_nonzero_bytes_addr (tree, unsigned HOST_WIDE_INT, unsigned HOST_WIDE_INT,
unsigned [3], bool *, bool *, bool *,
range_query *, ssa_name_limit_t &);
-/* Determines the minimum and maximum number of leading non-zero bytes
- in the representation of EXP and set LENRANGE[0] and LENRANGE[1]
+/* Recursively determine the minimum and maximum number of leading nonzero
+ bytes in the representation of EXP and set LENRANGE[0] and LENRANGE[1]
to each.
Sets LENRANGE[2] to the total size of the access (which may be less
than LENRANGE[1] when what's being referenced by EXP is a pointer
rather than an array).
- Sets *NULTERM if the representation contains a zero byte, and sets
- *ALLNUL if all the bytes are zero.
+ Sets *NULTERM if the representation contains a zero byte, sets *ALLNUL
+ if all the bytes are zero, and *ALLNONNUL is all are nonzero.
OFFSET and NBYTES are the offset into the representation and
the size of the access to it determined from an ADDR_EXPR (i.e.,
a pointer) or MEM_REF or zero for other expressions.
if (gimple_assign_single_p (stmt))
{
exp = gimple_assign_rhs1 (stmt);
- if (TREE_CODE (exp) != MEM_REF)
+ if (!DECL_P (exp)
+ && TREE_CODE (exp) != CONSTRUCTOR
+ && TREE_CODE (exp) != MEM_REF)
return false;
- /* Handle MEM_REF below. */
+ /* Handle DECLs, CONSTRUCTOR and MEM_REF below. */
}
else if (gimple_code (stmt) == GIMPLE_PHI)
{
}
}
+ if (TREE_CODE (exp) == CONSTRUCTOR)
+ {
+ if (nbytes)
+ /* If NBYTES has already been determined by an outer MEM_REF
+ fail rather than overwriting it (this shouldn't happen). */
+ return false;
+
+ tree type = TREE_TYPE (exp);
+ tree size = TYPE_SIZE_UNIT (type);
+ if (!size || !tree_fits_uhwi_p (size))
+ return false;
+
+ unsigned HOST_WIDE_INT byte_size = tree_to_uhwi (size);
+ if (byte_size < offset)
+ return false;
+
+ nbytes = byte_size - offset;
+ }
+
if (TREE_CODE (exp) == MEM_REF)
{
if (nbytes)
if (VAR_P (exp) || TREE_CODE (exp) == CONST_DECL)
{
- exp = ctor_for_folding (exp);
- if (!exp)
- return false;
+ /* If EXP can be folded into a constant use the result. Otherwise
+ proceed to use EXP to determine a range of the result. */
+ if (tree fold_exp = ctor_for_folding (exp))
+ if (fold_exp != error_mark_node)
+ exp = fold_exp;
}
const char *prep = NULL;
}
if (!nbytes)
- return false;
+ return nonzero_bytes_for_type (TREE_TYPE (exp), lenrange,
+ nulterm, allnul, allnonnul);
/* Compute the number of leading nonzero bytes in the representation
and update the minimum and maximum. */
return true;
}
-/* Same as above except with an implicit SSA_NAME limit. RVALS is used
- to determine ranges of dynamically computed string lengths (the results
- of strlen). */
+/* Same as above except with an implicit SSA_NAME limit. When EXPR_OR_TYPE
+ is a type rather than an expression use its size to compute the range.
+ RVALS is used to determine ranges of dynamically computed string lengths
+ (the results of strlen). */
static bool
-count_nonzero_bytes (tree exp, unsigned lenrange[3], bool *nulterm,
+count_nonzero_bytes (tree expr_or_type, unsigned lenrange[3], bool *nulterm,
bool *allnul, bool *allnonnul, range_query *rvals)
{
+ if (TYPE_P (expr_or_type))
+ return nonzero_bytes_for_type (expr_or_type, lenrange,
+ nulterm, allnul, allnonnul);
+
/* Set to optimistic values so the caller doesn't have to worry about
initializing these and to what. On success, the function will clear
these if it determines their values are different but being recursive
*allnonnul = true;
ssa_name_limit_t snlim;
- return count_nonzero_bytes (exp, 0, 0, lenrange, nulterm, allnul, allnonnul,
+ tree expr = expr_or_type;
+ return count_nonzero_bytes (expr, 0, 0, lenrange, nulterm, allnul, allnonnul,
rvals, snlim);
}
handle_store (gimple_stmt_iterator *gsi, bool *zero_write,
pointer_query &ptr_qry)
{
- int idx = -1;
- strinfo *si = NULL;
gimple *stmt = gsi_stmt (*gsi);
- tree ssaname = NULL_TREE, lhs = gimple_assign_lhs (stmt);
- tree rhs = gimple_assign_rhs1 (stmt);
+ /* The LHS and RHS of the store. The RHS is null if STMT is a function
+ call. STORETYPE is the type of the store (determined from either
+ the RHS of the assignment statement or the LHS of a function call. */
+ tree lhs, rhs, storetype;
+ if (is_gimple_assign (stmt))
+ {
+ lhs = gimple_assign_lhs (stmt);
+ rhs = gimple_assign_rhs1 (stmt);
+ storetype = TREE_TYPE (rhs);
+ }
+ else if (is_gimple_call (stmt))
+ {
+ lhs = gimple_call_lhs (stmt);
+ rhs = NULL_TREE;
+ storetype = TREE_TYPE (lhs);
+ }
+ else
+ return true;
+
+ tree ssaname = NULL_TREE;
+ strinfo *si = NULL;
+ int idx = -1;
range_query *const rvals = ptr_qry.rvals;
ssaname = TREE_OPERAND (lhs, 0);
else if (si == NULL || compare_nonzero_chars (si, offset, rvals) < 0)
{
- *zero_write = initializer_zerop (rhs);
+ *zero_write = rhs ? initializer_zerop (rhs) : false;
bool dummy;
unsigned lenrange[] = { UINT_MAX, 0, 0 };
- if (count_nonzero_bytes (rhs, lenrange, &dummy, &dummy, &dummy,
- rvals))
- maybe_warn_overflow (stmt, lenrange[2], ptr_qry);
+ if (count_nonzero_bytes (rhs ? rhs : storetype, lenrange,
+ &dummy, &dummy, &dummy, rvals))
+ maybe_warn_overflow (stmt, true, lenrange[2], ptr_qry);
return true;
}
bool full_string_p;
const bool ranges_valid
- = count_nonzero_bytes (rhs, lenrange, &full_string_p,
+ = count_nonzero_bytes (rhs ? rhs : storetype, lenrange, &full_string_p,
&storing_all_zeros_p, &storing_all_nonzero_p,
rvals);
+
if (ranges_valid)
{
rhs_minlen = lenrange[0];
storing_nonzero_p = lenrange[1] > 0;
*zero_write = storing_all_zeros_p;
- maybe_warn_overflow (stmt, lenrange[2], ptr_qry);
+ maybe_warn_overflow (stmt, true, lenrange[2], ptr_qry);
}
else
{
&& storing_nonzero_p
&& lenrange[0] == lenrange[1]
&& lenrange[0] == lenrange[2]
- && TREE_CODE (TREE_TYPE (rhs)) == INTEGER_TYPE)
+ && TREE_CODE (storetype) == INTEGER_TYPE)
{
/* Handle a store of one or more non-nul characters that ends
before the terminating nul of the destination and so does
if (!gimple_call_builtin_p (stmt, BUILT_IN_NORMAL))
{
tree fntype = gimple_call_fntype (stmt);
- if (fntype && lookup_attribute ("alloc_size", TYPE_ATTRIBUTES (fntype)))
- handle_alloc_call (BUILT_IN_NONE, gsi);
+ if (!fntype)
+ return true;
+
+ if (lookup_attribute ("alloc_size", TYPE_ATTRIBUTES (fntype)))
+ {
+ handle_alloc_call (BUILT_IN_NONE, gsi);
+ return true;
+ }
+
+ if (tree lhs = gimple_call_lhs (stmt))
+ handle_assign (gsi, lhs, zero_write, ptr_qry);
+
+ /* Proceed to handle user-defined formatting functions. */
}
/* When not optimizing we must be checking printf calls which
}
}
+/* Handle assignment statement at *GSI to LHS. Set *ZERO_WRITE if
+ the assignent stores all zero bytes.. */
+
+static bool
+handle_assign (gimple_stmt_iterator *gsi, tree lhs, bool *zero_write,
+ pointer_query &ptr_qry)
+{
+ tree type = TREE_TYPE (lhs);
+ if (TREE_CODE (type) == ARRAY_TYPE)
+ type = TREE_TYPE (type);
+
+ bool is_char_store = is_char_type (type);
+ if (!is_char_store && TREE_CODE (lhs) == MEM_REF)
+ {
+ /* To consider stores into char objects via integer types other
+ than char but not those to non-character objects, determine
+ the type of the destination rather than just the type of
+ the access. */
+ for (int i = 0; i != 2; ++i)
+ {
+ tree ref = TREE_OPERAND (lhs, i);
+ type = TREE_TYPE (ref);
+ if (TREE_CODE (type) == POINTER_TYPE)
+ type = TREE_TYPE (type);
+ if (TREE_CODE (type) == ARRAY_TYPE)
+ type = TREE_TYPE (type);
+ if (is_char_type (type))
+ {
+ is_char_store = true;
+ break;
+ }
+ }
+ }
+
+ /* Handle a single or multibyte assignment. */
+ if (is_char_store && !handle_store (gsi, zero_write, ptr_qry))
+ return false;
+
+ return true;
+}
+
+
/* Attempt to check for validity of the performed access a single statement
at *GSI using string length knowledge, and to optimize it.
If the given basic block needs clean-up of EH, CLEANUP_EH is set to
/* Handle assignment to a character. */
handle_integral_assign (gsi, cleanup_eh, ptr_qry.rvals);
else if (TREE_CODE (lhs) != SSA_NAME && !TREE_SIDE_EFFECTS (lhs))
- {
- tree type = TREE_TYPE (lhs);
- if (TREE_CODE (type) == ARRAY_TYPE)
- type = TREE_TYPE (type);
-
- bool is_char_store = is_char_type (type);
- if (!is_char_store && TREE_CODE (lhs) == MEM_REF)
- {
- /* To consider stores into char objects via integer types
- other than char but not those to non-character objects,
- determine the type of the destination rather than just
- the type of the access. */
- for (int i = 0; i != 2; ++i)
- {
- tree ref = TREE_OPERAND (lhs, i);
- type = TREE_TYPE (ref);
- if (TREE_CODE (type) == POINTER_TYPE)
- type = TREE_TYPE (type);
- if (TREE_CODE (type) == ARRAY_TYPE)
- type = TREE_TYPE (type);
- if (is_char_type (type))
- {
- is_char_store = true;
- break;
- }
- }
- }
-
- /* Handle a single or multibyte assignment. */
- if (is_char_store && !handle_store (gsi, &zero_write, ptr_qry))
- return false;
- }
+ if (!handle_assign (gsi, lhs, &zero_write, ptr_qry))
+ return false;
}
else if (gcond *cond = dyn_cast<gcond *> (stmt))
{