static rtx expand_builtin_memmove (tree, rtx);
static rtx expand_builtin_mempcpy (tree, rtx);
static rtx expand_builtin_mempcpy_args (tree, tree, tree, rtx, tree, memop_ret);
-static rtx expand_builtin_strcat (tree, rtx);
+static rtx expand_builtin_strcat (tree);
static rtx expand_builtin_strcpy (tree, rtx);
static rtx expand_builtin_strcpy_args (tree, tree, tree, rtx);
static rtx expand_builtin_stpcpy (tree, rtx, machine_mode);
static tree fold_builtin_abs (location_t, tree, tree);
static tree fold_builtin_unordered_cmp (location_t, tree, tree, tree, enum tree_code,
enum tree_code);
-static tree fold_builtin_0 (location_t, tree);
-static tree fold_builtin_1 (location_t, tree, tree);
-static tree fold_builtin_2 (location_t, tree, tree, tree);
-static tree fold_builtin_3 (location_t, tree, tree, tree, tree);
static tree fold_builtin_varargs (location_t, tree, tree*, int);
-static tree fold_builtin_strpbrk (location_t, tree, tree, tree);
-static tree fold_builtin_strspn (location_t, tree, tree);
-static tree fold_builtin_strcspn (location_t, tree, tree);
+static tree fold_builtin_strpbrk (location_t, tree, tree, tree, tree);
+static tree fold_builtin_strspn (location_t, tree, tree, tree);
+static tree fold_builtin_strcspn (location_t, tree, tree, tree);
static rtx expand_builtin_object_size (tree);
static rtx expand_builtin_memory_chk (tree, rtx, machine_mode,
}
}
+/* For a call EXPR (which may be null) that expects a string argument
+ and SRC as the argument, returns false if SRC is a character array
+ with no terminating NUL. When nonnull, BOUND is the number of
+ characters in which to expect the terminating NUL.
+ When EXPR is nonnull also issues a warning. */
+
+bool
+check_nul_terminated_array (tree expr, tree src, tree bound /* = NULL_TREE */)
+{
+ tree size;
+ bool exact;
+ tree nonstr = unterminated_array (src, &size, &exact);
+ if (!nonstr)
+ return true;
+
+ /* NONSTR refers to the non-nul terminated constant array and SIZE
+ is the constant size of the array in bytes. EXACT is true when
+ SIZE is exact. */
+
+ if (bound)
+ {
+ wide_int min, max;
+ if (TREE_CODE (bound) == INTEGER_CST)
+ min = max = wi::to_wide (bound);
+ else
+ {
+ value_range_kind rng = get_range_info (bound, &min, &max);
+ if (rng != VR_RANGE)
+ return true;
+ }
+
+ if (wi::leu_p (min, wi::to_wide (size)))
+ return true;
+ }
+
+ if (expr && !TREE_NO_WARNING (expr))
+ {
+ tree fndecl = get_callee_fndecl (expr);
+ const char *fname = IDENTIFIER_POINTER (DECL_NAME (fndecl));
+ warn_string_no_nul (EXPR_LOCATION (expr), fname, src, nonstr);
+ }
+
+ return false;
+}
+
/* If EXP refers to an unterminated constant character array return
the declaration of the object of which the array is a member or
element and if SIZE is not null, set *SIZE to the size of
to the library function. */
static rtx
-expand_builtin_strcat (tree exp, rtx)
+expand_builtin_strcat (tree exp)
{
if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)
|| !warn_stringop_overflow)
tree dest = CALL_EXPR_ARG (exp, 0);
tree src = CALL_EXPR_ARG (exp, 1);
+ /* Detect unterminated source (only). */
+ if (!check_nul_terminated_array (exp, src))
+ return NULL_RTX;
+
/* There is no way here to determine the length of the string in
the destination to which the SRC string is being appended so
just diagnose cases when the souce string is longer than
/* The exact number of bytes to write (not the maximum). */
tree len = CALL_EXPR_ARG (exp, 2);
+ if (!check_nul_terminated_array (exp, src, len))
+ return NULL_RTX;
/* The size of the destination object. */
tree destsize = compute_objsize (dest, warn_stringop_overflow - 1);
tree src = CALL_EXPR_ARG (exp, 1);
/* The upper bound on the number of bytes to write. */
tree maxread = CALL_EXPR_ARG (exp, 2);
+
+ /* Detect unterminated source (only). */
+ if (!check_nul_terminated_array (exp, src, maxread))
+ return NULL_RTX;
+
/* The length of the source sequence. */
tree slen = c_strlen (src, 1);
{
location_t loc = EXPR_LOCATION (exp);
- if (validate_arglist (exp,
- POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE))
- {
- tree dest = CALL_EXPR_ARG (exp, 0);
- tree src = CALL_EXPR_ARG (exp, 1);
- /* The number of bytes to write (not the maximum). */
- tree len = CALL_EXPR_ARG (exp, 2);
- /* The length of the source sequence. */
- tree slen = c_strlen (src, 1);
+ if (!validate_arglist (exp,
+ POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+ tree dest = CALL_EXPR_ARG (exp, 0);
+ tree src = CALL_EXPR_ARG (exp, 1);
+ /* The number of bytes to write (not the maximum). */
+ tree len = CALL_EXPR_ARG (exp, 2);
- if (warn_stringop_overflow)
- {
- tree destsize = compute_objsize (dest,
- warn_stringop_overflow - 1);
+ if (!check_nul_terminated_array (exp, src, len))
+ return NULL_RTX;
- /* The number of bytes to write is LEN but check_access will also
- check SLEN if LEN's value isn't known. */
- check_access (exp, dest, src, len, /*maxread=*/NULL_TREE, src,
- destsize);
- }
+ /* The length of the source sequence. */
+ tree slen = c_strlen (src, 1);
- /* 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;
+ if (warn_stringop_overflow)
+ {
+ tree destsize = compute_objsize (dest,
+ warn_stringop_overflow - 1);
- slen = size_binop_loc (loc, PLUS_EXPR, slen, ssize_int (1));
+ /* The number of bytes to write is LEN but check_access will also
+ check SLEN if LEN's value isn't known. */
+ check_access (exp, dest, src, len, /*maxread=*/NULL_TREE, src,
+ destsize);
+ }
- /* We're required to pad with trailing zeros if the requested
- len is greater than strlen(s2)+1. In that case try to
- use store_by_pieces, if it fails, punt. */
- if (tree_int_cst_lt (slen, len))
- {
- unsigned int dest_align = get_pointer_alignment (dest);
- const char *p = c_getstr (src);
- rtx dest_mem;
-
- if (!p || dest_align == 0 || !tree_fits_uhwi_p (len)
- || !can_store_by_pieces (tree_to_uhwi (len),
- builtin_strncpy_read_str,
- CONST_CAST (char *, p),
- dest_align, false))
- return NULL_RTX;
+ /* 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;
- dest_mem = get_memory_rtx (dest, len);
- store_by_pieces (dest_mem, tree_to_uhwi (len),
- builtin_strncpy_read_str,
- CONST_CAST (char *, p), dest_align, false,
- RETURN_BEGIN);
- dest_mem = force_operand (XEXP (dest_mem, 0), target);
- dest_mem = convert_memory_address (ptr_mode, dest_mem);
- return dest_mem;
- }
+ slen = size_binop_loc (loc, PLUS_EXPR, slen, ssize_int (1));
+
+ /* We're required to pad with trailing zeros if the requested
+ len is greater than strlen(s2)+1. In that case try to
+ use store_by_pieces, if it fails, punt. */
+ if (tree_int_cst_lt (slen, len))
+ {
+ unsigned int dest_align = get_pointer_alignment (dest);
+ const char *p = c_getstr (src);
+ rtx dest_mem;
+
+ if (!p || dest_align == 0 || !tree_fits_uhwi_p (len)
+ || !can_store_by_pieces (tree_to_uhwi (len),
+ builtin_strncpy_read_str,
+ CONST_CAST (char *, p),
+ dest_align, false))
+ return NULL_RTX;
+
+ dest_mem = get_memory_rtx (dest, len);
+ store_by_pieces (dest_mem, tree_to_uhwi (len),
+ builtin_strncpy_read_str,
+ CONST_CAST (char *, p), dest_align, false,
+ RETURN_BEGIN);
+ dest_mem = force_operand (XEXP (dest_mem, 0), target);
+ dest_mem = convert_memory_address (ptr_mode, dest_mem);
+ return dest_mem;
}
+
return NULL_RTX;
}
if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE))
return NULL_RTX;
+ tree arg1 = CALL_EXPR_ARG (exp, 0);
+ tree arg2 = CALL_EXPR_ARG (exp, 1);
+
+ if (!check_nul_terminated_array (exp, arg1)
+ || !check_nul_terminated_array (exp, arg2))
+ return NULL_RTX;
+
/* Due to the performance benefit, always inline the calls first. */
rtx result = NULL_RTX;
result = inline_expand_builtin_string_cmp (exp, target);
if (cmpstr_icode == CODE_FOR_nothing && cmpstrn_icode == CODE_FOR_nothing)
return NULL_RTX;
- tree arg1 = CALL_EXPR_ARG (exp, 0);
- tree arg2 = CALL_EXPR_ARG (exp, 1);
-
unsigned int arg1_align = get_pointer_alignment (arg1) / BITS_PER_UNIT;
unsigned int arg2_align = get_pointer_alignment (arg2) / BITS_PER_UNIT;
POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE))
return NULL_RTX;
+ tree arg1 = CALL_EXPR_ARG (exp, 0);
+ tree arg2 = CALL_EXPR_ARG (exp, 1);
+ tree arg3 = CALL_EXPR_ARG (exp, 2);
+
+ if (!check_nul_terminated_array (exp, arg1, arg3)
+ || !check_nul_terminated_array (exp, arg2, arg3))
+ return NULL_RTX;
+
/* Due to the performance benefit, always inline the calls first. */
rtx result = NULL_RTX;
result = inline_expand_builtin_string_cmp (exp, target);
tree len;
- tree arg1 = CALL_EXPR_ARG (exp, 0);
- tree arg2 = CALL_EXPR_ARG (exp, 1);
- tree arg3 = CALL_EXPR_ARG (exp, 2);
-
unsigned int arg1_align = get_pointer_alignment (arg1) / BITS_PER_UNIT;
unsigned int arg2_align = get_pointer_alignment (arg2) / BITS_PER_UNIT;
tree id, decl;
tree call;
+ if (DECL_FUNCTION_CODE (fn) != BUILT_IN_FORK)
+ {
+ /* Detect unterminated path. */
+ if (!check_nul_terminated_array (exp, CALL_EXPR_ARG (exp, 0)))
+ return NULL_RTX;
+
+ /* Also detect unterminated first argument. */
+ switch (DECL_FUNCTION_CODE (fn))
+ {
+ case BUILT_IN_EXECL:
+ case BUILT_IN_EXECLE:
+ case BUILT_IN_EXECLP:
+ if (!check_nul_terminated_array (exp, CALL_EXPR_ARG (exp, 0)))
+ return NULL_RTX;
+ default:
+ break;
+ }
+ }
+
+
/* If we are not profiling, just call the function. */
if (!profile_arc_flag)
return NULL_RTX;
break;
case BUILT_IN_STRCAT:
- target = expand_builtin_strcat (exp, target);
+ target = expand_builtin_strcat (exp);
if (target)
return target;
break;
+ case BUILT_IN_GETTEXT:
+ case BUILT_IN_PUTS:
+ case BUILT_IN_PUTS_UNLOCKED:
+ case BUILT_IN_STRDUP:
+ if (validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ check_nul_terminated_array (exp, CALL_EXPR_ARG (exp, 0));
+ break;
+
+ case BUILT_IN_INDEX:
+ case BUILT_IN_RINDEX:
+ case BUILT_IN_STRCHR:
+ case BUILT_IN_STRRCHR:
+ if (validate_arglist (exp, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE))
+ check_nul_terminated_array (exp, CALL_EXPR_ARG (exp, 0));
+ break;
+
+ case BUILT_IN_FPUTS:
+ case BUILT_IN_FPUTS_UNLOCKED:
+ if (validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE))
+ check_nul_terminated_array (exp, CALL_EXPR_ARG (exp, 0));
+ break;
+
+ case BUILT_IN_STRNDUP:
+ if (validate_arglist (exp, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE))
+ check_nul_terminated_array (exp,
+ CALL_EXPR_ARG (exp, 0),
+ CALL_EXPR_ARG (exp, 1));
+ break;
+
+ case BUILT_IN_STRCASECMP:
+ case BUILT_IN_STRSTR:
+ if (validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE))
+ {
+ check_nul_terminated_array (exp, CALL_EXPR_ARG (exp, 0));
+ check_nul_terminated_array (exp, CALL_EXPR_ARG (exp, 1));
+ }
+ break;
+
case BUILT_IN_STRCPY:
target = expand_builtin_strcpy (exp, target);
if (target)
}
-/* Fold a call to built-in function FNDECL with 2 arguments, ARG0 and ARG1.
- This function returns NULL_TREE if no simplification was possible. */
+/* Folds a call EXPR (which may be null) to built-in function FNDECL
+ with 2 arguments, ARG0 and ARG1. This function returns NULL_TREE
+ if no simplification was possible. */
static tree
-fold_builtin_2 (location_t loc, tree fndecl, tree arg0, tree arg1)
+fold_builtin_2 (location_t loc, tree expr, tree fndecl, tree arg0, tree arg1)
{
tree type = TREE_TYPE (TREE_TYPE (fndecl));
enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl);
return fold_builtin_modf (loc, arg0, arg1, type);
case BUILT_IN_STRSPN:
- return fold_builtin_strspn (loc, arg0, arg1);
+ return fold_builtin_strspn (loc, expr, arg0, arg1);
case BUILT_IN_STRCSPN:
- return fold_builtin_strcspn (loc, arg0, arg1);
+ return fold_builtin_strcspn (loc, expr, arg0, arg1);
case BUILT_IN_STRPBRK:
- return fold_builtin_strpbrk (loc, arg0, arg1, type);
+ return fold_builtin_strpbrk (loc, expr, arg0, arg1, type);
case BUILT_IN_EXPECT:
return fold_builtin_expect (loc, arg0, arg1, NULL_TREE, NULL_TREE);
return NULL_TREE;
}
-/* Fold a call to built-in function FNDECL. ARGS is an array of NARGS
- arguments. IGNORE is true if the result of the
- function call is ignored. This function returns NULL_TREE if no
- simplification was possible. */
+/* Folds a call EXPR (which may be null) to built-in function FNDECL.
+ ARGS is an array of NARGS arguments. IGNORE is true if the result
+ of the function call is ignored. This function returns NULL_TREE
+ if no simplification was possible. */
-tree
-fold_builtin_n (location_t loc, tree fndecl, tree *args, int nargs, bool)
+static tree
+fold_builtin_n (location_t loc, tree expr, tree fndecl, tree *args,
+ int nargs, bool)
{
tree ret = NULL_TREE;
ret = fold_builtin_1 (loc, fndecl, args[0]);
break;
case 2:
- ret = fold_builtin_2 (loc, fndecl, args[0], args[1]);
+ ret = fold_builtin_2 (loc, expr, fndecl, args[0], args[1]);
break;
case 3:
ret = fold_builtin_3 (loc, fndecl, args[0], args[1], args[2]);
else
{
tree *args = CALL_EXPR_ARGP (exp);
- ret = fold_builtin_n (loc, fndecl, args, nargs, ignore);
+ ret = fold_builtin_n (loc, exp, fndecl, args, nargs, ignore);
if (ret)
return ret;
}
if (DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_MD)
return targetm.fold_builtin (fndecl, n, argarray, false);
else
- return fold_builtin_n (loc, fndecl, argarray, n, false);
+ return fold_builtin_n (loc, NULL_TREE, fndecl, argarray, n, false);
}
return NULL_TREE;
form of the builtin function call. */
static tree
-fold_builtin_strpbrk (location_t loc, tree s1, tree s2, tree type)
+fold_builtin_strpbrk (location_t loc, tree expr, tree s1, tree s2, tree type)
{
if (!validate_arg (s1, POINTER_TYPE)
|| !validate_arg (s2, POINTER_TYPE))
return NULL_TREE;
- else
- {
- tree fn;
- const char *p1, *p2;
- p2 = c_getstr (s2);
- if (p2 == NULL)
- return NULL_TREE;
+ if (!check_nul_terminated_array (expr, s1)
+ || !check_nul_terminated_array (expr, s2))
+ return NULL_TREE;
- p1 = c_getstr (s1);
- if (p1 != NULL)
- {
- const char *r = strpbrk (p1, p2);
- tree tem;
+ tree fn;
+ const char *p1, *p2;
- if (r == NULL)
- return build_int_cst (TREE_TYPE (s1), 0);
+ p2 = c_getstr (s2);
+ if (p2 == NULL)
+ return NULL_TREE;
- /* Return an offset into the constant string argument. */
- tem = fold_build_pointer_plus_hwi_loc (loc, s1, r - p1);
- return fold_convert_loc (loc, type, tem);
- }
+ p1 = c_getstr (s1);
+ if (p1 != NULL)
+ {
+ const char *r = strpbrk (p1, p2);
+ tree tem;
- if (p2[0] == '\0')
- /* strpbrk(x, "") == NULL.
- Evaluate and ignore s1 in case it had side-effects. */
- return omit_one_operand_loc (loc, type, integer_zero_node, s1);
+ if (r == NULL)
+ return build_int_cst (TREE_TYPE (s1), 0);
- if (p2[1] != '\0')
- return NULL_TREE; /* Really call strpbrk. */
+ /* Return an offset into the constant string argument. */
+ tem = fold_build_pointer_plus_hwi_loc (loc, s1, r - p1);
+ return fold_convert_loc (loc, type, tem);
+ }
- fn = builtin_decl_implicit (BUILT_IN_STRCHR);
- if (!fn)
- return NULL_TREE;
+ if (p2[0] == '\0')
+ /* strpbrk(x, "") == NULL.
+ Evaluate and ignore s1 in case it had side-effects. */
+ return omit_one_operand_loc (loc, type, integer_zero_node, s1);
- /* New argument list transforming strpbrk(s1, s2) to
- strchr(s1, s2[0]). */
- return build_call_expr_loc (loc, fn, 2, s1,
- build_int_cst (integer_type_node, p2[0]));
- }
+ if (p2[1] != '\0')
+ return NULL_TREE; /* Really call strpbrk. */
+
+ fn = builtin_decl_implicit (BUILT_IN_STRCHR);
+ if (!fn)
+ return NULL_TREE;
+
+ /* New argument list transforming strpbrk(s1, s2) to
+ strchr(s1, s2[0]). */
+ return build_call_expr_loc (loc, fn, 2, s1,
+ build_int_cst (integer_type_node, p2[0]));
}
/* Simplify a call to the strspn builtin. S1 and S2 are the arguments
form of the builtin function call. */
static tree
-fold_builtin_strspn (location_t loc, tree s1, tree s2)
+fold_builtin_strspn (location_t loc, tree expr, tree s1, tree s2)
{
if (!validate_arg (s1, POINTER_TYPE)
|| !validate_arg (s2, POINTER_TYPE))
return NULL_TREE;
- else
- {
- const char *p1 = c_getstr (s1), *p2 = c_getstr (s2);
- /* If either argument is "", return NULL_TREE. */
- if ((p1 && *p1 == '\0') || (p2 && *p2 == '\0'))
- /* Evaluate and ignore both arguments in case either one has
- side-effects. */
- return omit_two_operands_loc (loc, size_type_node, size_zero_node,
+ if (!check_nul_terminated_array (expr, s1)
+ || !check_nul_terminated_array (expr, s2))
+ return NULL_TREE;
+
+ const char *p1 = c_getstr (s1), *p2 = c_getstr (s2);
+
+ /* If either argument is "", return NULL_TREE. */
+ if ((p1 && *p1 == '\0') || (p2 && *p2 == '\0'))
+ /* Evaluate and ignore both arguments in case either one has
+ side-effects. */
+ return omit_two_operands_loc (loc, size_type_node, size_zero_node,
s1, s2);
- return NULL_TREE;
- }
+ return NULL_TREE;
}
/* Simplify a call to the strcspn builtin. S1 and S2 are the arguments
form of the builtin function call. */
static tree
-fold_builtin_strcspn (location_t loc, tree s1, tree s2)
+fold_builtin_strcspn (location_t loc, tree expr, tree s1, tree s2)
{
if (!validate_arg (s1, POINTER_TYPE)
|| !validate_arg (s2, POINTER_TYPE))
return NULL_TREE;
- else
+
+ if (!check_nul_terminated_array (expr, s1)
+ || !check_nul_terminated_array (expr, s2))
+ return NULL_TREE;
+
+ /* If the first argument is "", return NULL_TREE. */
+ const char *p1 = c_getstr (s1);
+ if (p1 && *p1 == '\0')
{
- /* If the first argument is "", return NULL_TREE. */
- const char *p1 = c_getstr (s1);
- if (p1 && *p1 == '\0')
- {
- /* Evaluate and ignore argument s2 in case it has
- side-effects. */
- return omit_one_operand_loc (loc, size_type_node,
+ /* Evaluate and ignore argument s2 in case it has
+ side-effects. */
+ return omit_one_operand_loc (loc, size_type_node,
size_zero_node, s2);
- }
+ }
- /* If the second argument is "", return __builtin_strlen(s1). */
- const char *p2 = c_getstr (s2);
- if (p2 && *p2 == '\0')
- {
- tree fn = builtin_decl_implicit (BUILT_IN_STRLEN);
+ /* If the second argument is "", return __builtin_strlen(s1). */
+ const char *p2 = c_getstr (s2);
+ if (p2 && *p2 == '\0')
+ {
+ tree fn = builtin_decl_implicit (BUILT_IN_STRLEN);
- /* If the replacement _DECL isn't initialized, don't do the
- transformation. */
- if (!fn)
- return NULL_TREE;
+ /* If the replacement _DECL isn't initialized, don't do the
+ transformation. */
+ if (!fn)
+ return NULL_TREE;
- return build_call_expr_loc (loc, fn, 1, s1);
- }
- return NULL_TREE;
+ return build_call_expr_loc (loc, fn, 1, s1);
}
+ return NULL_TREE;
}
/* Fold the next_arg or va_start call EXP. Returns true if there was an error
}
else
{
- ret = fold_builtin_n (loc, fndecl, args, nargs, ignore);
+ ret = fold_builtin_n (loc, NULL_TREE, fndecl, args, nargs, ignore);
if (ret)
{
/* Propagate location information from original call to
--- /dev/null
+/* { dg-do compile }
+ { dg-options "-O2 -Wall -Wno-stringop-truncation -ftrack-macro-expansion=0" } */
+
+#define NULL (void*)0
+
+const char a[] = { 'a', 'b', 'c', 'd' };
+const char b[] = { 'a', '\0', 'c', '\0', 'e' };
+
+#define CONCAT(a, b) a ## b
+#define CAT(a, b) CONCAT (a, b)
+
+typedef struct FILE FILE;
+extern FILE *fp;
+
+extern char *d;
+extern const char *s;
+extern int n;
+
+#define T(func, ...) \
+ __attribute__ ((noipa)) void \
+ CAT (test_ ## func, __LINE__) (void) \
+ { \
+ sink (0, __builtin_ ## func (__VA_ARGS__), d, s, n); \
+ } typedef void dummy_type
+
+void sink (void*, ...);
+
+
+// Exercise string functions.
+T (index, a, 'x'); // { dg-warning "missing terminating nul" "index" }
+T (index, a, *s); // { dg-warning "missing terminating nul" "index" }
+
+T (index, b, '0');
+T (index, b + 1, '1');
+T (index, b + 2, '2');
+T (index, b + 3, '3');
+T (index, b + 4, '4'); // { dg-warning "missing terminating nul" "index" }
+
+T (rindex, a, 'x'); // { dg-warning "missing terminating nul" "rindex" }
+T (rindex, a, *s); // { dg-warning "missing terminating nul" "rindex" }
+
+T (rindex, b, '0');
+T (rindex, b + 1, '1');
+T (rindex, b + 2, '2');
+T (rindex, b + 3, '3');
+T (rindex, b + 4, '4'); // { dg-warning "missing terminating nul" "rindex" }
+
+T (stpcpy, d, a); // { dg-warning "missing terminating nul" "stpcpy" }
+
+T (stpncpy, d, a, 4);
+T (stpncpy, d, a, 5); // { dg-warning "missing terminating nul" "stpncpy" }
+T (stpncpy, d, a, n);
+
+T (stpncpy, d, a + n, 4);
+T (stpncpy, d, a + n, 5); // { dg-warning "missing terminating nul" "stpncpy" }
+
+T (stpncpy, d, b, 4);
+T (stpncpy, d, b, 5);
+T (stpncpy, d, b, n);
+
+T (stpncpy, d, b + 1, 4);
+T (stpncpy, d, b + 1, 5);
+T (stpncpy, d, b + 1, n);
+
+T (stpncpy, d, b + 3, 4);
+T (stpncpy, d, b + 3, 5);
+T (stpncpy, d, b + 3, n);
+
+T (stpncpy, d, b + 4, 1);
+T (stpncpy, d, b + 4, 2); // { dg-warning "missing terminating nul" "stpncpy" }
+T (stpncpy, d, b + 4, n);
+/* The following might be worth warning about since it's only safe with
+ n < 4. */
+T (stpncpy, d, b + n, 5);
+
+T (strcasecmp, a, "ab"); // { dg-warning "missing terminating nul" "strcasecmp" }
+T (strcasecmp, a, s); // { dg-warning "missing terminating nul" "strcasecmp" }
+T (strcasecmp, a, b); // { dg-warning "missing terminating nul" "strcasecmp" }
+T (strcasecmp, b, b + 1);
+T (strcasecmp, b, b + 2);
+T (strcasecmp, b, b + 3);
+T (strcasecmp, b, b + 4); // { dg-warning "missing terminating nul" "strcasecmp" }
+
+T (strcat, d, a); // { dg-warning "missing terminating nul" "strcat" }
+
+T (strncat, d, a, 4);
+T (strncat, d, a, 5); // { dg-warning "missing terminating nul" "strncat" }
+T (strncat, d, a, n);
+
+T (strncat, d, b, n);
+T (strncat, d, b + 1, n);
+T (strncat, d, b + 2, n);
+T (strncat, d, b + 3, n);
+T (strncat, d, b + 4, 0);
+T (strncat, d, b + 4, 1);
+T (strncat, d, b + 4, 2); // { dg-warning "missing terminating nul" "strncat" }
+/* The following should probably trigger a warning since it's only safe
+ when n < 2, makes little sense with n == 0, and not much more with
+ n == 1. */
+T (strncat, d, b + 4, n); // { dg-warning "missing terminating nul" "strncat" { xfail *-*-* } }
+
+T (strchr, a, 'x'); // { dg-warning "missing terminating nul" "strchr" }
+T (strchr, a, *s); // { dg-warning "missing terminating nul" "strchr" }
+
+T (strcmp, a, "ab"); // { dg-warning "missing terminating nul" "strcmp" }
+T (strcmp, "bc", a); // { dg-warning "missing terminating nul" "strcmp" }
+T (strcmp, a, s); // { dg-warning "missing terminating nul" "strcmp" }
+T (strcmp, s, a); // { dg-warning "missing terminating nul" "strcmp" }
+
+T (strcmp, a, b); // { dg-warning "missing terminating nul" "strcmp" }
+/* Even though most likely safe in reality because b[1] is nul,
+ the following is strictly undefined because a is not a string.
+ The warning is not issued because GCC folds the call to (int)*a. */
+T (strcmp, a, b + 1); // { dg-warning "missing terminating nul" "bug" { xfail *-*-* } }
+
+T (strncmp, a, "ab", 4);
+T (strncmp, "bc", a, 4);
+T (strncmp, a, a, 4);
+T (strncmp, a, s, 4);
+T (strncmp, s, a, 4);
+
+/* The warning below is not issued because GCC folds strncmp calls with
+ the same arguments to zero before it checks for the missing nul. */
+T (strncmp, a, a, 5); // { dg-warning "missing terminating nul" "pr92624" { xfail *-*-*} }
+T (strncmp, a, s, 5); // { dg-warning "missing terminating nul" "strcmp" }
+T (strncmp, s, a, 5); // { dg-warning "missing terminating nul" "strcmp" }
+
+T (strcpy, d, a); // { dg-warning "missing terminating nul" "strcpy" }
+
+T (strcspn, a, s); // { dg-warning "missing terminating nul" "strcspn" }
+T (strcspn, s, a); // { dg-warning "missing terminating nul" "strcspn" }
+
+T (strspn, a, s); // { dg-warning "missing terminating nul" "strcspn" }
+T (strspn, s, a); // { dg-warning "missing terminating nul" "strcspn" }
+
+T (strdup, a); // { dg-warning "missing terminating nul" "strdup" }
+
+T (strndup, a, 4);
+T (strndup, a, 5); // { dg-warning "missing terminating nul" "strndup" }
+T (strndup, b + 3, 2);
+T (strndup, b + 4, 1);
+T (strndup, b + 4, 2); // { dg-warning "missing terminating nul" "strndup" }
+
+T (strlen, a); // { dg-warning "missing terminating nul" "strlen" }
+
+T (strnlen, a, 4);
+T (strnlen, a, 5); // { dg-warning "specified bound 5 exceeds the size 4 of unterminated array" "strnlen" }
+T (strnlen, a, n);
+
+T (strpbrk, s, a); // { dg-warning "missing terminating nul" "strpbrk" }
+
+T (strrchr, a, 'x'); // { dg-warning "missing terminating nul" "strrchr" }
+T (strrchr, a, *s); // { dg-warning "missing terminating nul" "strrchr" }
+
+T (strstr, a, "cde"); // { dg-warning "missing terminating nul" "strstr" }
+T (strstr, a, s); // { dg-warning "missing terminating nul" "strstr" }
+
+
+// Exercise a few string checking functions.
+T (__stpcpy_chk, d, a, -1); // { dg-warning "missing terminating nul" "stpcpy" }
+
+
+T (__stpncpy_chk, d, a, 4, -1);
+T (__stpncpy_chk, d, a, 5, -1); // { dg-warning "missing terminating nul" "stpncpy_chk" }
+T (__stpncpy_chk, d, a, n, -1);
+
+T (__stpncpy_chk, d, a + n, 4, -1);
+T (__stpncpy_chk, d, a + n, 5, -1); // { dg-warning "missing terminating nul" "stpncpy_chk" }
+
+T (__stpncpy_chk, d, b, 4, -1);
+T (__stpncpy_chk, d, b, 5, -1);
+T (__stpncpy_chk, d, b, n, -1);
+
+T (__stpncpy_chk, d, b + 1, 4, -1);
+T (__stpncpy_chk, d, b + 1, 5, -1);
+T (__stpncpy_chk, d, b + 1, n, -1);
+
+T (__stpncpy_chk, d, b + 3, 4, -1);
+T (__stpncpy_chk, d, b + 3, 5, -1);
+T (__stpncpy_chk, d, b + 3, n, -1);
+
+T (__stpncpy_chk, d, b + 4, 1, -1);
+T (__stpncpy_chk, d, b + 4, 2, -1); // { dg-warning "missing terminating nul" "stpncpy_chk" }
+T (__stpncpy_chk, d, b + 4, n, -1);
+
+
+T (__strncat_chk, d, a, 4, -1);
+T (__strncat_chk, d, a, 5, -1); // { dg-warning "missing terminating nul" "strncat_chk" }
+T (__strncat_chk, d, a, n, -1);
+
+T (__strncat_chk, d, a + n, 4, -1);
+T (__strncat_chk, d, a + n, 5, -1); // { dg-warning "missing terminating nul" "strncat_chk" }
+
+T (__strncat_chk, d, b, 4, -1);
+T (__strncat_chk, d, b, 5, -1);
+T (__strncat_chk, d, b, n, -1);
+
+T (__strncat_chk, d, b + 1, 4, -1);
+T (__strncat_chk, d, b + 1, 5, -1);
+T (__strncat_chk, d, b + 1, n, -1);
+
+T (__strncat_chk, d, b + 3, 4, -1);
+T (__strncat_chk, d, b + 3, 5, -1);
+T (__strncat_chk, d, b + 3, n, -1);
+
+T (__strncat_chk, d, b + 4, 1, -1);
+T (__strncat_chk, d, b + 4, 2, -1); // { dg-warning "missing terminating nul" "strncat_chk" }
+T (__strncat_chk, d, b + 4, n, -1);
+
+
+T (__strncpy_chk, d, a, 4, -1);
+T (__strncpy_chk, d, a, 5, -1); // { dg-warning "missing terminating nul" "strncpy_chk" }
+T (__strncpy_chk, d, a, n, -1);
+
+T (__strncpy_chk, d, a + n, 4, -1);
+T (__strncpy_chk, d, a + n, 5, -1); // { dg-warning "missing terminating nul" "strncpy_chk" }
+
+T (__strncpy_chk, d, b, 4, -1);
+T (__strncpy_chk, d, b, 5, -1);
+T (__strncpy_chk, d, b, n, -1);
+
+T (__strncpy_chk, d, b + 1, 4, -1);
+T (__strncpy_chk, d, b + 1, 5, -1);
+T (__strncpy_chk, d, b + 1, n, -1);
+
+T (__strncpy_chk, d, b + 3, 4, -1);
+T (__strncpy_chk, d, b + 3, 5, -1);
+T (__strncpy_chk, d, b + 3, n, -1);
+
+T (__strncpy_chk, d, b + 4, 1, -1);
+T (__strncpy_chk, d, b + 4, 2, -1); // { dg-warning "missing terminating nul" "strncpy" }
+T (__strncpy_chk, d, b + 4, n, -1);
+
+
+// Exercise some stdio functions.
+T (printf, a); // { dg-warning "unterminated format string" "printf" }
+T (printf, "%s", a); // { dg-warning "not a nul-terminated string" "printf" }
+T (sprintf, d, "%s", a); // { dg-warning "not a nul-terminated string" "sprintf" }
+T (snprintf, d, n, "%s", a); // { dg-warning "not a nul-terminated string" "sprintf" }
+
+T (__sprintf_chk, d, 0, -1, "%s", a); // { dg-warning "not a nul-terminated string" "sprintf" }
+T (__snprintf_chk, d, n, 0, -1, "%s", a); // { dg-warning "not a nul-terminated string" "sprintf" }
+
+T (fputs, a, fp); // { dg-warning "missing terminating nul" "fputs" }
+T (fputs_unlocked, a, fp); // { dg-warning "missing terminating nul" "fputs_unlocked" }
+T (puts, a); // { dg-warning "missing terminating nul" "puts" }
+T (puts_unlocked, a); // { dg-warning "missing terminating nul" "puts_unlocked" }
+
+
+
+// Exerise exec functions.
+T (execl, a, s, NULL); // { dg-warning "missing terminating nul" "execl" }
+T (execl, a, s, NULL); // { dg-warning "missing terminating nul" "execl" }
+T (execle, a, s, NULL, NULL); // { dg-warning "missing terminating nul" "execl" }
+T (execlp, a, s, NULL); // { dg-warning "missing terminating nul" "execl" }
+
+T (execv, a, &d); // { dg-warning "missing terminating nul" "execl" }
+T (execve, a, &d, &d); // { dg-warning "missing terminating nul" "execl" }
+T (execvp, a, &d); // { dg-warning "missing terminating nul" "execl" }
+
+T (gettext, a); // { dg-warning "missing terminating nul" "gettext" }
+
+T (strfmon, d, n, a); // { dg-warning "unterminated format string" "strfmon" }