aboutsummaryrefslogtreecommitdiff
path: root/gcc/builtins.c
diff options
context:
space:
mode:
authorMartin Sebor <msebor@redhat.com>2020-09-19 17:37:05 -0600
committerMartin Sebor <msebor@redhat.com>2020-09-19 17:37:05 -0600
commitbaad4c48a85a354d2bf1b17e5aff71203c08adea (patch)
treeb29b89090fb1eb5436ff4183d0b11ba2dc765a4a /gcc/builtins.c
parent72be80e47d059f33ff11f5015b9494c42b4e0a12 (diff)
downloadgcc-baad4c48a85a354d2bf1b17e5aff71203c08adea.zip
gcc-baad4c48a85a354d2bf1b17e5aff71203c08adea.tar.gz
gcc-baad4c48a85a354d2bf1b17e5aff71203c08adea.tar.bz2
Extend -Wstringop-overflow to detect out-of-bounds accesses to array parameters.
gcc/ChangeLog: PR c/50584 * builtins.c (warn_for_access): Add argument. Distinguish between reads and writes. (check_access): Add argument. Distinguish between reads and writes. (gimple_call_alloc_size): Set range even on failure. (gimple_parm_array_size): New function. (compute_objsize): Call it. (check_memop_access): Pass check_access an additional argument. (expand_builtin_memchr, expand_builtin_strcat): Same. (expand_builtin_strcpy, expand_builtin_stpcpy_1): Same. (expand_builtin_stpncpy, check_strncat_sizes): Same. (expand_builtin_strncat, expand_builtin_strncpy): Same. (expand_builtin_memcmp): Same. * builtins.h (compute_objsize): Declare a new overload. (gimple_parm_array_size): Declare. (check_access): Add argument. * calls.c (append_attrname): Simplify. (maybe_warn_rdwr_sizes): Handle internal attribute access. * tree-ssa-uninit.c (maybe_warn_pass_by_reference): Avoid adding quotes. gcc/testsuite/ChangeLog: PR c/50584 * c-c++-common/Wsizeof-pointer-memaccess1.c: Disable new expected warnings. * g++.dg/ext/attr-access.C: Update text of expected warnings. * gcc.dg/Wstringop-overflow-23.c: Same. * gcc.dg/Wstringop-overflow-24.c: Same. * gcc.dg/attr-access-none.c: Same. * gcc.dg/dfp/composite-type.c: Prune expected warnings. * gcc.dg/torture/pr57147-1.c: Add a member to an otherwise empty struct to avoid a warning. * gcc.dg/torture/pr57147-3.c: Same. * gcc.dg/Warray-bounds-30.c: Adjust. * gcc.dg/attr-access-none.c: Same. * gcc.dg/Wstringop-overflow-40.c: New test. * gcc.dg/attr-access-2.c: New test.
Diffstat (limited to 'gcc/builtins.c')
-rw-r--r--gcc/builtins.c314
1 files changed, 233 insertions, 81 deletions
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 8b9a4a4..45efc1c 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -183,7 +183,6 @@ 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);
static tree fold_builtin_object_size (tree, tree);
-static tree compute_objsize (tree, int, access_ref *, const vr_values * = NULL);
static bool get_range (tree, signop, offset_int[2], const vr_values * = NULL);
static bool check_read_access (tree, tree, tree = NULL_TREE, int = 1);
@@ -3490,51 +3489,146 @@ maybe_warn_for_bound (int opt, location_t loc, tree exp, tree func,
}
/* For an expression EXP issue an access warning controlled by option OPT
- with access to a region SLEN bytes in size in the RANGE of sizes. */
+ with access to a region SIZE bytes in size in the RANGE of sizes.
+ WRITE is true for a write access, READ for a read access, neither for
+ call that may or may not perform an access but for which the range
+ is expected to valid.
+ Returns true when a warning has been issued. */
static bool
-warn_for_access (location_t loc, tree func, tree exp, tree range[2],
- tree slen, bool access)
+warn_for_access (location_t loc, tree func, tree exp, int opt, tree range[2],
+ tree size, bool write, bool read)
{
bool warned = false;
- if (access)
+ if (write && read)
+ {
+ if (tree_int_cst_equal (range[0], range[1]))
+ warned = (func
+ ? warning_n (loc, opt, tree_to_uhwi (range[0]),
+ "%K%qD accessing %E byte in a region "
+ "of size %E",
+ "%K%qD accessing %E bytes in a region "
+ "of size %E",
+ exp, func, range[0], size)
+ : warning_n (loc, opt, tree_to_uhwi (range[0]),
+ "%Kaccessing %E byte in a region "
+ "of size %E",
+ "%Kaccessing %E bytes in a region "
+ "of size %E",
+ exp, range[0], size));
+ else if (tree_int_cst_sign_bit (range[1]))
+ {
+ /* Avoid printing the upper bound if it's invalid. */
+ warned = (func
+ ? warning_at (loc, opt,
+ "%K%qD accessing %E or more bytes in "
+ "a region of size %E",
+ exp, func, range[0], size)
+ : warning_at (loc, opt,
+ "%Kaccessing %E or more bytes in "
+ "a region of size %E",
+ exp, range[0], size));
+ }
+ else
+ warned = (func
+ ? warning_at (loc, opt,
+ "%K%qD accessing between %E and %E bytes "
+ "in a region of size %E",
+ exp, func, range[0], range[1],
+ size)
+ : warning_at (loc, opt,
+ "%Kaccessing between %E and %E bytes "
+ "in a region of size %E",
+ exp, range[0], range[1],
+ size));
+ return warned;
+ }
+
+ if (write)
+ {
+ if (tree_int_cst_equal (range[0], range[1]))
+ warned = (func
+ ? warning_n (loc, opt, tree_to_uhwi (range[0]),
+ "%K%qD writing %E byte into a region "
+ "of size %E overflows the destination",
+ "%K%qD writing %E bytes into a region "
+ "of size %E overflows the destination",
+ exp, func, range[0], size)
+ : warning_n (loc, opt, tree_to_uhwi (range[0]),
+ "%Kwriting %E byte into a region "
+ "of size %E overflows the destination",
+ "%Kwriting %E bytes into a region "
+ "of size %E overflows the destination",
+ exp, range[0], size));
+ else if (tree_int_cst_sign_bit (range[1]))
+ {
+ /* Avoid printing the upper bound if it's invalid. */
+ warned = (func
+ ? warning_at (loc, opt,
+ "%K%qD writing %E or more bytes into "
+ "a region of size %E overflows "
+ "the destination",
+ exp, func, range[0], size)
+ : warning_at (loc, opt,
+ "%Kwriting %E or more bytes into "
+ "a region of size %E overflows "
+ "the destination",
+ exp, range[0], size));
+ }
+ else
+ warned = (func
+ ? warning_at (loc, opt,
+ "%K%qD writing between %E and %E bytes "
+ "into a region of size %E overflows "
+ "the destination",
+ exp, func, range[0], range[1],
+ size)
+ : warning_at (loc, opt,
+ "%Kwriting between %E and %E bytes "
+ "into a region of size %E overflows "
+ "the destination",
+ exp, range[0], range[1],
+ size));
+ return warned;
+ }
+
+ if (read)
{
if (tree_int_cst_equal (range[0], range[1]))
warned = (func
? warning_n (loc, OPT_Wstringop_overread,
tree_to_uhwi (range[0]),
"%K%qD reading %E byte from a region of size %E",
- "%K%qD reading %E bytes from a region of size %E",
- exp, func, range[0], slen)
+ "%K%qD reading %E bytes from a region of size %E", exp, func, range[0], size)
: warning_n (loc, OPT_Wstringop_overread,
tree_to_uhwi (range[0]),
"%Kreading %E byte from a region of size %E",
"%Kreading %E bytes from a region of size %E",
- exp, range[0], slen));
+ exp, range[0], size));
else if (tree_int_cst_sign_bit (range[1]))
{
/* Avoid printing the upper bound if it's invalid. */
warned = (func
? warning_at (loc, OPT_Wstringop_overread,
- "%K%qD reading %E or more bytes from a region "
- "of size %E",
- exp, func, range[0], slen)
+ "%K%qD reading %E or more bytes from "
+ "a region of size %E",
+ exp, func, range[0], size)
: warning_at (loc, OPT_Wstringop_overread,
"%Kreading %E or more bytes from a region "
"of size %E",
- exp, range[0], slen));
+ exp, range[0], size));
}
else
warned = (func
? warning_at (loc, OPT_Wstringop_overread,
"%K%qD reading between %E and %E bytes from "
"a region of size %E",
- exp, func, range[0], range[1], slen)
- : warning_at (loc, OPT_Wstringop_overread,
- "%Kreading between %E and %E bytes from "
+ exp, func, range[0], range[1], size)
+ : warning_at (loc, opt,
+ "%K reading between %E and %E bytes from "
"a region of size %E",
- exp, range[0], range[1], slen));
+ exp, range[0], range[1], size));
if (warned)
TREE_NO_WARNING (exp) = true;
@@ -3542,18 +3636,19 @@ warn_for_access (location_t loc, tree func, tree exp, tree range[2],
return warned;
}
- if (tree_int_cst_equal (range[0], range[1]))
+ if (tree_int_cst_equal (range[0], range[1])
+ || tree_int_cst_sign_bit (range[1]))
warned = (func
? warning_n (loc, OPT_Wstringop_overread,
tree_to_uhwi (range[0]),
"%K%qD epecting %E byte in a region of size %E",
"%K%qD expecting %E bytes in a region of size %E",
- exp, func, range[0], slen)
+ exp, func, range[0], size)
: warning_n (loc, OPT_Wstringop_overread,
tree_to_uhwi (range[0]),
"%Kexpecting %E byte in a region of size %E",
"%Kexpecting %E bytes in a region of size %E",
- exp, range[0], slen));
+ exp, range[0], size));
else if (tree_int_cst_sign_bit (range[1]))
{
/* Avoid printing the upper bound if it's invalid. */
@@ -3561,22 +3656,22 @@ warn_for_access (location_t loc, tree func, tree exp, tree range[2],
? warning_at (loc, OPT_Wstringop_overread,
"%K%qD expecting %E or more bytes in a region "
"of size %E",
- exp, func, range[0], slen)
+ exp, func, range[0], size)
: warning_at (loc, OPT_Wstringop_overread,
"%Kexpecting %E or more bytes in a region "
"of size %E",
- exp, range[0], slen));
+ exp, range[0], size));
}
else
warned = (func
? warning_at (loc, OPT_Wstringop_overread,
"%K%qD expecting between %E and %E bytes in "
"a region of size %E",
- exp, func, range[0], range[1], slen)
+ exp, func, range[0], range[1], size)
: warning_at (loc, OPT_Wstringop_overread,
"%Kexpectting between %E and %E bytes in "
"a region of size %E",
- exp, range[0], range[1], slen));
+ exp, range[0], range[1], size));
if (warned)
TREE_NO_WARNING (exp) = true;
@@ -3759,8 +3854,9 @@ get_size_range (tree bound, tree range[2], const offset_int bndrng[2])
When DSTWRITE is null LEN is checked to verify that it doesn't exceed
SIZE_MAX.
- ACCESS is true for accesses, false for simple size checks in calls
- to functions that neither read from nor write to the region.
+ WRITE is true for write accesses, READ is true for reads. Both are
+ false for simple size checks in calls to functions that neither read
+ from nor write to the region.
When nonnull, PAD points to a more detailed description of the access.
@@ -3857,6 +3953,11 @@ check_access (tree exp, tree dstwrite,
get_size_range (dstwrite, range, pad ? pad->dst.bndrng : NULL);
tree func = get_callee_fndecl (exp);
+ /* Read vs write access by built-ins can be determined from the const
+ qualifiers on the pointer argument. In the absence of attribute
+ access, non-const qualified pointer arguments to user-defined
+ functions are assumed to both read and write the objects. */
+ const bool builtin = func ? fndecl_built_in_p (func) : false;
/* First check the number of bytes to be written against the maximum
object size. */
@@ -3913,51 +4014,18 @@ check_access (tree exp, tree dstwrite,
"the destination",
exp, range[0], dstsize));
}
- else if (tree_int_cst_equal (range[0], range[1]))
- warned = (func
- ? warning_n (loc, OPT_Wstringop_overflow_,
- tree_to_uhwi (range[0]),
- "%K%qD writing %E byte into a region "
- "of size %E overflows the destination",
- "%K%qD writing %E bytes into a region "
- "of size %E overflows the destination",
- exp, func, range[0], dstsize)
- : warning_n (loc, OPT_Wstringop_overflow_,
- tree_to_uhwi (range[0]),
- "%Kwriting %E byte into a region "
- "of size %E overflows the destination",
- "%Kwriting %E bytes into a region "
- "of size %E overflows the destination",
- exp, range[0], dstsize));
- else if (tree_int_cst_sign_bit (range[1]))
+ else
{
- /* Avoid printing the upper bound if it's invalid. */
- warned = (func
- ? warning_at (loc, OPT_Wstringop_overflow_,
- "%K%qD writing %E or more bytes into "
- "a region of size %E overflows "
- "the destination",
- exp, func, range[0], dstsize)
- : warning_at (loc, OPT_Wstringop_overflow_,
- "%Kwriting %E or more bytes into "
- "a region of size %E overflows "
- "the destination",
- exp, range[0], dstsize));
+ const bool read
+ = mode == access_read_only || mode == access_read_write;
+ const bool write
+ = mode == access_write_only || mode == access_read_write;
+ warned = warn_for_access (loc, func, exp,
+ OPT_Wstringop_overflow_,
+ range, dstsize,
+ write, read && !builtin);
}
- else
- warned = (func
- ? warning_at (loc, OPT_Wstringop_overflow_,
- "%K%qD writing between %E and %E bytes "
- "into a region of size %E overflows "
- "the destination",
- exp, func, range[0], range[1],
- dstsize)
- : warning_at (loc, OPT_Wstringop_overflow_,
- "%Kwriting between %E and %E bytes "
- "into a region of size %E overflows "
- "the destination",
- exp, range[0], range[1],
- dstsize));
+
if (warned)
{
TREE_NO_WARNING (exp) = true;
@@ -4037,10 +4105,15 @@ check_access (tree exp, tree dstwrite,
location_t loc = tree_nonartificial_location (exp);
loc = expansion_point_location_if_in_system_header (loc);
- if (warn_for_access (loc, func, exp, range, slen, mode)
- && pad)
- inform_access (pad->src, access_read_only);
-
+ const bool read
+ = mode == access_read_only || mode == access_read_write;
+ if (warn_for_access (loc, func, exp, OPT_Wstringop_overread, range,
+ slen, false, read))
+ {
+ TREE_NO_WARNING (exp) = true;
+ if (pad)
+ inform_access (pad->src, access_read_only);
+ }
return false;
}
@@ -4065,8 +4138,11 @@ check_read_access (tree exp, tree src, tree bound /* = NULL_TREE */,
}
/* If STMT is a call to an allocation function, returns the constant
- size of the object allocated by the call represented as sizetype.
- If nonnull, sets RNG1[] to the range of the size. */
+ maximum size of the object allocated by the call represented as
+ sizetype. If nonnull, sets RNG1[] to the range of the size.
+ When nonnull, uses RVALS for range information, otherwise calls
+ get_range_info to get it.
+ Returns null when STMT is not a call to a valid allocation function. */
tree
gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */,
@@ -4122,8 +4198,14 @@ gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */,
if (!rng1)
rng1 = rng1_buf;
+ const int prec = ADDR_MAX_PRECISION;
+ const tree size_max = TYPE_MAX_VALUE (sizetype);
if (!get_range (size, rng1, rvals))
- return NULL_TREE;
+ {
+ /* Use the full non-negative range on failure. */
+ rng1[0] = wi::zero (prec);
+ rng1[1] = wi::to_wide (size_max, prec);
+ }
if (argidx2 > nargs && TREE_CODE (size) == INTEGER_CST)
return fold_convert (sizetype, size);
@@ -4133,10 +4215,13 @@ gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */,
tree n = argidx2 < nargs ? gimple_call_arg (stmt, argidx2) : integer_one_node;
wide_int rng2[2];
if (!get_range (n, rng2, rvals))
- return NULL_TREE;
+ {
+ /* As above, use the full non-negative range on failure. */
+ rng2[0] = wi::zero (prec);
+ rng2[1] = wi::to_wide (size_max, prec);
+ }
/* Extend to the maximum precision to avoid overflow. */
- const int prec = ADDR_MAX_PRECISION;
rng1[0] = wide_int::from (rng1[0], prec, UNSIGNED);
rng1[1] = wide_int::from (rng1[1], prec, UNSIGNED);
rng2[0] = wide_int::from (rng2[0], prec, UNSIGNED);
@@ -4146,7 +4231,6 @@ gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */,
of SIZE_MAX and the product of the upper bounds as a constant. */
rng1[0] = rng1[0] * rng2[0];
rng1[1] = rng1[1] * rng2[1];
- tree size_max = TYPE_MAX_VALUE (sizetype);
if (wi::gtu_p (rng1[1], wi::to_wide (size_max, prec)))
{
rng1[1] = wi::to_wide (size_max);
@@ -4156,6 +4240,61 @@ gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */,
return wide_int_to_tree (sizetype, rng1[1]);
}
+/* For an access to an object referenced to by the function parameter PTR
+ of pointer type, and set RNG[] to the range of sizes of the object
+ obtainedfrom the attribute access specification for the current function.
+ Return the function parameter on success and null otherwise. */
+
+tree
+gimple_parm_array_size (tree ptr, wide_int rng[2],
+ const vr_values * /* = NULL */)
+{
+ /* For a function argument try to determine the byte size of the array
+ from the current function declaratation (e.g., attribute access or
+ related). */
+ tree var = SSA_NAME_VAR (ptr);
+ if (TREE_CODE (var) != PARM_DECL)
+ return NULL_TREE;
+
+ const unsigned prec = TYPE_PRECISION (sizetype);
+
+ rdwr_map rdwr_idx;
+ attr_access *access = get_parm_access (rdwr_idx, var);
+ if (!access)
+ return NULL_TREE;
+
+ if (access->sizarg != UINT_MAX)
+ {
+ /* TODO: Try to extract the range from the argument based on
+ those of subsequent assertions or based on known calls to
+ the current function. */
+ return NULL_TREE;
+ }
+
+ if (!access->minsize)
+ return NULL_TREE;
+
+ /* Only consider ordinary array bound at level 2 (or above if it's
+ ever added). */
+ if (warn_array_parameter < 2 && !access->static_p)
+ return NULL_TREE;
+
+ rng[0] = wi::zero (prec);
+ rng[1] = wi::uhwi (access->minsize, prec);
+ /* If the PTR argument points to an array multiply MINSIZE by the size
+ of array element type. Otherwise, multiply it by the size of what
+ the pointer points to. */
+ tree eltype = TREE_TYPE (TREE_TYPE (ptr));
+ if (TREE_CODE (eltype) == ARRAY_TYPE)
+ eltype = TREE_TYPE (eltype);
+ tree size = TYPE_SIZE_UNIT (eltype);
+ if (!size || TREE_CODE (size) != INTEGER_CST)
+ return NULL_TREE;
+
+ rng[1] *= wi::to_wide (size, prec);
+ return var;
+}
+
/* Wrapper around the wide_int overload of get_range. Returns the same
result but accepts offset_int instead. */
@@ -4348,6 +4487,21 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
return false;
}
+ if (gimple_nop_p (stmt))
+ {
+ /* 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 (ptr, wr, rvals);
+ if (!ref)
+ return NULL_TREE;
+ pref->ref = ref;
+ pref->sizrng[0] = offset_int::from (wr[0], UNSIGNED);
+ pref->sizrng[1] = offset_int::from (wr[1], UNSIGNED);
+ return true;
+ }
+
/* TODO: Handle PHI. */
if (!is_gimple_assign (stmt))
@@ -4400,7 +4554,7 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
/* A "public" wrapper around the above. Clients should use this overload
instead. */
-static tree
+tree
compute_objsize (tree ptr, int ostype, access_ref *pref,
const vr_values *rvals /* = NULL */)
{
@@ -4977,7 +5131,6 @@ expand_builtin_stpncpy (tree exp, rtx)
/* The size of the destination object. */
tree destsize = compute_objsize (dest, warn_stringop_overflow - 1, &data.dst);
check_access (exp, len, /*maxread=*/len, src, destsize, data.mode, &data);
-
return NULL_RTX;
}
@@ -5130,7 +5283,6 @@ expand_builtin_strncat (tree exp, rtx)
check_access (exp, /*dstwrite=*/NULL_TREE, maxread, srclen,
destsize, data.mode, &data);
-
return NULL_RTX;
}