* [PATCH] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
@ 2017-07-08 20:45 Martin Sebor
2017-07-18 2:51 ` [PING] " Martin Sebor
2017-07-31 17:29 ` Jeff Law
0 siblings, 2 replies; 39+ messages in thread
From: Martin Sebor @ 2017-07-08 20:45 UTC (permalink / raw)
To: Gcc Patch List
[-- Attachment #1: Type: text/plain, Size: 2869 bytes --]
PR 81117 asks for improved detection of common misuses(*) of
strncpy and strncat. The attached patch is my solution. It
consists of three related sets of changes:
1) Adds a new option, -Wstringop-truncation, that diagnoses calls
to strncpy, and stpncpy (and also strncat) that truncate the copy.
This helps highlight the common but incorrect assumption that
the first two functions NUL-terminate the copy (see, for example,
CWE-170) For strncat, it helps detect cases of inadvertent
truncation of the source string by passing in a bound that's
less than or equal to its length.
2) Enhances -Wstringon-overflow to diagnose calls of the form
strncpy(D, S, N) where the bound N depends on a call to strlen(S).
This misuse is common in legacy code when, often in response to
the adoption of a secure coding initiative, while replacing uses
of strcpy with strncpy, the engineer either makes a mistake, or
doesn't have a good enough understanding of how the function works,
or does only the bare minimum to satisfy the requirement to avoid
using strcpy without actually improving anything.
3) Enhances -Wsizeof-pointer-memaccess to also warn about uses of
the functions to copy an array to a destination of an unknown size
that specify the size of the array as the bound. Given the
pervasive [mis]use of strncpy to bound the copy to the size of
the destination, instances like this suggest a bug: a possible
buffer overflow due to an excessive bound (see, for example,
CWE-806). In cases when the call is safe, it's equivalent to
the corresponding call to memcpy which is clearer and can be
more efficient.
Martin
PS By coincidence rather than by intent, the strncat warnings
are a superset of Clang's -Wstrncat-size. AFAICS, Clang only
warns when the destination is an array of known size and
doesn't have a corresponding warning for strncpy.
[*] Here's some background into these misuses.
The purpose of the historical strncpy function introduced in V7
UNIX was to completely fill an array of chars with data, either
by copying an initial portion of a source string, or by clearing
it. I.e., its purpose wasn't to create NUL-terminated strings.
An example of its use was to fill the directory entry d_name
array (dirent::d_name) with the name of a file.
The original purpose of the strncat function, on the other hand,
was to append a not necessarily NUL-terminated array of chars
to a string to form a NUL-terminated concatenation of the two.
An example use case is appending a directory entry (struct
dirent::d_name) that need not be NUL-terminated, to form
a pathname which does.
Largely due to a misunderstanding of the functions' purpose they
have become commonly used (and misused) to make "safe," bounded
string copies by safeguarding against accidentally overflowing
the destination. This has led to great many bugs and security
vulnerabilities.
[-- Attachment #2: gcc-81117.diff --]
[-- Type: text/x-patch, Size: 52667 bytes --]
PR c/81117 - Improve buffer overflow checking in strncpy
gcc/ChangeLog:
PR c/81117
* builtins.c (compute_objsize): Handle arrays that
compute_builtin_object_size likes to fail for.
(check_strncpy_sizes): New function.
(expand_builtin_strncpy): Call check_strncpy_sizes.
* doc/invoke.texi (-Wsizeof-pointer-memaccess): Document enhancement.
(-Wstringop-truncation): Document new option.
* gimple-fold.c (gimple_fold_builtin_strncpy): Implement
-Wstringop-truncation.
(gimple_fold_builtin_strncat): Same.
* gimple.c (gimple_build_call_from_tree): Set call location.
* tree-ssa-strlen.c (strlen_to_stridx): New global variable.
(is_strlen_related_p): New function.
(check_builtin_strxncpy, check_builtin_strncat): Same.
handle_builtin_strlen): Use strlen_to_stridx.
(strlen_optimize_stmt): Handle flavors of strncat, strncpy, and
stpncpy.
Use strlen_to_stridx.
(pass_strlen::execute): Release strlen_to_stridx.
gcc/ada/ChangeLog:
PR c/81117
* adadecode.c (__gnat_decode): Replace pointless strncpy with
memcpy.
* argv.c (__gnat_fill_arg): Same.
gcc/c-family/ChangeLog:
PR c/81117
* c-common.c (resort_sorted_fields): Replace pointless strncpy
with memcpy.
* c-warn.c (sizeof_pointer_memaccess_warning): Handle arrays.
* c.opt (-Wstriingop-truncation): New option.
gcc/fortran/ChangeLog:
PR c/81117
* decl.c (build_sym): Replace pointless strncpy with strcpy.
gcc/objc/ChangeLog:
PR c/81117
* objc-encoding.c (encode_type): Replace pointless strncpy with
memcpy.
gcc/testsuite/ChangeLog:
PR c/81117
* c-c++-common/Wsizeof-pointer-memaccess3.c: New test.
* c-c++-common/Wstringop-overflow.c: New test.
* c-c++-common/Wstringop-truncation.c: New test.
* g++.dg/torture/Wsizeof-pointer-memaccess1.C: Adjust.
* g++.dg/torture/Wsizeof-pointer-memaccess2.C: Same.
* gcc.dg/Walloca-1.c: Disable macro tracking.
diff --git a/gcc/ada/adadecode.c b/gcc/ada/adadecode.c
index 8c9c7ab..0cbef81 100644
--- a/gcc/ada/adadecode.c
+++ b/gcc/ada/adadecode.c
@@ -330,7 +330,7 @@ __gnat_decode (const char *coded_name, char *ada_name, int verbose)
}
/* Write symbol in the space. */
- strncpy (optoken, trans_table[k][1], oplen);
+ memcpy (optoken, trans_table[k][1], oplen);
}
else
k++;
diff --git a/gcc/ada/argv.c b/gcc/ada/argv.c
index 430404e..aee0f88 100644
--- a/gcc/ada/argv.c
+++ b/gcc/ada/argv.c
@@ -92,7 +92,7 @@ void
__gnat_fill_arg (char *a, int i)
{
if (gnat_argv != NULL)
- strncpy (a, gnat_argv[i], strlen(gnat_argv[i]));
+ memcpy (a, gnat_argv[i], strlen (gnat_argv[i]));
}
int
@@ -118,7 +118,7 @@ void
__gnat_fill_env (char *a, int i)
{
if (gnat_envp != NULL)
- strncpy (a, gnat_envp[i], strlen (gnat_envp[i]));
+ memcpy (a, gnat_envp[i], strlen (gnat_envp[i]));
}
#ifdef __cplusplus
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 608993a..706f051 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -3300,13 +3300,43 @@ check_sizes (int opt, tree exp, tree size, tree maxlen, tree src, tree objsize)
the size of the object if successful or NULL when the size cannot
be determined. */
-static inline tree
+static tree
compute_objsize (tree dest, int ostype)
{
unsigned HOST_WIDE_INT size;
- if (compute_builtin_object_size (dest, ostype & 3, &size))
+
+ /* Only the two least significant bits are meaningful. */
+ ostype &= 3;
+
+ if (compute_builtin_object_size (dest, ostype, &size))
return build_int_cst (sizetype, size);
+ /* Unless computing the largest size (for memcpy and other raw memory
+ functions), try to determine the size of the object from its type. */
+ if (!ostype)
+ return NULL_TREE;
+
+ if (TREE_CODE (dest) == SSA_NAME)
+ {
+ gimple *stmt = SSA_NAME_DEF_STMT (dest);
+ if (!is_gimple_assign (stmt))
+ return NULL_TREE;
+
+ dest = gimple_assign_rhs1 (stmt);
+ }
+
+ if (TREE_CODE (dest) != ADDR_EXPR)
+ return NULL_TREE;
+
+ tree type = TREE_TYPE (dest);
+ if (TREE_CODE (type) == POINTER_TYPE)
+ type = TREE_TYPE (type);
+
+ type = TYPE_MAIN_VARIANT (type);
+
+ if (TREE_CODE (type) == ARRAY_TYPE)
+ return TYPE_SIZE_UNIT (type);
+
return NULL_TREE;
}
@@ -3949,6 +3979,36 @@ expand_builtin_strncat (tree exp, rtx)
return NULL_RTX;
}
+/* Helper to check the sizes of sequences and the destination of calls
+ to __builtin_strncpy (DST, SRC, LEN) and __builtin___strncpy_chk.
+ Returns true on success (no overflow warning), false otherwise. */
+
+static bool
+check_strncpy_sizes (tree exp, tree dst, tree src, tree len)
+{
+ tree dstsize = compute_objsize (dst, warn_stringop_overflow - 1);
+
+ if (!check_sizes (OPT_Wstringop_overflow_,
+ exp, len, /*maxlen=*/NULL_TREE, src, dstsize))
+ return false;
+
+ if (!dstsize || TREE_CODE (len) != INTEGER_CST)
+ return true;
+
+ int cmp = tree_int_cst_compare (dstsize, len);
+
+ if (cmp == 0)
+ warning_at (EXPR_LOCATION (exp), OPT_Wstringop_truncation,
+ "%K%qD specified bound %E equals destination size",
+ exp, get_callee_fndecl (exp), len);
+ else if (cmp < 0)
+ warning_at (EXPR_LOCATION (exp), OPT_Wstringop_truncation,
+ "%K%qD specified bound %E exceeds destination size %E",
+ exp, get_callee_fndecl (exp), len, dstsize);
+
+ return true;
+}
+
/* Expand expression EXP, which is a call to the strncpy builtin. Return
NULL_RTX if we failed the caller should emit a normal call. */
@@ -3967,16 +4027,7 @@ expand_builtin_strncpy (tree exp, rtx target)
/* The length of the source sequence. */
tree slen = c_strlen (src, 1);
- if (warn_stringop_overflow)
- {
- tree destsize = compute_objsize (dest,
- warn_stringop_overflow - 1);
-
- /* The number of bytes to write is LEN but check_sizes will also
- check SLEN if LEN's value isn't known. */
- check_sizes (OPT_Wstringop_overflow_,
- exp, len, /*maxlen=*/NULL_TREE, src, destsize);
- }
+ check_strncpy_sizes (exp, dest, src, len);
/* We must be passed a constant len and src parameter. */
if (!tree_fits_uhwi_p (len) || !slen || !tree_fits_uhwi_p (slen))
diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index b4217f3..5fc4d24 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -5942,10 +5942,10 @@ resort_sorted_fields (void *obj,
static char *
catenate_strings (const char *lhs, const char *rhs_start, int rhs_size)
{
- const int lhs_size = strlen (lhs);
+ const size_t lhs_size = strlen (lhs);
char *result = XNEWVEC (char, lhs_size + rhs_size);
- strncpy (result, lhs, lhs_size);
- strncpy (result + lhs_size, rhs_start, rhs_size);
+ memcpy (result, lhs, lhs_size);
+ memcpy (result + lhs_size, rhs_start, rhs_size);
return result;
}
diff --git a/gcc/c-family/c-warn.c b/gcc/c-family/c-warn.c
index b9378c2..4b04d81 100644
--- a/gcc/c-family/c-warn.c
+++ b/gcc/c-family/c-warn.c
@@ -626,7 +626,8 @@ sizeof_pointer_memaccess_warning (location_t *sizeof_arg_loc, tree callee,
|| vec_safe_length (params) <= 1)
return;
- switch (DECL_FUNCTION_CODE (callee))
+ enum built_in_function fncode = DECL_FUNCTION_CODE (callee);
+ switch (fncode)
{
case BUILT_IN_STRNCMP:
case BUILT_IN_STRNCASECMP:
@@ -708,8 +709,27 @@ sizeof_pointer_memaccess_warning (location_t *sizeof_arg_loc, tree callee,
type = TYPE_P (sizeof_arg[idx])
? sizeof_arg[idx] : TREE_TYPE (sizeof_arg[idx]);
+
if (!POINTER_TYPE_P (type))
- return;
+ {
+ /* The argument type may be an array. Diagnose bounded string
+ copy functions that specify the bound in terms of the source
+ argument rather than the destination. */
+ if (strop && !cmp && fncode != BUILT_IN_STRNDUP && src)
+ {
+ tem = tree_strip_nop_conversions (src);
+ if (TREE_CODE (tem) == ADDR_EXPR)
+ tem = TREE_OPERAND (tem, 0);
+ if (operand_equal_p (tem, sizeof_arg[idx], OEP_ADDRESS_OF))
+ warning_at (sizeof_arg_loc[idx], OPT_Wsizeof_pointer_memaccess,
+ "argument to %<sizeof%> in %qD call is the same "
+ "expression as the source; did you mean to use "
+ "the size of the destination?",
+ callee);
+ }
+
+ return;
+ }
if (dest
&& (tem = tree_strip_nop_conversions (dest))
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 05766c4..4b9c975 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -732,10 +732,15 @@ Warn about buffer overflow in string manipulation functions like memcpy
and strcpy.
Wstringop-overflow=
-C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_stringop_overflow) Init(2) Warning LangEnabledBy(C ObjC C++ ObjC++, Wall) IntegerRange(0, 4)
+C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_stringop_overflow) Init(2) Warning LangEnabledBy(C ObjC C++ ObjC++, Wall, 2, 0) IntegerRange(0, 4)
Under the control of Object Size type, warn about buffer overflow in string
manipulation functions like memcpy and strcpy.
+Wstringop-truncation
+C ObjC C++ ObjC++ Var(warn_stringop_truncation) Warning Init (1) LangEnabledBy(C ObjC C++ ObjC++, Wall)
+Warn about buffer overflow in string manipulation functions like memcpy
+and strcpy.
+
Wsuggest-attribute=format
C ObjC C++ ObjC++ Var(warn_suggest_attribute_format) Warning
Warn about functions which might be candidates for format attributes.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index d0b9050..e4208d9 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -312,7 +312,7 @@ Objective-C and Objective-C++ Dialects}.
-Wsizeof-pointer-memaccess -Wsizeof-array-argument @gol
-Wstack-protector -Wstack-usage=@var{len} -Wstrict-aliasing @gol
-Wstrict-aliasing=n -Wstrict-overflow -Wstrict-overflow=@var{n} @gol
--Wstringop-overflow=@var{n} @gol
+-Wstringop-overflow=@var{n} -Wstringop-truncation @gol
-Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{|}format@r{]} @gol
-Wsuggest-final-types @gol -Wsuggest-final-methods -Wsuggest-override @gol
-Wmissing-format-attribute -Wsubobject-linkage @gol
@@ -5170,6 +5170,57 @@ whether to issue a warning. Similarly to @option{-Wstringop-overflow=3} this
setting of the option may result in warnings for benign code.
@end table
+@item -Wstringop-truncation
+@opindex Wstringop-overflow
+@opindex Wno-stringop-overflow
+Warn for calls to bounded string manipulation functions such as @code{strncat},
+@code{strncpy}, and @code{stpncpy} that may either truncate the copied string
+or leave the destination unchanged.
+
+In the following example, the call to @code{strncat} specifies the length
+of the source string as the bound. If the destination contains a non-empty
+string the copy of the source will be truncated. Therefore, the call is
+diagnosed. To avoid the warning use @code{strlen (d) - 4)} as the bound;
+
+@smallexample
+void append (char *d)
+@{
+ strncat (d, ".txt", 4);
+@}
+@end smallexample
+
+As another example, the following call to @code{strncpy} results in copying
+to @code{d} just the characters preceding the terminating NUL, without
+appending the NUL to the end. Assuming the result of @code{strncpy} is
+necessarily a NUL-terminated string is a common mistake, and so the call
+is diagnosed. To avoid the warning, if the goal is to avoid overflowing
+the destination, provide the size of the destination buffer if it's known
+and append the terminating NUL, or otherwise call @code{memcpy} instead.
+
+@smallexample
+void copy (char *d, const char *s)
+@{
+ strncpy (d, s, strlen (s));
+@}
+@end smallexample
+
+In the following example, the call to @code{strncpy} specifies the size
+of the destination buffer as the bound. If the length of the source
+string is equal to or greater than this size the result of the copy will
+not be NUL-terminated. Therefore, the call is also diagnosed. To avoid
+the warning, specify @code{sizeof buf - 1} as the bound and set the last
+element of the buffer to NUL.
+
+@smallexample
+void copy (const char *s)
+@{
+ char buf[80];
+ strncpy (buf, s, sizeof buf);
+ @dots{}
+@}
+@end smallexample
+
+
@item -Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{|}format@r{]}
@opindex Wsuggest-attribute=
@opindex Wno-suggest-attribute=
@@ -6154,11 +6205,26 @@ not an array, but a pointer. This warning is enabled by @option{-Wall}.
@opindex Wsizeof-pointer-memaccess
@opindex Wno-sizeof-pointer-memaccess
Warn for suspicious length parameters to certain string and memory built-in
-functions if the argument uses @code{sizeof}. This warning warns e.g.@:
-about @code{memset (ptr, 0, sizeof (ptr));} if @code{ptr} is not an array,
-but a pointer, and suggests a possible fix, or about
-@code{memcpy (&foo, ptr, sizeof (&foo));}. This warning is enabled by
-@option{-Wall}.
+functions if the argument uses @code{sizeof}. This warning triggers for
+example for @code{memset (ptr, 0, sizeof (ptr));} if @code{ptr} is not
+an array, but a pointer, and suggests a possible fix, or about
+@code{memcpy (&foo, ptr, sizeof (&foo));}. @option{-Wsizeof-pointer-memaccess}
+also warns about calls to bounded string copy functions like @code{strncat}
+or @code{strncpy} that specify as the bound a @code{sizeof} expression of
+the source array. For example, in the following function the call to
+@code{strncat} specifies the size of the source string as the bound. That
+is almost certainly a mistake and so the call is diagnosed.
+@smallexample
+void make_file (const char *name)
+@{
+ char path[PATH_MAX];
+ strncpy (path, name, sizeof path - 1);
+ strncat (path, ".text", sizeof ".text");
+ @dots{}
+@}
+@end smallexample
+
+Th @option{-Wsizeof-pointer-memaccess} option is enabled by @option{-Wall}.
@item -Wsizeof-array-argument
@opindex Wsizeof-array-argument
diff --git a/gcc/fortran/decl.c b/gcc/fortran/decl.c
index bd31070..ee91504 100644
--- a/gcc/fortran/decl.c
+++ b/gcc/fortran/decl.c
@@ -1417,11 +1417,9 @@ build_sym (const char *name, gfc_charlen *cl, bool cl_deferred,
{
char u_name[GFC_MAX_SYMBOL_LEN + 1];
gfc_symtree *st;
- int nlen;
- nlen = strlen(name);
- gcc_assert (nlen <= GFC_MAX_SYMBOL_LEN);
- strncpy (u_name, name, nlen + 1);
+ gcc_assert (strlen(name) <= GFC_MAX_SYMBOL_LEN);
+ strcpy (u_name, name);
u_name[0] = upper;
st = gfc_find_symtree (gfc_current_ns->sym_root, u_name);
diff --git a/gcc/gimple-fold.c b/gcc/gimple-fold.c
index d94dc9c..5d021f9 100644
--- a/gcc/gimple-fold.c
+++ b/gcc/gimple-fold.c
@@ -40,6 +40,7 @@ along with GCC; see the file COPYING3. If not see
#include "gimple-iterator.h"
#include "tree-into-ssa.h"
#include "tree-dfa.h"
+#include "tree-object-size.h"
#include "tree-ssa.h"
#include "tree-ssa-propagate.h"
#include "ipa-utils.h"
@@ -57,6 +58,8 @@ along with GCC; see the file COPYING3. If not see
#include "tree-cfg.h"
#include "fold-const-call.h"
#include "asan.h"
+#include "diagnostic-core.h"
+#include "intl.h"
/* Return true when DECL can be referenced from current unit.
FROM_DECL (if non-null) specify constructor of variable DECL was taken from.
@@ -1522,12 +1525,25 @@ static bool
gimple_fold_builtin_strncpy (gimple_stmt_iterator *gsi,
tree dest, tree src, tree len)
{
- location_t loc = gimple_location (gsi_stmt (*gsi));
- tree fn;
+ gimple *stmt = gsi_stmt (*gsi);
+ location_t loc = gimple_location (stmt);
/* If the LEN parameter is zero, return DEST. */
if (integer_zerop (len))
{
+ /* Warn about the lack of nul termination: the result is not
+ a (nul-terminated) string. */
+ tree slen = get_maxval_strlen (src, 0);
+ if (slen && !integer_zerop (slen))
+ warning_at (loc, OPT_Wstringop_truncation,
+ "%qD destination unchanged after copying no bytes "
+ "from a string of length %E",
+ gimple_call_fndecl (stmt), slen);
+ else
+ warning_at (loc, OPT_Wstringop_truncation,
+ "%qD destination unchanged after copying no bytes",
+ gimple_call_fndecl (stmt));
+
replace_call_with_value (gsi, dest);
return true;
}
@@ -1542,16 +1558,36 @@ gimple_fold_builtin_strncpy (gimple_stmt_iterator *gsi,
if (!slen || TREE_CODE (slen) != INTEGER_CST)
return false;
- slen = size_binop_loc (loc, PLUS_EXPR, slen, ssize_int (1));
+ /* The size of the source string including the terminating nul. */
+ tree ssize = size_binop_loc (loc, PLUS_EXPR, slen, ssize_int (1));
/* We do not support simplification of this case, though we do
support it when expanding trees into RTL. */
/* FIXME: generate a call to __builtin_memset. */
- if (tree_int_cst_lt (slen, len))
+ if (tree_int_cst_lt (ssize, len))
return false;
+ if (tree_int_cst_lt (len, slen))
+ warning_at (loc, OPT_Wstringop_truncation,
+ (tree_int_cst_equal (size_one_node, len)
+ ? G_("%qD output truncated copying %E byte "
+ "from a string of length %E")
+ : G_("%qD output truncated copying %E bytes "
+ "from a string of length %E")),
+ gimple_call_fndecl (stmt), len, slen);
+ else if (tree_int_cst_equal (len, slen))
+ warning_at (loc, OPT_Wstringop_truncation,
+ (tree_int_cst_equal (size_one_node, len)
+ ? G_("%qD output truncated before terminating nul "
+ "copying %E byte from a string of the same "
+ "length")
+ : G_("%qD output truncated before terminating nul "
+ "copying %E bytes from a string of the same "
+ "length")),
+ gimple_call_fndecl (stmt), len);
+
/* OK transform into builtin memcpy. */
- fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
+ tree fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
if (!fn)
return false;
@@ -1560,6 +1596,7 @@ gimple_fold_builtin_strncpy (gimple_stmt_iterator *gsi,
NULL_TREE, true, GSI_SAME_STMT);
gimple *repl = gimple_build_call (fn, 3, dest, src, len);
replace_call_with_call_and_fold (gsi, repl);
+
return true;
}
@@ -1856,21 +1893,68 @@ gimple_fold_builtin_strncat (gimple_stmt_iterator *gsi)
return true;
}
- /* If the requested len is greater than or equal to the string
- length, call strcat. */
- if (TREE_CODE (len) == INTEGER_CST && p
- && compare_tree_int (len, strlen (p)) >= 0)
+ if (TREE_CODE (len) == INTEGER_CST && p)
{
- tree fn = builtin_decl_implicit (BUILT_IN_STRCAT);
+ unsigned srclen = strlen (p);
- /* If the replacement _DECL isn't initialized, don't do the
- transformation. */
- if (!fn)
- return false;
+ int cmpsrc = compare_tree_int (len, srclen);
- gcall *repl = gimple_build_call (fn, 2, dst, src);
- replace_call_with_call_and_fold (gsi, repl);
- return true;
+ unsigned HOST_WIDE_INT dstsize;
+
+ bool nowarn = gimple_no_warning_p (stmt);
+
+ if (!nowarn && compute_builtin_object_size (dst, 1, &dstsize))
+ {
+ int cmpdst = compare_tree_int (len, dstsize);
+
+ if (cmpdst >= 0)
+ {
+ /* Strncat copies (at most) LEN bytes and always appends
+ the terminating NUL so the specified bound should never
+ be equal to (or greater than) the size of the destination.
+ If it is, the copy could overflow. */
+ location_t loc = gimple_location (stmt);
+ nowarn = warning_at (loc, OPT_Wstringop_overflow_,
+ cmpdst == 0
+ ? G_("%qD specified bound %E equals "
+ "destination size")
+ : G_("%qD specified bound %E exceeds "
+ "destination size %wu"),
+ gimple_call_fndecl (stmt), len, dstsize);
+ if (nowarn)
+ gimple_set_no_warning (stmt, true);
+ }
+ }
+
+ if (!nowarn && cmpsrc <= 0)
+ {
+ /* To avoid certain truncation the specified bound should also
+ not be equal to (or less than) the length of the source. */
+ location_t loc = gimple_location (stmt);
+ if (warning_at (loc, OPT_Wstringop_truncation,
+ cmpsrc == 0
+ ? G_("%qD specified bound %E equals source length")
+ : G_("%qD specified bound %E is less than source "
+ "length %u"),
+ gimple_call_fndecl (stmt), len, srclen))
+ gimple_set_no_warning (stmt, true);
+ }
+
+ /* If the requested len is greater than or equal to the string
+ length, call strcat. */
+ if (cmpsrc >= 0)
+ {
+ tree fn = builtin_decl_implicit (BUILT_IN_STRCAT);
+
+ /* If the replacement _DECL isn't initialized, don't do the
+ transformation. */
+ if (!fn)
+ return false;
+
+ gcall *repl = gimple_build_call (fn, 2, dst, src);
+ replace_call_with_call_and_fold (gsi, repl);
+ return true;
+ }
}
return false;
diff --git a/gcc/gimple.c b/gcc/gimple.c
index 488f8c8..f2dcece 100644
--- a/gcc/gimple.c
+++ b/gcc/gimple.c
@@ -359,6 +359,7 @@ gimple_build_call_from_tree (tree t)
gimple_call_set_arg (call, i, CALL_EXPR_ARG (t, i));
gimple_set_block (call, TREE_BLOCK (t));
+ gimple_set_location (call, EXPR_LOCATION (t));
/* Carry all the CALL_EXPR flags to the new GIMPLE_CALL. */
gimple_call_set_chain (call, CALL_EXPR_STATIC_CHAIN (t));
diff --git a/gcc/objc/objc-encoding.c b/gcc/objc/objc-encoding.c
index 2a2dfa5..e5d4f38 100644
--- a/gcc/objc/objc-encoding.c
+++ b/gcc/objc/objc-encoding.c
@@ -734,7 +734,7 @@ encode_type (tree type, int curtype, int format)
/* Rewrite "in const" from "nr" to "rn". */
if (curtype >= 1 && !strncmp (enc - 1, "nr", 2))
- strncpy (enc - 1, "rn", 2);
+ memcpy (enc - 1, "rn", 2);
}
}
}
diff --git a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess3.c b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess3.c
new file mode 100644
index 0000000..cf0ca67
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess3.c
@@ -0,0 +1,132 @@
+/* Test -Wsizeof-pointer-memaccess warnings. */
+/* { dg-do compile } */
+/* { dg-options "-Wsizeof-pointer-memaccess -Wno-stringop-truncation -ftrack-macro-expansion=0" } */
+
+#define bos(ptr) __builtin_object_size (ptr, 1)
+#define bos0(ptr) __builtin_object_size (ptr, 0)
+
+#define memset(dst, val, sz) \
+ (FUNC (memset, dst, val, sz, bos (dst)), sink ((dst)))
+
+#define memcpy(dst, src, sz) \
+ (FUNC (memcpy, dst, src, sz, bos (dst)), sink ((dst)))
+
+#define memmove(dst, src, sz) \
+ (FUNC (memmove, dst, src, sz, bos (dst)), sink ((dst)))
+
+#define mempcpy(dst, src, sz) \
+ (FUNC (mempcpy, dst, src, sz, bos (dst)), sink ((dst)))
+
+#define strncpy(dst, src, sz) \
+ (FUNC (strncpy, dst, src, sz, bos (dst)), sink (dst))
+
+#define strncat(dst, src, sz) \
+ (FUNC (strncat, dst, src, sz, bos (dst)), sink (dst))
+
+#define stpncpy(dst, src, sz) \
+ (FUNC (stpncpy, dst, src, sz, bos (dst)), sink (dst))
+
+void sink (void*);
+
+#define S10 "123456789"
+extern char a10[10];
+
+void test_string_literal (char *dst)
+{
+#define FUNC(f, d, s, n, x) __builtin_ ## f (d, s, n)
+
+ /* It's common to call memcpy and other raw memory functions with
+ size drerived from the source argument. Verify that no warning
+ is ussued for such calls. */
+ memcpy (dst, S10, sizeof S10);
+ mempcpy (dst, S10, sizeof S10);
+ memmove (dst, S10, sizeof S10);
+
+ memset (dst, 0, sizeof S10);
+
+ stpncpy (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncpy (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncat (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ /* Unlike in the cases above, even though the calls below are likely
+ wrong, it's not easy to detect that the expression (sizeof X - 1)
+ involves sizeof of the source, so no warning is issued here, as
+ helpful as one might be. Whether -Wstringop-truncation is issued
+ is tested elsewhere. */
+ stpncpy (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncpy (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncat (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+}
+
+
+void test_char_array (char *dst)
+{
+ memcpy (dst, a10, sizeof a10);
+ mempcpy (dst, a10, sizeof a10);
+ memmove (dst, a10, sizeof a10);
+
+ memset (dst, 0, sizeof a10);
+
+ stpncpy (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncpy (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncat (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ stpncpy (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncpy (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncat (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+}
+
+
+#undef FUNC
+#define FUNC(f, d, s, n, os) __builtin___ ## f ## _chk (d, s, n, os)
+
+void test_char_array_chk (char *dst)
+{
+ memcpy (dst, S10, sizeof S10);
+ mempcpy (dst, S10, sizeof S10);
+ memmove (dst, S10, sizeof S10);
+
+ memset (dst, 0, sizeof S10);
+
+ stpncpy (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncpy (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncat (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ stpncpy (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncpy (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncat (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+}
+
+
+void test_string_literal_chk (char *dst)
+{
+ memcpy (dst, a10, sizeof a10);
+ mempcpy (dst, a10, sizeof a10);
+ memmove (dst, a10, sizeof a10);
+
+ memset (dst, 0, sizeof a10);
+
+ stpncpy (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncpy (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncat (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ stpncpy (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncpy (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncat (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+}
diff --git a/gcc/testsuite/c-c++-common/Wstringop-overflow.c b/gcc/testsuite/c-c++-common/Wstringop-overflow.c
new file mode 100644
index 0000000..4c3bd73
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wstringop-overflow.c
@@ -0,0 +1,157 @@
+/* PR middle-end/81117 - Improve buffer overflow checking in strncpy
+ { dg-do compile }
+ { dg-options "-O2 -Wstringop-overflow -Wno-stringop-truncation -ftrack-macro-expansion=0" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+#if __cplusplus
+extern "C" {
+#endif
+
+size_t strlen (const char*);
+char* strncat (char*, const char*, size_t);
+char* strncpy (char*, const char*, size_t);
+#if __cplusplus
+}
+#endif
+
+const char ar[] = "123";
+
+void test_strncat (char **d, const char* s, int i)
+{
+ /* Use a fresh pointer for each test to prevent the optimizer from
+ eliminating redundant writes into the same destination. Avoid
+ calling functions like sink() on the result that would have to
+ be assumed to change the source string by the alias oracle. */
+#define T(d, s, len) strncat (*d++, (s), (len))
+
+ T (d, "", 0);
+ T (d, "", 1);
+ T (d, "", 2);
+ T (d, "", 3);
+ T (d, "123", 0);
+ /* The following three calls truncate the copy and are diagnosed
+ by -Wstringop-truncation but there is evidence of overflow so
+ they're not diagnosed by -Wstringop-overflow. */
+ T (d, "123", 1);
+ T (d, "123", 2);
+ T (d, "123", 3);
+ T (d, "123", 4);
+ T (d, "123", 9);
+
+ T (d, s, strlen (s)); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ T (d, s, strlen (s) + 1); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ /* The following could also be diagnosed by -Wstringop-truncation
+ (with some effort to distinguish the pattern from others like
+ the one above. */
+ T (d, s, strlen (s) - 1); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ T (d, s, strlen (s) - i); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+
+ /* The following is dubious but not necessarily a smoking gun. */
+ T (d, s, strlen (s) - strlen (s));
+
+ {
+ signed char n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ short n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ int n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ unsigned n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s) - 1; /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-message ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ /* This doesn't overflow so iit should not be diagnosed. */
+ size_t n = strlen (s) - strlen (s);
+ T (d, s, n);
+ }
+
+ {
+ size_t n = i < strlen (s) ? i : strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-message ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+}
+
+
+void test_strncpy (char **d, const char* s, int i)
+{
+#undef T
+#define T(d, s, len) strncpy (*d++, (s), (len))
+
+ T (d, "", 0);
+ T (d, "", 1);
+ T (d, "", 2);
+ T (d, "", 3);
+ T (d, "123", 0);
+ T (d, "123", 1);
+ T (d, "123", 2);
+ T (d, "123", 3);
+ T (d, "123", 4);
+ T (d, "123", 9);
+
+ T (d, "123", sizeof "123");
+ T (d, ar, sizeof ar);
+
+ T (d, s, strlen (s)); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+
+ {
+ int n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ }
+
+ {
+ unsigned n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s) - 1; /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ }
+
+ {
+ /* This is diagnosed by -Wstringop-truncation. Verify that it isn't
+ also diagnosed by -Wstringop-overflow. */
+ size_t n = strlen (s) - strlen (s);
+ T (d, s, n);
+ }
+
+ {
+ /* This use of strncpy is certainly dubious and it could well be
+ diagnosed by -Wstringop-truncation but it isn't. That it is
+ diagnosed with -Wstringop-overflow is more by accident than
+ by design. -Wstringop-overflow considers any dependency of
+ the bound on strlen(s) a potential bug. */
+ size_t n = i < strlen (s) ? i : strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-message ".strncpy\[^\n\r]* specified bound depends on the length of the source argument" } */
+ }
+}
diff --git a/gcc/testsuite/c-c++-common/Wstringop-truncation.c b/gcc/testsuite/c-c++-common/Wstringop-truncation.c
new file mode 100644
index 0000000..240f6aa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wstringop-truncation.c
@@ -0,0 +1,212 @@
+/* PR middle-end/81117 - Improve buffer overflow checking in strncpy
+ { dg-do compile }
+ { dg-options "-O2 -Wstringop-truncation -Wno-stringop-overflow -ftrack-macro-expansion=0" } */
+
+
+typedef __SIZE_TYPE__ size_t;
+
+#if __cplusplus
+extern "C" {
+#endif
+
+size_t strlen (const char*);
+char* strncat (char*, const char*, size_t);
+char* strncpy (char*, const char*, size_t);
+
+#if __cplusplus
+}
+#endif
+
+void sink (char*);
+
+#define S4 "123"
+const char a4[] = "123";
+
+
+typedef struct Dest
+{
+ char a[5];
+} Dest;
+
+char dest[7];
+
+/* Verify strncat warnings for arrays of known bounds. */
+
+void test_strncat_array (Dest *pd)
+{
+#undef T
+#define T(d, s, len) (strncat ((d), (s), (len)), sink (d))
+
+ T (dest, S4, 2); /* { dg-warning "specified bound 2 is less than source length 3" } */
+ T (dest, S4, 3); /* { dg-warning "specified bound 3 equals source length" } */
+
+ T (dest, a4, 1); /* { dg-warning "specified bound 1 is less than source length 3" } */
+ T (dest, a4, 3); /* { dg-warning "specified bound 3 equals source length" } */
+
+ T (pd->a, S4, 2); /* { dg-warning "specified bound 2 is less than source length" } */
+ T (pd->a, S4, 1); /* { dg-warning "specified bound 1 is less than source length 3" } */
+}
+
+/* Verify strncpy warnings for pointers to unknown strings. */
+
+void test_strncpy_ptr (char *d, const char* s, int i)
+{
+#undef T
+#define T(d, s, len) (strncpy ((d), (s), (len)), sink (d))
+
+ /* Strncpy doesn't nul-terminate so the following is diagnosed. */
+ T (d, "", 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
+
+ /* This is safe. */
+ T (d, "", 1);
+ T (d, "", 2);
+
+ /* Truncation. */
+ T (d, "123", 3); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ T (d, "123", 4);
+ T (d, "123", 9);
+
+ T (d, S4, sizeof S4); /* Covered by -Wsizeof-pointer-memaccess. */
+ T (d, S4, sizeof S4 - 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+
+ T (d, a4, sizeof a4); /* Covered by -Wsizeof-pointer-memaccess. */
+ T (d, a4, sizeof a4 - 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ T (d, a4, sizeof a4 - 3); /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 1 byte from a string of length 3" } */
+ T (d, a4, sizeof a4 - 4); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes from a string of length 3" } */
+
+ T (d, S4, strlen (S4)); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ /* Likely buggy but no truncation. Diagnosed by -Wstringop-overflow. */
+ T (d, S4, strlen (S4) + 1);
+ T (d, S4, strlen (S4) + i);
+
+ T (d, S4, strlen (a4)); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ /* As above, buggy but no evidence of truncation. */
+ T (d, S4, strlen (a4) + 1);
+ T (d, S4, strlen (a4) + i);
+
+ {
+ signed char n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ short n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ int n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ unsigned n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ size_t n;
+ char *dp2 = d + 1;
+ n = strlen (s); /* { dg-message "length computed here" } */
+ T (dp2, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ /* The following is likely buggy but there's no apparent truncation
+ so it's not diagnosed by -Wstringop-truncation. Instead, it is
+ diagnosed by -Wstringop-overflow (tested elsewhere). */
+ int n;
+ n = strlen (s) - 1;
+ T (d, s, n);
+ }
+
+ {
+ /* Same as above. */
+ size_t n;
+ n = strlen (s) - 1;
+ T (d, s, n);
+ }
+
+ {
+ size_t n = strlen (s) - strlen (s);
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
+ }
+
+ {
+ /* This use of strncpy is dubious but it's probably not worth
+ worrying about (truncation may not actually take place when
+ i is the result). It is diagnosed with -Wstringop-overflow
+ (although more by accident than by design).
+
+ size_t n = i < strlen (s) ? i : strlen (s);
+ T (d, s, n);
+ */
+ }
+}
+
+
+/* Verify strncpy warnings for arrays of known bounds. */
+
+void test_strncpy_array (Dest *pd, const char* s)
+{
+ T (dest, s, 7); /* { dg-warning "specified bound 7 equals destination size" } */
+ T (dest, s, sizeof dest); /* { dg-warning "specified bound 7 equals destination size" } */
+
+ /* Because strnlen appends as many NULs as necessary to write the specified
+ number of byts the following doesn't (necessarily) truncate but rather
+ overflow, and so is diagnosed by -Wstringop-overflow. */
+ T (dest, s, 8);
+
+ T (dest + 1, s, 6); /* { dg-warning "specified bound 6 equals destination size" } */
+ T (dest + 6, s, 1); /* { dg-warning "specified bound 1 equals destination size" } */
+
+ T (pd->a, s, 5); /* { dg-warning "specified bound 5 equals destination size" } */
+ T (pd->a, s, sizeof pd->a); /* { dg-warning "specified bound 5 equals destination size" } */
+
+ /* Same asbove, diagnosed by -Wstringop-overflow. */
+ T (pd->a, s, 6);
+}
+
+typedef struct Flex
+{
+ size_t n;
+ char a0[0];
+ char a[];
+} Flex;
+
+extern char array[];
+
+/* Verify that no warning is issued for array of unknown bound, flexible
+ array members, or zero-length arrays. */
+
+void test_strncpy_flexarray (Flex *pd, const char* s)
+{
+ T (array, s, 7);
+ T (array, s, 123);
+
+ T (pd->a0, s, 1);
+ T (pd->a0, s, 1234);
+
+ T (pd->a, s, 5);
+ T (pd->a, s, 12345);
+}
+
+/* Verify warnings for dynamically allocated objects. */
+
+void test_strncpy_alloc (const char* s)
+{
+ size_t n = 7;
+ char *d = (char *)__builtin_malloc (n);
+
+ T (d, s, n); /* { dg-warning "specified bound 7 equals destination size" "bug 79016" { xfail *-*-* } } */
+
+ Dest *pd = (Dest *)__builtin_malloc (sizeof *pd * n);
+ T (pd->a, s, 5); /* { dg-warning "specified bound 5 equals destination size" } */
+ T (pd->a, s, sizeof pd->a); /* { dg-warning "specified bound 5 equals destination size" } */
+}
diff --git a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C
index c72532b..5bc5c4c 100644
--- a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C
+++ b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C
@@ -1,6 +1,6 @@
// Test -Wsizeof-pointer-memaccess warnings.
// { dg-do compile }
-// { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow" }
+// { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow -Wno-stringop-truncation" }
// Test just twice, once with -O0 non-fortified, once with -O2 fortified.
// { dg-skip-if "" { *-*-* } { "*" } { "-O0" "-O2" } }
// { dg-skip-if "" { *-*-* } { "-flto" } { "" } }
@@ -698,12 +698,17 @@ f4 (char *x, char **y, int z, char w[64])
strncat (w, s2, sizeof (w)); // { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" }
stpncpy (w, s1, sizeof (w)); // { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" }
- // These are correct, no warning.
const char s3[] = "foobarbaz";
const char s4[] = "abcde12345678";
- strncpy (x, s3, sizeof (s3));
- strncat (x, s4, sizeof (s4));
- stpncpy (x, s3, sizeof (s3));
+
+ // These are pointless when the destination is large enough, and
+ // cause overflow otherwise. They might as well be replaced by
+ // strcpy() or memcpy().
+ strncpy (x, s3, sizeof (s3)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" }
+ strncat (x, s4, sizeof (s4)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" }
+ stpncpy (x, s3, sizeof (s3)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" }
+
+ // These are safe, no warning.
y[1] = strndup (s3, sizeof (s3));
z += strncmp (s3, s4, sizeof (s3));
z += strncmp (s3, s4, sizeof (s4));
diff --git a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C
index a216f47..f2c864b 100644
--- a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C
+++ b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C
@@ -1,6 +1,6 @@
// Test -Wsizeof-pointer-memaccess warnings.
// { dg-do compile }
-// { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow" }
+// { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow -Wno-stringop-truncation" }
// Test just twice, once with -O0 non-fortified, once with -O2 fortified,
// suppressing buffer overflow warnings.
// { dg-skip-if "" { *-*-* } { "*" } { "-O0" "-O2" } }
@@ -703,12 +703,13 @@ f4 (char *x, char **y, int z, char w[64])
strncat (w, s2, sizeof (w)); // { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" }
stpncpy (w, s1, sizeof (w)); // { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" }
- // These are correct, no warning.
const char s3[] = "foobarbaz";
const char s4[] = "abcde12345678";
- strncpy (x, s3, sizeof (s3));
- strncat (x, s4, sizeof (s4));
- stpncpy (x, s3, sizeof (s3));
+ strncpy (x, s3, sizeof (s3)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination" }
+ strncat (x, s4, sizeof (s4)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination" }
+ stpncpy (x, s3, sizeof (s3)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination" }
+
+ // These are safe, no warning.
y[1] = strndup (s3, sizeof (s3));
z += strncmp (s3, s4, sizeof (s3));
z += strncmp (s3, s4, sizeof (s4));
diff --git a/gcc/testsuite/gcc.dg/Walloca-1.c b/gcc/testsuite/gcc.dg/Walloca-1.c
index ad39373..85e9160 100644
--- a/gcc/testsuite/gcc.dg/Walloca-1.c
+++ b/gcc/testsuite/gcc.dg/Walloca-1.c
@@ -1,6 +1,6 @@
/* { dg-do compile } */
/* { dg-require-effective-target alloca } */
-/* { dg-options "-Walloca-larger-than=2000 -O2" } */
+/* { dg-options "-Walloca-larger-than=2000 -O2 -ftrack-macro-expansion=0" } */
#define alloca __builtin_alloca
diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c
index b0563fe..1f4b0f3 100644
--- a/gcc/tree-ssa-strlen.c
+++ b/gcc/tree-ssa-strlen.c
@@ -40,11 +40,14 @@ along with GCC; see the file COPYING3. If not see
#include "expr.h"
#include "tree-dfa.h"
#include "domwalk.h"
+#include "tree-ssa-alias.h"
#include "tree-ssa-propagate.h"
#include "params.h"
#include "ipa-chkp.h"
#include "tree-hash-traits.h"
#include "builtins.h"
+#include "diagnostic-core.h"
+#include "diagnostic.h"
/* A vector indexed by SSA_NAME_VERSION. 0 means unknown, positive value
is an index into strinfo vector, negative value stands for
@@ -146,6 +149,9 @@ struct decl_stridxlist_map
mappings. */
static hash_map<tree_decl_hash, stridxlist> *decl_to_stridxlist_htab;
+typedef std::pair<int, location_t> stridx_strlenloc;
+static hash_map<tree, stridx_strlenloc> strlen_to_stridx;
+
/* Obstack for struct stridxlist and struct decl_stridxlist_map. */
static struct obstack stridx_obstack;
@@ -1197,6 +1203,9 @@ handle_builtin_strlen (gimple_stmt_iterator *gsi)
si->nonzero_chars = lhs;
gcc_assert (si->full_string_p);
}
+
+ location_t loc = gimple_location (stmt);
+ strlen_to_stridx.put (lhs, stridx_strlenloc (idx, loc));
return;
}
}
@@ -1240,6 +1249,9 @@ handle_builtin_strlen (gimple_stmt_iterator *gsi)
strinfo *si = new_strinfo (src, idx, lhs, true);
set_strinfo (idx, si);
find_equal_ptrs (src, idx);
+
+ location_t loc = gimple_location (stmt);
+ strlen_to_stridx.put (lhs, stridx_strlenloc (idx, loc));
}
}
@@ -1606,6 +1618,133 @@ handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi)
fprintf (dump_file, "not possible.\n");
}
+/* Return true if LEN depends on a call to strlen(SRC) in an interesting
+ way. LEN can either be an integer expression, or a pointer (to char).
+ When it is the latter (such as in recursive calls to self) is is
+ assumed to be the argument in some call to strlen() whose relationship
+ to SRC is being ascertained. */
+
+static bool
+is_strlen_related_p (tree src, tree len)
+{
+ if (TREE_CODE (TREE_TYPE (len)) == POINTER_TYPE
+ && operand_equal_p (src, len, 0))
+ return true;
+
+ if (TREE_CODE (len) != SSA_NAME)
+ return false;
+
+ gimple *def_stmt = SSA_NAME_DEF_STMT (len);
+ if (!def_stmt)
+ return false;
+
+ if (is_gimple_call (def_stmt))
+ {
+ tree func = gimple_call_fndecl (def_stmt);
+ if (!valid_builtin_call (def_stmt)
+ || DECL_FUNCTION_CODE (func) != BUILT_IN_STRLEN)
+ return false;
+
+ tree arg = gimple_call_arg (def_stmt, 0);
+ return is_strlen_related_p (src, arg);
+ }
+
+ if (!is_gimple_assign (def_stmt))
+ return false;
+
+ tree_code code = gimple_assign_rhs_code (def_stmt);
+ tree rhs1 = gimple_assign_rhs1 (def_stmt);
+ tree rhstype = TREE_TYPE (rhs1);
+
+ if ((TREE_CODE (rhstype) == POINTER_TYPE && code == POINTER_PLUS_EXPR)
+ || (INTEGRAL_TYPE_P (rhstype)
+ && (code == BIT_AND_EXPR
+ || code == NOP_EXPR)))
+ {
+ /* Pointer plus (an integer) and integer cast or truncation are
+ considered among the (potentiall) related expressions to strlen.
+ Others are not. */
+ return is_strlen_related_p (src, rhs1);
+ }
+
+ return false;
+}
+
+/* Check the size argument to the built-in forms of stpncpy and strncpy
+ to see if it's derived from calling strlen() on the source argument
+ and if so, issue a warning. */
+
+static void
+check_builtin_stxncpy (built_in_function, gimple_stmt_iterator *gsi)
+{
+ gimple *stmt = gsi_stmt (*gsi);
+
+ bool with_bounds = gimple_call_with_bounds_p (stmt);
+
+ tree src = gimple_call_arg (stmt, with_bounds ? 2 : 1);
+ tree len = gimple_call_arg (stmt, with_bounds ? 3 : 2);
+
+ /* If the length argument was computed from strlen(S) for some string
+ S retrieve the strinfo index for the string (PSS->FIRST) alonng with
+ the location of the strlen() call (PSS->SECOND). */
+ stridx_strlenloc *pss = strlen_to_stridx.get (len);
+ if (!pss || pss->first <= 0)
+ return;
+
+ int sidx = get_stridx (src);
+ strinfo *sisrc = sidx > 0 ? get_strinfo (sidx) : NULL;
+
+ /* Strncpy() et al. cannot modify the source string. Prevent the rest
+ of the pass from invalidating the strinfo data. */
+ if (sisrc)
+ sisrc->dont_invalidate = true;
+
+ /* Retrieve the strinfo data for the string S that LEN was computed
+ from as some function F of strlen (S) (i.e., LEN need not be equal
+ to strlen(S)). */
+ strinfo *silen = get_strinfo (pss->first);
+
+ location_t callloc = gimple_location (stmt);
+ tree func = gimple_call_fndecl (stmt);
+
+ bool warned = false;
+
+ /* When -Wstringop-truncation is set, try to determine truncation
+ before diagnosing possible overflow. Truncation is implied by
+ the LEN argument being equal to strlen(SRC), regardless of
+ whether its value is known. Otherwise, issue the more generic
+ -Wstringop-overflow which triggers for LEN arguments that in
+ any meaningful way depend on strlen(SRC). */
+ if (warn_stringop_truncation
+ && sisrc == silen
+ && is_strlen_related_p (src, len))
+ warned = warning_at (callloc, OPT_Wstringop_truncation,
+ "%qD output truncated before terminating nul "
+ "copying as many bytes from a string as its length",
+ func);
+ else if (silen && is_strlen_related_p (src, silen->ptr))
+ warned = warning_at (callloc, OPT_Wstringop_overflow_,
+ "%qD specified bound depends on the length "
+ "of the source argument", func);
+ if (warned)
+ {
+ location_t strlenloc = pss->second;
+ if (strlenloc != UNKNOWN_LOCATION && strlenloc != callloc)
+ inform (strlenloc, "length computed here");
+ }
+}
+
+/* Check the size argument to the built-in forms of strncat to see if
+ it's derived from calling strlen() on the source argument and if so,
+ issue a warning. */
+
+static void
+check_builtin_strncat (built_in_function bcode, gimple_stmt_iterator *gsi)
+{
+ /* Same as stxncpy(). */
+ check_builtin_stxncpy (bcode, gsi);
+}
+
/* Handle a memcpy-like ({mem{,p}cpy,__mem{,p}cpy_chk}) call.
If strlen of the second argument is known and length of the third argument
is that plus one, strlen of the first argument is the same after this
@@ -2512,6 +2651,19 @@ strlen_optimize_stmt (gimple_stmt_iterator *gsi)
case BUILT_IN_STPCPY_CHK_CHKP:
handle_builtin_strcpy (DECL_FUNCTION_CODE (callee), gsi);
break;
+
+ case BUILT_IN_STRNCAT:
+ case BUILT_IN_STRNCAT_CHK:
+ check_builtin_strncat (DECL_FUNCTION_CODE (callee), gsi);
+ break;
+
+ case BUILT_IN_STPNCPY:
+ case BUILT_IN_STPNCPY_CHK:
+ case BUILT_IN_STRNCPY:
+ case BUILT_IN_STRNCPY_CHK:
+ check_builtin_stxncpy (DECL_FUNCTION_CODE (callee), gsi);
+ break;
+
case BUILT_IN_MEMCPY:
case BUILT_IN_MEMCPY_CHK:
case BUILT_IN_MEMPCPY:
@@ -2575,6 +2727,10 @@ strlen_optimize_stmt (gimple_stmt_iterator *gsi)
else if (code == EQ_EXPR || code == NE_EXPR)
fold_strstr_to_strncmp (gimple_assign_rhs1 (stmt),
gimple_assign_rhs2 (stmt), stmt);
+
+ tree rhs1 = gimple_assign_rhs1 (stmt);
+ if (stridx_strlenloc *ps = strlen_to_stridx.get (rhs1))
+ strlen_to_stridx.put (lhs, *ps);
}
else if (TREE_CODE (lhs) != SSA_NAME && !TREE_SIDE_EFFECTS (lhs))
{
@@ -2826,6 +2982,8 @@ pass_strlen::execute (function *fun)
laststmt.len = NULL_TREE;
laststmt.stridx = 0;
+ strlen_to_stridx.empty ();
+
return 0;
}
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PING] [PATCH] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-07-08 20:45 [PATCH] enhance overflow and truncation detection in strncpy and strncat (PR 81117) Martin Sebor
@ 2017-07-18 2:51 ` Martin Sebor
2017-07-25 3:10 ` [PING #2] " Martin Sebor
2017-07-31 17:29 ` Jeff Law
1 sibling, 1 reply; 39+ messages in thread
From: Martin Sebor @ 2017-07-18 2:51 UTC (permalink / raw)
To: Gcc Patch List
Ping:
https://gcc.gnu.org/ml/gcc-patches/2017-07/msg00411.html
On 07/08/2017 02:45 PM, Martin Sebor wrote:
> PR 81117 asks for improved detection of common misuses(*) of
> strncpy and strncat. The attached patch is my solution. It
> consists of three related sets of changes:
>
> 1) Adds a new option, -Wstringop-truncation, that diagnoses calls
> to strncpy, and stpncpy (and also strncat) that truncate the copy.
> This helps highlight the common but incorrect assumption that
> the first two functions NUL-terminate the copy (see, for example,
> CWE-170) For strncat, it helps detect cases of inadvertent
> truncation of the source string by passing in a bound that's
> less than or equal to its length.
>
> 2) Enhances -Wstringon-overflow to diagnose calls of the form
> strncpy(D, S, N) where the bound N depends on a call to strlen(S).
> This misuse is common in legacy code when, often in response to
> the adoption of a secure coding initiative, while replacing uses
> of strcpy with strncpy, the engineer either makes a mistake, or
> doesn't have a good enough understanding of how the function works,
> or does only the bare minimum to satisfy the requirement to avoid
> using strcpy without actually improving anything.
>
> 3) Enhances -Wsizeof-pointer-memaccess to also warn about uses of
> the functions to copy an array to a destination of an unknown size
> that specify the size of the array as the bound. Given the
> pervasive [mis]use of strncpy to bound the copy to the size of
> the destination, instances like this suggest a bug: a possible
> buffer overflow due to an excessive bound (see, for example,
> CWE-806). In cases when the call is safe, it's equivalent to
> the corresponding call to memcpy which is clearer and can be
> more efficient.
>
> Martin
>
> PS By coincidence rather than by intent, the strncat warnings
> are a superset of Clang's -Wstrncat-size. AFAICS, Clang only
> warns when the destination is an array of known size and
> doesn't have a corresponding warning for strncpy.
>
> [*] Here's some background into these misuses.
>
> The purpose of the historical strncpy function introduced in V7
> UNIX was to completely fill an array of chars with data, either
> by copying an initial portion of a source string, or by clearing
> it. I.e., its purpose wasn't to create NUL-terminated strings.
> An example of its use was to fill the directory entry d_name
> array (dirent::d_name) with the name of a file.
>
> The original purpose of the strncat function, on the other hand,
> was to append a not necessarily NUL-terminated array of chars
> to a string to form a NUL-terminated concatenation of the two.
> An example use case is appending a directory entry (struct
> dirent::d_name) that need not be NUL-terminated, to form
> a pathname which does.
>
> Largely due to a misunderstanding of the functions' purpose they
> have become commonly used (and misused) to make "safe," bounded
> string copies by safeguarding against accidentally overflowing
> the destination. This has led to great many bugs and security
> vulnerabilities.
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PING #2] [PATCH] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-07-18 2:51 ` [PING] " Martin Sebor
@ 2017-07-25 3:10 ` Martin Sebor
0 siblings, 0 replies; 39+ messages in thread
From: Martin Sebor @ 2017-07-25 3:10 UTC (permalink / raw)
To: Gcc Patch List
Ping #2:
https://gcc.gnu.org/ml/gcc-patches/2017-07/msg00411.html
>
> On 07/08/2017 02:45 PM, Martin Sebor wrote:
>> PR 81117 asks for improved detection of common misuses(*) of
>> strncpy and strncat. The attached patch is my solution. It
>> consists of three related sets of changes:
>>
>> 1) Adds a new option, -Wstringop-truncation, that diagnoses calls
>> to strncpy, and stpncpy (and also strncat) that truncate the copy.
>> This helps highlight the common but incorrect assumption that
>> the first two functions NUL-terminate the copy (see, for example,
>> CWE-170) For strncat, it helps detect cases of inadvertent
>> truncation of the source string by passing in a bound that's
>> less than or equal to its length.
>>
>> 2) Enhances -Wstringon-overflow to diagnose calls of the form
>> strncpy(D, S, N) where the bound N depends on a call to strlen(S).
>> This misuse is common in legacy code when, often in response to
>> the adoption of a secure coding initiative, while replacing uses
>> of strcpy with strncpy, the engineer either makes a mistake, or
>> doesn't have a good enough understanding of how the function works,
>> or does only the bare minimum to satisfy the requirement to avoid
>> using strcpy without actually improving anything.
>>
>> 3) Enhances -Wsizeof-pointer-memaccess to also warn about uses of
>> the functions to copy an array to a destination of an unknown size
>> that specify the size of the array as the bound. Given the
>> pervasive [mis]use of strncpy to bound the copy to the size of
>> the destination, instances like this suggest a bug: a possible
>> buffer overflow due to an excessive bound (see, for example,
>> CWE-806). In cases when the call is safe, it's equivalent to
>> the corresponding call to memcpy which is clearer and can be
>> more efficient.
>>
>> Martin
>>
>> PS By coincidence rather than by intent, the strncat warnings
>> are a superset of Clang's -Wstrncat-size. AFAICS, Clang only
>> warns when the destination is an array of known size and
>> doesn't have a corresponding warning for strncpy.
>>
>> [*] Here's some background into these misuses.
>>
>> The purpose of the historical strncpy function introduced in V7
>> UNIX was to completely fill an array of chars with data, either
>> by copying an initial portion of a source string, or by clearing
>> it. I.e., its purpose wasn't to create NUL-terminated strings.
>> An example of its use was to fill the directory entry d_name
>> array (dirent::d_name) with the name of a file.
>>
>> The original purpose of the strncat function, on the other hand,
>> was to append a not necessarily NUL-terminated array of chars
>> to a string to form a NUL-terminated concatenation of the two.
>> An example use case is appending a directory entry (struct
>> dirent::d_name) that need not be NUL-terminated, to form
>> a pathname which does.
>>
>> Largely due to a misunderstanding of the functions' purpose they
>> have become commonly used (and misused) to make "safe," bounded
>> string copies by safeguarding against accidentally overflowing
>> the destination. This has led to great many bugs and security
>> vulnerabilities.
>>
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-07-08 20:45 [PATCH] enhance overflow and truncation detection in strncpy and strncat (PR 81117) Martin Sebor
2017-07-18 2:51 ` [PING] " Martin Sebor
@ 2017-07-31 17:29 ` Jeff Law
2017-07-31 19:42 ` Martin Sebor
1 sibling, 1 reply; 39+ messages in thread
From: Jeff Law @ 2017-07-31 17:29 UTC (permalink / raw)
To: Martin Sebor, Gcc Patch List
On 07/08/2017 02:45 PM, Martin Sebor wrote:
> PR 81117 asks for improved detection of common misuses(*) of
> strncpy and strncat. The attached patch is my solution. It
> consists of three related sets of changes:
>
> 1) Adds a new option, -Wstringop-truncation, that diagnoses calls
> to strncpy, and stpncpy (and also strncat) that truncate the copy.
> This helps highlight the common but incorrect assumption that
> the first two functions NUL-terminate the copy (see, for example,
> CWE-170) For strncat, it helps detect cases of inadvertent
> truncation of the source string by passing in a bound that's
> less than or equal to its length.
>
> 2) Enhances -Wstringon-overflow to diagnose calls of the form
> strncpy(D, S, N) where the bound N depends on a call to strlen(S).
> This misuse is common in legacy code when, often in response to
> the adoption of a secure coding initiative, while replacing uses
> of strcpy with strncpy, the engineer either makes a mistake, or
> doesn't have a good enough understanding of how the function works,
> or does only the bare minimum to satisfy the requirement to avoid
> using strcpy without actually improving anything.
>
> 3) Enhances -Wsizeof-pointer-memaccess to also warn about uses of
> the functions to copy an array to a destination of an unknown size
> that specify the size of the array as the bound. Given the
> pervasive [mis]use of strncpy to bound the copy to the size of
> the destination, instances like this suggest a bug: a possible
> buffer overflow due to an excessive bound (see, for example,
> CWE-806). In cases when the call is safe, it's equivalent to
> the corresponding call to memcpy which is clearer and can be
> more efficient.
>
> Martin
>
> PS By coincidence rather than by intent, the strncat warnings
> are a superset of Clang's -Wstrncat-size. AFAICS, Clang only
> warns when the destination is an array of known size and
> doesn't have a corresponding warning for strncpy.
>
> [*] Here's some background into these misuses.
>
> The purpose of the historical strncpy function introduced in V7
> UNIX was to completely fill an array of chars with data, either
> by copying an initial portion of a source string, or by clearing
> it. I.e., its purpose wasn't to create NUL-terminated strings.
> An example of its use was to fill the directory entry d_name
> array (dirent::d_name) with the name of a file.
>
> The original purpose of the strncat function, on the other hand,
> was to append a not necessarily NUL-terminated array of chars
> to a string to form a NUL-terminated concatenation of the two.
> An example use case is appending a directory entry (struct
> dirent::d_name) that need not be NUL-terminated, to form
> a pathname which does.
>
> Largely due to a misunderstanding of the functions' purpose they
> have become commonly used (and misused) to make "safe," bounded
> string copies by safeguarding against accidentally overflowing
> the destination. This has led to great many bugs and security
> vulnerabilities.
>
>
> gcc-81117.diff
>
>
> PR c/81117 - Improve buffer overflow checking in strncpy
>
> gcc/ChangeLog:
>
> PR c/81117
> * builtins.c (compute_objsize): Handle arrays that
> compute_builtin_object_size likes to fail for.
> (check_strncpy_sizes): New function.
> (expand_builtin_strncpy): Call check_strncpy_sizes.
> * doc/invoke.texi (-Wsizeof-pointer-memaccess): Document enhancement.
> (-Wstringop-truncation): Document new option.
> * gimple-fold.c (gimple_fold_builtin_strncpy): Implement
> -Wstringop-truncation.
> (gimple_fold_builtin_strncat): Same.
> * gimple.c (gimple_build_call_from_tree): Set call location.
> * tree-ssa-strlen.c (strlen_to_stridx): New global variable.
> (is_strlen_related_p): New function.
> (check_builtin_strxncpy, check_builtin_strncat): Same.
> handle_builtin_strlen): Use strlen_to_stridx.
> (strlen_optimize_stmt): Handle flavors of strncat, strncpy, and
> stpncpy.
> Use strlen_to_stridx.
> (pass_strlen::execute): Release strlen_to_stridx.
>
> gcc/ada/ChangeLog:
>
> PR c/81117
> * adadecode.c (__gnat_decode): Replace pointless strncpy with
> memcpy.
> * argv.c (__gnat_fill_arg): Same.
>
> gcc/c-family/ChangeLog:
>
> PR c/81117
> * c-common.c (resort_sorted_fields): Replace pointless strncpy
> with memcpy.
> * c-warn.c (sizeof_pointer_memaccess_warning): Handle arrays.
> * c.opt (-Wstriingop-truncation): New option.
>
> gcc/fortran/ChangeLog:
>
> PR c/81117
> * decl.c (build_sym): Replace pointless strncpy with strcpy.
>
> gcc/objc/ChangeLog:
>
> PR c/81117
> * objc-encoding.c (encode_type): Replace pointless strncpy with
> memcpy.
>
> gcc/testsuite/ChangeLog:
>
> PR c/81117
> * c-c++-common/Wsizeof-pointer-memaccess3.c: New test.
> * c-c++-common/Wstringop-overflow.c: New test.
> * c-c++-common/Wstringop-truncation.c: New test.
> * g++.dg/torture/Wsizeof-pointer-memaccess1.C: Adjust.
> * g++.dg/torture/Wsizeof-pointer-memaccess2.C: Same.
> * gcc.dg/Walloca-1.c: Disable macro tracking.
So I think the fixes exposed by the new warning are OK to go in as-is
immediately if you wish to do so. Minor questions on the actual
improved warnings inline.
>
> diff --git a/gcc/builtins.c b/gcc/builtins.c
> index 608993a..706f051 100644
> --- a/gcc/builtins.c
> +++ b/gcc/builtins.c
> @@ -3300,13 +3300,43 @@ check_sizes (int opt, tree exp, tree size, tree maxlen, tree src, tree objsize)
> the size of the object if successful or NULL when the size cannot
> be determined. */
>
> -static inline tree
> +static tree
> compute_objsize (tree dest, int ostype)
> {
> unsigned HOST_WIDE_INT size;
> - if (compute_builtin_object_size (dest, ostype & 3, &size))
> +
> + /* Only the two least significant bits are meaningful. */
> + ostype &= 3;
> +
> + if (compute_builtin_object_size (dest, ostype, &size))
> return build_int_cst (sizetype, size);
>
> + /* Unless computing the largest size (for memcpy and other raw memory
> + functions), try to determine the size of the object from its type. */
> + if (!ostype)
> + return NULL_TREE;
> +
> + if (TREE_CODE (dest) == SSA_NAME)
> + {
> + gimple *stmt = SSA_NAME_DEF_STMT (dest);
> + if (!is_gimple_assign (stmt))
> + return NULL_TREE;
> +
> + dest = gimple_assign_rhs1 (stmt);
> + }
> +
> + if (TREE_CODE (dest) != ADDR_EXPR)
> + return NULL_TREE;
> +
> + tree type = TREE_TYPE (dest);
> + if (TREE_CODE (type) == POINTER_TYPE)
> + type = TREE_TYPE (type);
> +
> + type = TYPE_MAIN_VARIANT (type);
> +
> + if (TREE_CODE (type) == ARRAY_TYPE)
> + return TYPE_SIZE_UNIT (type);
So I *think* TYPE_SIZE_UNIT isn't necessarily guaranteed to be a
INTEGER_CST, it could be a non-constant expression for the size. Are
the callers of compute_objsize prepared to handle that? Just to be
clear, I'd prefer to return TYPE_SIZE_UNIT even if it's not an
INTEGER_CST, I'm just worried about the callers ability to handle that
correctly.
> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> index d0b9050..e4208d9 100644
> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -5170,6 +5170,57 @@ whether to issue a warning. Similarly to @option{-Wstringop-overflow=3} this
> setting of the option may result in warnings for benign code.
> @end table
>
> +@item -Wstringop-truncation
> +@opindex Wstringop-overflow
> +@opindex Wno-stringop-overflow
> +Warn for calls to bounded string manipulation functions such as @code{strncat},
> +@code{strncpy}, and @code{stpncpy} that may either truncate the copied string
> +or leave the destination unchanged.
> +
> +In the following example, the call to @code{strncat} specifies the length
> +of the source string as the bound. If the destination contains a non-empty
> +string the copy of the source will be truncated. Therefore, the call is
> +diagnosed. To avoid the warning use @code{strlen (d) - 4)} as the bound;
> +
> +@smallexample
> +void append (char *d)
> +@{
> + strncat (d, ".txt", 4);
> +@}
> +@end smallexample
Sorry. I don't follow this particular example. Where's the truncation
when strlen (SRC) == N for strncat? In that case aren't we going to
append SRC without the nul terminator, then nul terminate the result?
Isn't that the same as appending SRC with its nul terminator? Am I
missing something here?
Also your advice for avoiding the warning seems wrong. Isn't it going
to produce a huge value if strlen (d) < 4?
> +@smallexample
> +void make_file (const char *name)
> +@{
> + char path[PATH_MAX];
> + strncpy (path, name, sizeof path - 1);
> + strncat (path, ".text", sizeof ".text");
> + @dots{}
> +@}
> +@end smallexample
> +
> +Th @option{-Wsizeof-pointer-memaccess} option is enabled by @option{-Wall}.
Nit: s/Th/The/
>
> @item -Wsizeof-array-argument
> @opindex Wsizeof-array-argument
> diff --git a/gcc/gimple-fold.c b/gcc/gimple-fold.c
> index d94dc9c..5d021f9 100644
> --- a/gcc/gimple-fold.c
> +++ b/gcc/gimple-fold.c
> @@ -1856,21 +1893,68 @@ gimple_fold_builtin_strncat (gimple_stmt_iterator *gsi)
> return true;
> }
>
> + if (!nowarn && cmpsrc <= 0)
> + {
> + /* To avoid certain truncation the specified bound should also
> + not be equal to (or less than) the length of the source. */
> + location_t loc = gimple_location (stmt);
> + if (warning_at (loc, OPT_Wstringop_truncation,
> + cmpsrc == 0
> + ? G_("%qD specified bound %E equals source length")
> + : G_("%qD specified bound %E is less than source "
> + "length %u"),
> + gimple_call_fndecl (stmt), len, srclen))
> + gimple_set_no_warning (stmt, true);
> + }
So I think this corresponds to the example above I asked about. Where's
the truncation when the bound is equal to the source length?
> diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c
> index b0563fe..1f4b0f3 100644
> --- a/gcc/tree-ssa-strlen.c
> +++ b/gcc/tree-ssa-strlen.c
> @@ -40,11 +40,14 @@ along with GCC; see the file COPYING3. If not see
> #include "expr.h"
> #include "tree-dfa.h"
> #include "domwalk.h"
> +#include "tree-ssa-alias.h"
> #include "tree-ssa-propagate.h"
> #include "params.h"
> #include "ipa-chkp.h"
> #include "tree-hash-traits.h"
> #include "builtins.h"
> +#include "diagnostic-core.h"
> +#include "diagnostic.h"
>
> /* A vector indexed by SSA_NAME_VERSION. 0 means unknown, positive value
> is an index into strinfo vector, negative value stands for
> @@ -146,6 +149,9 @@ struct decl_stridxlist_map
> mappings. */
> static hash_map<tree_decl_hash, stridxlist> *decl_to_stridxlist_htab;
>
> +typedef std::pair<int, location_t> stridx_strlenloc;
> +static hash_map<tree, stridx_strlenloc> strlen_to_stridx;
> +
> /* Obstack for struct stridxlist and struct decl_stridxlist_map. */
> static struct obstack stridx_obstack;
>
> @@ -1606,6 +1618,133 @@ handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi)
> fprintf (dump_file, "not possible.\n");
> }
>
> +/* Return true if LEN depends on a call to strlen(SRC) in an interesting
> + way. LEN can either be an integer expression, or a pointer (to char).
> + When it is the latter (such as in recursive calls to self) is is
> + assumed to be the argument in some call to strlen() whose relationship
> + to SRC is being ascertained. */
> +
> +static bool
> +is_strlen_related_p (tree src, tree len)
> +{
> + if (TREE_CODE (TREE_TYPE (len)) == POINTER_TYPE
> + && operand_equal_p (src, len, 0))
> + return true;
> +
> + if (TREE_CODE (len) != SSA_NAME)
> + return false;
> +
> + gimple *def_stmt = SSA_NAME_DEF_STMT (len);
> + if (!def_stmt)
> + return false;
> +
> + if (is_gimple_call (def_stmt))
> + {
> + tree func = gimple_call_fndecl (def_stmt);
> + if (!valid_builtin_call (def_stmt)
> + || DECL_FUNCTION_CODE (func) != BUILT_IN_STRLEN)
> + return false;
> +
> + tree arg = gimple_call_arg (def_stmt, 0);
> + return is_strlen_related_p (src, arg);
> + }
> +
> + if (!is_gimple_assign (def_stmt))
> + return false;
> +
> + tree_code code = gimple_assign_rhs_code (def_stmt);
> + tree rhs1 = gimple_assign_rhs1 (def_stmt);
> + tree rhstype = TREE_TYPE (rhs1);
> +
> + if ((TREE_CODE (rhstype) == POINTER_TYPE && code == POINTER_PLUS_EXPR)
> + || (INTEGRAL_TYPE_P (rhstype)
> + && (code == BIT_AND_EXPR
> + || code == NOP_EXPR)))
> + {
> + /* Pointer plus (an integer) and integer cast or truncation are
> + considered among the (potentiall) related expressions to strlen.
NIT: s/potentiall/potentially/
> @@ -2512,6 +2651,19 @@ strlen_optimize_stmt (gimple_stmt_iterator *gsi)
> case BUILT_IN_STPCPY_CHK_CHKP:
> handle_builtin_strcpy (DECL_FUNCTION_CODE (callee), gsi);
> break;
> +
> + case BUILT_IN_STRNCAT:
> + case BUILT_IN_STRNCAT_CHK:
> + check_builtin_strncat (DECL_FUNCTION_CODE (callee), gsi);
> + break;
> +
> + case BUILT_IN_STPNCPY:
> + case BUILT_IN_STPNCPY_CHK:
> + case BUILT_IN_STRNCPY:
> + case BUILT_IN_STRNCPY_CHK:
> + check_builtin_stxncpy (DECL_FUNCTION_CODE (callee), gsi);
> + break;
> +
So we've got calls to check the arguments, but not optimize here. But
the containing function is "strlen_optimize_stmt".
Would it make sense to first call strlen_optimize_stmt to handle the
optimization cases, then to call a new separate function to handle
warnings for the strn* family?
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-07-31 17:29 ` Jeff Law
@ 2017-07-31 19:42 ` Martin Sebor
2017-08-02 16:59 ` Jeff Law
0 siblings, 1 reply; 39+ messages in thread
From: Martin Sebor @ 2017-07-31 19:42 UTC (permalink / raw)
To: Jeff Law, Gcc Patch List
[-- Attachment #1: Type: text/plain, Size: 5716 bytes --]
> So I think the fixes exposed by the new warning are OK to go in as-is
> immediately if you wish to do so. Minor questions on the actual
> improved warnings inline.
>
Sure, thanks.
>
>> -static inline tree
>> +static tree
>> compute_objsize (tree dest, int ostype)
>> {
...
>> + type = TYPE_MAIN_VARIANT (type);
>> +
>> + if (TREE_CODE (type) == ARRAY_TYPE)
>> + return TYPE_SIZE_UNIT (type);
> So I *think* TYPE_SIZE_UNIT isn't necessarily guaranteed to be a
> INTEGER_CST, it could be a non-constant expression for the size. Are
> the callers of compute_objsize prepared to handle that? Just to be
> clear, I'd prefer to return TYPE_SIZE_UNIT even if it's not an
> INTEGER_CST, I'm just worried about the callers ability to handle that
> correctly.
They should be prepared for it. If not, it's a bug. I've added
a few more test cases though I'm not sure the case you're concerned
about actually arises (VLA sizes are represented as gimple calls to
__builtin_alloca_with_align so the code doesn't get this far).
>
>> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
>> index d0b9050..e4208d9 100644
>> --- a/gcc/doc/invoke.texi
>> +++ b/gcc/doc/invoke.texi
>> @@ -5170,6 +5170,57 @@ whether to issue a warning. Similarly to @option{-Wstringop-overflow=3} this
>> setting of the option may result in warnings for benign code.
>> @end table
>>
>> +@item -Wstringop-truncation
>> +@opindex Wstringop-overflow
>> +@opindex Wno-stringop-overflow
Looks like I have a couple of typos up there. The option name
should be the same in all three entries. Let me fix that.
>> +Warn for calls to bounded string manipulation functions such as @code{strncat},
>> +@code{strncpy}, and @code{stpncpy} that may either truncate the copied string
>> +or leave the destination unchanged.
>> +
>> +In the following example, the call to @code{strncat} specifies the length
>> +of the source string as the bound. If the destination contains a non-empty
>> +string the copy of the source will be truncated. Therefore, the call is
>> +diagnosed. To avoid the warning use @code{strlen (d) - 4)} as the bound;
>> +
>> +@smallexample
>> +void append (char *d)
>> +@{
>> + strncat (d, ".txt", 4);
>> +@}
>> +@end smallexample
> Sorry. I don't follow this particular example. Where's the truncation
> when strlen (SRC) == N for strncat? In that case aren't we going to
> append SRC without the nul terminator, then nul terminate the result?
> Isn't that the same as appending SRC with its nul terminator? Am I
> missing something here?
You're right that there is no truncation and the effect is
the same but only in the unlikely case when the destination
is empty. Otherwise the result is truncated.
(Although well defined, calling strncat with an empty destination
and with the bound as big as the source is a misuse of the API.
The expected way to copy a string is to call one of the copy
functions and the warning relies on this as the basis for the
diagnostic.)
>
> Also your advice for avoiding the warning seems wrong. Isn't it going
> to produce a huge value if strlen (d) < 4?
Right, that advice is wrong. I had fixed this in my local tree
before posting the patch but must not have updated the patch.
Let me refresh the patch.
>> @item -Wsizeof-array-argument
>> @opindex Wsizeof-array-argument
>> diff --git a/gcc/gimple-fold.c b/gcc/gimple-fold.c
>> index d94dc9c..5d021f9 100644
>> --- a/gcc/gimple-fold.c
>> +++ b/gcc/gimple-fold.c
>> @@ -1856,21 +1893,68 @@ gimple_fold_builtin_strncat (gimple_stmt_iterator *gsi)
>> return true;
>> }
>>
>> + if (!nowarn && cmpsrc <= 0)
>> + {
>> + /* To avoid certain truncation the specified bound should also
>> + not be equal to (or less than) the length of the source. */
>> + location_t loc = gimple_location (stmt);
>> + if (warning_at (loc, OPT_Wstringop_truncation,
>> + cmpsrc == 0
>> + ? G_("%qD specified bound %E equals source length")
>> + : G_("%qD specified bound %E is less than source "
>> + "length %u"),
>> + gimple_call_fndecl (stmt), len, srclen))
>> + gimple_set_no_warning (stmt, true);
>> + }
> So I think this corresponds to the example above I asked about. Where's
> the truncation when the bound is equal to the source length?
Yes. Hopefully the above answers the question.
> NIT: s/potentiall/potentially/
>
Sure. Fixed along with the other typo above.
>
>> @@ -2512,6 +2651,19 @@ strlen_optimize_stmt (gimple_stmt_iterator *gsi)
>> case BUILT_IN_STPCPY_CHK_CHKP:
>> handle_builtin_strcpy (DECL_FUNCTION_CODE (callee), gsi);
>> break;
>> +
>> + case BUILT_IN_STRNCAT:
>> + case BUILT_IN_STRNCAT_CHK:
>> + check_builtin_strncat (DECL_FUNCTION_CODE (callee), gsi);
>> + break;
>> +
>> + case BUILT_IN_STPNCPY:
>> + case BUILT_IN_STPNCPY_CHK:
>> + case BUILT_IN_STRNCPY:
>> + case BUILT_IN_STRNCPY_CHK:
>> + check_builtin_stxncpy (DECL_FUNCTION_CODE (callee), gsi);
>> + break;
>> +
> So we've got calls to check the arguments, but not optimize here. But
> the containing function is "strlen_optimize_stmt".
>
> Would it make sense to first call strlen_optimize_stmt to handle the
> optimization cases, then to call a new separate function to handle
> warnings for the strn* family?
tree-ssa-strlen doesn't handle strncat or strncpy (for the latter
I'm tracking the enhancement in bug 81433). When the handling is
added I expect check_builtin_{strncat,stxncpy} will be renamed to
handle_builtin_{strncat,stxncpy} to match the existing functions,
and the optimization done there.
Or did you have something else in mind and I missed it?
Attached is an updated revision of the patch with the tweaks above.
Martin
[-- Attachment #2: gcc-81117.diff --]
[-- Type: text/x-patch, Size: 54487 bytes --]
PR c/81117 - Improve buffer overflow checking in strncpy
gcc/ChangeLog:
PR c/81117
* builtins.c (compute_objsize): Handle arrays that
compute_builtin_object_size likes to fail for.
(check_strncpy_sizes): New function.
(expand_builtin_strncpy): Call check_strncpy_sizes.
* doc/invoke.texi (-Wsizeof-pointer-memaccess): Document enhancement.
(-Wstringop-truncation): Document new option.
* gimple-fold.c (gimple_fold_builtin_strncpy): Implement
-Wstringop-truncation.
(gimple_fold_builtin_strncat): Same.
* gimple.c (gimple_build_call_from_tree): Set call location.
* tree-ssa-strlen.c (strlen_to_stridx): New global variable.
(is_strlen_related_p): New function.
(check_builtin_strxncpy, check_builtin_strncat): Same.
(handle_builtin_strlen): Use strlen_to_stridx.
(strlen_optimize_stmt): Handle flavors of strncat, strncpy, and
stpncpy.
Use strlen_to_stridx.
(pass_strlen::execute): Release strlen_to_stridx.
gcc/ada/ChangeLog:
PR c/81117
* adadecode.c (__gnat_decode): Replace pointless strncpy with
memcpy.
* argv.c (__gnat_fill_arg): Same.
gcc/c-family/ChangeLog:
PR c/81117
* c-common.c (resort_sorted_fields): Replace pointless strncpy
with memcpy.
* c-warn.c (sizeof_pointer_memaccess_warning): Handle arrays.
* c.opt (-Wstriingop-truncation): New option.
gcc/fortran/ChangeLog:
PR c/81117
* decl.c (build_sym): Replace pointless strncpy with strcpy.
gcc/objc/ChangeLog:
PR c/81117
* objc-encoding.c (encode_type): Replace pointless strncpy with
memcpy.
gcc/testsuite/ChangeLog:
PR c/81117
* c-c++-common/Wsizeof-pointer-memaccess3.c: New test.
* c-c++-common/Wstringop-overflow.c: New test.
* c-c++-common/Wstringop-truncation.c: New test.
* g++.dg/torture/Wsizeof-pointer-memaccess1.C: Adjust.
* g++.dg/torture/Wsizeof-pointer-memaccess2.C: Same.
* gcc.dg/Walloca-1.c: Disable macro tracking.
diff --git a/gcc/ada/adadecode.c b/gcc/ada/adadecode.c
index 8c9c7ab..0cbef81 100644
--- a/gcc/ada/adadecode.c
+++ b/gcc/ada/adadecode.c
@@ -330,7 +330,7 @@ __gnat_decode (const char *coded_name, char *ada_name, int verbose)
}
/* Write symbol in the space. */
- strncpy (optoken, trans_table[k][1], oplen);
+ memcpy (optoken, trans_table[k][1], oplen);
}
else
k++;
diff --git a/gcc/ada/argv.c b/gcc/ada/argv.c
index 430404e..aee0f88 100644
--- a/gcc/ada/argv.c
+++ b/gcc/ada/argv.c
@@ -92,7 +92,7 @@ void
__gnat_fill_arg (char *a, int i)
{
if (gnat_argv != NULL)
- strncpy (a, gnat_argv[i], strlen(gnat_argv[i]));
+ memcpy (a, gnat_argv[i], strlen (gnat_argv[i]));
}
int
@@ -118,7 +118,7 @@ void
__gnat_fill_env (char *a, int i)
{
if (gnat_envp != NULL)
- strncpy (a, gnat_envp[i], strlen (gnat_envp[i]));
+ memcpy (a, gnat_envp[i], strlen (gnat_envp[i]));
}
#ifdef __cplusplus
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 608993a..706f051 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -3300,13 +3300,43 @@ check_sizes (int opt, tree exp, tree size, tree maxlen, tree src, tree objsize)
the size of the object if successful or NULL when the size cannot
be determined. */
-static inline tree
+static tree
compute_objsize (tree dest, int ostype)
{
unsigned HOST_WIDE_INT size;
- if (compute_builtin_object_size (dest, ostype & 3, &size))
+
+ /* Only the two least significant bits are meaningful. */
+ ostype &= 3;
+
+ if (compute_builtin_object_size (dest, ostype, &size))
return build_int_cst (sizetype, size);
+ /* Unless computing the largest size (for memcpy and other raw memory
+ functions), try to determine the size of the object from its type. */
+ if (!ostype)
+ return NULL_TREE;
+
+ if (TREE_CODE (dest) == SSA_NAME)
+ {
+ gimple *stmt = SSA_NAME_DEF_STMT (dest);
+ if (!is_gimple_assign (stmt))
+ return NULL_TREE;
+
+ dest = gimple_assign_rhs1 (stmt);
+ }
+
+ if (TREE_CODE (dest) != ADDR_EXPR)
+ return NULL_TREE;
+
+ tree type = TREE_TYPE (dest);
+ if (TREE_CODE (type) == POINTER_TYPE)
+ type = TREE_TYPE (type);
+
+ type = TYPE_MAIN_VARIANT (type);
+
+ if (TREE_CODE (type) == ARRAY_TYPE)
+ return TYPE_SIZE_UNIT (type);
+
return NULL_TREE;
}
@@ -3949,6 +3979,36 @@ expand_builtin_strncat (tree exp, rtx)
return NULL_RTX;
}
+/* Helper to check the sizes of sequences and the destination of calls
+ to __builtin_strncpy (DST, SRC, LEN) and __builtin___strncpy_chk.
+ Returns true on success (no overflow warning), false otherwise. */
+
+static bool
+check_strncpy_sizes (tree exp, tree dst, tree src, tree len)
+{
+ tree dstsize = compute_objsize (dst, warn_stringop_overflow - 1);
+
+ if (!check_sizes (OPT_Wstringop_overflow_,
+ exp, len, /*maxlen=*/NULL_TREE, src, dstsize))
+ return false;
+
+ if (!dstsize || TREE_CODE (len) != INTEGER_CST)
+ return true;
+
+ int cmp = tree_int_cst_compare (dstsize, len);
+
+ if (cmp == 0)
+ warning_at (EXPR_LOCATION (exp), OPT_Wstringop_truncation,
+ "%K%qD specified bound %E equals destination size",
+ exp, get_callee_fndecl (exp), len);
+ else if (cmp < 0)
+ warning_at (EXPR_LOCATION (exp), OPT_Wstringop_truncation,
+ "%K%qD specified bound %E exceeds destination size %E",
+ exp, get_callee_fndecl (exp), len, dstsize);
+
+ return true;
+}
+
/* Expand expression EXP, which is a call to the strncpy builtin. Return
NULL_RTX if we failed the caller should emit a normal call. */
@@ -3967,16 +4027,7 @@ expand_builtin_strncpy (tree exp, rtx target)
/* The length of the source sequence. */
tree slen = c_strlen (src, 1);
- if (warn_stringop_overflow)
- {
- tree destsize = compute_objsize (dest,
- warn_stringop_overflow - 1);
-
- /* The number of bytes to write is LEN but check_sizes will also
- check SLEN if LEN's value isn't known. */
- check_sizes (OPT_Wstringop_overflow_,
- exp, len, /*maxlen=*/NULL_TREE, src, destsize);
- }
+ check_strncpy_sizes (exp, dest, src, len);
/* We must be passed a constant len and src parameter. */
if (!tree_fits_uhwi_p (len) || !slen || !tree_fits_uhwi_p (slen))
diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index b4217f3..5fc4d24 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -5942,10 +5942,10 @@ resort_sorted_fields (void *obj,
static char *
catenate_strings (const char *lhs, const char *rhs_start, int rhs_size)
{
- const int lhs_size = strlen (lhs);
+ const size_t lhs_size = strlen (lhs);
char *result = XNEWVEC (char, lhs_size + rhs_size);
- strncpy (result, lhs, lhs_size);
- strncpy (result + lhs_size, rhs_start, rhs_size);
+ memcpy (result, lhs, lhs_size);
+ memcpy (result + lhs_size, rhs_start, rhs_size);
return result;
}
diff --git a/gcc/c-family/c-warn.c b/gcc/c-family/c-warn.c
index b9378c2..4b04d81 100644
--- a/gcc/c-family/c-warn.c
+++ b/gcc/c-family/c-warn.c
@@ -626,7 +626,8 @@ sizeof_pointer_memaccess_warning (location_t *sizeof_arg_loc, tree callee,
|| vec_safe_length (params) <= 1)
return;
- switch (DECL_FUNCTION_CODE (callee))
+ enum built_in_function fncode = DECL_FUNCTION_CODE (callee);
+ switch (fncode)
{
case BUILT_IN_STRNCMP:
case BUILT_IN_STRNCASECMP:
@@ -708,8 +709,27 @@ sizeof_pointer_memaccess_warning (location_t *sizeof_arg_loc, tree callee,
type = TYPE_P (sizeof_arg[idx])
? sizeof_arg[idx] : TREE_TYPE (sizeof_arg[idx]);
+
if (!POINTER_TYPE_P (type))
- return;
+ {
+ /* The argument type may be an array. Diagnose bounded string
+ copy functions that specify the bound in terms of the source
+ argument rather than the destination. */
+ if (strop && !cmp && fncode != BUILT_IN_STRNDUP && src)
+ {
+ tem = tree_strip_nop_conversions (src);
+ if (TREE_CODE (tem) == ADDR_EXPR)
+ tem = TREE_OPERAND (tem, 0);
+ if (operand_equal_p (tem, sizeof_arg[idx], OEP_ADDRESS_OF))
+ warning_at (sizeof_arg_loc[idx], OPT_Wsizeof_pointer_memaccess,
+ "argument to %<sizeof%> in %qD call is the same "
+ "expression as the source; did you mean to use "
+ "the size of the destination?",
+ callee);
+ }
+
+ return;
+ }
if (dest
&& (tem = tree_strip_nop_conversions (dest))
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 05766c4..4b9c975 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -732,10 +732,15 @@ Warn about buffer overflow in string manipulation functions like memcpy
and strcpy.
Wstringop-overflow=
-C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_stringop_overflow) Init(2) Warning LangEnabledBy(C ObjC C++ ObjC++, Wall) IntegerRange(0, 4)
+C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_stringop_overflow) Init(2) Warning LangEnabledBy(C ObjC C++ ObjC++, Wall, 2, 0) IntegerRange(0, 4)
Under the control of Object Size type, warn about buffer overflow in string
manipulation functions like memcpy and strcpy.
+Wstringop-truncation
+C ObjC C++ ObjC++ Var(warn_stringop_truncation) Warning Init (1) LangEnabledBy(C ObjC C++ ObjC++, Wall)
+Warn about buffer overflow in string manipulation functions like memcpy
+and strcpy.
+
Wsuggest-attribute=format
C ObjC C++ ObjC++ Var(warn_suggest_attribute_format) Warning
Warn about functions which might be candidates for format attributes.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index d0b9050..20a1b4c 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -312,7 +312,7 @@ Objective-C and Objective-C++ Dialects}.
-Wsizeof-pointer-memaccess -Wsizeof-array-argument @gol
-Wstack-protector -Wstack-usage=@var{len} -Wstrict-aliasing @gol
-Wstrict-aliasing=n -Wstrict-overflow -Wstrict-overflow=@var{n} @gol
--Wstringop-overflow=@var{n} @gol
+-Wstringop-overflow=@var{n} -Wstringop-truncation @gol
-Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{|}format@r{]} @gol
-Wsuggest-final-types @gol -Wsuggest-final-methods -Wsuggest-override @gol
-Wmissing-format-attribute -Wsubobject-linkage @gol
@@ -5170,6 +5170,58 @@ whether to issue a warning. Similarly to @option{-Wstringop-overflow=3} this
setting of the option may result in warnings for benign code.
@end table
+@item -Wstringop-truncation
+@opindex Wstringop-truncation
+@opindex Wno-stringop-truncation
+Warn for calls to bounded string manipulation functions such as @code{strncat},
+@code{strncpy}, and @code{stpncpy} that may either truncate the copied string
+or leave the destination unchanged.
+
+In the following example, the call to @code{strncat} specifies the length
+of the source string as the bound. If the destination contains a non-empty
+string the copy of the source will be truncated. Therefore, the call is
+diagnosed. To avoid the warning use @code{bufsize - strlen (buf) - 1)} as
+the bound. If the destination is known to be empty, call instead one of
+the string copy functions such as @code{strcpy} @code{strncpy}.
+
+@smallexample
+void append (char *buf, size_t bufsize)
+@{
+ strncat (buf, ".txt", 4);
+@}
+@end smallexample
+
+As another example, the following call to @code{strncpy} results in copying
+to @code{d} just the characters preceding the terminating NUL, without
+appending the NUL to the end. Assuming the result of @code{strncpy} is
+necessarily a NUL-terminated string is a common mistake, and so the call
+is diagnosed. To avoid the warning when the result is not expected to be
+NUL-terminated, call @code{memcpy} instead.
+
+@smallexample
+void copy (char *d, const char *s)
+@{
+ strncpy (d, s, strlen (s));
+@}
+@end smallexample
+
+In the following example, the call to @code{strncpy} specifies the size
+of the destination buffer as the bound. If the length of the source
+string is equal to or greater than this size the result of the copy will
+not be NUL-terminated. Therefore, the call is also diagnosed. To avoid
+the warning, specify @code{sizeof buf - 1} as the bound and set the last
+element of the buffer to NUL.
+
+@smallexample
+void copy (const char *s)
+@{
+ char buf[80];
+ strncpy (buf, s, sizeof buf);
+ @dots{}
+@}
+@end smallexample
+
+
@item -Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{|}format@r{]}
@opindex Wsuggest-attribute=
@opindex Wno-suggest-attribute=
@@ -6154,11 +6206,26 @@ not an array, but a pointer. This warning is enabled by @option{-Wall}.
@opindex Wsizeof-pointer-memaccess
@opindex Wno-sizeof-pointer-memaccess
Warn for suspicious length parameters to certain string and memory built-in
-functions if the argument uses @code{sizeof}. This warning warns e.g.@:
-about @code{memset (ptr, 0, sizeof (ptr));} if @code{ptr} is not an array,
-but a pointer, and suggests a possible fix, or about
-@code{memcpy (&foo, ptr, sizeof (&foo));}. This warning is enabled by
-@option{-Wall}.
+functions if the argument uses @code{sizeof}. This warning triggers for
+example for @code{memset (ptr, 0, sizeof (ptr));} if @code{ptr} is not
+an array, but a pointer, and suggests a possible fix, or about
+@code{memcpy (&foo, ptr, sizeof (&foo));}. @option{-Wsizeof-pointer-memaccess}
+also warns about calls to bounded string copy functions like @code{strncat}
+or @code{strncpy} that specify as the bound a @code{sizeof} expression of
+the source array. For example, in the following function the call to
+@code{strncat} specifies the size of the source string as the bound. That
+is almost certainly a mistake and so the call is diagnosed.
+@smallexample
+void make_file (const char *name)
+@{
+ char path[PATH_MAX];
+ strncpy (path, name, sizeof path - 1);
+ strncat (path, ".text", sizeof ".text");
+ @dots{}
+@}
+@end smallexample
+
+The @option{-Wsizeof-pointer-memaccess} option is enabled by @option{-Wall}.
@item -Wsizeof-array-argument
@opindex Wsizeof-array-argument
diff --git a/gcc/fortran/decl.c b/gcc/fortran/decl.c
index bd31070..ee91504 100644
--- a/gcc/fortran/decl.c
+++ b/gcc/fortran/decl.c
@@ -1417,11 +1417,9 @@ build_sym (const char *name, gfc_charlen *cl, bool cl_deferred,
{
char u_name[GFC_MAX_SYMBOL_LEN + 1];
gfc_symtree *st;
- int nlen;
- nlen = strlen(name);
- gcc_assert (nlen <= GFC_MAX_SYMBOL_LEN);
- strncpy (u_name, name, nlen + 1);
+ gcc_assert (strlen(name) <= GFC_MAX_SYMBOL_LEN);
+ strcpy (u_name, name);
u_name[0] = upper;
st = gfc_find_symtree (gfc_current_ns->sym_root, u_name);
diff --git a/gcc/gimple-fold.c b/gcc/gimple-fold.c
index d94dc9c..5d021f9 100644
--- a/gcc/gimple-fold.c
+++ b/gcc/gimple-fold.c
@@ -40,6 +40,7 @@ along with GCC; see the file COPYING3. If not see
#include "gimple-iterator.h"
#include "tree-into-ssa.h"
#include "tree-dfa.h"
+#include "tree-object-size.h"
#include "tree-ssa.h"
#include "tree-ssa-propagate.h"
#include "ipa-utils.h"
@@ -57,6 +58,8 @@ along with GCC; see the file COPYING3. If not see
#include "tree-cfg.h"
#include "fold-const-call.h"
#include "asan.h"
+#include "diagnostic-core.h"
+#include "intl.h"
/* Return true when DECL can be referenced from current unit.
FROM_DECL (if non-null) specify constructor of variable DECL was taken from.
@@ -1522,12 +1525,25 @@ static bool
gimple_fold_builtin_strncpy (gimple_stmt_iterator *gsi,
tree dest, tree src, tree len)
{
- location_t loc = gimple_location (gsi_stmt (*gsi));
- tree fn;
+ gimple *stmt = gsi_stmt (*gsi);
+ location_t loc = gimple_location (stmt);
/* If the LEN parameter is zero, return DEST. */
if (integer_zerop (len))
{
+ /* Warn about the lack of nul termination: the result is not
+ a (nul-terminated) string. */
+ tree slen = get_maxval_strlen (src, 0);
+ if (slen && !integer_zerop (slen))
+ warning_at (loc, OPT_Wstringop_truncation,
+ "%qD destination unchanged after copying no bytes "
+ "from a string of length %E",
+ gimple_call_fndecl (stmt), slen);
+ else
+ warning_at (loc, OPT_Wstringop_truncation,
+ "%qD destination unchanged after copying no bytes",
+ gimple_call_fndecl (stmt));
+
replace_call_with_value (gsi, dest);
return true;
}
@@ -1542,16 +1558,36 @@ gimple_fold_builtin_strncpy (gimple_stmt_iterator *gsi,
if (!slen || TREE_CODE (slen) != INTEGER_CST)
return false;
- slen = size_binop_loc (loc, PLUS_EXPR, slen, ssize_int (1));
+ /* The size of the source string including the terminating nul. */
+ tree ssize = size_binop_loc (loc, PLUS_EXPR, slen, ssize_int (1));
/* We do not support simplification of this case, though we do
support it when expanding trees into RTL. */
/* FIXME: generate a call to __builtin_memset. */
- if (tree_int_cst_lt (slen, len))
+ if (tree_int_cst_lt (ssize, len))
return false;
+ if (tree_int_cst_lt (len, slen))
+ warning_at (loc, OPT_Wstringop_truncation,
+ (tree_int_cst_equal (size_one_node, len)
+ ? G_("%qD output truncated copying %E byte "
+ "from a string of length %E")
+ : G_("%qD output truncated copying %E bytes "
+ "from a string of length %E")),
+ gimple_call_fndecl (stmt), len, slen);
+ else if (tree_int_cst_equal (len, slen))
+ warning_at (loc, OPT_Wstringop_truncation,
+ (tree_int_cst_equal (size_one_node, len)
+ ? G_("%qD output truncated before terminating nul "
+ "copying %E byte from a string of the same "
+ "length")
+ : G_("%qD output truncated before terminating nul "
+ "copying %E bytes from a string of the same "
+ "length")),
+ gimple_call_fndecl (stmt), len);
+
/* OK transform into builtin memcpy. */
- fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
+ tree fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
if (!fn)
return false;
@@ -1560,6 +1596,7 @@ gimple_fold_builtin_strncpy (gimple_stmt_iterator *gsi,
NULL_TREE, true, GSI_SAME_STMT);
gimple *repl = gimple_build_call (fn, 3, dest, src, len);
replace_call_with_call_and_fold (gsi, repl);
+
return true;
}
@@ -1856,21 +1893,68 @@ gimple_fold_builtin_strncat (gimple_stmt_iterator *gsi)
return true;
}
- /* If the requested len is greater than or equal to the string
- length, call strcat. */
- if (TREE_CODE (len) == INTEGER_CST && p
- && compare_tree_int (len, strlen (p)) >= 0)
+ if (TREE_CODE (len) == INTEGER_CST && p)
{
- tree fn = builtin_decl_implicit (BUILT_IN_STRCAT);
+ unsigned srclen = strlen (p);
- /* If the replacement _DECL isn't initialized, don't do the
- transformation. */
- if (!fn)
- return false;
+ int cmpsrc = compare_tree_int (len, srclen);
- gcall *repl = gimple_build_call (fn, 2, dst, src);
- replace_call_with_call_and_fold (gsi, repl);
- return true;
+ unsigned HOST_WIDE_INT dstsize;
+
+ bool nowarn = gimple_no_warning_p (stmt);
+
+ if (!nowarn && compute_builtin_object_size (dst, 1, &dstsize))
+ {
+ int cmpdst = compare_tree_int (len, dstsize);
+
+ if (cmpdst >= 0)
+ {
+ /* Strncat copies (at most) LEN bytes and always appends
+ the terminating NUL so the specified bound should never
+ be equal to (or greater than) the size of the destination.
+ If it is, the copy could overflow. */
+ location_t loc = gimple_location (stmt);
+ nowarn = warning_at (loc, OPT_Wstringop_overflow_,
+ cmpdst == 0
+ ? G_("%qD specified bound %E equals "
+ "destination size")
+ : G_("%qD specified bound %E exceeds "
+ "destination size %wu"),
+ gimple_call_fndecl (stmt), len, dstsize);
+ if (nowarn)
+ gimple_set_no_warning (stmt, true);
+ }
+ }
+
+ if (!nowarn && cmpsrc <= 0)
+ {
+ /* To avoid certain truncation the specified bound should also
+ not be equal to (or less than) the length of the source. */
+ location_t loc = gimple_location (stmt);
+ if (warning_at (loc, OPT_Wstringop_truncation,
+ cmpsrc == 0
+ ? G_("%qD specified bound %E equals source length")
+ : G_("%qD specified bound %E is less than source "
+ "length %u"),
+ gimple_call_fndecl (stmt), len, srclen))
+ gimple_set_no_warning (stmt, true);
+ }
+
+ /* If the requested len is greater than or equal to the string
+ length, call strcat. */
+ if (cmpsrc >= 0)
+ {
+ tree fn = builtin_decl_implicit (BUILT_IN_STRCAT);
+
+ /* If the replacement _DECL isn't initialized, don't do the
+ transformation. */
+ if (!fn)
+ return false;
+
+ gcall *repl = gimple_build_call (fn, 2, dst, src);
+ replace_call_with_call_and_fold (gsi, repl);
+ return true;
+ }
}
return false;
diff --git a/gcc/gimple.c b/gcc/gimple.c
index 488f8c8..f2dcece 100644
--- a/gcc/gimple.c
+++ b/gcc/gimple.c
@@ -359,6 +359,7 @@ gimple_build_call_from_tree (tree t)
gimple_call_set_arg (call, i, CALL_EXPR_ARG (t, i));
gimple_set_block (call, TREE_BLOCK (t));
+ gimple_set_location (call, EXPR_LOCATION (t));
/* Carry all the CALL_EXPR flags to the new GIMPLE_CALL. */
gimple_call_set_chain (call, CALL_EXPR_STATIC_CHAIN (t));
diff --git a/gcc/objc/objc-encoding.c b/gcc/objc/objc-encoding.c
index 2a2dfa5..e5d4f38 100644
--- a/gcc/objc/objc-encoding.c
+++ b/gcc/objc/objc-encoding.c
@@ -734,7 +734,7 @@ encode_type (tree type, int curtype, int format)
/* Rewrite "in const" from "nr" to "rn". */
if (curtype >= 1 && !strncmp (enc - 1, "nr", 2))
- strncpy (enc - 1, "rn", 2);
+ memcpy (enc - 1, "rn", 2);
}
}
}
diff --git a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess3.c b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess3.c
new file mode 100644
index 0000000..cf0ca67
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess3.c
@@ -0,0 +1,132 @@
+/* Test -Wsizeof-pointer-memaccess warnings. */
+/* { dg-do compile } */
+/* { dg-options "-Wsizeof-pointer-memaccess -Wno-stringop-truncation -ftrack-macro-expansion=0" } */
+
+#define bos(ptr) __builtin_object_size (ptr, 1)
+#define bos0(ptr) __builtin_object_size (ptr, 0)
+
+#define memset(dst, val, sz) \
+ (FUNC (memset, dst, val, sz, bos (dst)), sink ((dst)))
+
+#define memcpy(dst, src, sz) \
+ (FUNC (memcpy, dst, src, sz, bos (dst)), sink ((dst)))
+
+#define memmove(dst, src, sz) \
+ (FUNC (memmove, dst, src, sz, bos (dst)), sink ((dst)))
+
+#define mempcpy(dst, src, sz) \
+ (FUNC (mempcpy, dst, src, sz, bos (dst)), sink ((dst)))
+
+#define strncpy(dst, src, sz) \
+ (FUNC (strncpy, dst, src, sz, bos (dst)), sink (dst))
+
+#define strncat(dst, src, sz) \
+ (FUNC (strncat, dst, src, sz, bos (dst)), sink (dst))
+
+#define stpncpy(dst, src, sz) \
+ (FUNC (stpncpy, dst, src, sz, bos (dst)), sink (dst))
+
+void sink (void*);
+
+#define S10 "123456789"
+extern char a10[10];
+
+void test_string_literal (char *dst)
+{
+#define FUNC(f, d, s, n, x) __builtin_ ## f (d, s, n)
+
+ /* It's common to call memcpy and other raw memory functions with
+ size drerived from the source argument. Verify that no warning
+ is ussued for such calls. */
+ memcpy (dst, S10, sizeof S10);
+ mempcpy (dst, S10, sizeof S10);
+ memmove (dst, S10, sizeof S10);
+
+ memset (dst, 0, sizeof S10);
+
+ stpncpy (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncpy (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncat (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ /* Unlike in the cases above, even though the calls below are likely
+ wrong, it's not easy to detect that the expression (sizeof X - 1)
+ involves sizeof of the source, so no warning is issued here, as
+ helpful as one might be. Whether -Wstringop-truncation is issued
+ is tested elsewhere. */
+ stpncpy (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncpy (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncat (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+}
+
+
+void test_char_array (char *dst)
+{
+ memcpy (dst, a10, sizeof a10);
+ mempcpy (dst, a10, sizeof a10);
+ memmove (dst, a10, sizeof a10);
+
+ memset (dst, 0, sizeof a10);
+
+ stpncpy (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncpy (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncat (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ stpncpy (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncpy (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncat (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+}
+
+
+#undef FUNC
+#define FUNC(f, d, s, n, os) __builtin___ ## f ## _chk (d, s, n, os)
+
+void test_char_array_chk (char *dst)
+{
+ memcpy (dst, S10, sizeof S10);
+ mempcpy (dst, S10, sizeof S10);
+ memmove (dst, S10, sizeof S10);
+
+ memset (dst, 0, sizeof S10);
+
+ stpncpy (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncpy (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncat (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ stpncpy (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncpy (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncat (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+}
+
+
+void test_string_literal_chk (char *dst)
+{
+ memcpy (dst, a10, sizeof a10);
+ mempcpy (dst, a10, sizeof a10);
+ memmove (dst, a10, sizeof a10);
+
+ memset (dst, 0, sizeof a10);
+
+ stpncpy (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncpy (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncat (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ stpncpy (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncpy (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncat (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+}
diff --git a/gcc/testsuite/c-c++-common/Wstringop-overflow.c b/gcc/testsuite/c-c++-common/Wstringop-overflow.c
new file mode 100644
index 0000000..4c3bd73
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wstringop-overflow.c
@@ -0,0 +1,157 @@
+/* PR middle-end/81117 - Improve buffer overflow checking in strncpy
+ { dg-do compile }
+ { dg-options "-O2 -Wstringop-overflow -Wno-stringop-truncation -ftrack-macro-expansion=0" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+#if __cplusplus
+extern "C" {
+#endif
+
+size_t strlen (const char*);
+char* strncat (char*, const char*, size_t);
+char* strncpy (char*, const char*, size_t);
+#if __cplusplus
+}
+#endif
+
+const char ar[] = "123";
+
+void test_strncat (char **d, const char* s, int i)
+{
+ /* Use a fresh pointer for each test to prevent the optimizer from
+ eliminating redundant writes into the same destination. Avoid
+ calling functions like sink() on the result that would have to
+ be assumed to change the source string by the alias oracle. */
+#define T(d, s, len) strncat (*d++, (s), (len))
+
+ T (d, "", 0);
+ T (d, "", 1);
+ T (d, "", 2);
+ T (d, "", 3);
+ T (d, "123", 0);
+ /* The following three calls truncate the copy and are diagnosed
+ by -Wstringop-truncation but there is evidence of overflow so
+ they're not diagnosed by -Wstringop-overflow. */
+ T (d, "123", 1);
+ T (d, "123", 2);
+ T (d, "123", 3);
+ T (d, "123", 4);
+ T (d, "123", 9);
+
+ T (d, s, strlen (s)); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ T (d, s, strlen (s) + 1); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ /* The following could also be diagnosed by -Wstringop-truncation
+ (with some effort to distinguish the pattern from others like
+ the one above. */
+ T (d, s, strlen (s) - 1); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ T (d, s, strlen (s) - i); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+
+ /* The following is dubious but not necessarily a smoking gun. */
+ T (d, s, strlen (s) - strlen (s));
+
+ {
+ signed char n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ short n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ int n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ unsigned n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s) - 1; /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-message ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ /* This doesn't overflow so iit should not be diagnosed. */
+ size_t n = strlen (s) - strlen (s);
+ T (d, s, n);
+ }
+
+ {
+ size_t n = i < strlen (s) ? i : strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-message ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+}
+
+
+void test_strncpy (char **d, const char* s, int i)
+{
+#undef T
+#define T(d, s, len) strncpy (*d++, (s), (len))
+
+ T (d, "", 0);
+ T (d, "", 1);
+ T (d, "", 2);
+ T (d, "", 3);
+ T (d, "123", 0);
+ T (d, "123", 1);
+ T (d, "123", 2);
+ T (d, "123", 3);
+ T (d, "123", 4);
+ T (d, "123", 9);
+
+ T (d, "123", sizeof "123");
+ T (d, ar, sizeof ar);
+
+ T (d, s, strlen (s)); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+
+ {
+ int n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ }
+
+ {
+ unsigned n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s) - 1; /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ }
+
+ {
+ /* This is diagnosed by -Wstringop-truncation. Verify that it isn't
+ also diagnosed by -Wstringop-overflow. */
+ size_t n = strlen (s) - strlen (s);
+ T (d, s, n);
+ }
+
+ {
+ /* This use of strncpy is certainly dubious and it could well be
+ diagnosed by -Wstringop-truncation but it isn't. That it is
+ diagnosed with -Wstringop-overflow is more by accident than
+ by design. -Wstringop-overflow considers any dependency of
+ the bound on strlen(s) a potential bug. */
+ size_t n = i < strlen (s) ? i : strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-message ".strncpy\[^\n\r]* specified bound depends on the length of the source argument" } */
+ }
+}
diff --git a/gcc/testsuite/c-c++-common/Wstringop-truncation.c b/gcc/testsuite/c-c++-common/Wstringop-truncation.c
new file mode 100644
index 0000000..bc47ec5
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wstringop-truncation.c
@@ -0,0 +1,263 @@
+/* PR middle-end/81117 - Improve buffer overflow checking in strncpy
+ { dg-do compile }
+ { dg-options "-O2 -Wstringop-truncation -Wno-stringop-overflow -ftrack-macro-expansion=0" } */
+
+
+typedef __SIZE_TYPE__ size_t;
+
+#if __cplusplus
+extern "C" {
+#endif
+
+size_t strlen (const char*);
+char* strncat (char*, const char*, size_t);
+char* strncpy (char*, const char*, size_t);
+
+#if __cplusplus
+}
+#endif
+
+void sink (char*);
+
+#define S4 "123"
+const char a4[] = "123";
+
+
+typedef struct Dest
+{
+ char a[5];
+} Dest;
+
+char dest[7];
+
+/* Verify strncat warnings for arrays of known bounds. */
+
+void test_strncat_array (Dest *pd)
+{
+#undef T
+#define T(d, s, len) (strncat ((d), (s), (len)), sink (d))
+
+ T (dest, S4, 2); /* { dg-warning "specified bound 2 is less than source length 3" } */
+ T (dest, S4, 3); /* { dg-warning "specified bound 3 equals source length" } */
+
+ T (dest, a4, 1); /* { dg-warning "specified bound 1 is less than source length 3" } */
+ T (dest, a4, 3); /* { dg-warning "specified bound 3 equals source length" } */
+
+ T (pd->a, S4, 2); /* { dg-warning "specified bound 2 is less than source length" } */
+ T (pd->a, S4, 1); /* { dg-warning "specified bound 1 is less than source length 3" } */
+}
+
+/* Verify strncat warnings for arrays of unknown bounds. */
+
+void test_strncat_vla (char *d, unsigned n)
+{
+ T (d, S4, 2); /* { dg-warning "specified bound 2 is less than source length 3" } */
+ T (d, S4, 3); /* { dg-warning "specified bound 3 equals source length" } */
+ T (d, S4, 4);
+
+ T (d, a4, 2); /* { dg-warning "specified bound 2 is less than source length 3" } */
+ T (d, a4, 3); /* { dg-warning "specified bound 3 equals source length" } */
+ T (d, a4, 4);
+
+ char vla[n];
+
+ T (vla, S4, 2); /* { dg-warning "specified bound 2 is less than source length 3" } */
+
+ T (vla, S4, 3); /* { dg-warning "specified bound 3 equals source length" } */
+
+ T (vla, S4, 4);
+ T (vla, S4, n);
+
+ T (vla, a4, 2); /* { dg-warning "specified bound 2 is less than source length 3" } */
+
+ T (vla, a4, 3); /* { dg-warning "specified bound 3 equals source length" } */
+
+ T (vla, a4, 4);
+ T (vla, a4, n);
+
+ T (d, vla, 1);
+ T (d, vla, 3);
+ T (d, vla, 4);
+ T (d, vla, n);
+}
+
+/* Verify strncpy warnings for pointers to unknown strings. */
+
+void test_strncpy_ptr (char *d, const char* s, int i)
+{
+#undef T
+#define T(d, s, len) (strncpy ((d), (s), (len)), sink (d))
+
+ /* Strncpy doesn't nul-terminate so the following is diagnosed. */
+ T (d, "", 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
+
+ /* This is safe. */
+ T (d, "", 1);
+ T (d, "", 2);
+
+ /* Truncation. */
+ T (d, "123", 3); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ T (d, "123", 4);
+ T (d, "123", 9);
+
+ T (d, S4, sizeof S4); /* Covered by -Wsizeof-pointer-memaccess. */
+ T (d, S4, sizeof S4 - 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+
+ T (d, a4, sizeof a4); /* Covered by -Wsizeof-pointer-memaccess. */
+ T (d, a4, sizeof a4 - 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ T (d, a4, sizeof a4 - 3); /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 1 byte from a string of length 3" } */
+ T (d, a4, sizeof a4 - 4); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes from a string of length 3" } */
+
+ T (d, S4, strlen (S4)); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ /* Likely buggy but no truncation. Diagnosed by -Wstringop-overflow. */
+ T (d, S4, strlen (S4) + 1);
+ T (d, S4, strlen (S4) + i);
+
+ T (d, S4, strlen (a4)); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ /* As above, buggy but no evidence of truncation. */
+ T (d, S4, strlen (a4) + 1);
+ T (d, S4, strlen (a4) + i);
+
+ {
+ signed char n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ short n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ int n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ unsigned n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ size_t n;
+ char *dp2 = d + 1;
+ n = strlen (s); /* { dg-message "length computed here" } */
+ T (dp2, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ /* The following is likely buggy but there's no apparent truncation
+ so it's not diagnosed by -Wstringop-truncation. Instead, it is
+ diagnosed by -Wstringop-overflow (tested elsewhere). */
+ int n;
+ n = strlen (s) - 1;
+ T (d, s, n);
+ }
+
+ {
+ /* Same as above. */
+ size_t n;
+ n = strlen (s) - 1;
+ T (d, s, n);
+ }
+
+ {
+ size_t n = strlen (s) - strlen (s);
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
+ }
+
+ {
+ /* This use of strncpy is dubious but it's probably not worth
+ worrying about (truncation may not actually take place when
+ i is the result). It is diagnosed with -Wstringop-overflow
+ (although more by accident than by design).
+
+ size_t n = i < strlen (s) ? i : strlen (s);
+ T (d, s, n);
+ */
+ }
+}
+
+
+/* Verify strncpy warnings for arrays of known bounds. */
+
+void test_strncpy_array (Dest *pd, const char* s)
+{
+ T (dest, s, 7); /* { dg-warning "specified bound 7 equals destination size" } */
+ T (dest, s, sizeof dest); /* { dg-warning "specified bound 7 equals destination size" } */
+
+ /* Because strnlen appends as many NULs as necessary to write the specified
+ number of byts the following doesn't (necessarily) truncate but rather
+ overflow, and so is diagnosed by -Wstringop-overflow. */
+ T (dest, s, 8);
+
+ T (dest + 1, s, 6); /* { dg-warning "specified bound 6 equals destination size" } */
+ T (dest + 6, s, 1); /* { dg-warning "specified bound 1 equals destination size" } */
+
+ T (pd->a, s, 5); /* { dg-warning "specified bound 5 equals destination size" } */
+ T (pd->a, s, sizeof pd->a); /* { dg-warning "specified bound 5 equals destination size" } */
+
+ /* Same asbove, diagnosed by -Wstringop-overflow. */
+ T (pd->a, s, 6);
+}
+
+typedef struct Flex
+{
+ size_t n;
+ char a0[0];
+ char a[];
+} Flex;
+
+extern char array[];
+
+/* Verify that no warning is issued for array of unknown bound, flexible
+ array members, or zero-length arrays. */
+
+void test_strncpy_flexarray (Flex *pd, const char* s)
+{
+ T (array, s, 7);
+ T (array, s, 123);
+
+ T (pd->a0, s, 1);
+ T (pd->a0, s, 1234);
+
+ T (pd->a, s, 5);
+ T (pd->a, s, 12345);
+}
+
+/* Verify warnings for dynamically allocated objects. */
+
+void test_strncpy_alloc (const char* s)
+{
+ size_t n = 7;
+ char *d = (char *)__builtin_malloc (n);
+
+ T (d, s, n); /* { dg-warning "specified bound 7 equals destination size" "bug 79016" { xfail *-*-* } } */
+
+ Dest *pd = (Dest *)__builtin_malloc (sizeof *pd * n);
+ T (pd->a, s, 5); /* { dg-warning "specified bound 5 equals destination size" } */
+ T (pd->a, s, sizeof pd->a); /* { dg-warning "specified bound 5 equals destination size" } */
+}
+
+/* Verify warnings for VLAs. */
+
+void test_strncpy_vla (unsigned n, const char* s)
+{
+ char vla[n];
+ T (vla, s, 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
+
+ T (vla, s, 1);
+ T (vla, s, 2);
+ T (vla, s, n);
+
+ T (vla, "", 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
+ T (vla, "", 1);
+ T (vla, S4, 3); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ T (vla, S4, n);
+}
diff --git a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C
index c72532b..5bc5c4c 100644
--- a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C
+++ b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C
@@ -1,6 +1,6 @@
// Test -Wsizeof-pointer-memaccess warnings.
// { dg-do compile }
-// { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow" }
+// { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow -Wno-stringop-truncation" }
// Test just twice, once with -O0 non-fortified, once with -O2 fortified.
// { dg-skip-if "" { *-*-* } { "*" } { "-O0" "-O2" } }
// { dg-skip-if "" { *-*-* } { "-flto" } { "" } }
@@ -698,12 +698,17 @@ f4 (char *x, char **y, int z, char w[64])
strncat (w, s2, sizeof (w)); // { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" }
stpncpy (w, s1, sizeof (w)); // { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" }
- // These are correct, no warning.
const char s3[] = "foobarbaz";
const char s4[] = "abcde12345678";
- strncpy (x, s3, sizeof (s3));
- strncat (x, s4, sizeof (s4));
- stpncpy (x, s3, sizeof (s3));
+
+ // These are pointless when the destination is large enough, and
+ // cause overflow otherwise. They might as well be replaced by
+ // strcpy() or memcpy().
+ strncpy (x, s3, sizeof (s3)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" }
+ strncat (x, s4, sizeof (s4)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" }
+ stpncpy (x, s3, sizeof (s3)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" }
+
+ // These are safe, no warning.
y[1] = strndup (s3, sizeof (s3));
z += strncmp (s3, s4, sizeof (s3));
z += strncmp (s3, s4, sizeof (s4));
diff --git a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C
index a216f47..f2c864b 100644
--- a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C
+++ b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C
@@ -1,6 +1,6 @@
// Test -Wsizeof-pointer-memaccess warnings.
// { dg-do compile }
-// { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow" }
+// { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow -Wno-stringop-truncation" }
// Test just twice, once with -O0 non-fortified, once with -O2 fortified,
// suppressing buffer overflow warnings.
// { dg-skip-if "" { *-*-* } { "*" } { "-O0" "-O2" } }
@@ -703,12 +703,13 @@ f4 (char *x, char **y, int z, char w[64])
strncat (w, s2, sizeof (w)); // { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" }
stpncpy (w, s1, sizeof (w)); // { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" }
- // These are correct, no warning.
const char s3[] = "foobarbaz";
const char s4[] = "abcde12345678";
- strncpy (x, s3, sizeof (s3));
- strncat (x, s4, sizeof (s4));
- stpncpy (x, s3, sizeof (s3));
+ strncpy (x, s3, sizeof (s3)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination" }
+ strncat (x, s4, sizeof (s4)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination" }
+ stpncpy (x, s3, sizeof (s3)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination" }
+
+ // These are safe, no warning.
y[1] = strndup (s3, sizeof (s3));
z += strncmp (s3, s4, sizeof (s3));
z += strncmp (s3, s4, sizeof (s4));
diff --git a/gcc/testsuite/gcc.dg/Walloca-1.c b/gcc/testsuite/gcc.dg/Walloca-1.c
index ad39373..85e9160 100644
--- a/gcc/testsuite/gcc.dg/Walloca-1.c
+++ b/gcc/testsuite/gcc.dg/Walloca-1.c
@@ -1,6 +1,6 @@
/* { dg-do compile } */
/* { dg-require-effective-target alloca } */
-/* { dg-options "-Walloca-larger-than=2000 -O2" } */
+/* { dg-options "-Walloca-larger-than=2000 -O2 -ftrack-macro-expansion=0" } */
#define alloca __builtin_alloca
diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c
index b0563fe..1f4b0f3 100644
--- a/gcc/tree-ssa-strlen.c
+++ b/gcc/tree-ssa-strlen.c
@@ -40,11 +40,14 @@ along with GCC; see the file COPYING3. If not see
#include "expr.h"
#include "tree-dfa.h"
#include "domwalk.h"
+#include "tree-ssa-alias.h"
#include "tree-ssa-propagate.h"
#include "params.h"
#include "ipa-chkp.h"
#include "tree-hash-traits.h"
#include "builtins.h"
+#include "diagnostic-core.h"
+#include "diagnostic.h"
/* A vector indexed by SSA_NAME_VERSION. 0 means unknown, positive value
is an index into strinfo vector, negative value stands for
@@ -146,6 +149,9 @@ struct decl_stridxlist_map
mappings. */
static hash_map<tree_decl_hash, stridxlist> *decl_to_stridxlist_htab;
+typedef std::pair<int, location_t> stridx_strlenloc;
+static hash_map<tree, stridx_strlenloc> strlen_to_stridx;
+
/* Obstack for struct stridxlist and struct decl_stridxlist_map. */
static struct obstack stridx_obstack;
@@ -1197,6 +1203,9 @@ handle_builtin_strlen (gimple_stmt_iterator *gsi)
si->nonzero_chars = lhs;
gcc_assert (si->full_string_p);
}
+
+ location_t loc = gimple_location (stmt);
+ strlen_to_stridx.put (lhs, stridx_strlenloc (idx, loc));
return;
}
}
@@ -1240,6 +1249,9 @@ handle_builtin_strlen (gimple_stmt_iterator *gsi)
strinfo *si = new_strinfo (src, idx, lhs, true);
set_strinfo (idx, si);
find_equal_ptrs (src, idx);
+
+ location_t loc = gimple_location (stmt);
+ strlen_to_stridx.put (lhs, stridx_strlenloc (idx, loc));
}
}
@@ -1606,6 +1618,133 @@ handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi)
fprintf (dump_file, "not possible.\n");
}
+/* Return true if LEN depends on a call to strlen(SRC) in an interesting
+ way. LEN can either be an integer expression, or a pointer (to char).
+ When it is the latter (such as in recursive calls to self) is is
+ assumed to be the argument in some call to strlen() whose relationship
+ to SRC is being ascertained. */
+
+static bool
+is_strlen_related_p (tree src, tree len)
+{
+ if (TREE_CODE (TREE_TYPE (len)) == POINTER_TYPE
+ && operand_equal_p (src, len, 0))
+ return true;
+
+ if (TREE_CODE (len) != SSA_NAME)
+ return false;
+
+ gimple *def_stmt = SSA_NAME_DEF_STMT (len);
+ if (!def_stmt)
+ return false;
+
+ if (is_gimple_call (def_stmt))
+ {
+ tree func = gimple_call_fndecl (def_stmt);
+ if (!valid_builtin_call (def_stmt)
+ || DECL_FUNCTION_CODE (func) != BUILT_IN_STRLEN)
+ return false;
+
+ tree arg = gimple_call_arg (def_stmt, 0);
+ return is_strlen_related_p (src, arg);
+ }
+
+ if (!is_gimple_assign (def_stmt))
+ return false;
+
+ tree_code code = gimple_assign_rhs_code (def_stmt);
+ tree rhs1 = gimple_assign_rhs1 (def_stmt);
+ tree rhstype = TREE_TYPE (rhs1);
+
+ if ((TREE_CODE (rhstype) == POINTER_TYPE && code == POINTER_PLUS_EXPR)
+ || (INTEGRAL_TYPE_P (rhstype)
+ && (code == BIT_AND_EXPR
+ || code == NOP_EXPR)))
+ {
+ /* Pointer plus (an integer) and integer cast or truncation are
+ considered among the (potentiall) related expressions to strlen.
+ Others are not. */
+ return is_strlen_related_p (src, rhs1);
+ }
+
+ return false;
+}
+
+/* Check the size argument to the built-in forms of stpncpy and strncpy
+ to see if it's derived from calling strlen() on the source argument
+ and if so, issue a warning. */
+
+static void
+check_builtin_stxncpy (built_in_function, gimple_stmt_iterator *gsi)
+{
+ gimple *stmt = gsi_stmt (*gsi);
+
+ bool with_bounds = gimple_call_with_bounds_p (stmt);
+
+ tree src = gimple_call_arg (stmt, with_bounds ? 2 : 1);
+ tree len = gimple_call_arg (stmt, with_bounds ? 3 : 2);
+
+ /* If the length argument was computed from strlen(S) for some string
+ S retrieve the strinfo index for the string (PSS->FIRST) alonng with
+ the location of the strlen() call (PSS->SECOND). */
+ stridx_strlenloc *pss = strlen_to_stridx.get (len);
+ if (!pss || pss->first <= 0)
+ return;
+
+ int sidx = get_stridx (src);
+ strinfo *sisrc = sidx > 0 ? get_strinfo (sidx) : NULL;
+
+ /* Strncpy() et al. cannot modify the source string. Prevent the rest
+ of the pass from invalidating the strinfo data. */
+ if (sisrc)
+ sisrc->dont_invalidate = true;
+
+ /* Retrieve the strinfo data for the string S that LEN was computed
+ from as some function F of strlen (S) (i.e., LEN need not be equal
+ to strlen(S)). */
+ strinfo *silen = get_strinfo (pss->first);
+
+ location_t callloc = gimple_location (stmt);
+ tree func = gimple_call_fndecl (stmt);
+
+ bool warned = false;
+
+ /* When -Wstringop-truncation is set, try to determine truncation
+ before diagnosing possible overflow. Truncation is implied by
+ the LEN argument being equal to strlen(SRC), regardless of
+ whether its value is known. Otherwise, issue the more generic
+ -Wstringop-overflow which triggers for LEN arguments that in
+ any meaningful way depend on strlen(SRC). */
+ if (warn_stringop_truncation
+ && sisrc == silen
+ && is_strlen_related_p (src, len))
+ warned = warning_at (callloc, OPT_Wstringop_truncation,
+ "%qD output truncated before terminating nul "
+ "copying as many bytes from a string as its length",
+ func);
+ else if (silen && is_strlen_related_p (src, silen->ptr))
+ warned = warning_at (callloc, OPT_Wstringop_overflow_,
+ "%qD specified bound depends on the length "
+ "of the source argument", func);
+ if (warned)
+ {
+ location_t strlenloc = pss->second;
+ if (strlenloc != UNKNOWN_LOCATION && strlenloc != callloc)
+ inform (strlenloc, "length computed here");
+ }
+}
+
+/* Check the size argument to the built-in forms of strncat to see if
+ it's derived from calling strlen() on the source argument and if so,
+ issue a warning. */
+
+static void
+check_builtin_strncat (built_in_function bcode, gimple_stmt_iterator *gsi)
+{
+ /* Same as stxncpy(). */
+ check_builtin_stxncpy (bcode, gsi);
+}
+
/* Handle a memcpy-like ({mem{,p}cpy,__mem{,p}cpy_chk}) call.
If strlen of the second argument is known and length of the third argument
is that plus one, strlen of the first argument is the same after this
@@ -2512,6 +2651,19 @@ strlen_optimize_stmt (gimple_stmt_iterator *gsi)
case BUILT_IN_STPCPY_CHK_CHKP:
handle_builtin_strcpy (DECL_FUNCTION_CODE (callee), gsi);
break;
+
+ case BUILT_IN_STRNCAT:
+ case BUILT_IN_STRNCAT_CHK:
+ check_builtin_strncat (DECL_FUNCTION_CODE (callee), gsi);
+ break;
+
+ case BUILT_IN_STPNCPY:
+ case BUILT_IN_STPNCPY_CHK:
+ case BUILT_IN_STRNCPY:
+ case BUILT_IN_STRNCPY_CHK:
+ check_builtin_stxncpy (DECL_FUNCTION_CODE (callee), gsi);
+ break;
+
case BUILT_IN_MEMCPY:
case BUILT_IN_MEMCPY_CHK:
case BUILT_IN_MEMPCPY:
@@ -2575,6 +2727,10 @@ strlen_optimize_stmt (gimple_stmt_iterator *gsi)
else if (code == EQ_EXPR || code == NE_EXPR)
fold_strstr_to_strncmp (gimple_assign_rhs1 (stmt),
gimple_assign_rhs2 (stmt), stmt);
+
+ tree rhs1 = gimple_assign_rhs1 (stmt);
+ if (stridx_strlenloc *ps = strlen_to_stridx.get (rhs1))
+ strlen_to_stridx.put (lhs, *ps);
}
else if (TREE_CODE (lhs) != SSA_NAME && !TREE_SIDE_EFFECTS (lhs))
{
@@ -2826,6 +2982,8 @@ pass_strlen::execute (function *fun)
laststmt.len = NULL_TREE;
laststmt.stridx = 0;
+ strlen_to_stridx.empty ();
+
return 0;
}
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-07-31 19:42 ` Martin Sebor
@ 2017-08-02 16:59 ` Jeff Law
2017-08-06 20:07 ` [PATCH 3/4] " Martin Sebor
` (4 more replies)
0 siblings, 5 replies; 39+ messages in thread
From: Jeff Law @ 2017-08-02 16:59 UTC (permalink / raw)
To: Martin Sebor, Gcc Patch List
On 07/31/2017 01:42 PM, Martin Sebor wrote:
>> So I *think* TYPE_SIZE_UNIT isn't necessarily guaranteed to be a
>> INTEGER_CST, it could be a non-constant expression for the size. Are
>> the callers of compute_objsize prepared to handle that? Just to be
>> clear, I'd prefer to return TYPE_SIZE_UNIT even if it's not an
>> INTEGER_CST, I'm just worried about the callers ability to handle that
>> correctly.
>
> They should be prepared for it. If not, it's a bug. I've added
> a few more test cases though I'm not sure the case you're concerned
> about actually arises (VLA sizes are represented as gimple calls to
> __builtin_alloca_with_align so the code doesn't get this far).
It may in the end be a non-issue because VLAs are still represented as
alloca calls at this point. BUt it looks like the result is typically
passed down into check_sizes which assumes the result is an INTEGER_CST
based on a quick scan.
So just to be future safe perhaps this:
if (TREE_CODE (type) == ARRAY_TYPE
&& TREE_CODE (TYPE_SIZE_UNIT (type)) == INTEGER_CST)
return TYPE_SIZE_UNIT (type);
>>> +
>>> +@smallexample
>>> +void append (char *d)
>>> +@{
>>> + strncat (d, ".txt", 4);
>>> +@}
>>> +@end smallexample
>> Sorry. I don't follow this particular example. Where's the truncation
>> when strlen (SRC) == N for strncat? In that case aren't we going to
>> append SRC without the nul terminator, then nul terminate the result?
>> Isn't that the same as appending SRC with its nul terminator? Am I
>> missing something here?
>
> You're right that there is no truncation and the effect is
> the same but only in the unlikely case when the destination
> is empty. Otherwise the result is truncated.
Maybe this is where I'm confused. How does the destination play into
truncation issues? I've always been under the impression that the
destination has to be large enough to hold the result, but that it
doesn't affect truncation of the source.
>
>>
>>> @@ -2512,6 +2651,19 @@ strlen_optimize_stmt (gimple_stmt_iterator *gsi)
>>> case BUILT_IN_STPCPY_CHK_CHKP:
>>> handle_builtin_strcpy (DECL_FUNCTION_CODE (callee), gsi);
>>> break;
>>> +
>>> + case BUILT_IN_STRNCAT:
>>> + case BUILT_IN_STRNCAT_CHK:
>>> + check_builtin_strncat (DECL_FUNCTION_CODE (callee), gsi);
>>> + break;
>>> +
>>> + case BUILT_IN_STPNCPY:
>>> + case BUILT_IN_STPNCPY_CHK:
>>> + case BUILT_IN_STRNCPY:
>>> + case BUILT_IN_STRNCPY_CHK:
>>> + check_builtin_stxncpy (DECL_FUNCTION_CODE (callee), gsi);
>>> + break;
>>> +
>> So we've got calls to check the arguments, but not optimize here. But
>> the containing function is "strlen_optimize_stmt".
>>
>> Would it make sense to first call strlen_optimize_stmt to handle the
>> optimization cases, then to call a new separate function to handle
>> warnings for the strn* family?
>
> tree-ssa-strlen doesn't handle strncat or strncpy (for the latter
> I'm tracking the enhancement in bug 81433). When the handling is
> added I expect check_builtin_{strncat,stxncpy} will be renamed to
> handle_builtin_{strncat,stxncpy} to match the existing functions,
> and the optimization done there.
>
> Or did you have something else in mind and I missed it?
So do you envision doing both optimization and checking together? If
so, then I think we'd want to rename strlen_optimize_stmt. If
optimization and checking are expected to remain separate we'd want a
new function to handle checking of strncat stpncpy, strncpy and the
caller would look something like this:
for (gimple_stmt_iterator gsi = gsi_start_bb (bb); !gsi_end_p (gsi); )
{
check_strstar_arguments (gsi); // Or whatever we named it
if (strlen_optimize_stmt (&gsi))
gsi_next (&gsi);
}
jeff
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH 4/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-02 16:59 ` Jeff Law
2017-08-06 20:07 ` [PATCH 3/4] " Martin Sebor
@ 2017-08-06 20:07 ` Martin Sebor
2017-08-06 20:07 ` [PATCH] " Martin Sebor
` (2 subsequent siblings)
4 siblings, 0 replies; 39+ messages in thread
From: Martin Sebor @ 2017-08-06 20:07 UTC (permalink / raw)
To: Jeff Law, Gcc Patch List
[-- Attachment #1: Type: text/plain, Size: 169 bytes --]
For completeness, patch 4 are the (already preapproved) fixups
to GCC to let it compile with the -Wstringop-truncation option.
I will commit this patch shortly.
Martin
[-- Attachment #2: gcc-81117-4.diff --]
[-- Type: text/x-patch, Size: 3119 bytes --]
PR c/81117 - Improve buffer overflow checking in strncpy
gcc/ada/ChangeLog:
PR c/81117
* adadecode.c (__gnat_decode): Replace pointless strncpy with
memcpy.
* argv.c (__gnat_fill_arg): Same.
gcc/c-family/ChangeLog:
PR c/81117
* c-common.c (resort_sorted_fields): Replace pointless strncpy
with memcpy.
gcc/fortran/ChangeLog:
PR c/81117
* decl.c (build_sym): Replace pointless strncpy with strcpy.
gcc/objc/ChangeLog:
PR c/81117
* objc-encoding.c (encode_type): Replace pointless strncpy with
memcpy.
diff --git a/gcc/ada/adadecode.c b/gcc/ada/adadecode.c
index 8c9c7ab..0cbef81 100644
--- a/gcc/ada/adadecode.c
+++ b/gcc/ada/adadecode.c
@@ -330,7 +330,7 @@ __gnat_decode (const char *coded_name, char *ada_name, int verbose)
}
/* Write symbol in the space. */
- strncpy (optoken, trans_table[k][1], oplen);
+ memcpy (optoken, trans_table[k][1], oplen);
}
else
k++;
diff --git a/gcc/ada/argv.c b/gcc/ada/argv.c
index 430404e..aee0f88 100644
--- a/gcc/ada/argv.c
+++ b/gcc/ada/argv.c
@@ -92,7 +92,7 @@ void
__gnat_fill_arg (char *a, int i)
{
if (gnat_argv != NULL)
- strncpy (a, gnat_argv[i], strlen(gnat_argv[i]));
+ memcpy (a, gnat_argv[i], strlen (gnat_argv[i]));
}
int
@@ -118,7 +118,7 @@ void
__gnat_fill_env (char *a, int i)
{
if (gnat_envp != NULL)
- strncpy (a, gnat_envp[i], strlen (gnat_envp[i]));
+ memcpy (a, gnat_envp[i], strlen (gnat_envp[i]));
}
#ifdef __cplusplus
diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index feb0904..8c18caf 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -5942,10 +5942,10 @@ resort_sorted_fields (void *obj,
static char *
catenate_strings (const char *lhs, const char *rhs_start, int rhs_size)
{
- const int lhs_size = strlen (lhs);
+ const size_t lhs_size = strlen (lhs);
char *result = XNEWVEC (char, lhs_size + rhs_size);
- strncpy (result, lhs, lhs_size);
- strncpy (result + lhs_size, rhs_start, rhs_size);
+ memcpy (result, lhs, lhs_size);
+ memcpy (result + lhs_size, rhs_start, rhs_size);
return result;
}
diff --git a/gcc/fortran/decl.c b/gcc/fortran/decl.c
index 54ee5d3..82971e8 100644
--- a/gcc/fortran/decl.c
+++ b/gcc/fortran/decl.c
@@ -1417,11 +1417,9 @@ build_sym (const char *name, gfc_charlen *cl, bool cl_deferred,
{
char u_name[GFC_MAX_SYMBOL_LEN + 1];
gfc_symtree *st;
- int nlen;
- nlen = strlen(name);
- gcc_assert (nlen <= GFC_MAX_SYMBOL_LEN);
- strncpy (u_name, name, nlen + 1);
+ gcc_assert (strlen(name) <= GFC_MAX_SYMBOL_LEN);
+ strcpy (u_name, name);
u_name[0] = upper;
st = gfc_find_symtree (gfc_current_ns->sym_root, u_name);
diff --git a/gcc/objc/objc-encoding.c b/gcc/objc/objc-encoding.c
index 2a2dfa5..e5d4f38 100644
--- a/gcc/objc/objc-encoding.c
+++ b/gcc/objc/objc-encoding.c
@@ -734,7 +734,7 @@ encode_type (tree type, int curtype, int format)
/* Rewrite "in const" from "nr" to "rn". */
if (curtype >= 1 && !strncmp (enc - 1, "nr", 2))
- strncpy (enc - 1, "rn", 2);
+ memcpy (enc - 1, "rn", 2);
}
}
}
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH 2/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-02 16:59 ` Jeff Law
` (2 preceding siblings ...)
2017-08-06 20:07 ` [PATCH] " Martin Sebor
@ 2017-08-06 20:07 ` Martin Sebor
2017-08-10 6:39 ` Jeff Law
2017-11-10 23:03 ` Marc Glisse
2017-08-06 20:07 ` [PATCH 1/4] " Martin Sebor
4 siblings, 2 replies; 39+ messages in thread
From: Martin Sebor @ 2017-08-06 20:07 UTC (permalink / raw)
To: Jeff Law, Gcc Patch List
[-- Attachment #1: Type: text/plain, Size: 534 bytes --]
Part 2 of the series adds attribute nostring to annotate arrays
of and pointers to char with that are intended to store sequences
of characters that aren't necessarily valid (nul-terminated)
strings. In the subsequent patch the attribute is relied on to
avoid diagnosing strcncpy calls that truncate strings and create
such copies. In the future I'd like to also use the attribute
to diagnose when arrays or pointers with the attribute are passed
to functions that expect nul-terminated strings (such as strlen
or strcpy).
Martin
[-- Attachment #2: gcc-81117-2.diff --]
[-- Type: text/x-patch, Size: 6837 bytes --]
PR c/81117 - Improve buffer overflow checking in strncpy
gcc/ChangeLog:
PR c/81117
* builtin-attrs.def (attribute nonstring): New.
* doc/extend.texi (attribute nonstring): Document new attribute.
gcc/c-family/ChangeLog:
PR c/81117
* c-attribs.c (c_common_attribute_table): Add nonstring entry.
(handle_nonstring_attribute): New function.
gcc/testsuite/ChangeLog:
PR c/81117
* c-c++-common/attr-nonstring-1.c: New test.
--- a/gcc/builtin-attrs.def
+++ b/gcc/builtin-attrs.def
@@ -93,6 +93,7 @@ DEF_ATTR_IDENT (ATTR_FORMAT, "format")
DEF_ATTR_IDENT (ATTR_FORMAT_ARG, "format_arg")
DEF_ATTR_IDENT (ATTR_MALLOC, "malloc")
DEF_ATTR_IDENT (ATTR_NONNULL, "nonnull")
+DEF_ATTR_IDENT (ATTR_NONSTRING, "nonstring")
DEF_ATTR_IDENT (ATTR_NORETURN, "noreturn")
DEF_ATTR_IDENT (ATTR_NOTHROW, "nothrow")
DEF_ATTR_IDENT (ATTR_LEAF, "leaf")
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index 0d9ab2d..69d020c 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -116,6 +116,7 @@ static tree handle_deprecated_attribute (tree *, tree, tree, int,
static tree handle_vector_size_attribute (tree *, tree, tree, int,
bool *);
static tree handle_nonnull_attribute (tree *, tree, tree, int, bool *);
+static tree handle_nonstring_attribute (tree *, tree, tree, int, bool *);
static tree handle_nothrow_attribute (tree *, tree, tree, int, bool *);
static tree handle_cleanup_attribute (tree *, tree, tree, int, bool *);
static tree handle_warn_unused_result_attribute (tree *, tree, tree, int,
@@ -270,6 +271,8 @@ const struct attribute_spec c_common_attribute_table[] =
handle_tls_model_attribute, false },
{ "nonnull", 0, -1, false, true, true,
handle_nonnull_attribute, false },
+ { "nonstring", 0, 0, true, false, false,
+ handle_nonstring_attribute, false },
{ "nothrow", 0, 0, true, false, false,
handle_nothrow_attribute, false },
{ "may_alias", 0, 0, false, true, false, NULL, false },
@@ -2970,6 +2973,48 @@ handle_nonnull_attribute (tree *node, tree ARG_UNUSED (name),
return NULL_TREE;
}
+/* Handle the "nonstring" variable attribute. */
+
+static tree
+handle_nonstring_attribute (tree *node, tree name, tree ARG_UNUSED (args),
+ int ARG_UNUSED (flags), bool *no_add_attrs)
+{
+ gcc_assert (!args);
+ tree_code code = TREE_CODE (*node);
+
+ if (VAR_P (*node)
+ || code == FIELD_DECL
+ || code == PARM_DECL)
+ {
+ tree type = TREE_TYPE (*node);
+
+ if (POINTER_TYPE_P (type) || TREE_CODE (type) == ARRAY_TYPE)
+ {
+ tree eltype = TREE_TYPE (type);
+ if (eltype == char_type_node)
+ return NULL_TREE;
+ }
+
+ warning (OPT_Wattributes,
+ "%qE attribute ignored on objects of type %qT",
+ name, type);
+ *no_add_attrs = true;
+ return NULL_TREE;
+ }
+
+ if (code == FUNCTION_DECL)
+ warning (OPT_Wattributes,
+ "%qE attribute does not apply to functions", name);
+ else if (code == TYPE_DECL)
+ warning (OPT_Wattributes,
+ "%qE attribute does not apply to types", name);
+ else
+ warning (OPT_Wattributes, "%qE attribute ignored", name);
+
+ *no_add_attrs = true;
+ return NULL_TREE;
+}
+
/* Handle a "nothrow" attribute; arguments as in
struct attribute_spec.handler. */
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index b253ccc..1954ca5 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -5835,6 +5835,30 @@ The @code{deprecated} attribute can also be used for functions and
types (@pxref{Common Function Attributes},
@pxref{Common Type Attributes}).
+@item nonstring (@var{nonstring})
+@cindex @code{nonstring} variable attribute
+The @code{nonstring} variable attribute specifies that an object or member
+declaration with type array of @code{char} or pointer to @code{char} is
+intended to store character arrays that do not necessarily contain
+a terminating @code{NUL} character. This is useful to avoid warnings
+when such an array or pointer is used as an argument to a bounded string
+manipulation function such as @code{strncpy}. For example, without the
+attribute, GCC will issue a warning for the call below because it may
+truncate the copy without appending the terminating NUL character. Using
+the attribute makes it possible to suppress the warning.
+
+@smallexample
+struct Data
+@{
+ char name [32] __attribute__ ((nonstring));
+@};
+void f (struct Data *pd, const char *s)
+@{
+ strncpy (pd->name, s, sizeof pd->name);
+ @dots{}
+@}
+@end smallexample
+
@item mode (@var{mode})
@cindex @code{mode} variable attribute
This attribute specifies the data type for the declaration---whichever
diff --git a/gcc/testsuite/c-c++-common/attr-nonstring-1.c b/gcc/testsuite/c-c++-common/attr-nonstring-1.c
new file mode 100644
index 0000000..10a6688
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/attr-nonstring-1.c
@@ -0,0 +1,60 @@
+/* Test to exercise attribute "nonstring" syntax.
+ { dg-do compile }
+ { dg-options "-Wattributes" } */
+
+#define ATTR(list) __attribute__ (list)
+#define NONSTR ATTR ((nonstring))
+
+/* Verify it's accepted on char arrays. */
+extern NONSTR char nsx_1[];
+extern char NONSTR nsx_2[];
+extern char nsx_3[] NONSTR;
+
+extern NONSTR char ns1[1];
+extern char NONSTR ns3[3];
+extern char ns5[5] NONSTR;
+
+/* Verify it's accepted on char pointers. */
+extern NONSTR char* pns_1;
+extern char NONSTR* pns_2;
+extern char* NONSTR pns_3;
+
+struct S
+{
+/* Verify it's accepted on char member pointers. */
+ NONSTR char* mpns_1;
+ char NONSTR* mpns_2;
+ char* NONSTR mpns_3;
+
+/* Verify it's accepted on char member arrays. */
+ NONSTR char mns1[1];
+ char NONSTR mns3[3];
+ char mns5[5] NONSTR;
+
+/* Verify it's accepted on char flexible array members. */
+ char mnsx[] NONSTR;
+};
+
+/* Verify it's rejected on non-array and non-pointer objects. */
+extern NONSTR char c1; /* { dg-warning ".nonstring. attribute ignored on objects of type .char." } */
+
+extern NONSTR int i1; /* { dg-warning ".nonstring. attribute ignored on objects of type .int." } */
+
+extern NONSTR int ia1[]; /* { dg-warning ".nonstring. attribute ignored on objects of type .int *\\\[\\\]." } */
+
+extern NONSTR int* pi1; /* { dg-warning ".nonstring. attribute ignored on objects of type .int *\\*." } */
+
+extern NONSTR
+void f (void); /* { dg-warning ".nonstring. attribute does not apply to functions" } */
+
+struct NONSTR
+NonStrType { int i; }; /* { dg-warning ".nonstring. attribute does not apply to types" } */
+
+typedef char NONSTR nschar_t; /* { dg-warning ".nonstring. attribute does not apply to types" } */
+
+void func (NONSTR char *pns1, char NONSTR *pns2, char* NONSTR pns3)
+{
+ (void)pns1;
+ (void)pns2;
+ (void)pns3;
+}
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH 1/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-02 16:59 ` Jeff Law
` (3 preceding siblings ...)
2017-08-06 20:07 ` [PATCH 2/4] " Martin Sebor
@ 2017-08-06 20:07 ` Martin Sebor
2017-08-10 5:02 ` Jeff Law
4 siblings, 1 reply; 39+ messages in thread
From: Martin Sebor @ 2017-08-06 20:07 UTC (permalink / raw)
To: Jeff Law, Gcc Patch List
[-- Attachment #1: Type: text/plain, Size: 292 bytes --]
The attached patch adds support for a new GCC format specifier,
G, that behaves like %K but accepts a gcall* argument. This
makes it possible to provide inlining context for "artificial"
inline functions like strncpy (with _FORTIFY_SOURCE) in
diagnostics issued from the middle end.
Martin
[-- Attachment #2: gcc-81117-1.diff --]
[-- Type: text/x-patch, Size: 14293 bytes --]
PR c/81117 - Improve buffer overflow checking in strncpy
gcc/ChangeLog:
PR c/81117
* tree-diagnostic.c (default_tree_printer): Handle %G.
* tree-pretty-print.h (percent_G_format): Declare new function.
* tree-pretty-print.c (percent_K_format): Define a static overload.
(percent_G_format): New function.
gcc/c/ChangeLog:
PR c/81117
* c-objc-common.c (c_objc_common_init): Handle 'G'.
gcc/c-family/ChangeLog:
* c-format.h (T89_G): New macro.
* c-format.c (local_gcall_ptr_node): New variable.
(init_dynamic_diag_info): Initialize it.
gcc/cp/ChangeLog:
PR c/81117
* error.c (cp_printer): Handle 'G'.
gcc/testsuite/ChangeLog:
PR c/81117
* gcc.dg/format/gcc_diag-10.c: Exercise %G.
diff --git a/gcc/tree-diagnostic.c b/gcc/tree-diagnostic.c
index 52b7e7f..ad58b69 100644
--- a/gcc/tree-diagnostic.c
+++ b/gcc/tree-diagnostic.c
@@ -275,6 +275,10 @@ default_tree_printer (pretty_printer *pp, text_info *text, const char *spec,
t = va_arg (*text->args_ptr, tree);
break;
+ case 'G':
+ percent_G_format (text);
+ return true;
+
case 'K':
percent_K_format (text);
return true;
diff --git a/gcc/tree-pretty-print.c b/gcc/tree-pretty-print.c
index 4d8177c..7c4c805 100644
--- a/gcc/tree-pretty-print.c
+++ b/gcc/tree-pretty-print.c
@@ -33,6 +33,7 @@ along with GCC; see the file COPYING3. If not see
#include "dumpfile.h"
#include "internal-fn.h"
#include "gomp-constants.h"
+#include "gimple.h"
/* Local functions, macros and variables. */
static const char *op_symbol (const_tree);
@@ -3970,15 +3971,15 @@ newline_and_indent (pretty_printer *pp, int spc)
INDENT (spc);
}
-/* Handle a %K format for TEXT. Separate from default_tree_printer so
- it can also be used in front ends.
- %K: a statement, from which EXPR_LOCATION and TREE_BLOCK will be recorded.
-*/
+/* Handle the %K and %G format for TEXT. Separate from default_tree_printer
+ so it can also be used in front ends.
+ argument is a statement from which EXPR_LOCATION and TREE_BLOCK will be
+ recorded. */
-void
-percent_K_format (text_info *text)
+static void
+percent_K_format (text_info *text, tree t)
{
- tree t = va_arg (*text->args_ptr, tree), block;
+ tree block;
text->set_location (0, EXPR_LOCATION (t), true);
gcc_assert (pp_ti_abstract_origin (text) != NULL);
block = TREE_BLOCK (t);
@@ -4022,6 +4023,34 @@ percent_K_format (text_info *text)
}
}
+/* Handle the %K format for TEXT. */
+
+void
+percent_K_format (text_info *text)
+{
+ tree t = va_arg (*text->args_ptr, tree);
+ percent_K_format (text, t);
+}
+
+/* Handle the %G format for TEXT. Same as %K but with a Gimple call
+ statement as an argument. */
+
+void
+percent_G_format (text_info *text)
+{
+ gcall *stmt = va_arg (*text->args_ptr, gcall*);
+
+ /* Build a call expression from the Gimple call statement and
+ pass it to the K formatter that knows how to format it. */
+ tree exp = build_vl_exp (CALL_EXPR, gimple_call_num_args (stmt) + 3);
+ CALL_EXPR_FN (exp) = gimple_call_fn (stmt);
+ TREE_TYPE (exp) = gimple_call_return_type (stmt);
+ CALL_EXPR_STATIC_CHAIN (exp) = gimple_call_chain (stmt);
+ SET_EXPR_LOCATION (exp, gimple_location (stmt));
+
+ percent_K_format (text, exp);
+}
+
/* Print the identifier ID to PRETTY-PRINTER. */
void
diff --git a/gcc/tree-pretty-print.h b/gcc/tree-pretty-print.h
index 07270b7..213841e 100644
--- a/gcc/tree-pretty-print.h
+++ b/gcc/tree-pretty-print.h
@@ -45,6 +45,7 @@ extern int op_code_prio (enum tree_code);
extern int op_prio (const_tree);
extern const char *op_symbol_code (enum tree_code);
extern void print_call_name (pretty_printer *, tree, dump_flags_t);
+extern void percent_G_format (text_info *);
extern void percent_K_format (text_info *);
extern void pp_tree_identifier (pretty_printer *, tree);
extern void dump_function_header (FILE *, tree, dump_flags_t);
diff --git a/gcc/c/c-objc-common.c b/gcc/c/c-objc-common.c
index 05212b2..9dd5718 100644
--- a/gcc/c/c-objc-common.c
+++ b/gcc/c/c-objc-common.c
@@ -66,6 +66,8 @@ c_objc_common_init (void)
%D: a general decl,
%E: an identifier or expression,
%F: a function declaration,
+ %G: a Gimple call statement,
+ %K: a CALL_EXPR,
%T: a type.
%V: a list of type qualifiers from a tree.
%v: an explicit list of type qualifiers
@@ -87,6 +89,12 @@ c_tree_printer (pretty_printer *pp, text_info *text, const char *spec,
if (precision != 0 || wide)
return false;
+ if (*spec == 'G')
+ {
+ percent_G_format (text);
+ return true;
+ }
+
if (*spec == 'K')
{
percent_K_format (text);
diff --git a/gcc/cp/error.c b/gcc/cp/error.c
index 2497c7f..483d3b5 100644
--- a/gcc/cp/error.c
+++ b/gcc/cp/error.c
@@ -4048,6 +4048,10 @@ cp_printer (pretty_printer *pp, text_info *text, const char *spec,
case 'V': result = cv_to_string (next_tree, verbose); break;
case 'X': result = eh_spec_to_string (next_tree, verbose); break;
+ case 'G':
+ percent_G_format (text);
+ return true;
+
case 'K':
percent_K_format (text);
return true;
diff --git a/gcc/c-family/c-format.c b/gcc/c-family/c-format.c
index 0a5cc03..0ff9446 100644
--- a/gcc/c-family/c-format.c
+++ b/gcc/c-family/c-format.c
@@ -55,6 +55,7 @@ struct function_format_info
/* Initialized in init_dynamic_diag_info. */
static GTY(()) tree local_tree_type_node;
+static GTY(()) tree local_gcall_ptr_node;
static GTY(()) tree locus;
static bool decode_format_attr (tree, function_format_info *, int);
@@ -689,7 +690,9 @@ static const format_char_info gcc_diag_char_table[] =
/* Custom conversion specifiers. */
- /* These will require a "tree" at runtime. */
+ /* G requires a "gcall*" argument at runtime. */
+ { "G", 1, STD_C89, { T89_G, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL },
+ /* K requires a "tree" argument at runtime. */
{ "K", 1, STD_C89, { T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL },
{ "r", 1, STD_C89, { T89_C, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "//cR", NULL },
@@ -718,6 +721,9 @@ static const format_char_info gcc_tdiag_char_table[] =
{ "E", 1, STD_C89, { T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q+", "", NULL },
{ "K", 1, STD_C89, { T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL },
+ /* G requires a "gcall*" argument at runtime. */
+ { "G", 1, STD_C89, { T89_G, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL },
+
{ "v", 0, STD_C89, { T89_I, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q#", "", NULL },
{ "r", 1, STD_C89, { T89_C, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "/cR", NULL },
@@ -747,6 +753,9 @@ static const format_char_info gcc_cdiag_char_table[] =
{ "E", 1, STD_C89, { T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q+", "", NULL },
{ "K", 1, STD_C89, { T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL },
+ /* G requires a "gcall*" argument at runtime. */
+ { "G", 1, STD_C89, { T89_G, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL },
+
{ "v", 0, STD_C89, { T89_I, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q#", "", NULL },
{ "r", 1, STD_C89, { T89_C, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "/cR", NULL },
@@ -777,6 +786,9 @@ static const format_char_info gcc_cxxdiag_char_table[] =
{ "K", 1, STD_C89,{ T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL },
{ "v", 0,STD_C89, { T89_I, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q#", "", NULL },
+ /* G requires a "gcall*" argument at runtime. */
+ { "G", 1, STD_C89,{ T89_G, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL },
+
/* These accept either an 'int' or an 'enum tree_code' (which is handled as an 'int'.) */
{ "CLOPQ",0,STD_C89, { T89_I, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q", "", NULL },
@@ -3834,6 +3846,29 @@ init_dynamic_diag_info (void)
local_tree_type_node = void_type_node;
}
+ /* Similar to the above but for gcall*. */
+ if (!local_gcall_ptr_node
+ || local_gcall_ptr_node == void_type_node)
+ {
+ if ((local_gcall_ptr_node = maybe_get_identifier ("gcall")))
+ {
+ local_gcall_ptr_node
+ = identifier_global_value (local_gcall_ptr_node);
+ if (local_gcall_ptr_node)
+ {
+ if (TREE_CODE (local_gcall_ptr_node) != TYPE_DECL)
+ {
+ error ("%<gcall%> is not defined as a type");
+ local_gcall_ptr_node = 0;
+ }
+ else
+ local_gcall_ptr_node = TREE_TYPE (local_gcall_ptr_node);
+ }
+ }
+ else
+ local_gcall_ptr_node = void_type_node;
+ }
+
static tree hwi;
if (!hwi)
diff --git a/gcc/c-family/c-format.h b/gcc/c-family/c-format.h
index 37fa382..05e9bb7 100644
--- a/gcc/c-family/c-format.h
+++ b/gcc/c-family/c-format.h
@@ -298,6 +298,7 @@ struct format_kind_info
#define T_UC &unsigned_char_type_node
#define T99_UC { STD_C99, NULL, T_UC }
#define T_V &void_type_node
+#define T89_G { STD_C89, NULL, &local_gcall_ptr_node }
#define T89_T { STD_C89, NULL, &local_tree_type_node }
#define T89_V { STD_C89, NULL, T_V }
#define T_W &wchar_type_node
diff --git a/gcc/testsuite/gcc.dg/format/gcc_diag-10.c b/gcc/testsuite/gcc.dg/format/gcc_diag-10.c
index b3be277..9bda73b 100644
--- a/gcc/testsuite/gcc.dg/format/gcc_diag-10.c
+++ b/gcc/testsuite/gcc.dg/format/gcc_diag-10.c
@@ -15,6 +15,9 @@ typedef struct location_s
union tree_node;
typedef union tree_node *tree;
+/* Define gcall as a dummy type. The typedef must be provided for
+ the C test to find the symbol. */
+typedef struct gcall gcall;
#define FORMAT(kind) __attribute__ ((format (__gcc_## kind ##__, 1, 2)))
@@ -23,12 +26,13 @@ void cdiag (const char*, ...) FORMAT (cdiag);
void tdiag (const char*, ...) FORMAT (tdiag);
void cxxdiag (const char*, ...) FORMAT (cxxdiag);
-void test_diag (tree t)
+void test_diag (tree t, gcall *gc)
{
diag ("%<"); /* { dg-warning "unterminated quoting directive" } */
diag ("%>"); /* { dg-warning "unmatched quoting directive " } */
diag ("%<foo%<bar%>%>"); /* { dg-warning "nested quoting directive" } */
+ diag ("%G", gc);
diag ("%K", t);
diag ("%R"); /* { dg-warning "unmatched color reset directive" } */
@@ -38,6 +42,7 @@ void test_diag (tree t)
diag ("%r%r%R", "", "");
diag ("%r%R%r%R", "", "");
+ diag ("%<%G%>", gc); /* { dg-warning ".G. conversion used within a quoted sequence" } */
diag ("%<%K%>", t); /* { dg-warning ".K. conversion used within a quoted sequence" } */
diag ("%<%R%>"); /* { dg-warning "unmatched color reset directive" } */
@@ -45,7 +50,7 @@ void test_diag (tree t)
diag ("%<%r%R%>", "");
}
-void test_cdiag (tree t)
+void test_cdiag (tree t, gcall *gc)
{
cdiag ("%<"); /* { dg-warning "unterminated quoting directive" } */
cdiag ("%>"); /* { dg-warning "unmatched quoting directive " } */
@@ -54,6 +59,7 @@ void test_cdiag (tree t)
cdiag ("%D", t); /* { dg-warning ".D. conversion used unquoted" } */
cdiag ("%E", t);
cdiag ("%F", t); /* { dg-warning ".F. conversion used unquoted" } */
+ cdiag ("%G", gc);
cdiag ("%K", t);
cdiag ("%R"); /* { dg-warning "unmatched color reset directive" } */
@@ -69,6 +75,7 @@ void test_cdiag (tree t)
cdiag ("%<%D%>", t);
cdiag ("%<%E%>", t);
cdiag ("%<%F%>", t);
+ cdiag ("%<%G%>", gc); /* { dg-warning ".G. conversion used within a quoted sequence" } */
cdiag ("%<%K%>", t); /* { dg-warning ".K. conversion used within a quoted sequence" } */
cdiag ("%<%R%>"); /* { dg-warning "unmatched color reset directive" } */
@@ -83,7 +90,7 @@ void test_cdiag (tree t)
cdiag ("%<%qT%>", t); /* { dg-warning ".q. flag used within a quoted sequence" } */
}
-void test_tdiag (tree t)
+void test_tdiag (tree t, gcall *gc)
{
tdiag ("%<"); /* { dg-warning "unterminated quoting directive" } */
tdiag ("%>"); /* { dg-warning "unmatched quoting directive " } */
@@ -91,6 +98,7 @@ void test_tdiag (tree t)
tdiag ("%D", t); /* { dg-warning ".D. conversion used unquoted" } */
tdiag ("%E", t);
+ tdiag ("%G", gc);
tdiag ("%K", t);
tdiag ("%R"); /* { dg-warning "unmatched color reset directive" } */
@@ -105,6 +113,7 @@ void test_tdiag (tree t)
tdiag ("%<%D%>", t);
tdiag ("%<%E%>", t);
+ tdiag ("%<%G%>", gc); /* { dg-warning ".G. conversion used within a quoted sequence" } */
tdiag ("%<%K%>", t); /* { dg-warning ".K. conversion used within a quoted sequence" } */
tdiag ("%<%R%>"); /* { dg-warning "unmatched color reset directive" } */
@@ -118,12 +127,14 @@ void test_tdiag (tree t)
tdiag ("%<%qT%>", t); /* { dg-warning ".q. flag used within a quoted sequence" } */
}
-void test_cxxdiag (tree t)
+void test_cxxdiag (tree t, gcall *gc)
{
cxxdiag ("%A", t); /* { dg-warning ".A. conversion used unquoted" } */
cxxdiag ("%D", t); /* { dg-warning ".D. conversion used unquoted" } */
cxxdiag ("%E", t);
cxxdiag ("%F", t); /* { dg-warning ".F. conversion used unquoted" } */
+ cxxdiag ("%G", gc);
+ cxxdiag ("%K", t);
cxxdiag ("%R"); /* { dg-warning "unmatched color reset directive" } */
cxxdiag ("%r", ""); /* { dg-warning "unterminated color directive" } */
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-02 16:59 ` Jeff Law
2017-08-06 20:07 ` [PATCH 3/4] " Martin Sebor
2017-08-06 20:07 ` [PATCH 4/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117) Martin Sebor
@ 2017-08-06 20:07 ` Martin Sebor
2017-08-09 19:21 ` Jeff Law
2017-08-06 20:07 ` [PATCH 2/4] " Martin Sebor
2017-08-06 20:07 ` [PATCH 1/4] " Martin Sebor
4 siblings, 1 reply; 39+ messages in thread
From: Martin Sebor @ 2017-08-06 20:07 UTC (permalink / raw)
To: Jeff Law, Gcc Patch List
On 08/02/2017 10:58 AM, Jeff Law wrote:
> On 07/31/2017 01:42 PM, Martin Sebor wrote:
>>> So I *think* TYPE_SIZE_UNIT isn't necessarily guaranteed to be a
>>> INTEGER_CST, it could be a non-constant expression for the size. Are
>>> the callers of compute_objsize prepared to handle that? Just to be
>>> clear, I'd prefer to return TYPE_SIZE_UNIT even if it's not an
>>> INTEGER_CST, I'm just worried about the callers ability to handle that
>>> correctly.
>>
>> They should be prepared for it. If not, it's a bug. I've added
>> a few more test cases though I'm not sure the case you're concerned
>> about actually arises (VLA sizes are represented as gimple calls to
>> __builtin_alloca_with_align so the code doesn't get this far).
> It may in the end be a non-issue because VLAs are still represented as
> alloca calls at this point. BUt it looks like the result is typically
> passed down into check_sizes which assumes the result is an INTEGER_CST
> based on a quick scan.
>
>
> So just to be future safe perhaps this:
>
> if (TREE_CODE (type) == ARRAY_TYPE
> && TREE_CODE (TYPE_SIZE_UNIT (type)) == INTEGER_CST)
> return TYPE_SIZE_UNIT (type);
Sure. I've added something close to the latest patch.
>>>> +@smallexample
>>>> +void append (char *d)
>>>> +@{
>>>> + strncat (d, ".txt", 4);
>>>> +@}
>>>> +@end smallexample
>>> Sorry. I don't follow this particular example. Where's the truncation
>>> when strlen (SRC) == N for strncat? In that case aren't we going to
>>> append SRC without the nul terminator, then nul terminate the result?
>>> Isn't that the same as appending SRC with its nul terminator? Am I
>>> missing something here?
>>
>> You're right that there is no truncation and the effect is
>> the same but only in the unlikely case when the destination
>> is empty. Otherwise the result is truncated.
> Maybe this is where I'm confused. How does the destination play into
> truncation issues? I've always been under the impression that the
> destination has to be large enough to hold the result, but that it
> doesn't affect truncation of the source.
No, you're right. It's me who's been confused, either thinking
of strncpy or -Wstringop-overflow. The difference between the
two warnings is just one byte in some cases and I got them mixed
up. Sorry about that and thanks for spotting this silly mistake!
I've updated the code to issue -Wstringop-overflow here and added
a better example to the manual.
>>> So we've got calls to check the arguments, but not optimize here. But
>>> the containing function is "strlen_optimize_stmt".
>>>
>>> Would it make sense to first call strlen_optimize_stmt to handle the
>>> optimization cases, then to call a new separate function to handle
>>> warnings for the strn* family?
>>
>> tree-ssa-strlen doesn't handle strncat or strncpy (for the latter
>> I'm tracking the enhancement in bug 81433). When the handling is
>> added I expect check_builtin_{strncat,stxncpy} will be renamed to
>> handle_builtin_{strncat,stxncpy} to match the existing functions,
>> and the optimization done there.
>>
>> Or did you have something else in mind and I missed it?
> So do you envision doing both optimization and checking together? If
> so, then I think we'd want to rename strlen_optimize_stmt.
I expect the checking and the optimization to be done either
in the same function, or by calling a new function from the
one that implements the optimization, so I changed the names
as you suggest.
I've also done some more testing with Binutils, GDB, and Glibc
and found a couple of reasonable use cases that the warning
shouldn't trigger on so I added more code to handle it as well.
1) In the following, the strncpy call would normally trigger
-Wstringop-truncation because of the possible missing terminating
NUL, but because the immediately following statement inserts the
NUL the call is safe.
strncpy (d, s, sizeof d); // possible missing NUL
d[sizeof d - 1] = '\0'; // okay, NUL add here
2) Building Glibc made me realize that in my effort to detect
the (common) misuses of strncpy I neglected the original (and
only intended but now rare) use case of filling a buffer
without necessarily NUL-terminating it (as in struct dirent::
d_name). To allow it the patch adds a new attribute that can
be used to annotate char arrays and pointers that are intended
not to be NUL-terminated. This suppresses the truncation
warning. When the patch is approved I'll propose the (trivial)
Glibc changes. In the future, the attribute will also let GCC
warn when passing such objects to functions that expect a NUL-
terminated string argument (e.g., strlen or strcpy).
3) Finally, to add inlining context to diagnostics issued by
the middle end, I've added a new %G directive to complement
%K by accepting a gcall* argument.
To make the patch easier to review I've broken it up into
four parts:
1. Add %G.
2. Add attribute nostring.
3. Implement -Wstringop-truncation and enhance -Wstringop-
overflow (the meat of the patch).
4. Fix up GCC to compile with the new and enhanced warnings.
Martin
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH 3/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-02 16:59 ` Jeff Law
@ 2017-08-06 20:07 ` Martin Sebor
2017-08-10 7:17 ` Jeff Law
` (2 more replies)
2017-08-06 20:07 ` [PATCH 4/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117) Martin Sebor
` (3 subsequent siblings)
4 siblings, 3 replies; 39+ messages in thread
From: Martin Sebor @ 2017-08-06 20:07 UTC (permalink / raw)
To: Jeff Law, Gcc Patch List
[-- Attachment #1: Type: text/plain, Size: 214 bytes --]
Part 3 of the series contains the meat of the patch: the new
-Wstringop-truncation option, and enhancements to -Wstringop-
overflow, and -Wpointer-sizeof-memaccess to detect misuses of
strncpy and strncat.
Martin
[-- Attachment #2: gcc-81117-3.diff --]
[-- Type: text/x-patch, Size: 69170 bytes --]
PR c/81117 - Improve buffer overflow checking in strncpy
gcc/ChangeLog:
PR c/81117
* builtins.c (compute_objsize): Handle arrays that
compute_builtin_object_size likes to fail for. Make extern.
* builtins.h (compute_objsize): Declare.
(check_strncpy_sizes): New function.
(expand_builtin_strncpy): Call check_strncpy_sizes.
* gimple-fold.c (gimple_fold_builtin_strncpy): Implement
-Wstringop-truncation.
(gimple_fold_builtin_strncat): Same.
* gimple.c (gimple_build_call_from_tree): Set call location.
* tree-ssa-strlen.c (strlen_to_stridx): New global variable.
(maybe_diag_bound_equal_length, is_strlen_related_p): New functions.
(handle_builtin_stxncpy, handle_builtin_strncat): Same.
(handle_builtin_strlen): Use strlen_to_stridx.
(strlen_optimize_stmt): Handle flavors of strncat, strncpy, and
stpncpy.
Use strlen_to_stridx.
(pass_strlen::execute): Release strlen_to_stridx.
* doc/invoke.texi (-Wsizeof-pointer-memaccess): Document enhancement.
(-Wstringop-truncation): Document new option.
gcc/c-family/ChangeLog:
PR c/81117
* c-warn.c (sizeof_pointer_memaccess_warning): Handle arrays.
* c.opt (-Wstriingop-truncation): New option.
gcc/testsuite/ChangeLog:
PR c/81117
* c-c++-common/Wsizeof-pointer-memaccess3.c: New test.
* c-c++-common/Wstringop-overflow.c: Same.
* c-c++-common/Wstringop-truncation.c: Same.
* c-c++-common/Wsizeof-pointer-memaccess2.c: Adjust.
* c-c++-common/attr-nonstring-2.c: New test.
* g++.dg/torture/Wsizeof-pointer-memaccess1.C: Adjust.
* g++.dg/torture/Wsizeof-pointer-memaccess2.C: Same.
* gcc.dg/torture/pr63554.c: Same.
* gcc.dg/Walloca-1.c: Disable macro tracking.
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 016f68d..1aa9e22 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -3220,18 +3220,55 @@ check_sizes (int opt, tree exp, tree size, tree maxlen, tree src, tree objsize)
}
/* Helper to compute the size of the object referenced by the DEST
- expression which must of of pointer type, using Object Size type
+ expression which must have pointer type, using Object Size type
OSTYPE (only the least significant 2 bits are used). Return
the size of the object if successful or NULL when the size cannot
be determined. */
-static inline tree
+tree
compute_objsize (tree dest, int ostype)
{
unsigned HOST_WIDE_INT size;
- if (compute_builtin_object_size (dest, ostype & 3, &size))
+
+ /* Only the two least significant bits are meaningful. */
+ ostype &= 3;
+
+ if (compute_builtin_object_size (dest, ostype, &size))
return build_int_cst (sizetype, size);
+ /* Unless computing the largest size (for memcpy and other raw memory
+ functions), try to determine the size of the object from its type. */
+ if (!ostype)
+ return NULL_TREE;
+
+ if (TREE_CODE (dest) == SSA_NAME)
+ {
+ gimple *stmt = SSA_NAME_DEF_STMT (dest);
+ if (!is_gimple_assign (stmt))
+ return NULL_TREE;
+
+ dest = gimple_assign_rhs1 (stmt);
+ }
+
+ if (TREE_CODE (dest) != ADDR_EXPR)
+ return NULL_TREE;
+
+ tree type = TREE_TYPE (dest);
+ if (TREE_CODE (type) == POINTER_TYPE)
+ type = TREE_TYPE (type);
+
+ type = TYPE_MAIN_VARIANT (type);
+
+ if (TREE_CODE (type) == ARRAY_TYPE)
+ {
+ /* Return the constant size unless it's zero (that's a zero-length
+ array likely at the end of a struct). */
+ tree size = TYPE_SIZE_UNIT (type);
+ if (size && TREE_CODE (size) == INTEGER_CST
+ && !integer_zerop (size))
+ return size;
+ }
+
return NULL_TREE;
}
@@ -3883,6 +3920,30 @@ expand_builtin_strncat (tree exp, rtx)
return NULL_RTX;
}
+/* Helper to check the sizes of sequences and the destination of calls
+ to __builtin_strncpy (DST, SRC, LEN) and __builtin___strncpy_chk.
+ Returns true on success (no overflow warning), false otherwise. */
+
+static bool
+check_strncpy_sizes (tree exp, tree dst, tree src, tree len)
+{
+ tree dstsize = compute_objsize (dst, warn_stringop_overflow - 1);
+
+ if (!check_sizes (OPT_Wstringop_overflow_,
+ exp, len, /*maxlen=*/NULL_TREE, src, dstsize))
+ return false;
+
+ if (!dstsize || TREE_CODE (len) != INTEGER_CST)
+ return true;
+
+ if (tree_int_cst_lt (dstsize, len))
+ warning_at (EXPR_LOCATION (exp), OPT_Wstringop_truncation,
+ "%K%qD specified bound %E exceeds destination size %E",
+ exp, get_callee_fndecl (exp), len, dstsize);
+
+ return true;
+}
+
/* Expand expression EXP, which is a call to the strncpy builtin. Return
NULL_RTX if we failed the caller should emit a normal call. */
@@ -3901,16 +3962,7 @@ expand_builtin_strncpy (tree exp, rtx target)
/* The length of the source sequence. */
tree slen = c_strlen (src, 1);
- if (warn_stringop_overflow)
- {
- tree destsize = compute_objsize (dest,
- warn_stringop_overflow - 1);
-
- /* The number of bytes to write is LEN but check_sizes will also
- check SLEN if LEN's value isn't known. */
- check_sizes (OPT_Wstringop_overflow_,
- exp, len, /*maxlen=*/NULL_TREE, src, destsize);
- }
+ check_strncpy_sizes (exp, dest, src, len);
/* We must be passed a constant len and src parameter. */
if (!tree_fits_uhwi_p (len) || !slen || !tree_fits_uhwi_p (slen))
diff --git a/gcc/builtins.h b/gcc/builtins.h
index f590f61..4d93eaf 100644
--- a/gcc/builtins.h
+++ b/gcc/builtins.h
@@ -89,6 +89,7 @@ extern tree fold_call_stmt (gcall *, bool);
extern void set_builtin_user_assembler_name (tree decl, const char *asmspec);
extern bool is_simple_builtin (tree);
extern bool is_inexpensive_builtin (tree);
+extern tree compute_objsize (tree, int);
extern bool readonly_data_expr (tree exp);
extern bool init_target_chars (void);
diff --git a/gcc/c-family/c-warn.c b/gcc/c-family/c-warn.c
index e970ab2..b8eb616 100644
--- a/gcc/c-family/c-warn.c
+++ b/gcc/c-family/c-warn.c
@@ -626,7 +626,8 @@ sizeof_pointer_memaccess_warning (location_t *sizeof_arg_loc, tree callee,
|| vec_safe_length (params) <= 1)
return;
- switch (DECL_FUNCTION_CODE (callee))
+ enum built_in_function fncode = DECL_FUNCTION_CODE (callee);
+ switch (fncode)
{
case BUILT_IN_STRNCMP:
case BUILT_IN_STRNCASECMP:
@@ -708,8 +709,27 @@ sizeof_pointer_memaccess_warning (location_t *sizeof_arg_loc, tree callee,
type = TYPE_P (sizeof_arg[idx])
? sizeof_arg[idx] : TREE_TYPE (sizeof_arg[idx]);
+
if (!POINTER_TYPE_P (type))
- return;
+ {
+ /* The argument type may be an array. Diagnose bounded string
+ copy functions that specify the bound in terms of the source
+ argument rather than the destination. */
+ if (strop && !cmp && fncode != BUILT_IN_STRNDUP && src)
+ {
+ tem = tree_strip_nop_conversions (src);
+ if (TREE_CODE (tem) == ADDR_EXPR)
+ tem = TREE_OPERAND (tem, 0);
+ if (operand_equal_p (tem, sizeof_arg[idx], OEP_ADDRESS_OF))
+ warning_at (sizeof_arg_loc[idx], OPT_Wsizeof_pointer_memaccess,
+ "argument to %<sizeof%> in %qD call is the same "
+ "expression as the source; did you mean to use "
+ "the size of the destination?",
+ callee);
+ }
+
+ return;
+ }
if (dest
&& (tem = tree_strip_nop_conversions (dest))
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index e0ad3ab..5caaf43 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -736,6 +736,10 @@ C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_stringop_overflow) Ini
Under the control of Object Size type, warn about buffer overflow in string
manipulation functions like memcpy and strcpy.
+Wstringop-truncation
+C ObjC C++ ObjC++ Var(warn_stringop_truncation) Warning Init (1) LangEnabledBy(C ObjC C++ ObjC++, Wall)
+Warn about truncation in string manipulation functions like strncat and strncpy.
+
Wsuggest-attribute=format
C ObjC C++ ObjC++ Var(warn_suggest_attribute_format) Warning
Warn about functions which might be candidates for format attributes.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 5ae9dc4..7370881a 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -312,7 +312,7 @@ Objective-C and Objective-C++ Dialects}.
-Wsizeof-pointer-memaccess -Wsizeof-array-argument @gol
-Wstack-protector -Wstack-usage=@var{len} -Wstrict-aliasing @gol
-Wstrict-aliasing=n -Wstrict-overflow -Wstrict-overflow=@var{n} @gol
--Wstringop-overflow=@var{n} @gol
+-Wstringop-overflow=@var{n} -Wstringop-truncation @gol
-Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{|}format@r{]} @gol
-Wsuggest-final-types @gol -Wsuggest-final-methods -Wsuggest-override @gol
-Wmissing-format-attribute -Wsubobject-linkage @gol
@@ -5168,6 +5168,56 @@ whether to issue a warning. Similarly to @option{-Wstringop-overflow=3} this
setting of the option may result in warnings for benign code.
@end table
+@item -Wstringop-truncation
+@opindex Wstringop-truncation
+@opindex Wno-stringop-truncation
+Warn for calls to bounded string manipulation functions such as @code{strncat},
+@code{strncpy}, and @code{stpncpy} that may either truncate the copied string
+or leave the destination unchanged.
+
+In the following example, the call to @code{strncat} specifies a bound that
+is less than the length of the source string. As a result, the copy of
+the source will be truncated and so the call is diagnosed. To avoid the
+warning use @code{bufsize - strlen (buf) - 1)} as the bound.
+
+@smallexample
+void append (char *buf, size_t bufsize)
+@{
+ strncat (buf, ".txt", 3);
+@}
+@end smallexample
+
+As another example, the following call to @code{strncpy} results in copying
+to @code{d} just the characters preceding the terminating NUL, without
+appending the NUL to the end. Assuming the result of @code{strncpy} is
+necessarily a NUL-terminated string is a common mistake, and so the call
+is diagnosed. To avoid the warning when the result is not expected to be
+NUL-terminated, call @code{memcpy} instead.
+
+@smallexample
+void copy (char *d, const char *s)
+@{
+ strncpy (d, s, strlen (s));
+@}
+@end smallexample
+
+In the following example, the call to @code{strncpy} specifies the size
+of the destination buffer as the bound. If the length of the source
+string is equal to or greater than this size the result of the copy will
+not be NUL-terminated. Therefore, the call is also diagnosed. To avoid
+the warning, specify @code{sizeof buf - 1} as the bound and set the last
+element of the buffer to @code{NUL}.
+
+@smallexample
+void copy (const char *s)
+@{
+ char buf[80];
+ strncpy (buf, s, sizeof buf);
+ @dots{}
+@}
+@end smallexample
+
+
@item -Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{|}format@r{]}
@opindex Wsuggest-attribute=
@opindex Wno-suggest-attribute=
@@ -6152,11 +6202,26 @@ not an array, but a pointer. This warning is enabled by @option{-Wall}.
@opindex Wsizeof-pointer-memaccess
@opindex Wno-sizeof-pointer-memaccess
Warn for suspicious length parameters to certain string and memory built-in
-functions if the argument uses @code{sizeof}. This warning warns e.g.@:
-about @code{memset (ptr, 0, sizeof (ptr));} if @code{ptr} is not an array,
-but a pointer, and suggests a possible fix, or about
-@code{memcpy (&foo, ptr, sizeof (&foo));}. This warning is enabled by
-@option{-Wall}.
+functions if the argument uses @code{sizeof}. This warning triggers for
+example for @code{memset (ptr, 0, sizeof (ptr));} if @code{ptr} is not
+an array, but a pointer, and suggests a possible fix, or about
+@code{memcpy (&foo, ptr, sizeof (&foo));}. @option{-Wsizeof-pointer-memaccess}
+also warns about calls to bounded string copy functions like @code{strncat}
+or @code{strncpy} that specify as the bound a @code{sizeof} expression of
+the source array. For example, in the following function the call to
+@code{strncat} specifies the size of the source string as the bound. That
+is almost certainly a mistake and so the call is diagnosed.
+@smallexample
+void make_file (const char *name)
+@{
+ char path[PATH_MAX];
+ strncpy (path, name, sizeof path - 1);
+ strncat (path, ".text", sizeof ".text");
+ @dots{}
+@}
+@end smallexample
+
+The @option{-Wsizeof-pointer-memaccess} option is enabled by @option{-Wall}.
@item -Wsizeof-array-argument
@opindex Wsizeof-array-argument
diff --git a/gcc/gimple-fold.c b/gcc/gimple-fold.c
index d82d060..2be2503 100644
--- a/gcc/gimple-fold.c
+++ b/gcc/gimple-fold.c
@@ -40,6 +40,7 @@ along with GCC; see the file COPYING3. If not see
#include "gimple-iterator.h"
#include "tree-into-ssa.h"
#include "tree-dfa.h"
+#include "tree-object-size.h"
#include "tree-ssa.h"
#include "tree-ssa-propagate.h"
#include "ipa-utils.h"
@@ -57,6 +58,8 @@ along with GCC; see the file COPYING3. If not see
#include "tree-cfg.h"
#include "fold-const-call.h"
#include "asan.h"
+#include "diagnostic-core.h"
+#include "intl.h"
/* Return true when DECL can be referenced from current unit.
FROM_DECL (if non-null) specify constructor of variable DECL was taken from.
@@ -1522,12 +1525,28 @@ static bool
gimple_fold_builtin_strncpy (gimple_stmt_iterator *gsi,
tree dest, tree src, tree len)
{
- location_t loc = gimple_location (gsi_stmt (*gsi));
- tree fn;
+ gimple *stmt = gsi_stmt (*gsi);
+ location_t loc = gimple_location (stmt);
/* If the LEN parameter is zero, return DEST. */
if (integer_zerop (len))
{
+ tree fndecl = gimple_call_fndecl (stmt);
+ gcall *call = as_a <gcall *> (stmt);
+
+ /* Warn about the lack of nul termination: the result is not
+ a (nul-terminated) string. */
+ tree slen = get_maxval_strlen (src, 0);
+ if (slen && !integer_zerop (slen))
+ warning_at (loc, OPT_Wstringop_truncation,
+ "%G%qD destination unchanged after copying no bytes "
+ "from a string of length %E",
+ call, fndecl, slen);
+ else
+ warning_at (loc, OPT_Wstringop_truncation,
+ "%G%qD destination unchanged after copying no bytes",
+ call, fndecl);
+
replace_call_with_value (gsi, dest);
return true;
}
@@ -1542,16 +1561,66 @@ gimple_fold_builtin_strncpy (gimple_stmt_iterator *gsi,
if (!slen || TREE_CODE (slen) != INTEGER_CST)
return false;
- slen = size_binop_loc (loc, PLUS_EXPR, slen, ssize_int (1));
+ /* The size of the source string including the terminating nul. */
+ tree ssize = size_binop_loc (loc, PLUS_EXPR, slen, ssize_int (1));
/* We do not support simplification of this case, though we do
support it when expanding trees into RTL. */
/* FIXME: generate a call to __builtin_memset. */
- if (tree_int_cst_lt (slen, len))
+ if (tree_int_cst_lt (ssize, len))
return false;
+ if (tree_int_cst_lt (len, slen))
+ {
+ tree fndecl = gimple_call_fndecl (stmt);
+ gcall *call = as_a <gcall *> (stmt);
+
+ warning_at (loc, OPT_Wstringop_truncation,
+ (tree_int_cst_equal (size_one_node, len)
+ ? G_("%G%qD output truncated copying %E byte "
+ "from a string of length %E")
+ : G_("%G%qD output truncated copying %E bytes "
+ "from a string of length %E")),
+ call, fndecl, len, slen);
+ }
+ else if (tree_int_cst_equal (len, slen))
+ {
+ tree decl = dest;
+ if (TREE_CODE (decl) == SSA_NAME)
+ {
+ gimple *def_stmt = SSA_NAME_DEF_STMT (decl);
+ if (is_gimple_assign (def_stmt))
+ {
+ tree_code code = gimple_assign_rhs_code (def_stmt);
+ if (code == ADDR_EXPR || code == VAR_DECL)
+ decl = gimple_assign_rhs1 (def_stmt);
+ }
+ }
+
+ if (TREE_CODE (decl) == ADDR_EXPR)
+ decl = TREE_OPERAND (decl, 0);
+
+ if (TREE_CODE (decl) == COMPONENT_REF)
+ decl = TREE_OPERAND (decl, 1);
+
+ tree fndecl = gimple_call_fndecl (stmt);
+ gcall *call = as_a <gcall *> (stmt);
+
+ if (!DECL_P (decl)
+ || !lookup_attribute ("nonstring", DECL_ATTRIBUTES (decl)))
+ warning_at (loc, OPT_Wstringop_truncation,
+ (tree_int_cst_equal (size_one_node, len)
+ ? G_("%G%qD output truncated before terminating nul "
+ "copying %E byte from a string of the same "
+ "length")
+ : G_("%G%qD output truncated before terminating nul "
+ "copying %E bytes from a string of the same "
+ "length")),
+ call, fndecl, len);
+ }
+
/* OK transform into builtin memcpy. */
- fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
+ tree fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
if (!fn)
return false;
@@ -1560,6 +1629,7 @@ gimple_fold_builtin_strncpy (gimple_stmt_iterator *gsi,
NULL_TREE, true, GSI_SAME_STMT);
gimple *repl = gimple_build_call (fn, 3, dest, src, len);
replace_call_with_call_and_fold (gsi, repl);
+
return true;
}
@@ -1856,21 +1926,77 @@ gimple_fold_builtin_strncat (gimple_stmt_iterator *gsi)
return true;
}
- /* If the requested len is greater than or equal to the string
- length, call strcat. */
- if (TREE_CODE (len) == INTEGER_CST && p
- && compare_tree_int (len, strlen (p)) >= 0)
+ if (TREE_CODE (len) == INTEGER_CST && p)
{
- tree fn = builtin_decl_implicit (BUILT_IN_STRCAT);
+ unsigned srclen = strlen (p);
- /* If the replacement _DECL isn't initialized, don't do the
- transformation. */
- if (!fn)
- return false;
+ int cmpsrc = compare_tree_int (len, srclen);
- gcall *repl = gimple_build_call (fn, 2, dst, src);
- replace_call_with_call_and_fold (gsi, repl);
- return true;
+ unsigned HOST_WIDE_INT dstsize;
+
+ bool nowarn = gimple_no_warning_p (stmt);
+
+ if (!nowarn && compute_builtin_object_size (dst, 1, &dstsize))
+ {
+ int cmpdst = compare_tree_int (len, dstsize);
+
+ if (cmpdst >= 0)
+ {
+ tree fndecl = gimple_call_fndecl (stmt);
+
+ /* Strncat copies (at most) LEN bytes and always appends
+ the terminating NUL so the specified bound should never
+ be equal to (or greater than) the size of the destination.
+ If it is, the copy could overflow. */
+ location_t loc = gimple_location (stmt);
+ nowarn = warning_at (loc, OPT_Wstringop_overflow_,
+ cmpdst == 0
+ ? G_("%G%qD specified bound %E equals "
+ "destination size")
+ : G_("%G%qD specified bound %E exceeds "
+ "destination size %wu"),
+ stmt, fndecl, len, dstsize);
+ if (nowarn)
+ gimple_set_no_warning (stmt, true);
+ }
+ }
+
+ if (!nowarn && cmpsrc <= 0)
+ {
+ tree fndecl = gimple_call_fndecl (stmt);
+
+ /* To avoid certain truncation the specified bound should also
+ not be equal to (or less than) the length of the source. */
+ location_t loc = gimple_location (stmt);
+ bool warn;
+ if (cmpsrc == 0)
+ warn = warning_at (loc, OPT_Wstringop_overflow_,
+ "%G%qD specified bound %E equals source length",
+ stmt, fndecl, len);
+ else
+ warn = warning_at (loc, OPT_Wstringop_truncation,
+ "%G%qD specified bound %E is less than source "
+ "length %u",
+ stmt, fndecl, len, srclen);
+ if (warn)
+ gimple_set_no_warning (stmt, true);
+ }
+
+ /* If the requested len is greater than or equal to the string
+ length, call strcat. */
+ if (cmpsrc >= 0)
+ {
+ tree fn = builtin_decl_implicit (BUILT_IN_STRCAT);
+
+ /* If the replacement _DECL isn't initialized, don't do the
+ transformation. */
+ if (!fn)
+ return false;
+
+ gcall *repl = gimple_build_call (fn, 2, dst, src);
+ replace_call_with_call_and_fold (gsi, repl);
+ return true;
+ }
}
return false;
diff --git a/gcc/gimple.c b/gcc/gimple.c
index 3a32b53..e000cb5 100644
--- a/gcc/gimple.c
+++ b/gcc/gimple.c
@@ -359,6 +359,7 @@ gimple_build_call_from_tree (tree t)
gimple_call_set_arg (call, i, CALL_EXPR_ARG (t, i));
gimple_set_block (call, TREE_BLOCK (t));
+ gimple_set_location (call, EXPR_LOCATION (t));
/* Carry all the CALL_EXPR flags to the new GIMPLE_CALL. */
gimple_call_set_chain (call, CALL_EXPR_STATIC_CHAIN (t));
diff --git a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c
index 895a50e..f7bfa35 100644
--- a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c
+++ b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c
@@ -1,7 +1,7 @@
/* Test -Wsizeof-pointer-memaccess warnings. */
/* { dg-do compile } */
-/* { dg-options "-Wall -O2 -Wno-sizeof-array-argument -ftrack-macro-expansion=0" } */
-/* { dg-options "-Wall -O2 -Wno-sizeof-array-argument -Wno-c++-compat -ftrack-macro-expansion=0" {target c} } */
+/* { dg-options "-Wall -O2 -Wno-sizeof-array-argument -Wno-stringop-truncation -ftrack-macro-expansion=0" } */
+/* { dg-options "-Wall -O2 -Wno-sizeof-array-argument -Wno-stringop-truncation -Wno-c++-compat -ftrack-macro-expansion=0" {target c} } */
/* { dg-require-effective-target alloca } */
#define bos(ptr) __builtin_object_size (ptr, 1)
@@ -473,12 +473,15 @@ f4 (char *x, char **y, int z, char w[64])
strncat (w, s2, sizeof (w)); /* { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" } */
stpncpy (w, s1, sizeof (w)); /* { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" } */
- /* These are correct, no warning. */
+ /* These are pointless when the destination is large enough, and
+ cause overflow otherwise. If the copies are guaranteed to be
+ safe the calls might as well be replaced by strcat(), strcpy(),
+ or memcpy(). */
const char s3[] = "foobarbaz";
const char s4[] = "abcde12345678";
- strncpy (x, s3, sizeof (s3));
- strncat (x, s4, sizeof (s4));
- stpncpy (x, s3, sizeof (s3));
+ strncpy (x, s3, sizeof (s3)); /* { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" } */
+ strncat (x, s4, sizeof (s4)); /* { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" } */
+ stpncpy (x, s3, sizeof (s3)); /* { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" } */
}
/* { dg-prune-output "\[\n\r\]*writing\[\n\r\]*" } */
diff --git a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess3.c b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess3.c
new file mode 100644
index 0000000..97598c4
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess3.c
@@ -0,0 +1,132 @@
+/* Test -Wsizeof-pointer-memaccess warnings. */
+/* { dg-do compile } */
+/* { dg-options "-Wsizeof-pointer-memaccess -Wno-stringop-overflow -Wno-stringop-truncation -ftrack-macro-expansion=0" } */
+
+#define bos(ptr) __builtin_object_size (ptr, 1)
+#define bos0(ptr) __builtin_object_size (ptr, 0)
+
+#define memset(dst, val, sz) \
+ (FUNC (memset, dst, val, sz, bos (dst)), sink ((dst)))
+
+#define memcpy(dst, src, sz) \
+ (FUNC (memcpy, dst, src, sz, bos (dst)), sink ((dst)))
+
+#define memmove(dst, src, sz) \
+ (FUNC (memmove, dst, src, sz, bos (dst)), sink ((dst)))
+
+#define mempcpy(dst, src, sz) \
+ (FUNC (mempcpy, dst, src, sz, bos (dst)), sink ((dst)))
+
+#define strncpy(dst, src, sz) \
+ (FUNC (strncpy, dst, src, sz, bos (dst)), sink (dst))
+
+#define strncat(dst, src, sz) \
+ (FUNC (strncat, dst, src, sz, bos (dst)), sink (dst))
+
+#define stpncpy(dst, src, sz) \
+ (FUNC (stpncpy, dst, src, sz, bos (dst)), sink (dst))
+
+void sink (void*);
+
+#define S10 "123456789"
+extern char a10[10];
+
+void test_string_literal (char *dst)
+{
+#define FUNC(f, d, s, n, x) __builtin_ ## f (d, s, n)
+
+ /* It's common to call memcpy and other raw memory functions with
+ size drerived from the source argument. Verify that no warning
+ is ussued for such calls. */
+ memcpy (dst, S10, sizeof S10);
+ mempcpy (dst, S10, sizeof S10);
+ memmove (dst, S10, sizeof S10);
+
+ memset (dst, 0, sizeof S10);
+
+ stpncpy (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncpy (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncat (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ /* Unlike in the cases above, even though the calls below are likely
+ wrong, it's not easy to detect that the expression (sizeof X - 1)
+ involves sizeof of the source, so no warning is issued here, as
+ helpful as one might be. Whether -Wstringop-truncation is issued
+ is tested elsewhere. */
+ stpncpy (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncpy (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncat (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+}
+
+
+void test_char_array (char *dst)
+{
+ memcpy (dst, a10, sizeof a10);
+ mempcpy (dst, a10, sizeof a10);
+ memmove (dst, a10, sizeof a10);
+
+ memset (dst, 0, sizeof a10);
+
+ stpncpy (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncpy (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncat (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ stpncpy (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncpy (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncat (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+}
+
+
+#undef FUNC
+#define FUNC(f, d, s, n, os) __builtin___ ## f ## _chk (d, s, n, os)
+
+void test_char_array_chk (char *dst)
+{
+ memcpy (dst, S10, sizeof S10);
+ mempcpy (dst, S10, sizeof S10);
+ memmove (dst, S10, sizeof S10);
+
+ memset (dst, 0, sizeof S10);
+
+ stpncpy (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncpy (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncat (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ stpncpy (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncpy (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncat (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+}
+
+
+void test_string_literal_chk (char *dst)
+{
+ memcpy (dst, a10, sizeof a10);
+ mempcpy (dst, a10, sizeof a10);
+ memmove (dst, a10, sizeof a10);
+
+ memset (dst, 0, sizeof a10);
+
+ stpncpy (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncpy (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncat (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ stpncpy (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncpy (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncat (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+}
diff --git a/gcc/testsuite/c-c++-common/Wstringop-overflow.c b/gcc/testsuite/c-c++-common/Wstringop-overflow.c
new file mode 100644
index 0000000..53f5166
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wstringop-overflow.c
@@ -0,0 +1,158 @@
+/* PR middle-end/81117 - Improve buffer overflow checking in strncpy
+ { dg-do compile }
+ { dg-options "-O2 -Wstringop-overflow -Wno-stringop-truncation -ftrack-macro-expansion=0" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+#if __cplusplus
+extern "C" {
+#endif
+
+size_t strlen (const char*);
+char* strncat (char*, const char*, size_t);
+char* strncpy (char*, const char*, size_t);
+#if __cplusplus
+}
+#endif
+
+const char ar[] = "123";
+
+void test_strncat (char **d, const char* s, int i)
+{
+ /* Use a fresh pointer for each test to prevent the optimizer from
+ eliminating redundant writes into the same destination. Avoid
+ calling functions like sink() on the result that would have to
+ be assumed to change the source string by the alias oracle. */
+#define T(d, s, len) strncat (*d++, (s), (len))
+
+ T (d, "", 0);
+ T (d, "", 1);
+ T (d, "", 2);
+ T (d, "", 3);
+ T (d, "123", 0);
+ /* The following two calls truncate the copy and are diagnosed
+ by -Wstringop-truncation but there is evidence of overflow so
+ they're not diagnosed by -Wstringop-overflow. */
+ T (d, "123", 1);
+ T (d, "123", 2);
+
+ T (d, "123", 3); /* { dg-warning ".strncat\[^\n\r\]* specified bound 3 equals source length" } */
+ T (d, "123", 4);
+ T (d, "123", 9);
+
+ T (d, s, strlen (s)); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ T (d, s, strlen (s) + 1); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ /* The following could also be diagnosed by -Wstringop-truncation
+ (with some effort to distinguish the pattern from others like
+ the one above. */
+ T (d, s, strlen (s) - 1); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ T (d, s, strlen (s) - i); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+
+ /* The following is dubious but not necessarily a smoking gun. */
+ T (d, s, strlen (s) - strlen (s));
+
+ {
+ signed char n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ short n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ int n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ unsigned n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s) - 1; /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-message ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ /* This doesn't overflow so iit should not be diagnosed. */
+ size_t n = strlen (s) - strlen (s);
+ T (d, s, n);
+ }
+
+ {
+ size_t n = i < strlen (s) ? i : strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-message ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+}
+
+
+void test_strncpy (char **d, const char* s, int i)
+{
+#undef T
+#define T(d, s, len) strncpy (*d++, (s), (len))
+
+ T (d, "", 0);
+ T (d, "", 1);
+ T (d, "", 2);
+ T (d, "", 3);
+ T (d, "123", 0);
+ T (d, "123", 1);
+ T (d, "123", 2);
+ T (d, "123", 3);
+ T (d, "123", 4);
+ T (d, "123", 9);
+
+ T (d, "123", sizeof "123");
+ T (d, ar, sizeof ar);
+
+ T (d, s, strlen (s)); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+
+ {
+ int n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ }
+
+ {
+ unsigned n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s) - 1; /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ }
+
+ {
+ /* This is diagnosed by -Wstringop-truncation. Verify that it isn't
+ also diagnosed by -Wstringop-overflow. */
+ size_t n = strlen (s) - strlen (s);
+ T (d, s, n);
+ }
+
+ {
+ /* This use of strncpy is certainly dubious and it could well be
+ diagnosed by -Wstringop-truncation but it isn't. That it is
+ diagnosed with -Wstringop-overflow is more by accident than
+ by design. -Wstringop-overflow considers any dependency of
+ the bound on strlen(s) a potential bug. */
+ size_t n = i < strlen (s) ? i : strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-message ".strncpy\[^\n\r]* specified bound depends on the length of the source argument" } */
+ }
+}
diff --git a/gcc/testsuite/c-c++-common/Wstringop-truncation.c b/gcc/testsuite/c-c++-common/Wstringop-truncation.c
new file mode 100644
index 0000000..8c6904f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wstringop-truncation.c
@@ -0,0 +1,378 @@
+/* PR middle-end/81117 - Improve buffer overflow checking in strncpy
+ { dg-do compile }
+ { dg-options "-O2 -Wstringop-truncation -Wno-stringop-overflow -ftrack-macro-expansion=0" } */
+
+
+typedef __SIZE_TYPE__ size_t;
+
+#if __cplusplus
+extern "C" {
+#endif
+
+size_t strlen (const char*);
+char* strncat (char*, const char*, size_t);
+char* strncpy (char*, const char*, size_t);
+
+#if __cplusplus
+}
+#endif
+
+void sink (void*);
+
+#define S4 "123"
+const char a4[] = "123";
+
+
+typedef struct Dest
+{
+ char a5[5];
+ char b7[7];
+ char c3ns[3] __attribute__ ((nonstring));
+} Dest;
+
+char dst7[7];
+char dst2_5[2][5];
+
+/* Verify strncat warnings for arrays of known bounds. */
+
+void test_strncat_array (Dest *pd)
+{
+#undef T
+#define T(d, s, len) (strncat ((d), (s), (len)), sink (d))
+
+ T (dst7, S4, 2); /* { dg-warning "specified bound 2 is less than source length 3" } */
+
+ T (dst7, a4, 1); /* { dg-warning "specified bound 1 is less than source length 3" } */
+
+ /* There is no truncation here but possible overflow so these
+ are diagnosed by -Wstringop-overflow:
+ T (dst7, S4, 3);
+ T (dst7, a4, 3);
+ */
+
+ T (pd->a5, S4, 2); /* { dg-warning "specified bound 2 is less than source length" } */
+ T (pd->a5, S4, 1); /* { dg-warning "specified bound 1 is less than source length 3" } */
+}
+
+/* Verify strncat warnings for arrays of unknown bounds. */
+
+void test_strncat_vla (char *d, unsigned n)
+{
+ T (d, S4, 2); /* { dg-warning "specified bound 2 is less than source length 3" } */
+ T (d, S4, 4);
+
+ T (d, a4, 2); /* { dg-warning "specified bound 2 is less than source length 3" } */
+
+ /* There is no truncation here but possible overflow so these
+ are diagnosed by -Wstringop-overflow:
+ T (d, S4, 3);
+ T (d, a4, 3);
+ */
+ T (d, a4, 4);
+
+ char vla[n];
+
+ T (vla, S4, 2); /* { dg-warning "specified bound 2 is less than source length 3" } */
+
+ T (vla, S4, 4);
+ T (vla, S4, n);
+
+ T (vla, a4, 2); /* { dg-warning "specified bound 2 is less than source length 3" } */
+
+ T (vla, a4, 4);
+ T (vla, a4, n);
+
+ T (d, vla, 1);
+ T (d, vla, 3);
+ T (d, vla, 4);
+ T (d, vla, n);
+
+ /* There is no truncation here but possible overflow so these
+ are diagnosed by -Wstringop-overflow:
+ T (vla, S4, 3);
+ T (vla, a4, 3);
+ */
+}
+
+/* Verify strncpy warnings for pointers to unknown strings. */
+
+void test_strncpy_ptr (char *d, const char* s, int i)
+{
+#undef T
+#define T(d, s, len) (strncpy ((d), (s), (len)), sink (d))
+
+ /* Strncpy doesn't nul-terminate so the following is diagnosed. */
+ T (d, "", 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
+
+ /* This is safe. */
+ T (d, "", 1);
+ T (d, "", 2);
+
+ /* Truncation. */
+ T (d, "123", 3); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ T (d, "123", 4);
+ T (d, "123", 9);
+
+ T (d, S4, sizeof S4); /* Covered by -Wsizeof-pointer-memaccess. */
+ T (d, S4, sizeof S4 - 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+
+ T (d, a4, sizeof a4); /* Covered by -Wsizeof-pointer-memaccess. */
+ T (d, a4, sizeof a4 - 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ T (d, a4, sizeof a4 - 3); /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 1 byte from a string of length 3" } */
+ T (d, a4, sizeof a4 - 4); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes from a string of length 3" } */
+
+ T (d, S4, strlen (S4)); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ /* Likely buggy but no truncation. Diagnosed by -Wstringop-overflow. */
+ T (d, a4, strlen (a4) + 1);
+ T (d, S4, strlen (S4) + i);
+
+ T (d, a4, strlen (a4)); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ /* As above, buggy but no evidence of truncation. */
+ T (d, S4, strlen (S4) + 1);
+
+ {
+ signed char n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ short n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ int n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ unsigned n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ size_t n;
+ char *dp2 = d + 1;
+ n = strlen (s); /* { dg-message "length computed here" } */
+ T (dp2, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ /* The following is likely buggy but there's no apparent truncation
+ so it's not diagnosed by -Wstringop-truncation. Instead, it is
+ diagnosed by -Wstringop-overflow (tested elsewhere). */
+ int n;
+ n = strlen (s) - 1;
+ T (d, s, n);
+ }
+
+ {
+ /* Same as above. */
+ size_t n;
+ n = strlen (s) - 1;
+ T (d, s, n);
+ }
+
+ {
+ size_t n = strlen (s) - strlen (s);
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
+ }
+
+ {
+ /* This use of strncpy is dubious but it's probably not worth
+ worrying about (truncation may not actually take place when
+ i is the result). It is diagnosed with -Wstringop-overflow
+ (although more by accident than by design).
+
+ size_t n = i < strlen (s) ? i : strlen (s);
+ T (d, s, n);
+ */
+ }
+}
+
+
+/* Verify strncpy warnings for arrays of known bounds. */
+
+void test_strncpy_array (Dest *pd, const char* s)
+{
+#undef T
+#define T(d, s, len) (strncpy ((d), (s), (len)), sink (d))
+
+ T (dst7, s, 7); /* { dg-warning "specified bound 7 equals destination size" } */
+ T (dst7, s, sizeof dst7); /* { dg-warning "specified bound 7 equals destination size" } */
+
+ T (dst2_5[0], s, sizeof dst2_5[0]); /* { dg-warning "specified bound 5 equals destination size" "bug 77293" { xfail *-*-* } } */
+ T (dst2_5[1], s, sizeof dst2_5[1]); /* { dg-warning "specified bound 5 equals destination size" } */
+
+ /* Verify that copies that nul-terminate are not diagnosed. */
+ T (dst7, "", sizeof dst7);
+ T (dst7 + 6, "", sizeof dst7 - 6);
+ T (dst7, "1", sizeof dst7);
+ T (dst7 + 1, "1", sizeof dst7 - 1);
+ T (dst7, "123456", sizeof dst7);
+ T (dst7 + 1, "12345", sizeof dst7 - 1);
+
+ /* No nul-termination here. */
+ T (dst7 + 2, "12345", sizeof dst7 - 2); /* { dg-warning "output truncated before terminating nul copying 5 bytes from a string of the same length" } */
+
+ /* Because strnlen appends as many NULs as necessary to write the specified
+ number of byts the following doesn't (necessarily) truncate but rather
+ overflow, and so is diagnosed by -Wstringop-overflow. */
+ T (dst7, s, 8);
+
+ T (dst7 + 1, s, 6); /* { dg-warning "specified bound 6 equals destination size" } */
+ T (dst7 + 6, s, 1); /* { dg-warning "specified bound 1 equals destination size" } */
+
+ T (pd->a5, s, 5); /* { dg-warning "specified bound 5 equals destination size" } */
+ T (pd->a5, s, sizeof pd->a5); /* { dg-warning "specified bound 5 equals destination size" } */
+
+ /* Verify that a copy that nul-terminates is not diagnosed. */
+ T (pd->a5, "1234", sizeof pd->a5);
+
+ /* Same above, diagnosed by -Wstringop-overflow. */
+ T (pd->a5, s, 6);
+
+ /* Exercise destination with attribute "nonstring". */
+ T (pd->c3ns, "", 3);
+ T (pd->c3ns, "", 1);
+ /* Truncation is still diagnosed -- using strncpy in this case is
+ pointless and should be replaced with memcpy. */
+ T (pd->c3ns, "12", 1); /* { dg-warning "output truncated copying 1 byte from a string of length 2" } */
+ T (pd->c3ns, "12", 2);
+ T (pd->c3ns, "12", 3);
+ T (pd->c3ns, "123", 3);
+ T (pd->c3ns, s, 3);
+ T (pd->c3ns, s, sizeof pd->c3ns);
+
+ /* Verify that the idiom of calling strncpy with a bound equal to
+ the size of the destination (and thus potentially without NUL-
+ terminating it) immediately followed by setting the last element
+ of the array to NUL is not diagnosed. */
+ {
+ /* This might be better written using memcpy() but it's safe so
+ it probably shouldn't be diagnosed. It currently triggers
+ a warning because of bug 81704. */
+ strncpy (dst7, "0123456", sizeof dst7); /* { dg-bogus "truncated" "bug 81704" { xfail *-*-* } } */
+ dst7[sizeof dst7 - 1] = '\0';
+ sink (dst7);
+ }
+
+ {
+ const char a[] = "0123456789";
+ strncpy (dst7, a, sizeof dst7);
+ dst7[sizeof dst7 - 1] = '\0';
+ sink (dst7);
+ }
+
+ {
+ strncpy (dst7, s, sizeof dst7);
+ dst7[sizeof dst7 - 1] = '\0';
+ sink (dst7);
+ }
+
+ {
+ strncpy (pd->a5, "01234", sizeof pd->a5); /* { dg-bogus "truncated" "bug 81704" { xfail *-*-* } } */
+ pd->a5[sizeof pd->a5 - 1] = '\0';
+ sink (pd);
+ }
+
+ {
+ strncpy (pd->a5, s, sizeof pd->a5);
+ pd->a5[sizeof pd->a5 - 1] = '\0';
+ sink (pd);
+ }
+
+ {
+ unsigned n = 7;
+ char *p = (char*)__builtin_malloc (n);
+ strncpy (p, s, n);
+ p[n - 1] = '\0';
+ sink (p);
+ }
+
+ {
+ /* This should be diagnosed because the NUL-termination doesn't
+ immediately follow the strncpy call (sink may expect pd->a5
+ to be NUL-terminated). */
+ strncpy (pd->a5, s, sizeof pd->a5); /* { dg-warning "specified bound 5 equals destination size" } */
+ sink (pd);
+ pd->a5[sizeof pd->a5] = '\0';
+ sink (pd);
+ }
+}
+
+typedef struct Flex
+{
+ size_t n;
+ char a0[0];
+ char ax[];
+} Flex;
+
+extern char array[];
+
+/* Verify that no warning is issued for array of unknown bound, flexible
+ array members, or zero-length arrays, except when the source is definitely
+ truncated. */
+
+void test_strncpy_flexarray (Flex *pf, const char* s)
+{
+#undef T
+#define T(d, s, len) (strncpy ((d), (s), (len)), sink (d))
+
+ T (array, "12345", 7);
+ T (array, "12345", 123);
+
+ T (array, s, 7);
+ T (array, s, 123);
+
+ T (pf->a0, s, 1);
+ T (pf->a0, s, 1234);
+
+ T (pf->a0, "", 1);
+ T (pf->a0, "12345", 5); /* { dg-warning "output truncated before terminating nul copying 5 bytes from a string of the same length" } */
+ T (pf->a0, "12345", 1234);
+
+ T (pf->ax, s, 5);
+ T (pf->ax, s, 12345);
+
+ T (pf->ax, "1234", 5);
+ T (pf->ax, "12345", 5); /* { dg-warning "output truncated before terminating nul copying 5 bytes from a string of the same length" } */
+ T (pf->ax, "12345", 12345);
+}
+
+/* Verify warnings for dynamically allocated objects. */
+
+void test_strncpy_alloc (const char* s)
+{
+ size_t n = 7;
+ char *d = (char *)__builtin_malloc (n);
+
+ T (d, s, n); /* { dg-warning "specified bound 7 equals destination size" "bug 79016" { xfail *-*-* } } */
+
+ Dest *pd = (Dest *)__builtin_malloc (sizeof *pd * n);
+ T (pd->a5, s, 5); /* { dg-warning "specified bound 5 equals destination size" } */
+ T (pd->a5, s, sizeof pd->a5); /* { dg-warning "specified bound 5 equals destination size" } */
+}
+
+/* Verify warnings for VLAs. */
+
+void test_strncpy_vla (unsigned n, const char* s)
+{
+ char vla[n];
+ T (vla, s, 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
+
+ T (vla, s, 1);
+ T (vla, s, 2);
+ T (vla, s, n);
+
+ T (vla, "", 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
+ T (vla, "", 1);
+ T (vla, S4, 3); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ T (vla, S4, n);
+}
diff --git a/gcc/testsuite/c-c++-common/attr-nonstring-2.c b/gcc/testsuite/c-c++-common/attr-nonstring-2.c
new file mode 100644
index 0000000..9698609
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/attr-nonstring-2.c
@@ -0,0 +1,110 @@
+/* Test to exercise attribute "nonstring".
+ { dg-do compile }
+ { dg-options "-O2 -Wattributes -Wstringop-truncation -ftrack-macro-expansion=0" } */
+
+#define ATTR(list) __attribute__ (list)
+#define NONSTR ATTR ((nonstring))
+#define strncpy(d, s, n) (__builtin_strncpy ((d), (s), (n)), sink (d))
+
+void sink (void*);
+
+/* Global string with an unknown bound. */
+extern char gsx[];
+
+/* Global string with an known bound. */
+extern char gs3[3];
+
+/* Global non-strings with an unknown bound. */
+extern NONSTR char gax_1[];
+extern char NONSTR gax_2[];
+extern char gax_3[] NONSTR;
+
+/* Global non-strings with a known bound. */
+NONSTR char gns3[3];
+char NONSTR gns4[4];
+char gns5[5] NONSTR;
+
+/* Global string pointer. */
+extern char *ps_1;
+
+/* Global non-string pointers. */
+extern NONSTR char *pns_1;
+extern char* NONSTR pns_2;
+extern char *pns_3 NONSTR;
+
+struct MemArrays
+{
+ NONSTR char ma3[3];
+ char NONSTR ma4[4];
+ char ma5[5] NONSTR;
+ char max[] NONSTR;
+};
+
+
+void test_array (const char *s, unsigned n)
+{
+ const char s7[] = "1234567";
+
+ strncpy (gs3, "", 0); /* { dg-warning "destination unchanged after copying no bytes" } */
+ strncpy (gs3, "a", 1); /* { dg-warning "output truncated before terminating nul copying 1 byte from a string of the same length" } */
+ strncpy (gs3, "a", 2);
+ strncpy (gs3, "a", 3);
+ strncpy (gs3, "ab", 3);
+ strncpy (gs3, "abc", 3); /* { dg-warning "output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+
+ strncpy (gax_3, s7, 3); /* { dg-warning "output truncated copying 3 bytes from a string of length 7" } */
+
+ strncpy (gax_1, "a", 1);
+ strncpy (gax_2, "ab", 2);
+ strncpy (gax_3, "abc", 3);
+ strncpy (gax_3, s7, 3); /* { dg-warning "output truncated copying 3 bytes from a string of length 7" } */
+
+ strncpy (gax_1, s, 1);
+ strncpy (gax_2, s, 1);
+ strncpy (gax_3, s, 1);
+
+ strncpy (gax_1, s, n);
+ strncpy (gax_2, s, n);
+ strncpy (gax_3, s, n);
+}
+
+
+void test_pointer (const char *s, unsigned n)
+{
+ const char s7[] = "1234567";
+
+ strncpy (pns_1, "a", 1);
+ strncpy (pns_2, "ab", 2);
+ strncpy (pns_3, "abc", 3);
+ strncpy (pns_3, s7, 3); /* { dg-warning "output truncated copying 3 bytes from a string of length 7" } */
+
+ strncpy (pns_1, s, 1);
+ strncpy (pns_2, s, 1);
+ strncpy (pns_3, s, 1);
+
+ strncpy (pns_1, s, n);
+ strncpy (pns_2, s, n);
+ strncpy (pns_3, s, n);
+}
+
+
+void test_member_array (struct MemArrays *p, const char *s, unsigned n)
+{
+ const char s7[] = "1234567";
+
+ strncpy (p->ma3, "a", 1);
+ strncpy (p->ma4, "ab", 2);
+ strncpy (p->ma5, "abc", 3);
+ strncpy (p->max, "abcd", 4);
+ strncpy (p->max, s7, 5); /* { dg-warning "output truncated copying 5 bytes from a string of length 7" } */
+
+ strncpy (p->ma3, s, 1);
+ strncpy (p->ma4, s, 1);
+ strncpy (p->ma5, s, 1);
+ strncpy (p->max, s, 1);
+
+ strncpy (p->ma3, s7, n);
+ strncpy (p->ma4, s7, n);
+ strncpy (p->ma5, s7, n);
+ strncpy (p->max, s7, n);
+}
diff --git a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C
index c72532b..5bc5c4c 100644
--- a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C
+++ b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C
@@ -1,6 +1,6 @@
// Test -Wsizeof-pointer-memaccess warnings.
// { dg-do compile }
-// { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow" }
+// { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow -Wno-stringop-truncation" }
// Test just twice, once with -O0 non-fortified, once with -O2 fortified.
// { dg-skip-if "" { *-*-* } { "*" } { "-O0" "-O2" } }
// { dg-skip-if "" { *-*-* } { "-flto" } { "" } }
@@ -698,12 +698,17 @@ f4 (char *x, char **y, int z, char w[64])
strncat (w, s2, sizeof (w)); // { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" }
stpncpy (w, s1, sizeof (w)); // { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" }
- // These are correct, no warning.
const char s3[] = "foobarbaz";
const char s4[] = "abcde12345678";
- strncpy (x, s3, sizeof (s3));
- strncat (x, s4, sizeof (s4));
- stpncpy (x, s3, sizeof (s3));
+
+ // These are pointless when the destination is large enough, and
+ // cause overflow otherwise. They might as well be replaced by
+ // strcpy() or memcpy().
+ strncpy (x, s3, sizeof (s3)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" }
+ strncat (x, s4, sizeof (s4)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" }
+ stpncpy (x, s3, sizeof (s3)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" }
+
+ // These are safe, no warning.
y[1] = strndup (s3, sizeof (s3));
z += strncmp (s3, s4, sizeof (s3));
z += strncmp (s3, s4, sizeof (s4));
diff --git a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C
index a216f47..f2c864b 100644
--- a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C
+++ b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C
@@ -1,6 +1,6 @@
// Test -Wsizeof-pointer-memaccess warnings.
// { dg-do compile }
-// { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow" }
+// { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow -Wno-stringop-truncation" }
// Test just twice, once with -O0 non-fortified, once with -O2 fortified,
// suppressing buffer overflow warnings.
// { dg-skip-if "" { *-*-* } { "*" } { "-O0" "-O2" } }
@@ -703,12 +703,13 @@ f4 (char *x, char **y, int z, char w[64])
strncat (w, s2, sizeof (w)); // { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" }
stpncpy (w, s1, sizeof (w)); // { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" }
- // These are correct, no warning.
const char s3[] = "foobarbaz";
const char s4[] = "abcde12345678";
- strncpy (x, s3, sizeof (s3));
- strncat (x, s4, sizeof (s4));
- stpncpy (x, s3, sizeof (s3));
+ strncpy (x, s3, sizeof (s3)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination" }
+ strncat (x, s4, sizeof (s4)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination" }
+ stpncpy (x, s3, sizeof (s3)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination" }
+
+ // These are safe, no warning.
y[1] = strndup (s3, sizeof (s3));
z += strncmp (s3, s4, sizeof (s3));
z += strncmp (s3, s4, sizeof (s4));
diff --git a/gcc/testsuite/gcc.dg/Walloca-1.c b/gcc/testsuite/gcc.dg/Walloca-1.c
index ad39373..85e9160 100644
--- a/gcc/testsuite/gcc.dg/Walloca-1.c
+++ b/gcc/testsuite/gcc.dg/Walloca-1.c
@@ -1,6 +1,6 @@
/* { dg-do compile } */
/* { dg-require-effective-target alloca } */
-/* { dg-options "-Walloca-larger-than=2000 -O2" } */
+/* { dg-options "-Walloca-larger-than=2000 -O2 -ftrack-macro-expansion=0" } */
#define alloca __builtin_alloca
diff --git a/gcc/testsuite/gcc.dg/torture/Wsizeof-pointer-memaccess1.c b/gcc/testsuite/gcc.dg/torture/Wsizeof-pointer-memaccess1.c
index f9bc57c..cd9dc72 100644
--- a/gcc/testsuite/gcc.dg/torture/Wsizeof-pointer-memaccess1.c
+++ b/gcc/testsuite/gcc.dg/torture/Wsizeof-pointer-memaccess1.c
@@ -1,6 +1,6 @@
/* Test -Wsizeof-pointer-memaccess warnings. */
/* { dg-do compile } */
-/* { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow" } */
+/* { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow -Wno-stringop-truncation" } */
/* Test just twice, once with -O0 non-fortified, once with -O2 fortified. */
/* { dg-skip-if "" { *-*-* } { "*" } { "-O0" "-O2" } } */
/* { dg-skip-if "" { *-*-* } { "-flto" } { "" } } */
@@ -704,12 +704,17 @@ f4 (char *x, char **y, int z, char w[64])
strncat (w, s2, sizeof (w)); /* { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" } */
stpncpy (w, s1, sizeof (w)); /* { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" } */
- /* These are correct, no warning. */
+ /* These are pointless when the destination is large enough, and
+ cause overflow otherwise. If the copies are guaranteed to be
+ safe the calls might as well be replaced by strcat(), strcpy(),
+ or memcpy(). */
const char s3[] = "foobarbaz";
const char s4[] = "abcde12345678";
- strncpy (x, s3, sizeof (s3));
- strncat (x, s4, sizeof (s4));
- stpncpy (x, s3, sizeof (s3));
+ strncpy (x, s3, sizeof (s3)); /* { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" } */
+ strncat (x, s4, sizeof (s4)); /* { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" } */
+ stpncpy (x, s3, sizeof (s3)); /* { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" } */
+
+ /* These are correct, no warning. */
y[1] = strndup (s3, sizeof (s3));
z += strncmp (s3, s4, sizeof (s3));
z += strncmp (s3, s4, sizeof (s4));
diff --git a/gcc/testsuite/gcc.dg/torture/pr63554.c b/gcc/testsuite/gcc.dg/torture/pr63554.c
index fa06c5a..9162266 100644
--- a/gcc/testsuite/gcc.dg/torture/pr63554.c
+++ b/gcc/testsuite/gcc.dg/torture/pr63554.c
@@ -1,4 +1,5 @@
-/* { dg-do compile } */
+/* PR c/63554 - ice in "execute_todo, at passes.c:1797" with -O3
+ { dg-do compile } */
char *a;
void
@@ -7,3 +8,5 @@ nssutil_ReadSecmodDB (void)
long b = __builtin_object_size (0, 0);
a = __builtin___strncat_chk (a, " ", 1, b);
}
+
+/* { dg-prune-output "\\\[-Wstringop-overflow=]" } */
diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c
index b0563fe..ac6503f 100644
--- a/gcc/tree-ssa-strlen.c
+++ b/gcc/tree-ssa-strlen.c
@@ -40,11 +40,15 @@ along with GCC; see the file COPYING3. If not see
#include "expr.h"
#include "tree-dfa.h"
#include "domwalk.h"
+#include "tree-ssa-alias.h"
#include "tree-ssa-propagate.h"
#include "params.h"
#include "ipa-chkp.h"
#include "tree-hash-traits.h"
#include "builtins.h"
+#include "diagnostic-core.h"
+#include "diagnostic.h"
+#include "intl.h"
/* A vector indexed by SSA_NAME_VERSION. 0 means unknown, positive value
is an index into strinfo vector, negative value stands for
@@ -146,6 +150,9 @@ struct decl_stridxlist_map
mappings. */
static hash_map<tree_decl_hash, stridxlist> *decl_to_stridxlist_htab;
+typedef std::pair<int, location_t> stridx_strlenloc;
+static hash_map<tree, stridx_strlenloc> strlen_to_stridx;
+
/* Obstack for struct stridxlist and struct decl_stridxlist_map. */
static struct obstack stridx_obstack;
@@ -1197,6 +1204,9 @@ handle_builtin_strlen (gimple_stmt_iterator *gsi)
si->nonzero_chars = lhs;
gcc_assert (si->full_string_p);
}
+
+ location_t loc = gimple_location (stmt);
+ strlen_to_stridx.put (lhs, stridx_strlenloc (idx, loc));
return;
}
}
@@ -1240,6 +1250,9 @@ handle_builtin_strlen (gimple_stmt_iterator *gsi)
strinfo *si = new_strinfo (src, idx, lhs, true);
set_strinfo (idx, si);
find_equal_ptrs (src, idx);
+
+ location_t loc = gimple_location (stmt);
+ strlen_to_stridx.put (lhs, stridx_strlenloc (idx, loc));
}
}
@@ -1606,6 +1619,270 @@ handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi)
fprintf (dump_file, "not possible.\n");
}
+/* Return true if LEN depends on a call to strlen(SRC) in an interesting
+ way. LEN can either be an integer expression, or a pointer (to char).
+ When it is the latter (such as in recursive calls to self) is is
+ assumed to be the argument in some call to strlen() whose relationship
+ to SRC is being ascertained. */
+
+static bool
+is_strlen_related_p (tree src, tree len)
+{
+ if (TREE_CODE (TREE_TYPE (len)) == POINTER_TYPE
+ && operand_equal_p (src, len, 0))
+ return true;
+
+ if (TREE_CODE (len) != SSA_NAME)
+ return false;
+
+ gimple *def_stmt = SSA_NAME_DEF_STMT (len);
+ if (!def_stmt)
+ return false;
+
+ if (is_gimple_call (def_stmt))
+ {
+ tree func = gimple_call_fndecl (def_stmt);
+ if (!valid_builtin_call (def_stmt)
+ || DECL_FUNCTION_CODE (func) != BUILT_IN_STRLEN)
+ return false;
+
+ tree arg = gimple_call_arg (def_stmt, 0);
+ return is_strlen_related_p (src, arg);
+ }
+
+ if (!is_gimple_assign (def_stmt))
+ return false;
+
+ tree_code code = gimple_assign_rhs_code (def_stmt);
+ tree rhs1 = gimple_assign_rhs1 (def_stmt);
+ tree rhstype = TREE_TYPE (rhs1);
+
+ if ((TREE_CODE (rhstype) == POINTER_TYPE && code == POINTER_PLUS_EXPR)
+ || (INTEGRAL_TYPE_P (rhstype)
+ && (code == BIT_AND_EXPR
+ || code == NOP_EXPR)))
+ {
+ /* Pointer plus (an integer) and integer cast or truncation are
+ considered among the (potentially) related expressions to strlen.
+ Others are not. */
+ return is_strlen_related_p (src, rhs1);
+ }
+
+ return false;
+}
+
+/* A helper of handle_builtin_stxncpy. Check to see if the specified
+ bound is a) equal to the size of the destination DST and if so, b)
+ if it's immediately followed by DST[LEN - 1] = '\0'. If a) holds
+ and b) does not, warn. Otherwise, do nothing. Return true if
+ diagnostic has been issued.
+
+ The purpose is to diagnose calls to strncpy and stpncpy that do
+ not nul-terminate the copy while allowing for the idiom where
+ such a call is immediately followed by setting the last element
+ to nul, as in:
+ char a[32];
+ strncpy (a, s, sizeof a);
+ a[sizeof a - 1] = '\0';
+*/
+
+static bool
+maybe_diag_stxncpy_trunc (gimple_stmt_iterator gsi, tree src, tree len)
+{
+ if (!warn_stringop_truncation)
+ return false;
+
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (TREE_CODE (len) != INTEGER_CST)
+ return false;
+
+ /* Negative value is the constant string length. */
+ int sidx = get_stridx (src);
+ if (sidx < 0 && compare_tree_int (len, ~sidx) > 0)
+ return false;
+
+ tree dst = gimple_call_arg (stmt, 0);
+
+ /* See if the destination is declared with attribute "nonstring"
+ and if so, avoid the truncation warning. */
+ if (TREE_CODE (dst) == SSA_NAME)
+ {
+ if (SSA_NAME_IS_DEFAULT_DEF (dst))
+ dst = SSA_NAME_VAR (dst);
+ else
+ {
+ gimple *def = SSA_NAME_DEF_STMT (dst);
+
+ if (is_gimple_assign (def)
+ && gimple_assign_rhs_code (def) == ADDR_EXPR)
+ dst = gimple_assign_rhs1 (def);
+ }
+ }
+
+ tree dstdecl = dst;
+ if (TREE_CODE (dstdecl) == ADDR_EXPR)
+ dstdecl = TREE_OPERAND (dstdecl, 0);
+
+ {
+ tree d = dstdecl;
+ if (TREE_CODE (d) == COMPONENT_REF)
+ d = TREE_OPERAND (d, 1);
+
+ if (DECL_P (d) && lookup_attribute ("nonstring", DECL_ATTRIBUTES (d)))
+ return false;
+ }
+
+ /* Look for dst[i] = '\0'; after the stxncpy() call and if found
+ avoid the truncation warning. */
+ gsi_next (&gsi);
+ gimple *next_stmt = gsi_stmt (gsi);
+
+ if (!gsi_end_p (gsi) && is_gimple_assign (next_stmt))
+ {
+ HOST_WIDE_INT off;
+ dstdecl = get_addr_base_and_unit_offset (dstdecl, &off);
+
+ tree lhs = gimple_assign_lhs (next_stmt);
+ tree lhsbase = get_addr_base_and_unit_offset (lhs, &off);
+ if (operand_equal_p (dstdecl, lhsbase, 0))
+ return false;
+ }
+
+ strinfo *sisrc = sidx > 0 ? get_strinfo (sidx) : NULL;
+
+ if (sisrc && sisrc->nonzero_chars
+ && tree_fits_uhwi_p (sisrc->nonzero_chars))
+ {
+ /* If the source is shorter than the specified bound the copy
+ is nul-terminated. */
+ if (tree_int_cst_lt (sisrc->nonzero_chars, len))
+ return false;
+
+ /* If the source is longer than the specified bound the copy
+ is truncated. */
+ if (tree_int_cst_lt (len, sisrc->nonzero_chars))
+ {
+ location_t callloc = gimple_location (stmt);
+ tree func = gimple_call_fndecl (stmt);
+
+ warning_at (callloc, OPT_Wstringop_truncation,
+ integer_onep (len)
+ ? G_("%qD output truncated copying %E byte from "
+ "a string of length %E")
+ : G_("%qD output truncated copying %E bytes from "
+ "a string of length %E"),
+ func, len, sisrc->nonzero_chars);
+ return true;
+ }
+
+ /* Otherwise the source length matches the bound. */
+ }
+ else
+ {
+ /* The source length is uknown. Try to determine the destination
+ size and see if it matches the specified bound. If not, bail.
+ Otherwise go on to see if it should be diagnosed for possible
+ truncation. */
+ tree objsize = compute_objsize (dst, 1);
+ if (!objsize)
+ return false;
+
+ if (!tree_int_cst_equal (objsize, len))
+ return false;
+ }
+
+ location_t callloc = gimple_location (stmt);
+ tree func = gimple_call_fndecl (stmt);
+
+ bool warned = warning_at (callloc, OPT_Wstringop_truncation,
+ "%qD specified bound %E equals destination size",
+ func, len);
+
+ gimple_set_no_warning (stmt, true);
+
+ return warned;
+}
+
+/* Check the size argument to the built-in forms of stpncpy and strncpy
+ to see if it's derived from calling strlen() on the source argument
+ and if so, issue a warning. */
+
+static void
+handle_builtin_stxncpy (built_in_function, gimple_stmt_iterator *gsi)
+{
+ gimple *stmt = gsi_stmt (*gsi);
+
+ bool with_bounds = gimple_call_with_bounds_p (stmt);
+
+ tree src = gimple_call_arg (stmt, with_bounds ? 2 : 1);
+ tree len = gimple_call_arg (stmt, with_bounds ? 3 : 2);
+
+ /* If the length argument was computed from strlen(S) for some string
+ S retrieve the strinfo index for the string (PSS->FIRST) alonng with
+ the location of the strlen() call (PSS->SECOND). */
+ stridx_strlenloc *pss = strlen_to_stridx.get (len);
+ if (!pss || pss->first <= 0)
+ {
+ maybe_diag_stxncpy_trunc (*gsi, src, len);
+ return;
+ }
+
+ int sidx = get_stridx (src);
+ strinfo *sisrc = sidx > 0 ? get_strinfo (sidx) : NULL;
+
+ /* Strncpy() et al. cannot modify the source string. Prevent the rest
+ of the pass from invalidating the strinfo data. */
+ if (sisrc)
+ sisrc->dont_invalidate = true;
+
+ /* Retrieve the strinfo data for the string S that LEN was computed
+ from as some function F of strlen (S) (i.e., LEN need not be equal
+ to strlen(S)). */
+ strinfo *silen = get_strinfo (pss->first);
+
+ location_t callloc = gimple_location (stmt);
+
+ tree func = gimple_call_fndecl (stmt);
+
+ bool warned = false;
+
+ /* When -Wstringop-truncation is set, try to determine truncation
+ before diagnosing possible overflow. Truncation is implied by
+ the LEN argument being equal to strlen(SRC), regardless of
+ whether its value is known. Otherwise, issue the more generic
+ -Wstringop-overflow which triggers for LEN arguments that in
+ any meaningful way depend on strlen(SRC). */
+ if (warn_stringop_truncation
+ && sisrc == silen
+ && is_strlen_related_p (src, len))
+ warned = warning_at (callloc, OPT_Wstringop_truncation,
+ "%qD output truncated before terminating nul "
+ "copying as many bytes from a string as its length",
+ func);
+ else if (silen && is_strlen_related_p (src, silen->ptr))
+ warned = warning_at (callloc, OPT_Wstringop_overflow_,
+ "%qD specified bound depends on the length "
+ "of the source argument", func);
+ if (warned)
+ {
+ location_t strlenloc = pss->second;
+ if (strlenloc != UNKNOWN_LOCATION && strlenloc != callloc)
+ inform (strlenloc, "length computed here");
+ }
+}
+
+/* Check the size argument to the built-in forms of strncat to see if
+ it's derived from calling strlen() on the source argument and if so,
+ issue a warning. */
+
+static void
+handle_builtin_strncat (built_in_function bcode, gimple_stmt_iterator *gsi)
+{
+ /* Same as stxncpy(). */
+ handle_builtin_stxncpy (bcode, gsi);
+}
+
/* Handle a memcpy-like ({mem{,p}cpy,__mem{,p}cpy_chk}) call.
If strlen of the second argument is known and length of the third argument
is that plus one, strlen of the first argument is the same after this
@@ -2512,6 +2789,19 @@ strlen_optimize_stmt (gimple_stmt_iterator *gsi)
case BUILT_IN_STPCPY_CHK_CHKP:
handle_builtin_strcpy (DECL_FUNCTION_CODE (callee), gsi);
break;
+
+ case BUILT_IN_STRNCAT:
+ case BUILT_IN_STRNCAT_CHK:
+ handle_builtin_strncat (DECL_FUNCTION_CODE (callee), gsi);
+ break;
+
+ case BUILT_IN_STPNCPY:
+ case BUILT_IN_STPNCPY_CHK:
+ case BUILT_IN_STRNCPY:
+ case BUILT_IN_STRNCPY_CHK:
+ handle_builtin_stxncpy (DECL_FUNCTION_CODE (callee), gsi);
+ break;
+
case BUILT_IN_MEMCPY:
case BUILT_IN_MEMCPY_CHK:
case BUILT_IN_MEMPCPY:
@@ -2575,6 +2865,10 @@ strlen_optimize_stmt (gimple_stmt_iterator *gsi)
else if (code == EQ_EXPR || code == NE_EXPR)
fold_strstr_to_strncmp (gimple_assign_rhs1 (stmt),
gimple_assign_rhs2 (stmt), stmt);
+
+ tree rhs1 = gimple_assign_rhs1 (stmt);
+ if (stridx_strlenloc *ps = strlen_to_stridx.get (rhs1))
+ strlen_to_stridx.put (lhs, *ps);
}
else if (TREE_CODE (lhs) != SSA_NAME && !TREE_SIDE_EFFECTS (lhs))
{
@@ -2826,6 +3120,8 @@ pass_strlen::execute (function *fun)
laststmt.len = NULL_TREE;
laststmt.stridx = 0;
+ strlen_to_stridx.empty ();
+
return 0;
}
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-06 20:07 ` [PATCH] " Martin Sebor
@ 2017-08-09 19:21 ` Jeff Law
0 siblings, 0 replies; 39+ messages in thread
From: Jeff Law @ 2017-08-09 19:21 UTC (permalink / raw)
To: Martin Sebor, Gcc Patch List
On 08/06/2017 02:07 PM, Martin Sebor wrote:
>>>
>>> You're right that there is no truncation and the effect is
>>> the same but only in the unlikely case when the destination
>>> is empty. Otherwise the result is truncated.
>> Maybe this is where I'm confused. How does the destination play into
>> truncation issues? I've always been under the impression that the
>> destination has to be large enough to hold the result, but that it
>> doesn't affect truncation of the source.
>
> No, you're right. It's me who's been confused, either thinking
> of strncpy or -Wstringop-overflow. The difference between the
> two warnings is just one byte in some cases and I got them mixed
> up. Sorry about that and thanks for spotting this silly mistake!
> I've updated the code to issue -Wstringop-overflow here and added
> a better example to the manual.
Thanks. I kept looking thinking I must have missed something somewhere...
>
> 1) In the following, the strncpy call would normally trigger
> -Wstringop-truncation because of the possible missing terminating
> NUL, but because the immediately following statement inserts the
> NUL the call is safe.
>
> strncpy (d, s, sizeof d); // possible missing NUL
> d[sizeof d - 1] = '\0'; // okay, NUL add here
At first I wondered if this was an optimization opportunity. BUt
thinking more about it, I don't think it is, unless you happen to know
that sizeof d == sizeof s, which I doubt happens often enough to matter.
>
> 2) Building Glibc made me realize that in my effort to detect
> the (common) misuses of strncpy I neglected the original (and
> only intended but now rare) use case of filling a buffer
> without necessarily NUL-terminating it (as in struct dirent::
> d_name). To allow it the patch adds a new attribute that can
> be used to annotate char arrays and pointers that are intended
> not to be NUL-terminated. This suppresses the truncation
> warning. When the patch is approved I'll propose the (trivial)
> Glibc changes. In the future, the attribute will also let GCC
> warn when passing such objects to functions that expect a NUL-
> terminated string argument (e.g., strlen or strcpy).
Seems reasonable.
>
> 3) Finally, to add inlining context to diagnostics issued by
> the middle end, I've added a new %G directive to complement
> %K by accepting a gcall* argument.
Also seems reasonable. I think we've wanted something like this for a
while.
>
> To make the patch easier to review I've broken it up into
> four parts:
>
> 1. Add %G.
> 2. Add attribute nostring.
> 3. Implement -Wstringop-truncation and enhance -Wstringop-
> overflow (the meat of the patch).
> 4. Fix up GCC to compile with the new and enhanced warnings.
I'll try to get to them today.
Thanks,
jeff
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 1/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-06 20:07 ` [PATCH 1/4] " Martin Sebor
@ 2017-08-10 5:02 ` Jeff Law
2017-08-14 19:21 ` Martin Sebor
0 siblings, 1 reply; 39+ messages in thread
From: Jeff Law @ 2017-08-10 5:02 UTC (permalink / raw)
To: Martin Sebor, Gcc Patch List
On 08/06/2017 02:07 PM, Martin Sebor wrote:
> The attached patch adds support for a new GCC format specifier,
> G, that behaves like %K but accepts a gcall* argument. This
> makes it possible to provide inlining context for "artificial"
> inline functions like strncpy (with _FORTIFY_SOURCE) in
> diagnostics issued from the middle end.
>
> Martin
>
> gcc-81117-1.diff
>
>
> PR c/81117 - Improve buffer overflow checking in strncpy
>
> gcc/ChangeLog:
>
> PR c/81117
> * tree-diagnostic.c (default_tree_printer): Handle %G.
> * tree-pretty-print.h (percent_G_format): Declare new function.
> * tree-pretty-print.c (percent_K_format): Define a static overload.
> (percent_G_format): New function.
>
> gcc/c/ChangeLog:
>
> PR c/81117
> * c-objc-common.c (c_objc_common_init): Handle 'G'.
>
> gcc/c-family/ChangeLog:
>
> * c-format.h (T89_G): New macro.
> * c-format.c (local_gcall_ptr_node): New variable.
> (init_dynamic_diag_info): Initialize it.
>
> gcc/cp/ChangeLog:
>
> PR c/81117
> * error.c (cp_printer): Handle 'G'.
>
> gcc/testsuite/ChangeLog:
>
> PR c/81117
> * gcc.dg/format/gcc_diag-10.c: Exercise %G.
>
> diff --git a/gcc/tree-diagnostic.c b/gcc/tree-diagnostic.c
> index 52b7e7f..ad58b69 100644
> --- a/gcc/tree-diagnostic.c
> +++ b/gcc/tree-diagnostic.c
> @@ -275,6 +275,10 @@ default_tree_printer (pretty_printer *pp, text_info *text, const char *spec,
> t = va_arg (*text->args_ptr, tree);
> break;
>
> + case 'G':
> + percent_G_format (text);
> + return true;
> +
> case 'K':
> percent_K_format (text);
> return true;
> diff --git a/gcc/tree-pretty-print.c b/gcc/tree-pretty-print.c
> index 4d8177c..7c4c805 100644
> --- a/gcc/tree-pretty-print.c
> +++ b/gcc/tree-pretty-print.c
> @@ -33,6 +33,7 @@ along with GCC; see the file COPYING3. If not see
> #include "dumpfile.h"
> #include "internal-fn.h"
> #include "gomp-constants.h"
> +#include "gimple.h"
This is an indication you're probably putting the 'G' handling in the
wrong place. Wouldn't gimple-pretty-print.c be more correct?
That's my only objection to this patch, so if it moves trivially, then
it's pre-approved. If it's non-trivial, then we'll want another iteration.
jeff
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 2/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-06 20:07 ` [PATCH 2/4] " Martin Sebor
@ 2017-08-10 6:39 ` Jeff Law
2017-08-14 18:04 ` Martin Sebor
2017-11-10 23:03 ` Marc Glisse
1 sibling, 1 reply; 39+ messages in thread
From: Jeff Law @ 2017-08-10 6:39 UTC (permalink / raw)
To: Martin Sebor, Gcc Patch List
On 08/06/2017 02:07 PM, Martin Sebor wrote:
> Part 2 of the series adds attribute nostring to annotate arrays
> of and pointers to char with that are intended to store sequences
> of characters that aren't necessarily valid (nul-terminated)
> strings. In the subsequent patch the attribute is relied on to
> avoid diagnosing strcncpy calls that truncate strings and create
> such copies. In the future I'd like to also use the attribute
> to diagnose when arrays or pointers with the attribute are passed
> to functions that expect nul-terminated strings (such as strlen
> or strcpy).
>
> Martin
>
>
> gcc-81117-2.diff
>
>
> PR c/81117 - Improve buffer overflow checking in strncpy
>
> gcc/ChangeLog:
>
> PR c/81117
> * builtin-attrs.def (attribute nonstring): New.
> * doc/extend.texi (attribute nonstring): Document new attribute.
>
> gcc/c-family/ChangeLog:
>
> PR c/81117
> * c-attribs.c (c_common_attribute_table): Add nonstring entry.
> (handle_nonstring_attribute): New function.
>
> gcc/testsuite/ChangeLog:
>
> PR c/81117
> * c-c++-common/attr-nonstring-1.c: New test.
>
> --- a/gcc/builtin-attrs.def
> +++ b/gcc/builtin-attrs.def
> @@ -93,6 +93,7 @@ DEF_ATTR_IDENT (ATTR_FORMAT, "format")
> DEF_ATTR_IDENT (ATTR_FORMAT_ARG, "format_arg")
> DEF_ATTR_IDENT (ATTR_MALLOC, "malloc")
> DEF_ATTR_IDENT (ATTR_NONNULL, "nonnull")
> +DEF_ATTR_IDENT (ATTR_NONSTRING, "nonstring")
> DEF_ATTR_IDENT (ATTR_NORETURN, "noreturn")
> DEF_ATTR_IDENT (ATTR_NOTHROW, "nothrow")
> DEF_ATTR_IDENT (ATTR_LEAF, "leaf")
So all the attributes here are associated with functions I believe.
You're defining a variable attribute. In fact, I'm not even sure that
variable attributes get a DEF_ATTR_<whatever>
> diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
> index b253ccc..1954ca5 100644
> --- a/gcc/doc/extend.texi
> +++ b/gcc/doc/extend.texi
> @@ -5835,6 +5835,30 @@ The @code{deprecated} attribute can also be used for functions and
> types (@pxref{Common Function Attributes},
> @pxref{Common Type Attributes}).
>
> +@item nonstring (@var{nonstring})
> +@cindex @code{nonstring} variable attribute
> +The @code{nonstring} variable attribute specifies that an object or member
> +declaration with type array of @code{char} or pointer to @code{char} is
> +intended to store character arrays that do not necessarily contain
> +a terminating @code{NUL} character. This is useful to avoid warnings
> +when such an array or pointer is used as an argument to a bounded string
> +manipulation function such as @code{strncpy}. For example, without the
> +attribute, GCC will issue a warning for the call below because it may
> +truncate the copy without appending the terminating NUL character. Using
> +the attribute makes it possible to suppress the warning.
[ ... ]
I think this is in the wrong section, I believe it belongs in the
"Variable Attributes" section.
Assuming you don't actually need the ATTR_NONSTRING, this patch is fine
with that hunk removed and the documentation moved into the right section.
jeff
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 3/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-06 20:07 ` [PATCH 3/4] " Martin Sebor
@ 2017-08-10 7:17 ` Jeff Law
2017-08-10 7:39 ` Richard Biener
2017-08-10 20:21 ` Martin Sebor
2017-11-14 9:24 ` [testsuite, committed] Require alloca for c-c++-common/Wstringop-truncation.c Tom de Vries
2017-11-15 15:30 ` [testsuite, committed] Compile strncpy-fix-1.c with -Wno-stringop-truncation Tom de Vries
2 siblings, 2 replies; 39+ messages in thread
From: Jeff Law @ 2017-08-10 7:17 UTC (permalink / raw)
To: Martin Sebor, Gcc Patch List
On 08/06/2017 02:07 PM, Martin Sebor wrote:
> Part 3 of the series contains the meat of the patch: the new
> -Wstringop-truncation option, and enhancements to -Wstringop-
> overflow, and -Wpointer-sizeof-memaccess to detect misuses of
> strncpy and strncat.
>
> Martin
>
> gcc-81117-3.diff
>
>
> PR c/81117 - Improve buffer overflow checking in strncpy
>
> gcc/ChangeLog:
>
> PR c/81117
> * builtins.c (compute_objsize): Handle arrays that
> compute_builtin_object_size likes to fail for. Make extern.
> * builtins.h (compute_objsize): Declare.
> (check_strncpy_sizes): New function.
> (expand_builtin_strncpy): Call check_strncpy_sizes.
> * gimple-fold.c (gimple_fold_builtin_strncpy): Implement
> -Wstringop-truncation.
> (gimple_fold_builtin_strncat): Same.
> * gimple.c (gimple_build_call_from_tree): Set call location.
> * tree-ssa-strlen.c (strlen_to_stridx): New global variable.
> (maybe_diag_bound_equal_length, is_strlen_related_p): New functions.
> (handle_builtin_stxncpy, handle_builtin_strncat): Same.
> (handle_builtin_strlen): Use strlen_to_stridx.
> (strlen_optimize_stmt): Handle flavors of strncat, strncpy, and
> stpncpy.
> Use strlen_to_stridx.
> (pass_strlen::execute): Release strlen_to_stridx.
> * doc/invoke.texi (-Wsizeof-pointer-memaccess): Document enhancement.
> (-Wstringop-truncation): Document new option.
>
> gcc/c-family/ChangeLog:
>
> PR c/81117
> * c-warn.c (sizeof_pointer_memaccess_warning): Handle arrays.
> * c.opt (-Wstriingop-truncation): New option.
>
> gcc/testsuite/ChangeLog:
>
> PR c/81117
> * c-c++-common/Wsizeof-pointer-memaccess3.c: New test.
> * c-c++-common/Wstringop-overflow.c: Same.
> * c-c++-common/Wstringop-truncation.c: Same.
> * c-c++-common/Wsizeof-pointer-memaccess2.c: Adjust.
> * c-c++-common/attr-nonstring-2.c: New test.
> * g++.dg/torture/Wsizeof-pointer-memaccess1.C: Adjust.
> * g++.dg/torture/Wsizeof-pointer-memaccess2.C: Same.
> * gcc.dg/torture/pr63554.c: Same.
> * gcc.dg/Walloca-1.c: Disable macro tracking.
>
> diff --git a/gcc/builtins.c b/gcc/builtins.c
> index 016f68d..1aa9e22 100644
> --- a/gcc/builtins.c
> +++ b/gcc/builtins.c
[ ... ]
> +
> + if (TREE_CODE (type) == ARRAY_TYPE)
> + {
> + /* Return the constant size unless it's zero (that's a zero-length
> + array likely at the end of a struct). */
> + tree size = TYPE_SIZE_UNIT (type);
> + if (size && TREE_CODE (size) == INTEGER_CST
> + && !integer_zerop (size))
> + return size;
> + }
Q. Do we have a canonical test for the trailing array idiom? In some
contexts isn't it size 1? ISTM This test needs slight improvement.
Ideally we'd use some canonical test for detect the trailing array idiom
rather than open-coding it here. You might look at the array index
warnings in tree-vrp.c to see if it's got a canonical test you can call
or factor and use.
> @@ -3883,6 +3920,30 @@ expand_builtin_strncat (tree exp, rtx)
> return NULL_RTX;
> }
>
> +/* Helper to check the sizes of sequences and the destination of calls
> + to __builtin_strncpy (DST, SRC, LEN) and __builtin___strncpy_chk.
> + Returns true on success (no overflow warning), false otherwise. */
> +
> +static bool
> +check_strncpy_sizes (tree exp, tree dst, tree src, tree len)
> +{
> + tree dstsize = compute_objsize (dst, warn_stringop_overflow - 1);
> +
> + if (!check_sizes (OPT_Wstringop_overflow_,
> + exp, len, /*maxlen=*/NULL_TREE, src, dstsize))
> + return false;
> +
> + if (!dstsize || TREE_CODE (len) != INTEGER_CST)
> + return true;
> +
> + if (tree_int_cst_lt (dstsize, len))
> + warning_at (EXPR_LOCATION (exp), OPT_Wstringop_truncation,
> + "%K%qD specified bound %E exceeds destination size %E",
> + exp, get_callee_fndecl (exp), len, dstsize);
> +
> + return true;
So in the case where you issue the warning, what should the return value
be? According to the comment it should be false. It looks like you got
the wrong return value for the tree_int_cst_lt (dstsize, len) test.
>
> return false;
> diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c
> index b0563fe..ac6503f 100644
> --- a/gcc/tree-ssa-strlen.c
> +++ b/gcc/tree-ssa-strlen.c
> +
> +/* A helper of handle_builtin_stxncpy. Check to see if the specified
> + bound is a) equal to the size of the destination DST and if so, b)
> + if it's immediately followed by DST[LEN - 1] = '\0'. If a) holds
> + and b) does not, warn. Otherwise, do nothing. Return true if
> + diagnostic has been issued.
> +
> + The purpose is to diagnose calls to strncpy and stpncpy that do
> + not nul-terminate the copy while allowing for the idiom where
> + such a call is immediately followed by setting the last element
> + to nul, as in:
> + char a[32];
> + strncpy (a, s, sizeof a);
> + a[sizeof a - 1] = '\0';
> +*/
So using gsi_next to find the next statement could make the heuristic
fail to find the a[sizeof a - 1] = '\0'; statement when debugging is
enabled.
gsi_next_nondebug would be better as it would skip over any debug insns.
What might be even better would be to use the immediate uses of the
memory tag. For your case there should be only one immediate use and it
should point to the statement which NUL terminates the destination. Or
maybe that would be worse in that you only want to allow this exception
when the statements are consecutive.
> +
> + /* Look for dst[i] = '\0'; after the stxncpy() call and if found
> + avoid the truncation warning. */
> + gsi_next (&gsi);
> + gimple *next_stmt = gsi_stmt (gsi);
Here's the gsi_next I'm referring to.
> + else
> + {
> + /* The source length is uknown. Try to determine the destination
s/uknown/unknown/
> /* Handle a memcpy-like ({mem{,p}cpy,__mem{,p}cpy_chk}) call.
> If strlen of the second argument is known and length of the third argument
> is that plus one, strlen of the first argument is the same after this
> @@ -2512,6 +2789,19 @@ strlen_optimize_stmt (gimple_stmt_iterator *gsi)
You still need to rename strlen_optimize_stmt since after your changes
it does both optimizations and warnings.
I think we're going to need one more iteration on this patch within the
kit. I'm glazing over a bit tonight.
jeff
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 3/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-10 7:17 ` Jeff Law
@ 2017-08-10 7:39 ` Richard Biener
2017-08-10 20:21 ` Martin Sebor
1 sibling, 0 replies; 39+ messages in thread
From: Richard Biener @ 2017-08-10 7:39 UTC (permalink / raw)
To: gcc-patches, Jeff Law, Martin Sebor, Gcc Patch List
On August 10, 2017 7:26:33 AM GMT+02:00, Jeff Law <law@redhat.com> wrote:
>On 08/06/2017 02:07 PM, Martin Sebor wrote:
>> Part 3 of the series contains the meat of the patch: the new
>> -Wstringop-truncation option, and enhancements to -Wstringop-
>> overflow, and -Wpointer-sizeof-memaccess to detect misuses of
>> strncpy and strncat.
>>
>> Martin
>>
>> gcc-81117-3.diff
>>
>>
>> PR c/81117 - Improve buffer overflow checking in strncpy
>>
>> gcc/ChangeLog:
>>
>> PR c/81117
>> * builtins.c (compute_objsize): Handle arrays that
>> compute_builtin_object_size likes to fail for. Make extern.
>> * builtins.h (compute_objsize): Declare.
>> (check_strncpy_sizes): New function.
>> (expand_builtin_strncpy): Call check_strncpy_sizes.
>> * gimple-fold.c (gimple_fold_builtin_strncpy): Implement
>> -Wstringop-truncation.
>> (gimple_fold_builtin_strncat): Same.
>> * gimple.c (gimple_build_call_from_tree): Set call location.
>> * tree-ssa-strlen.c (strlen_to_stridx): New global variable.
>> (maybe_diag_bound_equal_length, is_strlen_related_p): New functions.
>> (handle_builtin_stxncpy, handle_builtin_strncat): Same.
>> (handle_builtin_strlen): Use strlen_to_stridx.
>> (strlen_optimize_stmt): Handle flavors of strncat, strncpy, and
>> stpncpy.
>> Use strlen_to_stridx.
>> (pass_strlen::execute): Release strlen_to_stridx.
>> * doc/invoke.texi (-Wsizeof-pointer-memaccess): Document
>enhancement.
>> (-Wstringop-truncation): Document new option.
>>
>> gcc/c-family/ChangeLog:
>>
>> PR c/81117
>> * c-warn.c (sizeof_pointer_memaccess_warning): Handle arrays.
>> * c.opt (-Wstriingop-truncation): New option.
>>
>> gcc/testsuite/ChangeLog:
>>
>> PR c/81117
>> * c-c++-common/Wsizeof-pointer-memaccess3.c: New test.
>> * c-c++-common/Wstringop-overflow.c: Same.
>> * c-c++-common/Wstringop-truncation.c: Same.
>> * c-c++-common/Wsizeof-pointer-memaccess2.c: Adjust.
>> * c-c++-common/attr-nonstring-2.c: New test.
>> * g++.dg/torture/Wsizeof-pointer-memaccess1.C: Adjust.
>> * g++.dg/torture/Wsizeof-pointer-memaccess2.C: Same.
>> * gcc.dg/torture/pr63554.c: Same.
>> * gcc.dg/Walloca-1.c: Disable macro tracking.
>>
>> diff --git a/gcc/builtins.c b/gcc/builtins.c
>> index 016f68d..1aa9e22 100644
>> --- a/gcc/builtins.c
>> +++ b/gcc/builtins.c
>[ ... ]
>> +
>> + if (TREE_CODE (type) == ARRAY_TYPE)
>> + {
>> + /* Return the constant size unless it's zero (that's a
>zero-length
>> + array likely at the end of a struct). */
>> + tree size = TYPE_SIZE_UNIT (type);
>> + if (size && TREE_CODE (size) == INTEGER_CST
>> + && !integer_zerop (size))
>> + return size;
>> + }
>Q. Do we have a canonical test for the trailing array idiom?
Array_at_struct_end_p
In some
>contexts isn't it size 1? ISTM This test needs slight improvement.
>Ideally we'd use some canonical test for detect the trailing array
>idiom
>rather than open-coding it here. You might look at the array index
>warnings in tree-vrp.c to see if it's got a canonical test you can call
>or factor and use.
>
>
>
>
>> @@ -3883,6 +3920,30 @@ expand_builtin_strncat (tree exp, rtx)
>> return NULL_RTX;
>> }
>>
>> +/* Helper to check the sizes of sequences and the destination of
>calls
>> + to __builtin_strncpy (DST, SRC, LEN) and __builtin___strncpy_chk.
>> + Returns true on success (no overflow warning), false otherwise.
>*/
>> +
>> +static bool
>> +check_strncpy_sizes (tree exp, tree dst, tree src, tree len)
>> +{
>> + tree dstsize = compute_objsize (dst, warn_stringop_overflow - 1);
>> +
>> + if (!check_sizes (OPT_Wstringop_overflow_,
>> + exp, len, /*maxlen=*/NULL_TREE, src, dstsize))
>> + return false;
>> +
>> + if (!dstsize || TREE_CODE (len) != INTEGER_CST)
>> + return true;
>> +
>> + if (tree_int_cst_lt (dstsize, len))
>> + warning_at (EXPR_LOCATION (exp), OPT_Wstringop_truncation,
>> + "%K%qD specified bound %E exceeds destination size %E",
>> + exp, get_callee_fndecl (exp), len, dstsize);
>> +
>> + return true;
>So in the case where you issue the warning, what should the return
>value
>be? According to the comment it should be false. It looks like you
>got
>the wrong return value for the tree_int_cst_lt (dstsize, len) test.
>
>
>
>
>
>>
>> return false;
>> diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c
>> index b0563fe..ac6503f 100644
>> --- a/gcc/tree-ssa-strlen.c
>> +++ b/gcc/tree-ssa-strlen.c
>
>> +
>> +/* A helper of handle_builtin_stxncpy. Check to see if the
>specified
>> + bound is a) equal to the size of the destination DST and if so,
>b)
>> + if it's immediately followed by DST[LEN - 1] = '\0'. If a) holds
>> + and b) does not, warn. Otherwise, do nothing. Return true if
>> + diagnostic has been issued.
>> +
>> + The purpose is to diagnose calls to strncpy and stpncpy that do
>> + not nul-terminate the copy while allowing for the idiom where
>> + such a call is immediately followed by setting the last element
>> + to nul, as in:
>> + char a[32];
>> + strncpy (a, s, sizeof a);
>> + a[sizeof a - 1] = '\0';
>> +*/
>So using gsi_next to find the next statement could make the heuristic
>fail to find the a[sizeof a - 1] = '\0'; statement when debugging is
>enabled.
>
>gsi_next_nondebug would be better as it would skip over any debug
>insns.
>
>What might be even better would be to use the immediate uses of the
>memory tag. For your case there should be only one immediate use and
>it
>should point to the statement which NUL terminates the destination. Or
>maybe that would be worse in that you only want to allow this exception
>when the statements are consecutive.
>
>
>> +
>> + /* Look for dst[i] = '\0'; after the stxncpy() call and if found
>> + avoid the truncation warning. */
>> + gsi_next (&gsi);
>> + gimple *next_stmt = gsi_stmt (gsi);
>Here's the gsi_next I'm referring to.
>
>
>> + else
>> + {
>> + /* The source length is uknown. Try to determine the
>destination
>s/uknown/unknown/
>
>
>
>> /* Handle a memcpy-like ({mem{,p}cpy,__mem{,p}cpy_chk}) call.
>> If strlen of the second argument is known and length of the third
>argument
>> is that plus one, strlen of the first argument is the same after
>this
>> @@ -2512,6 +2789,19 @@ strlen_optimize_stmt (gimple_stmt_iterator
>*gsi)
>You still need to rename strlen_optimize_stmt since after your changes
>it does both optimizations and warnings.
>
>I think we're going to need one more iteration on this patch within the
>kit. I'm glazing over a bit tonight.
>
>jeff
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 3/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-10 7:17 ` Jeff Law
2017-08-10 7:39 ` Richard Biener
@ 2017-08-10 20:21 ` Martin Sebor
2017-08-15 3:06 ` Martin Sebor
2017-10-02 22:15 ` Jeff Law
1 sibling, 2 replies; 39+ messages in thread
From: Martin Sebor @ 2017-08-10 20:21 UTC (permalink / raw)
To: Jeff Law, Gcc Patch List
>> diff --git a/gcc/builtins.c b/gcc/builtins.c
>> index 016f68d..1aa9e22 100644
>> --- a/gcc/builtins.c
>> +++ b/gcc/builtins.c
> [ ... ]
>> +
>> + if (TREE_CODE (type) == ARRAY_TYPE)
>> + {
>> + /* Return the constant size unless it's zero (that's a zero-length
>> + array likely at the end of a struct). */
>> + tree size = TYPE_SIZE_UNIT (type);
>> + if (size && TREE_CODE (size) == INTEGER_CST
>> + && !integer_zerop (size))
>> + return size;
>> + }
> Q. Do we have a canonical test for the trailing array idiom? In some
> contexts isn't it size 1? ISTM This test needs slight improvement.
> Ideally we'd use some canonical test for detect the trailing array idiom
> rather than open-coding it here. You might look at the array index
> warnings in tree-vrp.c to see if it's got a canonical test you can call
> or factor and use.
You're right, there is an API for this (array_at_struct_end_p,
as Richard pointed out). I didn't want to use it because it
treats any array at the end of a struct as a flexible array
member, but simple tests show that that's what -Wstringop-
overflow does now, and it wasn't my intention to tighten up
the checking under this change. It surprises me that no tests
exposed this. Let me relax the check and think about proposing
to tighten it up separately.
>> @@ -3883,6 +3920,30 @@ expand_builtin_strncat (tree exp, rtx)
>> return NULL_RTX;
>> }
>>
>> +/* Helper to check the sizes of sequences and the destination of calls
>> + to __builtin_strncpy (DST, SRC, LEN) and __builtin___strncpy_chk.
>> + Returns true on success (no overflow warning), false otherwise. */
>> +
>> +static bool
>> +check_strncpy_sizes (tree exp, tree dst, tree src, tree len)
>> +{
>> + tree dstsize = compute_objsize (dst, warn_stringop_overflow - 1);
>> +
>> + if (!check_sizes (OPT_Wstringop_overflow_,
>> + exp, len, /*maxlen=*/NULL_TREE, src, dstsize))
>> + return false;
>> +
>> + if (!dstsize || TREE_CODE (len) != INTEGER_CST)
>> + return true;
>> +
>> + if (tree_int_cst_lt (dstsize, len))
>> + warning_at (EXPR_LOCATION (exp), OPT_Wstringop_truncation,
>> + "%K%qD specified bound %E exceeds destination size %E",
>> + exp, get_callee_fndecl (exp), len, dstsize);
>> +
>> + return true;
> So in the case where you issue the warning, what should the return value
> be? According to the comment it should be false. It looks like you got
> the wrong return value for the tree_int_cst_lt (dstsize, len) test.
Corrected. The return value is unused by the only caller so
there is no test to exercise it.
>> return false;
>> diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c
>> index b0563fe..ac6503f 100644
>> --- a/gcc/tree-ssa-strlen.c
>> +++ b/gcc/tree-ssa-strlen.c
>
>> +
>> +/* A helper of handle_builtin_stxncpy. Check to see if the specified
>> + bound is a) equal to the size of the destination DST and if so, b)
>> + if it's immediately followed by DST[LEN - 1] = '\0'. If a) holds
>> + and b) does not, warn. Otherwise, do nothing. Return true if
>> + diagnostic has been issued.
>> +
>> + The purpose is to diagnose calls to strncpy and stpncpy that do
>> + not nul-terminate the copy while allowing for the idiom where
>> + such a call is immediately followed by setting the last element
>> + to nul, as in:
>> + char a[32];
>> + strncpy (a, s, sizeof a);
>> + a[sizeof a - 1] = '\0';
>> +*/
> So using gsi_next to find the next statement could make the heuristic
> fail to find the a[sizeof a - 1] = '\0'; statement when debugging is
> enabled.
>
> gsi_next_nondebug would be better as it would skip over any debug insns.
Thanks. I'll have to remember this.
> What might be even better would be to use the immediate uses of the
> memory tag. For your case there should be only one immediate use and it
> should point to the statement which NUL terminates the destination. Or
> maybe that would be worse in that you only want to allow this exception
> when the statements are consecutive.
I'll have to try this to better understand how it might work.
>> + /* Look for dst[i] = '\0'; after the stxncpy() call and if found
>> + avoid the truncation warning. */
>> + gsi_next (&gsi);
>> + gimple *next_stmt = gsi_stmt (gsi);
> Here's the gsi_next I'm referring to.
>
>
>> + else
>> + {
>> + /* The source length is uknown. Try to determine the destination
> s/uknown/unknown/
>
>
>
>> /* Handle a memcpy-like ({mem{,p}cpy,__mem{,p}cpy_chk}) call.
>> If strlen of the second argument is known and length of the third argument
>> is that plus one, strlen of the first argument is the same after this
>> @@ -2512,6 +2789,19 @@ strlen_optimize_stmt (gimple_stmt_iterator *gsi)
> You still need to rename strlen_optimize_stmt since after your changes
> it does both optimizations and warnings.
I'm not sure I understand why. It's a pre-existing function that
just dispatches to the built-in handlers. We don't rename function
callers each time we improve error/warning detection in some
function they call (case in point: all the expanders in builtins.c)
Why do it here? And what would be a suitable name? All that comes
to my mind is awkward variations on strlen_optimize_stmt_and_warn.
> I think we're going to need one more iteration on this patch within the
> kit. I'm glazing over a bit tonight.
Sure. I'm working on a few things at the same time so let me try
to make some progress on those and post an updated of this work
next week.
Martin
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 2/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-10 6:39 ` Jeff Law
@ 2017-08-14 18:04 ` Martin Sebor
2017-08-14 18:29 ` Joseph Myers
0 siblings, 1 reply; 39+ messages in thread
From: Martin Sebor @ 2017-08-14 18:04 UTC (permalink / raw)
To: Jeff Law, Gcc Patch List
On 08/09/2017 11:00 PM, Jeff Law wrote:
> On 08/06/2017 02:07 PM, Martin Sebor wrote:
>> Part 2 of the series adds attribute nostring to annotate arrays
>> of and pointers to char with that are intended to store sequences
>> of characters that aren't necessarily valid (nul-terminated)
>> strings. In the subsequent patch the attribute is relied on to
>> avoid diagnosing strcncpy calls that truncate strings and create
>> such copies. In the future I'd like to also use the attribute
>> to diagnose when arrays or pointers with the attribute are passed
>> to functions that expect nul-terminated strings (such as strlen
>> or strcpy).
>>
>> Martin
>>
>>
>> gcc-81117-2.diff
>>
>>
>> PR c/81117 - Improve buffer overflow checking in strncpy
>>
>> gcc/ChangeLog:
>>
>> PR c/81117
>> * builtin-attrs.def (attribute nonstring): New.
>> * doc/extend.texi (attribute nonstring): Document new attribute.
>>
>> gcc/c-family/ChangeLog:
>>
>> PR c/81117
>> * c-attribs.c (c_common_attribute_table): Add nonstring entry.
>> (handle_nonstring_attribute): New function.
>>
>> gcc/testsuite/ChangeLog:
>>
>> PR c/81117
>> * c-c++-common/attr-nonstring-1.c: New test.
>>
>> --- a/gcc/builtin-attrs.def
>> +++ b/gcc/builtin-attrs.def
>> @@ -93,6 +93,7 @@ DEF_ATTR_IDENT (ATTR_FORMAT, "format")
>> DEF_ATTR_IDENT (ATTR_FORMAT_ARG, "format_arg")
>> DEF_ATTR_IDENT (ATTR_MALLOC, "malloc")
>> DEF_ATTR_IDENT (ATTR_NONNULL, "nonnull")
>> +DEF_ATTR_IDENT (ATTR_NONSTRING, "nonstring")
>> DEF_ATTR_IDENT (ATTR_NORETURN, "noreturn")
>> DEF_ATTR_IDENT (ATTR_NOTHROW, "nothrow")
>> DEF_ATTR_IDENT (ATTR_LEAF, "leaf")
> So all the attributes here are associated with functions I believe.
> You're defining a variable attribute. In fact, I'm not even sure that
> variable attributes get a DEF_ATTR_<whatever>
I assumed every attribute needed to define an identifier but
nothing broke after I removed it so it looks like you're right
variable attributes don't need one. Go figure. It would be
nice if there was a comment above the block that mentioned that.
I'll try to remember to add one separately.
>> diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
>> index b253ccc..1954ca5 100644
>> --- a/gcc/doc/extend.texi
>> +++ b/gcc/doc/extend.texi
>> @@ -5835,6 +5835,30 @@ The @code{deprecated} attribute can also be used for functions and
>> types (@pxref{Common Function Attributes},
>> @pxref{Common Type Attributes}).
>>
>> +@item nonstring (@var{nonstring})
>> +@cindex @code{nonstring} variable attribute
>> +The @code{nonstring} variable attribute specifies that an object or member
>> +declaration with type array of @code{char} or pointer to @code{char} is
>> +intended to store character arrays that do not necessarily contain
>> +a terminating @code{NUL} character. This is useful to avoid warnings
>> +when such an array or pointer is used as an argument to a bounded string
>> +manipulation function such as @code{strncpy}. For example, without the
>> +attribute, GCC will issue a warning for the call below because it may
>> +truncate the copy without appending the terminating NUL character. Using
>> +the attribute makes it possible to suppress the warning.
> [ ... ]
> I think this is in the wrong section, I believe it belongs in the
> "Variable Attributes" section.
It is in the Variable Attributes section. The "pxref{Common Type
Attributes})." reference above is just a cross-reference to the
Type Attributes section.
> Assuming you don't actually need the ATTR_NONSTRING, this patch is fine
> with that hunk removed and the documentation moved into the right section.
Okay, thanks.
Martin
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 2/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-14 18:04 ` Martin Sebor
@ 2017-08-14 18:29 ` Joseph Myers
2017-08-14 19:26 ` Martin Sebor
0 siblings, 1 reply; 39+ messages in thread
From: Joseph Myers @ 2017-08-14 18:29 UTC (permalink / raw)
To: Martin Sebor; +Cc: Jeff Law, Gcc Patch List
On Mon, 14 Aug 2017, Martin Sebor wrote:
> I assumed every attribute needed to define an identifier but
> nothing broke after I removed it so it looks like you're right
> variable attributes don't need one. Go figure. It would be
> nice if there was a comment above the block that mentioned that.
> I'll try to remember to add one separately.
builtin-attrs.def is only for attributes used in defining built-in
functions. If no built-in function uses an attribute, it should not be
defined there.
--
Joseph S. Myers
joseph@codesourcery.com
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 1/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-10 5:02 ` Jeff Law
@ 2017-08-14 19:21 ` Martin Sebor
0 siblings, 0 replies; 39+ messages in thread
From: Martin Sebor @ 2017-08-14 19:21 UTC (permalink / raw)
To: Jeff Law, Gcc Patch List
[-- Attachment #1: Type: text/plain, Size: 2629 bytes --]
On 08/09/2017 10:44 PM, Jeff Law wrote:
> On 08/06/2017 02:07 PM, Martin Sebor wrote:
>> The attached patch adds support for a new GCC format specifier,
>> G, that behaves like %K but accepts a gcall* argument. This
>> makes it possible to provide inlining context for "artificial"
>> inline functions like strncpy (with _FORTIFY_SOURCE) in
>> diagnostics issued from the middle end.
>>
>> Martin
>>
>> gcc-81117-1.diff
>>
>>
>> PR c/81117 - Improve buffer overflow checking in strncpy
>>
>> gcc/ChangeLog:
>>
>> PR c/81117
>> * tree-diagnostic.c (default_tree_printer): Handle %G.
>> * tree-pretty-print.h (percent_G_format): Declare new function.
>> * tree-pretty-print.c (percent_K_format): Define a static overload.
>> (percent_G_format): New function.
>>
>> gcc/c/ChangeLog:
>>
>> PR c/81117
>> * c-objc-common.c (c_objc_common_init): Handle 'G'.
>>
>> gcc/c-family/ChangeLog:
>>
>> * c-format.h (T89_G): New macro.
>> * c-format.c (local_gcall_ptr_node): New variable.
>> (init_dynamic_diag_info): Initialize it.
>>
>> gcc/cp/ChangeLog:
>>
>> PR c/81117
>> * error.c (cp_printer): Handle 'G'.
>>
>> gcc/testsuite/ChangeLog:
>>
>> PR c/81117
>> * gcc.dg/format/gcc_diag-10.c: Exercise %G.
>>
>> diff --git a/gcc/tree-diagnostic.c b/gcc/tree-diagnostic.c
>> index 52b7e7f..ad58b69 100644
>> --- a/gcc/tree-diagnostic.c
>> +++ b/gcc/tree-diagnostic.c
>> @@ -275,6 +275,10 @@ default_tree_printer (pretty_printer *pp, text_info *text, const char *spec,
>> t = va_arg (*text->args_ptr, tree);
>> break;
>>
>> + case 'G':
>> + percent_G_format (text);
>> + return true;
>> +
>> case 'K':
>> percent_K_format (text);
>> return true;
>> diff --git a/gcc/tree-pretty-print.c b/gcc/tree-pretty-print.c
>> index 4d8177c..7c4c805 100644
>> --- a/gcc/tree-pretty-print.c
>> +++ b/gcc/tree-pretty-print.c
>> @@ -33,6 +33,7 @@ along with GCC; see the file COPYING3. If not see
>> #include "dumpfile.h"
>> #include "internal-fn.h"
>> #include "gomp-constants.h"
>> +#include "gimple.h"
> This is an indication you're probably putting the 'G' handling in the
> wrong place. Wouldn't gimple-pretty-print.c be more correct?
>
> That's my only objection to this patch, so if it moves trivially, then
> it's pre-approved. If it's non-trivial, then we'll want another iteration.
I moved it and after retesting committed in r251098. The diff
is attached for reference.
Btw., there are lots of test suite failures that make it tricky
to spot regressions, mostly in ASan tests but others too (e.g.,
g++.dg/debug/debug9.C, g++.dg/template/nontype10.C, and some
others).
Martin
[-- Attachment #2: gcc-percent-G.diff --]
[-- Type: text/x-patch, Size: 18165 bytes --]
Index: gcc/tree-pretty-print.c
===================================================================
--- gcc/tree-pretty-print.c (revision 251097)
+++ gcc/tree-pretty-print.c (revision 251098)
@@ -33,6 +33,7 @@ along with GCC; see the file COPYING3. If not see
#include "dumpfile.h"
#include "internal-fn.h"
#include "gomp-constants.h"
+#include "gimple.h"
/* Local functions, macros and variables. */
static const char *op_symbol (const_tree);
@@ -3970,18 +3971,17 @@ newline_and_indent (pretty_printer *pp, int spc)
INDENT (spc);
}
-/* Handle a %K format for TEXT. Separate from default_tree_printer so
- it can also be used in front ends.
- %K: a statement, from which EXPR_LOCATION and TREE_BLOCK will be recorded.
-*/
+/* Handle the %K format for TEXT. Separate from default_tree_printer
+ so it can also be used in front ends.
+ Argument is a statement from which EXPR_LOCATION and TREE_BLOCK will
+ be recorded. */
void
-percent_K_format (text_info *text)
+percent_K_format (text_info *text, tree t)
{
- tree t = va_arg (*text->args_ptr, tree), block;
text->set_location (0, EXPR_LOCATION (t), true);
gcc_assert (pp_ti_abstract_origin (text) != NULL);
- block = TREE_BLOCK (t);
+ tree block = TREE_BLOCK (t);
*pp_ti_abstract_origin (text) = NULL;
if (in_lto_p)
Index: gcc/c-family/ChangeLog
===================================================================
--- gcc/c-family/ChangeLog (revision 251097)
+++ gcc/c-family/ChangeLog (revision 251098)
@@ -1,3 +1,10 @@
+2017-08-14 Martin Sebor <msebor@redhat.com>
+
+ PR c/81117
+ * c-format.h (T89_G): New macro.
+ * c-format.c (local_gcall_ptr_node): New variable.
+ (init_dynamic_diag_info): Initialize it.
+
2017-08-11 Martin Liska <mliska@suse.cz>
* c-opts.c (c_common_post_options): Replace ASM_OUTPUT_DEF with
Index: gcc/c-family/c-format.c
===================================================================
--- gcc/c-family/c-format.c (revision 251097)
+++ gcc/c-family/c-format.c (revision 251098)
@@ -56,6 +56,7 @@ struct function_format_info
/* Initialized in init_dynamic_diag_info. */
static GTY(()) tree local_tree_type_node;
+static GTY(()) tree local_gcall_ptr_node;
static GTY(()) tree locus;
static bool decode_format_attr (tree, function_format_info *, int);
@@ -689,7 +690,9 @@ static const format_char_info gcc_diag_char_table[
/* Custom conversion specifiers. */
- /* These will require a "tree" at runtime. */
+ /* G requires a "gcall*" argument at runtime. */
+ { "G", 1, STD_C89, { T89_G, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL },
+ /* K requires a "tree" argument at runtime. */
{ "K", 1, STD_C89, { T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL },
{ "r", 1, STD_C89, { T89_C, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "//cR", NULL },
@@ -718,6 +721,9 @@ static const format_char_info gcc_tdiag_char_table
{ "E", 1, STD_C89, { T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q+", "", NULL },
{ "K", 1, STD_C89, { T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL },
+ /* G requires a "gcall*" argument at runtime. */
+ { "G", 1, STD_C89, { T89_G, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL },
+
{ "v", 0, STD_C89, { T89_I, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q#", "", NULL },
{ "r", 1, STD_C89, { T89_C, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "/cR", NULL },
@@ -747,6 +753,9 @@ static const format_char_info gcc_cdiag_char_table
{ "E", 1, STD_C89, { T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q+", "", NULL },
{ "K", 1, STD_C89, { T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL },
+ /* G requires a "gcall*" argument at runtime. */
+ { "G", 1, STD_C89, { T89_G, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL },
+
{ "v", 0, STD_C89, { T89_I, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q#", "", NULL },
{ "r", 1, STD_C89, { T89_C, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "/cR", NULL },
@@ -777,6 +786,9 @@ static const format_char_info gcc_cxxdiag_char_tab
{ "K", 1, STD_C89,{ T89_T, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL },
{ "v", 0,STD_C89, { T89_I, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q#", "", NULL },
+ /* G requires a "gcall*" argument at runtime. */
+ { "G", 1, STD_C89,{ T89_G, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL },
+
/* These accept either an 'int' or an 'enum tree_code' (which is handled as an 'int'.) */
{ "CLOPQ",0,STD_C89, { T89_I, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q", "", NULL },
@@ -3834,6 +3846,29 @@ init_dynamic_diag_info (void)
local_tree_type_node = void_type_node;
}
+ /* Similar to the above but for gcall*. */
+ if (!local_gcall_ptr_node
+ || local_gcall_ptr_node == void_type_node)
+ {
+ if ((local_gcall_ptr_node = maybe_get_identifier ("gcall")))
+ {
+ local_gcall_ptr_node
+ = identifier_global_value (local_gcall_ptr_node);
+ if (local_gcall_ptr_node)
+ {
+ if (TREE_CODE (local_gcall_ptr_node) != TYPE_DECL)
+ {
+ error ("%<gcall%> is not defined as a type");
+ local_gcall_ptr_node = 0;
+ }
+ else
+ local_gcall_ptr_node = TREE_TYPE (local_gcall_ptr_node);
+ }
+ }
+ else
+ local_gcall_ptr_node = void_type_node;
+ }
+
static tree hwi;
if (!hwi)
Index: gcc/c-family/c-format.h
===================================================================
--- gcc/c-family/c-format.h (revision 251097)
+++ gcc/c-family/c-format.h (revision 251098)
@@ -298,6 +298,7 @@ struct format_kind_info
#define T_UC &unsigned_char_type_node
#define T99_UC { STD_C99, NULL, T_UC }
#define T_V &void_type_node
+#define T89_G { STD_C89, NULL, &local_gcall_ptr_node }
#define T89_T { STD_C89, NULL, &local_tree_type_node }
#define T89_V { STD_C89, NULL, T_V }
#define T_W &wchar_type_node
Index: gcc/tree-pretty-print.h
===================================================================
--- gcc/tree-pretty-print.h (revision 251097)
+++ gcc/tree-pretty-print.h (revision 251098)
@@ -45,7 +45,7 @@ extern int op_code_prio (enum tree_code);
extern int op_prio (const_tree);
extern const char *op_symbol_code (enum tree_code);
extern void print_call_name (pretty_printer *, tree, dump_flags_t);
-extern void percent_K_format (text_info *);
+extern void percent_K_format (text_info *, tree);
extern void pp_tree_identifier (pretty_printer *, tree);
extern void dump_function_header (FILE *, tree, dump_flags_t);
extern void pp_double_int (pretty_printer *pp, double_int d, bool uns);
Index: gcc/c/ChangeLog
===================================================================
--- gcc/c/ChangeLog (revision 251097)
+++ gcc/c/ChangeLog (revision 251098)
@@ -1,3 +1,8 @@
+2017-08-14 Martin Sebor <msebor@redhat.com>
+
+ PR c/81117
+ * c-objc-common.c (c_objc_common_init): Handle 'G'.
+
2017-08-11 Marek Polacek <polacek@redhat.com>
PR c/81795
Index: gcc/c/c-objc-common.c
===================================================================
--- gcc/c/c-objc-common.c (revision 251097)
+++ gcc/c/c-objc-common.c (revision 251098)
@@ -24,6 +24,7 @@ along with GCC; see the file COPYING3. If not see
#include "intl.h"
#include "c-family/c-pretty-print.h"
#include "tree-pretty-print.h"
+#include "gimple-pretty-print.h"
#include "langhooks.h"
#include "c-objc-common.h"
@@ -66,6 +67,8 @@ c_objc_common_init (void)
%D: a general decl,
%E: an identifier or expression,
%F: a function declaration,
+ %G: a Gimple call statement,
+ %K: a CALL_EXPR,
%T: a type.
%V: a list of type qualifiers from a tree.
%v: an explicit list of type qualifiers
@@ -87,9 +90,16 @@ c_tree_printer (pretty_printer *pp, text_info *tex
if (precision != 0 || wide)
return false;
+ if (*spec == 'G')
+ {
+ percent_G_format (text);
+ return true;
+ }
+
if (*spec == 'K')
{
- percent_K_format (text);
+ t = va_arg (*text->args_ptr, tree);
+ percent_K_format (text, t);
return true;
}
Index: gcc/tree-diagnostic.c
===================================================================
--- gcc/tree-diagnostic.c (revision 251097)
+++ gcc/tree-diagnostic.c (revision 251098)
@@ -25,6 +25,7 @@ along with GCC; see the file COPYING3. If not see
#include "tree.h"
#include "diagnostic.h"
#include "tree-pretty-print.h"
+#include "gimple-pretty-print.h"
#include "tree-diagnostic.h"
#include "langhooks.h"
#include "intl.h"
@@ -275,8 +276,13 @@ default_tree_printer (pretty_printer *pp, text_inf
t = va_arg (*text->args_ptr, tree);
break;
+ case 'G':
+ percent_G_format (text);
+ return true;
+
case 'K':
- percent_K_format (text);
+ t = va_arg (*text->args_ptr, tree);
+ percent_K_format (text, t);
return true;
default:
Index: gcc/ChangeLog
===================================================================
--- gcc/ChangeLog (revision 251097)
+++ gcc/ChangeLog (revision 251098)
@@ -1,5 +1,13 @@
2017-08-14 Martin Sebor <msebor@redhat.com>
+ PR c/81117
+ * tree-diagnostic.c (default_tree_printer): Handle %G.
+ * gimple-pretty-print.h (percent_G_format): Declare new function.
+ * gimple-pretty-print.c (percent_G_format): Define.
+ * tree-pretty-print.c (percent_K_format): Add argument.
+
+2017-08-14 Martin Sebor <msebor@redhat.com>
+
PR translation/79998
* gimple-ssa-sprintf.c (pass_sprintf_length::handle_gimple_call):
Remove a stray space.
Index: gcc/testsuite/gcc.dg/format/gcc_diag-10.c
===================================================================
--- gcc/testsuite/gcc.dg/format/gcc_diag-10.c (revision 251097)
+++ gcc/testsuite/gcc.dg/format/gcc_diag-10.c (revision 251098)
@@ -15,6 +15,9 @@ typedef struct location_s
union tree_node;
typedef union tree_node *tree;
+/* Define gcall as a dummy type. The typedef must be provided for
+ the C test to find the symbol. */
+typedef struct gcall gcall;
#define FORMAT(kind) __attribute__ ((format (__gcc_## kind ##__, 1, 2)))
@@ -23,12 +26,13 @@ void cdiag (const char*, ...) FORMAT (cdiag);
void tdiag (const char*, ...) FORMAT (tdiag);
void cxxdiag (const char*, ...) FORMAT (cxxdiag);
-void test_diag (tree t)
+void test_diag (tree t, gcall *gc)
{
diag ("%<"); /* { dg-warning "unterminated quoting directive" } */
diag ("%>"); /* { dg-warning "unmatched quoting directive " } */
diag ("%<foo%<bar%>%>"); /* { dg-warning "nested quoting directive" } */
+ diag ("%G", gc);
diag ("%K", t);
diag ("%R"); /* { dg-warning "unmatched color reset directive" } */
@@ -38,6 +42,7 @@ void cxxdiag (const char*, ...) FORMAT (cxxdiag);
diag ("%r%r%R", "", "");
diag ("%r%R%r%R", "", "");
+ diag ("%<%G%>", gc); /* { dg-warning ".G. conversion used within a quoted sequence" } */
diag ("%<%K%>", t); /* { dg-warning ".K. conversion used within a quoted sequence" } */
diag ("%<%R%>"); /* { dg-warning "unmatched color reset directive" } */
@@ -45,7 +50,7 @@ void cxxdiag (const char*, ...) FORMAT (cxxdiag);
diag ("%<%r%R%>", "");
}
-void test_cdiag (tree t)
+void test_cdiag (tree t, gcall *gc)
{
cdiag ("%<"); /* { dg-warning "unterminated quoting directive" } */
cdiag ("%>"); /* { dg-warning "unmatched quoting directive " } */
@@ -54,6 +59,7 @@ void cxxdiag (const char*, ...) FORMAT (cxxdiag);
cdiag ("%D", t); /* { dg-warning ".D. conversion used unquoted" } */
cdiag ("%E", t);
cdiag ("%F", t); /* { dg-warning ".F. conversion used unquoted" } */
+ cdiag ("%G", gc);
cdiag ("%K", t);
cdiag ("%R"); /* { dg-warning "unmatched color reset directive" } */
@@ -69,6 +75,7 @@ void cxxdiag (const char*, ...) FORMAT (cxxdiag);
cdiag ("%<%D%>", t);
cdiag ("%<%E%>", t);
cdiag ("%<%F%>", t);
+ cdiag ("%<%G%>", gc); /* { dg-warning ".G. conversion used within a quoted sequence" } */
cdiag ("%<%K%>", t); /* { dg-warning ".K. conversion used within a quoted sequence" } */
cdiag ("%<%R%>"); /* { dg-warning "unmatched color reset directive" } */
@@ -83,7 +90,7 @@ void cxxdiag (const char*, ...) FORMAT (cxxdiag);
cdiag ("%<%qT%>", t); /* { dg-warning ".q. flag used within a quoted sequence" } */
}
-void test_tdiag (tree t)
+void test_tdiag (tree t, gcall *gc)
{
tdiag ("%<"); /* { dg-warning "unterminated quoting directive" } */
tdiag ("%>"); /* { dg-warning "unmatched quoting directive " } */
@@ -91,6 +98,7 @@ void cxxdiag (const char*, ...) FORMAT (cxxdiag);
tdiag ("%D", t); /* { dg-warning ".D. conversion used unquoted" } */
tdiag ("%E", t);
+ tdiag ("%G", gc);
tdiag ("%K", t);
tdiag ("%R"); /* { dg-warning "unmatched color reset directive" } */
@@ -105,6 +113,7 @@ void cxxdiag (const char*, ...) FORMAT (cxxdiag);
tdiag ("%<%D%>", t);
tdiag ("%<%E%>", t);
+ tdiag ("%<%G%>", gc); /* { dg-warning ".G. conversion used within a quoted sequence" } */
tdiag ("%<%K%>", t); /* { dg-warning ".K. conversion used within a quoted sequence" } */
tdiag ("%<%R%>"); /* { dg-warning "unmatched color reset directive" } */
@@ -118,12 +127,14 @@ void cxxdiag (const char*, ...) FORMAT (cxxdiag);
tdiag ("%<%qT%>", t); /* { dg-warning ".q. flag used within a quoted sequence" } */
}
-void test_cxxdiag (tree t)
+void test_cxxdiag (tree t, gcall *gc)
{
cxxdiag ("%A", t); /* { dg-warning ".A. conversion used unquoted" } */
cxxdiag ("%D", t); /* { dg-warning ".D. conversion used unquoted" } */
cxxdiag ("%E", t);
cxxdiag ("%F", t); /* { dg-warning ".F. conversion used unquoted" } */
+ cxxdiag ("%G", gc);
+ cxxdiag ("%K", t);
cxxdiag ("%R"); /* { dg-warning "unmatched color reset directive" } */
cxxdiag ("%r", ""); /* { dg-warning "unterminated color directive" } */
Index: gcc/testsuite/ChangeLog
===================================================================
--- gcc/testsuite/ChangeLog (revision 251097)
+++ gcc/testsuite/ChangeLog (revision 251098)
@@ -1,3 +1,8 @@
+2017-08-14 Martin Sebor <msebor@redhat.com>
+
+ PR c/81117
+ * gcc.dg/format/gcc_diag-10.c: Exercise %G.
+
2017-08-14 David Edelsohn <dje.gcc@gmail.com>
* gcc.dg/ucnid-5.c: Skip on AIX.
Index: gcc/cp/error.c
===================================================================
--- gcc/cp/error.c (revision 251097)
+++ gcc/cp/error.c (revision 251098)
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3. If not see
#include "intl.h"
#include "cxx-pretty-print.h"
#include "tree-pretty-print.h"
+#include "gimple-pretty-print.h"
#include "c-family/c-objc.h"
#include "ubsan.h"
#include "internal-fn.h"
@@ -4050,8 +4051,13 @@ cp_printer (pretty_printer *pp, text_info *text, c
case 'V': result = cv_to_string (next_tree, verbose); break;
case 'X': result = eh_spec_to_string (next_tree, verbose); break;
+ case 'G':
+ percent_G_format (text);
+ return true;
+
case 'K':
- percent_K_format (text);
+ t = va_arg (*text->args_ptr, tree);
+ percent_K_format (text, t);
return true;
case 'H':
Index: gcc/cp/ChangeLog
===================================================================
--- gcc/cp/ChangeLog (revision 251097)
+++ gcc/cp/ChangeLog (revision 251098)
@@ -1,3 +1,8 @@
+2017-08-14 Martin Sebor <msebor@redhat.com>
+
+ PR c/81117
+ * error.c (cp_printer): Handle 'G'.
+
2017-08-11 Martin Liska <mliska@suse.cz>
* decl2.c (get_tls_init_fn): Replace ASM_OUTPUT_DEF with
Index: gcc/gimple-pretty-print.c
===================================================================
--- gcc/gimple-pretty-print.c (revision 251097)
+++ gcc/gimple-pretty-print.c (revision 251098)
@@ -2911,3 +2911,22 @@ gimple_dump_bb_for_graph (pretty_printer *pp, basi
pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
}
+
+/* Handle the %G format for TEXT. Same as %K in handle_K_format in
+ tree-pretty-print.c but with a Gimple call statement as an argument. */
+
+void
+percent_G_format (text_info *text)
+{
+ gcall *stmt = va_arg (*text->args_ptr, gcall*);
+
+ /* Build a call expression from the Gimple call statement and
+ pass it to the K formatter that knows how to format it. */
+ tree exp = build_vl_exp (CALL_EXPR, gimple_call_num_args (stmt) + 3);
+ CALL_EXPR_FN (exp) = gimple_call_fn (stmt);
+ TREE_TYPE (exp) = gimple_call_return_type (stmt);
+ CALL_EXPR_STATIC_CHAIN (exp) = gimple_call_chain (stmt);
+ SET_EXPR_LOCATION (exp, gimple_location (stmt));
+
+ percent_K_format (text, exp);
+}
Index: gcc/gimple-pretty-print.h
===================================================================
--- gcc/gimple-pretty-print.h (revision 251097)
+++ gcc/gimple-pretty-print.h (revision 251098)
@@ -35,5 +35,6 @@ extern void pp_gimple_stmt_1 (pretty_printer *, gi
extern void gimple_dump_bb (FILE *, basic_block, int, dump_flags_t);
extern void gimple_dump_bb_for_graph (pretty_printer *, basic_block);
extern void dump_ssaname_info_to_file (FILE *, tree, int);
+extern void percent_G_format (text_info *);
#endif /* ! GCC_GIMPLE_PRETTY_PRINT_H */
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 2/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-14 18:29 ` Joseph Myers
@ 2017-08-14 19:26 ` Martin Sebor
2017-08-14 20:41 ` Joseph Myers
0 siblings, 1 reply; 39+ messages in thread
From: Martin Sebor @ 2017-08-14 19:26 UTC (permalink / raw)
To: Joseph Myers; +Cc: Jeff Law, Gcc Patch List
On 08/14/2017 12:09 PM, Joseph Myers wrote:
> On Mon, 14 Aug 2017, Martin Sebor wrote:
>
>> I assumed every attribute needed to define an identifier but
>> nothing broke after I removed it so it looks like you're right
>> variable attributes don't need one. Go figure. It would be
>> nice if there was a comment above the block that mentioned that.
>> I'll try to remember to add one separately.
>
> builtin-attrs.def is only for attributes used in defining built-in
> functions. If no built-in function uses an attribute, it should not be
> defined there.
Okay, thanks for confirming that. Here's a patch to make it
clear. Please let me know if it's okay to commit.
Martin
2017-08-14 Martin Sebor <msebor@redhat.com>
* builtin-attrs.def: Add comments.
Index: gcc/builtin-attrs.def
===================================================================
--- gcc/builtin-attrs.def (revision 251097)
+++ gcc/builtin-attrs.def (working copy)
@@ -18,7 +18,9 @@ along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
/* This header provides a declarative way of describing the attributes
- that are applied to some functions by default.
+ that are applied to some built-in functions by default. Attributes
+ that apply to types or variables but not functions need not and
+ should not be defined here.
Before including this header, you must define the following macros.
In each case where there is an ENUM, it is an identifier used to
@@ -85,7 +87,7 @@ DEF_LIST_INT_INT (5,0)
DEF_LIST_INT_INT (5,6)
#undef DEF_LIST_INT_INT
-/* Construct trees for identifiers. */
+/* Construct trees for identifiers used in function attributes. */
DEF_ATTR_IDENT (ATTR_ALLOC_SIZE, "alloc_size")
DEF_ATTR_IDENT (ATTR_COLD, "cold")
DEF_ATTR_IDENT (ATTR_CONST, "const")
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 2/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-14 19:26 ` Martin Sebor
@ 2017-08-14 20:41 ` Joseph Myers
2017-08-14 20:44 ` Martin Sebor
0 siblings, 1 reply; 39+ messages in thread
From: Joseph Myers @ 2017-08-14 20:41 UTC (permalink / raw)
To: Martin Sebor; +Cc: Jeff Law, Gcc Patch List
On Mon, 14 Aug 2017, Martin Sebor wrote:
> /* This header provides a declarative way of describing the attributes
> - that are applied to some functions by default.
> + that are applied to some built-in functions by default. Attributes
> + that apply to types or variables but not functions need not and
> + should not be defined here.
It's not just type and variable attributes that shouldn't be here. Any
function attribute that's not used by at least one built-in function
shouldn't be here either. Every tree constructed here adds to startup
costs; they should only be present if actually used.
--
Joseph S. Myers
joseph@codesourcery.com
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 2/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-14 20:41 ` Joseph Myers
@ 2017-08-14 20:44 ` Martin Sebor
2017-08-15 3:03 ` Joseph Myers
0 siblings, 1 reply; 39+ messages in thread
From: Martin Sebor @ 2017-08-14 20:44 UTC (permalink / raw)
To: Joseph Myers; +Cc: Jeff Law, Gcc Patch List
On 08/14/2017 01:50 PM, Joseph Myers wrote:
> On Mon, 14 Aug 2017, Martin Sebor wrote:
>
>> /* This header provides a declarative way of describing the attributes
>> - that are applied to some functions by default.
>> + that are applied to some built-in functions by default. Attributes
>> + that apply to types or variables but not functions need not and
>> + should not be defined here.
>
> It's not just type and variable attributes that shouldn't be here. Any
> function attribute that's not used by at least one built-in function
> shouldn't be here either. Every tree constructed here adds to startup
> costs; they should only be present if actually used.
Okay. I expanded on that point in the updated comments below.
Martin
2017-08-14 Martin Sebor <msebor@redhat.com>
* builtin-attrs.def: Add comments.
Index: gcc/builtin-attrs.def
===================================================================
--- gcc/builtin-attrs.def (revision 251097)
+++ gcc/builtin-attrs.def (working copy)
@@ -18,7 +18,10 @@ along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
/* This header provides a declarative way of describing the attributes
- that are applied to some functions by default.
+ that are applied to some built-in functions by default. Attributes
+ that are meant to be used by user-defined functions but aren't used
+ by any built-ins, or attributes that apply to types or variables
+ but not to functions need not and should not be defined here.
Before including this header, you must define the following macros.
In each case where there is an ENUM, it is an identifier used to
@@ -85,7 +88,9 @@ DEF_LIST_INT_INT (5,0)
DEF_LIST_INT_INT (5,6)
#undef DEF_LIST_INT_INT
-/* Construct trees for identifiers. */
+/* Construct trees for identifiers used in built-in function attributes.
+ The construction contributes to startup costs so only attributes that
+ are used to define built-ins should be defined here. */
DEF_ATTR_IDENT (ATTR_ALLOC_SIZE, "alloc_size")
DEF_ATTR_IDENT (ATTR_COLD, "cold")
DEF_ATTR_IDENT (ATTR_CONST, "const")
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 2/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-14 20:44 ` Martin Sebor
@ 2017-08-15 3:03 ` Joseph Myers
0 siblings, 0 replies; 39+ messages in thread
From: Joseph Myers @ 2017-08-15 3:03 UTC (permalink / raw)
To: Martin Sebor; +Cc: Jeff Law, Gcc Patch List
On Mon, 14 Aug 2017, Martin Sebor wrote:
> Okay. I expanded on that point in the updated comments below.
>
> Martin
>
> 2017-08-14 Martin Sebor <msebor@redhat.com>
>
> * builtin-attrs.def: Add comments.
This version is OK.
--
Joseph S. Myers
joseph@codesourcery.com
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 3/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-10 20:21 ` Martin Sebor
@ 2017-08-15 3:06 ` Martin Sebor
2017-08-23 21:11 ` [PING] " Martin Sebor
2017-10-02 22:15 ` Jeff Law
1 sibling, 1 reply; 39+ messages in thread
From: Martin Sebor @ 2017-08-15 3:06 UTC (permalink / raw)
To: Jeff Law, Gcc Patch List
[-- Attachment #1: Type: text/plain, Size: 5513 bytes --]
On 08/10/2017 01:29 PM, Martin Sebor wrote:
>>> diff --git a/gcc/builtins.c b/gcc/builtins.c
>>> index 016f68d..1aa9e22 100644
>>> --- a/gcc/builtins.c
>>> +++ b/gcc/builtins.c
>> [ ... ]
>>> +
>>> + if (TREE_CODE (type) == ARRAY_TYPE)
>>> + {
>>> + /* Return the constant size unless it's zero (that's a
>>> zero-length
>>> + array likely at the end of a struct). */
>>> + tree size = TYPE_SIZE_UNIT (type);
>>> + if (size && TREE_CODE (size) == INTEGER_CST
>>> + && !integer_zerop (size))
>>> + return size;
>>> + }
>> Q. Do we have a canonical test for the trailing array idiom? In some
>> contexts isn't it size 1? ISTM This test needs slight improvement.
>> Ideally we'd use some canonical test for detect the trailing array idiom
>> rather than open-coding it here. You might look at the array index
>> warnings in tree-vrp.c to see if it's got a canonical test you can call
>> or factor and use.
>
> You're right, there is an API for this (array_at_struct_end_p,
> as Richard pointed out). I didn't want to use it because it
> treats any array at the end of a struct as a flexible array
> member, but simple tests show that that's what -Wstringop-
> overflow does now, and it wasn't my intention to tighten up
> the checking under this change. It surprises me that no tests
> exposed this. Let me relax the check and think about proposing
> to tighten it up separately.
Done in the attached patch. (I opened bug 81849 for the enhancement
to have -Wstringop-overflow diagnose overflows when writing to member
arrays bigger than 1 element even if they're last).
(I've left the handling for zero size in place because GCC allows
global arrays to be declared to have zero elements.)
>
>>> @@ -3883,6 +3920,30 @@ expand_builtin_strncat (tree exp, rtx)
>>> return NULL_RTX;
>>> }
>>>
>>> +/* Helper to check the sizes of sequences and the destination of calls
>>> + to __builtin_strncpy (DST, SRC, LEN) and __builtin___strncpy_chk.
>>> + Returns true on success (no overflow warning), false otherwise. */
>>> +
>>> +static bool
>>> +check_strncpy_sizes (tree exp, tree dst, tree src, tree len)
>>> +{
>>> + tree dstsize = compute_objsize (dst, warn_stringop_overflow - 1);
>>> +
>>> + if (!check_sizes (OPT_Wstringop_overflow_,
>>> + exp, len, /*maxlen=*/NULL_TREE, src, dstsize))
>>> + return false;
>>> +
>>> + if (!dstsize || TREE_CODE (len) != INTEGER_CST)
>>> + return true;
>>> +
>>> + if (tree_int_cst_lt (dstsize, len))
>>> + warning_at (EXPR_LOCATION (exp), OPT_Wstringop_truncation,
>>> + "%K%qD specified bound %E exceeds destination size %E",
>>> + exp, get_callee_fndecl (exp), len, dstsize);
>>> +
>>> + return true;
>> So in the case where you issue the warning, what should the return value
>> be? According to the comment it should be false. It looks like you got
>> the wrong return value for the tree_int_cst_lt (dstsize, len) test.
>
> Corrected. The return value is unused by the only caller so
> there is no test to exercise it.
Done in the attached patch.
>>> +/* A helper of handle_builtin_stxncpy. Check to see if the specified
>>> + bound is a) equal to the size of the destination DST and if so, b)
>>> + if it's immediately followed by DST[LEN - 1] = '\0'. If a) holds
>>> + and b) does not, warn. Otherwise, do nothing. Return true if
>>> + diagnostic has been issued.
>>> +
>>> + The purpose is to diagnose calls to strncpy and stpncpy that do
>>> + not nul-terminate the copy while allowing for the idiom where
>>> + such a call is immediately followed by setting the last element
>>> + to nul, as in:
>>> + char a[32];
>>> + strncpy (a, s, sizeof a);
>>> + a[sizeof a - 1] = '\0';
>>> +*/
>> So using gsi_next to find the next statement could make the heuristic
>> fail to find the a[sizeof a - 1] = '\0'; statement when debugging is
>> enabled.
>>
>> gsi_next_nondebug would be better as it would skip over any debug insns.
>
> Thanks. I'll have to remember this.
I went with this simple approach for now since it worked for GDB.
If it turns out that there are important instances of this idiom
that rely on intervening statements the warning can be relaxed.
>> What might be even better would be to use the immediate uses of the
>> memory tag. For your case there should be only one immediate use and it
>> should point to the statement which NUL terminates the destination. Or
>> maybe that would be worse in that you only want to allow this exception
>> when the statements are consecutive.
>>> /* Handle a memcpy-like ({mem{,p}cpy,__mem{,p}cpy_chk}) call.
>>> If strlen of the second argument is known and length of the third
>>> argument
>>> is that plus one, strlen of the first argument is the same after
>>> this
>>> @@ -2512,6 +2789,19 @@ strlen_optimize_stmt (gimple_stmt_iterator *gsi)
>> You still need to rename strlen_optimize_stmt since after your changes
>> it does both optimizations and warnings.
>
> I'm not sure I understand why. It's a pre-existing function that
> just dispatches to the built-in handlers. We don't rename function
> callers each time we improve error/warning detection in some
> function they call (case in point: all the expanders in builtins.c)
> Why do it here? And what would be a suitable name? All that comes
> to my mind is awkward variations on strlen_optimize_stmt_and_warn.
I've left the function name as is. If you feel strongly that
it needs to be renamed let me know.
Martin
[-- Attachment #2: gcc-81117-3.diff --]
[-- Type: text/x-patch, Size: 72718 bytes --]
PR c/81117 - Improve buffer overflow checking in strncpy
gcc/ChangeLog:
PR c/81117
* builtins.c (compute_objsize): Handle arrays that
compute_builtin_object_size likes to fail for. Make extern.
* builtins.h (compute_objsize): Declare.
(check_strncpy_sizes): New function.
(expand_builtin_strncpy): Call check_strncpy_sizes.
* gimple-fold.c (gimple_fold_builtin_strncpy): Implement
-Wstringop-truncation.
(gimple_fold_builtin_strncat): Same.
* gimple.c (gimple_build_call_from_tree): Set call location.
* tree-ssa-strlen.c (strlen_to_stridx): New global variable.
(maybe_diag_bound_equal_length, is_strlen_related_p): New functions.
(handle_builtin_stxncpy, handle_builtin_strncat): Same.
(handle_builtin_strlen): Use strlen_to_stridx.
(strlen_optimize_stmt): Handle flavors of strncat, strncpy, and
stpncpy.
Use strlen_to_stridx.
(pass_strlen::execute): Release strlen_to_stridx.
* doc/invoke.texi (-Wsizeof-pointer-memaccess): Document enhancement.
(-Wstringop-truncation): Document new option.
gcc/c-family/ChangeLog:
PR c/81117
* c-warn.c (sizeof_pointer_memaccess_warning): Handle arrays.
* c.opt (-Wstriingop-truncation): New option.
gcc/testsuite/ChangeLog:
PR c/81117
* c-c++-common/Wsizeof-pointer-memaccess3.c: New test.
* c-c++-common/Wstringop-overflow.c: Same.
* c-c++-common/Wstringop-truncation.c: Same.
* c-c++-common/Wstringop-truncation-2.c: Same.
* c-c++-common/Wsizeof-pointer-memaccess2.c: Adjust.
* c-c++-common/attr-nonstring-2.c: New test.
* g++.dg/torture/Wsizeof-pointer-memaccess1.C: Adjust.
* g++.dg/torture/Wsizeof-pointer-memaccess2.C: Same.
* gcc.dg/torture/pr63554.c: Same.
* gcc.dg/Walloca-1.c: Disable macro tracking.
Index: gcc/builtins.c
===================================================================
--- gcc/builtins.c (revision 251100)
+++ gcc/builtins.c (working copy)
@@ -3222,18 +3222,56 @@ check_sizes (int opt, tree exp, tree size, tree ma
}
/* Helper to compute the size of the object referenced by the DEST
- expression which must of of pointer type, using Object Size type
+ expression which must have pointer type, using Object Size type
OSTYPE (only the least significant 2 bits are used). Return
the size of the object if successful or NULL when the size cannot
be determined. */
-static inline tree
+tree
compute_objsize (tree dest, int ostype)
{
unsigned HOST_WIDE_INT size;
- if (compute_builtin_object_size (dest, ostype & 3, &size))
+
+ /* Only the two least significant bits are meaningful. */
+ ostype &= 3;
+
+ if (compute_builtin_object_size (dest, ostype, &size))
return build_int_cst (sizetype, size);
+ /* Unless computing the largest size (for memcpy and other raw memory
+ functions), try to determine the size of the object from its type. */
+ if (!ostype)
+ return NULL_TREE;
+
+ if (TREE_CODE (dest) == SSA_NAME)
+ {
+ gimple *stmt = SSA_NAME_DEF_STMT (dest);
+ if (!is_gimple_assign (stmt))
+ return NULL_TREE;
+
+ dest = gimple_assign_rhs1 (stmt);
+ }
+
+ if (TREE_CODE (dest) != ADDR_EXPR)
+ return NULL_TREE;
+
+ tree type = TREE_TYPE (dest);
+ if (TREE_CODE (type) == POINTER_TYPE)
+ type = TREE_TYPE (type);
+
+ type = TYPE_MAIN_VARIANT (type);
+
+ if (TREE_CODE (type) == ARRAY_TYPE
+ && !array_at_struct_end_p (dest))
+ {
+ /* Return the constant size unless it's zero (that's a zero-length
+ array likely at the end of a struct). */
+ tree size = TYPE_SIZE_UNIT (type);
+ if (size && TREE_CODE (size) == INTEGER_CST
+ && !integer_zerop (size))
+ return size;
+ }
+
return NULL_TREE;
}
@@ -3885,6 +3923,32 @@ expand_builtin_strncat (tree exp, rtx)
return NULL_RTX;
}
+/* Helper to check the sizes of sequences and the destination of calls
+ to __builtin_strncpy (DST, SRC, LEN) and __builtin___strncpy_chk.
+ Returns true on success (no overflow warning), false otherwise. */
+
+static bool
+check_strncpy_sizes (tree exp, tree dst, tree src, tree len)
+{
+ tree dstsize = compute_objsize (dst, warn_stringop_overflow - 1);
+
+ if (!check_sizes (OPT_Wstringop_overflow_,
+ exp, len, /*maxlen=*/NULL_TREE, src, dstsize))
+ return false;
+
+ if (!dstsize || TREE_CODE (len) != INTEGER_CST)
+ return true;
+
+ if (!tree_int_cst_lt (dstsize, len))
+ return true;
+
+ warning_at (EXPR_LOCATION (exp), OPT_Wstringop_truncation,
+ "%K%qD specified bound %E exceeds destination size %E",
+ exp, get_callee_fndecl (exp), len, dstsize);
+
+ return false;
+}
+
/* Expand expression EXP, which is a call to the strncpy builtin. Return
NULL_RTX if we failed the caller should emit a normal call. */
@@ -3903,17 +3967,8 @@ expand_builtin_strncpy (tree exp, rtx target)
/* The length of the source sequence. */
tree slen = c_strlen (src, 1);
- if (warn_stringop_overflow)
- {
- tree destsize = compute_objsize (dest,
- warn_stringop_overflow - 1);
+ check_strncpy_sizes (exp, dest, src, len);
- /* The number of bytes to write is LEN but check_sizes will also
- check SLEN if LEN's value isn't known. */
- check_sizes (OPT_Wstringop_overflow_,
- exp, len, /*maxlen=*/NULL_TREE, src, destsize);
- }
-
/* We must be passed a constant len and src parameter. */
if (!tree_fits_uhwi_p (len) || !slen || !tree_fits_uhwi_p (slen))
return NULL_RTX;
Index: gcc/builtins.h
===================================================================
--- gcc/builtins.h (revision 251100)
+++ gcc/builtins.h (working copy)
@@ -89,6 +89,7 @@ extern tree fold_call_stmt (gcall *, bool);
extern void set_builtin_user_assembler_name (tree decl, const char *asmspec);
extern bool is_simple_builtin (tree);
extern bool is_inexpensive_builtin (tree);
+extern tree compute_objsize (tree, int);
extern bool readonly_data_expr (tree exp);
extern bool init_target_chars (void);
Index: gcc/c-family/c-warn.c
===================================================================
--- gcc/c-family/c-warn.c (revision 251100)
+++ gcc/c-family/c-warn.c (working copy)
@@ -628,7 +628,8 @@ sizeof_pointer_memaccess_warning (location_t *size
|| vec_safe_length (params) <= 1)
return;
- switch (DECL_FUNCTION_CODE (callee))
+ enum built_in_function fncode = DECL_FUNCTION_CODE (callee);
+ switch (fncode)
{
case BUILT_IN_STRNCMP:
case BUILT_IN_STRNCASECMP:
@@ -710,9 +711,28 @@ sizeof_pointer_memaccess_warning (location_t *size
type = TYPE_P (sizeof_arg[idx])
? sizeof_arg[idx] : TREE_TYPE (sizeof_arg[idx]);
+
if (!POINTER_TYPE_P (type))
- return;
+ {
+ /* The argument type may be an array. Diagnose bounded string
+ copy functions that specify the bound in terms of the source
+ argument rather than the destination. */
+ if (strop && !cmp && fncode != BUILT_IN_STRNDUP && src)
+ {
+ tem = tree_strip_nop_conversions (src);
+ if (TREE_CODE (tem) == ADDR_EXPR)
+ tem = TREE_OPERAND (tem, 0);
+ if (operand_equal_p (tem, sizeof_arg[idx], OEP_ADDRESS_OF))
+ warning_at (sizeof_arg_loc[idx], OPT_Wsizeof_pointer_memaccess,
+ "argument to %<sizeof%> in %qD call is the same "
+ "expression as the source; did you mean to use "
+ "the size of the destination?",
+ callee);
+ }
+ return;
+ }
+
if (dest
&& (tem = tree_strip_nop_conversions (dest))
&& POINTER_TYPE_P (TREE_TYPE (tem))
Index: gcc/c-family/c.opt
===================================================================
--- gcc/c-family/c.opt (revision 251100)
+++ gcc/c-family/c.opt (working copy)
@@ -736,6 +736,10 @@ C ObjC C++ ObjC++ Joined RejectNegative UInteger V
Under the control of Object Size type, warn about buffer overflow in string
manipulation functions like memcpy and strcpy.
+Wstringop-truncation
+C ObjC C++ ObjC++ Var(warn_stringop_truncation) Warning Init (1) LangEnabledBy(C ObjC C++ ObjC++, Wall)
+Warn about truncation in string manipulation functions like strncat and strncpy.
+
Wsuggest-attribute=format
C ObjC C++ ObjC++ Var(warn_suggest_attribute_format) Warning
Warn about functions which might be candidates for format attributes.
Index: gcc/doc/invoke.texi
===================================================================
--- gcc/doc/invoke.texi (revision 251100)
+++ gcc/doc/invoke.texi (working copy)
@@ -312,7 +312,7 @@ Objective-C and Objective-C++ Dialects}.
-Wsizeof-pointer-memaccess -Wsizeof-array-argument @gol
-Wstack-protector -Wstack-usage=@var{len} -Wstrict-aliasing @gol
-Wstrict-aliasing=n -Wstrict-overflow -Wstrict-overflow=@var{n} @gol
--Wstringop-overflow=@var{n} @gol
+-Wstringop-overflow=@var{n} -Wstringop-truncation @gol
-Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{|}format@r{]} @gol
-Wsuggest-final-types @gol -Wsuggest-final-methods -Wsuggest-override @gol
-Wmissing-format-attribute -Wsubobject-linkage @gol
@@ -5171,6 +5171,56 @@ whether to issue a warning. Similarly to @option{
setting of the option may result in warnings for benign code.
@end table
+@item -Wstringop-truncation
+@opindex Wstringop-truncation
+@opindex Wno-stringop-truncation
+Warn for calls to bounded string manipulation functions such as @code{strncat},
+@code{strncpy}, and @code{stpncpy} that may either truncate the copied string
+or leave the destination unchanged.
+
+In the following example, the call to @code{strncat} specifies a bound that
+is less than the length of the source string. As a result, the copy of
+the source will be truncated and so the call is diagnosed. To avoid the
+warning use @code{bufsize - strlen (buf) - 1)} as the bound.
+
+@smallexample
+void append (char *buf, size_t bufsize)
+@{
+ strncat (buf, ".txt", 3);
+@}
+@end smallexample
+
+As another example, the following call to @code{strncpy} results in copying
+to @code{d} just the characters preceding the terminating NUL, without
+appending the NUL to the end. Assuming the result of @code{strncpy} is
+necessarily a NUL-terminated string is a common mistake, and so the call
+is diagnosed. To avoid the warning when the result is not expected to be
+NUL-terminated, call @code{memcpy} instead.
+
+@smallexample
+void copy (char *d, const char *s)
+@{
+ strncpy (d, s, strlen (s));
+@}
+@end smallexample
+
+In the following example, the call to @code{strncpy} specifies the size
+of the destination buffer as the bound. If the length of the source
+string is equal to or greater than this size the result of the copy will
+not be NUL-terminated. Therefore, the call is also diagnosed. To avoid
+the warning, specify @code{sizeof buf - 1} as the bound and set the last
+element of the buffer to @code{NUL}.
+
+@smallexample
+void copy (const char *s)
+@{
+ char buf[80];
+ strncpy (buf, s, sizeof buf);
+ @dots{}
+@}
+@end smallexample
+
+
@item -Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{|}format@r{]}
@opindex Wsuggest-attribute=
@opindex Wno-suggest-attribute=
@@ -6155,12 +6205,27 @@ not an array, but a pointer. This warning is enab
@opindex Wsizeof-pointer-memaccess
@opindex Wno-sizeof-pointer-memaccess
Warn for suspicious length parameters to certain string and memory built-in
-functions if the argument uses @code{sizeof}. This warning warns e.g.@:
-about @code{memset (ptr, 0, sizeof (ptr));} if @code{ptr} is not an array,
-but a pointer, and suggests a possible fix, or about
-@code{memcpy (&foo, ptr, sizeof (&foo));}. This warning is enabled by
-@option{-Wall}.
+functions if the argument uses @code{sizeof}. This warning triggers for
+example for @code{memset (ptr, 0, sizeof (ptr));} if @code{ptr} is not
+an array, but a pointer, and suggests a possible fix, or about
+@code{memcpy (&foo, ptr, sizeof (&foo));}. @option{-Wsizeof-pointer-memaccess}
+also warns about calls to bounded string copy functions like @code{strncat}
+or @code{strncpy} that specify as the bound a @code{sizeof} expression of
+the source array. For example, in the following function the call to
+@code{strncat} specifies the size of the source string as the bound. That
+is almost certainly a mistake and so the call is diagnosed.
+@smallexample
+void make_file (const char *name)
+@{
+ char path[PATH_MAX];
+ strncpy (path, name, sizeof path - 1);
+ strncat (path, ".text", sizeof ".text");
+ @dots{}
+@}
+@end smallexample
+The @option{-Wsizeof-pointer-memaccess} option is enabled by @option{-Wall}.
+
@item -Wsizeof-array-argument
@opindex Wsizeof-array-argument
@opindex Wno-sizeof-array-argument
Index: gcc/gimple-fold.c
===================================================================
--- gcc/gimple-fold.c (revision 251100)
+++ gcc/gimple-fold.c (working copy)
@@ -40,6 +40,7 @@ along with GCC; see the file COPYING3. If not see
#include "gimple-iterator.h"
#include "tree-into-ssa.h"
#include "tree-dfa.h"
+#include "tree-object-size.h"
#include "tree-ssa.h"
#include "tree-ssa-propagate.h"
#include "ipa-utils.h"
@@ -59,6 +60,8 @@ along with GCC; see the file COPYING3. If not see
#include "stringpool.h"
#include "attribs.h"
#include "asan.h"
+#include "diagnostic-core.h"
+#include "intl.h"
/* Return true when DECL can be referenced from current unit.
FROM_DECL (if non-null) specify constructor of variable DECL was taken from.
@@ -1524,12 +1527,28 @@ static bool
gimple_fold_builtin_strncpy (gimple_stmt_iterator *gsi,
tree dest, tree src, tree len)
{
- location_t loc = gimple_location (gsi_stmt (*gsi));
- tree fn;
+ gimple *stmt = gsi_stmt (*gsi);
+ location_t loc = gimple_location (stmt);
/* If the LEN parameter is zero, return DEST. */
if (integer_zerop (len))
{
+ tree fndecl = gimple_call_fndecl (stmt);
+ gcall *call = as_a <gcall *> (stmt);
+
+ /* Warn about the lack of nul termination: the result is not
+ a (nul-terminated) string. */
+ tree slen = get_maxval_strlen (src, 0);
+ if (slen && !integer_zerop (slen))
+ warning_at (loc, OPT_Wstringop_truncation,
+ "%G%qD destination unchanged after copying no bytes "
+ "from a string of length %E",
+ call, fndecl, slen);
+ else
+ warning_at (loc, OPT_Wstringop_truncation,
+ "%G%qD destination unchanged after copying no bytes",
+ call, fndecl);
+
replace_call_with_value (gsi, dest);
return true;
}
@@ -1544,16 +1563,66 @@ gimple_fold_builtin_strncpy (gimple_stmt_iterator
if (!slen || TREE_CODE (slen) != INTEGER_CST)
return false;
- slen = size_binop_loc (loc, PLUS_EXPR, slen, ssize_int (1));
+ /* The size of the source string including the terminating nul. */
+ tree ssize = size_binop_loc (loc, PLUS_EXPR, slen, ssize_int (1));
/* We do not support simplification of this case, though we do
support it when expanding trees into RTL. */
/* FIXME: generate a call to __builtin_memset. */
- if (tree_int_cst_lt (slen, len))
+ if (tree_int_cst_lt (ssize, len))
return false;
+ if (tree_int_cst_lt (len, slen))
+ {
+ tree fndecl = gimple_call_fndecl (stmt);
+ gcall *call = as_a <gcall *> (stmt);
+
+ warning_at (loc, OPT_Wstringop_truncation,
+ (tree_int_cst_equal (size_one_node, len)
+ ? G_("%G%qD output truncated copying %E byte "
+ "from a string of length %E")
+ : G_("%G%qD output truncated copying %E bytes "
+ "from a string of length %E")),
+ call, fndecl, len, slen);
+ }
+ else if (tree_int_cst_equal (len, slen))
+ {
+ tree decl = dest;
+ if (TREE_CODE (decl) == SSA_NAME)
+ {
+ gimple *def_stmt = SSA_NAME_DEF_STMT (decl);
+ if (is_gimple_assign (def_stmt))
+ {
+ tree_code code = gimple_assign_rhs_code (def_stmt);
+ if (code == ADDR_EXPR || code == VAR_DECL)
+ decl = gimple_assign_rhs1 (def_stmt);
+ }
+ }
+
+ if (TREE_CODE (decl) == ADDR_EXPR)
+ decl = TREE_OPERAND (decl, 0);
+
+ if (TREE_CODE (decl) == COMPONENT_REF)
+ decl = TREE_OPERAND (decl, 1);
+
+ tree fndecl = gimple_call_fndecl (stmt);
+ gcall *call = as_a <gcall *> (stmt);
+
+ if (!DECL_P (decl)
+ || !lookup_attribute ("nonstring", DECL_ATTRIBUTES (decl)))
+ warning_at (loc, OPT_Wstringop_truncation,
+ (tree_int_cst_equal (size_one_node, len)
+ ? G_("%G%qD output truncated before terminating nul "
+ "copying %E byte from a string of the same "
+ "length")
+ : G_("%G%qD output truncated before terminating nul "
+ "copying %E bytes from a string of the same "
+ "length")),
+ call, fndecl, len);
+ }
+
/* OK transform into builtin memcpy. */
- fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
+ tree fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
if (!fn)
return false;
@@ -1562,6 +1631,7 @@ gimple_fold_builtin_strncpy (gimple_stmt_iterator
NULL_TREE, true, GSI_SAME_STMT);
gimple *repl = gimple_build_call (fn, 3, dest, src, len);
replace_call_with_call_and_fold (gsi, repl);
+
return true;
}
@@ -1858,21 +1928,77 @@ gimple_fold_builtin_strncat (gimple_stmt_iterator
return true;
}
- /* If the requested len is greater than or equal to the string
- length, call strcat. */
- if (TREE_CODE (len) == INTEGER_CST && p
- && compare_tree_int (len, strlen (p)) >= 0)
+ if (TREE_CODE (len) == INTEGER_CST && p)
{
- tree fn = builtin_decl_implicit (BUILT_IN_STRCAT);
+ unsigned srclen = strlen (p);
- /* If the replacement _DECL isn't initialized, don't do the
- transformation. */
- if (!fn)
- return false;
+ int cmpsrc = compare_tree_int (len, srclen);
- gcall *repl = gimple_build_call (fn, 2, dst, src);
- replace_call_with_call_and_fold (gsi, repl);
- return true;
+ unsigned HOST_WIDE_INT dstsize;
+
+ bool nowarn = gimple_no_warning_p (stmt);
+
+ if (!nowarn && compute_builtin_object_size (dst, 1, &dstsize))
+ {
+ int cmpdst = compare_tree_int (len, dstsize);
+
+ if (cmpdst >= 0)
+ {
+ tree fndecl = gimple_call_fndecl (stmt);
+
+ /* Strncat copies (at most) LEN bytes and always appends
+ the terminating NUL so the specified bound should never
+ be equal to (or greater than) the size of the destination.
+ If it is, the copy could overflow. */
+ location_t loc = gimple_location (stmt);
+ nowarn = warning_at (loc, OPT_Wstringop_overflow_,
+ cmpdst == 0
+ ? G_("%G%qD specified bound %E equals "
+ "destination size")
+ : G_("%G%qD specified bound %E exceeds "
+ "destination size %wu"),
+ stmt, fndecl, len, dstsize);
+ if (nowarn)
+ gimple_set_no_warning (stmt, true);
+ }
+ }
+
+ if (!nowarn && cmpsrc <= 0)
+ {
+ tree fndecl = gimple_call_fndecl (stmt);
+
+ /* To avoid certain truncation the specified bound should also
+ not be equal to (or less than) the length of the source. */
+ location_t loc = gimple_location (stmt);
+ bool warn;
+ if (cmpsrc == 0)
+ warn = warning_at (loc, OPT_Wstringop_overflow_,
+ "%G%qD specified bound %E equals source length",
+ stmt, fndecl, len);
+ else
+ warn = warning_at (loc, OPT_Wstringop_truncation,
+ "%G%qD specified bound %E is less than source "
+ "length %u",
+ stmt, fndecl, len, srclen);
+ if (warn)
+ gimple_set_no_warning (stmt, true);
+ }
+
+ /* If the requested len is greater than or equal to the string
+ length, call strcat. */
+ if (cmpsrc >= 0)
+ {
+ tree fn = builtin_decl_implicit (BUILT_IN_STRCAT);
+
+ /* If the replacement _DECL isn't initialized, don't do the
+ transformation. */
+ if (!fn)
+ return false;
+
+ gcall *repl = gimple_build_call (fn, 2, dst, src);
+ replace_call_with_call_and_fold (gsi, repl);
+ return true;
+ }
}
return false;
Index: gcc/gimple.c
===================================================================
--- gcc/gimple.c (revision 251100)
+++ gcc/gimple.c (working copy)
@@ -361,6 +361,7 @@ gimple_build_call_from_tree (tree t)
gimple_call_set_arg (call, i, CALL_EXPR_ARG (t, i));
gimple_set_block (call, TREE_BLOCK (t));
+ gimple_set_location (call, EXPR_LOCATION (t));
/* Carry all the CALL_EXPR flags to the new GIMPLE_CALL. */
gimple_call_set_chain (call, CALL_EXPR_STATIC_CHAIN (t));
Index: gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c
===================================================================
--- gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c (revision 251100)
+++ gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c (working copy)
@@ -1,7 +1,7 @@
/* Test -Wsizeof-pointer-memaccess warnings. */
/* { dg-do compile } */
-/* { dg-options "-Wall -O2 -Wno-sizeof-array-argument -ftrack-macro-expansion=0" } */
-/* { dg-options "-Wall -O2 -Wno-sizeof-array-argument -Wno-c++-compat -ftrack-macro-expansion=0" {target c} } */
+/* { dg-options "-Wall -O2 -Wno-sizeof-array-argument -Wno-stringop-truncation -ftrack-macro-expansion=0" } */
+/* { dg-options "-Wall -O2 -Wno-sizeof-array-argument -Wno-stringop-truncation -Wno-c++-compat -ftrack-macro-expansion=0" {target c} } */
/* { dg-require-effective-target alloca } */
#define bos(ptr) __builtin_object_size (ptr, 1)
@@ -473,12 +473,15 @@ f4 (char *x, char **y, int z, char w[64])
strncat (w, s2, sizeof (w)); /* { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" } */
stpncpy (w, s1, sizeof (w)); /* { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" } */
- /* These are correct, no warning. */
+ /* These are pointless when the destination is large enough, and
+ cause overflow otherwise. If the copies are guaranteed to be
+ safe the calls might as well be replaced by strcat(), strcpy(),
+ or memcpy(). */
const char s3[] = "foobarbaz";
const char s4[] = "abcde12345678";
- strncpy (x, s3, sizeof (s3));
- strncat (x, s4, sizeof (s4));
- stpncpy (x, s3, sizeof (s3));
+ strncpy (x, s3, sizeof (s3)); /* { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" } */
+ strncat (x, s4, sizeof (s4)); /* { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" } */
+ stpncpy (x, s3, sizeof (s3)); /* { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" } */
}
/* { dg-prune-output "\[\n\r\]*writing\[\n\r\]*" } */
Index: gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess3.c
===================================================================
--- gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess3.c (nonexistent)
+++ gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess3.c (working copy)
@@ -0,0 +1,132 @@
+/* Test -Wsizeof-pointer-memaccess warnings. */
+/* { dg-do compile } */
+/* { dg-options "-Wsizeof-pointer-memaccess -Wno-stringop-overflow -Wno-stringop-truncation -ftrack-macro-expansion=0" } */
+
+#define bos(ptr) __builtin_object_size (ptr, 1)
+#define bos0(ptr) __builtin_object_size (ptr, 0)
+
+#define memset(dst, val, sz) \
+ (FUNC (memset, dst, val, sz, bos (dst)), sink ((dst)))
+
+#define memcpy(dst, src, sz) \
+ (FUNC (memcpy, dst, src, sz, bos (dst)), sink ((dst)))
+
+#define memmove(dst, src, sz) \
+ (FUNC (memmove, dst, src, sz, bos (dst)), sink ((dst)))
+
+#define mempcpy(dst, src, sz) \
+ (FUNC (mempcpy, dst, src, sz, bos (dst)), sink ((dst)))
+
+#define strncpy(dst, src, sz) \
+ (FUNC (strncpy, dst, src, sz, bos (dst)), sink (dst))
+
+#define strncat(dst, src, sz) \
+ (FUNC (strncat, dst, src, sz, bos (dst)), sink (dst))
+
+#define stpncpy(dst, src, sz) \
+ (FUNC (stpncpy, dst, src, sz, bos (dst)), sink (dst))
+
+void sink (void*);
+
+#define S10 "123456789"
+extern char a10[10];
+
+void test_string_literal (char *dst)
+{
+#define FUNC(f, d, s, n, x) __builtin_ ## f (d, s, n)
+
+ /* It's common to call memcpy and other raw memory functions with
+ size drerived from the source argument. Verify that no warning
+ is ussued for such calls. */
+ memcpy (dst, S10, sizeof S10);
+ mempcpy (dst, S10, sizeof S10);
+ memmove (dst, S10, sizeof S10);
+
+ memset (dst, 0, sizeof S10);
+
+ stpncpy (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncpy (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncat (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ /* Unlike in the cases above, even though the calls below are likely
+ wrong, it's not easy to detect that the expression (sizeof X - 1)
+ involves sizeof of the source, so no warning is issued here, as
+ helpful as one might be. Whether -Wstringop-truncation is issued
+ is tested elsewhere. */
+ stpncpy (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncpy (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncat (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+}
+
+
+void test_char_array (char *dst)
+{
+ memcpy (dst, a10, sizeof a10);
+ mempcpy (dst, a10, sizeof a10);
+ memmove (dst, a10, sizeof a10);
+
+ memset (dst, 0, sizeof a10);
+
+ stpncpy (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncpy (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncat (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ stpncpy (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncpy (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncat (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+}
+
+
+#undef FUNC
+#define FUNC(f, d, s, n, os) __builtin___ ## f ## _chk (d, s, n, os)
+
+void test_char_array_chk (char *dst)
+{
+ memcpy (dst, S10, sizeof S10);
+ mempcpy (dst, S10, sizeof S10);
+ memmove (dst, S10, sizeof S10);
+
+ memset (dst, 0, sizeof S10);
+
+ stpncpy (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncpy (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncat (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ stpncpy (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncpy (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncat (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+}
+
+
+void test_string_literal_chk (char *dst)
+{
+ memcpy (dst, a10, sizeof a10);
+ mempcpy (dst, a10, sizeof a10);
+ memmove (dst, a10, sizeof a10);
+
+ memset (dst, 0, sizeof a10);
+
+ stpncpy (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncpy (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncat (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ stpncpy (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncpy (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncat (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+}
Index: gcc/testsuite/c-c++-common/Wstringop-overflow.c
===================================================================
--- gcc/testsuite/c-c++-common/Wstringop-overflow.c (nonexistent)
+++ gcc/testsuite/c-c++-common/Wstringop-overflow.c (working copy)
@@ -0,0 +1,158 @@
+/* PR middle-end/81117 - Improve buffer overflow checking in strncpy
+ { dg-do compile }
+ { dg-options "-O2 -Wstringop-overflow -Wno-stringop-truncation -ftrack-macro-expansion=0" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+#if __cplusplus
+extern "C" {
+#endif
+
+size_t strlen (const char*);
+char* strncat (char*, const char*, size_t);
+char* strncpy (char*, const char*, size_t);
+#if __cplusplus
+}
+#endif
+
+const char ar[] = "123";
+
+void test_strncat (char **d, const char* s, int i)
+{
+ /* Use a fresh pointer for each test to prevent the optimizer from
+ eliminating redundant writes into the same destination. Avoid
+ calling functions like sink() on the result that would have to
+ be assumed to change the source string by the alias oracle. */
+#define T(d, s, len) strncat (*d++, (s), (len))
+
+ T (d, "", 0);
+ T (d, "", 1);
+ T (d, "", 2);
+ T (d, "", 3);
+ T (d, "123", 0);
+ /* The following two calls truncate the copy and are diagnosed
+ by -Wstringop-truncation but there is evidence of overflow so
+ they're not diagnosed by -Wstringop-overflow. */
+ T (d, "123", 1);
+ T (d, "123", 2);
+
+ T (d, "123", 3); /* { dg-warning ".strncat\[^\n\r\]* specified bound 3 equals source length" } */
+ T (d, "123", 4);
+ T (d, "123", 9);
+
+ T (d, s, strlen (s)); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ T (d, s, strlen (s) + 1); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ /* The following could also be diagnosed by -Wstringop-truncation
+ (with some effort to distinguish the pattern from others like
+ the one above. */
+ T (d, s, strlen (s) - 1); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ T (d, s, strlen (s) - i); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+
+ /* The following is dubious but not necessarily a smoking gun. */
+ T (d, s, strlen (s) - strlen (s));
+
+ {
+ signed char n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ short n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ int n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ unsigned n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s) - 1; /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-message ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ /* This doesn't overflow so iit should not be diagnosed. */
+ size_t n = strlen (s) - strlen (s);
+ T (d, s, n);
+ }
+
+ {
+ size_t n = i < strlen (s) ? i : strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-message ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+}
+
+
+void test_strncpy (char **d, const char* s, int i)
+{
+#undef T
+#define T(d, s, len) strncpy (*d++, (s), (len))
+
+ T (d, "", 0);
+ T (d, "", 1);
+ T (d, "", 2);
+ T (d, "", 3);
+ T (d, "123", 0);
+ T (d, "123", 1);
+ T (d, "123", 2);
+ T (d, "123", 3);
+ T (d, "123", 4);
+ T (d, "123", 9);
+
+ T (d, "123", sizeof "123");
+ T (d, ar, sizeof ar);
+
+ T (d, s, strlen (s)); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+
+ {
+ int n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ }
+
+ {
+ unsigned n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s) - 1; /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ }
+
+ {
+ /* This is diagnosed by -Wstringop-truncation. Verify that it isn't
+ also diagnosed by -Wstringop-overflow. */
+ size_t n = strlen (s) - strlen (s);
+ T (d, s, n);
+ }
+
+ {
+ /* This use of strncpy is certainly dubious and it could well be
+ diagnosed by -Wstringop-truncation but it isn't. That it is
+ diagnosed with -Wstringop-overflow is more by accident than
+ by design. -Wstringop-overflow considers any dependency of
+ the bound on strlen(s) a potential bug. */
+ size_t n = i < strlen (s) ? i : strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-message ".strncpy\[^\n\r]* specified bound depends on the length of the source argument" } */
+ }
+}
Index: gcc/testsuite/c-c++-common/Wstringop-truncation.c
===================================================================
--- gcc/testsuite/c-c++-common/Wstringop-truncation.c (nonexistent)
+++ gcc/testsuite/c-c++-common/Wstringop-truncation.c (working copy)
@@ -0,0 +1,378 @@
+/* PR middle-end/81117 - Improve buffer overflow checking in strncpy
+ { dg-do compile }
+ { dg-options "-O2 -Wstringop-truncation -Wno-stringop-overflow -ftrack-macro-expansion=0" } */
+
+
+typedef __SIZE_TYPE__ size_t;
+
+#if __cplusplus
+extern "C" {
+#endif
+
+size_t strlen (const char*);
+char* strncat (char*, const char*, size_t);
+char* strncpy (char*, const char*, size_t);
+
+#if __cplusplus
+}
+#endif
+
+void sink (void*);
+
+#define S4 "123"
+const char a4[] = "123";
+
+
+typedef struct Dest
+{
+ char a5[5];
+ char b7[7];
+ char c3ns[3] __attribute__ ((nonstring));
+} Dest;
+
+char dst7[7];
+char dst2_5[2][5];
+
+/* Verify strncat warnings for arrays of known bounds. */
+
+void test_strncat_array (Dest *pd)
+{
+#undef T
+#define T(d, s, len) (strncat ((d), (s), (len)), sink (d))
+
+ T (dst7, S4, 2); /* { dg-warning "specified bound 2 is less than source length 3" } */
+
+ T (dst7, a4, 1); /* { dg-warning "specified bound 1 is less than source length 3" } */
+
+ /* There is no truncation here but possible overflow so these
+ are diagnosed by -Wstringop-overflow:
+ T (dst7, S4, 3);
+ T (dst7, a4, 3);
+ */
+
+ T (pd->a5, S4, 2); /* { dg-warning "specified bound 2 is less than source length" } */
+ T (pd->a5, S4, 1); /* { dg-warning "specified bound 1 is less than source length 3" } */
+}
+
+/* Verify strncat warnings for arrays of unknown bounds. */
+
+void test_strncat_vla (char *d, unsigned n)
+{
+ T (d, S4, 2); /* { dg-warning "specified bound 2 is less than source length 3" } */
+ T (d, S4, 4);
+
+ T (d, a4, 2); /* { dg-warning "specified bound 2 is less than source length 3" } */
+
+ /* There is no truncation here but possible overflow so these
+ are diagnosed by -Wstringop-overflow:
+ T (d, S4, 3);
+ T (d, a4, 3);
+ */
+ T (d, a4, 4);
+
+ char vla[n];
+
+ T (vla, S4, 2); /* { dg-warning "specified bound 2 is less than source length 3" } */
+
+ T (vla, S4, 4);
+ T (vla, S4, n);
+
+ T (vla, a4, 2); /* { dg-warning "specified bound 2 is less than source length 3" } */
+
+ T (vla, a4, 4);
+ T (vla, a4, n);
+
+ T (d, vla, 1);
+ T (d, vla, 3);
+ T (d, vla, 4);
+ T (d, vla, n);
+
+ /* There is no truncation here but possible overflow so these
+ are diagnosed by -Wstringop-overflow:
+ T (vla, S4, 3);
+ T (vla, a4, 3);
+ */
+}
+
+/* Verify strncpy warnings for pointers to unknown strings. */
+
+void test_strncpy_ptr (char *d, const char* s, int i)
+{
+#undef T
+#define T(d, s, len) (strncpy ((d), (s), (len)), sink (d))
+
+ /* Strncpy doesn't nul-terminate so the following is diagnosed. */
+ T (d, "", 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
+
+ /* This is safe. */
+ T (d, "", 1);
+ T (d, "", 2);
+
+ /* Truncation. */
+ T (d, "123", 3); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ T (d, "123", 4);
+ T (d, "123", 9);
+
+ T (d, S4, sizeof S4); /* Covered by -Wsizeof-pointer-memaccess. */
+ T (d, S4, sizeof S4 - 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+
+ T (d, a4, sizeof a4); /* Covered by -Wsizeof-pointer-memaccess. */
+ T (d, a4, sizeof a4 - 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ T (d, a4, sizeof a4 - 3); /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 1 byte from a string of length 3" } */
+ T (d, a4, sizeof a4 - 4); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes from a string of length 3" } */
+
+ T (d, S4, strlen (S4)); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ /* Likely buggy but no truncation. Diagnosed by -Wstringop-overflow. */
+ T (d, a4, strlen (a4) + 1);
+ T (d, S4, strlen (S4) + i);
+
+ T (d, a4, strlen (a4)); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ /* As above, buggy but no evidence of truncation. */
+ T (d, S4, strlen (S4) + 1);
+
+ {
+ signed char n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ short n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ int n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ unsigned n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ size_t n;
+ char *dp2 = d + 1;
+ n = strlen (s); /* { dg-message "length computed here" } */
+ T (dp2, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ /* The following is likely buggy but there's no apparent truncation
+ so it's not diagnosed by -Wstringop-truncation. Instead, it is
+ diagnosed by -Wstringop-overflow (tested elsewhere). */
+ int n;
+ n = strlen (s) - 1;
+ T (d, s, n);
+ }
+
+ {
+ /* Same as above. */
+ size_t n;
+ n = strlen (s) - 1;
+ T (d, s, n);
+ }
+
+ {
+ size_t n = strlen (s) - strlen (s);
+ T (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
+ }
+
+ {
+ /* This use of strncpy is dubious but it's probably not worth
+ worrying about (truncation may not actually take place when
+ i is the result). It is diagnosed with -Wstringop-overflow
+ (although more by accident than by design).
+
+ size_t n = i < strlen (s) ? i : strlen (s);
+ T (d, s, n);
+ */
+ }
+}
+
+
+/* Verify strncpy warnings for arrays of known bounds. */
+
+void test_strncpy_array (Dest *pd, const char* s)
+{
+#undef T
+#define T(d, s, len) (strncpy ((d), (s), (len)), sink (d))
+
+ T (dst7, s, 7); /* { dg-warning "specified bound 7 equals destination size" } */
+ T (dst7, s, sizeof dst7); /* { dg-warning "specified bound 7 equals destination size" } */
+
+ T (dst2_5[0], s, sizeof dst2_5[0]); /* { dg-warning "specified bound 5 equals destination size" "bug 77293" { xfail *-*-* } } */
+ T (dst2_5[1], s, sizeof dst2_5[1]); /* { dg-warning "specified bound 5 equals destination size" } */
+
+ /* Verify that copies that nul-terminate are not diagnosed. */
+ T (dst7, "", sizeof dst7);
+ T (dst7 + 6, "", sizeof dst7 - 6);
+ T (dst7, "1", sizeof dst7);
+ T (dst7 + 1, "1", sizeof dst7 - 1);
+ T (dst7, "123456", sizeof dst7);
+ T (dst7 + 1, "12345", sizeof dst7 - 1);
+
+ /* No nul-termination here. */
+ T (dst7 + 2, "12345", sizeof dst7 - 2); /* { dg-warning "output truncated before terminating nul copying 5 bytes from a string of the same length" } */
+
+ /* Because strnlen appends as many NULs as necessary to write the specified
+ number of byts the following doesn't (necessarily) truncate but rather
+ overflow, and so is diagnosed by -Wstringop-overflow. */
+ T (dst7, s, 8);
+
+ T (dst7 + 1, s, 6); /* { dg-warning "specified bound 6 equals destination size" } */
+ T (dst7 + 6, s, 1); /* { dg-warning "specified bound 1 equals destination size" } */
+
+ T (pd->a5, s, 5); /* { dg-warning "specified bound 5 equals destination size" } */
+ T (pd->a5, s, sizeof pd->a5); /* { dg-warning "specified bound 5 equals destination size" } */
+
+ /* Verify that a copy that nul-terminates is not diagnosed. */
+ T (pd->a5, "1234", sizeof pd->a5);
+
+ /* Same above, diagnosed by -Wstringop-overflow. */
+ T (pd->a5, s, 6);
+
+ /* Exercise destination with attribute "nonstring". */
+ T (pd->c3ns, "", 3);
+ T (pd->c3ns, "", 1);
+ /* Truncation is still diagnosed -- using strncpy in this case is
+ pointless and should be replaced with memcpy. */
+ T (pd->c3ns, "12", 1); /* { dg-warning "output truncated copying 1 byte from a string of length 2" } */
+ T (pd->c3ns, "12", 2);
+ T (pd->c3ns, "12", 3);
+ T (pd->c3ns, "123", 3);
+ T (pd->c3ns, s, 3);
+ T (pd->c3ns, s, sizeof pd->c3ns);
+
+ /* Verify that the idiom of calling strncpy with a bound equal to
+ the size of the destination (and thus potentially without NUL-
+ terminating it) immediately followed by setting the last element
+ of the array to NUL is not diagnosed. */
+ {
+ /* This might be better written using memcpy() but it's safe so
+ it probably shouldn't be diagnosed. It currently triggers
+ a warning because of bug 81704. */
+ strncpy (dst7, "0123456", sizeof dst7); /* { dg-bogus "truncated" "bug 81704" { xfail *-*-* } } */
+ dst7[sizeof dst7 - 1] = '\0';
+ sink (dst7);
+ }
+
+ {
+ const char a[] = "0123456789";
+ strncpy (dst7, a, sizeof dst7);
+ dst7[sizeof dst7 - 1] = '\0';
+ sink (dst7);
+ }
+
+ {
+ strncpy (dst7, s, sizeof dst7);
+ dst7[sizeof dst7 - 1] = '\0';
+ sink (dst7);
+ }
+
+ {
+ strncpy (pd->a5, "01234", sizeof pd->a5); /* { dg-bogus "truncated" "bug 81704" { xfail *-*-* } } */
+ pd->a5[sizeof pd->a5 - 1] = '\0';
+ sink (pd);
+ }
+
+ {
+ strncpy (pd->a5, s, sizeof pd->a5);
+ pd->a5[sizeof pd->a5 - 1] = '\0';
+ sink (pd);
+ }
+
+ {
+ unsigned n = 7;
+ char *p = (char*)__builtin_malloc (n);
+ strncpy (p, s, n);
+ p[n - 1] = '\0';
+ sink (p);
+ }
+
+ {
+ /* This should be diagnosed because the NUL-termination doesn't
+ immediately follow the strncpy call (sink may expect pd->a5
+ to be NUL-terminated). */
+ strncpy (pd->a5, s, sizeof pd->a5); /* { dg-warning "specified bound 5 equals destination size" } */
+ sink (pd);
+ pd->a5[sizeof pd->a5] = '\0';
+ sink (pd);
+ }
+}
+
+typedef struct Flex
+{
+ size_t n;
+ char a0[0];
+ char ax[];
+} Flex;
+
+extern char array[];
+
+/* Verify that no warning is issued for array of unknown bound, flexible
+ array members, or zero-length arrays, except when the source is definitely
+ truncated. */
+
+void test_strncpy_flexarray (Flex *pf, const char* s)
+{
+#undef T
+#define T(d, s, len) (strncpy ((d), (s), (len)), sink (d))
+
+ T (array, "12345", 7);
+ T (array, "12345", 123);
+
+ T (array, s, 7);
+ T (array, s, 123);
+
+ T (pf->a0, s, 1);
+ T (pf->a0, s, 1234);
+
+ T (pf->a0, "", 1);
+ T (pf->a0, "12345", 5); /* { dg-warning "output truncated before terminating nul copying 5 bytes from a string of the same length" } */
+ T (pf->a0, "12345", 1234);
+
+ T (pf->ax, s, 5);
+ T (pf->ax, s, 12345);
+
+ T (pf->ax, "1234", 5);
+ T (pf->ax, "12345", 5); /* { dg-warning "output truncated before terminating nul copying 5 bytes from a string of the same length" } */
+ T (pf->ax, "12345", 12345);
+}
+
+/* Verify warnings for dynamically allocated objects. */
+
+void test_strncpy_alloc (const char* s)
+{
+ size_t n = 7;
+ char *d = (char *)__builtin_malloc (n);
+
+ T (d, s, n); /* { dg-warning "specified bound 7 equals destination size" "bug 79016" { xfail *-*-* } } */
+
+ Dest *pd = (Dest *)__builtin_malloc (sizeof *pd * n);
+ T (pd->a5, s, 5); /* { dg-warning "specified bound 5 equals destination size" } */
+ T (pd->a5, s, sizeof pd->a5); /* { dg-warning "specified bound 5 equals destination size" } */
+}
+
+/* Verify warnings for VLAs. */
+
+void test_strncpy_vla (unsigned n, const char* s)
+{
+ char vla[n];
+ T (vla, s, 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
+
+ T (vla, s, 1);
+ T (vla, s, 2);
+ T (vla, s, n);
+
+ T (vla, "", 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
+ T (vla, "", 1);
+ T (vla, S4, 3); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ T (vla, S4, n);
+}
Index: gcc/testsuite/c-c++-common/Wstringop-truncation-2.c
===================================================================
--- gcc/testsuite/c-c++-common/Wstringop-truncation-2.c (nonexistent)
+++ gcc/testsuite/c-c++-common/Wstringop-truncation-2.c (working copy)
@@ -0,0 +1,22 @@
+/* PR middle-end/81117 - Improve buffer overflow checking in strncpy
+ { dg-do compile }
+ { dg-options "-O2 -Wstringop-truncation -g" } */
+
+char dst[4];
+
+int test_debug_stmt (const char *src)
+{
+ int ret;
+
+ __builtin_strncpy (dst, src, sizeof dst);
+
+ /* The following assignment is preserved only in the form of a debug
+ statement but not actual executable code. */
+ ret = 123;
+
+ /* Verify that an intervening debug statement doesn't cause the warning
+ to trigger. */
+ dst[sizeof dst - 1] = '\0';
+
+ return ret;
+}
Index: gcc/testsuite/c-c++-common/attr-nonstring-1.c
===================================================================
--- gcc/testsuite/c-c++-common/attr-nonstring-1.c (nonexistent)
+++ gcc/testsuite/c-c++-common/attr-nonstring-1.c (working copy)
@@ -0,0 +1,60 @@
+/* Test to exercise attribute "nonstring" syntax.
+ { dg-do compile }
+ { dg-options "-Wattributes" } */
+
+#define ATTR(list) __attribute__ (list)
+#define NONSTR ATTR ((nonstring))
+
+/* Verify it's accepted on char arrays. */
+extern NONSTR char nsx_1[];
+extern char NONSTR nsx_2[];
+extern char nsx_3[] NONSTR;
+
+extern NONSTR char ns1[1];
+extern char NONSTR ns3[3];
+extern char ns5[5] NONSTR;
+
+/* Verify it's accepted on char pointers. */
+extern NONSTR char* pns_1;
+extern char NONSTR* pns_2;
+extern char* NONSTR pns_3;
+
+struct S
+{
+/* Verify it's accepted on char member pointers. */
+ NONSTR char* mpns_1;
+ char NONSTR* mpns_2;
+ char* NONSTR mpns_3;
+
+/* Verify it's accepted on char member arrays. */
+ NONSTR char mns1[1];
+ char NONSTR mns3[3];
+ char mns5[5] NONSTR;
+
+/* Verify it's accepted on char flexible array members. */
+ char mnsx[] NONSTR;
+};
+
+/* Verify it's rejected on non-array and non-pointer objects. */
+extern NONSTR char c1; /* { dg-warning ".nonstring. attribute ignored on objects of type .char." } */
+
+extern NONSTR int i1; /* { dg-warning ".nonstring. attribute ignored on objects of type .int." } */
+
+extern NONSTR int ia1[]; /* { dg-warning ".nonstring. attribute ignored on objects of type .int *\\\[\\\]." } */
+
+extern NONSTR int* pi1; /* { dg-warning ".nonstring. attribute ignored on objects of type .int *\\*." } */
+
+extern NONSTR
+void f (void); /* { dg-warning ".nonstring. attribute does not apply to functions" } */
+
+struct NONSTR
+NonStrType { int i; }; /* { dg-warning ".nonstring. attribute does not apply to types" } */
+
+typedef char NONSTR nschar_t; /* { dg-warning ".nonstring. attribute does not apply to types" } */
+
+void func (NONSTR char *pns1, char NONSTR *pns2, char* NONSTR pns3)
+{
+ (void)pns1;
+ (void)pns2;
+ (void)pns3;
+}
Index: gcc/testsuite/c-c++-common/attr-nonstring-2.c
===================================================================
--- gcc/testsuite/c-c++-common/attr-nonstring-2.c (nonexistent)
+++ gcc/testsuite/c-c++-common/attr-nonstring-2.c (working copy)
@@ -0,0 +1,110 @@
+/* Test to exercise attribute "nonstring".
+ { dg-do compile }
+ { dg-options "-O2 -Wattributes -Wstringop-truncation -ftrack-macro-expansion=0" } */
+
+#define ATTR(list) __attribute__ (list)
+#define NONSTR ATTR ((nonstring))
+#define strncpy(d, s, n) (__builtin_strncpy ((d), (s), (n)), sink (d))
+
+void sink (void*);
+
+/* Global string with an unknown bound. */
+extern char gsx[];
+
+/* Global string with an known bound. */
+extern char gs3[3];
+
+/* Global non-strings with an unknown bound. */
+extern NONSTR char gax_1[];
+extern char NONSTR gax_2[];
+extern char gax_3[] NONSTR;
+
+/* Global non-strings with a known bound. */
+NONSTR char gns3[3];
+char NONSTR gns4[4];
+char gns5[5] NONSTR;
+
+/* Global string pointer. */
+extern char *ps_1;
+
+/* Global non-string pointers. */
+extern NONSTR char *pns_1;
+extern char* NONSTR pns_2;
+extern char *pns_3 NONSTR;
+
+struct MemArrays
+{
+ NONSTR char ma3[3];
+ char NONSTR ma4[4];
+ char ma5[5] NONSTR;
+ char max[] NONSTR;
+};
+
+
+void test_array (const char *s, unsigned n)
+{
+ const char s7[] = "1234567";
+
+ strncpy (gs3, "", 0); /* { dg-warning "destination unchanged after copying no bytes" } */
+ strncpy (gs3, "a", 1); /* { dg-warning "output truncated before terminating nul copying 1 byte from a string of the same length" } */
+ strncpy (gs3, "a", 2);
+ strncpy (gs3, "a", 3);
+ strncpy (gs3, "ab", 3);
+ strncpy (gs3, "abc", 3); /* { dg-warning "output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+
+ strncpy (gax_3, s7, 3); /* { dg-warning "output truncated copying 3 bytes from a string of length 7" } */
+
+ strncpy (gax_1, "a", 1);
+ strncpy (gax_2, "ab", 2);
+ strncpy (gax_3, "abc", 3);
+ strncpy (gax_3, s7, 3); /* { dg-warning "output truncated copying 3 bytes from a string of length 7" } */
+
+ strncpy (gax_1, s, 1);
+ strncpy (gax_2, s, 1);
+ strncpy (gax_3, s, 1);
+
+ strncpy (gax_1, s, n);
+ strncpy (gax_2, s, n);
+ strncpy (gax_3, s, n);
+}
+
+
+void test_pointer (const char *s, unsigned n)
+{
+ const char s7[] = "1234567";
+
+ strncpy (pns_1, "a", 1);
+ strncpy (pns_2, "ab", 2);
+ strncpy (pns_3, "abc", 3);
+ strncpy (pns_3, s7, 3); /* { dg-warning "output truncated copying 3 bytes from a string of length 7" } */
+
+ strncpy (pns_1, s, 1);
+ strncpy (pns_2, s, 1);
+ strncpy (pns_3, s, 1);
+
+ strncpy (pns_1, s, n);
+ strncpy (pns_2, s, n);
+ strncpy (pns_3, s, n);
+}
+
+
+void test_member_array (struct MemArrays *p, const char *s, unsigned n)
+{
+ const char s7[] = "1234567";
+
+ strncpy (p->ma3, "a", 1);
+ strncpy (p->ma4, "ab", 2);
+ strncpy (p->ma5, "abc", 3);
+ strncpy (p->max, "abcd", 4);
+ strncpy (p->max, s7, 5); /* { dg-warning "output truncated copying 5 bytes from a string of length 7" } */
+
+ strncpy (p->ma3, s, 1);
+ strncpy (p->ma4, s, 1);
+ strncpy (p->ma5, s, 1);
+ strncpy (p->max, s, 1);
+
+ strncpy (p->ma3, s7, n);
+ strncpy (p->ma4, s7, n);
+ strncpy (p->ma5, s7, n);
+ strncpy (p->max, s7, n);
+}
Index: gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C
===================================================================
--- gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C (revision 251100)
+++ gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C (working copy)
@@ -1,6 +1,6 @@
// Test -Wsizeof-pointer-memaccess warnings.
// { dg-do compile }
-// { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow" }
+// { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow -Wno-stringop-truncation" }
// Test just twice, once with -O0 non-fortified, once with -O2 fortified.
// { dg-skip-if "" { *-*-* } { "*" } { "-O0" "-O2" } }
// { dg-skip-if "" { *-*-* } { "-flto" } { "" } }
@@ -698,12 +698,17 @@ f4 (char *x, char **y, int z, char w[64])
strncat (w, s2, sizeof (w)); // { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" }
stpncpy (w, s1, sizeof (w)); // { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" }
- // These are correct, no warning.
const char s3[] = "foobarbaz";
const char s4[] = "abcde12345678";
- strncpy (x, s3, sizeof (s3));
- strncat (x, s4, sizeof (s4));
- stpncpy (x, s3, sizeof (s3));
+
+ // These are pointless when the destination is large enough, and
+ // cause overflow otherwise. They might as well be replaced by
+ // strcpy() or memcpy().
+ strncpy (x, s3, sizeof (s3)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" }
+ strncat (x, s4, sizeof (s4)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" }
+ stpncpy (x, s3, sizeof (s3)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" }
+
+ // These are safe, no warning.
y[1] = strndup (s3, sizeof (s3));
z += strncmp (s3, s4, sizeof (s3));
z += strncmp (s3, s4, sizeof (s4));
Index: gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C
===================================================================
--- gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C (revision 251100)
+++ gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C (working copy)
@@ -1,6 +1,6 @@
// Test -Wsizeof-pointer-memaccess warnings.
// { dg-do compile }
-// { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow" }
+// { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow -Wno-stringop-truncation" }
// Test just twice, once with -O0 non-fortified, once with -O2 fortified,
// suppressing buffer overflow warnings.
// { dg-skip-if "" { *-*-* } { "*" } { "-O0" "-O2" } }
@@ -703,12 +703,13 @@ f4 (char *x, char **y, int z, char w[64])
strncat (w, s2, sizeof (w)); // { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" }
stpncpy (w, s1, sizeof (w)); // { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" }
- // These are correct, no warning.
const char s3[] = "foobarbaz";
const char s4[] = "abcde12345678";
- strncpy (x, s3, sizeof (s3));
- strncat (x, s4, sizeof (s4));
- stpncpy (x, s3, sizeof (s3));
+ strncpy (x, s3, sizeof (s3)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination" }
+ strncat (x, s4, sizeof (s4)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination" }
+ stpncpy (x, s3, sizeof (s3)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination" }
+
+ // These are safe, no warning.
y[1] = strndup (s3, sizeof (s3));
z += strncmp (s3, s4, sizeof (s3));
z += strncmp (s3, s4, sizeof (s4));
Index: gcc/testsuite/gcc.dg/Walloca-1.c
===================================================================
--- gcc/testsuite/gcc.dg/Walloca-1.c (revision 251100)
+++ gcc/testsuite/gcc.dg/Walloca-1.c (working copy)
@@ -1,6 +1,6 @@
/* { dg-do compile } */
/* { dg-require-effective-target alloca } */
-/* { dg-options "-Walloca-larger-than=2000 -O2" } */
+/* { dg-options "-Walloca-larger-than=2000 -O2 -ftrack-macro-expansion=0" } */
#define alloca __builtin_alloca
Index: gcc/testsuite/gcc.dg/torture/Wsizeof-pointer-memaccess1.c
===================================================================
--- gcc/testsuite/gcc.dg/torture/Wsizeof-pointer-memaccess1.c (revision 251100)
+++ gcc/testsuite/gcc.dg/torture/Wsizeof-pointer-memaccess1.c (working copy)
@@ -1,6 +1,6 @@
/* Test -Wsizeof-pointer-memaccess warnings. */
/* { dg-do compile } */
-/* { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow" } */
+/* { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow -Wno-stringop-truncation" } */
/* Test just twice, once with -O0 non-fortified, once with -O2 fortified. */
/* { dg-skip-if "" { *-*-* } { "*" } { "-O0" "-O2" } } */
/* { dg-skip-if "" { *-*-* } { "-flto" } { "" } } */
@@ -704,12 +704,17 @@ f4 (char *x, char **y, int z, char w[64])
strncat (w, s2, sizeof (w)); /* { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" } */
stpncpy (w, s1, sizeof (w)); /* { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" } */
- /* These are correct, no warning. */
+ /* These are pointless when the destination is large enough, and
+ cause overflow otherwise. If the copies are guaranteed to be
+ safe the calls might as well be replaced by strcat(), strcpy(),
+ or memcpy(). */
const char s3[] = "foobarbaz";
const char s4[] = "abcde12345678";
- strncpy (x, s3, sizeof (s3));
- strncat (x, s4, sizeof (s4));
- stpncpy (x, s3, sizeof (s3));
+ strncpy (x, s3, sizeof (s3)); /* { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" } */
+ strncat (x, s4, sizeof (s4)); /* { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" } */
+ stpncpy (x, s3, sizeof (s3)); /* { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" } */
+
+ /* These are correct, no warning. */
y[1] = strndup (s3, sizeof (s3));
z += strncmp (s3, s4, sizeof (s3));
z += strncmp (s3, s4, sizeof (s4));
Index: gcc/testsuite/gcc.dg/torture/pr63554.c
===================================================================
--- gcc/testsuite/gcc.dg/torture/pr63554.c (revision 251100)
+++ gcc/testsuite/gcc.dg/torture/pr63554.c (working copy)
@@ -1,4 +1,5 @@
-/* { dg-do compile } */
+/* PR c/63554 - ice in "execute_todo, at passes.c:1797" with -O3
+ { dg-do compile } */
char *a;
void
@@ -7,3 +8,5 @@ nssutil_ReadSecmodDB (void)
long b = __builtin_object_size (0, 0);
a = __builtin___strncat_chk (a, " ", 1, b);
}
+
+/* { dg-prune-output "\\\[-Wstringop-overflow=]" } */
Index: gcc/tree-ssa-strlen.c
===================================================================
--- gcc/tree-ssa-strlen.c (revision 251100)
+++ gcc/tree-ssa-strlen.c (working copy)
@@ -40,11 +40,16 @@ along with GCC; see the file COPYING3. If not see
#include "expr.h"
#include "tree-dfa.h"
#include "domwalk.h"
+#include "tree-ssa-alias.h"
#include "tree-ssa-propagate.h"
#include "params.h"
#include "ipa-chkp.h"
#include "tree-hash-traits.h"
#include "builtins.h"
+#include "diagnostic-core.h"
+#include "diagnostic.h"
+#include "intl.h"
+#include "attribs.h"
/* A vector indexed by SSA_NAME_VERSION. 0 means unknown, positive value
is an index into strinfo vector, negative value stands for
@@ -146,6 +151,9 @@ struct decl_stridxlist_map
mappings. */
static hash_map<tree_decl_hash, stridxlist> *decl_to_stridxlist_htab;
+typedef std::pair<int, location_t> stridx_strlenloc;
+static hash_map<tree, stridx_strlenloc> strlen_to_stridx;
+
/* Obstack for struct stridxlist and struct decl_stridxlist_map. */
static struct obstack stridx_obstack;
@@ -1197,6 +1205,9 @@ handle_builtin_strlen (gimple_stmt_iterator *gsi)
si->nonzero_chars = lhs;
gcc_assert (si->full_string_p);
}
+
+ location_t loc = gimple_location (stmt);
+ strlen_to_stridx.put (lhs, stridx_strlenloc (idx, loc));
return;
}
}
@@ -1240,6 +1251,9 @@ handle_builtin_strlen (gimple_stmt_iterator *gsi)
strinfo *si = new_strinfo (src, idx, lhs, true);
set_strinfo (idx, si);
find_equal_ptrs (src, idx);
+
+ location_t loc = gimple_location (stmt);
+ strlen_to_stridx.put (lhs, stridx_strlenloc (idx, loc));
}
}
@@ -1606,6 +1620,270 @@ handle_builtin_strcpy (enum built_in_function bcod
fprintf (dump_file, "not possible.\n");
}
+/* Return true if LEN depends on a call to strlen(SRC) in an interesting
+ way. LEN can either be an integer expression, or a pointer (to char).
+ When it is the latter (such as in recursive calls to self) is is
+ assumed to be the argument in some call to strlen() whose relationship
+ to SRC is being ascertained. */
+
+static bool
+is_strlen_related_p (tree src, tree len)
+{
+ if (TREE_CODE (TREE_TYPE (len)) == POINTER_TYPE
+ && operand_equal_p (src, len, 0))
+ return true;
+
+ if (TREE_CODE (len) != SSA_NAME)
+ return false;
+
+ gimple *def_stmt = SSA_NAME_DEF_STMT (len);
+ if (!def_stmt)
+ return false;
+
+ if (is_gimple_call (def_stmt))
+ {
+ tree func = gimple_call_fndecl (def_stmt);
+ if (!valid_builtin_call (def_stmt)
+ || DECL_FUNCTION_CODE (func) != BUILT_IN_STRLEN)
+ return false;
+
+ tree arg = gimple_call_arg (def_stmt, 0);
+ return is_strlen_related_p (src, arg);
+ }
+
+ if (!is_gimple_assign (def_stmt))
+ return false;
+
+ tree_code code = gimple_assign_rhs_code (def_stmt);
+ tree rhs1 = gimple_assign_rhs1 (def_stmt);
+ tree rhstype = TREE_TYPE (rhs1);
+
+ if ((TREE_CODE (rhstype) == POINTER_TYPE && code == POINTER_PLUS_EXPR)
+ || (INTEGRAL_TYPE_P (rhstype)
+ && (code == BIT_AND_EXPR
+ || code == NOP_EXPR)))
+ {
+ /* Pointer plus (an integer) and integer cast or truncation are
+ considered among the (potentially) related expressions to strlen.
+ Others are not. */
+ return is_strlen_related_p (src, rhs1);
+ }
+
+ return false;
+}
+
+/* A helper of handle_builtin_stxncpy. Check to see if the specified
+ bound is a) equal to the size of the destination DST and if so, b)
+ if it's immediately followed by DST[LEN - 1] = '\0'. If a) holds
+ and b) does not, warn. Otherwise, do nothing. Return true if
+ diagnostic has been issued.
+
+ The purpose is to diagnose calls to strncpy and stpncpy that do
+ not nul-terminate the copy while allowing for the idiom where
+ such a call is immediately followed by setting the last element
+ to nul, as in:
+ char a[32];
+ strncpy (a, s, sizeof a);
+ a[sizeof a - 1] = '\0';
+*/
+
+static bool
+maybe_diag_stxncpy_trunc (gimple_stmt_iterator gsi, tree src, tree len)
+{
+ if (!warn_stringop_truncation)
+ return false;
+
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (TREE_CODE (len) != INTEGER_CST)
+ return false;
+
+ /* Negative value is the constant string length. */
+ int sidx = get_stridx (src);
+ if (sidx < 0 && compare_tree_int (len, ~sidx) > 0)
+ return false;
+
+ tree dst = gimple_call_arg (stmt, 0);
+
+ /* See if the destination is declared with attribute "nonstring"
+ and if so, avoid the truncation warning. */
+ if (TREE_CODE (dst) == SSA_NAME)
+ {
+ if (SSA_NAME_IS_DEFAULT_DEF (dst))
+ dst = SSA_NAME_VAR (dst);
+ else
+ {
+ gimple *def = SSA_NAME_DEF_STMT (dst);
+
+ if (is_gimple_assign (def)
+ && gimple_assign_rhs_code (def) == ADDR_EXPR)
+ dst = gimple_assign_rhs1 (def);
+ }
+ }
+
+ tree dstdecl = dst;
+ if (TREE_CODE (dstdecl) == ADDR_EXPR)
+ dstdecl = TREE_OPERAND (dstdecl, 0);
+
+ {
+ tree d = dstdecl;
+ if (TREE_CODE (d) == COMPONENT_REF)
+ d = TREE_OPERAND (d, 1);
+
+ if (DECL_P (d) && lookup_attribute ("nonstring", DECL_ATTRIBUTES (d)))
+ return false;
+ }
+
+ /* Look for dst[i] = '\0'; after the stxncpy() call and if found
+ avoid the truncation warning. */
+ gsi_next_nondebug (&gsi);
+ gimple *next_stmt = gsi_stmt (gsi);
+
+ if (!gsi_end_p (gsi) && is_gimple_assign (next_stmt))
+ {
+ HOST_WIDE_INT off;
+ dstdecl = get_addr_base_and_unit_offset (dstdecl, &off);
+
+ tree lhs = gimple_assign_lhs (next_stmt);
+ tree lhsbase = get_addr_base_and_unit_offset (lhs, &off);
+ if (operand_equal_p (dstdecl, lhsbase, 0))
+ return false;
+ }
+
+ strinfo *sisrc = sidx > 0 ? get_strinfo (sidx) : NULL;
+
+ if (sisrc && sisrc->nonzero_chars
+ && tree_fits_uhwi_p (sisrc->nonzero_chars))
+ {
+ /* If the source is shorter than the specified bound the copy
+ is nul-terminated. */
+ if (tree_int_cst_lt (sisrc->nonzero_chars, len))
+ return false;
+
+ /* If the source is longer than the specified bound the copy
+ is truncated. */
+ if (tree_int_cst_lt (len, sisrc->nonzero_chars))
+ {
+ location_t callloc = gimple_location (stmt);
+ tree func = gimple_call_fndecl (stmt);
+
+ warning_at (callloc, OPT_Wstringop_truncation,
+ integer_onep (len)
+ ? G_("%qD output truncated copying %E byte from "
+ "a string of length %E")
+ : G_("%qD output truncated copying %E bytes from "
+ "a string of length %E"),
+ func, len, sisrc->nonzero_chars);
+ return true;
+ }
+
+ /* Otherwise the source length matches the bound. */
+ }
+ else
+ {
+ /* The source length is uknown. Try to determine the destination
+ size and see if it matches the specified bound. If not, bail.
+ Otherwise go on to see if it should be diagnosed for possible
+ truncation. */
+ tree objsize = compute_objsize (dst, 1);
+ if (!objsize)
+ return false;
+
+ if (!tree_int_cst_equal (objsize, len))
+ return false;
+ }
+
+ location_t callloc = gimple_location (stmt);
+ tree func = gimple_call_fndecl (stmt);
+
+ bool warned = warning_at (callloc, OPT_Wstringop_truncation,
+ "%qD specified bound %E equals destination size",
+ func, len);
+
+ gimple_set_no_warning (stmt, true);
+
+ return warned;
+}
+
+/* Check the size argument to the built-in forms of stpncpy and strncpy
+ to see if it's derived from calling strlen() on the source argument
+ and if so, issue a warning. */
+
+static void
+handle_builtin_stxncpy (built_in_function, gimple_stmt_iterator *gsi)
+{
+ gimple *stmt = gsi_stmt (*gsi);
+
+ bool with_bounds = gimple_call_with_bounds_p (stmt);
+
+ tree src = gimple_call_arg (stmt, with_bounds ? 2 : 1);
+ tree len = gimple_call_arg (stmt, with_bounds ? 3 : 2);
+
+ /* If the length argument was computed from strlen(S) for some string
+ S retrieve the strinfo index for the string (PSS->FIRST) alonng with
+ the location of the strlen() call (PSS->SECOND). */
+ stridx_strlenloc *pss = strlen_to_stridx.get (len);
+ if (!pss || pss->first <= 0)
+ {
+ maybe_diag_stxncpy_trunc (*gsi, src, len);
+ return;
+ }
+
+ int sidx = get_stridx (src);
+ strinfo *sisrc = sidx > 0 ? get_strinfo (sidx) : NULL;
+
+ /* Strncpy() et al. cannot modify the source string. Prevent the rest
+ of the pass from invalidating the strinfo data. */
+ if (sisrc)
+ sisrc->dont_invalidate = true;
+
+ /* Retrieve the strinfo data for the string S that LEN was computed
+ from as some function F of strlen (S) (i.e., LEN need not be equal
+ to strlen(S)). */
+ strinfo *silen = get_strinfo (pss->first);
+
+ location_t callloc = gimple_location (stmt);
+
+ tree func = gimple_call_fndecl (stmt);
+
+ bool warned = false;
+
+ /* When -Wstringop-truncation is set, try to determine truncation
+ before diagnosing possible overflow. Truncation is implied by
+ the LEN argument being equal to strlen(SRC), regardless of
+ whether its value is known. Otherwise, issue the more generic
+ -Wstringop-overflow which triggers for LEN arguments that in
+ any meaningful way depend on strlen(SRC). */
+ if (warn_stringop_truncation
+ && sisrc == silen
+ && is_strlen_related_p (src, len))
+ warned = warning_at (callloc, OPT_Wstringop_truncation,
+ "%qD output truncated before terminating nul "
+ "copying as many bytes from a string as its length",
+ func);
+ else if (silen && is_strlen_related_p (src, silen->ptr))
+ warned = warning_at (callloc, OPT_Wstringop_overflow_,
+ "%qD specified bound depends on the length "
+ "of the source argument", func);
+ if (warned)
+ {
+ location_t strlenloc = pss->second;
+ if (strlenloc != UNKNOWN_LOCATION && strlenloc != callloc)
+ inform (strlenloc, "length computed here");
+ }
+}
+
+/* Check the size argument to the built-in forms of strncat to see if
+ it's derived from calling strlen() on the source argument and if so,
+ issue a warning. */
+
+static void
+handle_builtin_strncat (built_in_function bcode, gimple_stmt_iterator *gsi)
+{
+ /* Same as stxncpy(). */
+ handle_builtin_stxncpy (bcode, gsi);
+}
+
/* Handle a memcpy-like ({mem{,p}cpy,__mem{,p}cpy_chk}) call.
If strlen of the second argument is known and length of the third argument
is that plus one, strlen of the first argument is the same after this
@@ -2512,6 +2790,19 @@ strlen_optimize_stmt (gimple_stmt_iterator *gsi)
case BUILT_IN_STPCPY_CHK_CHKP:
handle_builtin_strcpy (DECL_FUNCTION_CODE (callee), gsi);
break;
+
+ case BUILT_IN_STRNCAT:
+ case BUILT_IN_STRNCAT_CHK:
+ handle_builtin_strncat (DECL_FUNCTION_CODE (callee), gsi);
+ break;
+
+ case BUILT_IN_STPNCPY:
+ case BUILT_IN_STPNCPY_CHK:
+ case BUILT_IN_STRNCPY:
+ case BUILT_IN_STRNCPY_CHK:
+ handle_builtin_stxncpy (DECL_FUNCTION_CODE (callee), gsi);
+ break;
+
case BUILT_IN_MEMCPY:
case BUILT_IN_MEMCPY_CHK:
case BUILT_IN_MEMPCPY:
@@ -2575,6 +2866,10 @@ strlen_optimize_stmt (gimple_stmt_iterator *gsi)
else if (code == EQ_EXPR || code == NE_EXPR)
fold_strstr_to_strncmp (gimple_assign_rhs1 (stmt),
gimple_assign_rhs2 (stmt), stmt);
+
+ tree rhs1 = gimple_assign_rhs1 (stmt);
+ if (stridx_strlenloc *ps = strlen_to_stridx.get (rhs1))
+ strlen_to_stridx.put (lhs, *ps);
}
else if (TREE_CODE (lhs) != SSA_NAME && !TREE_SIDE_EFFECTS (lhs))
{
@@ -2826,6 +3121,8 @@ pass_strlen::execute (function *fun)
laststmt.len = NULL_TREE;
laststmt.stridx = 0;
+ strlen_to_stridx.empty ();
+
return 0;
}
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PING] [PATCH 3/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-15 3:06 ` Martin Sebor
@ 2017-08-23 21:11 ` Martin Sebor
2017-08-29 5:07 ` [PING 2] " Martin Sebor
0 siblings, 1 reply; 39+ messages in thread
From: Martin Sebor @ 2017-08-23 21:11 UTC (permalink / raw)
To: Jeff Law, Gcc Patch List
Ping: https://gcc.gnu.org/ml/gcc-patches/2017-08/msg00912.html
Jeff, is this version good to commit or are there any other
changes you'd like to see?
Martin
On 08/14/2017 04:40 PM, Martin Sebor wrote:
> On 08/10/2017 01:29 PM, Martin Sebor wrote:
>>>> diff --git a/gcc/builtins.c b/gcc/builtins.c
>>>> index 016f68d..1aa9e22 100644
>>>> --- a/gcc/builtins.c
>>>> +++ b/gcc/builtins.c
>>> [ ... ]
>>>> +
>>>> + if (TREE_CODE (type) == ARRAY_TYPE)
>>>> + {
>>>> + /* Return the constant size unless it's zero (that's a
>>>> zero-length
>>>> + array likely at the end of a struct). */
>>>> + tree size = TYPE_SIZE_UNIT (type);
>>>> + if (size && TREE_CODE (size) == INTEGER_CST
>>>> + && !integer_zerop (size))
>>>> + return size;
>>>> + }
>>> Q. Do we have a canonical test for the trailing array idiom? In some
>>> contexts isn't it size 1? ISTM This test needs slight improvement.
>>> Ideally we'd use some canonical test for detect the trailing array idiom
>>> rather than open-coding it here. You might look at the array index
>>> warnings in tree-vrp.c to see if it's got a canonical test you can call
>>> or factor and use.
>>
>> You're right, there is an API for this (array_at_struct_end_p,
>> as Richard pointed out). I didn't want to use it because it
>> treats any array at the end of a struct as a flexible array
>> member, but simple tests show that that's what -Wstringop-
>> overflow does now, and it wasn't my intention to tighten up
>> the checking under this change. It surprises me that no tests
>> exposed this. Let me relax the check and think about proposing
>> to tighten it up separately.
>
> Done in the attached patch. (I opened bug 81849 for the enhancement
> to have -Wstringop-overflow diagnose overflows when writing to member
> arrays bigger than 1 element even if they're last).
>
> (I've left the handling for zero size in place because GCC allows
> global arrays to be declared to have zero elements.)
>
>>
>>>> @@ -3883,6 +3920,30 @@ expand_builtin_strncat (tree exp, rtx)
>>>> return NULL_RTX;
>>>> }
>>>>
>>>> +/* Helper to check the sizes of sequences and the destination of calls
>>>> + to __builtin_strncpy (DST, SRC, LEN) and __builtin___strncpy_chk.
>>>> + Returns true on success (no overflow warning), false otherwise. */
>>>> +
>>>> +static bool
>>>> +check_strncpy_sizes (tree exp, tree dst, tree src, tree len)
>>>> +{
>>>> + tree dstsize = compute_objsize (dst, warn_stringop_overflow - 1);
>>>> +
>>>> + if (!check_sizes (OPT_Wstringop_overflow_,
>>>> + exp, len, /*maxlen=*/NULL_TREE, src, dstsize))
>>>> + return false;
>>>> +
>>>> + if (!dstsize || TREE_CODE (len) != INTEGER_CST)
>>>> + return true;
>>>> +
>>>> + if (tree_int_cst_lt (dstsize, len))
>>>> + warning_at (EXPR_LOCATION (exp), OPT_Wstringop_truncation,
>>>> + "%K%qD specified bound %E exceeds destination size %E",
>>>> + exp, get_callee_fndecl (exp), len, dstsize);
>>>> +
>>>> + return true;
>>> So in the case where you issue the warning, what should the return value
>>> be? According to the comment it should be false. It looks like you got
>>> the wrong return value for the tree_int_cst_lt (dstsize, len) test.
>>
>> Corrected. The return value is unused by the only caller so
>> there is no test to exercise it.
>
> Done in the attached patch.
>
>>>> +/* A helper of handle_builtin_stxncpy. Check to see if the specified
>>>> + bound is a) equal to the size of the destination DST and if so, b)
>>>> + if it's immediately followed by DST[LEN - 1] = '\0'. If a) holds
>>>> + and b) does not, warn. Otherwise, do nothing. Return true if
>>>> + diagnostic has been issued.
>>>> +
>>>> + The purpose is to diagnose calls to strncpy and stpncpy that do
>>>> + not nul-terminate the copy while allowing for the idiom where
>>>> + such a call is immediately followed by setting the last element
>>>> + to nul, as in:
>>>> + char a[32];
>>>> + strncpy (a, s, sizeof a);
>>>> + a[sizeof a - 1] = '\0';
>>>> +*/
>>> So using gsi_next to find the next statement could make the heuristic
>>> fail to find the a[sizeof a - 1] = '\0'; statement when debugging is
>>> enabled.
>>>
>>> gsi_next_nondebug would be better as it would skip over any debug insns.
>>
>> Thanks. I'll have to remember this.
>
> I went with this simple approach for now since it worked for GDB.
> If it turns out that there are important instances of this idiom
> that rely on intervening statements the warning can be relaxed.
>
>>> What might be even better would be to use the immediate uses of the
>>> memory tag. For your case there should be only one immediate use and it
>>> should point to the statement which NUL terminates the destination. Or
>>> maybe that would be worse in that you only want to allow this exception
>>> when the statements are consecutive.
>
>>>> /* Handle a memcpy-like ({mem{,p}cpy,__mem{,p}cpy_chk}) call.
>>>> If strlen of the second argument is known and length of the third
>>>> argument
>>>> is that plus one, strlen of the first argument is the same after
>>>> this
>>>> @@ -2512,6 +2789,19 @@ strlen_optimize_stmt (gimple_stmt_iterator *gsi)
>>> You still need to rename strlen_optimize_stmt since after your changes
>>> it does both optimizations and warnings.
>>
>> I'm not sure I understand why. It's a pre-existing function that
>> just dispatches to the built-in handlers. We don't rename function
>> callers each time we improve error/warning detection in some
>> function they call (case in point: all the expanders in builtins.c)
>> Why do it here? And what would be a suitable name? All that comes
>> to my mind is awkward variations on strlen_optimize_stmt_and_warn.
>
> I've left the function name as is. If you feel strongly that
> it needs to be renamed let me know.
>
> Martin
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PING 2] [PATCH 3/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-23 21:11 ` [PING] " Martin Sebor
@ 2017-08-29 5:07 ` Martin Sebor
2017-09-19 15:44 ` [PING 3] " Martin Sebor
0 siblings, 1 reply; 39+ messages in thread
From: Martin Sebor @ 2017-08-29 5:07 UTC (permalink / raw)
To: Jeff Law, Gcc Patch List
Ping #2: https://gcc.gnu.org/ml/gcc-patches/2017-08/msg00912.html
On 08/23/2017 01:46 PM, Martin Sebor wrote:
> Ping: https://gcc.gnu.org/ml/gcc-patches/2017-08/msg00912.html
>
> Jeff, is this version good to commit or are there any other
> changes you'd like to see?
>
> Martin
>
> On 08/14/2017 04:40 PM, Martin Sebor wrote:
>> On 08/10/2017 01:29 PM, Martin Sebor wrote:
>>>>> diff --git a/gcc/builtins.c b/gcc/builtins.c
>>>>> index 016f68d..1aa9e22 100644
>>>>> --- a/gcc/builtins.c
>>>>> +++ b/gcc/builtins.c
>>>> [ ... ]
>>>>> +
>>>>> + if (TREE_CODE (type) == ARRAY_TYPE)
>>>>> + {
>>>>> + /* Return the constant size unless it's zero (that's a
>>>>> zero-length
>>>>> + array likely at the end of a struct). */
>>>>> + tree size = TYPE_SIZE_UNIT (type);
>>>>> + if (size && TREE_CODE (size) == INTEGER_CST
>>>>> + && !integer_zerop (size))
>>>>> + return size;
>>>>> + }
>>>> Q. Do we have a canonical test for the trailing array idiom? In some
>>>> contexts isn't it size 1? ISTM This test needs slight improvement.
>>>> Ideally we'd use some canonical test for detect the trailing array
>>>> idiom
>>>> rather than open-coding it here. You might look at the array index
>>>> warnings in tree-vrp.c to see if it's got a canonical test you can call
>>>> or factor and use.
>>>
>>> You're right, there is an API for this (array_at_struct_end_p,
>>> as Richard pointed out). I didn't want to use it because it
>>> treats any array at the end of a struct as a flexible array
>>> member, but simple tests show that that's what -Wstringop-
>>> overflow does now, and it wasn't my intention to tighten up
>>> the checking under this change. It surprises me that no tests
>>> exposed this. Let me relax the check and think about proposing
>>> to tighten it up separately.
>>
>> Done in the attached patch. (I opened bug 81849 for the enhancement
>> to have -Wstringop-overflow diagnose overflows when writing to member
>> arrays bigger than 1 element even if they're last).
>>
>> (I've left the handling for zero size in place because GCC allows
>> global arrays to be declared to have zero elements.)
>>
>>>
>>>>> @@ -3883,6 +3920,30 @@ expand_builtin_strncat (tree exp, rtx)
>>>>> return NULL_RTX;
>>>>> }
>>>>>
>>>>> +/* Helper to check the sizes of sequences and the destination of
>>>>> calls
>>>>> + to __builtin_strncpy (DST, SRC, LEN) and __builtin___strncpy_chk.
>>>>> + Returns true on success (no overflow warning), false
>>>>> otherwise. */
>>>>> +
>>>>> +static bool
>>>>> +check_strncpy_sizes (tree exp, tree dst, tree src, tree len)
>>>>> +{
>>>>> + tree dstsize = compute_objsize (dst, warn_stringop_overflow - 1);
>>>>> +
>>>>> + if (!check_sizes (OPT_Wstringop_overflow_,
>>>>> + exp, len, /*maxlen=*/NULL_TREE, src, dstsize))
>>>>> + return false;
>>>>> +
>>>>> + if (!dstsize || TREE_CODE (len) != INTEGER_CST)
>>>>> + return true;
>>>>> +
>>>>> + if (tree_int_cst_lt (dstsize, len))
>>>>> + warning_at (EXPR_LOCATION (exp), OPT_Wstringop_truncation,
>>>>> + "%K%qD specified bound %E exceeds destination size %E",
>>>>> + exp, get_callee_fndecl (exp), len, dstsize);
>>>>> +
>>>>> + return true;
>>>> So in the case where you issue the warning, what should the return
>>>> value
>>>> be? According to the comment it should be false. It looks like you
>>>> got
>>>> the wrong return value for the tree_int_cst_lt (dstsize, len) test.
>>>
>>> Corrected. The return value is unused by the only caller so
>>> there is no test to exercise it.
>>
>> Done in the attached patch.
>>
>>>>> +/* A helper of handle_builtin_stxncpy. Check to see if the specified
>>>>> + bound is a) equal to the size of the destination DST and if so, b)
>>>>> + if it's immediately followed by DST[LEN - 1] = '\0'. If a) holds
>>>>> + and b) does not, warn. Otherwise, do nothing. Return true if
>>>>> + diagnostic has been issued.
>>>>> +
>>>>> + The purpose is to diagnose calls to strncpy and stpncpy that do
>>>>> + not nul-terminate the copy while allowing for the idiom where
>>>>> + such a call is immediately followed by setting the last element
>>>>> + to nul, as in:
>>>>> + char a[32];
>>>>> + strncpy (a, s, sizeof a);
>>>>> + a[sizeof a - 1] = '\0';
>>>>> +*/
>>>> So using gsi_next to find the next statement could make the heuristic
>>>> fail to find the a[sizeof a - 1] = '\0'; statement when debugging is
>>>> enabled.
>>>>
>>>> gsi_next_nondebug would be better as it would skip over any debug
>>>> insns.
>>>
>>> Thanks. I'll have to remember this.
>>
>> I went with this simple approach for now since it worked for GDB.
>> If it turns out that there are important instances of this idiom
>> that rely on intervening statements the warning can be relaxed.
>>
>>>> What might be even better would be to use the immediate uses of the
>>>> memory tag. For your case there should be only one immediate use
>>>> and it
>>>> should point to the statement which NUL terminates the destination. Or
>>>> maybe that would be worse in that you only want to allow this exception
>>>> when the statements are consecutive.
>>
>>>>> /* Handle a memcpy-like ({mem{,p}cpy,__mem{,p}cpy_chk}) call.
>>>>> If strlen of the second argument is known and length of the third
>>>>> argument
>>>>> is that plus one, strlen of the first argument is the same after
>>>>> this
>>>>> @@ -2512,6 +2789,19 @@ strlen_optimize_stmt (gimple_stmt_iterator
>>>>> *gsi)
>>>> You still need to rename strlen_optimize_stmt since after your changes
>>>> it does both optimizations and warnings.
>>>
>>> I'm not sure I understand why. It's a pre-existing function that
>>> just dispatches to the built-in handlers. We don't rename function
>>> callers each time we improve error/warning detection in some
>>> function they call (case in point: all the expanders in builtins.c)
>>> Why do it here? And what would be a suitable name? All that comes
>>> to my mind is awkward variations on strlen_optimize_stmt_and_warn.
>>
>> I've left the function name as is. If you feel strongly that
>> it needs to be renamed let me know.
>>
>> Martin
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PING 3] [PATCH 3/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-29 5:07 ` [PING 2] " Martin Sebor
@ 2017-09-19 15:44 ` Martin Sebor
2017-09-26 2:27 ` [PING 4] " Martin Sebor
0 siblings, 1 reply; 39+ messages in thread
From: Martin Sebor @ 2017-09-19 15:44 UTC (permalink / raw)
To: Jeff Law, Gcc Patch List
Ping #3: https://gcc.gnu.org/ml/gcc-patches/2017-08/msg00912.html
Thanks
Martin
On 08/28/2017 08:34 PM, Martin Sebor wrote:
> Ping #2: https://gcc.gnu.org/ml/gcc-patches/2017-08/msg00912.html
>
> On 08/23/2017 01:46 PM, Martin Sebor wrote:
>> Ping: https://gcc.gnu.org/ml/gcc-patches/2017-08/msg00912.html
>>
>> Jeff, is this version good to commit or are there any other
>> changes you'd like to see?
>>
>> Martin
>>
>> On 08/14/2017 04:40 PM, Martin Sebor wrote:
>>> On 08/10/2017 01:29 PM, Martin Sebor wrote:
>>>>>> diff --git a/gcc/builtins.c b/gcc/builtins.c
>>>>>> index 016f68d..1aa9e22 100644
>>>>>> --- a/gcc/builtins.c
>>>>>> +++ b/gcc/builtins.c
>>>>> [ ... ]
>>>>>> +
>>>>>> + if (TREE_CODE (type) == ARRAY_TYPE)
>>>>>> + {
>>>>>> + /* Return the constant size unless it's zero (that's a
>>>>>> zero-length
>>>>>> + array likely at the end of a struct). */
>>>>>> + tree size = TYPE_SIZE_UNIT (type);
>>>>>> + if (size && TREE_CODE (size) == INTEGER_CST
>>>>>> + && !integer_zerop (size))
>>>>>> + return size;
>>>>>> + }
>>>>> Q. Do we have a canonical test for the trailing array idiom? In some
>>>>> contexts isn't it size 1? ISTM This test needs slight improvement.
>>>>> Ideally we'd use some canonical test for detect the trailing array
>>>>> idiom
>>>>> rather than open-coding it here. You might look at the array index
>>>>> warnings in tree-vrp.c to see if it's got a canonical test you can
>>>>> call
>>>>> or factor and use.
>>>>
>>>> You're right, there is an API for this (array_at_struct_end_p,
>>>> as Richard pointed out). I didn't want to use it because it
>>>> treats any array at the end of a struct as a flexible array
>>>> member, but simple tests show that that's what -Wstringop-
>>>> overflow does now, and it wasn't my intention to tighten up
>>>> the checking under this change. It surprises me that no tests
>>>> exposed this. Let me relax the check and think about proposing
>>>> to tighten it up separately.
>>>
>>> Done in the attached patch. (I opened bug 81849 for the enhancement
>>> to have -Wstringop-overflow diagnose overflows when writing to member
>>> arrays bigger than 1 element even if they're last).
>>>
>>> (I've left the handling for zero size in place because GCC allows
>>> global arrays to be declared to have zero elements.)
>>>
>>>>
>>>>>> @@ -3883,6 +3920,30 @@ expand_builtin_strncat (tree exp, rtx)
>>>>>> return NULL_RTX;
>>>>>> }
>>>>>>
>>>>>> +/* Helper to check the sizes of sequences and the destination of
>>>>>> calls
>>>>>> + to __builtin_strncpy (DST, SRC, LEN) and __builtin___strncpy_chk.
>>>>>> + Returns true on success (no overflow warning), false
>>>>>> otherwise. */
>>>>>> +
>>>>>> +static bool
>>>>>> +check_strncpy_sizes (tree exp, tree dst, tree src, tree len)
>>>>>> +{
>>>>>> + tree dstsize = compute_objsize (dst, warn_stringop_overflow - 1);
>>>>>> +
>>>>>> + if (!check_sizes (OPT_Wstringop_overflow_,
>>>>>> + exp, len, /*maxlen=*/NULL_TREE, src, dstsize))
>>>>>> + return false;
>>>>>> +
>>>>>> + if (!dstsize || TREE_CODE (len) != INTEGER_CST)
>>>>>> + return true;
>>>>>> +
>>>>>> + if (tree_int_cst_lt (dstsize, len))
>>>>>> + warning_at (EXPR_LOCATION (exp), OPT_Wstringop_truncation,
>>>>>> + "%K%qD specified bound %E exceeds destination size %E",
>>>>>> + exp, get_callee_fndecl (exp), len, dstsize);
>>>>>> +
>>>>>> + return true;
>>>>> So in the case where you issue the warning, what should the return
>>>>> value
>>>>> be? According to the comment it should be false. It looks like you
>>>>> got
>>>>> the wrong return value for the tree_int_cst_lt (dstsize, len) test.
>>>>
>>>> Corrected. The return value is unused by the only caller so
>>>> there is no test to exercise it.
>>>
>>> Done in the attached patch.
>>>
>>>>>> +/* A helper of handle_builtin_stxncpy. Check to see if the
>>>>>> specified
>>>>>> + bound is a) equal to the size of the destination DST and if
>>>>>> so, b)
>>>>>> + if it's immediately followed by DST[LEN - 1] = '\0'. If a) holds
>>>>>> + and b) does not, warn. Otherwise, do nothing. Return true if
>>>>>> + diagnostic has been issued.
>>>>>> +
>>>>>> + The purpose is to diagnose calls to strncpy and stpncpy that do
>>>>>> + not nul-terminate the copy while allowing for the idiom where
>>>>>> + such a call is immediately followed by setting the last element
>>>>>> + to nul, as in:
>>>>>> + char a[32];
>>>>>> + strncpy (a, s, sizeof a);
>>>>>> + a[sizeof a - 1] = '\0';
>>>>>> +*/
>>>>> So using gsi_next to find the next statement could make the heuristic
>>>>> fail to find the a[sizeof a - 1] = '\0'; statement when debugging is
>>>>> enabled.
>>>>>
>>>>> gsi_next_nondebug would be better as it would skip over any debug
>>>>> insns.
>>>>
>>>> Thanks. I'll have to remember this.
>>>
>>> I went with this simple approach for now since it worked for GDB.
>>> If it turns out that there are important instances of this idiom
>>> that rely on intervening statements the warning can be relaxed.
>>>
>>>>> What might be even better would be to use the immediate uses of the
>>>>> memory tag. For your case there should be only one immediate use
>>>>> and it
>>>>> should point to the statement which NUL terminates the
>>>>> destination. Or
>>>>> maybe that would be worse in that you only want to allow this
>>>>> exception
>>>>> when the statements are consecutive.
>>>
>>>>>> /* Handle a memcpy-like ({mem{,p}cpy,__mem{,p}cpy_chk}) call.
>>>>>> If strlen of the second argument is known and length of the third
>>>>>> argument
>>>>>> is that plus one, strlen of the first argument is the same after
>>>>>> this
>>>>>> @@ -2512,6 +2789,19 @@ strlen_optimize_stmt (gimple_stmt_iterator
>>>>>> *gsi)
>>>>> You still need to rename strlen_optimize_stmt since after your changes
>>>>> it does both optimizations and warnings.
>>>>
>>>> I'm not sure I understand why. It's a pre-existing function that
>>>> just dispatches to the built-in handlers. We don't rename function
>>>> callers each time we improve error/warning detection in some
>>>> function they call (case in point: all the expanders in builtins.c)
>>>> Why do it here? And what would be a suitable name? All that comes
>>>> to my mind is awkward variations on strlen_optimize_stmt_and_warn.
>>>
>>> I've left the function name as is. If you feel strongly that
>>> it needs to be renamed let me know.
>>>
>>> Martin
>>
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PING 4] [PATCH 3/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-09-19 15:44 ` [PING 3] " Martin Sebor
@ 2017-09-26 2:27 ` Martin Sebor
0 siblings, 0 replies; 39+ messages in thread
From: Martin Sebor @ 2017-09-26 2:27 UTC (permalink / raw)
To: Jeff Law, Gcc Patch List
Ping #4: https://gcc.gnu.org/ml/gcc-patches/2017-08/msg00912.html
On 09/19/2017 09:44 AM, Martin Sebor wrote:
> Ping #3: https://gcc.gnu.org/ml/gcc-patches/2017-08/msg00912.html
>
> Thanks
> Martin
>
> On 08/28/2017 08:34 PM, Martin Sebor wrote:
>> Ping #2: https://gcc.gnu.org/ml/gcc-patches/2017-08/msg00912.html
>>
>> On 08/23/2017 01:46 PM, Martin Sebor wrote:
>>> Ping: https://gcc.gnu.org/ml/gcc-patches/2017-08/msg00912.html
>>>
>>> Jeff, is this version good to commit or are there any other
>>> changes you'd like to see?
>>>
>>> Martin
>>>
>>> On 08/14/2017 04:40 PM, Martin Sebor wrote:
>>>> On 08/10/2017 01:29 PM, Martin Sebor wrote:
>>>>>>> diff --git a/gcc/builtins.c b/gcc/builtins.c
>>>>>>> index 016f68d..1aa9e22 100644
>>>>>>> --- a/gcc/builtins.c
>>>>>>> +++ b/gcc/builtins.c
>>>>>> [ ... ]
>>>>>>> +
>>>>>>> + if (TREE_CODE (type) == ARRAY_TYPE)
>>>>>>> + {
>>>>>>> + /* Return the constant size unless it's zero (that's a
>>>>>>> zero-length
>>>>>>> + array likely at the end of a struct). */
>>>>>>> + tree size = TYPE_SIZE_UNIT (type);
>>>>>>> + if (size && TREE_CODE (size) == INTEGER_CST
>>>>>>> + && !integer_zerop (size))
>>>>>>> + return size;
>>>>>>> + }
>>>>>> Q. Do we have a canonical test for the trailing array idiom? In
>>>>>> some
>>>>>> contexts isn't it size 1? ISTM This test needs slight improvement.
>>>>>> Ideally we'd use some canonical test for detect the trailing array
>>>>>> idiom
>>>>>> rather than open-coding it here. You might look at the array index
>>>>>> warnings in tree-vrp.c to see if it's got a canonical test you can
>>>>>> call
>>>>>> or factor and use.
>>>>>
>>>>> You're right, there is an API for this (array_at_struct_end_p,
>>>>> as Richard pointed out). I didn't want to use it because it
>>>>> treats any array at the end of a struct as a flexible array
>>>>> member, but simple tests show that that's what -Wstringop-
>>>>> overflow does now, and it wasn't my intention to tighten up
>>>>> the checking under this change. It surprises me that no tests
>>>>> exposed this. Let me relax the check and think about proposing
>>>>> to tighten it up separately.
>>>>
>>>> Done in the attached patch. (I opened bug 81849 for the enhancement
>>>> to have -Wstringop-overflow diagnose overflows when writing to member
>>>> arrays bigger than 1 element even if they're last).
>>>>
>>>> (I've left the handling for zero size in place because GCC allows
>>>> global arrays to be declared to have zero elements.)
>>>>
>>>>>
>>>>>>> @@ -3883,6 +3920,30 @@ expand_builtin_strncat (tree exp, rtx)
>>>>>>> return NULL_RTX;
>>>>>>> }
>>>>>>>
>>>>>>> +/* Helper to check the sizes of sequences and the destination of
>>>>>>> calls
>>>>>>> + to __builtin_strncpy (DST, SRC, LEN) and
>>>>>>> __builtin___strncpy_chk.
>>>>>>> + Returns true on success (no overflow warning), false
>>>>>>> otherwise. */
>>>>>>> +
>>>>>>> +static bool
>>>>>>> +check_strncpy_sizes (tree exp, tree dst, tree src, tree len)
>>>>>>> +{
>>>>>>> + tree dstsize = compute_objsize (dst, warn_stringop_overflow - 1);
>>>>>>> +
>>>>>>> + if (!check_sizes (OPT_Wstringop_overflow_,
>>>>>>> + exp, len, /*maxlen=*/NULL_TREE, src, dstsize))
>>>>>>> + return false;
>>>>>>> +
>>>>>>> + if (!dstsize || TREE_CODE (len) != INTEGER_CST)
>>>>>>> + return true;
>>>>>>> +
>>>>>>> + if (tree_int_cst_lt (dstsize, len))
>>>>>>> + warning_at (EXPR_LOCATION (exp), OPT_Wstringop_truncation,
>>>>>>> + "%K%qD specified bound %E exceeds destination size %E",
>>>>>>> + exp, get_callee_fndecl (exp), len, dstsize);
>>>>>>> +
>>>>>>> + return true;
>>>>>> So in the case where you issue the warning, what should the return
>>>>>> value
>>>>>> be? According to the comment it should be false. It looks like you
>>>>>> got
>>>>>> the wrong return value for the tree_int_cst_lt (dstsize, len) test.
>>>>>
>>>>> Corrected. The return value is unused by the only caller so
>>>>> there is no test to exercise it.
>>>>
>>>> Done in the attached patch.
>>>>
>>>>>>> +/* A helper of handle_builtin_stxncpy. Check to see if the
>>>>>>> specified
>>>>>>> + bound is a) equal to the size of the destination DST and if
>>>>>>> so, b)
>>>>>>> + if it's immediately followed by DST[LEN - 1] = '\0'. If a)
>>>>>>> holds
>>>>>>> + and b) does not, warn. Otherwise, do nothing. Return true if
>>>>>>> + diagnostic has been issued.
>>>>>>> +
>>>>>>> + The purpose is to diagnose calls to strncpy and stpncpy that do
>>>>>>> + not nul-terminate the copy while allowing for the idiom where
>>>>>>> + such a call is immediately followed by setting the last element
>>>>>>> + to nul, as in:
>>>>>>> + char a[32];
>>>>>>> + strncpy (a, s, sizeof a);
>>>>>>> + a[sizeof a - 1] = '\0';
>>>>>>> +*/
>>>>>> So using gsi_next to find the next statement could make the heuristic
>>>>>> fail to find the a[sizeof a - 1] = '\0'; statement when debugging is
>>>>>> enabled.
>>>>>>
>>>>>> gsi_next_nondebug would be better as it would skip over any debug
>>>>>> insns.
>>>>>
>>>>> Thanks. I'll have to remember this.
>>>>
>>>> I went with this simple approach for now since it worked for GDB.
>>>> If it turns out that there are important instances of this idiom
>>>> that rely on intervening statements the warning can be relaxed.
>>>>
>>>>>> What might be even better would be to use the immediate uses of the
>>>>>> memory tag. For your case there should be only one immediate use
>>>>>> and it
>>>>>> should point to the statement which NUL terminates the
>>>>>> destination. Or
>>>>>> maybe that would be worse in that you only want to allow this
>>>>>> exception
>>>>>> when the statements are consecutive.
>>>>
>>>>>>> /* Handle a memcpy-like ({mem{,p}cpy,__mem{,p}cpy_chk}) call.
>>>>>>> If strlen of the second argument is known and length of the
>>>>>>> third
>>>>>>> argument
>>>>>>> is that plus one, strlen of the first argument is the same after
>>>>>>> this
>>>>>>> @@ -2512,6 +2789,19 @@ strlen_optimize_stmt (gimple_stmt_iterator
>>>>>>> *gsi)
>>>>>> You still need to rename strlen_optimize_stmt since after your
>>>>>> changes
>>>>>> it does both optimizations and warnings.
>>>>>
>>>>> I'm not sure I understand why. It's a pre-existing function that
>>>>> just dispatches to the built-in handlers. We don't rename function
>>>>> callers each time we improve error/warning detection in some
>>>>> function they call (case in point: all the expanders in builtins.c)
>>>>> Why do it here? And what would be a suitable name? All that comes
>>>>> to my mind is awkward variations on strlen_optimize_stmt_and_warn.
>>>>
>>>> I've left the function name as is. If you feel strongly that
>>>> it needs to be renamed let me know.
>>>>
>>>> Martin
>>>
>>
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 3/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-10 20:21 ` Martin Sebor
2017-08-15 3:06 ` Martin Sebor
@ 2017-10-02 22:15 ` Jeff Law
2017-10-21 0:26 ` Martin Sebor
1 sibling, 1 reply; 39+ messages in thread
From: Jeff Law @ 2017-10-02 22:15 UTC (permalink / raw)
To: Martin Sebor, Gcc Patch List
On 08/10/2017 01:29 PM, Martin Sebor wrote:
>>> diff --git a/gcc/builtins.c b/gcc/builtins.c
>>> index 016f68d..1aa9e22 100644
>>> --- a/gcc/builtins.c
>>> +++ b/gcc/builtins.c
>> [ ... ]
>>> +
>>> +Â if (TREE_CODE (type) == ARRAY_TYPE)
>>> +Â Â Â {
>>> +Â Â Â Â Â /* Return the constant size unless it's zero (that's a
>>> zero-length
>>> +    array likely at the end of a struct). */
>>> +Â Â Â Â Â tree size = TYPE_SIZE_UNIT (type);
>>> +Â Â Â Â Â if (size && TREE_CODE (size) == INTEGER_CST
>>> +Â Â Â Â Â && !integer_zerop (size))
>>> +Â Â Â return size;
>>> +Â Â Â }
>> Q. Do we have a canonical test for the trailing array idiom?  In some
>> contexts isn't it size 1? ISTM This test needs slight improvement.
>> Ideally we'd use some canonical test for detect the trailing array idiom
>> rather than open-coding it here. You might look at the array index
>> warnings in tree-vrp.c to see if it's got a canonical test you can call
>> or factor and use.
>
> You're right, there is an API for this (array_at_struct_end_p,
> as Richard pointed out). I didn't want to use it because it
> treats any array at the end of a struct as a flexible array
> member, but simple tests show that that's what -Wstringop-
> overflow does now, and it wasn't my intention to tighten up
> the checking under this change. It surprises me that no tests
> exposed this. Let me relax the check and think about proposing
> to tighten it up separately.
>
>
>
>> What might be even better would be to use the immediate uses of the
>> memory tag. For your case there should be only one immediate use and it
>> should point to the statement which NUL terminates the destination. Or
>> maybe that would be worse in that you only want to allow this exception
>> when the statements are consecutive.
>
> I'll have to try this to better understand how it might work.
It's actually quite simple.
Rather than looking at the next statement in the chain via
gsi_next_nondebug you follow the def->use chain for the memory tag
associated with the string copy statement.
/* Get the memory tag that is defined by this statement. */
defvar = gimple_vdef (gsi_stmt (gsi));
imm_use_iterator iter;
gimple *use_stmt;
if (num_imm_uses (defvar) == 1)
{
imm_use_terator iter;
gimple *use_stmt;
/* Iterate over the immediate uses of the memory tag. */
FOR_EACH_IMM_USE_STMT (use_stmt, ui, defvar)
{
Check if STMT is dst[i] = '\0'
}
}
The check that there is a single immediate use is designed to make sure
you get a warning for this scenario:
strxncpy
read the destination
terminate the destination
Which I think you'd want to consider non-terminated because of the read
of the destination prior to termination.
But avoids warnings for
strxncpy
stuff that doesn't read the destination
terminate the destintion
>> You still need to rename strlen_optimize_stmt since after your changes
>> it does both optimizations and warnings.
>
> I'm not sure I understand why. It's a pre-existing function that
> just dispatches to the built-in handlers. We don't rename function
> callers each time we improve error/warning detection in some
> function they call (case in point: all the expanders in builtins.c)
> Why do it here? And what would be a suitable name? All that comes
> to my mind is awkward variations on strlen_optimize_stmt_and_warn.
Actually we often end up renaming functions as their capabilities
change. If I was to read that name, I'd think its only purpose was to
optimize. Given its static with a single use we should just fix it.
Something in compute_objsize I just noticed.
When DEST is an SSA_NAME, you follow the use->def chain back to its
defining statement, then get a new dest from the RHS of that statement:
> + if (TREE_CODE (dest) == SSA_NAME)
> + {
> + gimple *stmt = SSA_NAME_DEF_STMT (dest);
> + if (!is_gimple_assign (stmt))
> + return NULL_TREE;
> +
> + dest = gimple_assign_rhs1 (stmt);
> + }
This seems wrong as-written -- you have no idea what the RHS code is.
You're just blindly taking the first operand, then digging into its
type. It probably works in practice, but it would seem better to verify
that gimple_assign_rhs_code is a conversion first (CONVERT_EXPR_CODE_P).
If it's not a CONVERT_EXPR_CODE_P, return NULL_TREE.
[ Assuming the point here here is to look back through a conversion from
an array type to a pointer. ]
Jeff
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 3/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-10-02 22:15 ` Jeff Law
@ 2017-10-21 0:26 ` Martin Sebor
2017-11-04 3:49 ` Jeff Law
0 siblings, 1 reply; 39+ messages in thread
From: Martin Sebor @ 2017-10-21 0:26 UTC (permalink / raw)
To: Jeff Law, Gcc Patch List
On 10/02/2017 04:15 PM, Jeff Law wrote:
> On 08/10/2017 01:29 PM, Martin Sebor wrote:
>>>> diff --git a/gcc/builtins.c b/gcc/builtins.c
>>>> index 016f68d..1aa9e22 100644
>>>> --- a/gcc/builtins.c
>>>> +++ b/gcc/builtins.c
>>> [ ... ]
>>>> +
>>>> + if (TREE_CODE (type) == ARRAY_TYPE)
>>>> + {
>>>> + /* Return the constant size unless it's zero (that's a
>>>> zero-length
>>>> + array likely at the end of a struct). */
>>>> + tree size = TYPE_SIZE_UNIT (type);
>>>> + if (size && TREE_CODE (size) == INTEGER_CST
>>>> + && !integer_zerop (size))
>>>> + return size;
>>>> + }
>>> Q. Do we have a canonical test for the trailing array idiom? In some
>>> contexts isn't it size 1? ISTM This test needs slight improvement.
>>> Ideally we'd use some canonical test for detect the trailing array idiom
>>> rather than open-coding it here. You might look at the array index
>>> warnings in tree-vrp.c to see if it's got a canonical test you can call
>>> or factor and use.
>>
>> You're right, there is an API for this (array_at_struct_end_p,
>> as Richard pointed out). I didn't want to use it because it
>> treats any array at the end of a struct as a flexible array
>> member, but simple tests show that that's what -Wstringop-
>> overflow does now, and it wasn't my intention to tighten up
>> the checking under this change. It surprises me that no tests
>> exposed this. Let me relax the check and think about proposing
>> to tighten it up separately.
>>
>>
>>
>>> What might be even better would be to use the immediate uses of the
>>> memory tag. For your case there should be only one immediate use and it
>>> should point to the statement which NUL terminates the destination. Or
>>> maybe that would be worse in that you only want to allow this exception
>>> when the statements are consecutive.
You said "maybe that would be worse" so I hadn't implemented it.
I went ahead and coded it up but with more testing I don't think
it has the desired result. See below.
>>
>> I'll have to try this to better understand how it might work.
> It's actually quite simple.
>
> Rather than looking at the next statement in the chain via
> gsi_next_nondebug you follow the def->use chain for the memory tag
> associated with the string copy statement.
>
> /* Get the memory tag that is defined by this statement. */
> defvar = gimple_vdef (gsi_stmt (gsi));
>
> imm_use_iterator iter;
> gimple *use_stmt;
>
> if (num_imm_uses (defvar) == 1)
> {
> imm_use_terator iter;
> gimple *use_stmt;
>
> /* Iterate over the immediate uses of the memory tag. */
> FOR_EACH_IMM_USE_STMT (use_stmt, ui, defvar)
> {
> Check if STMT is dst[i] = '\0'
> }
> }
>
>
>
> The check that there is a single immediate use is designed to make sure
> you get a warning for this scenario:
Thanks for the outline of the solution. I managed to get it to
work with only a few minor changes(*) but...
> strxncpy
> read the destination
> terminate the destination
>
> Which I think you'd want to consider non-terminated because of the read
> of the destination prior to termination.
>
> But avoids warnings for
>
> strxncpy
> stuff that doesn't read the destination
> terminate the destintion
...while it works fine for the basic cases it has the downside
of missing more subtle problems like this one:
char a[8];
void f (void) { puts (a); }
void g (const char *s)
{
strncpy (a, s);
f (); // assumes a is a string
a[sizeof a - 1] = '\0';
}
or this one:
struct S { char a[8]; };
void f (const struct S *p) { puts (p->a); }
void g (struct S *p, const char *s)
{
strncpy (p->a, s);
f (p); // assumes p->a is a string
a[sizeof p->a - 1] = '\0';
}
I would rather have the test be a little more strict than possibly
miss these kinds of insidious bugs for what seems like a unlike use
case (other code between the strncpy and the *dst = '\0'). I think
sticking with the original also encourages cleaner code: keeping
the nul-termination as close to the strncpy call.
>>> You still need to rename strlen_optimize_stmt since after your changes
>>> it does both optimizations and warnings.
>>
>> I'm not sure I understand why. It's a pre-existing function that
>> just dispatches to the built-in handlers. We don't rename function
>> callers each time we improve error/warning detection in some
>> function they call (case in point: all the expanders in builtins.c)
>> Why do it here? And what would be a suitable name? All that comes
>> to my mind is awkward variations on strlen_optimize_stmt_and_warn.
> Actually we often end up renaming functions as their capabilities
> change. If I was to read that name, I'd think its only purpose was to
> optimize. Given its static with a single use we should just fix it.
>
>
> Something in compute_objsize I just noticed.
>
> When DEST is an SSA_NAME, you follow the use->def chain back to its
> defining statement, then get a new dest from the RHS of that statement:
>
>> + if (TREE_CODE (dest) == SSA_NAME)
>> + {
>> + gimple *stmt = SSA_NAME_DEF_STMT (dest);
>> + if (!is_gimple_assign (stmt))
>> + return NULL_TREE;
>> +
>> + dest = gimple_assign_rhs1 (stmt);
>> + }
>
> This seems wrong as-written -- you have no idea what the RHS code is.
> You're just blindly taking the first operand, then digging into its
> type. It probably works in practice, but it would seem better to verify
> that gimple_assign_rhs_code is a conversion first (CONVERT_EXPR_CODE_P).
> If it's not a CONVERT_EXPR_CODE_P, return NULL_TREE.
The code doesn't match CONVERT_EXPR_P(). It's expected to match
ADDR_EXPR and that's also what it tests for on the line just below
the hunk you pasted above. Here it is:
if (TREE_CODE (dest) == SSA_NAME)
{
gimple *stmt = SSA_NAME_DEF_STMT (dest);
if (!is_gimple_assign (stmt))
return NULL_TREE;
dest = gimple_assign_rhs1 (stmt);
}
if (TREE_CODE (dest) != ADDR_EXPR)
return NULL_TREE;
So unless I'm missing something this does what you're looking
for.
>
> [ Assuming the point here here is to look back through a conversion from
> an array type to a pointer. ]
Yes. It determines the size of the member array in cases like
this one:
struct S { char a[5]; void (*pf)(void); };
void sink (void*);
void g (struct S *p, int n)
{
if (n < 7) n = 7;
__builtin_strncpy (p->a, "123456", n);
sink (p->a);
}
Assuming this response resolves all your concerns, is the last
version of the patch okay to commit?
Martin
[*] For reference this is the code I used:
if (tree defvar = gimple_vdef (gsi_stmt (gsi)))
{
if (num_imm_uses (defvar) >= 1)
{
imm_use_iterator iter;
gimple *use_stmt;
/* Iterate over the immediate uses of the memory tag. */
FOR_EACH_IMM_USE_STMT (use_stmt, iter, defvar)
{
if (is_gimple_assign (use_stmt))
{
HOST_WIDE_INT off;
dstdecl = get_addr_base_and_unit_offset (dstdecl, &off);
tree lhs = gimple_assign_lhs (use_stmt);
if (tree lhsbase = get_addr_base_and_unit_offset (lhs, &off))
if (operand_equal_p (dstdecl, lhsbase, 0))
return false;
}
}
}
}
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 3/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-10-21 0:26 ` Martin Sebor
@ 2017-11-04 3:49 ` Jeff Law
2017-11-10 0:17 ` Martin Sebor
0 siblings, 1 reply; 39+ messages in thread
From: Jeff Law @ 2017-11-04 3:49 UTC (permalink / raw)
To: Martin Sebor, Gcc Patch List
On 10/20/2017 06:18 PM, Martin Sebor wrote:
>>>> What might be even better would be to use the immediate uses of the
>>>> memory tag. For your case there should be only one immediate use
>>>> and it
>>>> should point to the statement which NUL terminates the destination. Or
>>>> maybe that would be worse in that you only want to allow this exception
>>>> when the statements are consecutive.
>
> You said "maybe that would be worse" so I hadn't implemented it.
> I went ahead and coded it up but with more testing I don't think
> it has the desired result. See below.
I probably should have said that while it may be better at rooting out
false positives, the ability to find things that are not consecutive
could perhaps be seen as a negative in that it might fail to warn on
code that while not technically incorrect is likely dodgy.
>
>>>
>>> I'll have to try this to better understand how it might work.
>> It's actually quite simple.
>>
>> Rather than looking at the next statement in the chain via
>> gsi_next_nondebug you follow the def->use chain for the memory tag
>> associated with the string copy statement.
>>
>> /* Get the memory tag that is defined by this statement. */
>> defvar = gimple_vdef (gsi_stmt (gsi));
>>
>> imm_use_iterator iter;
>> gimple *use_stmt;
>>
>> if (num_imm_uses (defvar) == 1)
>> Â {
>> Â Â Â imm_use_terator iter;
>> Â Â Â gimple *use_stmt;
>>
>>    /* Iterate over the immediate uses of the memory tag. */
>> Â Â Â FOR_EACH_IMM_USE_STMT (use_stmt, ui, defvar)
>> Â Â Â Â Â {
>> Â Â Â Â Â Â Â Check if STMT is dst[i] = '\0'
>> Â Â Â Â Â }
>> Â }
>>
>>
>>
>> The check that there is a single immediate use is designed to make sure
>> you get a warning for this scenario:
>
> Thanks for the outline of the solution. I managed to get it to
> work with only a few minor changes(*) but...
>
>> strxncpy
>> read the destination
>> terminate the destination
>>
>> Which I think you'd want to consider non-terminated because of the read
>> of the destination prior to termination.
>>
>> But avoids warnings for
>>
>> strxncpy
>> stuff that doesn't read the destination
>> terminate the destintion
>
> ...while it works fine for the basic cases it has the downside
> of missing more subtle problems like this one:
>
> Â char a[8];
>
> Â void f (void) { puts (a); }
>
> Â void g (const char *s)
> Â {
> Â Â Â strncpy (a, s);
>
> Â Â Â f ();Â Â // assumes a is a string
>
> Â Â Â a[sizeof a - 1] = '\0';
> Â }
Don't you have a VDEF at the call to f()? Wouldn't that be the only
immediate use? If you hit a VDEF without finding the termination, then
you warn.
Or is it the case that the call gets inlined and we know enough about
puts that we get a VUSE rather than a VDEF?
In which case we have a VUSE at the puts and a VDEF at the array
assignment in the immediate use list.
I think this highlights that I over-simplified my pseudo code. If you
find a VUSE in the list, then you have to warn. IF you find a VDEF that
does not terminate, then you have to warn.
It's probably academic at this point -- it's probably more useful to
understand the immediate uses and the vuse/vdef work for the future. I
can live with a statement walking implementation.
>
> or this one:
>
> Â struct S { char a[8]; };
>
> Â void f (const struct S *p) { puts (p->a); }
>
> Â void g (struct S *p, const char *s)
> Â {
> Â Â Â strncpy (p->a, s);
>
> Â Â Â f (p);Â Â // assumes p->a is a string
>
> Â Â Â a[sizeof p->a - 1] = '\0';
> Â }
This one looks similar.
>>
>>> +Â if (TREE_CODE (dest) == SSA_NAME)
>>> +Â Â Â {
>>> +Â Â Â Â Â gimple *stmt = SSA_NAME_DEF_STMT (dest);
>>> +Â Â Â Â Â if (!is_gimple_assign (stmt))
>>> +Â Â Â return NULL_TREE;
>>> +
>>> +Â Â Â Â Â dest = gimple_assign_rhs1 (stmt);
>>> +Â Â Â }
>>
>> This seems wrong as-written -- you have no idea what the RHS code is.
>> You're just blindly taking the first operand, then digging into its
>> type. It probably works in practice, but it would seem better to verify
>> that gimple_assign_rhs_code is a conversion first (CONVERT_EXPR_CODE_P).
>> Â If it's not a CONVERT_EXPR_CODE_P, return NULL_TREE.
>
> The code doesn't match CONVERT_EXPR_P(). It's expected to match
> ADDR_EXPR and that's also what it tests for on the line just below
> the hunk you pasted above. Here it is:
[ ... [
>
> Â if (TREE_CODE (dest) == SSA_NAME)
> Â Â Â {
> Â Â Â Â Â gimple *stmt = SSA_NAME_DEF_STMT (dest);
> Â Â Â Â Â if (!is_gimple_assign (stmt))
> Â Â Â Â return NULL_TREE;
>
> Â Â Â Â Â dest = gimple_assign_rhs1 (stmt);
> Â Â Â }
>
> Â if (TREE_CODE (dest) != ADDR_EXPR)
> Â Â Â return NULL_TREE;
>
> So unless I'm missing something this does what you're looking
> for.But you still making assumptions that you don't know are valid.
You walk to the defining statement of the given SSA_NAME. You verify it
is an assignment. You're OK up to this point.
You then proceed to look at rhs1. Instead you should look at
gimple_assign_rhs_code and verify that is an ADDR_EXPR.
For your particular example I don't think it matters, but I suspect it'd
matter for something if the defining statement was something like a
POINTER_PLUS_EXPR where the first operand might be an ADDR_EXPR.
Jeff
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 3/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-11-04 3:49 ` Jeff Law
@ 2017-11-10 0:17 ` Martin Sebor
2017-11-10 0:31 ` Jeff Law
0 siblings, 1 reply; 39+ messages in thread
From: Martin Sebor @ 2017-11-10 0:17 UTC (permalink / raw)
To: Jeff Law, Gcc Patch List
[-- Attachment #1: Type: text/plain, Size: 6700 bytes --]
On 11/03/2017 09:49 PM, Jeff Law wrote:
> On 10/20/2017 06:18 PM, Martin Sebor wrote:
>
>>>>> What might be even better would be to use the immediate uses of the
>>>>> memory tag. For your case there should be only one immediate use
>>>>> and it
>>>>> should point to the statement which NUL terminates the
>>>>> destination. Or
>>>>> maybe that would be worse in that you only want to allow this
>>>>> exception
>>>>> when the statements are consecutive.
>>
>> You said "maybe that would be worse" so I hadn't implemented it.
>> I went ahead and coded it up but with more testing I don't think
>> it has the desired result. See below.
> I probably should have said that while it may be better at rooting out
> false positives, the ability to find things that are not consecutive
> could perhaps be seen as a negative in that it might fail to warn on
> code that while not technically incorrect is likely dodgy.
That's my feeling as well.
>>>> I'll have to try this to better understand how it might work.
>>> It's actually quite simple.
>>>
>>> Rather than looking at the next statement in the chain via
>>> gsi_next_nondebug you follow the def->use chain for the memory tag
>>> associated with the string copy statement.
>>>
>>> /* Get the memory tag that is defined by this statement. */
>>> defvar = gimple_vdef (gsi_stmt (gsi));
>>>
>>> imm_use_iterator iter;
>>> gimple *use_stmt;
>>>
>>> if (num_imm_uses (defvar) == 1)
>>> {
>>> imm_use_terator iter;
>>> gimple *use_stmt;
>>>
>>> /* Iterate over the immediate uses of the memory tag. */
>>> FOR_EACH_IMM_USE_STMT (use_stmt, ui, defvar)
>>> {
>>> Check if STMT is dst[i] = '\0'
>>> }
>>> }
>>>
>>>
>>>
>>> The check that there is a single immediate use is designed to make sure
>>> you get a warning for this scenario:
>>
>> Thanks for the outline of the solution. I managed to get it to
>> work with only a few minor changes(*) but...
>>
>>> strxncpy
>>> read the destination
>>> terminate the destination
>>>
>>> Which I think you'd want to consider non-terminated because of the read
>>> of the destination prior to termination.
>>>
>>> But avoids warnings for
>>>
>>> strxncpy
>>> stuff that doesn't read the destination
>>> terminate the destintion
>>
>> ...while it works fine for the basic cases it has the downside
>> of missing more subtle problems like this one:
>>
>> char a[8];
>>
>> void f (void) { puts (a); }
>>
>> void g (const char *s)
>> {
>> strncpy (a, s);
>>
>> f (); // assumes a is a string
>>
>> a[sizeof a - 1] = '\0';
>> }
> Don't you have a VDEF at the call to f()? Wouldn't that be the only
> immediate use? If you hit a VDEF without finding the termination, then
> you warn.
>
> Or is it the case that the call gets inlined and we know enough about
> puts that we get a VUSE rather than a VDEF?
There is a VDEF in both cases. The point of the puts was just
to illustrate that f() could read the array even though it's
not obvious from the call.
> In which case we have a VUSE at the puts and a VDEF at the array
> assignment in the immediate use list.
>
> I think this highlights that I over-simplified my pseudo code. If you
> find a VUSE in the list, then you have to warn. IF you find a VDEF that
> does not terminate, then you have to warn.
I don't think it's that simple. Suppose we have:
strncpy (a, s, sizeof a);
unsigned n = strlen (s);
a[sizeof a - 1] = 0;
return n;
The next statement after the strncpy is a VUSE but warning would
be a false positive because the next statement appends the NUL
to a. So I'd have to iterate over all subsequent statements and
inspect each to see if it happens to either use or set a. I'm
sure it's possible to get it to work but I don't think it's worth
the effort or the complexity or the compile time costs. I haven't
come across any code that does this yet.
> It's probably academic at this point -- it's probably more useful to
> understand the immediate uses and the vuse/vdef work for the future. I
> can live with a statement walking implementation.
Since we both feel that the practice to encourage is to have
the nul termination follow immediately the function call I agree
that this is academic. I do appreciate the suggestion though.
>>>> + if (TREE_CODE (dest) == SSA_NAME)
>>>> + {
>>>> + gimple *stmt = SSA_NAME_DEF_STMT (dest);
>>>> + if (!is_gimple_assign (stmt))
>>>> + return NULL_TREE;
>>>> +
>>>> + dest = gimple_assign_rhs1 (stmt);
>>>> + }
>>>
>>> This seems wrong as-written -- you have no idea what the RHS code is.
>>> You're just blindly taking the first operand, then digging into its
>>> type. It probably works in practice, but it would seem better to verify
>>> that gimple_assign_rhs_code is a conversion first (CONVERT_EXPR_CODE_P).
>>> If it's not a CONVERT_EXPR_CODE_P, return NULL_TREE.
>>
>> The code doesn't match CONVERT_EXPR_P(). It's expected to match
>> ADDR_EXPR and that's also what it tests for on the line just below
>> the hunk you pasted above. Here it is:
> [ ... [
>>
>> if (TREE_CODE (dest) == SSA_NAME)
>> {
>> gimple *stmt = SSA_NAME_DEF_STMT (dest);
>> if (!is_gimple_assign (stmt))
>> return NULL_TREE;
>>
>> dest = gimple_assign_rhs1 (stmt);
>> }
>>
>> if (TREE_CODE (dest) != ADDR_EXPR)
>> return NULL_TREE;
>>
>> So unless I'm missing something this does what you're looking
>> for.But you still making assumptions that you don't know are valid.
>
> You walk to the defining statement of the given SSA_NAME. You verify it
> is an assignment. You're OK up to this point.
>
> You then proceed to look at rhs1. Instead you should look at
> gimple_assign_rhs_code and verify that is an ADDR_EXPR.
I see what you mean. The code also (optimistically) handles things
like
char a[10];
extern unsigned offset;
strcpy (a + offset, "123456");
i.e., POINTER_PLUS_EXPR with a non-constant offset. I've added
a check for these two codes and excluded everything else (I'm not
sure what else could reasonably come up here so I have no tests
for this).
> For your particular example I don't think it matters, but I suspect it'd
> matter for something if the defining statement was something like a
> POINTER_PLUS_EXPR where the first operand might be an ADDR_EXPR.
Attached is another update to the patch with the above. While
going through it again I noticed a couple of opportunities to
handle ranges so I added those as well. I have retested the
patch with GDB and Glibc. They both require minor changes and
I will submit patches with those once this work is checked in.
Is this good enough to commit now?
Martin
[-- Attachment #2: gcc-81117.diff --]
[-- Type: text/x-patch, Size: 81741 bytes --]
PR c/81117 - Improve buffer overflow checking in strncpy
ada/ChangeLog:
PR c/81117
* adadecode.c (__gnat_decode): Use memcpy instead of strncpy.
* argv.c (__gnat_fill_env): Same.
fortran/ChangeLog:
PR c/81117
* decl.c (build_sym): Use memcpy instead of strncpy.
gcc/ChangeLog:
PR c/81117
* builtins.c (compute_objsize): Handle arrays that
compute_builtin_object_size likes to fail for. Make extern.
* builtins.h (compute_objsize): Declare.
(check_strncpy_sizes): New function.
(expand_builtin_strncpy): Call check_strncpy_sizes.
* gimple-fold.c (gimple_fold_builtin_strncpy): Implement
-Wstringop-truncation.
(gimple_fold_builtin_strncat): Same.
* gimple.c (gimple_build_call_from_tree): Set call location.
* tree-ssa-strlen.c (strlen_to_stridx): New global variable.
(maybe_diag_bound_equal_length, is_strlen_related_p): New functions.
(handle_builtin_stxncpy, handle_builtin_strncat): Same.
(handle_builtin_strlen): Use strlen_to_stridx.
(strlen_optimize_stmt): Handle flavors of strncat, strncpy, and
stpncpy.
Use strlen_to_stridx.
(pass_strlen::execute): Release strlen_to_stridx.
* doc/invoke.texi (-Wsizeof-pointer-memaccess): Document enhancement.
(-Wstringop-truncation): Document new option.
gcc/c-family/ChangeLog:
PR c/81117
* c-common.c (catenate_strings): Use memcpy instead of strncpy.
* c-warn.c (sizeof_pointer_memaccess_warning): Handle arrays.
* c.opt (-Wstringop-truncation): New option.
gcc/objc/ChangeLog:
PR c/81117
* objc-encoding.c (encode_type): Use memcpy instead of strncpy.
gcc/testsuite/ChangeLog:
PR c/81117
* c-c++-common/Wsizeof-pointer-memaccess3.c: New test.
* c-c++-common/Wstringop-overflow.c: Same.
* c-c++-common/Wstringop-truncation.c: Same.
* c-c++-common/Wsizeof-pointer-memaccess2.c: Adjust.
* c-c++-common/attr-nonstring-2.c: New test.
* g++.dg/torture/Wsizeof-pointer-memaccess1.C: Adjust.
* g++.dg/torture/Wsizeof-pointer-memaccess2.C: Same.
* gcc.dg/torture/pr63554.c: Same.
* gcc.dg/Walloca-1.c: Disable macro tracking.
diff --git a/gcc/ada/adadecode.c b/gcc/ada/adadecode.c
index 8c9c7ab..0cbef81 100644
--- a/gcc/ada/adadecode.c
+++ b/gcc/ada/adadecode.c
@@ -330,7 +330,7 @@ __gnat_decode (const char *coded_name, char *ada_name, int verbose)
}
/* Write symbol in the space. */
- strncpy (optoken, trans_table[k][1], oplen);
+ memcpy (optoken, trans_table[k][1], oplen);
}
else
k++;
diff --git a/gcc/ada/argv.c b/gcc/ada/argv.c
index 430404e..aee0f88 100644
--- a/gcc/ada/argv.c
+++ b/gcc/ada/argv.c
@@ -92,7 +92,7 @@ void
__gnat_fill_arg (char *a, int i)
{
if (gnat_argv != NULL)
- strncpy (a, gnat_argv[i], strlen(gnat_argv[i]));
+ memcpy (a, gnat_argv[i], strlen (gnat_argv[i]));
}
int
@@ -118,7 +118,7 @@ void
__gnat_fill_env (char *a, int i)
{
if (gnat_envp != NULL)
- strncpy (a, gnat_envp[i], strlen (gnat_envp[i]));
+ memcpy (a, gnat_envp[i], strlen (gnat_envp[i]));
}
#ifdef __cplusplus
diff --git a/gcc/builtins.c b/gcc/builtins.c
index a677705..ce53073 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -3260,18 +3260,60 @@ check_sizes (int opt, tree exp, tree size, tree maxlen, tree src, tree objsize)
}
/* Helper to compute the size of the object referenced by the DEST
- expression which must of of pointer type, using Object Size type
+ expression which must have pointer type, using Object Size type
OSTYPE (only the least significant 2 bits are used). Return
the size of the object if successful or NULL when the size cannot
be determined. */
-static inline tree
+tree
compute_objsize (tree dest, int ostype)
{
unsigned HOST_WIDE_INT size;
- if (compute_builtin_object_size (dest, ostype & 3, &size))
+
+ /* Only the two least significant bits are meaningful. */
+ ostype &= 3;
+
+ if (compute_builtin_object_size (dest, ostype, &size))
return build_int_cst (sizetype, size);
+ /* Unless computing the largest size (for memcpy and other raw memory
+ functions), try to determine the size of the object from its type. */
+ if (!ostype)
+ return NULL_TREE;
+
+ if (TREE_CODE (dest) == SSA_NAME)
+ {
+ gimple *stmt = SSA_NAME_DEF_STMT (dest);
+ if (!is_gimple_assign (stmt))
+ return NULL_TREE;
+
+ tree_code code = gimple_assign_rhs_code (stmt);
+ if (code != ADDR_EXPR && code != POINTER_PLUS_EXPR)
+ return NULL_TREE;
+
+ dest = gimple_assign_rhs1 (stmt);
+ }
+
+ if (TREE_CODE (dest) != ADDR_EXPR)
+ return NULL_TREE;
+
+ tree type = TREE_TYPE (dest);
+ if (TREE_CODE (type) == POINTER_TYPE)
+ type = TREE_TYPE (type);
+
+ type = TYPE_MAIN_VARIANT (type);
+
+ if (TREE_CODE (type) == ARRAY_TYPE
+ && !array_at_struct_end_p (dest))
+ {
+ /* Return the constant size unless it's zero (that's a zero-length
+ array likely at the end of a struct). */
+ tree size = TYPE_SIZE_UNIT (type);
+ if (size && TREE_CODE (size) == INTEGER_CST
+ && !integer_zerop (size))
+ return size;
+ }
+
return NULL_TREE;
}
@@ -3923,6 +3965,22 @@ expand_builtin_strncat (tree exp, rtx)
return NULL_RTX;
}
+/* Helper to check the sizes of sequences and the destination of calls
+ to __builtin_strncpy (DST, SRC, CNT) and __builtin___strncpy_chk.
+ Returns true on success (no overflow warning), false otherwise. */
+
+static bool
+check_strncpy_sizes (tree exp, tree dst, tree src, tree cnt)
+{
+ tree dstsize = compute_objsize (dst, warn_stringop_overflow - 1);
+
+ if (!check_sizes (OPT_Wstringop_overflow_,
+ exp, cnt, /*maxlen=*/NULL_TREE, src, dstsize))
+ return false;
+
+ return true;
+}
+
/* Expand expression EXP, which is a call to the strncpy builtin. Return
NULL_RTX if we failed the caller should emit a normal call. */
@@ -3941,16 +3999,7 @@ expand_builtin_strncpy (tree exp, rtx target)
/* The length of the source sequence. */
tree slen = c_strlen (src, 1);
- if (warn_stringop_overflow)
- {
- tree destsize = compute_objsize (dest,
- warn_stringop_overflow - 1);
-
- /* The number of bytes to write is LEN but check_sizes will also
- check SLEN if LEN's value isn't known. */
- check_sizes (OPT_Wstringop_overflow_,
- exp, len, /*maxlen=*/NULL_TREE, src, destsize);
- }
+ check_strncpy_sizes (exp, dest, src, len);
/* We must be passed a constant len and src parameter. */
if (!tree_fits_uhwi_p (len) || !slen || !tree_fits_uhwi_p (slen))
diff --git a/gcc/builtins.h b/gcc/builtins.h
index 4ae7056..cf3fc17 100644
--- a/gcc/builtins.h
+++ b/gcc/builtins.h
@@ -89,6 +89,7 @@ extern tree fold_call_stmt (gcall *, bool);
extern void set_builtin_user_assembler_name (tree decl, const char *asmspec);
extern bool is_simple_builtin (tree);
extern bool is_inexpensive_builtin (tree);
+extern tree compute_objsize (tree, int);
extern bool readonly_data_expr (tree exp);
extern bool init_target_chars (void);
diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index 24077c7..a76fae7 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -5890,10 +5890,10 @@ check_builtin_function_arguments (location_t loc, vec<location_t> arg_loc,
static char *
catenate_strings (const char *lhs, const char *rhs_start, int rhs_size)
{
- const int lhs_size = strlen (lhs);
+ const size_t lhs_size = strlen (lhs);
char *result = XNEWVEC (char, lhs_size + rhs_size);
- strncpy (result, lhs, lhs_size);
- strncpy (result + lhs_size, rhs_start, rhs_size);
+ memcpy (result, lhs, lhs_size);
+ memcpy (result + lhs_size, rhs_start, rhs_size);
return result;
}
diff --git a/gcc/c-family/c-warn.c b/gcc/c-family/c-warn.c
index 09ef685..6cfded9 100644
--- a/gcc/c-family/c-warn.c
+++ b/gcc/c-family/c-warn.c
@@ -693,7 +693,8 @@ sizeof_pointer_memaccess_warning (location_t *sizeof_arg_loc, tree callee,
|| vec_safe_length (params) <= 1)
return;
- switch (DECL_FUNCTION_CODE (callee))
+ enum built_in_function fncode = DECL_FUNCTION_CODE (callee);
+ switch (fncode)
{
case BUILT_IN_STRNCMP:
case BUILT_IN_STRNCASECMP:
@@ -775,8 +776,27 @@ sizeof_pointer_memaccess_warning (location_t *sizeof_arg_loc, tree callee,
type = TYPE_P (sizeof_arg[idx])
? sizeof_arg[idx] : TREE_TYPE (sizeof_arg[idx]);
+
if (!POINTER_TYPE_P (type))
- return;
+ {
+ /* The argument type may be an array. Diagnose bounded string
+ copy functions that specify the bound in terms of the source
+ argument rather than the destination. */
+ if (strop && !cmp && fncode != BUILT_IN_STRNDUP && src)
+ {
+ tem = tree_strip_nop_conversions (src);
+ if (TREE_CODE (tem) == ADDR_EXPR)
+ tem = TREE_OPERAND (tem, 0);
+ if (operand_equal_p (tem, sizeof_arg[idx], OEP_ADDRESS_OF))
+ warning_at (sizeof_arg_loc[idx], OPT_Wsizeof_pointer_memaccess,
+ "argument to %<sizeof%> in %qD call is the same "
+ "expression as the source; did you mean to use "
+ "the size of the destination?",
+ callee);
+ }
+
+ return;
+ }
if (dest
&& (tem = tree_strip_nop_conversions (dest))
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 9ab31f0..479ae63 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -744,6 +744,10 @@ C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_stringop_overflow) Ini
Under the control of Object Size type, warn about buffer overflow in string
manipulation functions like memcpy and strcpy.
+Wstringop-truncation
+C ObjC C++ ObjC++ Var(warn_stringop_truncation) Warning Init (1) LangEnabledBy(C ObjC C++ ObjC++, Wall)
+Warn about truncation in string manipulation functions like strncat and strncpy.
+
Wsuggest-attribute=format
C ObjC C++ ObjC++ Var(warn_suggest_attribute_format) Warning
Warn about functions which might be candidates for format attributes.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index e897d93..4427328 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -314,7 +314,7 @@ Objective-C and Objective-C++ Dialects}.
-Wsizeof-pointer-memaccess -Wsizeof-array-argument @gol
-Wstack-protector -Wstack-usage=@var{len} -Wstrict-aliasing @gol
-Wstrict-aliasing=n -Wstrict-overflow -Wstrict-overflow=@var{n} @gol
--Wstringop-overflow=@var{n} @gol
+-Wstringop-overflow=@var{n} -Wstringop-truncation @gol
-Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{|}format@r{|}malloc@r{]} @gol
-Wsuggest-final-types @gol -Wsuggest-final-methods -Wsuggest-override @gol
-Wmissing-format-attribute -Wsubobject-linkage @gol
@@ -5214,6 +5214,55 @@ whether to issue a warning. Similarly to @option{-Wstringop-overflow=3} this
setting of the option may result in warnings for benign code.
@end table
+@item -Wstringop-truncation
+@opindex Wstringop-truncation
+@opindex Wno-stringop-truncation
+Warn for calls to bounded string manipulation functions such as @code{strncat},
+@code{strncpy}, and @code{stpncpy} that may either truncate the copied string
+or leave the destination unchanged.
+
+In the following example, the call to @code{strncat} specifies a bound that
+is less than the length of the source string. As a result, the copy of
+the source will be truncated and so the call is diagnosed. To avoid the
+warning use @code{bufsize - strlen (buf) - 1)} as the bound.
+
+@smallexample
+void append (char *buf, size_t bufsize)
+@{
+ strncat (buf, ".txt", 3);
+@}
+@end smallexample
+
+As another example, the following call to @code{strncpy} results in copying
+to @code{d} just the characters preceding the terminating NUL, without
+appending the NUL to the end. Assuming the result of @code{strncpy} is
+necessarily a NUL-terminated string is a common mistake, and so the call
+is diagnosed. To avoid the warning when the result is not expected to be
+NUL-terminated, call @code{memcpy} instead.
+
+@smallexample
+void copy (char *d, const char *s)
+@{
+ strncpy (d, s, strlen (s));
+@}
+@end smallexample
+
+In the following example, the call to @code{strncpy} specifies the size
+of the destination buffer as the bound. If the length of the source
+string is equal to or greater than this size the result of the copy will
+not be NUL-terminated. Therefore, the call is also diagnosed. To avoid
+the warning, specify @code{sizeof buf - 1} as the bound and set the last
+element of the buffer to @code{NUL}.
+
+@smallexample
+void copy (const char *s)
+@{
+ char buf[80];
+ strncpy (buf, s, sizeof buf);
+ @dots{}
+@}
+@end smallexample
+
@item -Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{|}format@r{|}cold@r{|}malloc@r{]}
@opindex Wsuggest-attribute=
@opindex Wno-suggest-attribute=
@@ -6230,11 +6279,26 @@ not an array, but a pointer. This warning is enabled by @option{-Wall}.
@opindex Wsizeof-pointer-memaccess
@opindex Wno-sizeof-pointer-memaccess
Warn for suspicious length parameters to certain string and memory built-in
-functions if the argument uses @code{sizeof}. This warning warns e.g.@:
-about @code{memset (ptr, 0, sizeof (ptr));} if @code{ptr} is not an array,
-but a pointer, and suggests a possible fix, or about
-@code{memcpy (&foo, ptr, sizeof (&foo));}. This warning is enabled by
-@option{-Wall}.
+functions if the argument uses @code{sizeof}. This warning triggers for
+example for @code{memset (ptr, 0, sizeof (ptr));} if @code{ptr} is not
+an array, but a pointer, and suggests a possible fix, or about
+@code{memcpy (&foo, ptr, sizeof (&foo));}. @option{-Wsizeof-pointer-memaccess}
+also warns about calls to bounded string copy functions like @code{strncat}
+or @code{strncpy} that specify as the bound a @code{sizeof} expression of
+the source array. For example, in the following function the call to
+@code{strncat} specifies the size of the source string as the bound. That
+is almost certainly a mistake and so the call is diagnosed.
+@smallexample
+void make_file (const char *name)
+@{
+ char path[PATH_MAX];
+ strncpy (path, name, sizeof path - 1);
+ strncat (path, ".text", sizeof ".text");
+ @dots{}
+@}
+@end smallexample
+
+The @option{-Wsizeof-pointer-memaccess} option is enabled by @option{-Wall}.
@item -Wsizeof-array-argument
@opindex Wsizeof-array-argument
diff --git a/gcc/fortran/decl.c b/gcc/fortran/decl.c
index 1a2d8f0..11264e7 100644
--- a/gcc/fortran/decl.c
+++ b/gcc/fortran/decl.c
@@ -1427,11 +1427,9 @@ build_sym (const char *name, gfc_charlen *cl, bool cl_deferred,
{
char u_name[GFC_MAX_SYMBOL_LEN + 1];
gfc_symtree *st;
- int nlen;
- nlen = strlen(name);
- gcc_assert (nlen <= GFC_MAX_SYMBOL_LEN);
- strncpy (u_name, name, nlen + 1);
+ gcc_assert (strlen(name) <= GFC_MAX_SYMBOL_LEN);
+ strcpy (u_name, name);
u_name[0] = upper;
st = gfc_find_symtree (gfc_current_ns->sym_root, u_name);
diff --git a/gcc/gimple-fold.c b/gcc/gimple-fold.c
index 85fd397..c65b418 100644
--- a/gcc/gimple-fold.c
+++ b/gcc/gimple-fold.c
@@ -40,6 +40,7 @@ along with GCC; see the file COPYING3. If not see
#include "gimple-iterator.h"
#include "tree-into-ssa.h"
#include "tree-dfa.h"
+#include "tree-object-size.h"
#include "tree-ssa.h"
#include "tree-ssa-propagate.h"
#include "ipa-utils.h"
@@ -59,6 +60,8 @@ along with GCC; see the file COPYING3. If not see
#include "stringpool.h"
#include "attribs.h"
#include "asan.h"
+#include "diagnostic-core.h"
+#include "intl.h"
/* Return true when DECL can be referenced from current unit.
FROM_DECL (if non-null) specify constructor of variable DECL was taken from.
@@ -1553,12 +1556,28 @@ static bool
gimple_fold_builtin_strncpy (gimple_stmt_iterator *gsi,
tree dest, tree src, tree len)
{
- location_t loc = gimple_location (gsi_stmt (*gsi));
- tree fn;
+ gimple *stmt = gsi_stmt (*gsi);
+ location_t loc = gimple_location (stmt);
/* If the LEN parameter is zero, return DEST. */
if (integer_zerop (len))
{
+ tree fndecl = gimple_call_fndecl (stmt);
+ gcall *call = as_a <gcall *> (stmt);
+
+ /* Warn about the lack of nul termination: the result is not
+ a (nul-terminated) string. */
+ tree slen = get_maxval_strlen (src, 0);
+ if (slen && !integer_zerop (slen))
+ warning_at (loc, OPT_Wstringop_truncation,
+ "%G%qD destination unchanged after copying no bytes "
+ "from a string of length %E",
+ call, fndecl, slen);
+ else
+ warning_at (loc, OPT_Wstringop_truncation,
+ "%G%qD destination unchanged after copying no bytes",
+ call, fndecl);
+
replace_call_with_value (gsi, dest);
return true;
}
@@ -1573,16 +1592,66 @@ gimple_fold_builtin_strncpy (gimple_stmt_iterator *gsi,
if (!slen || TREE_CODE (slen) != INTEGER_CST)
return false;
- slen = size_binop_loc (loc, PLUS_EXPR, slen, ssize_int (1));
+ /* The size of the source string including the terminating nul. */
+ tree ssize = size_binop_loc (loc, PLUS_EXPR, slen, ssize_int (1));
/* We do not support simplification of this case, though we do
support it when expanding trees into RTL. */
/* FIXME: generate a call to __builtin_memset. */
- if (tree_int_cst_lt (slen, len))
+ if (tree_int_cst_lt (ssize, len))
return false;
+ if (tree_int_cst_lt (len, slen))
+ {
+ tree fndecl = gimple_call_fndecl (stmt);
+ gcall *call = as_a <gcall *> (stmt);
+
+ warning_at (loc, OPT_Wstringop_truncation,
+ (tree_int_cst_equal (size_one_node, len)
+ ? G_("%G%qD output truncated copying %E byte "
+ "from a string of length %E")
+ : G_("%G%qD output truncated copying %E bytes "
+ "from a string of length %E")),
+ call, fndecl, len, slen);
+ }
+ else if (tree_int_cst_equal (len, slen))
+ {
+ tree decl = dest;
+ if (TREE_CODE (decl) == SSA_NAME)
+ {
+ gimple *def_stmt = SSA_NAME_DEF_STMT (decl);
+ if (is_gimple_assign (def_stmt))
+ {
+ tree_code code = gimple_assign_rhs_code (def_stmt);
+ if (code == ADDR_EXPR || code == VAR_DECL)
+ decl = gimple_assign_rhs1 (def_stmt);
+ }
+ }
+
+ if (TREE_CODE (decl) == ADDR_EXPR)
+ decl = TREE_OPERAND (decl, 0);
+
+ if (TREE_CODE (decl) == COMPONENT_REF)
+ decl = TREE_OPERAND (decl, 1);
+
+ tree fndecl = gimple_call_fndecl (stmt);
+ gcall *call = as_a <gcall *> (stmt);
+
+ if (!DECL_P (decl)
+ || !lookup_attribute ("nonstring", DECL_ATTRIBUTES (decl)))
+ warning_at (loc, OPT_Wstringop_truncation,
+ (tree_int_cst_equal (size_one_node, len)
+ ? G_("%G%qD output truncated before terminating nul "
+ "copying %E byte from a string of the same "
+ "length")
+ : G_("%G%qD output truncated before terminating nul "
+ "copying %E bytes from a string of the same "
+ "length")),
+ call, fndecl, len);
+ }
+
/* OK transform into builtin memcpy. */
- fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
+ tree fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
if (!fn)
return false;
@@ -1591,6 +1660,7 @@ gimple_fold_builtin_strncpy (gimple_stmt_iterator *gsi,
NULL_TREE, true, GSI_SAME_STMT);
gimple *repl = gimple_build_call (fn, 3, dest, src, len);
replace_call_with_call_and_fold (gsi, repl);
+
return true;
}
@@ -1887,24 +1957,71 @@ gimple_fold_builtin_strncat (gimple_stmt_iterator *gsi)
return true;
}
- /* If the requested len is greater than or equal to the string
- length, call strcat. */
- if (TREE_CODE (len) == INTEGER_CST && p
- && compare_tree_int (len, strlen (p)) >= 0)
+ if (TREE_CODE (len) != INTEGER_CST || !p)
+ return false;
+
+ unsigned srclen = strlen (p);
+
+ int cmpsrc = compare_tree_int (len, srclen);
+
+ /* Return early if the requested len is less than the string length.
+ Warnings will be issued elsewhere later. */
+ if (cmpsrc < 0)
+ return false;
+
+ unsigned HOST_WIDE_INT dstsize;
+
+ bool nowarn = gimple_no_warning_p (stmt);
+
+ if (!nowarn && compute_builtin_object_size (dst, 1, &dstsize))
{
- tree fn = builtin_decl_implicit (BUILT_IN_STRCAT);
+ int cmpdst = compare_tree_int (len, dstsize);
- /* If the replacement _DECL isn't initialized, don't do the
- transformation. */
- if (!fn)
- return false;
+ if (cmpdst >= 0)
+ {
+ tree fndecl = gimple_call_fndecl (stmt);
+
+ /* Strncat copies (at most) LEN bytes and always appends
+ the terminating NUL so the specified bound should never
+ be equal to (or greater than) the size of the destination.
+ If it is, the copy could overflow. */
+ location_t loc = gimple_location (stmt);
+ nowarn = warning_at (loc, OPT_Wstringop_overflow_,
+ cmpdst == 0
+ ? G_("%G%qD specified bound %E equals "
+ "destination size")
+ : G_("%G%qD specified bound %E exceeds "
+ "destination size %wu"),
+ stmt, fndecl, len, dstsize);
+ if (nowarn)
+ gimple_set_no_warning (stmt, true);
+ }
+ }
- gcall *repl = gimple_build_call (fn, 2, dst, src);
- replace_call_with_call_and_fold (gsi, repl);
- return true;
+ if (!nowarn && cmpsrc == 0)
+ {
+ tree fndecl = gimple_call_fndecl (stmt);
+
+ /* To avoid certain truncation the specified bound should also
+ not be equal to (or less than) the length of the source. */
+ location_t loc = gimple_location (stmt);
+ if (warning_at (loc, OPT_Wstringop_overflow_,
+ "%G%qD specified bound %E equals source length",
+ stmt, fndecl, len))
+ gimple_set_no_warning (stmt, true);
}
- return false;
+ tree fn = builtin_decl_implicit (BUILT_IN_STRCAT);
+
+ /* If the replacement _DECL isn't initialized, don't do the
+ transformation. */
+ if (!fn)
+ return false;
+
+ /* Otherwise, emit a call to strcat. */
+ gcall *repl = gimple_build_call (fn, 2, dst, src);
+ replace_call_with_call_and_fold (gsi, repl);
+ return true;
}
/* Fold a call to the __strncat_chk builtin with arguments DEST, SRC,
diff --git a/gcc/gimple.c b/gcc/gimple.c
index 37f2248..c986a73 100644
--- a/gcc/gimple.c
+++ b/gcc/gimple.c
@@ -361,6 +361,7 @@ gimple_build_call_from_tree (tree t, tree fnptrtype)
gimple_call_set_arg (call, i, CALL_EXPR_ARG (t, i));
gimple_set_block (call, TREE_BLOCK (t));
+ gimple_set_location (call, EXPR_LOCATION (t));
/* Carry all the CALL_EXPR flags to the new GIMPLE_CALL. */
gimple_call_set_chain (call, CALL_EXPR_STATIC_CHAIN (t));
diff --git a/gcc/objc/objc-encoding.c b/gcc/objc/objc-encoding.c
index 9f46d57..f9d8d47 100644
--- a/gcc/objc/objc-encoding.c
+++ b/gcc/objc/objc-encoding.c
@@ -734,7 +734,7 @@ encode_type (tree type, int curtype, int format)
/* Rewrite "in const" from "nr" to "rn". */
if (curtype >= 1 && !strncmp (enc - 1, "nr", 2))
- strncpy (enc - 1, "rn", 2);
+ memcpy (enc - 1, "rn", 2);
}
}
}
diff --git a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c
index 895a50e..f7bfa35 100644
--- a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c
+++ b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c
@@ -1,7 +1,7 @@
/* Test -Wsizeof-pointer-memaccess warnings. */
/* { dg-do compile } */
-/* { dg-options "-Wall -O2 -Wno-sizeof-array-argument -ftrack-macro-expansion=0" } */
-/* { dg-options "-Wall -O2 -Wno-sizeof-array-argument -Wno-c++-compat -ftrack-macro-expansion=0" {target c} } */
+/* { dg-options "-Wall -O2 -Wno-sizeof-array-argument -Wno-stringop-truncation -ftrack-macro-expansion=0" } */
+/* { dg-options "-Wall -O2 -Wno-sizeof-array-argument -Wno-stringop-truncation -Wno-c++-compat -ftrack-macro-expansion=0" {target c} } */
/* { dg-require-effective-target alloca } */
#define bos(ptr) __builtin_object_size (ptr, 1)
@@ -473,12 +473,15 @@ f4 (char *x, char **y, int z, char w[64])
strncat (w, s2, sizeof (w)); /* { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" } */
stpncpy (w, s1, sizeof (w)); /* { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" } */
- /* These are correct, no warning. */
+ /* These are pointless when the destination is large enough, and
+ cause overflow otherwise. If the copies are guaranteed to be
+ safe the calls might as well be replaced by strcat(), strcpy(),
+ or memcpy(). */
const char s3[] = "foobarbaz";
const char s4[] = "abcde12345678";
- strncpy (x, s3, sizeof (s3));
- strncat (x, s4, sizeof (s4));
- stpncpy (x, s3, sizeof (s3));
+ strncpy (x, s3, sizeof (s3)); /* { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" } */
+ strncat (x, s4, sizeof (s4)); /* { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" } */
+ stpncpy (x, s3, sizeof (s3)); /* { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" } */
}
/* { dg-prune-output "\[\n\r\]*writing\[\n\r\]*" } */
diff --git a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess3.c b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess3.c
new file mode 100644
index 0000000..97598c4
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess3.c
@@ -0,0 +1,132 @@
+/* Test -Wsizeof-pointer-memaccess warnings. */
+/* { dg-do compile } */
+/* { dg-options "-Wsizeof-pointer-memaccess -Wno-stringop-overflow -Wno-stringop-truncation -ftrack-macro-expansion=0" } */
+
+#define bos(ptr) __builtin_object_size (ptr, 1)
+#define bos0(ptr) __builtin_object_size (ptr, 0)
+
+#define memset(dst, val, sz) \
+ (FUNC (memset, dst, val, sz, bos (dst)), sink ((dst)))
+
+#define memcpy(dst, src, sz) \
+ (FUNC (memcpy, dst, src, sz, bos (dst)), sink ((dst)))
+
+#define memmove(dst, src, sz) \
+ (FUNC (memmove, dst, src, sz, bos (dst)), sink ((dst)))
+
+#define mempcpy(dst, src, sz) \
+ (FUNC (mempcpy, dst, src, sz, bos (dst)), sink ((dst)))
+
+#define strncpy(dst, src, sz) \
+ (FUNC (strncpy, dst, src, sz, bos (dst)), sink (dst))
+
+#define strncat(dst, src, sz) \
+ (FUNC (strncat, dst, src, sz, bos (dst)), sink (dst))
+
+#define stpncpy(dst, src, sz) \
+ (FUNC (stpncpy, dst, src, sz, bos (dst)), sink (dst))
+
+void sink (void*);
+
+#define S10 "123456789"
+extern char a10[10];
+
+void test_string_literal (char *dst)
+{
+#define FUNC(f, d, s, n, x) __builtin_ ## f (d, s, n)
+
+ /* It's common to call memcpy and other raw memory functions with
+ size drerived from the source argument. Verify that no warning
+ is ussued for such calls. */
+ memcpy (dst, S10, sizeof S10);
+ mempcpy (dst, S10, sizeof S10);
+ memmove (dst, S10, sizeof S10);
+
+ memset (dst, 0, sizeof S10);
+
+ stpncpy (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncpy (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncat (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ /* Unlike in the cases above, even though the calls below are likely
+ wrong, it's not easy to detect that the expression (sizeof X - 1)
+ involves sizeof of the source, so no warning is issued here, as
+ helpful as one might be. Whether -Wstringop-truncation is issued
+ is tested elsewhere. */
+ stpncpy (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncpy (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncat (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+}
+
+
+void test_char_array (char *dst)
+{
+ memcpy (dst, a10, sizeof a10);
+ mempcpy (dst, a10, sizeof a10);
+ memmove (dst, a10, sizeof a10);
+
+ memset (dst, 0, sizeof a10);
+
+ stpncpy (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncpy (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncat (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ stpncpy (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncpy (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncat (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+}
+
+
+#undef FUNC
+#define FUNC(f, d, s, n, os) __builtin___ ## f ## _chk (d, s, n, os)
+
+void test_char_array_chk (char *dst)
+{
+ memcpy (dst, S10, sizeof S10);
+ mempcpy (dst, S10, sizeof S10);
+ memmove (dst, S10, sizeof S10);
+
+ memset (dst, 0, sizeof S10);
+
+ stpncpy (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncpy (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncat (dst, S10, sizeof S10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ stpncpy (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncpy (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncat (dst, S10, sizeof S10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+}
+
+
+void test_string_literal_chk (char *dst)
+{
+ memcpy (dst, a10, sizeof a10);
+ mempcpy (dst, a10, sizeof a10);
+ memmove (dst, a10, sizeof a10);
+
+ memset (dst, 0, sizeof a10);
+
+ stpncpy (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncpy (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ strncat (dst, a10, sizeof a10); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" } */
+
+ stpncpy (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncpy (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+
+ strncat (dst, a10, sizeof a10 - 1); /* { dg-warning "\\\[-Wsizeof-pointer-memaccess]" "" { xfail *-*-* } } */
+}
diff --git a/gcc/testsuite/c-c++-common/Wstringop-overflow.c b/gcc/testsuite/c-c++-common/Wstringop-overflow.c
new file mode 100644
index 0000000..53f5166
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wstringop-overflow.c
@@ -0,0 +1,158 @@
+/* PR middle-end/81117 - Improve buffer overflow checking in strncpy
+ { dg-do compile }
+ { dg-options "-O2 -Wstringop-overflow -Wno-stringop-truncation -ftrack-macro-expansion=0" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+#if __cplusplus
+extern "C" {
+#endif
+
+size_t strlen (const char*);
+char* strncat (char*, const char*, size_t);
+char* strncpy (char*, const char*, size_t);
+#if __cplusplus
+}
+#endif
+
+const char ar[] = "123";
+
+void test_strncat (char **d, const char* s, int i)
+{
+ /* Use a fresh pointer for each test to prevent the optimizer from
+ eliminating redundant writes into the same destination. Avoid
+ calling functions like sink() on the result that would have to
+ be assumed to change the source string by the alias oracle. */
+#define T(d, s, len) strncat (*d++, (s), (len))
+
+ T (d, "", 0);
+ T (d, "", 1);
+ T (d, "", 2);
+ T (d, "", 3);
+ T (d, "123", 0);
+ /* The following two calls truncate the copy and are diagnosed
+ by -Wstringop-truncation but there is evidence of overflow so
+ they're not diagnosed by -Wstringop-overflow. */
+ T (d, "123", 1);
+ T (d, "123", 2);
+
+ T (d, "123", 3); /* { dg-warning ".strncat\[^\n\r\]* specified bound 3 equals source length" } */
+ T (d, "123", 4);
+ T (d, "123", 9);
+
+ T (d, s, strlen (s)); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ T (d, s, strlen (s) + 1); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ /* The following could also be diagnosed by -Wstringop-truncation
+ (with some effort to distinguish the pattern from others like
+ the one above. */
+ T (d, s, strlen (s) - 1); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ T (d, s, strlen (s) - i); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+
+ /* The following is dubious but not necessarily a smoking gun. */
+ T (d, s, strlen (s) - strlen (s));
+
+ {
+ signed char n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ short n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ int n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ unsigned n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s) - 1; /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-message ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+
+ {
+ /* This doesn't overflow so iit should not be diagnosed. */
+ size_t n = strlen (s) - strlen (s);
+ T (d, s, n);
+ }
+
+ {
+ size_t n = i < strlen (s) ? i : strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-message ".strncat\[^\n\r\]* specified bound depends on the length of the source argument" } */
+ }
+}
+
+
+void test_strncpy (char **d, const char* s, int i)
+{
+#undef T
+#define T(d, s, len) strncpy (*d++, (s), (len))
+
+ T (d, "", 0);
+ T (d, "", 1);
+ T (d, "", 2);
+ T (d, "", 3);
+ T (d, "123", 0);
+ T (d, "123", 1);
+ T (d, "123", 2);
+ T (d, "123", 3);
+ T (d, "123", 4);
+ T (d, "123", 9);
+
+ T (d, "123", sizeof "123");
+ T (d, ar, sizeof ar);
+
+ T (d, s, strlen (s)); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+
+ {
+ int n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ }
+
+ {
+ unsigned n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s) - 1; /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ }
+
+ {
+ /* This is diagnosed by -Wstringop-truncation. Verify that it isn't
+ also diagnosed by -Wstringop-overflow. */
+ size_t n = strlen (s) - strlen (s);
+ T (d, s, n);
+ }
+
+ {
+ /* This use of strncpy is certainly dubious and it could well be
+ diagnosed by -Wstringop-truncation but it isn't. That it is
+ diagnosed with -Wstringop-overflow is more by accident than
+ by design. -Wstringop-overflow considers any dependency of
+ the bound on strlen(s) a potential bug. */
+ size_t n = i < strlen (s) ? i : strlen (s); /* { dg-message "length computed here" } */
+ T (d, s, n); /* { dg-message ".strncpy\[^\n\r]* specified bound depends on the length of the source argument" } */
+ }
+}
diff --git a/gcc/testsuite/c-c++-common/Wstringop-truncation.c b/gcc/testsuite/c-c++-common/Wstringop-truncation.c
new file mode 100644
index 0000000..c536a13
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wstringop-truncation.c
@@ -0,0 +1,448 @@
+/* PR middle-end/81117 - Improve buffer overflow checking in strncpy
+ { dg-do compile }
+ { dg-options "-O2 -Wstringop-truncation -Wno-stringop-overflow -ftrack-macro-expansion=0" } */
+
+
+typedef __SIZE_TYPE__ size_t;
+
+#if __cplusplus
+extern "C" {
+#endif
+
+size_t strlen (const char*);
+char* strncat (char*, const char*, size_t);
+char* strncpy (char*, const char*, size_t);
+
+#if __cplusplus
+}
+#endif
+
+extern size_t unsigned_value (void)
+{
+ extern volatile size_t unsigned_value_source;
+ return unsigned_value_source;
+}
+
+size_t unsigned_range (size_t min, size_t max)
+{
+ size_t val = unsigned_value ();
+ return val < min || max < val ? min : val;
+}
+
+#define UR(min, max) unsigned_range (min, max)
+
+void sink (void*);
+
+#define S4 "123"
+const char a4[] = "123";
+
+#define CHOOSE(a, b) (unsigned_value () & 1 ? a : b)
+
+
+typedef struct Dest
+{
+ char a5[5];
+ char b7[7];
+ char c3ns[3] __attribute__ ((nonstring));
+} Dest;
+
+char dst7[7];
+char dst2_5[2][5];
+
+/* Verify strncat warnings for arrays of known bounds. */
+
+void test_strncat_array (Dest *pd)
+{
+#define CAT(d, s, len) (strncat ((d), (s), (len)), sink (d))
+
+ CAT (dst7, S4, 2); /* { dg-warning "output truncated copying 2 bytes from a string of length 3" } */
+
+ CAT (dst7, a4, 1); /* { dg-warning "output truncated copying 1 byte from a string of length 3" } */
+
+ /* There is no truncation here but possible overflow so these
+ are diagnosed by -Wstringop-overflow:
+ CAT (dst7, S4, 3);
+ CAT (dst7, a4, 3);
+ */
+
+ CAT (pd->a5, S4, 2); /* { dg-warning "output truncated copying 2 bytes from a string of length 3" } */
+ CAT (pd->a5, S4, 1); /* { dg-warning "output truncated copying 1 byte from a string of length 3" } */
+}
+
+/* Verify strncat warnings for arrays of known bounds and a non-const
+ character count in some range. */
+
+void test_strncat_array_range (Dest *pd)
+{
+ CAT (dst7, S4, UR (0, 1)); /* { dg-warning "output truncated copying between 0 and 1 bytes from a string of length 3" } */
+ CAT (dst7, S4, UR (0, 2)); /* { dg-warning "output truncated copying between 0 and 2 bytes from a string of length 3" } */
+ CAT (dst7, S4, UR (1, 3)); /* { dg-warning "output truncated copying between 1 and 3 bytes from a string of length 3" } */
+ CAT (dst7, S4, UR (2, 4)); /* { dg-warning "output may be truncated copying between 2 and 4 bytes from a string of length 3" } */
+
+ CAT (dst7, S4, UR (0, 7));
+ CAT (dst7, S4, UR (1, 7));
+ CAT (dst7, S4, UR (6, 7));
+
+ CAT (dst7, S4, UR (0, 99));
+
+ CAT (dst7, S4, UR (0, 99));
+}
+
+/* Verify strncat warnings for arrays of unknown bounds. */
+
+void test_strncat_vla (char *d, unsigned n)
+{
+ CAT (d, S4, 2); /* { dg-warning "output truncated copying 2 bytes from a string of length 3" } */
+ CAT (d, S4, 4);
+
+ CAT (d, a4, 2); /* { dg-warning "output truncated copying 2 bytes from a string of length 3" } */
+
+ /* There is no truncation here but possible overflow so these
+ are diagnosed by -Wstringop-overflow:
+ CAT (d, S4, 3);
+ CAT (d, a4, 3);
+ */
+ CAT (d, a4, 4);
+
+ char vla[n];
+
+ CAT (vla, S4, 2); /* { dg-warning "output truncated copying 2 bytes from a string of length 3" } */
+
+ CAT (vla, S4, 4);
+ CAT (vla, S4, n);
+
+ CAT (vla, a4, 2); /* { dg-warning "output truncated copying 2 bytes from a string of length 3" } */
+
+ CAT (vla, a4, 4);
+ CAT (vla, a4, n);
+
+ CAT (d, vla, 1);
+ CAT (d, vla, 3);
+ CAT (d, vla, 4);
+ CAT (d, vla, n);
+
+ /* There is no truncation here but possible overflow so these
+ are diagnosed by -Wstringop-overflow:
+ CAT (vla, S4, 3);
+ CAT (vla, a4, 3);
+ */
+}
+
+/* Verify strncpy warnings with at least one pointer to an object
+ or string of unknown size (destination) or length (source). */
+
+void test_strncpy_ptr (char *d, const char* s, const char *t, int i)
+{
+#define CPY(d, s, len) (strncpy ((d), (s), (len)), sink (d))
+
+ /* Strncpy doesn't nul-terminate so the following is diagnosed. */
+ CPY (d, "", 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
+ CPY (d, s, 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
+
+ /* This is safe. */
+ CPY (d, "", 1);
+ CPY (d, "", 2);
+
+ /* This could be safe. */
+ CPY (d, s, 1);
+ CPY (d, s, 2);
+
+ /* Truncation. */
+ CPY (d, "123", 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 1 byte from a string of length 3" } */
+ CPY (d, "123", 2); /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 2 bytes from a string of length 3" } */
+ CPY (d, "123", 3); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ CPY (d, "123", 4);
+ CPY (d, "123", 9);
+
+ CPY (d, S4, sizeof S4); /* Covered by -Wsizeof-pointer-memaccess. */
+ CPY (d, S4, sizeof S4 - 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+
+ CPY (d, a4, sizeof a4); /* Covered by -Wsizeof-pointer-memaccess. */
+ CPY (d, a4, sizeof a4 - 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ CPY (d, a4, sizeof a4 - 3); /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 1 byte from a string of length 3" } */
+ CPY (d, a4, sizeof a4 - 4); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes from a string of length 3" } */
+
+ CPY (d, S4, strlen (S4)); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ /* Likely buggy but no truncation. Diagnosed by -Wstringop-overflow. */
+ CPY (d, a4, strlen (a4) + 1);
+ CPY (d, S4, strlen (S4) + i);
+
+ CPY (d, a4, strlen (a4)); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ /* As above, buggy but no evidence of truncation. */
+ CPY (d, S4, strlen (S4) + 1);
+
+ CPY (d, CHOOSE ("", "1"), 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
+ CPY (d, CHOOSE ("1", "12"), 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
+
+ CPY (d, CHOOSE ("", "1"), 1); /* { dg-warning ".strncpy\[^\n\r\]* output may be truncated copying 1 byte from a string of length 1" } */
+ CPY (d, CHOOSE ("1", ""), 1); /* { dg-warning ".strncpy\[^\n\r\]* output may be truncated copying 1 byte from a string of length 1" } */
+ CPY (d, CHOOSE (s, "1"), 1); /* { dg-warning ".strncpy\[^\n\r\]* output may be truncated copying 1 byte from a string of length 1" } */
+ CPY (d, CHOOSE (s, t), 1);
+
+ CPY (d, CHOOSE ("", "1"), 2);
+ CPY (d, CHOOSE ("1", ""), 2);
+ CPY (d, CHOOSE ("1", "2"), 2);
+ CPY (d, CHOOSE ("1", s), 2);
+ CPY (d, CHOOSE (s, "1"), 2);
+ CPY (d, CHOOSE (s, t), 2);
+
+ CPY (d, CHOOSE ("", "123"), 1); /* { dg-warning ".strncpy\[^\n\r\]* output may be truncated copying 1 byte from a string of length 3" } */
+ CPY (d, CHOOSE ("1", "123"), 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 1 byte from a string of length 1" } */
+ CPY (d, CHOOSE ("12", "123"), 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 1 byte from a string of length 2" } */
+ CPY (d, CHOOSE ("123", "12"), 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 1 byte from a string of length 2" } */
+
+ {
+ signed char n = strlen (s); /* { dg-message "length computed here" } */
+ CPY (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ short n = strlen (s); /* { dg-message "length computed here" } */
+ CPY (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ int n = strlen (s); /* { dg-message "length computed here" } */
+ CPY (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ unsigned n = strlen (s); /* { dg-message "length computed here" } */
+ CPY (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ size_t n;
+ n = strlen (s); /* { dg-message "length computed here" } */
+ CPY (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ size_t n;
+ char *dp2 = d + 1;
+ n = strlen (s); /* { dg-message "length computed here" } */
+ CPY (dp2, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
+ }
+
+ {
+ /* The following is likely buggy but there's no apparent truncation
+ so it's not diagnosed by -Wstringop-truncation. Instead, it is
+ diagnosed by -Wstringop-overflow (tested elsewhere). */
+ int n;
+ n = strlen (s) - 1;
+ CPY (d, s, n);
+ }
+
+ {
+ /* Same as above. */
+ size_t n;
+ n = strlen (s) - 1;
+ CPY (d, s, n);
+ }
+
+ {
+ size_t n = strlen (s) - strlen (s);
+ CPY (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
+ }
+
+ {
+ /* This use of strncpy is dubious but it's probably not worth
+ worrying about (truncation may not actually take place when
+ i is the result). It is diagnosed with -Wstringop-overflow
+ (although more by accident than by design).
+
+ size_t n = i < strlen (s) ? i : strlen (s);
+ CPY (d, s, n);
+ */
+ }
+}
+
+
+/* Verify strncpy warnings for arrays of known bounds. */
+
+void test_strncpy_array (Dest *pd, int i, const char* s)
+{
+#undef CPY
+#define CPY(d, s, len) (strncpy ((d), (s), (len)), sink (d))
+
+ CPY (dst7, s, 7); /* { dg-warning "specified bound 7 equals destination size" } */
+ CPY (dst7, s, sizeof dst7); /* { dg-warning "specified bound 7 equals destination size" } */
+
+ CPY (dst2_5[0], s, sizeof dst2_5[0]); /* { dg-warning "specified bound 5 equals destination size" "bug 77293" { xfail *-*-* } } */
+ CPY (dst2_5[1], s, sizeof dst2_5[1]); /* { dg-warning "specified bound 5 equals destination size" } */
+
+ /* Verify that copies that nul-terminate are not diagnosed. */
+ CPY (dst7, "", sizeof dst7);
+ CPY (dst7 + 6, "", sizeof dst7 - 6);
+ CPY (dst7, "1", sizeof dst7);
+ CPY (dst7 + 1, "1", sizeof dst7 - 1);
+ CPY (dst7, "123456", sizeof dst7);
+ CPY (dst7 + 1, "12345", sizeof dst7 - 1);
+
+ CPY (dst7 + i, s, 6);
+ CPY (dst7 + i, s, 7); /* { dg-warning "specified bound 7 equals destination size" } */
+ /* The following two calls are diagnosed by -Wstringop-overflow. */
+ CPY (dst7 + i, s, 8);
+ CPY (dst7 + i, s, UR (8, 9));
+
+ /* No nul-termination here. */
+ CPY (dst7 + 2, "12345", sizeof dst7 - 2); /* { dg-warning "output truncated before terminating nul copying 5 bytes from a string of the same length" } */
+
+ /* Because strnlen appends as many NULs as necessary to write the specified
+ number of byts the following doesn't (necessarily) truncate but rather
+ overflow, and so is diagnosed by -Wstringop-overflow. */
+ CPY (dst7, s, 8);
+
+ CPY (dst7 + 1, s, 6); /* { dg-warning "specified bound 6 equals destination size" } */
+ CPY (dst7 + 6, s, 1); /* { dg-warning "specified bound 1 equals destination size" } */
+
+ CPY (pd->a5, s, 5); /* { dg-warning "specified bound 5 equals destination size" } */
+ CPY (pd->a5, s, sizeof pd->a5); /* { dg-warning "specified bound 5 equals destination size" } */
+
+ /* The following is not yet handled. */
+ CPY (pd->a5 + i, s, sizeof pd->a5); /* { dg-warning "specified bound 5 equals destination size" "member array" { xfail *-*-* } } */
+
+ /* Verify that a copy that nul-terminates is not diagnosed. */
+ CPY (pd->a5, "1234", sizeof pd->a5);
+
+ /* Same above, diagnosed by -Wstringop-overflow. */
+ CPY (pd->a5, s, 6);
+
+ /* Exercise destination with attribute "nonstring". */
+ CPY (pd->c3ns, "", 3);
+ CPY (pd->c3ns, "", 1);
+ /* Truncation is still diagnosed -- using strncpy in this case is
+ pointless and should be replaced with memcpy. */
+ CPY (pd->c3ns, "12", 1); /* { dg-warning "output truncated copying 1 byte from a string of length 2" } */
+ CPY (pd->c3ns, "12", 2);
+ CPY (pd->c3ns, "12", 3);
+ CPY (pd->c3ns, "123", 3);
+ CPY (pd->c3ns, s, 3);
+ CPY (pd->c3ns, s, sizeof pd->c3ns);
+
+ /* Verify that the idiom of calling strncpy with a bound equal to
+ the size of the destination (and thus potentially without NUL-
+ terminating it) immediately followed by setting the last element
+ of the array to NUL is not diagnosed. */
+ {
+ /* This might be better written using memcpy() but it's safe so
+ it probably shouldn't be diagnosed. It currently triggers
+ a warning because of bug 81704. */
+ strncpy (dst7, "0123456", sizeof dst7); /* { dg-bogus "truncated" "bug 81704" { xfail *-*-* } } */
+ dst7[sizeof dst7 - 1] = '\0';
+ sink (dst7);
+ }
+
+ {
+ const char a[] = "0123456789";
+ strncpy (dst7, a, sizeof dst7);
+ dst7[sizeof dst7 - 1] = '\0';
+ sink (dst7);
+ }
+
+ {
+ strncpy (dst7, s, sizeof dst7);
+ dst7[sizeof dst7 - 1] = '\0';
+ sink (dst7);
+ }
+
+ {
+ strncpy (pd->a5, "01234", sizeof pd->a5); /* { dg-bogus "truncated" "bug 81704" { xfail *-*-* } } */
+ pd->a5[sizeof pd->a5 - 1] = '\0';
+ sink (pd);
+ }
+
+ {
+ strncpy (pd->a5, s, sizeof pd->a5);
+ pd->a5[sizeof pd->a5 - 1] = '\0';
+ sink (pd);
+ }
+
+ {
+ unsigned n = 7;
+ char *p = (char*)__builtin_malloc (n);
+ strncpy (p, s, n);
+ p[n - 1] = '\0';
+ sink (p);
+ }
+
+ {
+ /* This should be diagnosed because the NUL-termination doesn't
+ immediately follow the strncpy call (sink may expect pd->a5
+ to be NUL-terminated). */
+ strncpy (pd->a5, s, sizeof pd->a5); /* { dg-warning "specified bound 5 equals destination size" } */
+ sink (pd);
+ pd->a5[sizeof pd->a5] = '\0';
+ sink (pd);
+ }
+}
+
+typedef struct Flex
+{
+ size_t n;
+ char a0[0];
+ char ax[];
+} Flex;
+
+extern char array[];
+
+/* Verify that no warning is issued for array of unknown bound, flexible
+ array members, or zero-length arrays, except when the source is definitely
+ truncated. */
+
+void test_strncpy_flexarray (Flex *pf, const char* s)
+{
+#undef CPY
+#define CPY(d, s, len) (strncpy ((d), (s), (len)), sink (d))
+
+ CPY (array, "12345", 7);
+ CPY (array, "12345", 123);
+
+ CPY (array, s, 7);
+ CPY (array, s, 123);
+
+ CPY (pf->a0, s, 1);
+ CPY (pf->a0, s, 1234);
+
+ CPY (pf->a0, "", 1);
+ CPY (pf->a0, "12345", 5); /* { dg-warning "output truncated before terminating nul copying 5 bytes from a string of the same length" } */
+ CPY (pf->a0, "12345", 1234);
+
+ CPY (pf->ax, s, 5);
+ CPY (pf->ax, s, 12345);
+
+ CPY (pf->ax, "1234", 5);
+ CPY (pf->ax, "12345", 5); /* { dg-warning "output truncated before terminating nul copying 5 bytes from a string of the same length" } */
+ CPY (pf->ax, "12345", 12345);
+}
+
+/* Verify warnings for dynamically allocated objects. */
+
+void test_strncpy_alloc (const char* s)
+{
+ size_t n = 7;
+ char *d = (char *)__builtin_malloc (n);
+
+ CPY (d, s, n); /* { dg-warning "specified bound 7 equals destination size" "bug 79016" { xfail *-*-* } } */
+
+ Dest *pd = (Dest *)__builtin_malloc (sizeof *pd * n);
+ CPY (pd->a5, s, 5); /* { dg-warning "specified bound 5 equals destination size" } */
+ CPY (pd->a5, s, sizeof pd->a5); /* { dg-warning "specified bound 5 equals destination size" } */
+}
+
+/* Verify warnings for VLAs. */
+
+void test_strncpy_vla (unsigned n, const char* s)
+{
+ char vla[n];
+ CPY (vla, s, 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
+
+ CPY (vla, s, 1);
+ CPY (vla, s, 2);
+ CPY (vla, s, n);
+
+ CPY (vla, "", 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
+ CPY (vla, "", 1);
+ CPY (vla, S4, 3); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+ CPY (vla, S4, n);
+}
diff --git a/gcc/testsuite/c-c++-common/attr-nonstring-1.c b/gcc/testsuite/c-c++-common/attr-nonstring-1.c
new file mode 100644
index 0000000..10a6688
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/attr-nonstring-1.c
@@ -0,0 +1,60 @@
+/* Test to exercise attribute "nonstring" syntax.
+ { dg-do compile }
+ { dg-options "-Wattributes" } */
+
+#define ATTR(list) __attribute__ (list)
+#define NONSTR ATTR ((nonstring))
+
+/* Verify it's accepted on char arrays. */
+extern NONSTR char nsx_1[];
+extern char NONSTR nsx_2[];
+extern char nsx_3[] NONSTR;
+
+extern NONSTR char ns1[1];
+extern char NONSTR ns3[3];
+extern char ns5[5] NONSTR;
+
+/* Verify it's accepted on char pointers. */
+extern NONSTR char* pns_1;
+extern char NONSTR* pns_2;
+extern char* NONSTR pns_3;
+
+struct S
+{
+/* Verify it's accepted on char member pointers. */
+ NONSTR char* mpns_1;
+ char NONSTR* mpns_2;
+ char* NONSTR mpns_3;
+
+/* Verify it's accepted on char member arrays. */
+ NONSTR char mns1[1];
+ char NONSTR mns3[3];
+ char mns5[5] NONSTR;
+
+/* Verify it's accepted on char flexible array members. */
+ char mnsx[] NONSTR;
+};
+
+/* Verify it's rejected on non-array and non-pointer objects. */
+extern NONSTR char c1; /* { dg-warning ".nonstring. attribute ignored on objects of type .char." } */
+
+extern NONSTR int i1; /* { dg-warning ".nonstring. attribute ignored on objects of type .int." } */
+
+extern NONSTR int ia1[]; /* { dg-warning ".nonstring. attribute ignored on objects of type .int *\\\[\\\]." } */
+
+extern NONSTR int* pi1; /* { dg-warning ".nonstring. attribute ignored on objects of type .int *\\*." } */
+
+extern NONSTR
+void f (void); /* { dg-warning ".nonstring. attribute does not apply to functions" } */
+
+struct NONSTR
+NonStrType { int i; }; /* { dg-warning ".nonstring. attribute does not apply to types" } */
+
+typedef char NONSTR nschar_t; /* { dg-warning ".nonstring. attribute does not apply to types" } */
+
+void func (NONSTR char *pns1, char NONSTR *pns2, char* NONSTR pns3)
+{
+ (void)pns1;
+ (void)pns2;
+ (void)pns3;
+}
diff --git a/gcc/testsuite/c-c++-common/attr-nonstring-2.c b/gcc/testsuite/c-c++-common/attr-nonstring-2.c
new file mode 100644
index 0000000..6e273e7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/attr-nonstring-2.c
@@ -0,0 +1,123 @@
+/* Test to exercise attribute "nonstring".
+ { dg-do compile }
+ { dg-options "-O2 -Wattributes -Wstringop-truncation -ftrack-macro-expansion=0" } */
+
+#define ATTR(list) __attribute__ (list)
+#define NONSTR ATTR ((nonstring))
+#define strncpy(d, s, n) (__builtin_strncpy ((d), (s), (n)), sink (d))
+
+void sink (void*);
+
+/* Global string with an unknown bound. */
+extern char gsx[];
+
+/* Global string with an known bound. */
+extern char gs3[3];
+
+/* Global non-strings with an unknown bound. */
+extern NONSTR char gax_1[];
+extern char NONSTR gax_2[];
+extern char gax_3[] NONSTR;
+
+/* Global non-strings with a known bound. */
+NONSTR char gns3[3];
+char NONSTR gns4[4];
+char gns5[5] NONSTR;
+
+/* Global string pointer. */
+extern char *ps_1;
+
+/* Global non-string pointers. */
+extern NONSTR char *pns_1;
+extern char* NONSTR pns_2;
+extern char *pns_3 NONSTR;
+
+struct MemArrays
+{
+ NONSTR char ma3[3];
+ char NONSTR ma4[4];
+ char ma5[5] NONSTR;
+ char max[] NONSTR;
+};
+
+
+void test_array (const char *s, unsigned n)
+{
+ const char s7[] = "1234567";
+
+ strncpy (gs3, "", 0); /* { dg-warning "destination unchanged after copying no bytes" } */
+ strncpy (gs3, "a", 1); /* { dg-warning "output truncated before terminating nul copying 1 byte from a string of the same length" } */
+ strncpy (gs3, "a", 2);
+ strncpy (gs3, "a", 3);
+ strncpy (gs3, "ab", 3);
+ strncpy (gs3, "abc", 3); /* { dg-warning "output truncated before terminating nul copying 3 bytes from a string of the same length" } */
+
+ /* It might perhaps be helpful to diagnose certain truncation even
+ for non-strings. Then again, since the destination has been
+ explicitly annotated as non-string, it might be viewed as a false
+ positive. A valid use case seen in Glibc goes something like this:
+
+ #if FOO
+ # define S "1234"
+ #else
+ # define S "12345678"
+ #endif
+
+ strncpy (d, S, 8);
+ */
+ strncpy (gax_3, s7, 3);
+
+ strncpy (gax_1, "a", 1);
+ strncpy (gax_2, "ab", 2);
+ strncpy (gax_3, "abc", 3);
+ strncpy (gax_3, s7, 3);
+
+ strncpy (gax_1, s, 1);
+ strncpy (gax_2, s, 1);
+ strncpy (gax_3, s, 1);
+
+ strncpy (gax_1, s, n);
+ strncpy (gax_2, s, n);
+ strncpy (gax_3, s, n);
+}
+
+
+void test_pointer (const char *s, unsigned n)
+{
+ const char s7[] = "1234567";
+
+ strncpy (pns_1, "a", 1);
+ strncpy (pns_2, "ab", 2);
+ strncpy (pns_3, "abc", 3);
+ strncpy (pns_3, s7, 3); /* { dg-warning "output truncated copying 3 bytes from a string of length 7" } */
+
+ strncpy (pns_1, s, 1);
+ strncpy (pns_2, s, 1);
+ strncpy (pns_3, s, 1);
+
+ strncpy (pns_1, s, n);
+ strncpy (pns_2, s, n);
+ strncpy (pns_3, s, n);
+}
+
+
+void test_member_array (struct MemArrays *p, const char *s, unsigned n)
+{
+ const char s7[] = "1234567";
+
+ strncpy (p->ma3, "a", 1);
+ strncpy (p->ma4, "ab", 2);
+ strncpy (p->ma5, "abc", 3);
+ strncpy (p->max, "abcd", 4);
+ strncpy (p->max, s7, 5);
+
+ strncpy (p->ma3, s, 1);
+ strncpy (p->ma4, s, 1);
+ strncpy (p->ma5, s, 1);
+ strncpy (p->max, s, 1);
+
+ strncpy (p->ma3, s7, n);
+ strncpy (p->ma4, s7, n);
+ strncpy (p->ma5, s7, n);
+ strncpy (p->max, s7, n);
+}
diff --git a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C
index c72532b..5bc5c4c 100644
--- a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C
+++ b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C
@@ -1,6 +1,6 @@
// Test -Wsizeof-pointer-memaccess warnings.
// { dg-do compile }
-// { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow" }
+// { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow -Wno-stringop-truncation" }
// Test just twice, once with -O0 non-fortified, once with -O2 fortified.
// { dg-skip-if "" { *-*-* } { "*" } { "-O0" "-O2" } }
// { dg-skip-if "" { *-*-* } { "-flto" } { "" } }
@@ -698,12 +698,17 @@ f4 (char *x, char **y, int z, char w[64])
strncat (w, s2, sizeof (w)); // { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" }
stpncpy (w, s1, sizeof (w)); // { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" }
- // These are correct, no warning.
const char s3[] = "foobarbaz";
const char s4[] = "abcde12345678";
- strncpy (x, s3, sizeof (s3));
- strncat (x, s4, sizeof (s4));
- stpncpy (x, s3, sizeof (s3));
+
+ // These are pointless when the destination is large enough, and
+ // cause overflow otherwise. They might as well be replaced by
+ // strcpy() or memcpy().
+ strncpy (x, s3, sizeof (s3)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" }
+ strncat (x, s4, sizeof (s4)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" }
+ stpncpy (x, s3, sizeof (s3)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" }
+
+ // These are safe, no warning.
y[1] = strndup (s3, sizeof (s3));
z += strncmp (s3, s4, sizeof (s3));
z += strncmp (s3, s4, sizeof (s4));
diff --git a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C
index a216f47..f2c864b 100644
--- a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C
+++ b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C
@@ -1,6 +1,6 @@
// Test -Wsizeof-pointer-memaccess warnings.
// { dg-do compile }
-// { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow" }
+// { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow -Wno-stringop-truncation" }
// Test just twice, once with -O0 non-fortified, once with -O2 fortified,
// suppressing buffer overflow warnings.
// { dg-skip-if "" { *-*-* } { "*" } { "-O0" "-O2" } }
@@ -703,12 +703,13 @@ f4 (char *x, char **y, int z, char w[64])
strncat (w, s2, sizeof (w)); // { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" }
stpncpy (w, s1, sizeof (w)); // { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" }
- // These are correct, no warning.
const char s3[] = "foobarbaz";
const char s4[] = "abcde12345678";
- strncpy (x, s3, sizeof (s3));
- strncat (x, s4, sizeof (s4));
- stpncpy (x, s3, sizeof (s3));
+ strncpy (x, s3, sizeof (s3)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination" }
+ strncat (x, s4, sizeof (s4)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination" }
+ stpncpy (x, s3, sizeof (s3)); // { dg-warning "call is the same expression as the source; did you mean to use the size of the destination" }
+
+ // These are safe, no warning.
y[1] = strndup (s3, sizeof (s3));
z += strncmp (s3, s4, sizeof (s3));
z += strncmp (s3, s4, sizeof (s4));
diff --git a/gcc/testsuite/gcc.dg/Walloca-1.c b/gcc/testsuite/gcc.dg/Walloca-1.c
index ad39373..85e9160 100644
--- a/gcc/testsuite/gcc.dg/Walloca-1.c
+++ b/gcc/testsuite/gcc.dg/Walloca-1.c
@@ -1,6 +1,6 @@
/* { dg-do compile } */
/* { dg-require-effective-target alloca } */
-/* { dg-options "-Walloca-larger-than=2000 -O2" } */
+/* { dg-options "-Walloca-larger-than=2000 -O2 -ftrack-macro-expansion=0" } */
#define alloca __builtin_alloca
diff --git a/gcc/testsuite/gcc.dg/torture/Wsizeof-pointer-memaccess1.c b/gcc/testsuite/gcc.dg/torture/Wsizeof-pointer-memaccess1.c
index f9bc57c..cd9dc72 100644
--- a/gcc/testsuite/gcc.dg/torture/Wsizeof-pointer-memaccess1.c
+++ b/gcc/testsuite/gcc.dg/torture/Wsizeof-pointer-memaccess1.c
@@ -1,6 +1,6 @@
/* Test -Wsizeof-pointer-memaccess warnings. */
/* { dg-do compile } */
-/* { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow" } */
+/* { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow -Wno-stringop-truncation" } */
/* Test just twice, once with -O0 non-fortified, once with -O2 fortified. */
/* { dg-skip-if "" { *-*-* } { "*" } { "-O0" "-O2" } } */
/* { dg-skip-if "" { *-*-* } { "-flto" } { "" } } */
@@ -704,12 +704,17 @@ f4 (char *x, char **y, int z, char w[64])
strncat (w, s2, sizeof (w)); /* { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" } */
stpncpy (w, s1, sizeof (w)); /* { dg-warning "call is the same expression as the destination; did you mean to provide an explicit length" } */
- /* These are correct, no warning. */
+ /* These are pointless when the destination is large enough, and
+ cause overflow otherwise. If the copies are guaranteed to be
+ safe the calls might as well be replaced by strcat(), strcpy(),
+ or memcpy(). */
const char s3[] = "foobarbaz";
const char s4[] = "abcde12345678";
- strncpy (x, s3, sizeof (s3));
- strncat (x, s4, sizeof (s4));
- stpncpy (x, s3, sizeof (s3));
+ strncpy (x, s3, sizeof (s3)); /* { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" } */
+ strncat (x, s4, sizeof (s4)); /* { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" } */
+ stpncpy (x, s3, sizeof (s3)); /* { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" } */
+
+ /* These are correct, no warning. */
y[1] = strndup (s3, sizeof (s3));
z += strncmp (s3, s4, sizeof (s3));
z += strncmp (s3, s4, sizeof (s4));
diff --git a/gcc/testsuite/gcc.dg/torture/pr63554.c b/gcc/testsuite/gcc.dg/torture/pr63554.c
index fa06c5a..9162266 100644
--- a/gcc/testsuite/gcc.dg/torture/pr63554.c
+++ b/gcc/testsuite/gcc.dg/torture/pr63554.c
@@ -1,4 +1,5 @@
-/* { dg-do compile } */
+/* PR c/63554 - ice in "execute_todo, at passes.c:1797" with -O3
+ { dg-do compile } */
char *a;
void
@@ -7,3 +8,5 @@ nssutil_ReadSecmodDB (void)
long b = __builtin_object_size (0, 0);
a = __builtin___strncat_chk (a, " ", 1, b);
}
+
+/* { dg-prune-output "\\\[-Wstringop-overflow=]" } */
diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c
index 4ec0dac..25fdcd0 100644
--- a/gcc/tree-ssa-strlen.c
+++ b/gcc/tree-ssa-strlen.c
@@ -40,12 +40,17 @@ along with GCC; see the file COPYING3. If not see
#include "expr.h"
#include "tree-dfa.h"
#include "domwalk.h"
+#include "tree-ssa-alias.h"
#include "tree-ssa-propagate.h"
#include "params.h"
#include "ipa-chkp.h"
#include "tree-hash-traits.h"
#include "builtins.h"
#include "target.h"
+#include "diagnostic-core.h"
+#include "diagnostic.h"
+#include "intl.h"
+#include "attribs.h"
/* A vector indexed by SSA_NAME_VERSION. 0 means unknown, positive value
is an index into strinfo vector, negative value stands for
@@ -147,6 +152,9 @@ struct decl_stridxlist_map
mappings. */
static hash_map<tree_decl_hash, stridxlist> *decl_to_stridxlist_htab;
+typedef std::pair<int, location_t> stridx_strlenloc;
+static hash_map<tree, stridx_strlenloc> strlen_to_stridx;
+
/* Obstack for struct stridxlist and struct decl_stridxlist_map. */
static struct obstack stridx_obstack;
@@ -1198,6 +1206,9 @@ handle_builtin_strlen (gimple_stmt_iterator *gsi)
si->nonzero_chars = lhs;
gcc_assert (si->full_string_p);
}
+
+ location_t loc = gimple_location (stmt);
+ strlen_to_stridx.put (lhs, stridx_strlenloc (idx, loc));
return;
}
}
@@ -1241,6 +1252,9 @@ handle_builtin_strlen (gimple_stmt_iterator *gsi)
strinfo *si = new_strinfo (src, idx, lhs, true);
set_strinfo (idx, si);
find_equal_ptrs (src, idx);
+
+ location_t loc = gimple_location (stmt);
+ strlen_to_stridx.put (lhs, stridx_strlenloc (idx, loc));
}
}
@@ -1607,6 +1621,368 @@ handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi)
fprintf (dump_file, "not possible.\n");
}
+/* Return true if LEN depends on a call to strlen(SRC) in an interesting
+ way. LEN can either be an integer expression, or a pointer (to char).
+ When it is the latter (such as in recursive calls to self) is is
+ assumed to be the argument in some call to strlen() whose relationship
+ to SRC is being ascertained. */
+
+static bool
+is_strlen_related_p (tree src, tree len)
+{
+ if (TREE_CODE (TREE_TYPE (len)) == POINTER_TYPE
+ && operand_equal_p (src, len, 0))
+ return true;
+
+ if (TREE_CODE (len) != SSA_NAME)
+ return false;
+
+ gimple *def_stmt = SSA_NAME_DEF_STMT (len);
+ if (!def_stmt)
+ return false;
+
+ if (is_gimple_call (def_stmt))
+ {
+ tree func = gimple_call_fndecl (def_stmt);
+ if (!valid_builtin_call (def_stmt)
+ || DECL_FUNCTION_CODE (func) != BUILT_IN_STRLEN)
+ return false;
+
+ tree arg = gimple_call_arg (def_stmt, 0);
+ return is_strlen_related_p (src, arg);
+ }
+
+ if (!is_gimple_assign (def_stmt))
+ return false;
+
+ tree_code code = gimple_assign_rhs_code (def_stmt);
+ tree rhs1 = gimple_assign_rhs1 (def_stmt);
+ tree rhstype = TREE_TYPE (rhs1);
+
+ if ((TREE_CODE (rhstype) == POINTER_TYPE && code == POINTER_PLUS_EXPR)
+ || (INTEGRAL_TYPE_P (rhstype)
+ && (code == BIT_AND_EXPR
+ || code == NOP_EXPR)))
+ {
+ /* Pointer plus (an integer) and integer cast or truncation are
+ considered among the (potentially) related expressions to strlen.
+ Others are not. */
+ return is_strlen_related_p (src, rhs1);
+ }
+
+ return false;
+}
+
+/* A helper of handle_builtin_stxncpy. Check to see if the specified
+ bound is a) equal to the size of the destination DST and if so, b)
+ if it's immediately followed by DST[CNT - 1] = '\0'. If a) holds
+ and b) does not, warn. Otherwise, do nothing. Return true if
+ diagnostic has been issued.
+
+ The purpose is to diagnose calls to strncpy and stpncpy that do
+ not nul-terminate the copy while allowing for the idiom where
+ such a call is immediately followed by setting the last element
+ to nul, as in:
+ char a[32];
+ strncpy (a, s, sizeof a);
+ a[sizeof a - 1] = '\0';
+*/
+
+static bool
+maybe_diag_stxncpy_trunc (gimple_stmt_iterator gsi, tree src, tree cnt)
+{
+ if (!warn_stringop_truncation)
+ return false;
+
+ gimple *stmt = gsi_stmt (gsi);
+
+ wide_int cntrange[2];
+
+ if (TREE_CODE (cnt) == INTEGER_CST)
+ cntrange[0] = cntrange[1] = wi::to_wide (cnt);
+ else if (TREE_CODE (cnt) == SSA_NAME)
+ {
+ enum value_range_type rng = get_range_info (cnt, cntrange, cntrange + 1);
+ if (rng == VR_RANGE)
+ ;
+ else if (rng == VR_ANTI_RANGE)
+ {
+ wide_int maxobjsize = wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node));
+
+ if (wi::ltu_p (cntrange[1], maxobjsize))
+ {
+ cntrange[0] = cntrange[1] + 1;
+ cntrange[1] = maxobjsize;
+ }
+ else
+ {
+ cntrange[1] = cntrange[0] - 1;
+ cntrange[0] = wi::zero (TYPE_PRECISION (TREE_TYPE (cnt)));
+ }
+ }
+ else
+ return false;
+ }
+ else
+ return false;
+
+ /* Negative value is the constant string length. If it's less than
+ the lower bound there is no truncation. */
+ int sidx = get_stridx (src);
+ if (sidx < 0 && wi::gtu_p (cntrange[0], ~sidx))
+ return false;
+
+ tree dst = gimple_call_arg (stmt, 0);
+
+ /* See if the destination is declared with attribute "nonstring"
+ and if so, avoid the truncation warning. */
+ if (TREE_CODE (dst) == SSA_NAME)
+ {
+ if (SSA_NAME_IS_DEFAULT_DEF (dst))
+ dst = SSA_NAME_VAR (dst);
+ else
+ {
+ gimple *def = SSA_NAME_DEF_STMT (dst);
+
+ if (is_gimple_assign (def)
+ && gimple_assign_rhs_code (def) == ADDR_EXPR)
+ dst = gimple_assign_rhs1 (def);
+ }
+ }
+
+ tree dstdecl = dst;
+ if (TREE_CODE (dstdecl) == ADDR_EXPR)
+ dstdecl = TREE_OPERAND (dstdecl, 0);
+
+ {
+ tree d = dstdecl;
+ if (TREE_CODE (d) == COMPONENT_REF)
+ d = TREE_OPERAND (d, 1);
+
+ if (DECL_P (d) && lookup_attribute ("nonstring", DECL_ATTRIBUTES (d)))
+ return false;
+ }
+
+ /* Look for dst[i] = '\0'; after the stxncpy() call and if found
+ avoid the truncation warning. */
+ gsi_next (&gsi);
+ gimple *next_stmt = gsi_stmt (gsi);
+
+ if (!gsi_end_p (gsi) && is_gimple_assign (next_stmt))
+ {
+ HOST_WIDE_INT off;
+ dstdecl = get_addr_base_and_unit_offset (dstdecl, &off);
+
+ tree lhs = gimple_assign_lhs (next_stmt);
+ tree lhsbase = get_addr_base_and_unit_offset (lhs, &off);
+ if (lhsbase && operand_equal_p (dstdecl, lhsbase, 0))
+ return false;
+ }
+
+ int prec = TYPE_PRECISION (TREE_TYPE (cnt));
+ wide_int lenrange[2];
+ if (strinfo *sisrc = sidx > 0 ? get_strinfo (sidx) : NULL)
+ {
+ lenrange[0] = (sisrc->nonzero_chars
+ && TREE_CODE (sisrc->nonzero_chars) == INTEGER_CST
+ ? wi::to_wide (sisrc->nonzero_chars)
+ : wi::zero (prec));
+ lenrange[1] = lenrange[0];
+ }
+ else if (sidx < 0)
+ lenrange[0] = lenrange[1] = wi::shwi (~sidx, prec);
+ else
+ {
+ tree range[2];
+ get_range_strlen (src, range);
+ if (range[0])
+ {
+ lenrange[0] = wi::to_wide (range[0], prec);
+ lenrange[1] = wi::to_wide (range[1], prec);
+ }
+ else
+ {
+ lenrange[0] = wi::shwi (0, prec);
+ lenrange[1] = wi::shwi (-1, prec);
+ }
+ }
+
+ location_t callloc = gimple_location (stmt);
+ tree func = gimple_call_fndecl (stmt);
+
+ if (lenrange[0] != 0 || !wi::neg_p (lenrange[1]))
+ {
+ /* If the longest source string is shorter than the lower bound
+ of the specified count the copy is definitely nul-terminated. */
+ if (wi::ltu_p (lenrange[1], cntrange[0]))
+ return false;
+
+ if (wi::neg_p (lenrange[1]))
+ {
+ /* The length of one of the strings is unknown but at least
+ one has non-zero length and that length is stored in
+ LENRANGE[1]. Swap the bounds to force a "may be truncated"
+ warning below. */
+ lenrange[1] = lenrange[0];
+ lenrange[0] = wi::shwi (0, prec);
+ }
+
+ if (wi::geu_p (lenrange[0], cntrange[1]))
+ {
+ /* The shortest string is longer than the upper bound of
+ the count so the truncation is certain. */
+ if (cntrange[0] == cntrange[1])
+ return warning_at (callloc, OPT_Wstringop_truncation,
+ integer_onep (cnt)
+ ? G_("%qD output truncated copying %E byte "
+ "from a string of length %wu")
+ : G_("%qD output truncated copying %E bytes "
+ "from a string of length %wu"),
+ func, cnt, lenrange[0].to_uhwi ());
+
+ return warning_at (callloc, OPT_Wstringop_truncation,
+ "%qD output truncated copying between %wu "
+ "and %wu bytes from a string of length %wu",
+ func, cntrange[0].to_uhwi (),
+ cntrange[1].to_uhwi (), lenrange[0].to_uhwi ());
+ }
+ else if (wi::geu_p (lenrange[1], cntrange[1]))
+ {
+ /* The longest string is longer than the upper bound of
+ the count so the truncation is possible. */
+ if (cntrange[0] == cntrange[1])
+ return warning_at (callloc, OPT_Wstringop_truncation,
+ integer_onep (cnt)
+ ? G_("%qD output may be truncated copying %E "
+ "byte from a string of length %wu")
+ : G_("%qD output may be truncated copying %E "
+ "bytes from a string of length %wu"),
+ func, cnt, lenrange[1].to_uhwi ());
+
+ return warning_at (callloc, OPT_Wstringop_truncation,
+ "%qD output may be truncated copying between %wu "
+ "and %wu bytes from a string of length %wu",
+ func, cntrange[0].to_uhwi (),
+ cntrange[1].to_uhwi (), lenrange[1].to_uhwi ());
+ }
+
+ if (cntrange[0] != cntrange[1]
+ && wi::leu_p (cntrange[0], lenrange[0])
+ && wi::leu_p (cntrange[1], lenrange[0] + 1))
+ {
+ /* If the source (including the terminating nul) is longer than
+ the lower bound of the specified count but shorter than the
+ upper bound the copy may (but need not) be truncated. */
+ return warning_at (callloc, OPT_Wstringop_truncation,
+ "%qD output may be truncated copying between %wu "
+ "and %wu bytes from a string of length %wu",
+ func, cntrange[0].to_uhwi (),
+ cntrange[1].to_uhwi (), lenrange[0].to_uhwi ());
+ }
+ }
+
+ if (tree dstsize = compute_objsize (dst, 1))
+ {
+ /* The source length is uknown. Try to determine the destination
+ size and see if it matches the specified bound. If not, bail.
+ Otherwise go on to see if it should be diagnosed for possible
+ truncation. */
+ if (!dstsize)
+ return false;
+
+ if (wi::to_wide (dstsize) != cntrange[1])
+ return false;
+
+ if (cntrange[0] == cntrange[1])
+ return warning_at (callloc, OPT_Wstringop_truncation,
+ "%qD specified bound %E equals destination size",
+ func, cnt);
+ }
+
+ return false;
+}
+
+/* Check the size argument to the built-in forms of stpncpy and strncpy
+ to see if it's derived from calling strlen() on the source argument
+ and if so, issue a warning. */
+
+static void
+handle_builtin_stxncpy (built_in_function, gimple_stmt_iterator *gsi)
+{
+ gimple *stmt = gsi_stmt (*gsi);
+
+ bool with_bounds = gimple_call_with_bounds_p (stmt);
+
+ tree src = gimple_call_arg (stmt, with_bounds ? 2 : 1);
+ tree len = gimple_call_arg (stmt, with_bounds ? 3 : 2);
+
+ /* If the length argument was computed from strlen(S) for some string
+ S retrieve the strinfo index for the string (PSS->FIRST) alonng with
+ the location of the strlen() call (PSS->SECOND). */
+ stridx_strlenloc *pss = strlen_to_stridx.get (len);
+ if (!pss || pss->first <= 0)
+ {
+ if (maybe_diag_stxncpy_trunc (*gsi, src, len))
+ gimple_set_no_warning (stmt, true);
+
+ return;
+ }
+
+ int sidx = get_stridx (src);
+ strinfo *sisrc = sidx > 0 ? get_strinfo (sidx) : NULL;
+
+ /* Strncpy() et al. cannot modify the source string. Prevent the rest
+ of the pass from invalidating the strinfo data. */
+ if (sisrc)
+ sisrc->dont_invalidate = true;
+
+ /* Retrieve the strinfo data for the string S that LEN was computed
+ from as some function F of strlen (S) (i.e., LEN need not be equal
+ to strlen(S)). */
+ strinfo *silen = get_strinfo (pss->first);
+
+ location_t callloc = gimple_location (stmt);
+
+ tree func = gimple_call_fndecl (stmt);
+
+ bool warned = false;
+
+ /* When -Wstringop-truncation is set, try to determine truncation
+ before diagnosing possible overflow. Truncation is implied by
+ the LEN argument being equal to strlen(SRC), regardless of
+ whether its value is known. Otherwise, issue the more generic
+ -Wstringop-overflow which triggers for LEN arguments that in
+ any meaningful way depend on strlen(SRC). */
+ if (warn_stringop_truncation
+ && sisrc == silen
+ && is_strlen_related_p (src, len))
+ warned = warning_at (callloc, OPT_Wstringop_truncation,
+ "%qD output truncated before terminating nul "
+ "copying as many bytes from a string as its length",
+ func);
+ else if (silen && is_strlen_related_p (src, silen->ptr))
+ warned = warning_at (callloc, OPT_Wstringop_overflow_,
+ "%qD specified bound depends on the length "
+ "of the source argument", func);
+ if (warned)
+ {
+ location_t strlenloc = pss->second;
+ if (strlenloc != UNKNOWN_LOCATION && strlenloc != callloc)
+ inform (strlenloc, "length computed here");
+ }
+}
+
+/* Check the size argument to the built-in forms of strncat to see if
+ it's derived from calling strlen() on the source argument and if so,
+ issue a warning. */
+
+static void
+handle_builtin_strncat (built_in_function bcode, gimple_stmt_iterator *gsi)
+{
+ /* Same as stxncpy(). */
+ handle_builtin_stxncpy (bcode, gsi);
+}
+
/* Handle a memcpy-like ({mem{,p}cpy,__mem{,p}cpy_chk}) call.
If strlen of the second argument is known and length of the third argument
is that plus one, strlen of the first argument is the same after this
@@ -2513,6 +2889,19 @@ strlen_optimize_stmt (gimple_stmt_iterator *gsi)
case BUILT_IN_STPCPY_CHK_CHKP:
handle_builtin_strcpy (DECL_FUNCTION_CODE (callee), gsi);
break;
+
+ case BUILT_IN_STRNCAT:
+ case BUILT_IN_STRNCAT_CHK:
+ handle_builtin_strncat (DECL_FUNCTION_CODE (callee), gsi);
+ break;
+
+ case BUILT_IN_STPNCPY:
+ case BUILT_IN_STPNCPY_CHK:
+ case BUILT_IN_STRNCPY:
+ case BUILT_IN_STRNCPY_CHK:
+ handle_builtin_stxncpy (DECL_FUNCTION_CODE (callee), gsi);
+ break;
+
case BUILT_IN_MEMCPY:
case BUILT_IN_MEMCPY_CHK:
case BUILT_IN_MEMPCPY:
@@ -2576,6 +2965,10 @@ strlen_optimize_stmt (gimple_stmt_iterator *gsi)
else if (code == EQ_EXPR || code == NE_EXPR)
fold_strstr_to_strncmp (gimple_assign_rhs1 (stmt),
gimple_assign_rhs2 (stmt), stmt);
+
+ tree rhs1 = gimple_assign_rhs1 (stmt);
+ if (stridx_strlenloc *ps = strlen_to_stridx.get (rhs1))
+ strlen_to_stridx.put (lhs, *ps);
}
else if (TREE_CODE (lhs) != SSA_NAME && !TREE_SIDE_EFFECTS (lhs))
{
@@ -2827,6 +3220,8 @@ pass_strlen::execute (function *fun)
laststmt.len = NULL_TREE;
laststmt.stridx = 0;
+ strlen_to_stridx.empty ();
+
return 0;
}
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 3/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-11-10 0:17 ` Martin Sebor
@ 2017-11-10 0:31 ` Jeff Law
0 siblings, 0 replies; 39+ messages in thread
From: Jeff Law @ 2017-11-10 0:31 UTC (permalink / raw)
To: Martin Sebor, Gcc Patch List
On 11/09/2017 04:44 PM, Martin Sebor wrote:
> Attached is another update to the patch with the above.  While
> going through it again I noticed a couple of opportunities to
> handle ranges so I added those as well.  I have retested the
> patch with GDB and Glibc.  They both require minor changes and
> I will submit patches with those once this work is checked in.
>
> Is this good enough to commit now?
>
> Martin
>
> gcc-81117.diff
>
>
> PR c/81117 - Improve buffer overflow checking in strncpy
>
> ada/ChangeLog:
> PR c/81117
> * adadecode.c (__gnat_decode): Use memcpy instead of strncpy.
> * argv.c (__gnat_fill_env): Same.
>
> fortran/ChangeLog:
> PR c/81117
> * decl.c (build_sym): Use memcpy instead of strncpy.
>
> gcc/ChangeLog:
>
> PR c/81117
> * builtins.c (compute_objsize): Handle arrays that
> compute_builtin_object_size likes to fail for. Make extern.
> * builtins.h (compute_objsize): Declare.
> (check_strncpy_sizes): New function.
> (expand_builtin_strncpy): Call check_strncpy_sizes.
> * gimple-fold.c (gimple_fold_builtin_strncpy): Implement
> -Wstringop-truncation.
> (gimple_fold_builtin_strncat): Same.
> * gimple.c (gimple_build_call_from_tree): Set call location.
> * tree-ssa-strlen.c (strlen_to_stridx): New global variable.
> (maybe_diag_bound_equal_length, is_strlen_related_p): New functions.
> (handle_builtin_stxncpy, handle_builtin_strncat): Same.
> (handle_builtin_strlen): Use strlen_to_stridx.
> (strlen_optimize_stmt): Handle flavors of strncat, strncpy, and
> stpncpy.
> Use strlen_to_stridx.
> (pass_strlen::execute): Release strlen_to_stridx.
> * doc/invoke.texi (-Wsizeof-pointer-memaccess): Document enhancement.
> (-Wstringop-truncation): Document new option.
>
> gcc/c-family/ChangeLog:
>
> PR c/81117
> * c-common.c (catenate_strings): Use memcpy instead of strncpy.
> * c-warn.c (sizeof_pointer_memaccess_warning): Handle arrays.
> * c.opt (-Wstringop-truncation): New option.
>
> gcc/objc/ChangeLog:
>
> PR c/81117
> * objc-encoding.c (encode_type): Use memcpy instead of strncpy.
>
> gcc/testsuite/ChangeLog:
>
> PR c/81117
> * c-c++-common/Wsizeof-pointer-memaccess3.c: New test.
> * c-c++-common/Wstringop-overflow.c: Same.
> * c-c++-common/Wstringop-truncation.c: Same.
> * c-c++-common/Wsizeof-pointer-memaccess2.c: Adjust.
> * c-c++-common/attr-nonstring-2.c: New test.
> * g++.dg/torture/Wsizeof-pointer-memaccess1.C: Adjust.
> * g++.dg/torture/Wsizeof-pointer-memaccess2.C: Same.
> * gcc.dg/torture/pr63554.c: Same.
> * gcc.dg/Walloca-1.c: Disable macro tracking.
OK. Sorry for the long review cycles on this one.
jeff
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 2/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-08-06 20:07 ` [PATCH 2/4] " Martin Sebor
2017-08-10 6:39 ` Jeff Law
@ 2017-11-10 23:03 ` Marc Glisse
2017-11-11 21:10 ` Martin Sebor
1 sibling, 1 reply; 39+ messages in thread
From: Marc Glisse @ 2017-11-10 23:03 UTC (permalink / raw)
To: Martin Sebor; +Cc: Jeff Law, Gcc Patch List
On Sun, 6 Aug 2017, Martin Sebor wrote:
> +@item nonstring (@var{nonstring})
Hello,
what is the "(@var{nonstring})" for? This attribute does not seem to take
any argument...
--
Marc Glisse
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 2/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117)
2017-11-10 23:03 ` Marc Glisse
@ 2017-11-11 21:10 ` Martin Sebor
0 siblings, 0 replies; 39+ messages in thread
From: Martin Sebor @ 2017-11-11 21:10 UTC (permalink / raw)
To: gcc-patches; +Cc: Jeff Law
On 11/10/2017 03:52 PM, Marc Glisse wrote:
> On Sun, 6 Aug 2017, Martin Sebor wrote:
>
>> +@item nonstring (@var{nonstring})
>
> Hello,
>
> what is the "(@var{nonstring})" for? This attribute does not seem to
> take any argument...
It's a copy and paste typo. I removed it in r254659.
Thanks for pointing it out.
Martin
^ permalink raw reply [flat|nested] 39+ messages in thread
* [testsuite, committed] Require alloca for c-c++-common/Wstringop-truncation.c
2017-08-06 20:07 ` [PATCH 3/4] " Martin Sebor
2017-08-10 7:17 ` Jeff Law
@ 2017-11-14 9:24 ` Tom de Vries
2017-11-15 15:30 ` [testsuite, committed] Compile strncpy-fix-1.c with -Wno-stringop-truncation Tom de Vries
2 siblings, 0 replies; 39+ messages in thread
From: Tom de Vries @ 2017-11-14 9:24 UTC (permalink / raw)
To: Martin Sebor, Jeff Law, Gcc Patch List
[-- Attachment #1: Type: text/plain, Size: 453 bytes --]
[ was: Re: [PATCH 3/4] enhance overflow and truncation detection in
strncpy and strncat (PR 81117) ]
On 08/06/2017 10:07 PM, Martin Sebor wrote:
> * c-c++-common/Wstringop-truncation.c: Same.
> +/* Verify warnings for VLAs. */
> +
> +void test_strncpy_vla (unsigned n, const char* s)
> +{
> + char vla[n];
Hi,
this patch requires effective target alloca for test-case
c-c++-common/Wstringop-truncation.c.
Committed as obvious.
Thanks,
- Tom
[-- Attachment #2: 0001-Require-alloca-for-c-c-common-Wstringop-truncation.c.patch --]
[-- Type: text/x-patch, Size: 933 bytes --]
Require alloca for c-c++-common/Wstringop-truncation.c
2017-11-14 Tom de Vries <tom@codesourcery.com>
* c-c++-common/Wstringop-truncation.c: Require effective target alloca.
---
gcc/testsuite/c-c++-common/Wstringop-truncation.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/gcc/testsuite/c-c++-common/Wstringop-truncation.c b/gcc/testsuite/c-c++-common/Wstringop-truncation.c
index c536a13..7fc439f 100644
--- a/gcc/testsuite/c-c++-common/Wstringop-truncation.c
+++ b/gcc/testsuite/c-c++-common/Wstringop-truncation.c
@@ -1,6 +1,7 @@
/* PR middle-end/81117 - Improve buffer overflow checking in strncpy
{ dg-do compile }
- { dg-options "-O2 -Wstringop-truncation -Wno-stringop-overflow -ftrack-macro-expansion=0" } */
+ { dg-options "-O2 -Wstringop-truncation -Wno-stringop-overflow -ftrack-macro-expansion=0" }
+ { dg-require-effective-target alloca } */
typedef __SIZE_TYPE__ size_t;
^ permalink raw reply [flat|nested] 39+ messages in thread
* [testsuite, committed] Compile strncpy-fix-1.c with -Wno-stringop-truncation
2017-08-06 20:07 ` [PATCH 3/4] " Martin Sebor
2017-08-10 7:17 ` Jeff Law
2017-11-14 9:24 ` [testsuite, committed] Require alloca for c-c++-common/Wstringop-truncation.c Tom de Vries
@ 2017-11-15 15:30 ` Tom de Vries
2017-11-15 15:58 ` Martin Sebor
2 siblings, 1 reply; 39+ messages in thread
From: Tom de Vries @ 2017-11-15 15:30 UTC (permalink / raw)
To: Martin Sebor, Jeff Law, Gcc Patch List
[-- Attachment #1: Type: text/plain, Size: 1880 bytes --]
[ Re: [PATCH 3/4] enhance overflow and truncation detection in strncpy
and strncat (PR 81117) ]
On 08/06/2017 10:07 PM, Martin Sebor wrote:
> Part 3 of the series contains the meat of the patch: the new
> -Wstringop-truncation option, and enhancements to -Wstringop-
> overflow, and -Wpointer-sizeof-memaccess to detect misuses of
> strncpy and strncat.
>
> Martin
>
> gcc-81117-3.diff
>
>
> PR c/81117 - Improve buffer overflow checking in strncpy
>
> gcc/testsuite/ChangeLog:
>
> PR c/81117
> * c-c++-common/Wsizeof-pointer-memaccess3.c: New test.
> * c-c++-common/Wstringop-overflow.c: Same.
> * c-c++-common/Wstringop-truncation.c: Same.
> * c-c++-common/Wsizeof-pointer-memaccess2.c: Adjust.
> * c-c++-common/attr-nonstring-2.c: New test.
> * g++.dg/torture/Wsizeof-pointer-memaccess1.C: Adjust.
> * g++.dg/torture/Wsizeof-pointer-memaccess2.C: Same.
> * gcc.dg/torture/pr63554.c: Same.
> * gcc.dg/Walloca-1.c: Disable macro tracking.
>
Hi,
this also caused a regression in strncpy-fix-1.c. I noticed it for nvptx
(but I also saw it in other test results, f.i. for
x86_64-unknown-freebsd12.0 at
https://gcc.gnu.org/ml/gcc-testresults/2017-11/msg01276.html ).
On linux you don't see this unless you add -Wsystem-headers:
...
$ gcc src/gcc/testsuite/gcc.dg/strncpy-fix-1.c
-fno-diagnostics-show-caret -fdiagnostics-color=never -O2 -Wall
-Wsystem-headers -S -o strncpy-fix-1.s
In file included from /usr/include/string.h:630,
from src/gcc/testsuite/gcc.dg/strncpy-fix-1.c:6:
src/gcc/testsuite/gcc.dg/strncpy-fix-1.c: In function âfâ:
src/gcc/testsuite/gcc.dg/strncpy-fix-1.c:10:3: warning:
â__builtin_strncpyâ output truncated before terminating nul copying 2
bytes from a string of the same length [-Wstringop-truncation]
...
Fixed by adding -Wno-stringop-truncation.
Committed as obvious.
Thanks,
- Tom
[-- Attachment #2: 0001-Compile-strncpy-fix-1.c-with-Wno-stringop-truncation.patch --]
[-- Type: text/x-patch, Size: 738 bytes --]
Compile strncpy-fix-1.c with -Wno-stringop-truncation
2017-11-15 Tom de Vries <tom@codesourcery.com>
* gcc.dg/strncpy-fix-1.c: Add -Wno-stringop-truncation to dg-options.
---
gcc/testsuite/gcc.dg/strncpy-fix-1.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/gcc/testsuite/gcc.dg/strncpy-fix-1.c b/gcc/testsuite/gcc.dg/strncpy-fix-1.c
index b8bc916..b4fd4aa 100644
--- a/gcc/testsuite/gcc.dg/strncpy-fix-1.c
+++ b/gcc/testsuite/gcc.dg/strncpy-fix-1.c
@@ -1,7 +1,7 @@
/* Test that use of strncpy does not result in a "value computed is
not used" warning. */
/* { dg-do compile } */
-/* { dg-options "-O2 -Wall" } */
+/* { dg-options "-O2 -Wall -Wno-stringop-truncation" } */
#include <string.h>
void
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [testsuite, committed] Compile strncpy-fix-1.c with -Wno-stringop-truncation
2017-11-15 15:30 ` [testsuite, committed] Compile strncpy-fix-1.c with -Wno-stringop-truncation Tom de Vries
@ 2017-11-15 15:58 ` Martin Sebor
0 siblings, 0 replies; 39+ messages in thread
From: Martin Sebor @ 2017-11-15 15:58 UTC (permalink / raw)
To: Tom de Vries, Jeff Law, Gcc Patch List
On 11/15/2017 08:12 AM, Tom de Vries wrote:
> [ Re: [PATCH 3/4] enhance overflow and truncation detection in strncpy
> and strncat (PR 81117) ]
>
> On 08/06/2017 10:07 PM, Martin Sebor wrote:
>> Part 3 of the series contains the meat of the patch: the new
>> -Wstringop-truncation option, and enhancements to -Wstringop-
>> overflow, and -Wpointer-sizeof-memaccess to detect misuses of
>> strncpy and strncat.
>>
>> Martin
>>
>> gcc-81117-3.diff
>>
>>
>> PR c/81117 - Improve buffer overflow checking in strncpy
>>
>
>> gcc/testsuite/ChangeLog:
>>
>> PR c/81117
>> * c-c++-common/Wsizeof-pointer-memaccess3.c: New test.
>> * c-c++-common/Wstringop-overflow.c: Same.
>> * c-c++-common/Wstringop-truncation.c: Same.
>> * c-c++-common/Wsizeof-pointer-memaccess2.c: Adjust.
>> * c-c++-common/attr-nonstring-2.c: New test.
>> * g++.dg/torture/Wsizeof-pointer-memaccess1.C: Adjust.
>> * g++.dg/torture/Wsizeof-pointer-memaccess2.C: Same.
>> * gcc.dg/torture/pr63554.c: Same.
>> * gcc.dg/Walloca-1.c: Disable macro tracking.
>>
>
> Hi,
>
> this also caused a regression in strncpy-fix-1.c. I noticed it for nvptx
> (but I also saw it in other test results, f.i. for
> x86_64-unknown-freebsd12.0 at
> https://gcc.gnu.org/ml/gcc-testresults/2017-11/msg01276.html ).
>
> On linux you don't see this unless you add -Wsystem-headers:
Yes, some Glibc versions (I think 2.24 and prior) define strncpy
as a macro. The macro has been removed from newer versions, which
makes the warning show up inconsistently. I test on Fedora 25 with
the older Glibc so I don't see all these warnings.
I'm tracking the problem bug 82944.
> ...
> $ gcc src/gcc/testsuite/gcc.dg/strncpy-fix-1.c
> -fno-diagnostics-show-caret -fdiagnostics-color=never -O2 -Wall
> -Wsystem-headers -S -o strncpy-fix-1.s
> In file included from /usr/include/string.h:630,
> from src/gcc/testsuite/gcc.dg/strncpy-fix-1.c:6:
> src/gcc/testsuite/gcc.dg/strncpy-fix-1.c: In function âfâ:
> src/gcc/testsuite/gcc.dg/strncpy-fix-1.c:10:3: warning:
> â__builtin_strncpyâ output truncated before terminating nul copying 2
> bytes from a string of the same length [-Wstringop-truncation]
> ...
>
> Fixed by adding -Wno-stringop-truncation.
>
> Committed as obvious.
Thanks
Martin
^ permalink raw reply [flat|nested] 39+ messages in thread
end of thread, other threads:[~2017-11-15 15:43 UTC | newest]
Thread overview: 39+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-07-08 20:45 [PATCH] enhance overflow and truncation detection in strncpy and strncat (PR 81117) Martin Sebor
2017-07-18 2:51 ` [PING] " Martin Sebor
2017-07-25 3:10 ` [PING #2] " Martin Sebor
2017-07-31 17:29 ` Jeff Law
2017-07-31 19:42 ` Martin Sebor
2017-08-02 16:59 ` Jeff Law
2017-08-06 20:07 ` [PATCH 3/4] " Martin Sebor
2017-08-10 7:17 ` Jeff Law
2017-08-10 7:39 ` Richard Biener
2017-08-10 20:21 ` Martin Sebor
2017-08-15 3:06 ` Martin Sebor
2017-08-23 21:11 ` [PING] " Martin Sebor
2017-08-29 5:07 ` [PING 2] " Martin Sebor
2017-09-19 15:44 ` [PING 3] " Martin Sebor
2017-09-26 2:27 ` [PING 4] " Martin Sebor
2017-10-02 22:15 ` Jeff Law
2017-10-21 0:26 ` Martin Sebor
2017-11-04 3:49 ` Jeff Law
2017-11-10 0:17 ` Martin Sebor
2017-11-10 0:31 ` Jeff Law
2017-11-14 9:24 ` [testsuite, committed] Require alloca for c-c++-common/Wstringop-truncation.c Tom de Vries
2017-11-15 15:30 ` [testsuite, committed] Compile strncpy-fix-1.c with -Wno-stringop-truncation Tom de Vries
2017-11-15 15:58 ` Martin Sebor
2017-08-06 20:07 ` [PATCH 4/4] enhance overflow and truncation detection in strncpy and strncat (PR 81117) Martin Sebor
2017-08-06 20:07 ` [PATCH] " Martin Sebor
2017-08-09 19:21 ` Jeff Law
2017-08-06 20:07 ` [PATCH 2/4] " Martin Sebor
2017-08-10 6:39 ` Jeff Law
2017-08-14 18:04 ` Martin Sebor
2017-08-14 18:29 ` Joseph Myers
2017-08-14 19:26 ` Martin Sebor
2017-08-14 20:41 ` Joseph Myers
2017-08-14 20:44 ` Martin Sebor
2017-08-15 3:03 ` Joseph Myers
2017-11-10 23:03 ` Marc Glisse
2017-11-11 21:10 ` Martin Sebor
2017-08-06 20:07 ` [PATCH 1/4] " Martin Sebor
2017-08-10 5:02 ` Jeff Law
2017-08-14 19:21 ` Martin Sebor
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).